Add support for NCA0 files.

Any operation that is supported with other NCAs is supported NCA0s. This includes reading both encrypted and decrypted NCA0s, and encrypting and decrypting NCA0s.
This commit is contained in:
Alex Barney 2020-08-13 22:09:36 -07:00
parent 6bab1d9273
commit f20337d774
10 changed files with 406 additions and 61 deletions

View File

@ -90,10 +90,10 @@ namespace LibHac
}
}
internal static BigInteger GetBigInteger(byte[] bytes)
internal static BigInteger GetBigInteger(ReadOnlySpan<byte> bytes)
{
var signPadded = new byte[bytes.Length + 1];
Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
bytes.CopyTo(signPadded.AsSpan(1));
Array.Reverse(signPadded);
return new BigInteger(signPadded);
}
@ -142,11 +142,18 @@ namespace LibHac
{
using (var rsa = RSA.Create())
{
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
try
{
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
? Validity.Valid
: Validity.Invalid;
}
catch (CryptographicException)
{
return Validity.Invalid;
}
}
}
@ -154,20 +161,54 @@ namespace LibHac
{
using (var rsa = RSA.Create())
{
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
try
{
rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)
? Validity.Valid
: Validity.Invalid;
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)
? Validity.Valid
: Validity.Invalid;
}
catch (CryptographicException)
{
return Validity.Invalid;
}
}
}
public static byte[] DecryptTitleKey(byte[] titleKeyblock, RSAParameters rsaParams)
public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams)
{
var rsa = RSA.Create();
rsa.ImportParameters(rsaParams);
return rsa.Decrypt(titleKeyblock, RSAEncryptionPadding.OaepSHA256);
return rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256);
}
public static bool DecryptRsaOaep(ReadOnlySpan<byte> data, Span<byte> destination, RSAParameters rsaParams, out int bytesWritten)
{
using (var rsa = RSA.Create())
{
try
{
rsa.ImportParameters(rsaParams);
return rsa.TryDecrypt(data, destination, RSAEncryptionPadding.OaepSHA256, out bytesWritten);
}
catch (CryptographicException)
{
bytesWritten = 0;
return false;
}
}
}
public static RSAParameters RecoverRsaParameters(ReadOnlySpan<byte> modulus, ReadOnlySpan<byte> exponent)
{
BigInteger dInt = GetBigInteger(exponent);
BigInteger nInt = GetBigInteger(modulus);
BigInteger eInt = GetBigInteger(new byte[] { 1, 0, 1 });
return RecoverRsaParameters(nInt, eInt, dInt);
}
private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d)

View File

@ -40,7 +40,7 @@ namespace LibHac.FsSrv.Creators
}
storage = storageTemp;
fsHeader = nca.Header.GetFsHeader(fsIndex);
fsHeader = nca.GetFsHeader(fsIndex);
return Result.Success;
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.RomFs;
@ -14,6 +15,10 @@ namespace LibHac.FsSystem.NcaUtils
{
private Keyset Keyset { get; }
private bool IsEncrypted { get; set; }
private byte[] Nca0KeyArea { get; set; }
private IStorage Nca0TransformedBody { get; set; }
public IStorage BaseStorage { get; }
public NcaHeader Header { get; }
@ -33,6 +38,12 @@ namespace LibHac.FsSystem.NcaUtils
{
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
// Handle old NCA0s that use different key area encryption
if (Header.FormatVersion == NcaVersion.Nca0FixedKey || Header.FormatVersion == NcaVersion.Nca0RsaOaep)
{
return GetDecryptedKeyAreaNca0().AsSpan(0x10 * index, 0x10).ToArray();
}
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
byte[] keyAreaKey = Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex];
@ -94,7 +105,7 @@ namespace LibHac.FsSystem.NcaUtils
public bool CanOpenSection(int index)
{
if (!SectionExists(index)) return false;
if (Header.GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true;
if (GetFsHeader(index).EncryptionType == NcaEncryptionType.None) return true;
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
@ -122,6 +133,14 @@ namespace LibHac.FsSystem.NcaUtils
return Header.IsSectionEnabled(index);
}
public NcaFsHeader GetFsHeader(int index)
{
if (Header.IsNca0())
return GetNca0FsHeader(index);
return Header.GetFsHeader(index);
}
private IStorage OpenSectionStorage(int index)
{
if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
@ -142,7 +161,7 @@ namespace LibHac.FsSystem.NcaUtils
private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting)
{
NcaFsHeader header = Header.GetFsHeader(index);
NcaFsHeader header = GetFsHeader(index);
switch (header.EncryptionType)
{
@ -174,7 +193,7 @@ namespace LibHac.FsSystem.NcaUtils
private IStorage OpenAesCtrStorage(IStorage baseStorage, int index)
{
NcaFsHeader fsHeader = Header.GetFsHeader(index);
NcaFsHeader fsHeader = GetFsHeader(index);
byte[] key = GetContentKey(NcaKeyType.AesCtr);
byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, Header.GetSectionStartOffset(index));
@ -184,7 +203,7 @@ namespace LibHac.FsSystem.NcaUtils
private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting)
{
NcaFsHeader fsHeader = Header.GetFsHeader(index);
NcaFsHeader fsHeader = GetFsHeader(index);
NcaFsPatchInfo info = fsHeader.GetPatchInfo();
long sectionOffset = Header.GetSectionStartOffset(index);
@ -233,6 +252,9 @@ namespace LibHac.FsSystem.NcaUtils
public IStorage OpenRawStorage(int index, bool openEncrypted)
{
if (Header.IsNca0())
return OpenNca0RawStorage(index, openEncrypted);
IStorage storage = OpenSectionStorage(index);
if (IsEncrypted == openEncrypted)
@ -255,7 +277,7 @@ namespace LibHac.FsSystem.NcaUtils
patchStorage.GetSize(out long patchSize).ThrowIfFailure();
baseStorage.GetSize(out long baseSize).ThrowIfFailure();
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
NcaFsHeader header = patchNca.GetFsHeader(index);
NcaFsPatchInfo patchInfo = header.GetPatchInfo();
if (patchInfo.RelocationTreeSize == 0)
@ -286,7 +308,7 @@ namespace LibHac.FsSystem.NcaUtils
public IStorage OpenStorage(int index, IntegrityCheckLevel integrityCheckLevel)
{
IStorage rawStorage = OpenRawStorage(index);
NcaFsHeader header = Header.GetFsHeader(index);
NcaFsHeader header = GetFsHeader(index);
if (header.EncryptionType == NcaEncryptionType.AesCtrEx)
{
@ -307,7 +329,7 @@ namespace LibHac.FsSystem.NcaUtils
public IStorage OpenStorageWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
{
IStorage rawStorage = OpenRawStorageWithPatch(patchNca, index);
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
NcaFsHeader header = patchNca.GetFsHeader(index);
switch (header.HashType)
{
@ -323,7 +345,7 @@ namespace LibHac.FsSystem.NcaUtils
public IFileSystem OpenFileSystem(int index, IntegrityCheckLevel integrityCheckLevel)
{
IStorage storage = OpenStorage(index, integrityCheckLevel);
NcaFsHeader header = Header.GetFsHeader(index);
NcaFsHeader header = GetFsHeader(index);
return OpenFileSystem(storage, header);
}
@ -331,7 +353,7 @@ namespace LibHac.FsSystem.NcaUtils
public IFileSystem OpenFileSystemWithPatch(Nca patchNca, int index, IntegrityCheckLevel integrityCheckLevel)
{
IStorage storage = OpenStorageWithPatch(patchNca, index, integrityCheckLevel);
NcaFsHeader header = patchNca.Header.GetFsHeader(index);
NcaFsHeader header = patchNca.GetFsHeader(index);
return OpenFileSystem(storage, header);
}
@ -392,6 +414,12 @@ namespace LibHac.FsSystem.NcaUtils
var builder = new ConcatenationStorageBuilder();
builder.Add(OpenHeaderStorage(openEncrypted), 0);
if (Header.IsNca0())
{
builder.Add(OpenNca0BodyStorage(openEncrypted), 0x400);
return builder.Build();
}
for (int i = 0; i < NcaHeader.SectionCount; i++)
{
if (Header.IsSectionEnabled(i))
@ -566,6 +594,9 @@ namespace LibHac.FsSystem.NcaUtils
case 2:
header = OpenNca2Header(headerSize, !openEncrypted);
break;
case 0:
header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
break;
default:
throw new NotSupportedException("Unsupported NCA version");
}
@ -588,6 +619,96 @@ namespace LibHac.FsSystem.NcaUtils
return new ConcatenationStorage(sources, true);
}
private byte[] GetDecryptedKeyAreaNca0()
{
if (Nca0KeyArea != null)
return Nca0KeyArea;
if (Header.FormatVersion == NcaVersion.Nca0FixedKey)
{
Nca0KeyArea = Header.GetKeyArea().ToArray();
}
else if (Header.FormatVersion == NcaVersion.Nca0RsaOaep)
{
Span<byte> keyArea = Header.GetKeyArea();
var decKeyArea = new byte[0x100];
if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, Keyset.Nca0RsaKeyAreaKey, out _))
{
Nca0KeyArea = decKeyArea;
}
else
{
throw new InvalidDataException("Unable to decrypt NCA0 key area.");
}
}
else
{
throw new NotSupportedException();
}
return Nca0KeyArea;
}
private IStorage OpenNca0BodyStorage(bool openEncrypted)
{
// NCA0 encrypts the entire NCA body using AES-XTS instead of
// using different encryption types and IVs for each section.
Assert.Equal(0, Header.Version);
if (openEncrypted == IsEncrypted)
{
return GetRawStorage();
}
if (Nca0TransformedBody != null)
return Nca0TransformedBody;
byte[] key0 = GetContentKey(NcaKeyType.AesXts0);
byte[] key1 = GetContentKey(NcaKeyType.AesXts1);
Nca0TransformedBody = new CachedStorage(new Aes128XtsStorage(GetRawStorage(), key0, key1, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
return Nca0TransformedBody;
IStorage GetRawStorage()
{
BaseStorage.GetSize(out long ncaSize).ThrowIfFailure();
return BaseStorage.Slice(0x400, ncaSize - 0x400);
}
}
private IStorage OpenNca0RawStorage(int index, bool openEncrypted)
{
if (!SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
long offset = Header.GetSectionStartOffset(index) - 0x400;
long size = Header.GetSectionSize(index);
IStorage bodyStorage = OpenNca0BodyStorage(openEncrypted);
bodyStorage.GetSize(out long baseSize).ThrowIfFailure();
if (!Utilities.IsSubRange(offset, size, baseSize))
{
throw new InvalidDataException(
$"Section offset (0x{offset + 0x400:x}) and length (0x{size:x}) fall outside the total NCA length (0x{baseSize + 0x400:x}).");
}
return new SubStorage(bodyStorage, offset, size);
}
public NcaFsHeader GetNca0FsHeader(int index)
{
// NCA0 stores the FS header in the first block of the section instead of the header
IStorage bodyStorage = OpenNca0BodyStorage(false);
long offset = Header.GetSectionStartOffset(index) - 0x400;
var fsHeaderData = new byte[0x200];
bodyStorage.Read(offset, fsHeaderData).ThrowIfFailure();
return new NcaFsHeader(fsHeaderData);
}
public Validity VerifyHeaderSignature()
{
return Header.VerifySignature1(Keyset.NcaHdrFixedKeyModulus);
@ -598,7 +719,7 @@ namespace LibHac.FsSystem.NcaUtils
int counterType;
int counterVersion;
NcaFsHeader header = Header.GetFsHeader(sectionIndex);
NcaFsHeader header = GetFsHeader(sectionIndex);
if (header.EncryptionType != NcaEncryptionType.AesCtr &&
header.EncryptionType != NcaEncryptionType.AesCtrEx) return;

View File

@ -56,7 +56,7 @@ namespace LibHac.FsSystem.NcaUtils
if (!nca.SectionExists(index)) throw new ArgumentException(nameof(index), Messages.NcaSectionMissing);
if (!nca.CanOpenSection(index)) return Validity.MissingKey;
NcaFsHeader header = nca.Header.GetFsHeader(index);
NcaFsHeader header = nca.GetFsHeader(index);
// The base data is needed to validate the hash, so use a trick involving the AES-CTR extended
// encryption table to check if the decryption is invalid.
@ -116,7 +116,7 @@ namespace LibHac.FsSystem.NcaUtils
Debug.Assert(nca.CanOpenSection(index));
NcaFsPatchInfo header = nca.Header.GetFsHeader(index).GetPatchInfo();
NcaFsPatchInfo header = nca.GetFsHeader(index).GetPatchInfo();
IStorage decryptedStorage = nca.OpenRawStorage(index);
Span<byte> buffer = stackalloc byte[sizeof(long)];
@ -145,7 +145,7 @@ namespace LibHac.FsSystem.NcaUtils
public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false)
{
NcaFsHeader sect = nca.Header.GetFsHeader(index);
NcaFsHeader sect = nca.GetFsHeader(index);
NcaHashType hashType = sect.HashType;
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
@ -176,7 +176,7 @@ namespace LibHac.FsSystem.NcaUtils
public static Validity VerifySection(this Nca nca, Nca patchNca, int index, IProgressReport logger = null, bool quiet = false)
{
NcaFsHeader sect = nca.Header.GetFsHeader(index);
NcaFsHeader sect = nca.GetFsHeader(index);
NcaHashType hashType = sect.HashType;
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;

View File

@ -2,6 +2,7 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Crypto;
using LibHac.Fs;
@ -16,15 +17,22 @@ namespace LibHac.FsSystem.NcaUtils
private readonly Memory<byte> _header;
public NcaVersion FormatVersion { get; private set; }
public NcaHeader(IStorage headerStorage)
{
_header = new byte[HeaderSize];
headerStorage.Read(0, _header.Span);
Span<byte> headerSpan = _header.Span;
headerStorage.Read(0, headerSpan).ThrowIfFailure();
FormatVersion = DetectNcaVersion(headerSpan);
}
public NcaHeader(Keyset keyset, IStorage headerStorage)
{
_header = DecryptHeader(keyset, headerStorage);
FormatVersion = DetectNcaVersion(_header.Span);
}
private ref NcaHeaderStruct Header => ref Unsafe.As<byte, NcaHeaderStruct>(ref _header.Span[0]);
@ -104,6 +112,11 @@ namespace LibHac.FsSystem.NcaUtils
public bool HasRightsId => !Utilities.IsEmpty(RightsId);
public Span<byte> GetKeyArea()
{
return _header.Span.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize);
}
private ref NcaSectionEntryStruct GetSectionEntry(int index)
{
ValidateSectionIndex(index);
@ -161,7 +174,6 @@ namespace LibHac.FsSystem.NcaUtils
Span<byte> expectedHash = GetFsHeaderHash(index);
int offset = NcaHeaderStruct.FsHeadersOffset + NcaHeaderStruct.FsHeaderSize * index;
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
Memory<byte> headerData = _header.Slice(offset, NcaHeaderStruct.FsHeaderSize);
Span<byte> actualHash = stackalloc byte[Sha256.DigestSize];
@ -223,7 +235,7 @@ namespace LibHac.FsSystem.NcaUtils
transform.TransformBlock(buf, i, HeaderSectorSize, 0);
}
}
else
else if (version != 0)
{
throw new NotSupportedException($"NCA version {version} is not supported.");
}
@ -231,6 +243,39 @@ namespace LibHac.FsSystem.NcaUtils
return buf;
}
private static NcaVersion DetectNcaVersion(ReadOnlySpan<byte> header)
{
int version = header[0x203] - '0';
if (version == 3) return NcaVersion.Nca3;
if (version == 2) return NcaVersion.Nca2;
if (version != 0) return NcaVersion.Unknown;
// There are multiple versions of NCA0 that each encrypt the key area differently.
// Examine the key area to detect which version this NCA is.
ReadOnlySpan<byte> keyArea = header.Slice(NcaHeaderStruct.KeyAreaOffset, NcaHeaderStruct.KeyAreaSize);
// The end of the key area will only be non-zero if it's RSA-OAEP encrypted
var zeros = new Buffer16();
if (!keyArea.Slice(0x80, 0x10).SequenceEqual(zeros))
{
return NcaVersion.Nca0RsaOaep;
}
// Key areas using fixed, unencrypted keys always use the same keys.
// Check for these keys by comparing the key area with the known hash of the fixed body keys.
Buffer32 hash = default;
Sha256.GenerateSha256Hash(keyArea.Slice(0, 0x20), hash);
if (Nca0FixedBodyKeySha256Hash.SequenceEqual(hash))
{
return NcaVersion.Nca0FixedKey;
}
// Otherwise the key area is encrypted the same as modern NCAs.
return NcaVersion.Nca0;
}
public Validity VerifySignature1(byte[] modulus)
{
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature1.ToArray(), modulus);
@ -241,6 +286,14 @@ namespace LibHac.FsSystem.NcaUtils
return CryptoOld.Rsa2048PssVerify(_header.Span.Slice(0x200, 0x200).ToArray(), Signature2.ToArray(), modulus);
}
public bool IsNca0() => FormatVersion >= NcaVersion.Nca0;
private static ReadOnlySpan<byte> Nca0FixedBodyKeySha256Hash => new byte[]
{
0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
};
[StructLayout(LayoutKind.Explicit, Size = 0xC00)]
private struct NcaHeaderStruct
{
@ -250,6 +303,7 @@ namespace LibHac.FsSystem.NcaUtils
public const int FsHeaderHashOffset = 0x280;
public const int FsHeaderHashSize = 0x20;
public const int KeyAreaOffset = 0x300;
public const int KeyAreaSize = 0x100;
public const int FsHeadersOffset = 0x400;
public const int FsHeaderSize = 0x200;
@ -277,4 +331,14 @@ namespace LibHac.FsSystem.NcaUtils
public bool IsEnabled;
}
}
public enum NcaVersion
{
Unknown,
Nca3,
Nca2,
Nca0,
Nca0FixedKey,
Nca0RsaOaep
}
}

View File

@ -1,4 +1,5 @@
using LibHac.Common;
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@ -125,16 +126,30 @@ namespace LibHac.FsSystem.RomFs
{
var reader = new FileReader(file);
HeaderSize = reader.ReadInt64();
DirHashTableOffset = reader.ReadInt64();
DirHashTableSize = reader.ReadInt64();
DirMetaTableOffset = reader.ReadInt64();
DirMetaTableSize = reader.ReadInt64();
FileHashTableOffset = reader.ReadInt64();
FileHashTableSize = reader.ReadInt64();
FileMetaTableOffset = reader.ReadInt64();
FileMetaTableSize = reader.ReadInt64();
DataOffset = reader.ReadInt64();
HeaderSize = reader.ReadInt32();
Func<long> func;
// Old pre-release romfs is exactly the same except the fields in the header are 32-bit instead of 64-bit
if (HeaderSize == 0x28)
{
func = () => reader.ReadInt32();
}
else
{
func = reader.ReadInt64;
reader.Position += 4;
}
DirHashTableOffset = func();
DirHashTableSize = func();
DirMetaTableOffset = func();
DirMetaTableSize = func();
FileHashTableOffset = func();
FileHashTableSize = func();
FileMetaTableOffset = func();
FileMetaTableSize = func();
DataOffset = func();
}
}
}

View File

@ -72,6 +72,20 @@ namespace LibHac
public RSAParameters EticketExtKeyRsa { get; set; }
private RSAParameters? _nca0RsaKeyAreaKey;
public RSAParameters Nca0RsaKeyAreaKey
{
// Lazily init this key
get
{
if (_nca0RsaKeyAreaKey.HasValue)
return _nca0RsaKeyAreaKey.Value;
_nca0RsaKeyAreaKey = CryptoOld.RecoverRsaParameters(BetaNca0Modulus, BetaNca0Exponent);
return _nca0RsaKeyAreaKey.Value;
}
}
public bool KeysetForDev;
public byte[] NcaHdrFixedKeyModulus
{
@ -118,6 +132,46 @@ namespace LibHac
}
}
public readonly byte[] BetaNca0Modulus =
{
0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49,
0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD,
0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4,
0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B,
0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55,
0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4,
0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F,
0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1,
0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72,
0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D,
0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1,
0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7,
0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13,
0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7,
0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1,
0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5
};
public readonly byte[] BetaNca0Exponent =
{
0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA,
0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E,
0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA,
0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00,
0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9,
0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC,
0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58,
0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD,
0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7,
0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81,
0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1,
0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B,
0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6,
0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7,
0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6,
0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65
};
private static readonly byte[] NcaHdrFixedKeyModulusProd =
{
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,

View File

@ -40,14 +40,25 @@ namespace LibHac.Npdm
int kernelAccessControlOffset = reader.ReadInt32();
int kernelAccessControlSize = reader.ReadInt32();
var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset);
if (fsAccessHeaderSize > 0)
{
var accessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset);
FsVersion = accessHeader.Version;
FsPermissionsBitmask = accessHeader.PermissionsBitmask;
FsVersion = accessHeader.Version;
FsPermissionsBitmask = accessHeader.PermissionsBitmask;
}
ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize);
if (serviceAccessControlSize > 0)
{
ServiceAccess = new ServiceAccessControl(stream, offset + serviceAccessControlOffset,
serviceAccessControlSize);
}
KernelAccess = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize);
if (kernelAccessControlSize > 0)
{
KernelAccess =
new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize);
}
}
}
}

View File

@ -153,7 +153,7 @@ namespace LibHac
return commonKey;
}
return CryptoOld.DecryptTitleKey(TitleKeyBlock, keyset.EticketExtKeyRsa);
return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keyset.EticketExtKeyRsa);
}
}

View File

@ -60,7 +60,7 @@ namespace hactoolnet
if (ctx.Options.Validate && nca.SectionExists(i))
{
if (nca.Header.GetFsHeader(i).IsPatchSection() && baseNca != null)
if (nca.GetFsHeader(i).IsPatchSection() && baseNca != null)
{
ncaHolder.Validities[i] = baseNca.VerifySection(nca, i, ctx.Logger);
}
@ -261,24 +261,55 @@ namespace hactoolnet
}
else
{
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex);
sb.AppendLine("Key Area (Encrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
}
sb.AppendLine("Key Area (Decrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i));
}
PrintKeyArea();
}
PrintSections();
return sb.ToString();
void PrintKeyArea()
{
NcaVersion version = nca.Header.FormatVersion;
if (version == NcaVersion.Nca0RsaOaep)
{
sb.AppendLine("Key Area (Encrypted):");
PrintItem(sb, colLen, "Key (RSA-OAEP Encrypted):", nca.Header.GetKeyArea().ToArray());
sb.AppendLine("Key Area (Decrypted):");
for (int i = 0; i < 2; i++)
{
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
}
}
else if (version == NcaVersion.Nca0FixedKey)
{
sb.AppendLine("Key Area:");
for (int i = 0; i < 2; i++)
{
PrintItem(sb, colLen, $" Key {i}:", nca.Header.GetEncryptedKey(i).ToArray());
}
}
else
{
int keyCount = version == NcaVersion.Nca0 ? 2 : 4;
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KeyAreaKeyIndex);
sb.AppendLine("Key Area (Encrypted):");
for (int i = 0; i < keyCount; i++)
{
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray());
}
sb.AppendLine("Key Area (Decrypted):");
for (int i = 0; i < keyCount; i++)
{
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i));
}
}
}
void PrintSections()
{
sb.AppendLine("Sections:");
@ -287,13 +318,13 @@ namespace hactoolnet
{
if (!nca.Header.IsSectionEnabled(i)) continue;
NcaFsHeader sectHeader = nca.Header.GetFsHeader(i);
NcaFsHeader sectHeader = nca.GetFsHeader(i);
bool isExefs = nca.Header.ContentType == NcaContentType.Program && i == 0;
sb.AppendLine($" Section {i}:");
PrintItem(sb, colLen, " Offset:", $"0x{nca.Header.GetSectionStartOffset(i):x12}");
PrintItem(sb, colLen, " Size:", $"0x{nca.Header.GetSectionSize(i):x12}");
PrintItem(sb, colLen, " Partition Type:", (isExefs ? "ExeFS" : sectHeader.FormatType.ToString()) + (sectHeader.IsPatchSection() ? " patch" : ""));
PrintItem(sb, colLen, " Partition Type:", GetPartitionType(sectHeader, isExefs, nca.Header.IsNca0()));
PrintItem(sb, colLen, " Section CTR:", $"{sectHeader.Counter:x16}");
PrintItem(sb, colLen, " Section Validity:", $"{ncaHolder.Validities[i]}");
@ -314,12 +345,20 @@ namespace hactoolnet
}
}
static string GetPartitionType(NcaFsHeader fsHeader, bool isExefs, bool isNca0)
{
if (isExefs) return "ExeFS";
if (isNca0 && fsHeader.FormatType == NcaFormatType.Romfs) return "NCA0 RomFS";
return fsHeader.FormatType + (fsHeader.IsPatchSection() ? " patch" : "");
}
void PrintSha256Hash(NcaFsHeader sect, int index)
{
NcaFsIntegrityInfoSha256 hashInfo = sect.GetIntegrityInfoSha256();
PrintItem(sb, colLen, $" Master Hash{nca.ValidateSectionMasterHash(index).GetValidityString()}:", hashInfo.MasterHash.ToArray());
sb.AppendLine($" Hash Table:");
sb.AppendLine(" Hash Table:");
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.GetLevelOffset(0):x12}");
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.GetLevelSize(0):x12}");