From 61ce892697c5bae52eb5d40455e9f2c4549f5d7d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 28 Sep 2020 01:18:15 -0700 Subject: [PATCH 01/16] Rewrite the Package1 class, updating it to handle newer package1s --- build/CodeGen/results.csv | 5 + src/LibHac/Boot/Package1.cs | 547 +++++++++++++++++++++++++++ src/LibHac/Common/ResultLibHac.cs | 9 + src/LibHac/FsSystem/AesCbcStorage.cs | 83 ++++ src/LibHac/FsSystem/CachedStorage.cs | 2 +- src/LibHac/Keyset.cs | 7 +- src/hactoolnet/ProcessPackage.cs | 85 ++++- 7 files changed, 730 insertions(+), 8 deletions(-) create mode 100644 src/LibHac/Boot/Package1.cs create mode 100644 src/LibHac/FsSystem/AesCbcStorage.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 95a37997..fa74307a 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -467,3 +467,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 428,1031,,InvalidPackage2MetaPayloadsOverlap, 428,1032,,InvalidPackage2MetaEntryPointNotFound, 428,1033,,InvalidPackage2PayloadCorrupted, + +428,1040,1059,InvalidPackage1, +428,1041,,InvalidPackage1SectionSize, +428,1042,,InvalidPackage1MarikoBodySize, +428,1043,,InvalidPackage1Pk11Size, diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs new file mode 100644 index 00000000..f8e04200 --- /dev/null +++ b/src/LibHac/Boot/Package1.cs @@ -0,0 +1,547 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSystem; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Diag; + +namespace LibHac.Boot +{ + [StructLayout(LayoutKind.Explicit, Size = 0x170)] + public struct Package1MarikoOemHeader + { + [FieldOffset(0x000)] private byte _aesMac; + [FieldOffset(0x010)] private byte _rsaSig; + [FieldOffset(0x110)] private byte _salt; + [FieldOffset(0x130)] private byte _hash; + [FieldOffset(0x150)] public int Version; + [FieldOffset(0x154)] public int Size; + [FieldOffset(0x158)] public int LoadAddress; + [FieldOffset(0x15C)] public int EntryPoint; + [FieldOffset(0x160)] private byte _reserved; + + public ReadOnlySpan AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10); + public ReadOnlySpan RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100); + public ReadOnlySpan Salt => SpanHelpers.CreateSpan(ref _salt, 0x20); + public ReadOnlySpan Hash => SpanHelpers.CreateSpan(ref _hash, 0x20); + public ReadOnlySpan Reserved => SpanHelpers.CreateSpan(ref _reserved, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1MetaData + { + [FieldOffset(0x00)] public uint LoaderHash; + [FieldOffset(0x04)] public uint SecureMonitorHash; + [FieldOffset(0x08)] public uint BootloaderHash; + [FieldOffset(0x10)] private byte _buildDate; + [FieldOffset(0x1E)] public byte KeyGeneration; + [FieldOffset(0x1F)] public byte Version; + + public U8Span BuildDate => new U8Span(SpanHelpers.CreateSpan(ref _buildDate, 0xE)); + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _buildDate, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1Stage1Footer + { + [FieldOffset(0x00)] public int Pk11Size; + [FieldOffset(0x10)] private byte _iv; + + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _iv, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1Pk11Header + { + public const uint ExpectedMagic = 0x31314B50; // PK11 + + [FieldOffset(0x00)] public uint Magic; + [FieldOffset(0x04)] public int WarmBootSize; + [FieldOffset(0x08)] public int WarmBootOffset; + [FieldOffset(0x10)] public int BootloaderSize; + [FieldOffset(0x14)] public int BootloaderOffset; + [FieldOffset(0x18)] public int SecureMonitorSize; + [FieldOffset(0x1C)] public int SecureMonitorOffset; + } + + public enum Package1Section + { + Bootloader, + SecureMonitor, + WarmBoot + } + + public class Package1 + { + private const int LegacyStage1Size = 0x4000; + private const int ModernStage1Size = 0x7000; + private const int MarikoWarmBootPlainTextSectionSize = 0x330; + + private IStorage BaseStorage { get; set; } + private Keyset Keyset { get; set; } + + public bool IsModern { get; private set; } + public bool IsMariko { get; private set; } + + /// + /// Returns if the package1 can be decrypted. + /// + public bool IsDecrypted { get; private set; } + public byte KeyRevision { get; private set; } + + public int Pk11Size { get; private set; } + private IStorage Pk11Storage { get; set; } + private IStorage BodyStorage { get; set; } + + private Package1MarikoOemHeader _marikoOemHeader; + private Package1MetaData _metaData; + private Package1Stage1Footer _stage1Footer; + private Package1Pk11Header _pk11Header; + private Buffer16 _pk11Mac; + + public ref readonly Package1MarikoOemHeader MarikoOemHeader => ref _marikoOemHeader; + public ref readonly Package1MetaData MetaData => ref _metaData; + public ref readonly Package1Stage1Footer Stage1Footer => ref _stage1Footer; + public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header; + public ref readonly Buffer16 Pk11Mac => ref _pk11Mac; + + public Result Initialize(Keyset keyset, IStorage storage) + { + Keyset = keyset; + BaseStorage = storage; + + // Read what might be a mariko header and check if it actually is a mariko header + Result rc = BaseStorage.Read(0, SpanHelpers.AsByteSpan(ref _marikoOemHeader)); + if (rc.IsFailure()) return rc; + + IsMariko = IsMarikoImpl(); + + if (IsMariko) + { + rc = InitMarikoBodyStorage(); + if (rc.IsFailure()) return rc; + } + else + { + BodyStorage = BaseStorage; + rc = BodyStorage.Read(0, SpanHelpers.AsByteSpan(ref _metaData)); + if (rc.IsFailure()) return rc; + } + + rc = ParseStage1(); + if (rc.IsFailure()) return rc; + + rc = ReadPk11Header(); + if (rc.IsFailure()) return rc; + + if (!IsMariko && IsModern) + { + rc = ReadModernEristaMac(); + if (rc.IsFailure()) return rc; + } + + rc = SetPk11Storage(); + if (rc.IsFailure()) return rc; + + // Make sure the PK11 section sizes add up to the expected size + if (IsDecrypted && !VerifyPk11Sizes()) + { + return ResultLibHac.InvalidPackage1Pk11Size.Log(); + } + + return Result.Success; + } + + /// + /// Read the encrypted section of a Mariko Package1 and try to decrypt it. + /// + /// : The operation was successful.
+ /// : The package1 is + /// too small or the size in the OEM header is incorrect.
+ private Result InitMarikoBodyStorage() + { + // Body must be large enough to hold at least one metadata struct + if (MarikoOemHeader.Size < Unsafe.SizeOf()) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Verify the body storage size is not smaller than the size in the header + Result rc = BaseStorage.GetSize(out long totalSize); + if (rc.IsFailure()) return rc; + + long bodySize = totalSize - Unsafe.SizeOf(); + if (bodySize < MarikoOemHeader.Size) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Create body SubStorage and metadata buffers + var bodySubStorage = new SubStorage(BaseStorage, Unsafe.SizeOf(), bodySize); + + Span metaData = stackalloc Package1MetaData[2]; + Span metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]); + Span metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]); + + // Read both the plaintext metadata and encrypted metadata + rc = bodySubStorage.Read(0, MemoryMarshal.Cast(metaData)); + if (rc.IsFailure()) return rc; + + // Set the body storage and decrypted metadata + _metaData = metaData[0]; + BodyStorage = bodySubStorage; + + // The plaintext metadata is followed by an encrypted copy + // If these two match then the body is already decrypted + IsDecrypted = metaData1.SequenceEqual(metaData2); + + if (IsDecrypted) + { + return Result.Success; + } + + // If encrypted, check if the body can be decrypted + Crypto.Aes.DecryptCbc128(metaData2, metaData2, Keyset.MarikoBek, _metaData.Iv); + IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData)); + + // Get a decrypted body storage if we have the correct key + if (IsDecrypted) + { + var decStorage = new AesCbcStorage(bodySubStorage, Keyset.MarikoBek, _metaData.Iv, true); + BodyStorage = new CachedStorage(decStorage, 0x4000, 1, true); + } + + return Result.Success; + } + + private Result ParseStage1() + { + // Erista package1ldr is stored unencrypted, so we can always directly read the size + // field at the end of package1ldr. + + // Mariko package1ldr is stored encrypted. If we're able to decrypt it, + // directly read the size field at the end of package1ldr. + + IsModern = !IsLegacyImpl(); + int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; + + if (IsMariko && !IsDecrypted) + { + // If we're not able to decrypt the Mariko package1ldr, calculate the size by subtracting + // the known package1ldr size from the total size in the OEM header. + Pk11Size = MarikoOemHeader.Size - stage1Size; + return Result.Success; + } + + // Read the package1ldr footer + int footerOffset = stage1Size - Unsafe.SizeOf(); + + Result rc = BodyStorage.Read(footerOffset, SpanHelpers.AsByteSpan(ref _stage1Footer)); + if (rc.IsFailure()) return rc; + + // Get the PK11 size from the field in the unencrypted stage 1 footer + Pk11Size = _stage1Footer.Pk11Size; + + return Result.Success; + } + + private Result ReadPk11Header() + { + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + return BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + } + + private Result ReadModernEristaMac() + { + return BaseStorage.Read(ModernStage1Size + Pk11Size, _pk11Mac.Bytes); + } + + private Result SetPk11Storage() + { + // Read the PK11 header from the body storage + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + Result rc = BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + if (rc.IsFailure()) return rc; + + // Check if PK11 is already decrypted, creating the PK11 storage if it is + IsDecrypted = _pk11Header.Magic == Package1Pk11Header.ExpectedMagic; + + if (IsDecrypted) + { + Pk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size); + return Result.Success; + } + + var encPk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size); + + // See if we have an Erista package1 key that can decrypt this PK11 + if (!IsMariko && TryFindEristaKeyRevision()) + { + IsDecrypted = true; + IStorage decPk11Storage; + + if (IsModern) + { + decPk11Storage = new AesCbcStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], + _stage1Footer.Iv, true); + } + else + { + decPk11Storage = new Aes128CtrStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], + _stage1Footer.Iv.ToArray(), true); + } + + Pk11Storage = new CachedStorage(decPk11Storage, 0x4000, 1, true); + + return Result.Success; + } + + // We can't decrypt the PK11. Set Pk11Storage to the encrypted PK11 storage + Pk11Storage = encPk11Storage; + return Result.Success; + } + + private delegate void Decryptor(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false); + + private bool TryFindEristaKeyRevision() + { + // Package1 has no indication of which key it's encrypted with, + // so we must test each known key to find one that works + + var decHeader = new Package1Pk11Header(); + + int start = IsModern ? 6 : 0; + int end = IsModern ? 0x20 : 6; + Decryptor decryptor = IsModern ? Crypto.Aes.DecryptCbc128 : (Decryptor)Crypto.Aes.DecryptCtr128; + + for (int i = start; i < end; i++) + { + decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader), + Keyset.Package1Keys[i], _stage1Footer.Iv); + + if (decHeader.Magic == Package1Pk11Header.ExpectedMagic) + { + KeyRevision = (byte)i; + _pk11Header = decHeader; + return true; + } + } + + return false; + } + + private bool VerifyPk11Sizes() + { + Assert.AssertTrue(IsDecrypted); + + int pk11Size = Unsafe.SizeOf() + GetSectionSize(Package1Section.WarmBoot) + + GetSectionSize(Package1Section.Bootloader) + GetSectionSize(Package1Section.SecureMonitor); + + pk11Size = Utilities.AlignUp(pk11Size, 0x10); + + return pk11Size == Pk11Size; + } + + // MetaData must be read first + private bool IsLegacyImpl() + { + return _metaData.Version < 0xE || StringUtils.Compare(_metaData.BuildDate, LegacyDateCutoff) < 0; + } + + // MarikoOemHeader must be read first + private bool IsMarikoImpl() + { + return MarikoOemHeader.AesMac.IsEmpty() && MarikoOemHeader.Reserved.IsEmpty(); + } + + /// + /// Opens an of the entire package1, decrypting any encrypted data. + /// + /// If the package1 can be decrypted, an + /// of the package1, . + public IStorage OpenDecryptedPackage1Storage() + { + if (!IsDecrypted) + return null; + + var storages = new List(); + + if (IsMariko) + { + int metaSize = Unsafe.SizeOf(); + + // The metadata at the start of the body is unencrypted, so don't take its data from the decrypted + // body storage + storages.Add(new SubStorage(BaseStorage, 0, Unsafe.SizeOf() + metaSize)); + storages.Add(new SubStorage(BodyStorage, metaSize, _marikoOemHeader.Size - metaSize)); + } + else + { + int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; + + storages.Add(new SubStorage(BaseStorage, 0, stage1Size)); + storages.Add(Pk11Storage); + + if (IsModern) + { + storages.Add(new MemoryStorage(_pk11Mac.Bytes.ToArray())); + } + } + + return new ConcatenationStorage(storages, true); + } + + /// + /// Opens an of the warmboot section. + /// + /// If the section can be decrypted, an of the + /// warmboot section; otherwise, . + public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot); + + /// + /// Opens an of the bootloader section. + /// + /// If the section can be decrypted, an of the + /// bootloader section; otherwise, . + public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader); + + /// + /// Opens an of the secure monitor section. + /// + /// If the section can be decrypted, an of the + /// secure monitor section; otherwise, . + public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor); + + /// + /// Opens an for the specified . + /// + /// The section to open. + /// If the section can be decrypted, an of that + /// section; otherwise, . + public IStorage OpenSectionStorage(Package1Section sectionType) + { + if (!IsDecrypted) + return null; + + int offset = Unsafe.SizeOf() + GetSectionOffset(sectionType); + int size = GetSectionSize(sectionType); + + return new SubStorage(Pk11Storage, offset, size); + } + + + public IStorage OpenDecryptedWarmBootStorage() + { + if (!IsDecrypted) + return null; + + IStorage warmBootStorage = OpenWarmBootStorage(); + + // Only Mariko warmboot storage is encrypted + if (!IsMariko) + { + return warmBootStorage; + } + + int size = GetSectionSize(Package1Section.WarmBoot); + int encryptedSectionSize = size - MarikoWarmBootPlainTextSectionSize; + + var plainTextSection = new SubStorage(warmBootStorage, 0, MarikoWarmBootPlainTextSectionSize); + var encryptedSubStorage = + new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize); + + var zeroIv = new Buffer16(); + IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, Keyset.MarikoBek, zeroIv.Bytes, true); + + decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true); + + return new ConcatenationStorage(new List { plainTextSection, decryptedSection }, true); + } + + public int GetSectionSize(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + return sectionType switch + { + Package1Section.Bootloader => _pk11Header.BootloaderSize, + Package1Section.SecureMonitor => _pk11Header.SecureMonitorSize, + Package1Section.WarmBoot => _pk11Header.WarmBootSize, + _ => 0 + }; + } + + public int GetSectionOffset(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + switch (GetSectionIndex(sectionType)) + { + case 0: + return 0; + case 1: + return GetSectionSize(GetSectionType(0)); + case 2: + return GetSectionSize(GetSectionType(0)) + GetSectionSize(GetSectionType(1)); + default: + return -1; + } + } + + private int GetSectionIndex(Package1Section sectionType) + { + if (_metaData.Version >= 0x07) + { + return sectionType switch + { + Package1Section.Bootloader => 0, + Package1Section.SecureMonitor => 1, + Package1Section.WarmBoot => 2, + _ => -1, + }; + } + + if (_metaData.Version >= 0x02) + { + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 2, + Package1Section.WarmBoot => 0, + _ => -1, + }; + } + + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 0, + Package1Section.WarmBoot => 2, + _ => -1, + }; + } + + private Package1Section GetSectionType(int index) + { + if (GetSectionIndex(Package1Section.Bootloader) == index) + return Package1Section.Bootloader; + + if (GetSectionIndex(Package1Section.SecureMonitor) == index) + return Package1Section.SecureMonitor; + + if (GetSectionIndex(Package1Section.WarmBoot) == index) + return Package1Section.WarmBoot; + + return (Package1Section)(-1); + } + + private static ReadOnlySpan LegacyDateCutoff => // 20181107 + new[] + { + (byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7' + }; + } +} diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs index f93961b7..7d783f88 100644 --- a/src/LibHac/Common/ResultLibHac.cs +++ b/src/LibHac/Common/ResultLibHac.cs @@ -83,5 +83,14 @@ namespace LibHac.Common public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); /// Error code: 2428-1033; Inner value: 0x813ac public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); + + /// Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac + public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); } + /// Error code: 2428-1041; Inner value: 0x823ac + public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041); + /// Error code: 2428-1042; Inner value: 0x825ac + public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042); + /// Error code: 2428-1043; Inner value: 0x827ac + public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043); } } diff --git a/src/LibHac/FsSystem/AesCbcStorage.cs b/src/LibHac/FsSystem/AesCbcStorage.cs new file mode 100644 index 00000000..0e27753b --- /dev/null +++ b/src/LibHac/FsSystem/AesCbcStorage.cs @@ -0,0 +1,83 @@ +using LibHac.Crypto; +using LibHac.Fs; +using System; + +namespace LibHac.FsSystem +{ + public class AesCbcStorage : SectorStorage + { + private const int BlockSize = 0x10; + + private readonly byte[] _key; + private readonly byte[] _iv; + + private readonly long _size; + + public AesCbcStorage(IStorage baseStorage, ReadOnlySpan key, ReadOnlySpan iv, + bool leaveOpen) : base(baseStorage, BlockSize, leaveOpen) + { + if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); + if (iv.Length != BlockSize) throw new ArgumentException(nameof(iv), $"Counter must be {BlockSize} bytes long"); + + _key = key.ToArray(); + _iv = iv.ToArray(); + + baseStorage.GetSize(out _size).ThrowIfFailure(); + } + + protected override Result DoRead(long offset, Span destination) + { + if (!IsRangeValid(offset, destination.Length, _size)) + return ResultFs.OutOfRange.Log(); + + Result rc = base.DoRead(offset, destination); + if (rc.IsFailure()) return rc; + + rc = GetDecryptor(out ICipher cipher, offset); + if (rc.IsFailure()) return rc; + + cipher.Transform(destination, destination); + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedOperation.Log(); + } + + protected override Result DoFlush() + { + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedOperation.Log(); + } + + private Result GetDecryptor(out ICipher decryptor, long offset) + { + if (offset == 0) + { + // Use the IV directly + decryptor = Aes.CreateCbcDecryptor(_key, _iv); + return Result.Success; + } + + decryptor = default; + + // Need to get the output of the previous block + Span prevBlock = stackalloc byte[BlockSize]; + Result rc = BaseStorage.Read(offset - BlockSize, prevBlock); + if (rc.IsFailure()) return rc; + + ICipher tmpDecryptor = Aes.CreateCbcDecryptor(_key, _iv); + + tmpDecryptor.Transform(prevBlock, prevBlock); + + decryptor = tmpDecryptor; + return Result.Success; + } + } +} diff --git a/src/LibHac/FsSystem/CachedStorage.cs b/src/LibHac/FsSystem/CachedStorage.cs index 945a82b3..5f6390f3 100644 --- a/src/LibHac/FsSystem/CachedStorage.cs +++ b/src/LibHac/FsSystem/CachedStorage.cs @@ -145,7 +145,7 @@ namespace LibHac.FsSystem Blocks.AddFirst(node); } - return node.Value; + return node!.Value; } // An inactive node shouldn't be null, but we'll fix it if it is anyway diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index 20006594..6cb05822 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -59,6 +59,8 @@ namespace LibHac public byte[] BisKekSource { get; } = new byte[0x10]; public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20); public byte[] SslRsaKek { get; } = new byte[0x10]; + public byte[] MarikoBek { get; } = new byte[0x10]; + public byte[] MarikoKek { get; } = new byte[0x10]; // Device-specific keys public byte[] SecureBootKey { get; } = new byte[0x10]; @@ -697,7 +699,7 @@ namespace LibHac if (keySlot.Group > currentGroup) { - if (currentGroup > 0) sb.AppendLine(); + sb.AppendLine(); currentGroup = keySlot.Group; } @@ -760,6 +762,9 @@ namespace LibHac { new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource), + new KeyValue("mariko_bek", 0x10, 32, set => set.MarikoBek), + new KeyValue("mariko_kek", 0x10, 32, set => set.MarikoKek), + new KeyValue("master_key_source", 0x10, 60, set => set.MasterKeySource), new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource), diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index a08e0aeb..40e08814 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -1,7 +1,9 @@ using System.IO; +using System.Runtime.CompilerServices; using System.Text; using LibHac; using LibHac.Boot; +using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; using static hactoolnet.Print; @@ -14,21 +16,92 @@ namespace hactoolnet { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - var package1 = new Package1(ctx.Keyset, file); + var package1 = new LibHac.Boot.Package1(); + package1.Initialize(ctx.Keyset, file).ThrowIfFailure(); + + ctx.Logger.LogMessage(package1.Print()); + string outDir = ctx.Options.OutDir; - if (outDir != null) + if (package1.IsDecrypted && outDir != null) { Directory.CreateDirectory(outDir); - package1.Pk11.OpenWarmboot().WriteAllBytes(Path.Combine(outDir, "Warmboot.bin"), ctx.Logger); - package1.Pk11.OpenNxBootloader().WriteAllBytes(Path.Combine(outDir, "NX_Bootloader.bin"), ctx.Logger); - package1.Pk11.OpenSecureMonitor().WriteAllBytes(Path.Combine(outDir, "Secure_Monitor.bin"), ctx.Logger); - package1.OpenDecryptedPackage().WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); + IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage(); + + WriteFile(decryptedStorage, "Decrypted.bin"); + WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin"); + WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin"); + WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin"); + + if (package1.IsMariko) + { + WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin"); + + var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf(), + package1.MarikoOemHeader.Size); + + WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin"); + } + } + + void WriteFile(IStorage storage, string filename) + { + string path = Path.Combine(outDir, filename); + ctx.Logger.LogMessage($"Writing {path}..."); + storage.WriteAllBytes(path, ctx.Logger); } } } + private static string Print(this LibHac.Boot.Package1 package1) + { + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + if (package1.IsMariko) + { + sb.AppendLine("Mariko OEM Header:"); + PrintItem(sb, colLen, " Signature:", package1.MarikoOemHeader.RsaSig.ToArray()); + PrintItem(sb, colLen, " Random Salt:", package1.MarikoOemHeader.Salt.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Hash:", package1.MarikoOemHeader.Hash.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Version:", $"{package1.MarikoOemHeader.Version:x2}"); + PrintItem(sb, colLen, " OEM Bootloader Size:", $"{package1.MarikoOemHeader.Size:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Load Address:", $"{package1.MarikoOemHeader.LoadAddress:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Entrypoint:", $"{package1.MarikoOemHeader.EntryPoint:x8}"); + } + + sb.AppendLine("Package1 Metadata:"); + PrintItem(sb, colLen, " Build Date:", package1.MetaData.BuildDate.ToString()); + PrintItem(sb, colLen, " Package1ldr Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.LoaderHash).ToArray()); + PrintItem(sb, colLen, " Secure Monitor Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.SecureMonitorHash).ToArray()); + PrintItem(sb, colLen, " NX Bootloader Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.BootloaderHash).ToArray()); + PrintItem(sb, colLen, " Version:", $"{package1.MetaData.Version:x2}"); + + if (!package1.IsMariko && package1.IsModern) + { + PrintItem(sb, colLen, " PK11 MAC:", package1.Pk11Mac); + } + + if (package1.IsDecrypted) + { + sb.AppendLine("PK11:"); + + if (!package1.IsMariko) + { + PrintItem(sb, colLen, " Key Revision:", $"{package1.KeyRevision:x2} ({Utilities.GetKeyRevisionSummary(package1.KeyRevision)})"); + } + + PrintItem(sb, colLen, " PK11 Size:", $"{package1.Pk11Size:x8}"); + PrintItem(sb, colLen, " Warmboot.bin Size:", $"{package1.GetSectionSize(Package1Section.WarmBoot):x8}"); + PrintItem(sb, colLen, " NX_Bootloader.bin Size:", $"{package1.GetSectionSize(Package1Section.Bootloader):x8}"); + PrintItem(sb, colLen, " Secure_Monitor.bin Size:", $"{package1.GetSectionSize(Package1Section.SecureMonitor):x8}"); + } + + return sb.ToString(); + } + public static void ProcessPk21(Context ctx) { using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)) From f7030aef4fac2dd31406972c6a9327b43d03476b Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 1 Oct 2020 18:06:32 -0700 Subject: [PATCH 02/16] Add a new KeySet class --- src/LibHac/Boot/KeyBlob.cs | 93 ++++ src/LibHac/Common/FixedArrays/Array12.cs | 33 ++ src/LibHac/Common/FixedArrays/Array2.cs | 23 + src/LibHac/Common/FixedArrays/Array3.cs | 24 + src/LibHac/Common/FixedArrays/Array32.cs | 53 ++ src/LibHac/Common/FixedArrays/Array4.cs | 25 + src/LibHac/Common/Keys/ExternalKeyReader.cs | 475 ++++++++++++++++++ src/LibHac/Common/Keys/KeySet.cs | 438 ++++++++++++++++ src/LibHac/Crypto/Aes.cs | 2 + src/LibHac/Crypto/KeyTypes.cs | 118 +++++ src/LibHac/Keyset.cs | 2 + src/LibHac/Package1.cs | 1 + src/LibHac/Utilities.cs | 26 + tests/LibHac.Tests/Boot/TypeSizeTests.cs | 22 + .../LibHac.Tests/CryptoTests/TypeSizeTests.cs | 34 ++ tests/LibHac.Tests/Spl/TypeSizeTests.cs | 16 + 16 files changed, 1385 insertions(+) create mode 100644 src/LibHac/Boot/KeyBlob.cs create mode 100644 src/LibHac/Common/FixedArrays/Array12.cs create mode 100644 src/LibHac/Common/FixedArrays/Array2.cs create mode 100644 src/LibHac/Common/FixedArrays/Array3.cs create mode 100644 src/LibHac/Common/FixedArrays/Array32.cs create mode 100644 src/LibHac/Common/FixedArrays/Array4.cs create mode 100644 src/LibHac/Common/Keys/ExternalKeyReader.cs create mode 100644 src/LibHac/Common/Keys/KeySet.cs create mode 100644 src/LibHac/Crypto/KeyTypes.cs create mode 100644 tests/LibHac.Tests/Boot/TypeSizeTests.cs create mode 100644 tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs create mode 100644 tests/LibHac.Tests/Spl/TypeSizeTests.cs diff --git a/src/LibHac/Boot/KeyBlob.cs b/src/LibHac/Boot/KeyBlob.cs new file mode 100644 index 00000000..8ad397fe --- /dev/null +++ b/src/LibHac/Boot/KeyBlob.cs @@ -0,0 +1,93 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Crypto; + +namespace LibHac.Boot +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = 0xB0)] + public struct EncryptedKeyBlob + { +#if DEBUG + [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; + [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; + [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; + [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; + [FieldOffset(0x80)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy5; + [FieldOffset(0xA0)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer16 _dummy6; +#endif + + [FieldOffset(0x00)] public AesCmac Cmac; + [FieldOffset(0x10)] public AesIv Counter; + + public Span Payload => Bytes.Slice(0x20, Unsafe.SizeOf()); + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() + { + ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); + + for (int i = 0; i < ulongSpan.Length; i++) + { + if (ulongSpan[i] != 0) + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in EncryptedKeyBlob value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + public override readonly string ToString() => ReadOnlyBytes.ToHexString(); + } + + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = 0x90)] + public struct KeyBlob + { +#if DEBUG + [FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1; + [FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2; + [FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3; + [FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4; +#endif + + [FieldOffset(0x00)] public AesKey MasterKek; + [FieldOffset(0x80)] public AesKey Package1Key; + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() + { + ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); + + for (int i = 0; i < ulongSpan.Length; i++) + { + if (ulongSpan[i] != 0) + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in KeyBlob value) + { + return SpanHelpers.AsReadOnlyByteSpan(in value); + } + + public override readonly string ToString() => ReadOnlyBytes.ToHexString(); + } +} diff --git a/src/LibHac/Common/FixedArrays/Array12.cs b/src/LibHac/Common/FixedArrays/Array12.cs new file mode 100644 index 00000000..72b8710a --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array12.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array12 + { + public const int Length = 12; + + private T _item1; + private T _item2; + private T _item3; + private T _item4; + private T _item5; + private T _item6; + private T _item7; + private T _item8; + private T _item9; + private T _item10; + private T _item11; + private T _item12; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array12 value) => value.ReadOnlyItems; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array2.cs b/src/LibHac/Common/FixedArrays/Array2.cs new file mode 100644 index 00000000..1e9529ff --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array2.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array2 + { + public const int Length = 2; + + private T _item1; + private T _item2; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array2 value) => value.ReadOnlyItems; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array3.cs b/src/LibHac/Common/FixedArrays/Array3.cs new file mode 100644 index 00000000..ece69cc2 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array3.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array3 + { + public const int Length = 3; + + private T _item1; + private T _item2; + private T _item3; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array3 value) => value.ReadOnlyItems; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array32.cs b/src/LibHac/Common/FixedArrays/Array32.cs new file mode 100644 index 00000000..480e20a2 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array32.cs @@ -0,0 +1,53 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array32 + { + public const int Length = 32; + + private T _item1; + private T _item2; + private T _item3; + private T _item4; + private T _item5; + private T _item6; + private T _item7; + private T _item8; + private T _item9; + private T _item10; + private T _item11; + private T _item12; + private T _item13; + private T _item14; + private T _item15; + private T _item16; + private T _item17; + private T _item18; + private T _item19; + private T _item20; + private T _item21; + private T _item22; + private T _item23; + private T _item24; + private T _item25; + private T _item26; + private T _item27; + private T _item28; + private T _item29; + private T _item30; + private T _item31; + private T _item32; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array32 value) => value.ReadOnlyItems; + } +} diff --git a/src/LibHac/Common/FixedArrays/Array4.cs b/src/LibHac/Common/FixedArrays/Array4.cs new file mode 100644 index 00000000..de0d0737 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array4.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array4 + { + public const int Length = 4; + + private T _item1; + private T _item2; + private T _item3; + private T _item4; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array4 value) => value.ReadOnlyItems; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs new file mode 100644 index 00000000..552b3652 --- /dev/null +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Spl; + +namespace LibHac.Common.Keys +{ + public static class ExternalKeyReader + { + [DebuggerDisplay("{" + nameof(Name) + "}")] + public readonly struct KeyInfo + { + public readonly string Name; + public readonly KeyGetter Getter; + public readonly int Group; + public readonly KeyRangeType RangeType; + public readonly KeyType Type; + public readonly byte RangeStart; + public readonly byte RangeEnd; + + public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0); + + public delegate Span KeyGetter(KeySet keySet, int i); + + public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc) + { + Assert.AssertTrue(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Single; + Type = type; + RangeStart = default; + RangeEnd = default; + Group = group; + Getter = retrieveFunc; + } + + public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc) + { + Assert.AssertTrue(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Range; + Type = type; + RangeStart = rangeStart; + RangeEnd = rangeEnd; + Group = group; + Getter = retrieveFunc; + } + + public bool Matches(string keyName, out int keyIndex) + { + keyIndex = default; + + if (RangeType == KeyRangeType.Single) + { + return keyName == Name; + } + else if (RangeType == KeyRangeType.Range) + { + // Check that the length of the key name with the trailing index matches + if (keyName.Length != Name.Length + 3) + return false; + + // Check if the name before the "_XX" index matches + if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + return false; + + // The name should have an underscore before the index value + if (keyName[keyName.Length - 3] != '_') + return false; + + byte index = default; + + // Try to get the index of the key name + if (!Utilities.TryToBytes(keyName.AsSpan(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index))) + return false; + + // Check if the index is in this key's range + if (index < RangeStart || index >= RangeEnd) + return false; + + keyIndex = index; + return true; + } + + return false; + } + + private static bool IsKeyTypeValid(KeyType type) + { + // Make sure the type has exactly one flag set for each type + KeyType type1 = type & (KeyType.Common | KeyType.Device); + KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived); + + bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device; + bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived; + + return isValid1 && isValid2; + } + } + + public enum KeyRangeType : byte + { + Single, + Range + } + + [Flags] + public enum KeyType : byte + { + Common = 1 << 0, + Device = 1 << 1, + Root = 1 << 2, + Seed = 1 << 3, + Derived = 1 << 4, + + CommonRoot = Common | Root, + CommonSeed = Common | Seed, + CommonDrvd = Common | Derived, + DeviceRoot = Device | Root, + DeviceSeed = Device | Seed, + DeviceDrvd = Device | Derived, + } + + private const int TitleKeySize = 0x10; + + public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, + string consoleKeysFilename = null, IProgressReport logger = null) + { + List keyInfos = CreateKeyList(); + + if (filename != null) ReadMainKeys(keySet, filename, keyInfos, logger); + if (consoleKeysFilename != null) ReadMainKeys(keySet, consoleKeysFilename, keyInfos, logger); + if (titleKeysFilename != null) ReadTitleKeys(keySet, titleKeysFilename, logger); + + keySet.DeriveKeys(logger); + } + + public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, + string consoleKeysFilename = null, IProgressReport logger = null, bool dev = false) + { + var keySet = new KeySet(); + //keyset.KeysetForDev = dev; + ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); + + return keySet; + } + + private static void ReadMainKeys(KeySet keySet, string filename, List keyList, IProgressReport logger = null) + { + if (filename == null) return; + + using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + string line; + while ((line = reader.ReadLine()) != null) + { + string[] a = line.Split(',', '='); + if (a.Length != 2) continue; + + string keyName = a[0].Trim(); + string valueStr = a[1].Trim(); + + if (!TryGetKeyInfo(out KeyInfo info, out int keyIndex, keyList, keyName)) + { + logger?.LogMessage($"Failed to match key {keyName}"); + continue; + } + + Span key = info.Getter(keySet, keyIndex); + + if (valueStr.Length != key.Length * 2) + { + logger?.LogMessage($"Key {keyName} had incorrect size {valueStr.Length}. Must be {key.Length * 2} hex digits."); + continue; + } + + if (!Utilities.TryToBytes(valueStr, key)) + { + key.Clear(); + + logger?.LogMessage($"Key {keyName} had an invalid value. Must be {key.Length * 2} hex digits."); + } + } + } + } + + private static void ReadTitleKeys(KeySet keySet, string filename, IProgressReport progress = null) + { + if (filename == null) return; + + using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + { + string line; + while ((line = reader.ReadLine()) != null) + { + string[] splitLine; + + // Some people use pipes as delimiters + if (line.Contains('|')) + { + splitLine = line.Split('|'); + } + else + { + splitLine = line.Split(',', '='); + } + + if (splitLine.Length < 2) continue; + + if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) + { + progress?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); + continue; + } + + if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) + { + progress?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); + continue; + } + + if (rightsId.Length != TitleKeySize) + { + progress?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); + continue; + } + + if (titleKey.Length != TitleKeySize) + { + progress?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); + continue; + } + + keySet.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); + } + } + } + + private static bool TryGetKeyInfo(out KeyInfo info, out int keyIndex, List keyList, string keyName) + { + for (int i = 0; i < keyList.Count; i++) + { + if (keyList[i].Matches(keyName, out keyIndex)) + { + info = keyList[i]; + return true; + } + } + + info = default; + keyIndex = default; + return false; + } + + public static string PrintKeys(KeySet keySet, List keys, KeyType filter) + { + if (keys.Count == 0) return string.Empty; + + var sb = new StringBuilder(); + int maxNameLength = keys.Max(x => x.NameLength); + int currentGroup = 0; + + bool FilterMatches(KeyInfo keyInfo) + { + KeyType filter1 = filter & (KeyType.Common | KeyType.Device); + KeyType filter2 = filter & (KeyType.Root | KeyType.Seed | KeyType.Derived); + + // Skip sub-filters that have no flags set + return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && + (filter2 == 0 || (filter2 & keyInfo.Type) != 0); + } + + bool isFirstPrint = true; + + foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches) + .OrderBy(x => x.Group).ThenBy(x => x.Name)) + { + bool isNewGroup = false; + + if (info.Group == currentGroup + 1) + { + currentGroup = info.Group; + } + else if (info.Group > currentGroup) + { + // Don't update the current group yet because if this key is empty and the next key + // is in the same group, we need to be able to know to add a blank line before printing it. + isNewGroup = !isFirstPrint; + } + + if (info.RangeType == KeyRangeType.Single) + { + Span key = info.Getter(keySet, 0); + if (key.IsEmpty()) + continue; + + if (isNewGroup) + { + sb.AppendLine(); + } + + string line = $"{info.Name.PadRight(maxNameLength)} = {key.ToHexString()}"; + sb.AppendLine(line); + isFirstPrint = false; + currentGroup = info.Group; + } + else if (info.RangeType == KeyRangeType.Range) + { + bool hasPrintedKey = false; + + for (int i = info.RangeStart; i < info.RangeEnd; i++) + { + Span key = info.Getter(keySet, i); + if (key.IsEmpty()) + continue; + + if (hasPrintedKey == false) + { + if (isNewGroup) + { + sb.AppendLine(); + } + + hasPrintedKey = true; + } + + string keyName = $"{info.Name}_{i:x2}"; + + string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; + sb.AppendLine(line); + isFirstPrint = false; + currentGroup = info.Group; + } + } + } + + return sb.ToString(); + } + + public static string PrintTitleKeys(KeySet keySet) + { + var sb = new StringBuilder(); + + foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList() + .OrderBy(x => x.rightsId.ToString())) + { + string line = $"{kv.rightsId} = {kv.key}"; + sb.AppendLine(line); + } + + return sb.ToString(); + } + + public static string PrintCommonKeys(KeySet keySet) + { + return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed); + } + + public static string PrintDeviceKeys(KeySet keySet) + { + return PrintKeys(keySet, CreateKeyList(), KeyType.Device); + } + + public static string PrintAllKeys(KeySet keySet) + { + return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Device); + } + + public static List CreateKeyList() + { + // Update this value if more keys are added + var keys = new List(70); + + // Keys with a group value of -1 are keys that will be read but not written. + // This is for compatibility since some keys had other names in the past. + + // TSEC secrets aren't public yet, so the TSEC root keys will be treated as + // root keys even though they're derived. + + keys.Add(new KeyInfo(10, KeyType.DeviceRoot, "secure_boot_key", (set, i) => set.SecureBootKey)); + keys.Add(new KeyInfo(11, KeyType.DeviceRoot, "tsec_key", (set, i) => set.TsecKey)); + keys.Add(new KeyInfo(12, KeyType.DeviceDrvd, "device_key", (set, i) => set.DeviceKey)); + + keys.Add(new KeyInfo(20, KeyType.CommonRoot, "tsec_root_kek", (set, i) => set.TsecRootKek)); + keys.Add(new KeyInfo(21, KeyType.CommonRoot, "package1_mac_kek", (set, i) => set.Package1MacKek)); + keys.Add(new KeyInfo(22, KeyType.CommonRoot, "package1_kek", (set, i) => set.Package1Kek)); + + keys.Add(new KeyInfo(30, KeyType.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i])); + + keys.Add(new KeyInfo(40, KeyType.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i])); + + keys.Add(new KeyInfo(50, KeyType.CommonSeed, "keyblob_mac_key_source", (set, i) => set.KeyBlobMacKeySource)); + keys.Add(new KeyInfo(51, KeyType.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i])); + + keys.Add(new KeyInfo(55, KeyType.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i])); + + keys.Add(new KeyInfo(60, KeyType.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i])); + + keys.Add(new KeyInfo(70, KeyType.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(80, KeyType.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(90, KeyType.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i])); + + keys.Add(new KeyInfo(100, KeyType.CommonRoot, "mariko_bek", (set, i) => set.MarikoBek)); + keys.Add(new KeyInfo(101, KeyType.CommonRoot, "mariko_kek", (set, i) => set.MarikoKek)); + + keys.Add(new KeyInfo(110, KeyType.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); + keys.Add(new KeyInfo(120, KeyType.CommonSeed, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); + keys.Add(new KeyInfo(130, KeyType.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); + keys.Add(new KeyInfo(140, KeyType.CommonDrvd, "master_key_source", (set, i) => set.MasterKeySource)); + keys.Add(new KeyInfo(150, KeyType.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); + + keys.Add(new KeyInfo(160, KeyType.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); + keys.Add(new KeyInfo(170, KeyType.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i])); + keys.Add(new KeyInfo(180, KeyType.CommonSeed, "package2_key_source", (set, i) => set.Package2KeySource)); + keys.Add(new KeyInfo(190, KeyType.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i])); + + keys.Add(new KeyInfo(200, KeyType.CommonSeed, "bis_kek_source", (set, i) => set.BisKekSource)); + keys.Add(new KeyInfo(201, KeyType.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i])); + + keys.Add(new KeyInfo(205, KeyType.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i])); + + keys.Add(new KeyInfo(210, KeyType.CommonSeed, "per_console_key_source", (set, i) => set.PerConsoleKeySource)); + keys.Add(new KeyInfo(211, KeyType.CommonSeed, "retail_specific_aes_key_source", (set, i) => set.RetailSpecificAesKeySource)); + keys.Add(new KeyInfo(212, KeyType.CommonSeed, "aes_kek_generation_source", (set, i) => set.AesKekGenerationSource)); + keys.Add(new KeyInfo(213, KeyType.CommonSeed, "aes_key_generation_source", (set, i) => set.AesKeyGenerationSource)); + keys.Add(new KeyInfo(214, KeyType.CommonSeed, "titlekek_source", (set, i) => set.TitleKekSource)); + + keys.Add(new KeyInfo(220, KeyType.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i])); + + keys.Add(new KeyInfo(230, KeyType.CommonSeed, "header_kek_source", (set, i) => set.HeaderKekSource)); + keys.Add(new KeyInfo(231, KeyType.CommonSeed, "header_key_source", (set, i) => set.HeaderKeySource)); + keys.Add(new KeyInfo(232, KeyType.CommonDrvd, "header_key", (set, i) => set.HeaderKey)); + + keys.Add(new KeyInfo(240, KeyType.CommonSeed, "key_area_key_application_source", (set, i) => set.KeyAreaKeyApplicationSource)); + keys.Add(new KeyInfo(241, KeyType.CommonSeed, "key_area_key_ocean_source", (set, i) => set.KeyAreaKeyOceanSource)); + keys.Add(new KeyInfo(242, KeyType.CommonSeed, "key_area_key_system_source", (set, i) => set.KeyAreaKeySystemSource)); + + keys.Add(new KeyInfo(250, KeyType.CommonSeed, "save_mac_kek_source", (set, i) => set.DeviceUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(251, KeyType.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i])); + keys.Add(new KeyInfo(252, KeyType.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i])); + keys.Add(new KeyInfo(-01, KeyType.CommonSeed, "save_mac_key_source", (set, i) => set.DeviceUniqueSaveMacKeySources[0])); + + keys.Add(new KeyInfo(253, KeyType.CommonSeed, "save_mac_sd_card_kek_source", (set, i) => set.SeedUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(254, KeyType.CommonSeed, "save_mac_sd_card_key_source", (set, i) => set.SeedUniqueSaveMacKeySource)); + keys.Add(new KeyInfo(255, KeyType.DeviceDrvd, "save_mac_sd_card_key", (set, i) => set.SeedUniqueSaveMacKey)); + + keys.Add(new KeyInfo(260, KeyType.DeviceRoot, "sd_seed", (set, i) => set.SdCardEncryptionSeed)); + + keys.Add(new KeyInfo(261, KeyType.CommonSeed, "sd_card_kek_source", (set, i) => set.SdCardKekSource)); + keys.Add(new KeyInfo(262, KeyType.CommonSeed, "sd_card_save_key_source", (set, i) => set.SdCardKeySources[0])); + keys.Add(new KeyInfo(263, KeyType.CommonSeed, "sd_card_nca_key_source", (set, i) => set.SdCardKeySources[1])); + keys.Add(new KeyInfo(264, KeyType.CommonSeed, "sd_card_custom_storage_key_source", (set, i) => set.SdCardKeySources[2])); + + keys.Add(new KeyInfo(270, KeyType.CommonRoot, "xci_header_key", (set, i) => set.XciHeaderKey)); + + keys.Add(new KeyInfo(280, KeyType.CommonRoot, "eticket_rsa_kek", (set, i) => set.EticketRsaKek)); + keys.Add(new KeyInfo(281, KeyType.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek)); + + keys.Add(new KeyInfo(290, KeyType.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); + keys.Add(new KeyInfo(300, KeyType.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1])); + keys.Add(new KeyInfo(310, KeyType.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2])); + + return keys; + } + } +} diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs new file mode 100644 index 00000000..f2097537 --- /dev/null +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -0,0 +1,438 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Boot; +using LibHac.Common.FixedArrays; +using LibHac.Crypto; +using LibHac.FsSrv; + +namespace LibHac.Common.Keys +{ + public class KeySet + { + /// + /// The number of keyblobs that were used for < 6.2.0 crypto + /// + private const int UsedKeyBlobCount = 6; + private const int SdCardKeyIdCount = 3; + private const int KeyRevisionCount = 0x20; + + private AllKeys _keys; + public ref AllKeys KeyStruct => ref _keys; + + public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet(); + + public Span MarikoAesClassKeys => _keys._rootKeys.MarikoAesClassKeys.Items; + public ref AesKey MarikoKek => ref _keys._rootKeys.MarikoKek; + public ref AesKey MarikoBek => ref _keys._rootKeys.MarikoBek; + public Span KeyBlobs => _keys._rootKeys.KeyBlobs.Items; + public Span KeyBlobKeySources => _keys._keySeeds.KeyBlobKeySources.Items; + public ref AesKey KeyBlobMacKeySource => ref _keys._keySeeds.KeyBlobMacKeySource; + public ref AesKey TsecRootKek => ref _keys._rootKeys.TsecRootKek; + public ref AesKey Package1MacKek => ref _keys._rootKeys.Package1MacKek; + public ref AesKey Package1Kek => ref _keys._rootKeys.Package1Kek; + public Span TsecAuthSignatures => _keys._rootKeys.TsecAuthSignatures.Items; + public Span TsecRootKeys => _keys._rootKeys.TsecRootKeys.Items; + public Span MasterKekSources => _keys._keySeeds.MasterKekSources.Items; + public Span MarikoMasterKekSources => _keys._keySeeds.MarikoMasterKekSources.Items; + public Span MasterKeks => _keys._derivedKeys.MasterKeks.Items; + public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource; + public Span MasterKeys => _keys._derivedKeys.MasterKeys.Items; + public Span Package1MacKeys => _keys._derivedKeys.Package1MacKeys.Items; + public Span Package1Keys => _keys._derivedKeys.Package1Keys.Items; + public Span Package2Keys => _keys._derivedKeys.Package2Keys.Items; + public ref AesKey Package2KeySource => ref _keys._keySeeds.Package2KeySource; + public ref AesKey PerConsoleKeySource => ref _keys._keySeeds.PerConsoleKeySource; + public ref AesKey RetailSpecificAesKeySource => ref _keys._keySeeds.RetailSpecificAesKeySource; + public ref AesKey BisKekSource => ref _keys._keySeeds.BisKekSource; + public Span BisKeySources => _keys._keySeeds.BisKeySources.Items; + public ref AesKey AesKekGenerationSource => ref _keys._keySeeds.AesKekGenerationSource; + public ref AesKey AesKeyGenerationSource => ref _keys._keySeeds.AesKeyGenerationSource; + public ref AesKey KeyAreaKeyApplicationSource => ref _keys._keySeeds.KeyAreaKeyApplicationSource; + public ref AesKey KeyAreaKeyOceanSource => ref _keys._keySeeds.KeyAreaKeyOceanSource; + public ref AesKey KeyAreaKeySystemSource => ref _keys._keySeeds.KeyAreaKeySystemSource; + public ref AesKey TitleKekSource => ref _keys._keySeeds.TitleKekSource; + public ref AesKey HeaderKekSource => ref _keys._keySeeds.HeaderKekSource; + public ref AesKey SdCardKekSource => ref _keys._keySeeds.SdCardKekSource; + public Span SdCardKeySources => _keys._keySeeds.SdCardKeySources.Items; + public ref AesKey DeviceUniqueSaveMacKekSource => ref _keys._keySeeds.DeviceUniqueSaveMacKekSource; + public Span DeviceUniqueSaveMacKeySources => _keys._keySeeds.DeviceUniqueSaveMacKeySources.Items; + public ref AesKey SeedUniqueSaveMacKekSource => ref _keys._keySeeds.SeedUniqueSaveMacKekSource; + public ref AesKey SeedUniqueSaveMacKeySource => ref _keys._keySeeds.SeedUniqueSaveMacKeySource; + public ref AesXtsKey HeaderKeySource => ref _keys._keySeeds.HeaderKeySource; + public ref AesXtsKey HeaderKey => ref _keys._derivedKeys.HeaderKey; + public Span TitleKeks => _keys._derivedKeys.TitleKeks.Items; + public Span> KeyAreaKeys => _keys._derivedKeys.KeyAreaKeys.Items; + public ref AesKey XciHeaderKey => ref _keys._rootKeys.XciHeaderKey; + public ref AesKey EticketRsaKek => ref _keys._derivedKeys.EticketRsaKek; + public ref AesKey SslRsaKek => ref _keys._derivedKeys.SslRsaKek; + + public ref AesKey SecureBootKey => ref _keys._deviceKeys.SecureBootKey; + public ref AesKey TsecKey => ref _keys._deviceKeys.TsecKey; + public Span KeyBlobKeys => _keys._deviceKeys.KeyBlobKeys.Items; + public Span KeyBlobMacKeys => _keys._deviceKeys.KeyBlobMacKeys.Items; + public Span EncryptedKeyBlobs => _keys._deviceKeys.EncryptedKeyBlobs.Items; + public ref AesKey DeviceKey => ref _keys._deviceKeys.DeviceKey; + public Span BisKeys => _keys._deviceKeys.BisKeys.Items; + public Span DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items; + public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey; + public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed; + public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; + + public void SetSdSeed(ReadOnlySpan sdSeed) + { + if (sdSeed.Length != 0x10) + throw new ArgumentException("Sd card encryption seed must be 16 bytes long."); + + sdSeed.CopyTo(SdCardEncryptionSeed); + DeriveSdCardKeys(); + } + + public void DeriveKeys(IProgressReport logger = null) + { + DeriveKeyBlobKeys(); + DecryptKeyBlobs(logger); + ReadKeyBlobs(); + + Derive620MasterKeks(); + DeriveMarikoMasterKeks(); + DeriveMasterKeys(); + + DerivePerConsoleKeys(); + DerivePerFirmwareKeys(); + DeriveNcaHeaderKey(); + DeriveSdCardKeys(); + } + + private void DeriveKeyBlobKeys() + { + if (SecureBootKey.IsEmpty() || TsecKey.IsEmpty()) return; + + bool haveKeyBlobMacKeySource = !MasterKeySource.IsEmpty(); + var temp = new AesKey(); + + for (int i = 0; i < UsedKeyBlobCount; i++) + { + if (KeyBlobKeySources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(KeyBlobKeySources[i], temp, TsecKey); + Aes.DecryptEcb128(temp, KeyBlobKeys[i], SecureBootKey); + + if (!haveKeyBlobMacKeySource) continue; + + Aes.DecryptEcb128(KeyBlobMacKeySource, KeyBlobMacKeys[i], KeyBlobKeys[i]); + } + } + + private void DecryptKeyBlobs(IProgressReport logger = null) + { + var cmac = new AesCmac(); + + for (int i = 0; i < UsedKeyBlobCount; i++) + { + if (KeyBlobKeys[i].IsEmpty() || KeyBlobMacKeys[i].IsEmpty() || EncryptedKeyBlobs[i].IsEmpty()) + { + continue; + } + + Aes.CalculateCmac(cmac, EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), KeyBlobMacKeys[i]); + + if (!Utilities.SpansEqual(cmac, EncryptedKeyBlobs[i].Cmac)) + { + logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); + } + + Aes.DecryptCtr128(EncryptedKeyBlobs[i].Bytes.Slice(0x20), KeyBlobs[i].Bytes, KeyBlobKeys[i], + EncryptedKeyBlobs[i].Counter); + } + } + + private void ReadKeyBlobs() + { + for (int i = 0; i < UsedKeyBlobCount; i++) + { + if (KeyBlobs[i].IsEmpty()) continue; + + MasterKeks[i] = KeyBlobs[i].MasterKek; + Package1Keys[i] = KeyBlobs[i].Package1Key; + } + } + + private void Derive620MasterKeks() + { + for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) + { + // Key revisions >= 8 all use the same TSEC root key + int tsecRootKeyIndex = Math.Min(i, 8) - UsedKeyBlobCount; + if (TsecRootKeys[tsecRootKeyIndex].IsEmpty() || MasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(MasterKekSources[i], MasterKeks[i], TsecRootKeys[tsecRootKeyIndex]); + } + } + + private void DeriveMarikoMasterKeks() + { + if (MarikoKek.IsEmpty()) return; + + for (int i = 0; i < KeyRevisionCount; i++) + { + if (MarikoMasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(MarikoMasterKekSources[i], MasterKeks[i], MarikoKek); + } + } + + private void DeriveMasterKeys() + { + if (MasterKeySource.IsEmpty()) return; + + for (int i = 0; i < KeyRevisionCount; i++) + { + if (MasterKeks[i].IsEmpty()) continue; + + Aes.DecryptEcb128(MasterKeySource, MasterKeys[i], MasterKeks[i]); + } + } + + private void DerivePerConsoleKeys() + { + var kek = new AesKey(); + + // Derive the device key + if (!PerConsoleKeySource.IsEmpty() && !KeyBlobKeys[0].IsEmpty()) + { + Aes.DecryptEcb128(PerConsoleKeySource, DeviceKey, KeyBlobKeys[0]); + } + + // Derive device-unique save keys + for (int i = 0; i < DeviceUniqueSaveMacKeySources.Length; i++) + { + if (!DeviceUniqueSaveMacKekSource.IsEmpty() && !DeviceUniqueSaveMacKeySources[i].IsEmpty() && + !DeviceKey.IsEmpty()) + { + GenerateKek(DeviceKey, DeviceUniqueSaveMacKekSource, kek, AesKekGenerationSource, null); + Aes.DecryptEcb128(DeviceUniqueSaveMacKeySources[i], DeviceUniqueSaveMacKeys[i], kek); + } + } + + // Derive BIS keys + if (DeviceKey.IsEmpty() + || BisKekSource.IsEmpty() + || AesKekGenerationSource.IsEmpty() + || AesKeyGenerationSource.IsEmpty() + || RetailSpecificAesKeySource.IsEmpty()) + { + return; + } + + // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 + if (BisKeySources[3].IsEmpty() && !BisKeySources[2].IsEmpty()) + { + BisKeySources[3] = BisKeySources[2]; + } + + Aes.DecryptEcb128(RetailSpecificAesKeySource, kek, DeviceKey); + if (!BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(BisKeySources[0], BisKeys[0], kek); + + GenerateKek(DeviceKey, BisKekSource, kek, AesKekGenerationSource, AesKeyGenerationSource); + + for (int i = 1; i < 4; i++) + { + if (!BisKeySources[i].IsEmpty()) + Aes.DecryptEcb128(BisKeySources[i], BisKeys[i], kek); + } + } + + private void DerivePerFirmwareKeys() + { + bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty(); + bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty(); + bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty(); + bool haveTitleKekSource = !TitleKekSource.IsEmpty(); + bool havePackage2KeySource = !Package2KeySource.IsEmpty(); + + for (int i = 0; i < KeyRevisionCount; i++) + { + if (MasterKeys[i].IsEmpty()) + { + continue; + } + + if (haveKakSource0) + { + GenerateKek(MasterKeys[i], KeyAreaKeyApplicationSource, KeyAreaKeys[i][0], + AesKekGenerationSource, AesKeyGenerationSource); + } + + if (haveKakSource1) + { + GenerateKek(MasterKeys[i], KeyAreaKeyOceanSource, KeyAreaKeys[i][1], + AesKekGenerationSource, AesKeyGenerationSource); + } + + if (haveKakSource2) + { + GenerateKek(MasterKeys[i], KeyAreaKeySystemSource, KeyAreaKeys[i][2], + AesKekGenerationSource, AesKeyGenerationSource); + } + + if (haveTitleKekSource) + { + Aes.DecryptEcb128(TitleKekSource, TitleKeks[i], MasterKeys[i]); + } + + if (havePackage2KeySource) + { + Aes.DecryptEcb128(Package2KeySource, Package2Keys[i], MasterKeys[i]); + } + } + } + + private void DeriveNcaHeaderKey() + { + if (HeaderKekSource.IsEmpty() || HeaderKeySource.IsEmpty() || MasterKeys[0].IsEmpty()) return; + + var headerKek = new AesKey(); + + GenerateKek(MasterKeys[0], HeaderKekSource, headerKek, AesKekGenerationSource, + AesKeyGenerationSource); + Aes.DecryptEcb128(HeaderKeySource, HeaderKey, headerKek); + } + + public void DeriveSdCardKeys() + { + var sdKek = new AesKey(); + var tempKey = new AesXtsKey(); + GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource); + + for (int k = 0; k < SdCardKeyIdCount; k++) + { + for (int i = 0; i < 4; i++) + { + tempKey.Data64[i] = SdCardKeySources[k].Data64[i] ^ SdCardEncryptionSeed.Data64[i & 1]; + } + + tempKey.Data64[0] = SdCardKeySources[k].Data64[0] ^ SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[1] = SdCardKeySources[k].Data64[1] ^ SdCardEncryptionSeed.Data64[1]; + tempKey.Data64[2] = SdCardKeySources[k].Data64[2] ^ SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[3] = SdCardKeySources[k].Data64[3] ^ SdCardEncryptionSeed.Data64[1]; + + Aes.DecryptEcb128(tempKey, SdCardEncryptionKeys[k], sdKek); + } + + // Derive sd card save key + if (!SeedUniqueSaveMacKekSource.IsEmpty() && !SeedUniqueSaveMacKeySource.IsEmpty()) + { + var keySource = new AesKey(); + + keySource.Data64[0] = SeedUniqueSaveMacKeySource.Data64[0] ^ SdCardEncryptionSeed.Data64[0]; + keySource.Data64[1] = SeedUniqueSaveMacKeySource.Data64[1] ^ SdCardEncryptionSeed.Data64[1]; + + GenerateKek(MasterKeys[0], SeedUniqueSaveMacKekSource, sdKek, AesKekGenerationSource, null); + Aes.DecryptEcb128(keySource, SeedUniqueSaveMacKey, sdKek); + } + } + + private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, + ReadOnlySpan kekSeed, ReadOnlySpan keySeed) + { + var kek = new AesKey(); + var srcKek = new AesKey(); + + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); + + if (!keySeed.IsEmpty) + { + Aes.DecryptEcb128(keySeed, dest, srcKek); + } + else + { + srcKek.Data.CopyTo(dest); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct AllKeys + { + public RootKeys _rootKeys; + public KeySeeds _keySeeds; + public DerivedKeys _derivedKeys; + public DeviceKeys _deviceKeys; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RootKeys + { + public Array12 MarikoAesClassKeys; + public AesKey MarikoKek; + public AesKey MarikoBek; + public Array32 KeyBlobs; + public AesKey TsecRootKek; + public AesKey Package1MacKek; + public AesKey Package1Kek; + public Array32 TsecAuthSignatures; + public Array32 TsecRootKeys; + public AesKey XciHeaderKey; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KeySeeds + { + public Array32 KeyBlobKeySources; + public AesKey KeyBlobMacKeySource; + public Array32 MasterKekSources; + public Array32 MarikoMasterKekSources; + public AesKey MasterKeySource; + public AesKey Package2KeySource; + public AesKey PerConsoleKeySource; + public AesKey RetailSpecificAesKeySource; + public AesKey BisKekSource; + public Array4 BisKeySources; + public AesKey AesKekGenerationSource; + public AesKey AesKeyGenerationSource; + public AesKey KeyAreaKeyApplicationSource; + public AesKey KeyAreaKeyOceanSource; + public AesKey KeyAreaKeySystemSource; + public AesKey TitleKekSource; + public AesKey HeaderKekSource; + public AesKey SdCardKekSource; + public Array3 SdCardKeySources; + public AesKey DeviceUniqueSaveMacKekSource; + public Array2 DeviceUniqueSaveMacKeySources; + public AesKey SeedUniqueSaveMacKekSource; + public AesKey SeedUniqueSaveMacKeySource; + public AesXtsKey HeaderKeySource; + } + + [StructLayout(LayoutKind.Sequential)] + public struct DerivedKeys + { + public Array32 MasterKeks; + public Array32 MasterKeys; + public Array32 Package1MacKeys; + public Array32 Package1Keys; + public Array32 Package2Keys; + public Array32> KeyAreaKeys; + public Array32 TitleKeks; + public AesXtsKey HeaderKey; + public AesKey EticketRsaKek; + public AesKey SslRsaKek; + } + + [StructLayout(LayoutKind.Sequential)] + public struct DeviceKeys + { + public AesKey SecureBootKey; + public AesKey TsecKey; + public Array32 KeyBlobKeys; + public Array32 KeyBlobMacKeys; + public Array32 EncryptedKeyBlobs; + public AesKey DeviceKey; + public Array4 BisKeys; + public Array2 DeviceUniqueSaveMacKeys; + public AesKey SeedUniqueSaveMacKey; + public AesKey SdCardEncryptionSeed; + public Array3 SdCardEncryptionKeys; + } +} diff --git a/src/LibHac/Crypto/Aes.cs b/src/LibHac/Crypto/Aes.cs index 17652cf4..51a8bdba 100644 --- a/src/LibHac/Crypto/Aes.cs +++ b/src/LibHac/Crypto/Aes.cs @@ -1,5 +1,6 @@ // ReSharper disable AssignmentIsFullyDiscarded using System; +using System.Runtime.CompilerServices; using LibHac.Diag; #if HAS_INTRINSICS using LibHac.Crypto.Detail; @@ -133,6 +134,7 @@ namespace LibHac.Crypto cipher.Transform(input, output); } + [MethodImpl(MethodImplOptions.NoInlining)] public static void DecryptEcb128(ReadOnlySpan input, Span output, ReadOnlySpan key, bool preferDotNetCrypto = false) { diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs new file mode 100644 index 00000000..e6871910 --- /dev/null +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -0,0 +1,118 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.Crypto +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct AesKey + { + private const int Size = 0x10; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + + public static implicit operator Span(in AesKey value) => Unsafe.AsRef(in value).Data; + + public static implicit operator ReadOnlySpan(in AesKey value) => value.DataRo; + + public override readonly string ToString() => DataRo.ToHexString(); + +#if DEBUG + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; +#endif + } + + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct AesXtsKey + { + private const int Size = 0x20; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + [FieldOffset(0)] public AesKey DataKey; + [FieldOffset(0)] public AesKey TweakKey; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + public Span SubKeys => SpanHelpers.CreateSpan(ref DataKey, Size / Unsafe.SizeOf()); + + public static implicit operator Span(in AesXtsKey value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesXtsKey value) => value.DataRo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0; + + public override readonly string ToString() => DataRo.ToHexString(); + } + + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct AesIv + { + private const int Size = 0x10; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + + public static implicit operator Span(in AesIv value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesIv value) => value.DataRo; + + public override readonly string ToString() => DataRo.ToHexString(); + +#if DEBUG + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; +#endif + } + + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct AesCmac + { + private const int Size = 0x10; + + [FieldOffset(0)] private byte _byte; + [FieldOffset(0)] private ulong _ulong; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); + public Span Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong)); + public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + + public static implicit operator Span(in AesCmac value) => Unsafe.AsRef(in value).Data; + public static implicit operator ReadOnlySpan(in AesCmac value) => value.DataRo; + + public override readonly string ToString() => DataRo.ToHexString(); + +#if DEBUG + [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; +#endif + } +} diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index 6cb05822..9c3d2556 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -29,6 +29,7 @@ namespace LibHac public byte[] KeyblobMacKeySource { get; } = new byte[0x10]; public byte[][] TsecRootKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); public byte[][] MasterKekSources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); + public byte[][] MarikoMasterKekSources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); public byte[][] MasterKeks { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); public byte[] MasterKeySource { get; } = new byte[0x10]; public byte[][] MasterKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); @@ -806,6 +807,7 @@ namespace LibHac keys.Add(new KeyValue($"keyblob_{i:x2}", 0x90, 10, set => set.Keyblobs[i])); keys.Add(new KeyValue($"tsec_root_key_{i:x2}", 0x10, 20, set => set.TsecRootKeys[i])); keys.Add(new KeyValue($"master_kek_source_{i:x2}", 0x10, 30, set => set.MasterKekSources[i])); + keys.Add(new KeyValue($"mariko_master_kek_source_{i:x2}", 0x10, 35, set => set.MarikoMasterKekSources[i])); keys.Add(new KeyValue($"master_kek_{i:x2}", 0x10, 40, set => set.MasterKeks[i])); keys.Add(new KeyValue($"package1_key_{i:x2}", 0x10, 50, set => set.Package1Keys[i])); diff --git a/src/LibHac/Package1.cs b/src/LibHac/Package1.cs index f8dc9947..94c9a34c 100644 --- a/src/LibHac/Package1.cs +++ b/src/LibHac/Package1.cs @@ -5,6 +5,7 @@ using LibHac.FsSystem; namespace LibHac { + [Obsolete("This class has been deprecated. LibHac.Boot.Package1 should be used instead.")] public class Package1 { private const uint Pk11Magic = 0x31314B50; // PK11 diff --git a/src/LibHac/Utilities.cs b/src/LibHac/Utilities.cs index 304c22b6..1b6a5a78 100644 --- a/src/LibHac/Utilities.cs +++ b/src/LibHac/Utilities.cs @@ -62,6 +62,11 @@ namespace LibHac return a1.SequenceEqual(a2); } + public static bool SpansEqual(ReadOnlySpan a1, ReadOnlySpan a2) where T : IEquatable + { + return a1.SequenceEqual(a2); + } + public static ReadOnlySpan GetUtf8Bytes(string value) { return Encoding.UTF8.GetBytes(value).AsSpan(); @@ -84,6 +89,7 @@ namespace LibHac } public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan)array).IsEmpty(); + public static bool IsEmpty(this Span span) => ((ReadOnlySpan)span).IsEmpty(); public static bool IsEmpty(this ReadOnlySpan span) { @@ -319,6 +325,26 @@ namespace LibHac return true; } + public static bool TryToBytes(this ReadOnlySpan input, Span output) + { + if (input.Length != output.Length * 2) + return false; + + int lastcell = output.Length - 1; + int lastchar = input.Length - 1; + for (int i = 0; i < input.Length; i++) + { + if (!TryHexToInt(input[lastchar - i], out int hexInt)) + { + return false; + } + + output[lastcell - (i >> 1)] |= ByteLookup[i & 1, hexInt]; + } + + return true; + } + private static readonly uint[] Lookup32 = CreateLookup32(); private static uint[] CreateLookup32() diff --git a/tests/LibHac.Tests/Boot/TypeSizeTests.cs b/tests/LibHac.Tests/Boot/TypeSizeTests.cs new file mode 100644 index 00000000..19a93762 --- /dev/null +++ b/tests/LibHac.Tests/Boot/TypeSizeTests.cs @@ -0,0 +1,22 @@ +// ReSharper disable InconsistentNaming +using System.Runtime.CompilerServices; +using LibHac.Boot; +using Xunit; + +namespace LibHac.Tests.Boot +{ + public class TypeSizeTests + { + [Fact] + public static void EncryptedKeyBlobSizeIs0xB0() + { + Assert.Equal(0xB0, Unsafe.SizeOf()); + } + + [Fact] + public static void KeyBlobSizeIs0x90() + { + Assert.Equal(0x90, Unsafe.SizeOf()); + } + } +} diff --git a/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs b/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs new file mode 100644 index 00000000..ac2802e5 --- /dev/null +++ b/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs @@ -0,0 +1,34 @@ +// ReSharper disable InconsistentNaming +using System.Runtime.CompilerServices; +using LibHac.Crypto; +using Xunit; + +namespace LibHac.Tests.CryptoTests +{ + public class TypeSizeTests + { + [Fact] + public static void AesKeySizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + + [Fact] + public static void AesXtsKeySizeIs0x20() + { + Assert.Equal(0x20, Unsafe.SizeOf()); + } + + [Fact] + public static void AesIvSizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + + [Fact] + public static void AesCmacSizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/tests/LibHac.Tests/Spl/TypeSizeTests.cs b/tests/LibHac.Tests/Spl/TypeSizeTests.cs new file mode 100644 index 00000000..eaacd097 --- /dev/null +++ b/tests/LibHac.Tests/Spl/TypeSizeTests.cs @@ -0,0 +1,16 @@ +// ReSharper disable InconsistentNaming +using System.Runtime.CompilerServices; +using LibHac.Spl; +using Xunit; + +namespace LibHac.Tests.Spl +{ + public class TypeSizeTests + { + [Fact] + public static void AccessKeySizeIs0x10() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + } +} From 333ef8729f381bea65e069743c079da8890c78a5 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Fri, 2 Oct 2020 01:23:21 -0700 Subject: [PATCH 03/16] Fix RSA regression from 5f755bc7 and add a test --- src/LibHac/Crypto/Rsa.cs | 68 ++++++----- src/LibHac/Keyset.cs | 2 +- src/hactoolnet/ProcessNca.cs | 2 +- tests/LibHac.Tests/CryptoTests/RsaTests.cs | 129 +++++++++++++++++++++ 4 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 tests/LibHac.Tests/CryptoTests/RsaTests.cs diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs index 2ecbf826..ead858b2 100644 --- a/src/LibHac/Crypto/Rsa.cs +++ b/src/LibHac/Crypto/Rsa.cs @@ -61,7 +61,14 @@ namespace LibHac.Crypto BigInteger dq = d % (q - BigInteger.One); BigInteger inverseQ = Utilities.ModInverse(q, p); - int modLen = n.ToByteArray().Length; + byte[] nBytes = n.ToByteArray(); + int modLen = nBytes.Length; + + if (nBytes[^1] == 0) + { + modLen--; + } + int halfModLen = (modLen + 1) / 2; return new RSAParameters @@ -122,44 +129,43 @@ namespace LibHac.Crypto bool cracked = false; BigInteger y = BigInteger.Zero; - using (var rng = RandomNumberGenerator.Create()) + var rng = new Random(0); + + for (int i = 0; i < 100 && !cracked; i++) { - for (int i = 0; i < 100 && !cracked; i++) + BigInteger g; + + do { - BigInteger g; + rng.NextBytes(rndBuf); + g = Utilities.GetBigInteger(rndBuf); + } + while (g >= n); - do + y = BigInteger.ModPow(g, r, n); + + if (y.IsOne || y == nMinusOne) + { + i--; + continue; + } + + for (BigInteger j = BigInteger.One; j < t; j++) + { + BigInteger x = BigInteger.ModPow(y, two, n); + + if (x.IsOne) { - rng.GetBytes(rndBuf); - g = Utilities.GetBigInteger(rndBuf); - } - while (g >= n); - - y = BigInteger.ModPow(g, r, n); - - if (y.IsOne || y == nMinusOne) - { - i--; - continue; + cracked = true; + break; } - for (BigInteger j = BigInteger.One; j < t; j++) + if (x == nMinusOne) { - BigInteger x = BigInteger.ModPow(y, two, n); - - if (x.IsOne) - { - cracked = true; - break; - } - - if (x == nMinusOne) - { - break; - } - - y = x; + break; } + + y = x; } } diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index 9c3d2556..d56c25b0 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -85,7 +85,7 @@ namespace LibHac if (_nca0RsaKeyAreaKey.HasValue) return _nca0RsaKeyAreaKey.Value; - _nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, BetaNca0Exponent, new byte[] { 1, 0, 1 }); + _nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, new byte[] { 1, 0, 1 }, BetaNca0Exponent); return _nca0RsaKeyAreaKey.Value; } } diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index cb3287ff..d5530a23 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -280,7 +280,7 @@ namespace hactoolnet sb.AppendLine("Key Area (Decrypted):"); for (int i = 0; i < 2; i++) { - PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.GetEncryptedKey(i).ToArray()); + PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.GetDecryptedKey(i)); } } else if (version == NcaVersion.Nca0FixedKey) diff --git a/tests/LibHac.Tests/CryptoTests/RsaTests.cs b/tests/LibHac.Tests/CryptoTests/RsaTests.cs new file mode 100644 index 00000000..d46a461c --- /dev/null +++ b/tests/LibHac.Tests/CryptoTests/RsaTests.cs @@ -0,0 +1,129 @@ +using System.Security.Cryptography; +using LibHac.Crypto; +using Xunit; + +namespace LibHac.Tests.CryptoTests +{ + public class RsaTests + { + [Fact] + public void RecoverRsaParameters_ReturnsCorrectParameters() + { + RSAParameters rsaParams = Rsa.RecoverParameters(Modulus, PublicExponent, PrivateExponent); + + Assert.Equal(PrivateExponent, rsaParams.D); + Assert.Equal(ExpectedDp, rsaParams.DP); + Assert.Equal(ExpectedDq, rsaParams.DQ); + Assert.Equal(PublicExponent, rsaParams.Exponent); + Assert.Equal(ExpectedInverseQ, rsaParams.InverseQ); + Assert.Equal(Modulus, rsaParams.Modulus); + Assert.Equal(ExpectedP, rsaParams.P); + Assert.Equal(ExpectedQ, rsaParams.Q); + } + + public readonly byte[] Modulus = +{ + 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[] PrivateExponent = + { + 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 + }; + + public readonly byte[] PublicExponent = + { + 0x01, 0x00, 0x01 + }; + + public readonly byte[] ExpectedDp = + { + 0x80, 0x1f, 0x3e, 0xe6, 0xed, 0xa2, 0xff, 0x60, 0x0e, 0xc3, 0xb7, 0xf3, 0xed, 0x73, 0x95, 0x05, + 0x6d, 0x1f, 0x8a, 0xab, 0x14, 0x42, 0xa0, 0x21, 0x07, 0xe9, 0x92, 0xc7, 0x38, 0x26, 0x84, 0x60, + 0xf9, 0xb3, 0x54, 0xad, 0x13, 0xf0, 0xc7, 0xd5, 0xfb, 0xac, 0xe2, 0x40, 0x68, 0x65, 0xf5, 0xb8, + 0xc7, 0x4f, 0xd1, 0x58, 0xd6, 0xd2, 0x19, 0x26, 0x0a, 0xdb, 0x39, 0x2d, 0x23, 0xdd, 0x6f, 0x52, + 0x48, 0xf2, 0x5d, 0x86, 0xa8, 0xe5, 0x38, 0x6c, 0x7e, 0xa4, 0x99, 0x1a, 0x40, 0xbf, 0x88, 0x11, + 0x38, 0x08, 0x64, 0xff, 0xb7, 0xe9, 0x4c, 0x4d, 0xae, 0x2c, 0xfe, 0xb0, 0xe6, 0x66, 0x63, 0x14, + 0x26, 0x85, 0x88, 0xe6, 0xc4, 0xe2, 0xe9, 0x15, 0xfa, 0xbb, 0xd1, 0x58, 0x70, 0x73, 0xe2, 0xbb, + 0x3d, 0x4d, 0x28, 0x8b, 0xa9, 0x04, 0x7c, 0x16, 0xc6, 0xd1, 0x8e, 0x5b, 0x6b, 0xad, 0x4d, 0xdb + }; + + public readonly byte[] ExpectedDq = + { + 0x84, 0x03, 0x42, 0x57, 0x72, 0xa5, 0x7a, 0x5a, 0x71, 0x1e, 0xb6, 0xba, 0x94, 0xd1, 0xfe, 0xea, + 0x91, 0x2f, 0xce, 0x75, 0x22, 0xb3, 0x0a, 0xd1, 0x39, 0xce, 0xab, 0xe2, 0xee, 0x14, 0xa4, 0x24, + 0xc2, 0x12, 0x74, 0xdd, 0xa8, 0x4d, 0xae, 0x2f, 0x99, 0xd7, 0x34, 0x2e, 0x5a, 0xe9, 0xfc, 0x5e, + 0x69, 0xdf, 0xfb, 0xcb, 0x63, 0x91, 0x80, 0xbe, 0x00, 0x4d, 0x93, 0x8b, 0x24, 0x64, 0xd5, 0x24, + 0x9a, 0x40, 0xea, 0xef, 0x25, 0x49, 0x14, 0xe6, 0xca, 0xb0, 0xae, 0xc4, 0xb4, 0x7d, 0x0e, 0x4f, + 0xf4, 0x76, 0x00, 0xb6, 0x12, 0xc0, 0x7f, 0x86, 0x2b, 0xbb, 0xd8, 0x91, 0xcd, 0x49, 0x32, 0xad, + 0x52, 0x11, 0x9c, 0x56, 0x7a, 0x28, 0xc6, 0x97, 0x80, 0x3e, 0xfa, 0xb1, 0x34, 0x9e, 0xb2, 0xb8, + 0x1d, 0x16, 0xee, 0xa9, 0x22, 0xdc, 0x77, 0x5a, 0xde, 0x0b, 0xeb, 0xeb, 0x4e, 0xe8, 0x03, 0x41 + }; + + public readonly byte[] ExpectedInverseQ = + { + 0xb5, 0x71, 0x24, 0x42, 0xf4, 0x4c, 0xbd, 0xe2, 0x27, 0x6a, 0x22, 0x32, 0x9f, 0xe6, 0x88, 0x3e, + 0xcf, 0xc8, 0x5d, 0x15, 0x04, 0x2c, 0x88, 0xac, 0x4f, 0xb9, 0xf1, 0x70, 0xef, 0x04, 0x07, 0xb4, + 0x51, 0xd6, 0xab, 0x48, 0xa2, 0x70, 0x0b, 0xbc, 0xb9, 0xd9, 0xbc, 0xd8, 0x81, 0x29, 0x80, 0x6e, + 0x0e, 0xab, 0xa0, 0x8a, 0x39, 0x62, 0x8f, 0x8e, 0x4b, 0xc3, 0xc9, 0x19, 0x64, 0x3b, 0x58, 0x9e, + 0x94, 0x3c, 0xab, 0xfd, 0x53, 0xe1, 0xc0, 0xc6, 0x0f, 0x20, 0x30, 0x76, 0x16, 0xd1, 0xf0, 0xc6, + 0x1d, 0x94, 0x58, 0xd3, 0x4b, 0xe8, 0x5e, 0x73, 0x02, 0x54, 0x88, 0x02, 0xc6, 0x8d, 0x62, 0xfd, + 0xf1, 0x56, 0x35, 0x06, 0x81, 0x57, 0x79, 0x00, 0xc1, 0x7c, 0x9e, 0xd3, 0x61, 0x81, 0x65, 0xae, + 0x70, 0x56, 0x24, 0x32, 0xfc, 0x92, 0x17, 0xc4, 0xed, 0x09, 0x27, 0xe3, 0x40, 0xc5, 0xfe, 0xe4 + }; + + public readonly byte[] ExpectedP = + { + 0xd2, 0x96, 0x44, 0xee, 0x2c, 0x8e, 0x97, 0x24, 0x83, 0x7c, 0xf0, 0x59, 0xeb, 0x8a, 0xa0, 0x24, + 0xca, 0x2b, 0xd9, 0x92, 0x51, 0xf2, 0xa9, 0x33, 0x5b, 0x5f, 0x53, 0xf0, 0x53, 0xcf, 0x5c, 0xd0, + 0xf5, 0x56, 0x73, 0xf1, 0x22, 0x64, 0xa1, 0xb5, 0x6e, 0x36, 0x22, 0xde, 0xf8, 0xa6, 0xa9, 0x3e, + 0xff, 0x3d, 0x63, 0xb8, 0x1e, 0x52, 0xb5, 0x0a, 0xfc, 0x3d, 0x46, 0xbf, 0xa1, 0x26, 0x20, 0x1c, + 0xeb, 0x4c, 0x66, 0x31, 0x84, 0x47, 0x46, 0x55, 0x5f, 0x4e, 0x06, 0x7a, 0x2a, 0x86, 0xf9, 0x7c, + 0xf5, 0xe9, 0x3d, 0x6c, 0xaf, 0x06, 0xb5, 0xef, 0x2e, 0x81, 0xd6, 0xad, 0x7f, 0xa6, 0xe5, 0x01, + 0x77, 0xbb, 0x52, 0xe8, 0x8b, 0x83, 0x4d, 0x98, 0x97, 0x95, 0x7f, 0xc9, 0x5c, 0x79, 0x92, 0x37, + 0xf9, 0x1e, 0xb1, 0xe3, 0x70, 0x77, 0x80, 0xd8, 0x90, 0xbe, 0x48, 0x35, 0xbd, 0x24, 0x8c, 0x5b + }; + + public readonly byte[] ExpectedQ = + { + 0xd2, 0xba, 0xcd, 0x49, 0x28, 0xe2, 0x8c, 0xde, 0x8c, 0xd3, 0xc0, 0xb4, 0xd9, 0x2b, 0x6b, 0xec, + 0xf1, 0xff, 0x66, 0x30, 0xfe, 0x0e, 0x40, 0x46, 0xa9, 0xcd, 0x27, 0x9d, 0xfd, 0xf3, 0xbe, 0x1c, + 0xf8, 0x90, 0x01, 0x7f, 0x48, 0xb3, 0x51, 0xfd, 0xa7, 0xd9, 0x60, 0xce, 0xcf, 0x44, 0x2f, 0x8a, + 0x83, 0xae, 0x6d, 0x37, 0x11, 0x23, 0xf1, 0x54, 0xce, 0xc9, 0xa6, 0xc3, 0x7d, 0xce, 0x13, 0x9c, + 0xed, 0xad, 0x6d, 0x0b, 0x79, 0x77, 0x7b, 0xcf, 0x1c, 0xad, 0xf3, 0x90, 0x15, 0x29, 0x52, 0x16, + 0xc3, 0x94, 0x2f, 0x5d, 0x4d, 0x8f, 0x2d, 0xbc, 0x07, 0x98, 0x45, 0x1f, 0x90, 0x07, 0xfb, 0x26, + 0xf0, 0x3a, 0xf3, 0xad, 0xee, 0xd5, 0xe7, 0x42, 0x7c, 0xa5, 0xfa, 0xb6, 0xaa, 0xe6, 0x1b, 0x47, + 0x54, 0xe5, 0x22, 0xeb, 0x28, 0x8a, 0x3d, 0x66, 0xe4, 0x81, 0xc2, 0x17, 0x1d, 0x0d, 0x7b, 0x5f + }; + } +} From 7a7cded4f25a43564a183c53124e9659b55c22c1 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 4 Oct 2020 20:18:30 -0700 Subject: [PATCH 04/16] Add RSA keys --- src/LibHac/Common/FixedArrays/Array1.cs | 22 ++ src/LibHac/Common/FixedArrays/Array12.cs | 4 +- src/LibHac/Common/FixedArrays/Array2.cs | 4 +- src/LibHac/Common/FixedArrays/Array3.cs | 4 +- src/LibHac/Common/FixedArrays/Array32.cs | 4 +- src/LibHac/Common/FixedArrays/Array4.cs | 4 +- src/LibHac/Common/Keys/ExternalKeyReader.cs | 13 +- src/LibHac/Common/Keys/KeySet.cs | 345 ++++++++++++++++++-- src/LibHac/Crypto/KeyTypes.cs | 47 +++ 9 files changed, 404 insertions(+), 43 deletions(-) create mode 100644 src/LibHac/Common/FixedArrays/Array1.cs diff --git a/src/LibHac/Common/FixedArrays/Array1.cs b/src/LibHac/Common/FixedArrays/Array1.cs new file mode 100644 index 00000000..47377226 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array1.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays +{ + [StructLayout(LayoutKind.Sequential)] + public struct Array1 + { + public const int Length = 1; + + private T _item1; + + public ref T this[int i] => ref Items[i]; + + public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array1 value) => value.ItemsRo; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array12.cs b/src/LibHac/Common/FixedArrays/Array12.cs index 72b8710a..12a81c48 100644 --- a/src/LibHac/Common/FixedArrays/Array12.cs +++ b/src/LibHac/Common/FixedArrays/Array12.cs @@ -25,9 +25,9 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array12 value) => value.ReadOnlyItems; + public static implicit operator ReadOnlySpan(in Array12 value) => value.ItemsRo; } } \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array2.cs b/src/LibHac/Common/FixedArrays/Array2.cs index 1e9529ff..2d3ba490 100644 --- a/src/LibHac/Common/FixedArrays/Array2.cs +++ b/src/LibHac/Common/FixedArrays/Array2.cs @@ -15,9 +15,9 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array2 value) => value.ReadOnlyItems; + public static implicit operator ReadOnlySpan(in Array2 value) => value.ItemsRo; } } \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array3.cs b/src/LibHac/Common/FixedArrays/Array3.cs index ece69cc2..1d3365e3 100644 --- a/src/LibHac/Common/FixedArrays/Array3.cs +++ b/src/LibHac/Common/FixedArrays/Array3.cs @@ -16,9 +16,9 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array3 value) => value.ReadOnlyItems; + public static implicit operator ReadOnlySpan(in Array3 value) => value.ItemsRo; } } \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array32.cs b/src/LibHac/Common/FixedArrays/Array32.cs index 480e20a2..a00433f4 100644 --- a/src/LibHac/Common/FixedArrays/Array32.cs +++ b/src/LibHac/Common/FixedArrays/Array32.cs @@ -45,9 +45,9 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array32 value) => value.ReadOnlyItems; + public static implicit operator ReadOnlySpan(in Array32 value) => value.ItemsRo; } } diff --git a/src/LibHac/Common/FixedArrays/Array4.cs b/src/LibHac/Common/FixedArrays/Array4.cs index de0d0737..d031ef3f 100644 --- a/src/LibHac/Common/FixedArrays/Array4.cs +++ b/src/LibHac/Common/FixedArrays/Array4.cs @@ -17,9 +17,9 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(in Array4 value) => value.ReadOnlyItems; + public static implicit operator ReadOnlySpan(in Array4 value) => value.ItemsRo; } } \ No newline at end of file diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 552b3652..a12e58ff 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -143,10 +143,11 @@ namespace LibHac.Common.Keys } public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, - string consoleKeysFilename = null, IProgressReport logger = null, bool dev = false) + string consoleKeysFilename = null, IProgressReport logger = null, KeySet.Mode mode = KeySet.Mode.Prod) { var keySet = new KeySet(); - //keyset.KeysetForDev = dev; + keySet.SetMode(mode); + ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); return keySet; @@ -360,7 +361,7 @@ namespace LibHac.Common.Keys public static string PrintCommonKeys(KeySet keySet) { - return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed); + return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived); } public static string PrintDeviceKeys(KeySet keySet) @@ -370,7 +371,7 @@ namespace LibHac.Common.Keys public static string PrintAllKeys(KeySet keySet) { - return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Device); + return PrintKeys(keySet, CreateKeyList(), 0); } public static List CreateKeyList() @@ -415,7 +416,7 @@ namespace LibHac.Common.Keys keys.Add(new KeyInfo(110, KeyType.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); keys.Add(new KeyInfo(120, KeyType.CommonSeed, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); keys.Add(new KeyInfo(130, KeyType.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); - keys.Add(new KeyInfo(140, KeyType.CommonDrvd, "master_key_source", (set, i) => set.MasterKeySource)); + keys.Add(new KeyInfo(140, KeyType.CommonSeed, "master_key_source", (set, i) => set.MasterKeySource)); keys.Add(new KeyInfo(150, KeyType.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); keys.Add(new KeyInfo(160, KeyType.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); @@ -462,7 +463,7 @@ namespace LibHac.Common.Keys keys.Add(new KeyInfo(270, KeyType.CommonRoot, "xci_header_key", (set, i) => set.XciHeaderKey)); - keys.Add(new KeyInfo(280, KeyType.CommonRoot, "eticket_rsa_kek", (set, i) => set.EticketRsaKek)); + keys.Add(new KeyInfo(280, KeyType.CommonRoot, "eticket_rsa_kek", (set, i) => set.ETicketRsaKek)); keys.Add(new KeyInfo(281, KeyType.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek)); keys.Add(new KeyInfo(290, KeyType.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index f2097537..36addad0 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -1,14 +1,22 @@ using System; using System.Runtime.InteropServices; +using System.Security.Cryptography; using LibHac.Boot; using LibHac.Common.FixedArrays; using LibHac.Crypto; using LibHac.FsSrv; +using Aes = LibHac.Crypto.Aes; namespace LibHac.Common.Keys { public class KeySet { + public enum Mode + { + Dev, + Prod + } + /// /// The number of keyblobs that were used for < 6.2.0 crypto /// @@ -17,29 +25,45 @@ namespace LibHac.Common.Keys private const int KeyRevisionCount = 0x20; private AllKeys _keys; + private Mode _mode = Mode.Prod; + public ref AllKeys KeyStruct => ref _keys; + private ref RootKeys RootKeys => ref _mode == Mode.Dev ? ref _keys._rootKeysDev : ref _keys._rootKeysProd; + private ref StoredKeys StoredKeys => ref _mode == Mode.Dev ? ref _keys._storedKeysDev : ref _keys._storedKeysProd; + private ref DerivedKeys DerivedKeys => ref _mode == Mode.Dev ? ref _keys._derivedKeysDev : ref _keys._derivedKeysProd; + private ref RsaSigningKeys RsaSigningKeys => ref _mode == Mode.Dev ? ref _keys._rsaSigningKeysDev : ref _keys._rsaSigningKeysProd; + private ref RsaKeys RsaKeys => ref _keys._rsaKeys; + + private ref RsaSigningKeyParameters RsaSigningKeyParams => ref _mode == Mode.Dev + ? ref _rsaSigningKeyParamsDev + : ref _rsaSigningKeyParamsProd; + public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet(); - public Span MarikoAesClassKeys => _keys._rootKeys.MarikoAesClassKeys.Items; - public ref AesKey MarikoKek => ref _keys._rootKeys.MarikoKek; - public ref AesKey MarikoBek => ref _keys._rootKeys.MarikoBek; - public Span KeyBlobs => _keys._rootKeys.KeyBlobs.Items; + public Span MarikoAesClassKeys => RootKeys.MarikoAesClassKeys.Items; + public ref AesKey MarikoKek => ref RootKeys.MarikoKek; + public ref AesKey MarikoBek => ref RootKeys.MarikoBek; + public Span KeyBlobs => RootKeys.KeyBlobs.Items; public Span KeyBlobKeySources => _keys._keySeeds.KeyBlobKeySources.Items; public ref AesKey KeyBlobMacKeySource => ref _keys._keySeeds.KeyBlobMacKeySource; - public ref AesKey TsecRootKek => ref _keys._rootKeys.TsecRootKek; - public ref AesKey Package1MacKek => ref _keys._rootKeys.Package1MacKek; - public ref AesKey Package1Kek => ref _keys._rootKeys.Package1Kek; - public Span TsecAuthSignatures => _keys._rootKeys.TsecAuthSignatures.Items; - public Span TsecRootKeys => _keys._rootKeys.TsecRootKeys.Items; + public ref AesKey TsecRootKek => ref RootKeys.TsecRootKek; + public ref AesKey Package1MacKek => ref RootKeys.Package1MacKek; + public ref AesKey Package1Kek => ref RootKeys.Package1Kek; + public Span TsecAuthSignatures => RootKeys.TsecAuthSignatures.Items; + public Span TsecRootKeys => RootKeys.TsecRootKeys.Items; public Span MasterKekSources => _keys._keySeeds.MasterKekSources.Items; - public Span MarikoMasterKekSources => _keys._keySeeds.MarikoMasterKekSources.Items; - public Span MasterKeks => _keys._derivedKeys.MasterKeks.Items; + + public Span MarikoMasterKekSources => _mode == Mode.Dev + ? _keys._keySeeds.MarikoMasterKekSources_dev.Items + : _keys._keySeeds.MarikoMasterKekSources.Items; + + public Span MasterKeks => DerivedKeys.MasterKeks.Items; public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource; - public Span MasterKeys => _keys._derivedKeys.MasterKeys.Items; - public Span Package1MacKeys => _keys._derivedKeys.Package1MacKeys.Items; - public Span Package1Keys => _keys._derivedKeys.Package1Keys.Items; - public Span Package2Keys => _keys._derivedKeys.Package2Keys.Items; + public Span MasterKeys => DerivedKeys.MasterKeys.Items; + public Span Package1MacKeys => DerivedKeys.Package1MacKeys.Items; + public Span Package1Keys => DerivedKeys.Package1Keys.Items; + public Span Package2Keys => DerivedKeys.Package2Keys.Items; public ref AesKey Package2KeySource => ref _keys._keySeeds.Package2KeySource; public ref AesKey PerConsoleKeySource => ref _keys._keySeeds.PerConsoleKeySource; public ref AesKey RetailSpecificAesKeySource => ref _keys._keySeeds.RetailSpecificAesKeySource; @@ -59,12 +83,12 @@ namespace LibHac.Common.Keys public ref AesKey SeedUniqueSaveMacKekSource => ref _keys._keySeeds.SeedUniqueSaveMacKekSource; public ref AesKey SeedUniqueSaveMacKeySource => ref _keys._keySeeds.SeedUniqueSaveMacKeySource; public ref AesXtsKey HeaderKeySource => ref _keys._keySeeds.HeaderKeySource; - public ref AesXtsKey HeaderKey => ref _keys._derivedKeys.HeaderKey; - public Span TitleKeks => _keys._derivedKeys.TitleKeks.Items; - public Span> KeyAreaKeys => _keys._derivedKeys.KeyAreaKeys.Items; - public ref AesKey XciHeaderKey => ref _keys._rootKeys.XciHeaderKey; - public ref AesKey EticketRsaKek => ref _keys._derivedKeys.EticketRsaKek; - public ref AesKey SslRsaKek => ref _keys._derivedKeys.SslRsaKek; + public ref AesXtsKey HeaderKey => ref DerivedKeys.HeaderKey; + public Span TitleKeks => DerivedKeys.TitleKeks.Items; + public Span> KeyAreaKeys => DerivedKeys.KeyAreaKeys.Items; + public ref AesKey XciHeaderKey => ref StoredKeys.XciHeaderKey; + public ref AesKey ETicketRsaKek => ref DerivedKeys.ETicketRsaKek; + public ref AesKey SslRsaKek => ref DerivedKeys.SslRsaKek; public ref AesKey SecureBootKey => ref _keys._deviceKeys.SecureBootKey; public ref AesKey TsecKey => ref _keys._deviceKeys.TsecKey; @@ -78,6 +102,76 @@ namespace LibHac.Common.Keys public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed; public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; + private RsaSigningKeyParameters _rsaSigningKeyParamsDev; + private RsaSigningKeyParameters _rsaSigningKeyParamsProd; + private RsaKeyParameters _rsaKeyParams; + + public Span NcaHeaderSigningKeys + { + get + { + ref Array2? keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; + + if (keys is null) + { + keys = new Array2(); + keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[1]); + } + + return keys.Value.Items; + } + } + + public Span AcidSigningKeys + { + get + { + ref Array2? keys = ref RsaSigningKeyParams.AcidSigningKeys; + + if (keys is null) + { + keys = new Array2(); + keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[1]); + } + + return keys.Value.Items; + } + } + + public ref RSAParameters Package2SigningKey + { + get + { + ref Array1? keys = ref RsaSigningKeyParams.Package2SigningKey; + + if (keys is null) + { + keys = new Array1(); + keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.Package2SigningKey); + } + + return ref keys.Value[0]; + } + } + + public ref RSAParameters BetaNca0KeyAreaKey + { + get + { + ref Array1? keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; + + if (keys is null) + { + keys = new Array1(); + keys.Value[0] = CreateRsaParameters(in RsaKeys.BetaNca0KeyAreaKey); + } + + return ref keys.Value[0]; + } + } + public void SetSdSeed(ReadOnlySpan sdSeed) { if (sdSeed.Length != 0x10) @@ -87,15 +181,22 @@ namespace LibHac.Common.Keys DeriveSdCardKeys(); } + public void SetMode(Mode mode) + { + _mode = mode; + } + public void DeriveKeys(IProgressReport logger = null) { DeriveKeyBlobKeys(); DecryptKeyBlobs(logger); ReadKeyBlobs(); + Derive620Keys(); Derive620MasterKeks(); DeriveMarikoMasterKeks(); DeriveMasterKeys(); + PopulateOldMasterKeys(); DerivePerConsoleKeys(); DerivePerFirmwareKeys(); @@ -157,6 +258,37 @@ namespace LibHac.Common.Keys } } + private void Derive620Keys() + { + bool haveTsecRootKek = !TsecRootKek.IsEmpty(); + bool havePackage1MacKek = !Package1MacKek.IsEmpty(); + bool havePackage1Kek = !Package1Kek.IsEmpty(); + + for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) + { + if (TsecAuthSignatures[i - UsedKeyBlobCount].IsEmpty()) + continue; + + if (haveTsecRootKek) + { + Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], + TsecRootKeys[i - UsedKeyBlobCount], TsecRootKek); + } + + if (havePackage1MacKek) + { + Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], + Package1MacKeys[i], Package1MacKek); + } + + if (havePackage1Kek) + { + Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], + Package1Keys[i], Package1Kek); + } + } + } + private void Derive620MasterKeks() { for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) @@ -193,8 +325,92 @@ namespace LibHac.Common.Keys } } + private void PopulateOldMasterKeys() + { + // Find the newest master key we have + int newestMasterKey = -1; + + for (int i = MasterKeyVectors.Length - 1; i >= 0; i--) + { + if (!MasterKeys[i].IsEmpty()) + { + newestMasterKey = i; + break; + } + } + + if (newestMasterKey == -1) + return; + + // Don't populate old master keys unless the newest master key is valid + if (!TestKeyGeneration(newestMasterKey)) + return; + + // Decrypt all previous master keys + for (int i = newestMasterKey; i > 0; i--) + { + Aes.DecryptEcb128(MasterKeyVectors[i], MasterKeys[i - 1], MasterKeys[i]); + } + } + + /// + /// Check if the master key of the specified generation is correct. + /// + /// The generation to test. + /// if the key is correct. + private bool TestKeyGeneration(int generation) + { + // Decrypt the vector chain until we get Master Key 0 + AesKey key = MasterKeys[generation]; + + for (int i = generation; i > 0; i--) + { + Aes.DecryptEcb128(MasterKeyVectors[i], key, key); + } + + // Decrypt the zeros with Master Key 0 + Aes.DecryptEcb128(MasterKeyVectors[0], key, key); + + // If we don't get zeros, MasterKeys[generation] is incorrect + return key.IsEmpty(); + } + + private ReadOnlySpan MasterKeyVectors => + MemoryMarshal.Cast(_mode == Mode.Dev ? MasterKeyVectorsDev : MasterKeyVectorsProd); + + private static ReadOnlySpan MasterKeyVectorsDev => new byte[] + { + 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. + 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. + 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. + 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. + 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. + 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. + 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. + 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. + 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. + 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. + 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 // Master key 09 encrypted with Master key 0A. + }; + + private static ReadOnlySpan MasterKeyVectorsProd => new byte[] + { + 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. + 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. + 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. + 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. + 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. + 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. + 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. + 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. + 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. + 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. + 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A // Master key 09 encrypted with Master key 0A. + }; + private void DerivePerConsoleKeys() { + // Todo: Dev and newer key generations var kek = new AesKey(); // Derive the device key @@ -350,30 +566,81 @@ namespace LibHac.Common.Keys srcKek.Data.CopyTo(dest); } } + + private static RSAParameters CreateRsaParameters(in RsaKey key) + { + return new RSAParameters + { + Exponent = key.PublicExponent.DataRo.ToArray(), + Modulus = key.Modulus.DataRo.ToArray() + }; + } + + private static RSAParameters CreateRsaParameters(in RsaFullKey key) + { + return new RSAParameters + { + D = key.PrivateExponent.DataRo.ToArray(), + DP = key.Dp.DataRo.ToArray(), + DQ = key.Dq.DataRo.ToArray(), + Exponent = key.PublicExponent.DataRo.ToArray(), + InverseQ = key.InverseQ.DataRo.ToArray(), + Modulus = key.Modulus.DataRo.ToArray(), + P = key.P.DataRo.ToArray(), + Q = key.Q.DataRo.ToArray() + }; + } + + private struct RsaSigningKeyParameters + { + public Array2? NcaHeaderSigningKeys; + public Array2? AcidSigningKeys; + public Array1? Package2SigningKey; + } + + private struct RsaKeyParameters + { + public Array1? BetaNca0KeyAreaKey; + } } [StructLayout(LayoutKind.Sequential)] public struct AllKeys { - public RootKeys _rootKeys; + public RootKeys _rootKeysDev; + public RootKeys _rootKeysProd; public KeySeeds _keySeeds; - public DerivedKeys _derivedKeys; + public StoredKeys _storedKeysDev; + public StoredKeys _storedKeysProd; + public DerivedKeys _derivedKeysDev; + public DerivedKeys _derivedKeysProd; public DeviceKeys _deviceKeys; + public RsaSigningKeys _rsaSigningKeysDev; + public RsaSigningKeys _rsaSigningKeysProd; + public RsaKeys _rsaKeys; } [StructLayout(LayoutKind.Sequential)] public struct RootKeys { - public Array12 MarikoAesClassKeys; + // Mariko keys. The AES class keys are currently unused. public AesKey MarikoKek; public AesKey MarikoBek; + public Array12 MarikoAesClassKeys; + + // The key blobs are technically derived from the encrypted key blobs and their keys, + // however those keys are device-unique. The decrypted key blobs are basically the common root + // keys used by pre-6.2.0 Erista. public Array32 KeyBlobs; + + // Used by TSEC in >= 6.2.0 Erista firmware + public Array32 TsecAuthSignatures; public AesKey TsecRootKek; public AesKey Package1MacKek; public AesKey Package1Kek; - public Array32 TsecAuthSignatures; + + // Derived by TSEC. This is the first public root key for >= 6.2.0 Erista public Array32 TsecRootKeys; - public AesKey XciHeaderKey; } [StructLayout(LayoutKind.Sequential)] @@ -383,6 +650,7 @@ namespace LibHac.Common.Keys public AesKey KeyBlobMacKeySource; public Array32 MasterKekSources; public Array32 MarikoMasterKekSources; + public Array32 MarikoMasterKekSources_dev; public AesKey MasterKeySource; public AesKey Package2KeySource; public AesKey PerConsoleKeySource; @@ -405,6 +673,15 @@ namespace LibHac.Common.Keys public AesXtsKey HeaderKeySource; } + /// + /// Holds keys that are stored directly in Horizon programs. + /// + [StructLayout(LayoutKind.Sequential)] + public struct StoredKeys + { + public AesKey XciHeaderKey; + } + [StructLayout(LayoutKind.Sequential)] public struct DerivedKeys { @@ -416,7 +693,7 @@ namespace LibHac.Common.Keys public Array32> KeyAreaKeys; public Array32 TitleKeks; public AesXtsKey HeaderKey; - public AesKey EticketRsaKek; + public AesKey ETicketRsaKek; public AesKey SslRsaKek; } @@ -435,4 +712,18 @@ namespace LibHac.Common.Keys public AesKey SdCardEncryptionSeed; public Array3 SdCardEncryptionKeys; } + + [StructLayout(LayoutKind.Sequential)] + public struct RsaSigningKeys + { + public Array2 NcaHeaderSigningKeys; + public Array2 AcidSigningKeys; + public RsaKey Package2SigningKey; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RsaKeys + { + public RsaFullKey BetaNca0KeyAreaKey; + } } diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs index e6871910..ff076f65 100644 --- a/src/LibHac/Crypto/KeyTypes.cs +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -115,4 +115,51 @@ namespace LibHac.Crypto [FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; #endif } + + [StructLayout(LayoutKind.Sequential)] + public struct RsaFullKey + { + public Data100 PrivateExponent; + public Data80 Dp; + public Data80 Dq; + public Data3 PublicExponent; + public Data80 InverseQ; + public Data100 Modulus; + public Data80 P; + public Data80 Q; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RsaKey + { + public Data100 Modulus; + public Data3 PublicExponent; + } + + [StructLayout(LayoutKind.Explicit, Size = 0x100)] + public struct Data100 + { + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x100); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x100); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x80)] + public struct Data80 + { + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 0x80); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x80); + } + + [StructLayout(LayoutKind.Explicit, Size = 3)] + public struct Data3 + { + [FieldOffset(0)] private byte _byte; + + public Span Data => SpanHelpers.CreateSpan(ref _byte, 3); + public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 3); + } } From e3bb1f1fac51b226dda7f05d1425b11e7039636f Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 5 Oct 2020 01:26:54 -0700 Subject: [PATCH 05/16] Allow reading dev keys from key files --- src/LibHac/Common/FixedArrays/Array12.cs | 22 +- src/LibHac/Common/FixedArrays/Array32.cs | 22 +- src/LibHac/Common/Keys/ExternalKeyReader.cs | 219 +++++++++++++++----- src/LibHac/Common/Keys/KeySet.cs | 1 + 4 files changed, 193 insertions(+), 71 deletions(-) diff --git a/src/LibHac/Common/FixedArrays/Array12.cs b/src/LibHac/Common/FixedArrays/Array12.cs index 12a81c48..2e2fb4ab 100644 --- a/src/LibHac/Common/FixedArrays/Array12.cs +++ b/src/LibHac/Common/FixedArrays/Array12.cs @@ -9,23 +9,23 @@ namespace LibHac.Common.FixedArrays { public const int Length = 12; - private T _item1; - private T _item2; - private T _item3; - private T _item4; - private T _item5; - private T _item6; - private T _item7; - private T _item8; - private T _item9; + private T _item01; + private T _item02; + private T _item03; + private T _item04; + private T _item05; + private T _item06; + private T _item07; + private T _item08; + private T _item09; private T _item10; private T _item11; private T _item12; public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _item01, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator ReadOnlySpan(in Array12 value) => value.ItemsRo; diff --git a/src/LibHac/Common/FixedArrays/Array32.cs b/src/LibHac/Common/FixedArrays/Array32.cs index a00433f4..7be52de4 100644 --- a/src/LibHac/Common/FixedArrays/Array32.cs +++ b/src/LibHac/Common/FixedArrays/Array32.cs @@ -9,15 +9,15 @@ namespace LibHac.Common.FixedArrays { public const int Length = 32; - private T _item1; - private T _item2; - private T _item3; - private T _item4; - private T _item5; - private T _item6; - private T _item7; - private T _item8; - private T _item9; + private T _item01; + private T _item02; + private T _item03; + private T _item04; + private T _item05; + private T _item06; + private T _item07; + private T _item08; + private T _item09; private T _item10; private T _item11; private T _item12; @@ -44,8 +44,8 @@ namespace LibHac.Common.FixedArrays public ref T this[int i] => ref Items[i]; - public Span Items => SpanHelpers.CreateSpan(ref _item1, Length); - public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length); + public Span Items => SpanHelpers.CreateSpan(ref _item01, Length); + public readonly ReadOnlySpan ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator ReadOnlySpan(in Array32 value) => value.ItemsRo; diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index a12e58ff..b6fafc3d 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -53,43 +53,84 @@ namespace LibHac.Common.Keys Getter = retrieveFunc; } - public bool Matches(string keyName, out int keyIndex) + public bool Matches(string keyName, out int keyIndex, out bool isDev) { keyIndex = default; + isDev = default; - if (RangeType == KeyRangeType.Single) + return RangeType switch { - return keyName == Name; + KeyRangeType.Single => MatchesSingle(keyName, out isDev), + KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev), + _ => false + }; + } + + private bool MatchesSingle(string keyName, out bool isDev) + { + Assert.Equal((int)KeyRangeType.Single, (int)RangeType); + + isDev = false; + + if (keyName.Length == NameLength + 4) + { + // Might be a dev key. Check if "_dev" comes after the base key name + if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + return false; + + isDev = true; } - else if (RangeType == KeyRangeType.Range) + else if (keyName.Length != NameLength) { - // Check that the length of the key name with the trailing index matches - if (keyName.Length != Name.Length + 3) - return false; - - // Check if the name before the "_XX" index matches - if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) - return false; - - // The name should have an underscore before the index value - if (keyName[keyName.Length - 3] != '_') - return false; - - byte index = default; - - // Try to get the index of the key name - if (!Utilities.TryToBytes(keyName.AsSpan(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index))) - return false; - - // Check if the index is in this key's range - if (index < RangeStart || index >= RangeEnd) - return false; - - keyIndex = index; - return true; + return false; } - return false; + // Check if the base name matches + if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + return false; + + return true; + } + + private bool MatchesRangedKey(string keyName, ref int keyIndex, out bool isDev) + { + Assert.Equal((int)KeyRangeType.Range, (int)RangeType); + + isDev = false; + + // Check if this is an explicit dev key + if (keyName.Length == Name.Length + 7) + { + // Check if "_dev" comes after the base key name + if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + return false; + + isDev = true; + } + // Not a dev key. Check that the length of the key name with the trailing index matches + else if (keyName.Length != Name.Length + 3) + return false; + + // Check if the name before the "_XX" index matches + if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + return false; + + // The name should have an underscore before the index value + if (keyName[keyName.Length - 3] != '_') + return false; + + byte index = default; + + // Try to get the index of the key name + if (!keyName.AsSpan(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) + return false; + + // Check if the index is in this key's range + if (index < RangeStart || index >= RangeEnd) + return false; + + keyIndex = index; + return true; } private static bool IsKeyTypeValid(KeyType type) @@ -105,6 +146,24 @@ namespace LibHac.Common.Keys } } + // Contains info from a specific key being read from a file + [DebuggerDisplay("{" + nameof(Name) + "}")] + private struct SpecificKeyInfo + { + public KeyInfo Key; + public int Index; + public bool IsDev; + + public string Name => Key.Name; + + public SpecificKeyInfo(KeyInfo info, int index, bool isDev) + { + Key = info; + Index = index; + IsDev = isDev; + } + } + public enum KeyRangeType : byte { Single, @@ -120,11 +179,14 @@ namespace LibHac.Common.Keys Seed = 1 << 3, Derived = 1 << 4, + /// Specifies that a seed is different in prod and dev. + DifferentDev = 1 << 5, + CommonRoot = Common | Root, CommonSeed = Common | Seed, + CommonSeedDiff = Common | Seed | DifferentDev, CommonDrvd = Common | Derived, DeviceRoot = Device | Root, - DeviceSeed = Device | Seed, DeviceDrvd = Device | Derived, } @@ -140,6 +202,13 @@ namespace LibHac.Common.Keys if (titleKeysFilename != null) ReadTitleKeys(keySet, titleKeysFilename, logger); keySet.DeriveKeys(logger); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + keySet.DeriveKeys(logger); + keySet.SetMode(KeySet.Mode.Prod); + } } public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, @@ -168,13 +237,25 @@ namespace LibHac.Common.Keys string keyName = a[0].Trim(); string valueStr = a[1].Trim(); - if (!TryGetKeyInfo(out KeyInfo info, out int keyIndex, keyList, keyName)) + if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, keyName)) { logger?.LogMessage($"Failed to match key {keyName}"); continue; } - Span key = info.Getter(keySet, keyIndex); + Span key; + + // Get the dev key in the key set if needed. + if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + key = info.Key.Getter(keySet, info.Index); + keySet.SetMode(KeySet.Mode.Prod); + } + else + { + key = info.Key.Getter(keySet, info.Index); + } if (valueStr.Length != key.Length * 2) { @@ -244,38 +325,41 @@ namespace LibHac.Common.Keys } } - private static bool TryGetKeyInfo(out KeyInfo info, out int keyIndex, List keyList, string keyName) + private static bool TryGetKeyInfo(out SpecificKeyInfo info, List keyList, string keyName) { for (int i = 0; i < keyList.Count; i++) { - if (keyList[i].Matches(keyName, out keyIndex)) + if (keyList[i].Matches(keyName, out int keyIndex, out bool isDev)) { - info = keyList[i]; + info = new SpecificKeyInfo(keyList[i], keyIndex, isDev); return true; } } info = default; - keyIndex = default; return false; } - public static string PrintKeys(KeySet keySet, List keys, KeyType filter) + public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, KeyType filter, bool isDev) { - if (keys.Count == 0) return string.Empty; + if (keys.Count == 0) return; - var sb = new StringBuilder(); + string devSuffix = isDev ? "_dev" : string.Empty; int maxNameLength = keys.Max(x => x.NameLength); int currentGroup = 0; + // Todo: Better filtering bool FilterMatches(KeyInfo keyInfo) { KeyType filter1 = filter & (KeyType.Common | KeyType.Device); KeyType filter2 = filter & (KeyType.Root | KeyType.Seed | KeyType.Derived); + KeyType filter3 = filter & KeyType.DifferentDev; // Skip sub-filters that have no flags set return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && - (filter2 == 0 || (filter2 & keyInfo.Type) != 0); + (filter2 == 0 || (filter2 & keyInfo.Type) != 0) && + filter3 == (filter3 & keyInfo.Type) || + isDev && keyInfo.Type.HasFlag(KeyType.DifferentDev); } bool isFirstPrint = true; @@ -307,7 +391,9 @@ namespace LibHac.Common.Keys sb.AppendLine(); } - string line = $"{info.Name.PadRight(maxNameLength)} = {key.ToHexString()}"; + string keyName = $"{info.Name}{devSuffix}"; + + string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; sb.AppendLine(line); isFirstPrint = false; currentGroup = info.Group; @@ -332,7 +418,7 @@ namespace LibHac.Common.Keys hasPrintedKey = true; } - string keyName = $"{info.Name}_{i:x2}"; + string keyName = $"{info.Name}{devSuffix}_{i:x2}"; string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; sb.AppendLine(line); @@ -341,8 +427,6 @@ namespace LibHac.Common.Keys } } } - - return sb.ToString(); } public static string PrintTitleKeys(KeySet keySet) @@ -361,17 +445,54 @@ namespace LibHac.Common.Keys public static string PrintCommonKeys(KeySet keySet) { - return PrintKeys(keySet, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived); + var sb = new StringBuilder(); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived, false); + return sb.ToString(); } public static string PrintDeviceKeys(KeySet keySet) { - return PrintKeys(keySet, CreateKeyList(), KeyType.Device); + var sb = new StringBuilder(); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Device, false); + return sb.ToString(); } public static string PrintAllKeys(KeySet keySet) { - return PrintKeys(keySet, CreateKeyList(), 0); + var sb = new StringBuilder(); + PrintKeys(keySet, sb, CreateKeyList(), 0, false); + return sb.ToString(); + } + + public static string PrintAllSeeds(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Seed, false); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Seed | KeyType.DifferentDev, true); + keySet.SetMode(KeySet.Mode.Prod); + } + return sb.ToString(); + } + + public static string PrintCommonKeysWithDev(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived, false); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Derived, true); + keySet.SetMode(KeySet.Mode.Prod); + } + + return sb.ToString(); } public static List CreateKeyList() @@ -414,7 +535,7 @@ namespace LibHac.Common.Keys keys.Add(new KeyInfo(101, KeyType.CommonRoot, "mariko_kek", (set, i) => set.MarikoKek)); keys.Add(new KeyInfo(110, KeyType.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); - keys.Add(new KeyInfo(120, KeyType.CommonSeed, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); + keys.Add(new KeyInfo(120, KeyType.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); keys.Add(new KeyInfo(130, KeyType.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); keys.Add(new KeyInfo(140, KeyType.CommonSeed, "master_key_source", (set, i) => set.MasterKeySource)); keys.Add(new KeyInfo(150, KeyType.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); @@ -461,7 +582,7 @@ namespace LibHac.Common.Keys keys.Add(new KeyInfo(263, KeyType.CommonSeed, "sd_card_nca_key_source", (set, i) => set.SdCardKeySources[1])); keys.Add(new KeyInfo(264, KeyType.CommonSeed, "sd_card_custom_storage_key_source", (set, i) => set.SdCardKeySources[2])); - keys.Add(new KeyInfo(270, KeyType.CommonRoot, "xci_header_key", (set, i) => set.XciHeaderKey)); + keys.Add(new KeyInfo(270, KeyType.CommonSeedDiff, "xci_header_key", (set, i) => set.XciHeaderKey)); keys.Add(new KeyInfo(280, KeyType.CommonRoot, "eticket_rsa_kek", (set, i) => set.ETicketRsaKek)); keys.Add(new KeyInfo(281, KeyType.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek)); diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index 36addad0..1eec5f3b 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -28,6 +28,7 @@ namespace LibHac.Common.Keys private Mode _mode = Mode.Prod; public ref AllKeys KeyStruct => ref _keys; + public Mode CurrentMode => _mode; private ref RootKeys RootKeys => ref _mode == Mode.Dev ? ref _keys._rootKeysDev : ref _keys._rootKeysProd; private ref StoredKeys StoredKeys => ref _mode == Mode.Dev ? ref _keys._storedKeysDev : ref _keys._storedKeysProd; From 770406e9c25d338703ab9c1f2ac151349d37d31e Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 5 Oct 2020 12:25:39 -0700 Subject: [PATCH 06/16] Remove the old Keyset class --- src/LibHac/Boot/Package1.cs | 21 +- src/LibHac/Boot/Package2StorageReader.cs | 21 +- src/LibHac/Common/Keys/KeySet.cs | 4 + .../Creators/EncryptedFileSystemCreator.cs | 12 +- .../Creators/SaveDataFileSystemCreator.cs | 9 +- .../FsSrv/Creators/StorageOnNcaCreator.cs | 9 +- src/LibHac/FsSrv/DefaultFsServerObjects.cs | 13 +- src/LibHac/FsSrv/EmulatedGameCard.cs | 9 +- src/LibHac/FsSystem/NcaUtils/Nca.cs | 37 +- src/LibHac/FsSystem/NcaUtils/NcaHeader.cs | 11 +- src/LibHac/FsSystem/Save/Header.cs | 9 +- .../FsSystem/Save/SaveDataFileSystem.cs | 19 +- src/LibHac/KeyType.cs | 10 + src/LibHac/Keyset.cs | 885 ------------------ src/LibHac/Npdm/Acid.cs | 8 +- src/LibHac/Npdm/NpdmBinary.cs | 5 +- src/LibHac/Package1.cs | 5 +- src/LibHac/Package2.cs | 20 +- src/LibHac/SwitchFs.cs | 27 +- src/LibHac/Ticket.cs | 5 +- src/LibHac/Xci.cs | 5 +- src/LibHac/XciHeader.cs | 7 +- src/hactoolnet/Options.cs | 5 +- src/hactoolnet/ProcessDelta.cs | 2 +- src/hactoolnet/ProcessNax0.cs | 15 +- src/hactoolnet/ProcessNca.cs | 13 +- src/hactoolnet/ProcessPackage.cs | 4 +- src/hactoolnet/ProcessSave.cs | 19 +- src/hactoolnet/ProcessSwitchFs.cs | 6 +- src/hactoolnet/ProcessXci.cs | 4 +- src/hactoolnet/Program.cs | 35 +- .../FileSystemServerFactory.cs | 5 +- tests/LibHac.Tests/HorizonFactory.cs | 5 +- 33 files changed, 224 insertions(+), 1040 deletions(-) create mode 100644 src/LibHac/KeyType.cs delete mode 100644 src/LibHac/Keyset.cs diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs index f8e04200..7b39fd7b 100644 --- a/src/LibHac/Boot/Package1.cs +++ b/src/LibHac/Boot/Package1.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using LibHac.Common.Keys; using LibHac.Diag; namespace LibHac.Boot @@ -80,7 +81,7 @@ namespace LibHac.Boot private const int MarikoWarmBootPlainTextSectionSize = 0x330; private IStorage BaseStorage { get; set; } - private Keyset Keyset { get; set; } + private KeySet KeySet { get; set; } public bool IsModern { get; private set; } public bool IsMariko { get; private set; } @@ -107,9 +108,9 @@ namespace LibHac.Boot public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header; public ref readonly Buffer16 Pk11Mac => ref _pk11Mac; - public Result Initialize(Keyset keyset, IStorage storage) + public Result Initialize(KeySet keySet, IStorage storage) { - Keyset = keyset; + KeySet = keySet; BaseStorage = storage; // Read what might be a mariko header and check if it actually is a mariko header @@ -199,13 +200,13 @@ namespace LibHac.Boot } // If encrypted, check if the body can be decrypted - Crypto.Aes.DecryptCbc128(metaData2, metaData2, Keyset.MarikoBek, _metaData.Iv); + Crypto.Aes.DecryptCbc128(metaData2, metaData2, KeySet.MarikoBek, _metaData.Iv); IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData)); // Get a decrypted body storage if we have the correct key if (IsDecrypted) { - var decStorage = new AesCbcStorage(bodySubStorage, Keyset.MarikoBek, _metaData.Iv, true); + var decStorage = new AesCbcStorage(bodySubStorage, KeySet.MarikoBek, _metaData.Iv, true); BodyStorage = new CachedStorage(decStorage, 0x4000, 1, true); } @@ -282,13 +283,13 @@ namespace LibHac.Boot if (IsModern) { - decPk11Storage = new AesCbcStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], + decPk11Storage = new AesCbcStorage(encPk11Storage, KeySet.Package1Keys[KeyRevision], _stage1Footer.Iv, true); } else { - decPk11Storage = new Aes128CtrStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], - _stage1Footer.Iv.ToArray(), true); + decPk11Storage = new Aes128CtrStorage(encPk11Storage, + KeySet.Package1Keys[KeyRevision].DataRo.ToArray(), _stage1Footer.Iv.ToArray(), true); } Pk11Storage = new CachedStorage(decPk11Storage, 0x4000, 1, true); @@ -318,7 +319,7 @@ namespace LibHac.Boot for (int i = start; i < end; i++) { decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader), - Keyset.Package1Keys[i], _stage1Footer.Iv); + KeySet.Package1Keys[i], _stage1Footer.Iv); if (decHeader.Magic == Package1Pk11Header.ExpectedMagic) { @@ -452,7 +453,7 @@ namespace LibHac.Boot new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize); var zeroIv = new Buffer16(); - IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, Keyset.MarikoBek, zeroIv.Bytes, true); + IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, KeySet.MarikoBek, zeroIv.Bytes, true); decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true); diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs index cfd2709e..187256ac 100644 --- a/src/LibHac/Boot/Package2StorageReader.cs +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSystem; @@ -19,27 +20,27 @@ namespace LibHac.Boot private IStorage _storage; private Package2Header _header; - private Keyset _keyset; - private byte[] _key; + private KeySet _keySet; + private AesKey _key; public ref readonly Package2Header Header => ref _header; /// /// Initializes the . /// - /// The keyset to use for decrypting the package. + /// The keyset to use for decrypting the package. /// An of the encrypted package2. /// The of the operation. - public Result Initialize(Keyset keyset, IStorage storage) + public Result Initialize(KeySet keySet, IStorage storage) { Result rc = storage.Read(0, SpanHelpers.AsByteSpan(ref _header)); if (rc.IsFailure()) return rc; - _key = keyset.Package2Keys[_header.Meta.KeyGeneration]; + _key = keySet.Package2Keys[_header.Meta.KeyGeneration]; DecryptHeader(_key, ref _header.Meta, ref _header.Meta); _storage = storage; - _keyset = keyset; + _keySet = keySet; return Result.Success; } @@ -69,7 +70,7 @@ namespace LibHac.Boot } byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray(); - payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key, iv, true), 0x4000, 1, true); + payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true); return Result.Success; } @@ -142,7 +143,7 @@ namespace LibHac.Boot Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes); if (rc.IsFailure()) return rc; - return _header.VerifySignature(_keyset.Package2FixedKeyModulus, metaBytes); + return _header.VerifySignature(_keySet.Package2SigningKey.Modulus, metaBytes); } /// @@ -230,7 +231,7 @@ namespace LibHac.Boot byte[] iv = _header.Meta.HeaderIv.Bytes.ToArray(); Utilities.IncrementByteArray(iv); - storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key, iv, true), 0x100, 1, true)); + storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key.DataRo.ToArray(), iv, true), 0x100, 1, true)); // Open all the payloads for (int i = 0; i < Package2Header.PayloadCount; i++) @@ -252,7 +253,7 @@ namespace LibHac.Boot return Result.Success; } - private void DecryptHeader(byte[] key, ref Package2Meta source, ref Package2Meta dest) + private void DecryptHeader(ReadOnlySpan key, ref Package2Meta source, ref Package2Meta dest) { Buffer16 iv = source.HeaderIv; diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index 1eec5f3b..b8011338 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -101,12 +101,16 @@ namespace LibHac.Common.Keys public Span DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items; public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey; public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed; + + // Todo: Make a separate type? Not actually an AES-XTS key, but it's still the same shape. public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; private RsaSigningKeyParameters _rsaSigningKeyParamsDev; private RsaSigningKeyParameters _rsaSigningKeyParamsProd; private RsaKeyParameters _rsaKeyParams; + public RSAParameters ETicketExtKeyRsa { get; set; } + public Span NcaHeaderSigningKeys { get diff --git a/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs index 04790031..b92be85c 100644 --- a/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -7,11 +8,11 @@ namespace LibHac.FsSrv.Creators { public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator { - private Keyset Keyset { get; } + private KeySet KeySet { get; } - public EncryptedFileSystemCreator(Keyset keyset) + public EncryptedFileSystemCreator(KeySet keySet) { - Keyset = keyset; + KeySet = keySet; } public Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId, @@ -25,9 +26,10 @@ namespace LibHac.FsSrv.Creators } // todo: "proper" key generation instead of a lazy hack - Keyset.SetSdSeed(encryptionSeed.ToArray()); + KeySet.SetSdSeed(encryptionSeed.ToArray()); - encryptedFileSystem = new AesXtsFileSystem(baseFileSystem, Keyset.SdCardKeys[(int)keyId], 0x4000); + encryptedFileSystem = new AesXtsFileSystem(baseFileSystem, + KeySet.SdCardEncryptionKeys[(int) keyId].DataRo.ToArray(), 0x4000); return Result.Success; } diff --git a/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs index 495cacfc..96a337d0 100644 --- a/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs @@ -1,5 +1,6 @@ using System; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -9,11 +10,11 @@ namespace LibHac.FsSrv.Creators { public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator { - private Keyset Keyset { get; } + private KeySet KeySet { get; } - public SaveDataFileSystemCreator(Keyset keyset) + public SaveDataFileSystemCreator(KeySet keySet) { - Keyset = keyset; + KeySet = keySet; } public Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode) @@ -61,7 +62,7 @@ namespace LibHac.FsSrv.Creators if (rc.IsFailure()) return rc; var saveDataStorage = new DisposingFileStorage(saveDataFile); - fileSystem = new SaveDataFileSystem(Keyset, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false); + fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false); // Todo: ISaveDataExtraDataAccessor diff --git a/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs b/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs index f5ddb71c..422d91e9 100644 --- a/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -11,11 +12,11 @@ namespace LibHac.FsSrv.Creators { // ReSharper disable once UnusedMember.Local private bool IsEnabledProgramVerification { get; set; } - private Keyset Keyset { get; } + private KeySet KeySet { get; } - public StorageOnNcaCreator(Keyset keyset) + public StorageOnNcaCreator(KeySet keySet) { - Keyset = keyset; + KeySet = keySet; } // todo: Implement NcaReader and other Nca classes @@ -52,7 +53,7 @@ namespace LibHac.FsSrv.Creators public Result OpenNca(out Nca nca, IStorage ncaStorage) { - nca = new Nca(Keyset, ncaStorage); + nca = new Nca(KeySet, ncaStorage); return Result.Success; } diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index 357a77e3..d9f0e7e0 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -1,4 +1,5 @@ -using LibHac.Fs.Fsa; +using LibHac.Common.Keys; +using LibHac.Fs.Fsa; using LibHac.FsSrv.Creators; namespace LibHac.FsSrv @@ -10,23 +11,23 @@ namespace LibHac.FsSrv public EmulatedGameCard GameCard { get; set; } public EmulatedSdCard SdCard { get; set; } - public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, Keyset keyset) + public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet) { var creators = new FileSystemCreators(); - var gameCard = new EmulatedGameCard(keyset); + var gameCard = new EmulatedGameCard(keySet); var sdCard = new EmulatedSdCard(); var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard); creators.RomFileSystemCreator = new RomFileSystemCreator(); creators.PartitionFileSystemCreator = new PartitionFileSystemCreator(); - creators.StorageOnNcaCreator = new StorageOnNcaCreator(keyset); + creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet); creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator(); - creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keyset); + creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keySet); creators.GameCardStorageCreator = gcStorageCreator; creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); - creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keyset); + creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem); creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(sdCard, rootFileSystem); diff --git a/src/LibHac/FsSrv/EmulatedGameCard.cs b/src/LibHac/FsSrv/EmulatedGameCard.cs index 48266de9..2c22a7ae 100644 --- a/src/LibHac/FsSrv/EmulatedGameCard.cs +++ b/src/LibHac/FsSrv/EmulatedGameCard.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common.Keys; using LibHac.Fs; namespace LibHac.FsSrv @@ -9,13 +10,13 @@ namespace LibHac.FsSrv private int Handle { get; set; } private XciHeader CardHeader { get; set; } private Xci CardImage { get; set; } - private Keyset Keyset { get; set; } + private KeySet KeySet { get; set; } public EmulatedGameCard() { } - public EmulatedGameCard(Keyset keyset) + public EmulatedGameCard(KeySet keySet) { - Keyset = keyset; + KeySet = keySet; } public GameCardHandle GetGameCardHandle() { @@ -38,7 +39,7 @@ namespace LibHac.FsSrv CardImageStorage = cardImageStorage; - CardImage = new Xci(Keyset, cardImageStorage); + CardImage = new Xci(KeySet, cardImageStorage); CardHeader = CardImage.Header; } diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index a1931d5d..99257c78 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Diag; using LibHac.Fs; @@ -14,7 +15,7 @@ namespace LibHac.FsSystem.NcaUtils { public class Nca { - private Keyset Keyset { get; } + private KeySet KeySet { get; } private bool IsEncrypted => Header.IsEncrypted; private byte[] Nca0KeyArea { get; set; } @@ -24,11 +25,11 @@ namespace LibHac.FsSystem.NcaUtils public NcaHeader Header { get; } - public Nca(Keyset keyset, IStorage storage) + public Nca(KeySet keySet, IStorage storage) { - Keyset = keyset; + KeySet = keySet; BaseStorage = storage; - Header = new NcaHeader(keyset, storage); + Header = new NcaHeader(keySet, storage); } public byte[] GetDecryptedKey(int index) @@ -42,11 +43,11 @@ namespace LibHac.FsSystem.NcaUtils } int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); - byte[] keyAreaKey = Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex]; + byte[] keyAreaKey = KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].DataRo.ToArray(); if (keyAreaKey.IsEmpty()) { - string keyName = $"key_area_key_{Keyset.KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}"; + string keyName = $"key_area_key_{KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}"; throw new MissingKeyException("Unable to decrypt NCA section.", keyName, KeyType.Common); } @@ -58,14 +59,16 @@ namespace LibHac.FsSystem.NcaUtils return decryptedKey; } + private static readonly string[] KakNames = { "application", "ocean", "system" }; + public byte[] GetDecryptedTitleKey() { int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); - byte[] titleKek = Keyset.TitleKeks[keyRevision]; + byte[] titleKek = KeySet.TitleKeks[keyRevision].DataRo.ToArray(); var rightsId = new RightsId(Header.RightsId); - if (Keyset.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure()) + if (KeySet.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure()) { throw new MissingKeyException("Missing NCA title key.", rightsId.ToString(), KeyType.Title); } @@ -108,11 +111,11 @@ namespace LibHac.FsSystem.NcaUtils if (Header.HasRightsId) { - return Keyset.ExternalKeySet.Contains(new RightsId(Header.RightsId)) && - !Keyset.TitleKeks[keyRevision].IsEmpty(); + return KeySet.ExternalKeySet.Contains(new RightsId(Header.RightsId)) && + !KeySet.TitleKeks[keyRevision].IsEmpty(); } - return !Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsEmpty(); + return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsEmpty(); } public bool SectionExists(NcaSectionType type) @@ -586,13 +589,13 @@ namespace LibHac.FsSystem.NcaUtils switch (Header.Version) { case 3: - header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); + header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true); break; 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); + 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"); @@ -606,11 +609,11 @@ namespace LibHac.FsSystem.NcaUtils const int sectorSize = NcaHeader.HeaderSectorSize; var sources = new List(); - sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true)); + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); for (int i = 0x400; i < size; i += sectorSize) { - sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true)); + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true)); } return new ConcatenationStorage(sources, true); @@ -630,7 +633,7 @@ namespace LibHac.FsSystem.NcaUtils Span keyArea = Header.GetKeyArea(); var decKeyArea = new byte[0x100]; - if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, Keyset.Nca0RsaKeyAreaKey, out _)) + if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKey, out _)) { Nca0KeyArea = decKeyArea; } @@ -708,7 +711,7 @@ namespace LibHac.FsSystem.NcaUtils public Validity VerifyHeaderSignature() { - return Header.VerifySignature1(Keyset.NcaHdrFixedKeyModulus); + return Header.VerifySignature1(KeySet.NcaHeaderSigningKeys[0].Modulus); } internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion) diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index f52d3d22..60945f30 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Diag; using LibHac.Fs; @@ -21,9 +22,9 @@ namespace LibHac.FsSystem.NcaUtils public NcaVersion FormatVersion { get; } public bool IsEncrypted { get; } - public NcaHeader(Keyset keyset, IStorage headerStorage) + public NcaHeader(KeySet keySet, IStorage headerStorage) { - (byte[] header, bool isEncrypted) = DecryptHeader(keyset, headerStorage); + (byte[] header, bool isEncrypted) = DecryptHeader(keySet, headerStorage); _header = header; IsEncrypted = isEncrypted; @@ -195,7 +196,7 @@ namespace LibHac.FsSystem.NcaUtils return (long)blockIndex * BlockSize; } - private static (byte[] header, bool isEncrypted) DecryptHeader(Keyset keyset, IStorage storage) + private static (byte[] header, bool isEncrypted) DecryptHeader(KeySet keySet, IStorage storage) { var buf = new byte[HeaderSize]; storage.Read(0, buf).ThrowIfFailure(); @@ -212,8 +213,8 @@ namespace LibHac.FsSystem.NcaUtils return (buf, false); } - byte[] key1 = keyset.HeaderKey.AsSpan(0, 0x10).ToArray(); - byte[] key2 = keyset.HeaderKey.AsSpan(0x10, 0x10).ToArray(); + byte[] key1 = keySet.HeaderKey.SubKeys[0].DataRo.ToArray(); + byte[] key2 = keySet.HeaderKey.SubKeys[1].DataRo.ToArray(); var transform = new Aes128XtsTransform(key1, key2, true); diff --git a/src/LibHac/FsSystem/Save/Header.cs b/src/LibHac/FsSystem/Save/Header.cs index 6c8b4250..55ecca34 100644 --- a/src/LibHac/FsSystem/Save/Header.cs +++ b/src/LibHac/FsSystem/Save/Header.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; @@ -37,7 +38,7 @@ namespace LibHac.FsSystem.Save public byte[] Data { get; } - public Header(IStorage storage, Keyset keyset) + public Header(IStorage storage, KeySet keySet) { MainStorage = storage; MainHeader = MainStorage.Slice(0x100, 0x200); @@ -86,14 +87,14 @@ namespace LibHac.FsSystem.Save Sha256.GenerateSha256Hash(Data.AsSpan(0x300, 0x3d00), actualHeaderHash); HeaderHashValidity = Utilities.SpansEqual(Layout.Hash, actualHeaderHash) ? Validity.Valid : Validity.Invalid; - SignatureValidity = ValidateSignature(keyset); + SignatureValidity = ValidateSignature(keySet); } - private Validity ValidateSignature(Keyset keyset) + private Validity ValidateSignature(KeySet keySet) { Span calculatedCmac = stackalloc byte[0x10]; - Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keyset.SaveMacKey); + Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keySet.DeviceUniqueSaveMacKeys[0]); return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid; } diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index f6d4a37a..ffdb6029 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -28,16 +29,16 @@ namespace LibHac.FsSystem.Save public HierarchicalIntegrityVerificationStorage CoreDataIvfcStorage { get; } public HierarchicalIntegrityVerificationStorage FatIvfcStorage { get; } - private Keyset Keyset { get; } + private KeySet KeySet { get; } - public SaveDataFileSystem(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) + public SaveDataFileSystem(KeySet keySet, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) { BaseStorage = storage; LeaveOpen = leaveOpen; - Keyset = keyset; + KeySet = keySet; - var headerA = new Header(BaseStorage, keyset); - var headerB = new Header(BaseStorage.Slice(0x4000), keyset); + var headerA = new Header(BaseStorage, keySet); + var headerB = new Header(BaseStorage.Slice(0x4000), keySet); if (headerA.HeaderHashValidity == Validity.Valid) { @@ -238,12 +239,12 @@ namespace LibHac.FsSystem.Save protected override Result DoCommit() { - Result result = Commit(Keyset); + Result result = Commit(KeySet); return SaveResults.ConvertToExternalResult(result).LogConverted(result); } - public Result Commit(Keyset keyset) + public Result Commit(KeySet keySet) { CoreDataIvfcStorage.Flush(); FatIvfcStorage?.Flush(); @@ -261,7 +262,7 @@ namespace LibHac.FsSystem.Save headerStream.Position = 0x108; headerStream.Write(hash, 0, hash.Length); - if (keyset == null || keyset.SaveMacKey.IsEmpty()) return ResultFs.PreconditionViolation.Log(); + if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsEmpty()) return ResultFs.PreconditionViolation.Log(); var cmacData = new byte[0x200]; var cmac = new byte[0x10]; @@ -269,7 +270,7 @@ namespace LibHac.FsSystem.Save headerStream.Position = 0x100; headerStream.Read(cmacData, 0, 0x200); - Aes.CalculateCmac(cmac, cmacData, keyset.SaveMacKey); + Aes.CalculateCmac(cmac, cmacData, keySet.DeviceUniqueSaveMacKeys[0]); headerStream.Position = 0; headerStream.Write(cmac, 0, 0x10); diff --git a/src/LibHac/KeyType.cs b/src/LibHac/KeyType.cs new file mode 100644 index 00000000..be68f96e --- /dev/null +++ b/src/LibHac/KeyType.cs @@ -0,0 +1,10 @@ +namespace LibHac +{ + public enum KeyType + { + None, + Common, + Unique, + Title + } +} diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs deleted file mode 100644 index d56c25b0..00000000 --- a/src/LibHac/Keyset.cs +++ /dev/null @@ -1,885 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using LibHac.Crypto; -using LibHac.Fs; -using LibHac.FsSrv; -using LibHac.Spl; -using Aes = LibHac.Crypto.Aes; - -namespace LibHac -{ - public class Keyset - { - /// - /// The number of keyblobs that were used for < 6.2.0 crypto - /// - private const int UsedKeyblobCount = 6; - - private const int SdCardKeyIdCount = 3; - - public byte[][] KeyblobKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] KeyblobMacKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] EncryptedKeyblobs { get; } = Utilities.CreateJaggedByteArray(0x20, 0xB0); - public byte[][] Keyblobs { get; } = Utilities.CreateJaggedByteArray(0x20, 0x90); - public byte[][] KeyblobKeySources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[] KeyblobMacKeySource { get; } = new byte[0x10]; - public byte[][] TsecRootKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] MasterKekSources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] MarikoMasterKekSources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] MasterKeks { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[] MasterKeySource { get; } = new byte[0x10]; - public byte[][] MasterKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] Package1Keys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][] Package2Keys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[] Package2KeySource { get; } = new byte[0x10]; - public byte[] AesKekGenerationSource { get; } = new byte[0x10]; - public byte[] AesKeyGenerationSource { get; } = new byte[0x10]; - public byte[] KeyAreaKeyApplicationSource { get; } = new byte[0x10]; - public byte[] KeyAreaKeyOceanSource { get; } = new byte[0x10]; - public byte[] KeyAreaKeySystemSource { get; } = new byte[0x10]; - public byte[] SaveMacKekSource { get; } = new byte[0x10]; - public byte[] SaveMacSdCardKekSource { get; } = new byte[0x10]; - public byte[] SaveMacKeySource { get; } = new byte[0x10]; - public byte[] SaveMacSdCardKeySource { get; } = new byte[0x10]; - public byte[] TitleKekSource { get; } = new byte[0x10]; - public byte[] HeaderKekSource { get; } = new byte[0x10]; - public byte[] SdCardKekSource { get; } = new byte[0x10]; - public byte[][] SdCardKeySources { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20); - public byte[] HeaderKeySource { get; } = new byte[0x20]; - public byte[] HeaderKey { get; } = new byte[0x20]; - public byte[] XciHeaderKey { get; } = new byte[0x10]; - public byte[][] TitleKeks { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10); - public byte[][][] KeyAreaKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 3, 0x10); - public byte[] EticketRsaKek { get; } = new byte[0x10]; - public byte[] RetailSpecificAesKeySource { get; } = new byte[0x10]; - public byte[] PerConsoleKeySource { get; } = new byte[0x10]; - public byte[] BisKekSource { get; } = new byte[0x10]; - public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20); - public byte[] SslRsaKek { get; } = new byte[0x10]; - public byte[] MarikoBek { get; } = new byte[0x10]; - public byte[] MarikoKek { get; } = new byte[0x10]; - - // Device-specific keys - public byte[] SecureBootKey { get; } = new byte[0x10]; - public byte[] TsecKey { get; } = new byte[0x10]; - public byte[] DeviceKey { get; } = new byte[0x10]; - public byte[][] BisKeys { get; } = Utilities.CreateJaggedByteArray(4, 0x20); - public byte[] SaveMacKey { get; } = new byte[0x10]; - public byte[] SaveMacSdCardKey { get; } = new byte[0x10]; - public byte[] SdSeed { get; } = new byte[0x10]; - public byte[][] SdCardKeySourcesSpecific { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20); - public byte[][] SdCardKeys { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20); - - public RSAParameters EticketExtKeyRsa { get; set; } - - private RSAParameters? _nca0RsaKeyAreaKey; - public RSAParameters Nca0RsaKeyAreaKey - { - // Lazily init this key - get - { - if (_nca0RsaKeyAreaKey.HasValue) - return _nca0RsaKeyAreaKey.Value; - - _nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, new byte[] { 1, 0, 1 }, BetaNca0Exponent); - return _nca0RsaKeyAreaKey.Value; - } - } - - public bool KeysetForDev; - public byte[] NcaHdrFixedKeyModulus - { - get - { - if (KeysetForDev) - { - return NcaHdrFixedKeyModulusDev; - } - else - { - return NcaHdrFixedKeyModulusProd; - } - } - } - - public byte[] AcidFixedKeyModulus - { - get - { - if (KeysetForDev) - { - return AcidFixedKeyModulusDev; - } - else - { - return AcidFixedKeyModulusProd; - } - } - } - - public byte[] Package2FixedKeyModulus - { - get - { - if (KeysetForDev) - { - return Package2FixedKeyModulusDev; - } - else - { - return Package2FixedKeyModulusProd; - } - } - } - - 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, - 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, - 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, - 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, - 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, - 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, - 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, - 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, - 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, - 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, - 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, - 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, - 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, - 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, - 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, - 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 - }; - - private static readonly byte[] AcidFixedKeyModulusProd = - { - 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, - 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, - 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, - 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, - 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, - 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, - 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, - 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, - 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, - 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, - 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, - 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, - 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, - 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, - 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, - 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD - }; - - private static readonly byte[] Package2FixedKeyModulusProd = - { - 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, - 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, - 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, - 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, - 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, - 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, - 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, - 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, - 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, - 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, - 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, - 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, - 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, - 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, - 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, - 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F - }; - - private static readonly byte[] NcaHdrFixedKeyModulusDev = - { - 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, - 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, - 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, - 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, - 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, - 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, - 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, - 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, - 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, - 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, - 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, - 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, - 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, - 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, - 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, - 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 - }; - - private static readonly byte[] AcidFixedKeyModulusDev = - { - 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, - 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, - 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, - 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, - 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, - 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, - 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, - 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, - 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, - 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, - 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, - 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, - 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, - 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, - 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, - 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 - }; - - private static readonly byte[] Package2FixedKeyModulusDev = - { - 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, - 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, - 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, - 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, - 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, - 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, - 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, - 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, - 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, - 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, - 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, - 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, - 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, - 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, - 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, - 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B - }; - - public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet(); - - public void SetSdSeed(byte[] sdseed) - { - Array.Copy(sdseed, SdSeed, SdSeed.Length); - DeriveSdCardKeys(); - } - - public void DeriveKeys(IProgressReport logger = null) - { - DeriveKeyblobKeys(); - DecryptKeyblobs(logger); - ReadKeyblobs(); - - Derive620MasterKeks(); - DeriveMasterKeys(); - - DerivePerConsoleKeys(); - DerivePerFirmwareKeys(); - DeriveNcaHeaderKey(); - DeriveSdCardKeys(); - } - - private void DeriveKeyblobKeys() - { - if (SecureBootKey.IsEmpty() || TsecKey.IsEmpty()) return; - - bool haveKeyblobMacKeySource = !MasterKeySource.IsEmpty(); - var temp = new byte[0x10]; - - for (int i = 0; i < UsedKeyblobCount; i++) - { - if (KeyblobKeySources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(KeyblobKeySources[i], temp, TsecKey); - Aes.DecryptEcb128(temp, KeyblobKeys[i], SecureBootKey); - - if (!haveKeyblobMacKeySource) continue; - - Aes.DecryptEcb128(KeyblobMacKeySource, KeyblobMacKeys[i], KeyblobKeys[i]); - } - } - - private void DecryptKeyblobs(IProgressReport logger = null) - { - var cmac = new byte[0x10]; - var expectedCmac = new byte[0x10]; - var counter = new byte[0x10]; - - for (int i = 0; i < UsedKeyblobCount; i++) - { - if (KeyblobKeys[i].IsEmpty() || KeyblobMacKeys[i].IsEmpty() || EncryptedKeyblobs[i].IsEmpty()) - { - continue; - } - - Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10); - Aes.CalculateCmac(cmac, EncryptedKeyblobs[i].AsSpan(0x10, 0xA0), KeyblobMacKeys[i]); - - if (!Utilities.ArraysEqual(cmac, expectedCmac)) - { - logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); - } - - Array.Copy(EncryptedKeyblobs[i], 0x10, counter, 0, 0x10); - - Aes.DecryptCtr128(EncryptedKeyblobs[i].AsSpan(0x20), Keyblobs[i], KeyblobKeys[i], counter); - } - } - - private void ReadKeyblobs() - { - for (int i = 0; i < UsedKeyblobCount; i++) - { - if (Keyblobs[i].IsEmpty()) continue; - - Array.Copy(Keyblobs[i], 0x80, Package1Keys[i], 0, 0x10); - Array.Copy(Keyblobs[i], MasterKeks[i], 0x10); - } - } - - private void Derive620MasterKeks() - { - for (int i = UsedKeyblobCount; i < 0x20; i++) - { - if (TsecRootKeys[i - UsedKeyblobCount].IsEmpty() || MasterKekSources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(MasterKekSources[i], MasterKeks[i], TsecRootKeys[i - UsedKeyblobCount]); - } - } - - private void DeriveMasterKeys() - { - if (MasterKeySource.IsEmpty()) return; - - for (int i = 0; i < 0x20; i++) - { - if (MasterKeks[i].IsEmpty()) continue; - - Aes.DecryptEcb128(MasterKeySource, MasterKeys[i], MasterKeks[i]); - } - } - - private void DerivePerConsoleKeys() - { - var kek = new byte[0x10]; - - // Derive the device key - if (!PerConsoleKeySource.IsEmpty() && !KeyblobKeys[0].IsEmpty()) - { - Aes.DecryptEcb128(PerConsoleKeySource, DeviceKey, KeyblobKeys[0]); - } - - // Derive save key - if (!SaveMacKekSource.IsEmpty() && !SaveMacKeySource.IsEmpty() && !DeviceKey.IsEmpty()) - { - GenerateKek(DeviceKey, SaveMacKekSource, kek, AesKekGenerationSource, null); - Aes.DecryptEcb128(SaveMacKeySource, SaveMacKey, kek); - } - - // Derive BIS keys - if (DeviceKey.IsEmpty() - || BisKekSource.IsEmpty() - || AesKekGenerationSource.IsEmpty() - || AesKeyGenerationSource.IsEmpty() - || RetailSpecificAesKeySource.IsEmpty()) - { - return; - } - - // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 - if (BisKeySource[3].IsEmpty() && !BisKeySource[2].IsEmpty()) - { - Array.Copy(BisKeySource[2], BisKeySource[3], 0x20); - } - - Aes.DecryptEcb128(RetailSpecificAesKeySource, kek, DeviceKey); - if (!BisKeySource[0].IsEmpty()) Aes.DecryptEcb128(BisKeySource[0], BisKeys[0], kek); - - GenerateKek(DeviceKey, BisKekSource, kek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int i = 1; i < 4; i++) - { - if (!BisKeySource[i].IsEmpty()) Aes.DecryptEcb128(BisKeySource[i], BisKeys[i], kek); - } - } - - private void DerivePerFirmwareKeys() - { - bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty(); - bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty(); - bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty(); - bool haveTitleKekSource = !TitleKekSource.IsEmpty(); - bool havePackage2KeySource = !Package2KeySource.IsEmpty(); - - for (int i = 0; i < 0x20; i++) - { - if (MasterKeys[i].IsEmpty()) - { - continue; - } - - if (haveKakSource0) - { - GenerateKek(MasterKeys[i], KeyAreaKeyApplicationSource, KeyAreaKeys[i][0], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource1) - { - GenerateKek(MasterKeys[i], KeyAreaKeyOceanSource, KeyAreaKeys[i][1], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource2) - { - GenerateKek(MasterKeys[i], KeyAreaKeySystemSource, KeyAreaKeys[i][2], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveTitleKekSource) - { - Aes.DecryptEcb128(TitleKekSource, TitleKeks[i], MasterKeys[i]); - } - - if (havePackage2KeySource) - { - Aes.DecryptEcb128(Package2KeySource, Package2Keys[i], MasterKeys[i]); - } - } - } - - private void DeriveNcaHeaderKey() - { - if (HeaderKekSource.IsEmpty() || HeaderKeySource.IsEmpty() || MasterKeys[0].IsEmpty()) return; - - var headerKek = new byte[0x10]; - - GenerateKek(MasterKeys[0], HeaderKekSource, headerKek, AesKekGenerationSource, - AesKeyGenerationSource); - Aes.DecryptEcb128(HeaderKeySource, HeaderKey, headerKek); - } - - public void DeriveSdCardKeys() - { - var sdKek = new byte[0x10]; - GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int k = 0; k < SdCardKeyIdCount; k++) - { - for (int i = 0; i < 0x20; i++) - { - SdCardKeySourcesSpecific[k][i] = (byte)(SdCardKeySources[k][i] ^ SdSeed[i & 0xF]); - } - } - - for (int k = 0; k < SdCardKeyIdCount; k++) - { - Aes.DecryptEcb128(SdCardKeySourcesSpecific[k], SdCardKeys[k], sdKek); - } - - // Derive sd card save key - if (!SaveMacSdCardKekSource.IsEmpty() && !SaveMacSdCardKeySource.IsEmpty()) - { - var keySource = new byte[0x10]; - - for (int i = 0; i < 0x10; i++) - { - keySource[i] = (byte)(SaveMacSdCardKeySource[i] ^ SdSeed[i]); - } - - GenerateKek(MasterKeys[0], SaveMacSdCardKekSource, sdKek, AesKekGenerationSource, null); - Aes.DecryptEcb128(keySource, SaveMacSdCardKey, sdKek); - } - } - - internal static readonly string[] KakNames = { "application", "ocean", "system" }; - - public static int GetMasterKeyRevisionFromKeyGeneration(int keyGeneration) - { - if (keyGeneration == 0) return 0; - - return keyGeneration - 1; - } - - private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, ReadOnlySpan kekSeed, ReadOnlySpan keySeed) - { - Span kek = stackalloc byte[0x10]; - Span srcKek = stackalloc byte[0x10]; - - Aes.DecryptEcb128(kekSeed, kek, key); - Aes.DecryptEcb128(src, srcKek, kek); - - if (!keySeed.IsEmpty) - { - Aes.DecryptEcb128(keySeed, dest, srcKek); - } - else - { - srcKek.CopyTo(dest); - } - } - } - - public static class ExternalKeyReader - { - private const int TitleKeySize = 0x10; - - public static void ReadKeyFile(Keyset keyset, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) - { - Dictionary keyDictionary = CreateFullKeyDictionary(); - - if (filename != null) ReadMainKeys(keyset, filename, keyDictionary, logger); - if (consoleKeysFilename != null) ReadMainKeys(keyset, consoleKeysFilename, keyDictionary, logger); - if (titleKeysFilename != null) ReadTitleKeys(keyset, titleKeysFilename, logger); - - keyset.ExternalKeySet.TrimExcess(); - keyset.DeriveKeys(logger); - } - - public static Keyset ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null, bool dev = false) - { - var keyset = new Keyset(); - keyset.KeysetForDev = dev; - ReadKeyFile(keyset, filename, titleKeysFilename, consoleKeysFilename, logger); - - return keyset; - } - - public static void LoadConsoleKeys(this Keyset keyset, string filename, IProgressReport logger = null) - { - Dictionary uniqueKeyDictionary = CreateUniqueKeyDictionary(); - - foreach (KeyValue key in uniqueKeyDictionary.Values) - { - byte[] keyBytes = key.GetKey(keyset); - Array.Clear(keyBytes, 0, keyBytes.Length); - } - - ReadMainKeys(keyset, filename, uniqueKeyDictionary, logger); - keyset.DeriveKeys(); - } - - private static void ReadMainKeys(Keyset keyset, string filename, Dictionary keyDict, IProgressReport logger = null) - { - if (filename == null) return; - - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) - { - string line; - while ((line = reader.ReadLine()) != null) - { - string[] a = line.Split(',', '='); - if (a.Length != 2) continue; - - string key = a[0].Trim(); - string valueStr = a[1].Trim(); - - if (!keyDict.TryGetValue(key, out KeyValue kv)) - { - logger?.LogMessage($"Failed to match key {key}"); - continue; - } - - byte[] value = valueStr.ToBytes(); - if (value.Length != kv.Size) - { - logger?.LogMessage($"Key {key} had incorrect size {value.Length}. (Expected {kv.Size})"); - continue; - } - - byte[] dest = kv.GetKey(keyset); - Array.Copy(value, dest, value.Length); - } - } - } - - private static void ReadTitleKeys(Keyset keyset, string filename, IProgressReport progress = null) - { - if (filename == null) return; - - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) - { - string line; - while ((line = reader.ReadLine()) != null) - { - string[] splitLine; - - // Some people use pipes as delimiters - if (line.Contains('|')) - { - splitLine = line.Split('|'); - } - else - { - splitLine = line.Split(',', '='); - } - - if (splitLine.Length < 2) continue; - - if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) - { - progress?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); - continue; - } - - if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) - { - progress?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); - continue; - } - - if (rightsId.Length != TitleKeySize) - { - progress?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); - continue; - } - - if (titleKey.Length != TitleKeySize) - { - progress?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); - continue; - } - - keyset.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); - } - } - } - - public static string PrintKeys(Keyset keyset, Dictionary dict) - { - if (dict.Count == 0) return string.Empty; - - var sb = new StringBuilder(); - int maxNameLength = dict.Values.Max(x => x.Name.Length); - int currentGroup = 0; - - foreach (KeyValue keySlot in dict.Values.Where(x => x.Group >= 0).OrderBy(x => x.Group).ThenBy(x => x.Name)) - { - byte[] key = keySlot.GetKey(keyset); - if (key.IsEmpty()) continue; - - if (keySlot.Group > currentGroup) - { - sb.AppendLine(); - currentGroup = keySlot.Group; - } - - string line = $"{keySlot.Name.PadRight(maxNameLength)} = {key.ToHexString()}"; - sb.AppendLine(line); - } - - return sb.ToString(); - } - - public static string PrintCommonKeys(Keyset keyset) - { - return PrintKeys(keyset, CreateCommonKeyDictionary()); - } - - public static string PrintUniqueKeys(Keyset keyset) - { - return PrintKeys(keyset, CreateUniqueKeyDictionary()); - } - - public static string PrintAllKeys(Keyset keyset) - { - return PrintKeys(keyset, CreateFullKeyDictionary()); - } - - public static string PrintTitleKeys(Keyset keyset) - { - var sb = new StringBuilder(); - - foreach ((RightsId rightsId, AccessKey key) kv in keyset.ExternalKeySet.ToList().OrderBy(x => x.rightsId.ToString())) - { - string line = $"{kv.rightsId} = {kv.key}"; - sb.AppendLine(line); - } - - return sb.ToString(); - } - - public static Dictionary CreateCommonKeyDictionary() - { - return CreateCommonKeyList().ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase); - } - - public static Dictionary CreateUniqueKeyDictionary() - { - return CreateUniqueKeyList().ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase); - } - - public static Dictionary CreateFullKeyDictionary() - { - List commonKeys = CreateCommonKeyList(); - List uniqueKeys = CreateUniqueKeyList(); - - return uniqueKeys.Concat(commonKeys).ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase); - } - - private static List CreateCommonKeyList() - { - var keys = new List - { - new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource), - - new KeyValue("mariko_bek", 0x10, 32, set => set.MarikoBek), - new KeyValue("mariko_kek", 0x10, 32, set => set.MarikoKek), - - new KeyValue("master_key_source", 0x10, 60, set => set.MasterKeySource), - new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource), - - new KeyValue("aes_kek_generation_source", 0x10, 70, set => set.AesKekGenerationSource), - new KeyValue("aes_key_generation_source", 0x10, 70, set => set.AesKeyGenerationSource), - - new KeyValue("bis_kek_source", 0x10, 80, set => set.BisKekSource), - - new KeyValue("retail_specific_aes_key_source", 0x10, 90, set => set.RetailSpecificAesKeySource), - new KeyValue("per_console_key_source", 0x10, 90, set => set.PerConsoleKeySource), - - new KeyValue("header_kek_source", 0x10, 100, set => set.HeaderKekSource), - new KeyValue("header_key_source", 0x20, 100, set => set.HeaderKeySource), - new KeyValue("key_area_key_application_source", 0x10, 100, set => set.KeyAreaKeyApplicationSource), - new KeyValue("key_area_key_ocean_source", 0x10, 100, set => set.KeyAreaKeyOceanSource), - new KeyValue("key_area_key_system_source", 0x10, 100, set => set.KeyAreaKeySystemSource), - new KeyValue("titlekek_source", 0x10, 100, set => set.TitleKekSource), - - new KeyValue("save_mac_kek_source", 0x10, 110, set => set.SaveMacKekSource), - new KeyValue("save_mac_sd_card_kek_source", 0x10, 110, set => set.SaveMacSdCardKekSource), - new KeyValue("save_mac_key_source", 0x10, 110, set => set.SaveMacKeySource), - new KeyValue("save_mac_sd_card_key_source", 0x10, 110, set => set.SaveMacSdCardKeySource), - new KeyValue("sd_card_kek_source", 0x10, 110, set => set.SdCardKekSource), - new KeyValue("sd_card_save_key_source", 0x20, 110, set => set.SdCardKeySources[0]), - new KeyValue("sd_card_nca_key_source", 0x20, 110, set => set.SdCardKeySources[1]), - new KeyValue("sd_card_custom_storage_key_source", 0x20, 110, set => set.SdCardKeySources[2]), - - new KeyValue("eticket_rsa_kek", 0x10, 120, set => set.EticketRsaKek), - new KeyValue("ssl_rsa_kek", 0x10, 120, set => set.SslRsaKek), - new KeyValue("xci_header_key", 0x10, 130, set => set.XciHeaderKey), - - new KeyValue("header_key", 0x20, 220, set => set.HeaderKey) - }; - - for (int slot = 0; slot < 0x20; slot++) - { - int i = slot; - keys.Add(new KeyValue($"keyblob_key_source_{i:x2}", 0x10, 0, set => set.KeyblobKeySources[i])); - keys.Add(new KeyValue($"keyblob_{i:x2}", 0x90, 10, set => set.Keyblobs[i])); - keys.Add(new KeyValue($"tsec_root_key_{i:x2}", 0x10, 20, set => set.TsecRootKeys[i])); - keys.Add(new KeyValue($"master_kek_source_{i:x2}", 0x10, 30, set => set.MasterKekSources[i])); - keys.Add(new KeyValue($"mariko_master_kek_source_{i:x2}", 0x10, 35, set => set.MarikoMasterKekSources[i])); - keys.Add(new KeyValue($"master_kek_{i:x2}", 0x10, 40, set => set.MasterKeks[i])); - keys.Add(new KeyValue($"package1_key_{i:x2}", 0x10, 50, set => set.Package1Keys[i])); - - keys.Add(new KeyValue($"master_key_{i:x2}", 0x10, 200, set => set.MasterKeys[i])); - keys.Add(new KeyValue($"package2_key_{i:x2}", 0x10, 210, set => set.Package2Keys[i])); - keys.Add(new KeyValue($"titlekek_{i:x2}", 0x10, 230, set => set.TitleKeks[i])); - keys.Add(new KeyValue($"key_area_key_application_{i:x2}", 0x10, 240, set => set.KeyAreaKeys[i][0])); - keys.Add(new KeyValue($"key_area_key_ocean_{i:x2}", 0x10, 250, set => set.KeyAreaKeys[i][1])); - keys.Add(new KeyValue($"key_area_key_system_{i:x2}", 0x10, 260, set => set.KeyAreaKeys[i][2])); - } - - for (int slot = 0; slot < 4; slot++) - { - int i = slot; - keys.Add(new KeyValue($"bis_key_source_{i:x2}", 0x20, 80, set => set.BisKeySource[i])); - } - - return keys; - } - - private static List CreateUniqueKeyList() - { - var keys = new List - { - new KeyValue("secure_boot_key", 0x10, 0, set => set.SecureBootKey), - new KeyValue("tsec_key", 0x10, 0, set => set.TsecKey), - new KeyValue("sd_seed", 0x10, 10, set => set.SdSeed), - - new KeyValue("device_key", 0x10, 40, set => set.DeviceKey), - new KeyValue("save_mac_key", 0x10, 60, set => set.SaveMacKey), - new KeyValue("save_mac_sd_card_key", 0x10, 60, set => set.SaveMacSdCardKey) - }; - - for (int slot = 0; slot < 0x20; slot++) - { - int i = slot; - keys.Add(new KeyValue($"keyblob_mac_key_{i:x2}", 0x10, 20, set => set.KeyblobMacKeys[i])); - keys.Add(new KeyValue($"keyblob_key_{i:x2}", 0x10, 30, set => set.KeyblobKeys[i])); - keys.Add(new KeyValue($"encrypted_keyblob_{i:x2}", 0xB0, 100, set => set.EncryptedKeyblobs[i])); - } - - for (int slot = 0; slot < 4; slot++) - { - int i = slot; - keys.Add(new KeyValue($"bis_key_{i:x2}", 0x20, 50, set => set.BisKeys[i])); - } - - return keys; - } - - public class KeyValue - { - public readonly string Name; - public readonly int Size; - public readonly int Group; - public readonly Func GetKey; - - public KeyValue(string name, int size, int group, Func retrieveFunc) - { - Name = name; - Size = size; - Group = group; - GetKey = retrieveFunc; - } - } - } - - public enum KeyType - { - None, - Common, - Unique, - Title - } -} diff --git a/src/LibHac/Npdm/Acid.cs b/src/LibHac/Npdm/Acid.cs index f332781b..e62d31b3 100644 --- a/src/LibHac/Npdm/Acid.cs +++ b/src/LibHac/Npdm/Acid.cs @@ -1,6 +1,7 @@ // ReSharper disable UnusedVariable using System; using System.IO; +using LibHac.Common.Keys; namespace LibHac.Npdm { @@ -23,7 +24,7 @@ namespace LibHac.Npdm public Acid(Stream stream, int offset) : this(stream, offset, null) { } - public Acid(Stream stream, int offset, Keyset keyset) + public Acid(Stream stream, int offset, KeySet keySet) { stream.Seek(offset, SeekOrigin.Begin); @@ -40,11 +41,12 @@ namespace LibHac.Npdm Size = reader.ReadInt32(); - if (keyset != null) + if (keySet != null) { reader.BaseStream.Position = offset + 0x100; byte[] signatureData = reader.ReadBytes(Size); - SignatureValidity = CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keyset.AcidFixedKeyModulus); + SignatureValidity = + CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeys[0].Modulus); } reader.BaseStream.Position = offset + 0x208; diff --git a/src/LibHac/Npdm/NpdmBinary.cs b/src/LibHac/Npdm/NpdmBinary.cs index 941ec422..ef298f53 100644 --- a/src/LibHac/Npdm/NpdmBinary.cs +++ b/src/LibHac/Npdm/NpdmBinary.cs @@ -2,6 +2,7 @@ using System; using System.Buffers.Binary; using System.IO; +using LibHac.Common.Keys; namespace LibHac.Npdm { @@ -27,7 +28,7 @@ namespace LibHac.Npdm public NpdmBinary(Stream stream) : this(stream, null) { } - public NpdmBinary(Stream stream, Keyset keyset) + public NpdmBinary(Stream stream, KeySet keySet) { var reader = new BinaryReader(stream); @@ -74,7 +75,7 @@ namespace LibHac.Npdm int acidSize = reader.ReadInt32(); Aci0 = new Aci0(stream, aci0Offset); - AciD = new Acid(stream, acidOffset, keyset); + AciD = new Acid(stream, acidOffset, keySet); } } } diff --git a/src/LibHac/Package1.cs b/src/LibHac/Package1.cs index 94c9a34c..6cc0c4df 100644 --- a/src/LibHac/Package1.cs +++ b/src/LibHac/Package1.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.FsSystem; @@ -20,7 +21,7 @@ namespace LibHac private IStorage Storage { get; } - public Package1(Keyset keyset, IStorage storage) + public Package1(KeySet keySet, IStorage storage) { Storage = storage; var reader = new BinaryReader(storage.AsStream()); @@ -41,7 +42,7 @@ namespace LibHac for (int i = 0; i < 0x20; i++) { - var dec = new Aes128CtrStorage(encStorage, keyset.Package1Keys[i], Counter, true); + var dec = new Aes128CtrStorage(encStorage, keySet.Package1Keys[i].DataRo.ToArray(), Counter, true); dec.Read(0, decBuffer).ThrowIfFailure(); if (BitConverter.ToUInt32(decBuffer, 0) == Pk11Magic) diff --git a/src/LibHac/Package2.cs b/src/LibHac/Package2.cs index e94149c6..6b2eda82 100644 --- a/src/LibHac/Package2.cs +++ b/src/LibHac/Package2.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.FsSystem; @@ -18,15 +19,15 @@ namespace LibHac private IStorage Storage { get; } - public Package2(Keyset keyset, IStorage storage) + public Package2(KeySet keySet, IStorage storage) { Storage = storage; IStorage headerStorage = Storage.Slice(0, 0x200); - KeyRevision = FindKeyGeneration(keyset, headerStorage); - Key = keyset.Package2Keys[KeyRevision]; + KeyRevision = FindKeyGeneration(keySet, headerStorage); + Key = keySet.Package2Keys[KeyRevision].DataRo.ToArray(); - Header = new Package2Header(headerStorage, keyset, KeyRevision); + Header = new Package2Header(headerStorage, keySet, KeyRevision); PackageSize = BitConverter.ToInt32(Header.Counter, 0) ^ BitConverter.ToInt32(Header.Counter, 8) ^ BitConverter.ToInt32(Header.Counter, 12); @@ -106,7 +107,7 @@ namespace LibHac return new CachedStorage(new Aes128CtrStorage(encStorage, Key, Header.SectionCounters[1], true), 0x4000, 4, true); } - private int FindKeyGeneration(Keyset keyset, IStorage storage) + private int FindKeyGeneration(KeySet keySet, IStorage storage) { var counter = new byte[0x10]; var decBuffer = new byte[0x10]; @@ -115,7 +116,8 @@ namespace LibHac for (int i = 0; i < 0x20; i++) { - var dec = new Aes128CtrStorage(storage.Slice(0x100), keyset.Package2Keys[i], counter, false); + var dec = new Aes128CtrStorage(storage.Slice(0x100), keySet.Package2Keys[i].DataRo.ToArray(), counter, + false); dec.Read(0x50, decBuffer).ThrowIfFailure(); if (BitConverter.ToUInt32(decBuffer, 0) == Pk21Magic) @@ -145,14 +147,14 @@ namespace LibHac public Validity SignatureValidity { get; } - public Package2Header(IStorage storage, Keyset keyset, int keyGeneration) + public Package2Header(IStorage storage, KeySet keySet, int keyGeneration) { var reader = new BinaryReader(storage.AsStream()); - byte[] key = keyset.Package2Keys[keyGeneration]; + byte[] key = keySet.Package2Keys[keyGeneration].DataRo.ToArray(); Signature = reader.ReadBytes(0x100); byte[] sigData = reader.ReadBytes(0x100); - SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keyset.Package2FixedKeyModulus); + SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKey.Modulus); reader.BaseStream.Position -= 0x100; Counter = reader.ReadBytes(0x10); diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 4361002e..ed52dda7 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -16,7 +17,7 @@ namespace LibHac { public class SwitchFs : IDisposable { - public Keyset Keyset { get; } + public KeySet KeySet { get; } public IFileSystem ContentFs { get; } public IFileSystem SaveFs { get; } @@ -25,9 +26,9 @@ namespace LibHac public Dictionary Titles { get; } = new Dictionary(); public Dictionary Applications { get; } = new Dictionary(); - public SwitchFs(Keyset keyset, IFileSystem contentFileSystem, IFileSystem saveFileSystem) + public SwitchFs(KeySet keySet, IFileSystem contentFileSystem, IFileSystem saveFileSystem) { - Keyset = keyset; + KeySet = keySet; ContentFs = contentFileSystem; SaveFs = saveFileSystem; @@ -38,7 +39,7 @@ namespace LibHac CreateApplications(); } - public static SwitchFs OpenSdCard(Keyset keyset, IAttributeFileSystem fileSystem) + public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem) { var concatFs = new ConcatenationFileSystem(fileSystem); SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); @@ -47,15 +48,15 @@ namespace LibHac if (fileSystem.DirectoryExists("/Nintendo/save")) { SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem saveDirFs, concatFs, "/Nintendo/save".ToU8String()).ThrowIfFailure(); - encSaveFs = new AesXtsFileSystem(saveDirFs, keyset.SdCardKeys[0], 0x4000); + encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000); } - var encContentFs = new AesXtsFileSystem(contentDirFs, keyset.SdCardKeys[1], 0x4000); + var encContentFs = new AesXtsFileSystem(contentDirFs, keySet.SdCardEncryptionKeys[1].DataRo.ToArray(), 0x4000); - return new SwitchFs(keyset, encContentFs, encSaveFs); + return new SwitchFs(keySet, encContentFs, encSaveFs); } - public static SwitchFs OpenNandPartition(Keyset keyset, IAttributeFileSystem fileSystem) + public static SwitchFs OpenNandPartition(KeySet keySet, IAttributeFileSystem fileSystem) { var concatFs = new ConcatenationFileSystem(fileSystem); SubdirectoryFileSystem saveDirFs = null; @@ -67,12 +68,12 @@ namespace LibHac SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Contents".ToU8String()).ThrowIfFailure(); - return new SwitchFs(keyset, contentDirFs, saveDirFs); + return new SwitchFs(keySet, contentDirFs, saveDirFs); } - public static SwitchFs OpenNcaDirectory(Keyset keyset, IFileSystem fileSystem) + public static SwitchFs OpenNcaDirectory(KeySet keySet, IFileSystem fileSystem) { - return new SwitchFs(keyset, fileSystem, null); + return new SwitchFs(keySet, fileSystem, null); } private void OpenAllNcas() @@ -88,7 +89,7 @@ namespace LibHac { ContentFs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - nca = new SwitchFsNca(new Nca(Keyset, ncaFile.AsStorage())); + nca = new SwitchFsNca(new Nca(KeySet, ncaFile.AsStorage())); nca.NcaId = GetNcaFilename(fileEntry.Name, nca); string extension = nca.Nca.Header.ContentType == NcaContentType.Meta ? ".cnmt.nca" : ".nca"; @@ -126,7 +127,7 @@ namespace LibHac { SaveFs.OpenFile(out IFile file, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - save = new SaveDataFileSystem(Keyset, file.AsStorage(), IntegrityCheckLevel.None, true); + save = new SaveDataFileSystem(KeySet, file.AsStorage(), IntegrityCheckLevel.None, true); } catch (Exception ex) { diff --git a/src/LibHac/Ticket.cs b/src/LibHac/Ticket.cs index 9bf27664..6b328197 100644 --- a/src/LibHac/Ticket.cs +++ b/src/LibHac/Ticket.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using LibHac.Common.Keys; namespace LibHac { @@ -144,7 +145,7 @@ namespace LibHac return stream.ToArray(); } - public byte[] GetTitleKey(Keyset keyset) + public byte[] GetTitleKey(KeySet keySet) { if (TitleKeyType == TitleKeyType.Common) { @@ -153,7 +154,7 @@ namespace LibHac return commonKey; } - return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keyset.EticketExtKeyRsa); + return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keySet.ETicketExtKeyRsa); } } diff --git a/src/LibHac/Xci.cs b/src/LibHac/Xci.cs index 66eeab96..f0e0375e 100644 --- a/src/LibHac/Xci.cs +++ b/src/LibHac/Xci.cs @@ -1,4 +1,5 @@ using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -13,10 +14,10 @@ namespace LibHac private object InitLocker { get; } = new object(); private XciPartition RootPartition { get; set; } - public Xci(Keyset keyset, IStorage storage) + public Xci(KeySet keySet, IStorage storage) { BaseStorage = storage; - Header = new XciHeader(keyset, storage.AsStream()); + Header = new XciHeader(keySet, storage.AsStream()); } public bool HasPartition(XciPartitionType type) diff --git a/src/LibHac/XciHeader.cs b/src/LibHac/XciHeader.cs index 7fa5fb4c..b893c375 100644 --- a/src/LibHac/XciHeader.cs +++ b/src/LibHac/XciHeader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text; +using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Fs; @@ -69,7 +70,7 @@ namespace LibHac public Validity SignatureValidity { get; set; } public Validity PartitionFsHeaderValidity { get; set; } - public XciHeader(Keyset keyset, Stream stream) + public XciHeader(KeySet keySet, Stream stream) { using (var reader = new BinaryReader(stream, Encoding.Default, true)) { @@ -107,11 +108,11 @@ namespace LibHac SelKey = reader.ReadInt32(); LimAreaPage = reader.ReadInt32(); - if (keyset != null && !keyset.XciHeaderKey.IsEmpty()) + if (keySet != null && !keySet.XciHeaderKey.IsEmpty()) { byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize); var decHeader = new byte[EncryptedHeaderSize]; - Aes.DecryptCbc128(encHeader, decHeader, keyset.XciHeaderKey, AesCbcIv); + Aes.DecryptCbc128(encHeader, decHeader, keySet.XciHeaderKey, AesCbcIv); using (var decreader = new BinaryReader(new MemoryStream(decHeader))) { diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index 0791c14b..bd73f28a 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -1,4 +1,5 @@ using LibHac; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.FsSystem; @@ -69,6 +70,8 @@ namespace hactoolnet return IntegrityCheckLevel.None; } } + + public KeySet.Mode KeyMode => UseDevKeys ? KeySet.Mode.Dev : KeySet.Mode.Prod; } internal enum FileType @@ -95,7 +98,7 @@ namespace hactoolnet internal class Context { public Options Options; - public Keyset Keyset; + public KeySet KeySet; public ProgressBar Logger; public FileSystemClient FsClient; } diff --git a/src/hactoolnet/ProcessDelta.cs b/src/hactoolnet/ProcessDelta.cs index 00f6da1b..791e5427 100644 --- a/src/hactoolnet/ProcessDelta.cs +++ b/src/hactoolnet/ProcessDelta.cs @@ -28,7 +28,7 @@ namespace hactoolnet { try { - var nca = new Nca(ctx.Keyset, deltaStorage); + var nca = new Nca(ctx.KeySet, deltaStorage); IFileSystem fs = nca.OpenFileSystem(0, IntegrityCheckLevel.ErrorOnInvalid); if (!fs.FileExists(FragmentFileName)) diff --git a/src/hactoolnet/ProcessNax0.cs b/src/hactoolnet/ProcessNax0.cs index 4ddc7055..5507fe85 100644 --- a/src/hactoolnet/ProcessNax0.cs +++ b/src/hactoolnet/ProcessNax0.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using LibHac; using LibHac.Common; +using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSystem; using static hactoolnet.Print; @@ -13,7 +14,13 @@ namespace hactoolnet { public static void Process(Context ctx) { - byte[][] keys = ctx.Keyset.SdCardKeys; + if (ctx.Options.SdPath == null) + { + ctx.Logger.LogMessage("SD path must be specified."); + return; + } + + Span keys = ctx.KeySet.SdCardEncryptionKeys; using var baseFile = new LocalFile(ctx.Options.InFile, OpenMode.Read); @@ -22,8 +29,8 @@ namespace hactoolnet for (int i = 0; i < keys.Length; i++) { - byte[] kekSource = keys[i].AsSpan(0, 0x10).ToArray(); - byte[] validationKey = keys[i].AsSpan(0x10, 0x10).ToArray(); + byte[] kekSource = keys[i].SubKeys[0].DataRo.ToArray(); + byte[] validationKey = keys[i].SubKeys[1].DataRo.ToArray(); try { @@ -37,7 +44,7 @@ namespace hactoolnet if (xtsFile == null) { - ctx.Logger.LogMessage($"Error: NAX0 key derivation failed. Check SD card seed and relative path."); + ctx.Logger.LogMessage("Error: NAX0 key derivation failed. Check SD card seed and relative path."); return; } diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index d5530a23..e3ba8ffb 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -17,7 +17,7 @@ namespace hactoolnet { using (IStorage file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - var nca = new Nca(ctx.Keyset, file); + var nca = new Nca(ctx.KeySet, file); Nca baseNca = null; var ncaHolder = new NcaHolder { Nca = nca }; @@ -33,7 +33,7 @@ namespace hactoolnet if (ctx.Options.BaseNca != null) { IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read); - baseNca = new Nca(ctx.Keyset, baseFile); + baseNca = new Nca(ctx.KeySet, baseFile); } for (int i = 0; i < 3; i++) @@ -223,10 +223,17 @@ namespace hactoolnet return nca.Header.VerifySignature2(npdm.AciD.Rsa2048Modulus); } + public static int GetMasterKeyRevisionFromKeyGeneration(int keyGeneration) + { + if (keyGeneration == 0) return 0; + + return keyGeneration - 1; + } + private static string Print(this NcaHolder ncaHolder) { Nca nca = ncaHolder.Nca; - int masterKey = Keyset.GetMasterKeyRevisionFromKeyGeneration(nca.Header.KeyGeneration); + int masterKey = GetMasterKeyRevisionFromKeyGeneration(nca.Header.KeyGeneration); int colLen = 36; var sb = new StringBuilder(); diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index 40e08814..5e54b286 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -17,7 +17,7 @@ namespace hactoolnet using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { var package1 = new LibHac.Boot.Package1(); - package1.Initialize(ctx.Keyset, file).ThrowIfFailure(); + package1.Initialize(ctx.KeySet, file).ThrowIfFailure(); ctx.Logger.LogMessage(package1.Print()); @@ -107,7 +107,7 @@ namespace hactoolnet using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)) { var package2 = new Package2StorageReader(); - package2.Initialize(ctx.Keyset, file).ThrowIfFailure(); + package2.Initialize(ctx.KeySet, file).ThrowIfFailure(); ctx.Logger.LogMessage(package2.Print()); diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index 0ea5e29c..f717d56a 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using LibHac; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -29,7 +30,7 @@ namespace hactoolnet { bool signNeeded = ctx.Options.SignSave; - var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true); + var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true); FileSystemClient fs = ctx.FsClient; fs.Register("save".ToU8Span(), save); @@ -104,9 +105,10 @@ namespace hactoolnet { if (signNeeded) { - if (save.Commit(ctx.Keyset).IsSuccess()) + if (save.Commit(ctx.KeySet).IsSuccess()) { - ctx.Logger.LogMessage($"Successfully signed save file with key {ctx.Keyset.SaveMacKey.ToHexString()}"); + ctx.Logger.LogMessage( + $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); } else { @@ -126,9 +128,10 @@ namespace hactoolnet if (signNeeded) { - if (save.Commit(ctx.Keyset).IsSuccess()) + if (save.Commit(ctx.KeySet).IsSuccess()) { - ctx.Logger.LogMessage($"Successfully signed save file with key {ctx.Keyset.SaveMacKey.ToHexString()}"); + ctx.Logger.LogMessage( + $"Successfully signed save file with key {ctx.KeySet.DeviceUniqueSaveMacKeys[0].ToString()}"); } else { @@ -147,7 +150,7 @@ namespace hactoolnet } } - ctx.Logger.LogMessage(save.Print(ctx.Keyset)); + ctx.Logger.LogMessage(save.Print(ctx.KeySet)); //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore)); fs.Unmount("save".ToU8Span()); @@ -316,7 +319,7 @@ namespace hactoolnet } } - private static string Print(this SaveDataFileSystem save, Keyset keyset) + private static string Print(this SaveDataFileSystem save, KeySet keySet) { int colLen = 25; var sb = new StringBuilder(); @@ -325,7 +328,7 @@ namespace hactoolnet save.GetFreeSpaceSize(out long freeSpace, "".ToU8String()).ThrowIfFailure(); sb.AppendLine("Savefile:"); - PrintItem(sb, colLen, "CMAC Key Used:", keyset.SaveMacKey); + PrintItem(sb, colLen, "CMAC Key Used:", keySet.DeviceUniqueSaveMacKeys[0].DataRo.ToArray()); PrintItem(sb, colLen, $"CMAC Signature{save.Header.SignatureValidity.GetValidityString()}:", save.Header.Cmac); PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}"); PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId); diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index b3926b05..fbe9c50b 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -24,21 +24,21 @@ namespace hactoolnet if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Nintendo", "Contents", "registered"))) { ctx.Logger.LogMessage("Treating path as SD card storage"); - switchFs = SwitchFs.OpenSdCard(ctx.Keyset, baseFs); + switchFs = SwitchFs.OpenSdCard(ctx.KeySet, baseFs); CheckForNcaFolders(ctx, switchFs); } else if (Directory.Exists(Path.Combine(ctx.Options.InFile, "Contents", "registered"))) { ctx.Logger.LogMessage("Treating path as NAND storage"); - switchFs = SwitchFs.OpenNandPartition(ctx.Keyset, baseFs); + switchFs = SwitchFs.OpenNandPartition(ctx.KeySet, baseFs); CheckForNcaFolders(ctx, switchFs); } else { ctx.Logger.LogMessage("Treating path as a directory of loose NCAs"); - switchFs = SwitchFs.OpenNcaDirectory(ctx.Keyset, baseFs); + switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, baseFs); } if (ctx.Options.ListNcas) diff --git a/src/hactoolnet/ProcessXci.cs b/src/hactoolnet/ProcessXci.cs index 10d61aa7..8b1c7807 100644 --- a/src/hactoolnet/ProcessXci.cs +++ b/src/hactoolnet/ProcessXci.cs @@ -15,7 +15,7 @@ namespace hactoolnet { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - var xci = new Xci(ctx.Keyset, file); + var xci = new Xci(ctx.KeySet, file); ctx.Logger.LogMessage(xci.Print()); @@ -125,7 +125,7 @@ namespace hactoolnet foreach (PartitionFileEntry fileEntry in partition.Files.Where(x => x.Name.EndsWith(".nca"))) { IStorage ncaStorage = partition.OpenFile(fileEntry, OpenMode.Read).AsStorage(); - var nca = new Nca(ctx.Keyset, ncaStorage); + var nca = new Nca(ctx.KeySet, ncaStorage); if (nca.Header.ContentType == NcaContentType.Program) { diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 0302c5b3..f33f67bb 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Text; using LibHac; +using LibHac.Common.Keys; using LibHac.Fs; namespace hactoolnet @@ -197,26 +198,34 @@ namespace hactoolnet consoleKeyFile = homeConsoleKeyFile; } - ctx.Keyset = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile, ctx.Logger, ctx.Options.UseDevKeys); + ctx.KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile, ctx.Logger, ctx.Options.KeyMode); + if (ctx.Options.SdSeed != null) { - ctx.Keyset.SetSdSeed(ctx.Options.SdSeed.ToBytes()); - } - - if (ctx.Options.InFileType == FileType.Keygen && ctx.Options.OutDir != null) - { - string dir = ctx.Options.OutDir; - Directory.CreateDirectory(dir); - - File.WriteAllText(Path.Combine(dir, keyFileName), ExternalKeyReader.PrintCommonKeys(ctx.Keyset)); - File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyReader.PrintUniqueKeys(ctx.Keyset)); - File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyReader.PrintTitleKeys(ctx.Keyset)); + ctx.KeySet.SetSdSeed(ctx.Options.SdSeed.ToBytes()); } } private static void ProcessKeygen(Context ctx) { - Console.WriteLine(ExternalKeyReader.PrintCommonKeys(ctx.Keyset)); + Console.WriteLine(ExternalKeyReader.PrintAllKeys(ctx.KeySet)); + + if (ctx.Options.OutDir != null) + { + string keyFileName = ctx.Options.UseDevKeys ? "dev.keys" : "prod.keys"; + string dir = ctx.Options.OutDir; + Directory.CreateDirectory(dir); + + File.WriteAllText(Path.Combine(dir, keyFileName), ExternalKeyReader.PrintCommonKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyReader.PrintDeviceKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyReader.PrintTitleKeys(ctx.KeySet)); + + if (!ctx.Options.UseDevKeys) + { + File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), + ExternalKeyReader.PrintCommonKeysWithDev(ctx.KeySet)); + } + } } // For running random stuff diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs index c5f80d91..132e5ada 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs @@ -1,4 +1,5 @@ -using LibHac.Fs; +using LibHac.Common.Keys; +using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; @@ -10,7 +11,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests { rootFs = new InMemoryFileSystem(); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new Keyset()); + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new KeySet()); defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); diff --git a/tests/LibHac.Tests/HorizonFactory.cs b/tests/LibHac.Tests/HorizonFactory.cs index a580ecb8..cdfbca8d 100644 --- a/tests/LibHac.Tests/HorizonFactory.cs +++ b/tests/LibHac.Tests/HorizonFactory.cs @@ -1,4 +1,5 @@ -using LibHac.Fs; +using LibHac.Common.Keys; +using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; @@ -10,7 +11,7 @@ namespace LibHac.Tests { IFileSystem rootFs = new InMemoryFileSystem(); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new Keyset()); + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, new KeySet()); var config = new FileSystemServerConfig(); config.FsCreators = defaultObjects.FsCreators; From bac541947f13d0de17b4e51d8883b04882e59ec9 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 6 Oct 2020 18:15:32 -0700 Subject: [PATCH 07/16] Embed key sources in the library Embedded keys are generated at build-time from the IncludedKeys.txt file under the build directory. A separate codegen build project was created because generating the embedded keys requires the same LibHac that is being built. _buildCodeGen.csproj is located under CodeGen because NUKE doesn't like having two build projects in the same directory. --- .gitignore | 3 + LibHac.sln | 7 + build/Build.cs | 24 +- build/CodeGen/Common.cs | 100 ++++ build/CodeGen/IncludedKeys.txt | 56 +++ build/CodeGen/{ => Stage1}/ResultCodegen.cs | 93 +--- build/CodeGen/Stage2/KeysCodeGen.cs | 393 +++++++++++++++ build/CodeGen/Stage2/RunStage2.cs | 26 + build/CodeGen/_buildCodeGen.csproj | 27 ++ build/_build.csproj | 3 + src/LibHac/Boot/Package2StorageReader.cs | 2 +- src/LibHac/Common/Keys/DefaultKeySet.Empty.cs | 19 + src/LibHac/Common/Keys/DefaultKeySet.cs | 71 +++ src/LibHac/Common/Keys/ExternalKeyReader.cs | 219 +++++---- src/LibHac/Common/Keys/KeyDerivation.cs | 396 +++++++++++++++ src/LibHac/Common/Keys/KeySet.cs | 454 ++---------------- src/LibHac/FsSystem/NcaUtils/Nca.cs | 4 +- src/LibHac/LibHac.csproj | 1 + src/LibHac/Npdm/Acid.cs | 2 +- src/LibHac/Package2.cs | 2 +- src/LibHac/Util/Optional.cs | 34 ++ 21 files changed, 1342 insertions(+), 594 deletions(-) create mode 100644 build/CodeGen/Common.cs create mode 100644 build/CodeGen/IncludedKeys.txt rename build/CodeGen/{ => Stage1}/ResultCodegen.cs (83%) create mode 100644 build/CodeGen/Stage2/KeysCodeGen.cs create mode 100644 build/CodeGen/Stage2/RunStage2.cs create mode 100644 build/CodeGen/_buildCodeGen.csproj create mode 100644 src/LibHac/Common/Keys/DefaultKeySet.Empty.cs create mode 100644 src/LibHac/Common/Keys/DefaultKeySet.cs create mode 100644 src/LibHac/Common/Keys/KeyDerivation.cs create mode 100644 src/LibHac/Util/Optional.cs diff --git a/.gitignore b/.gitignore index 961d8cf4..aecebd82 100644 --- a/.gitignore +++ b/.gitignore @@ -267,4 +267,7 @@ global.json !tests/LibHac.Tests/CryptoTests/TestVectors/* **/DisasmoBin/ +# Files generated at build time +ResultNameResolver.Generated.cs +DefaultKeySet.Generated.cs ResultNameResolver.Generated.cs \ No newline at end of file diff --git a/LibHac.sln b/LibHac.sln index 1a9b8892..ce921fd1 100644 --- a/LibHac.sln +++ b/LibHac.sln @@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibHac.Tests", "tests\LibHa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{C7150117-90B8-4083-8141-BBC35C9F44F6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_buildCodeGen", "build\CodeGen\_buildCodeGen.csproj", "{93B175E1-F12F-4A8C-85CA-CAC74691102A}" + ProjectSection(ProjectDependencies) = postProject + {FFCA6C31-D9D4-4ED8-A06D-0CC6B94422B8} = {FFCA6C31-D9D4-4ED8-A06D-0CC6B94422B8} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +36,8 @@ Global {679C89BD-5FDF-4CC2-9129-ABABD759035B}.Release|Any CPU.Build.0 = Release|Any CPU {C7150117-90B8-4083-8141-BBC35C9F44F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7150117-90B8-4083-8141-BBC35C9F44F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93B175E1-F12F-4A8C-85CA-CAC74691102A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93B175E1-F12F-4A8C-85CA-CAC74691102A}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/Build.cs b/build/Build.cs index 69106473..bf44a41a 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -8,7 +8,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using ICSharpCode.SharpZipLib.Zip; -using LibHacBuild.CodeGen; +using LibHacBuild.CodeGen.Stage1; using Nuke.Common; using Nuke.Common.CI.AppVeyor; using Nuke.Common.Git; @@ -54,6 +54,8 @@ namespace LibHacBuild Project LibHacTestProject => _solution.GetProject("LibHac.Tests").NotNull(); Project HactoolnetProject => _solution.GetProject("hactoolnet").NotNull(); + Project CodeGenProject => _solution.GetProject("_buildCodeGen").NotNull(); + private bool HasGitDir { get; set; } private string NativeRuntime { get; set; } @@ -196,6 +198,7 @@ namespace LibHacBuild .Executes(() => { ResultCodeGen.Run(); + RunCodegenStage2(); }); Target Compile => _ => _ @@ -668,5 +671,24 @@ namespace LibHacBuild { return XmlTasks.XmlPeekSingle(LibHacProject.Path, "/Project/PropertyGroup/VersionPrefix", null); } + + public void RunCodegenStage2() + { + Logger.Normal("\nBuilding stage 2 codegen project."); + + DotNetRunSettings settings = new DotNetRunSettings() + .SetProjectFile(CodeGenProject.Path); + // .SetLogOutput(false); + + try + { + DotNetRun(settings); + Logger.Normal(); + } + catch (ProcessException) + { + Logger.Error("\nError running stage 2 codegen. Skipping...\n"); + } + } } } diff --git a/build/CodeGen/Common.cs b/build/CodeGen/Common.cs new file mode 100644 index 00000000..8960dd7a --- /dev/null +++ b/build/CodeGen/Common.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Nuke.Common; + +namespace LibHacBuild.CodeGen +{ + public static class Common + { + public static string GetHeader() + { + string nl = Environment.NewLine; + return + "//-----------------------------------------------------------------------------" + nl + + "// This file was automatically generated." + nl + + "// Changes to this file will be lost when the file is regenerated." + nl + + "//" + nl + + "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + + "// repo and run the build script." + nl + + "//" + nl + + "// The script can be run with the \"codegen\" option to run only the" + nl + + "// code generation portion of the build." + nl + + "//-----------------------------------------------------------------------------"; + } + + // Write the file only if it has changed + // Preserve the UTF-8 BOM usage if the file already exists + public static void WriteOutput(string relativePath, string text) + { + if (string.IsNullOrWhiteSpace(relativePath)) + return; + + string rootPath = FindProjectDirectory(); + string fullPath = Path.Combine(rootPath, relativePath); + + // Default is true because Visual Studio saves .cs files with the BOM by default + bool hasBom = true; + byte[] bom = Encoding.UTF8.GetPreamble(); + byte[] oldFile = null; + + if (File.Exists(fullPath)) + { + oldFile = File.ReadAllBytes(fullPath); + + if (oldFile.Length >= 3) + hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); + } + + // Make line endings the same on Windows and Unix + if (Environment.NewLine == "\n") + { + text = text.Replace("\n", "\r\n"); + } + + byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); + + if (oldFile?.SequenceEqual(newFile) == true) + { + Logger.Normal($"{relativePath} is already up-to-date"); + return; + } + + Logger.Normal($"Generated file {relativePath}"); + File.WriteAllBytes(fullPath, newFile); + } + + public static Stream GetResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + string path = $"LibHacBuild.CodeGen.{name}"; + + Stream stream = assembly.GetManifestResourceStream(path); + if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); + + return stream; + } + + public static string FindProjectDirectory() + { + string currentDir = Environment.CurrentDirectory; + + while (currentDir != null) + { + if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) + { + break; + } + + currentDir = Path.GetDirectoryName(currentDir); + } + + if (currentDir == null) + throw new DirectoryNotFoundException("Unable to find project directory."); + + return Path.Combine(currentDir, "src"); + } + } +} diff --git a/build/CodeGen/IncludedKeys.txt b/build/CodeGen/IncludedKeys.txt new file mode 100644 index 00000000..3441482d --- /dev/null +++ b/build/CodeGen/IncludedKeys.txt @@ -0,0 +1,56 @@ +keyblob_mac_key_source = 59C7FB6FBE9BBE87656B15C0537336A5 +keyblob_key_source_00 = DF206F594454EFDC7074483B0DED9FD3 +keyblob_key_source_01 = 0C25615D684CEB421C2379EA822512AC +keyblob_key_source_02 = 337685EE884AAE0AC28AFD7D63C0433B +keyblob_key_source_03 = 2D1F4880EDECED3E3CF248B5657DF7BE +keyblob_key_source_04 = BB5A01F988AFF5FC6CFF079E133C3980 +keyblob_key_source_05 = D8CCE1266A353FCC20F32D3B517DE9C0 + +master_kek_source_06 = 374B772959B4043081F6E58C6D36179A +master_kek_source_07 = 9A3EA9ABFD56461C9BF6487F5CFA095C +master_kek_source_08 = DEDCE339308816F8AE97ADEC642D4141 +master_kek_source_09 = 1AEC11822B32387A2BEDBA01477E3B67 +master_kek_source_0a = 303F027ED838ECD7932534B530EBCA7A + +mariko_master_kek_source_06 = 1E80B8173EC060AA11BE1A4AA66FE4AE +mariko_master_kek_source_07 = 940867BD0A00388411D31ADBDD8DF18A +mariko_master_kek_source_08 = 5C24E3B8B4F700C23CFD0ACE13C3DC23 +mariko_master_kek_source_09 = 8669F00987C805AEB57B4874DE62A613 +mariko_master_kek_source_0a = 0E440CEDB436C03FAA1DAEBF62B10982 + +mariko_master_kek_source_dev_0a = F937CF9ABD86BBA99C9E03C4FCBC3BCE + +master_key_source = D8A2410AC6C59001C61D6A267C513F3C + +package2_key_source = FB8B6A9C7900C849EFD24D854D30A0C7 + +bis_kek_source = 34C1A0C48258F8B4FA9E5E6ADAFC7E4F +bis_key_source_00 = F83F386E2CD2CA32A89AB9AA29BFC7487D92B03AA8BFDEE1A74C3B6E35CB7106 +bis_key_source_01 = 41003049DDCCC065647A7EB41EED9C5F44424EDAB49DFCD98777249ADC9F7CA4 +bis_key_source_02 = 52C2E9EB09E3EE2932A10C1FB6A0926C4D12E14B2A474C1C09CB0359F015F4E4 +bis_key_source_03 = 52C2E9EB09E3EE2932A10C1FB6A0926C4D12E14B2A474C1C09CB0359F015F4E4 + +per_console_key_source = 4F025F0EB66D110EDC327D4186C2F478 +retail_specific_aes_key_source = E2D6B87A119CB880E822888A46FBA195 +aes_kek_generation_source = 4D870986C45D20722FBA1053DA92E8A9 +aes_key_generation_source = 89615EE05C31B6805FE58F3DA24F7AA8 +titlekek_source = 1EDC7B3B60E6B4D878B81715985E629B + +header_kek_source = 1F12913A4ACBF00D4CDE3AF6D523882A +header_key_source = 5A3ED84FDEC0D82631F7E25D197BF5D01C9B7BFAF628183D71F64D73F150B9D2 + +key_area_key_application_source = 7F59971E629F36A13098066F2144C30D +key_area_key_ocean_source = 327D36085AD1758DAB4E6FBAA555D882 +key_area_key_system_source = 8745F1BBA6BE79647D048BA67B5FDA4A + +save_mac_kek_source = D89C236EC9124E43C82B038743F9CF1B +save_mac_key_source_00 = E4CD3D4AD50F742845A487E5A063EA1F +save_mac_key_source_01 = EC249895656ADF4AA066B9880AC82C4C + +save_mac_sd_card_kek_source = 0489EF5D326E1A59C4B7AB8C367AAB17 +save_mac_sd_card_key_source = 6F645947C56146F9FFA045D595332918 + +sd_card_kek_source = 88358D9C629BA1A00147DBE0621B5432 +sd_card_save_key_source = 2449B722726703A81965E6E3EA582FDD9A951517B16E8F7F1F68263152EA296A +sd_card_nca_key_source = 5841A284935B56278B8E1FC518E99F2B67C793F0F24FDED075495DCA006D99C2 +sd_card_custom_storage_key_source = 370C345E12E4CEFE21B58E64DB52AF354F2CA5A3FC999A47C03EE004485B2FD0 \ No newline at end of file diff --git a/build/CodeGen/ResultCodegen.cs b/build/CodeGen/Stage1/ResultCodegen.cs similarity index 83% rename from build/CodeGen/ResultCodegen.cs rename to build/CodeGen/Stage1/ResultCodegen.cs index 184ad316..936f42c7 100644 --- a/build/CodeGen/ResultCodegen.cs +++ b/build/CodeGen/Stage1/ResultCodegen.cs @@ -4,15 +4,14 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using CsvHelper; using CsvHelper.Configuration; -using Nuke.Common; +using static LibHacBuild.CodeGen.Common; -namespace LibHacBuild.CodeGen +namespace LibHacBuild.CodeGen.Stage1 { public static class ResultCodeGen { @@ -282,63 +281,6 @@ namespace LibHacBuild.CodeGen return doc; } - private static string GetHeader() - { - string nl = Environment.NewLine; - return - "//-----------------------------------------------------------------------------" + nl + - "// This file was automatically generated." + nl + - "// Changes to this file will be lost when the file is regenerated." + nl + - "//" + nl + - "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + - "// repo and run the build script." + nl + - "//" + nl + - "// The script can be run with the \"codegen\" option to run only the" + nl + - "// code generation portion of the build." + nl + - "//-----------------------------------------------------------------------------"; - } - - // Write the file only if it has changed - // Preserve the UTF-8 BOM usage if the file already exists - private static void WriteOutput(string relativePath, string text) - { - if (string.IsNullOrWhiteSpace(relativePath)) - return; - - string rootPath = FindProjectDirectory(); - string fullPath = Path.Combine(rootPath, relativePath); - - // Default is true because Visual Studio saves .cs files with the BOM by default - bool hasBom = true; - byte[] bom = Encoding.UTF8.GetPreamble(); - byte[] oldFile = null; - - if (File.Exists(fullPath)) - { - oldFile = File.ReadAllBytes(fullPath); - - if (oldFile.Length >= 3) - hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); - } - - // Make line endings the same on Windows and Unix - if (Environment.NewLine == "\n") - { - text = text.Replace("\n", "\r\n"); - } - - byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); - - if (oldFile?.SequenceEqual(newFile) == true) - { - Logger.Normal($"{relativePath} is already up-to-date"); - return; - } - - Logger.Normal($"Generated file {relativePath}"); - File.WriteAllBytes(fullPath, newFile); - } - private static byte[] BuildArchive(ModuleInfo[] modules) { var builder = new ResultArchiveBuilder(); @@ -409,37 +351,6 @@ namespace LibHacBuild.CodeGen } } - private static Stream GetResource(string name) - { - var assembly = Assembly.GetExecutingAssembly(); - string path = $"LibHacBuild.CodeGen.{name}"; - - Stream stream = assembly.GetManifestResourceStream(path); - if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); - - return stream; - } - - private static string FindProjectDirectory() - { - string currentDir = Environment.CurrentDirectory; - - while (currentDir != null) - { - if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) - { - break; - } - - currentDir = Path.GetDirectoryName(currentDir); - } - - if (currentDir == null) - throw new DirectoryNotFoundException("Unable to find project directory."); - - return Path.Combine(currentDir, "src"); - } - private static int EstimateCilSize(ResultInfo result) { int size = 0; diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs new file mode 100644 index 00000000..f969b4a3 --- /dev/null +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Crypto; +using static LibHacBuild.CodeGen.Common; + +namespace LibHacBuild.CodeGen.Stage2 +{ + public static class KeysCodeGen + { + private static string InputMainKeyFileName = "IncludedKeys.txt"; + private static string GeneratedFilePath = "LibHac/Common/Keys/DefaultKeySet.Generated.cs"; + + public static void Run() + { + KeySet keySet = CreateKeySet(); + + WriteOutput(GeneratedFilePath, BuildDefaultKeySetFile(keySet)); + } + + private static string BuildDefaultKeySetFile(KeySet keySet) + { + var sb = new IndentingStringBuilder(); + + sb.AppendLine(GetHeader()); + sb.AppendLine(); + + sb.AppendLine("using System;"); + sb.AppendLine(); + + sb.AppendLine("namespace LibHac.Common.Keys"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine("internal static partial class DefaultKeySet"); + sb.AppendLineAndIncrease("{"); + + BuildArray(sb, "RootKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysDev)); + BuildArray(sb, "RootKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rootKeysProd)); + BuildArray(sb, "KeySeeds", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._keySeeds)); + BuildArray(sb, "StoredKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysDev)); + BuildArray(sb, "StoredKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysProd)); + BuildArray(sb, "DerivedKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysDev)); + BuildArray(sb, "DerivedKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysProd)); + BuildArray(sb, "DeviceKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._deviceKeys)); + BuildArray(sb, "RsaSigningKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysDev)); + BuildArray(sb, "RsaSigningKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaSigningKeysProd)); + BuildArray(sb, "RsaKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._rsaKeys)); + + sb.DecreaseAndAppendLine("}"); + sb.DecreaseAndAppendLine("}"); + + return sb.ToString(); + } + + private static void BuildArray(IndentingStringBuilder sb, string name, ReadOnlySpan data) + { + sb.AppendSpacerLine(); + sb.Append($"private static ReadOnlySpan {name} => new byte[]"); + + if (data.IsEmpty()) + { + sb.AppendLine(" { };"); + return; + } + + sb.AppendLine(); + sb.AppendLineAndIncrease("{"); + + for (int i = 0; i < data.Length; i++) + { + if (i % 16 != 0) sb.Append(" "); + sb.Append($"0x{data[i]:x2}"); + + if (i != data.Length - 1) + { + sb.Append(","); + if (i % 16 == 15) sb.AppendLine(); + } + } + + sb.AppendLine(); + sb.DecreaseAndAppendLine("};"); + } + + private static KeySet CreateKeySet() + { + var keySet = new KeySet(); + + // Populate the key set with all the keys in IncludedKeys.txt + using (var reader = new StreamReader(Common.GetResource(InputMainKeyFileName))) + { + List list = ExternalKeyReader.CreateKeyList(); + ExternalKeyReader.ReadMainKeys(keySet, reader, list); + } + + // Recover all the RSA key parameters and write the key to the key set + RSAParameters betaNca0Params = + Rsa.RecoverParameters(BetaNca0Modulus, StandardPublicExponent, BetaNca0Exponent); + + betaNca0Params.D.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PrivateExponent.Data); + betaNca0Params.DP.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dp.Data); + betaNca0Params.DQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dq.Data); + betaNca0Params.Exponent.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PublicExponent.Data); + betaNca0Params.InverseQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.InverseQ.Data); + betaNca0Params.Modulus.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Modulus.Data); + betaNca0Params.P.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.P.Data); + betaNca0Params.Q.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Q.Data); + + // First populate the prod RSA keys + keySet.SetMode(KeySet.Mode.Prod); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Prod.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Prod.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Prod.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Prod.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusProd.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + // Populate the dev RSA keys + keySet.SetMode(KeySet.Mode.Dev); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Dev.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Dev.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Dev.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Dev.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusDev.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + return keySet; + } + + private static ReadOnlySpan StandardPublicExponent => new byte[] + { + 0x01, 0x00, 0x01 + }; + + private static ReadOnlySpan BetaNca0Modulus => new byte[] + { + 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 + }; + + private static ReadOnlySpan BetaNca0Exponent => new byte[] + { + 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 ReadOnlySpan NcaHdrFixedKeyModulus0Prod => new byte[] + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Prod => new byte[] + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Prod => new byte[] + { + 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Prod => new byte[] + { + 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 + }; + + private static ReadOnlySpan Package2FixedKeyModulusProd => new byte[] + { + 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, + 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, + 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, + 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, + 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, + 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, + 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, + 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, + 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, + 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, + 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, + 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, + 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, + 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, + 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, + 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus0Dev => new byte[] + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Dev => new byte[] + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Dev => new byte[] + { + 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Dev => new byte[] + { + 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B + }; + + private static ReadOnlySpan Package2FixedKeyModulusDev => new byte[] + { + 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, + 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, + 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, + 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, + 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, + 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, + 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, + 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, + 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, + 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, + 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, + 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, + 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, + 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, + 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, + 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B + }; + } +} diff --git a/build/CodeGen/Stage2/RunStage2.cs b/build/CodeGen/Stage2/RunStage2.cs new file mode 100644 index 00000000..b339b91d --- /dev/null +++ b/build/CodeGen/Stage2/RunStage2.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using Octokit; + +namespace LibHacBuild.CodeGen.Stage2 +{ + // Some codegen depends on classes in LibHac. + // The part that does is split out into a separate project so the main build project + // doesn't depend on LibHac. + public static class RunStage2 + { + private const string SolutionFileName = "LibHac.sln"; + public static int Main(string[] args) + { + if (!File.Exists(SolutionFileName)) + { + Console.Error.WriteLine($"Could not find the solution file {SolutionFileName}."); + return 1; + } + + KeysCodeGen.Run(); + + return 0; + } + } +} diff --git a/build/CodeGen/_buildCodeGen.csproj b/build/CodeGen/_buildCodeGen.csproj new file mode 100644 index 00000000..f4811b2d --- /dev/null +++ b/build/CodeGen/_buildCodeGen.csproj @@ -0,0 +1,27 @@ + + + + Exe + netcoreapp3.1 + false + LibHacBuild.CodeGen + False + CS0649;CS0169 + false + + + + + + + + + + + + + + + + + diff --git a/build/_build.csproj b/build/_build.csproj index 8e372242..aa32db29 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -22,6 +22,9 @@ + + + diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs index 187256ac..9e53ad65 100644 --- a/src/LibHac/Boot/Package2StorageReader.cs +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -143,7 +143,7 @@ namespace LibHac.Boot Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes); if (rc.IsFailure()) return rc; - return _header.VerifySignature(_keySet.Package2SigningKey.Modulus, metaBytes); + return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes); } /// diff --git a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs new file mode 100644 index 00000000..fda79d5c --- /dev/null +++ b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs @@ -0,0 +1,19 @@ +using System; + +namespace LibHac.Common.Keys +{ + internal static partial class DefaultKeySet + { + private static ReadOnlySpan RootKeysDev => new byte[] { }; + private static ReadOnlySpan RootKeysProd => new byte[] { }; + private static ReadOnlySpan KeySeeds => new byte[] { }; + private static ReadOnlySpan StoredKeysDev => new byte[] { }; + private static ReadOnlySpan StoredKeysProd => new byte[] { }; + private static ReadOnlySpan DerivedKeysDev => new byte[] { }; + private static ReadOnlySpan DerivedKeysProd => new byte[] { }; + private static ReadOnlySpan DeviceKeys => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysDev => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysProd => new byte[] { }; + private static ReadOnlySpan RsaKeys => new byte[] { }; + } +} diff --git a/src/LibHac/Common/Keys/DefaultKeySet.cs b/src/LibHac/Common/Keys/DefaultKeySet.cs new file mode 100644 index 00000000..e6e4d818 --- /dev/null +++ b/src/LibHac/Common/Keys/DefaultKeySet.cs @@ -0,0 +1,71 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.Keys +{ + internal static partial class DefaultKeySet + { + public static KeySet CreateDefaultKeySet() + { + var keySet = new KeySet(); + + // Fill the key set with any key structs included in the library. + if (RootKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rootKeysDev = MemoryMarshal.Cast(RootKeysDev)[0]; + } + + if (RootKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rootKeysProd = MemoryMarshal.Cast(RootKeysProd)[0]; + } + + if (KeySeeds.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._keySeeds = MemoryMarshal.Cast(KeySeeds)[0]; + } + + if (StoredKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysDev = MemoryMarshal.Cast(StoredKeysDev)[0]; + } + + if (StoredKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysProd = MemoryMarshal.Cast(StoredKeysProd)[0]; + } + + if (DerivedKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysDev = MemoryMarshal.Cast(DerivedKeysDev)[0]; + } + + if (DerivedKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysProd = MemoryMarshal.Cast(DerivedKeysProd)[0]; + } + + if (DeviceKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._deviceKeys = MemoryMarshal.Cast(DeviceKeys)[0]; + } + + if (RsaSigningKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysDev = MemoryMarshal.Cast(RsaSigningKeysDev)[0]; + } + + if (RsaSigningKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysProd = MemoryMarshal.Cast(RsaSigningKeysProd)[0]; + } + + if (RsaKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaKeys = MemoryMarshal.Cast(RsaKeys)[0]; + } + + return keySet; + } + } +} diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index b6fafc3d..29f47ea6 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -192,17 +192,41 @@ namespace LibHac.Common.Keys private const int TitleKeySize = 0x10; + /// + /// Loads keys from key files into an existing . Missing keys will be + /// derived from existing keys if possible. Any file names will be skipped. + /// + /// The where the loaded keys will be placed. + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) { List keyInfos = CreateKeyList(); - if (filename != null) ReadMainKeys(keySet, filename, keyInfos, logger); - if (consoleKeysFilename != null) ReadMainKeys(keySet, consoleKeysFilename, keyInfos, logger); - if (titleKeysFilename != null) ReadTitleKeys(keySet, titleKeysFilename, logger); + if (filename != null) + { + using var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)); + ReadMainKeys(keySet, reader, keyInfos, logger); + } + + if (consoleKeysFilename != null) + { + using var reader = new StreamReader(new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read)); + ReadMainKeys(keySet, reader, keyInfos, logger); + } + + if (titleKeysFilename != null) + { + using var reader = new StreamReader(new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read)); + ReadTitleKeys(keySet, reader, logger); + } keySet.DeriveKeys(logger); + // Dev keys can read from prod key files, so derive any missing keys if necessary. if (keySet.CurrentMode == KeySet.Mode.Prod) { keySet.SetMode(KeySet.Mode.Dev); @@ -211,10 +235,20 @@ namespace LibHac.Common.Keys } } + /// + /// Creates a new initialized with the key files specified and any keys included in the library. + /// Missing keys will be derived from existing keys if possible. Any file names will be skipped. + /// + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + /// Specifies whether the keys being read are dev or prod keys. + /// The created . public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null, KeySet.Mode mode = KeySet.Mode.Prod) { - var keySet = new KeySet(); + var keySet = KeySet.CreateDefaultKeySet(); keySet.SetMode(mode); ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); @@ -222,106 +256,119 @@ namespace LibHac.Common.Keys return keySet; } - private static void ReadMainKeys(KeySet keySet, string filename, List keyList, IProgressReport logger = null) + /// + /// Loads non-title keys from a into an existing . + /// Missing keys will not be derived. + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// A list of all the keys that will be loaded into the key set. + /// will create a list containing all loadable keys. + /// An optional logger that key-parsing errors will be written to. + public static void ReadMainKeys(KeySet keySet, TextReader keyFileReader, List keyList, + IProgressReport logger = null) { - if (filename == null) return; + if (keyFileReader == null) return; - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + // Todo: Improve key parsing + string line; + while ((line = keyFileReader.ReadLine()) != null) { - string line; - while ((line = reader.ReadLine()) != null) + string[] a = line.Split(',', '='); + if (a.Length != 2) continue; + + string keyName = a[0].Trim(); + string valueStr = a[1].Trim(); + + if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, keyName)) { - string[] a = line.Split(',', '='); - if (a.Length != 2) continue; + logger?.LogMessage($"Failed to match key {keyName}"); + continue; + } - string keyName = a[0].Trim(); - string valueStr = a[1].Trim(); + Span key; - if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, keyName)) - { - logger?.LogMessage($"Failed to match key {keyName}"); - continue; - } + // Get the dev key in the key set if needed. + if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + key = info.Key.Getter(keySet, info.Index); + keySet.SetMode(KeySet.Mode.Prod); + } + else + { + key = info.Key.Getter(keySet, info.Index); + } - Span key; + if (valueStr.Length != key.Length * 2) + { + logger?.LogMessage( + $"Key {keyName} had incorrect size {valueStr.Length}. Must be {key.Length * 2} hex digits."); + continue; + } - // Get the dev key in the key set if needed. - if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) - { - keySet.SetMode(KeySet.Mode.Dev); - key = info.Key.Getter(keySet, info.Index); - keySet.SetMode(KeySet.Mode.Prod); - } - else - { - key = info.Key.Getter(keySet, info.Index); - } + if (!Utilities.TryToBytes(valueStr, key)) + { + key.Clear(); - if (valueStr.Length != key.Length * 2) - { - logger?.LogMessage($"Key {keyName} had incorrect size {valueStr.Length}. Must be {key.Length * 2} hex digits."); - continue; - } - - if (!Utilities.TryToBytes(valueStr, key)) - { - key.Clear(); - - logger?.LogMessage($"Key {keyName} had an invalid value. Must be {key.Length * 2} hex digits."); - } + logger?.LogMessage($"Key {keyName} had an invalid value. Must be {key.Length * 2} hex digits."); } } } - private static void ReadTitleKeys(KeySet keySet, string filename, IProgressReport progress = null) + /// + /// Loads title keys from a into an existing . + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// An optional logger that key-parsing errors will be written to. + public static void ReadTitleKeys(KeySet keySet, TextReader keyFileReader, IProgressReport logger = null) { - if (filename == null) return; + if (keyFileReader == null) return; - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + // Todo: Improve key parsing + string line; + while ((line = keyFileReader.ReadLine()) != null) { - string line; - while ((line = reader.ReadLine()) != null) + string[] splitLine; + + // Some people use pipes as delimiters + if (line.Contains('|')) { - string[] splitLine; - - // Some people use pipes as delimiters - if (line.Contains('|')) - { - splitLine = line.Split('|'); - } - else - { - splitLine = line.Split(',', '='); - } - - if (splitLine.Length < 2) continue; - - if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) - { - progress?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); - continue; - } - - if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) - { - progress?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); - continue; - } - - if (rightsId.Length != TitleKeySize) - { - progress?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); - continue; - } - - if (titleKey.Length != TitleKeySize) - { - progress?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); - continue; - } - - keySet.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); + splitLine = line.Split('|'); } + else + { + splitLine = line.Split(',', '='); + } + + if (splitLine.Length < 2) continue; + + if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) + { + logger?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); + continue; + } + + if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) + { + logger?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); + continue; + } + + if (rightsId.Length != TitleKeySize) + { + logger?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); + continue; + } + + if (titleKey.Length != TitleKeySize) + { + logger?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); + continue; + } + + keySet.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); } } diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs new file mode 100644 index 00000000..13bb44d8 --- /dev/null +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -0,0 +1,396 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Crypto; + +namespace LibHac.Common.Keys +{ + internal static class KeyDerivation + { + public static void DeriveAllKeys(KeySet keySet, IProgressReport logger = null) + { + DeriveKeyBlobKeys(keySet); + DecryptKeyBlobs(keySet, logger); + ReadKeyBlobs(keySet); + + Derive620Keys(keySet); + Derive620MasterKeks(keySet); + DeriveMarikoMasterKeks(keySet); + DeriveMasterKeys(keySet); + PopulateOldMasterKeys(keySet); + + DerivePerConsoleKeys(keySet); + DerivePerFirmwareKeys(keySet); + DeriveNcaHeaderKey(keySet); + DeriveSdCardKeys(keySet); + } + + private static void DeriveKeyBlobKeys(KeySet s) + { + if (s.SecureBootKey.IsEmpty() || s.TsecKey.IsEmpty()) return; + + bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsEmpty(); + var temp = new AesKey(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobKeySources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.KeyBlobKeySources[i], temp, s.TsecKey); + Aes.DecryptEcb128(temp, s.KeyBlobKeys[i], s.SecureBootKey); + + if (!haveKeyBlobMacKeySource) continue; + + Aes.DecryptEcb128(s.KeyBlobMacKeySource, s.KeyBlobMacKeys[i], s.KeyBlobKeys[i]); + } + } + + private static void DecryptKeyBlobs(KeySet s, IProgressReport logger = null) + { + var cmac = new AesCmac(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobKeys[i].IsEmpty() || s.KeyBlobMacKeys[i].IsEmpty() || s.EncryptedKeyBlobs[i].IsEmpty()) + { + continue; + } + + Aes.CalculateCmac(cmac, s.EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), s.KeyBlobMacKeys[i]); + + if (!Utilities.SpansEqual(cmac, s.EncryptedKeyBlobs[i].Cmac)) + { + logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); + } + + Aes.DecryptCtr128(s.EncryptedKeyBlobs[i].Bytes.Slice(0x20), s.KeyBlobs[i].Bytes, s.KeyBlobKeys[i], + s.EncryptedKeyBlobs[i].Counter); + } + } + + private static void ReadKeyBlobs(KeySet s) + { + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobs[i].IsEmpty()) continue; + + s.MasterKeks[i] = s.KeyBlobs[i].MasterKek; + s.Package1Keys[i] = s.KeyBlobs[i].Package1Key; + } + } + + private static void Derive620Keys(KeySet s) + { + bool haveTsecRootKek = !s.TsecRootKek.IsEmpty(); + bool havePackage1MacKek = !s.Package1MacKek.IsEmpty(); + bool havePackage1Kek = !s.Package1Kek.IsEmpty(); + + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) + { + if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsEmpty()) + continue; + + if (haveTsecRootKek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], + s.TsecRootKeys[i - KeySet.UsedKeyBlobCount], s.TsecRootKek); + } + + if (havePackage1MacKek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1MacKeys[i], + s.Package1MacKek); + } + + if (havePackage1Kek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1Keys[i], s.Package1Kek); + } + } + } + + private static void Derive620MasterKeks(KeySet s) + { + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) + { + // Key revisions >= 8 all use the same TSEC root key + int tsecRootKeyIndex = Math.Min(i, 8) - KeySet.UsedKeyBlobCount; + if (s.TsecRootKeys[tsecRootKeyIndex].IsEmpty() || s.MasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]); + } + } + + private static void DeriveMarikoMasterKeks(KeySet s) + { + if (s.MarikoKek.IsEmpty()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MarikoMasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek); + } + } + + private static void DeriveMasterKeys(KeySet s) + { + if (s.MasterKeySource.IsEmpty()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeks[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MasterKeySource, s.MasterKeys[i], s.MasterKeks[i]); + } + } + + private static void PopulateOldMasterKeys(KeySet s) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Find the newest master key we have + int newestMasterKey = -1; + + for (int i = keyVectors.Length - 1; i >= 0; i--) + { + if (!s.MasterKeys[i].IsEmpty()) + { + newestMasterKey = i; + break; + } + } + + if (newestMasterKey == -1) + return; + + // Don't populate old master keys unless the newest master key is valid + if (!TestKeyGeneration(s, newestMasterKey)) + return; + + // Decrypt all previous master keys + for (int i = newestMasterKey; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], s.MasterKeys[i - 1], s.MasterKeys[i]); + } + } + + /// + /// Check if the master key of the specified generation is correct. + /// + /// The to test. + /// The generation to test. + /// if the key is correct. + private static bool TestKeyGeneration(KeySet s, int generation) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Decrypt the vector chain until we get Master Key 0 + AesKey key = s.MasterKeys[generation]; + + for (int i = generation; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], key, key); + } + + // Decrypt the zeros with Master Key 0 + Aes.DecryptEcb128(keyVectors[0], key, key); + + // If we don't get zeros, MasterKeys[generation] is incorrect + return key.IsEmpty(); + } + + private static ReadOnlySpan MasterKeyVectors(KeySet s) => + MemoryMarshal.Cast(s.CurrentMode == KeySet.Mode.Dev + ? MasterKeyVectorsDev + : MasterKeyVectorsProd); + + private static ReadOnlySpan MasterKeyVectorsDev => new byte[] + { + 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. + 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. + 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. + 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. + 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. + 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. + 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. + 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. + 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. + 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. + 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 // Master key 09 encrypted with Master key 0A. + }; + + private static ReadOnlySpan MasterKeyVectorsProd => new byte[] + { + 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. + 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. + 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. + 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. + 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. + 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. + 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. + 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. + 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. + 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. + 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A // Master key 09 encrypted with Master key 0A. + }; + + private static void DerivePerConsoleKeys(KeySet s) + { + // Todo: Dev and newer key generations + var kek = new AesKey(); + + // Derive the device key + if (!s.PerConsoleKeySource.IsEmpty() && !s.KeyBlobKeys[0].IsEmpty()) + { + Aes.DecryptEcb128(s.PerConsoleKeySource, s.DeviceKey, s.KeyBlobKeys[0]); + } + + // Derive device-unique save keys + for (int i = 0; i < s.DeviceUniqueSaveMacKeySources.Length; i++) + { + if (!s.DeviceUniqueSaveMacKekSource.IsEmpty() && !s.DeviceUniqueSaveMacKeySources[i].IsEmpty() && + !s.DeviceKey.IsEmpty()) + { + GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek); + } + } + + // Derive BIS keys + if (s.DeviceKey.IsEmpty() + || s.BisKekSource.IsEmpty() + || s.AesKekGenerationSource.IsEmpty() + || s.AesKeyGenerationSource.IsEmpty() + || s.RetailSpecificAesKeySource.IsEmpty()) + { + return; + } + + // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 + if (s.BisKeySources[3].IsEmpty() && !s.BisKeySources[2].IsEmpty()) + { + s.BisKeySources[3] = s.BisKeySources[2]; + } + + Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey); + if (!s.BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); + + GenerateKek(s.DeviceKey, s.BisKekSource, kek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int i = 1; i < 4; i++) + { + if (!s.BisKeySources[i].IsEmpty()) + Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek); + } + } + + private static void DerivePerFirmwareKeys(KeySet s) + { + bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsEmpty(); + bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsEmpty(); + bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsEmpty(); + bool haveTitleKekSource = !s.TitleKekSource.IsEmpty(); + bool havePackage2KeySource = !s.Package2KeySource.IsEmpty(); + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeys[i].IsEmpty()) + { + continue; + } + + if (haveKakSource0) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyApplicationSource, s.KeyAreaKeys[i][0], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveKakSource1) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyOceanSource, s.KeyAreaKeys[i][1], s.AesKekGenerationSource, + s.AesKeyGenerationSource); + } + + if (haveKakSource2) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeySystemSource, s.KeyAreaKeys[i][2], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveTitleKekSource) + { + Aes.DecryptEcb128(s.TitleKekSource, s.TitleKeks[i], s.MasterKeys[i]); + } + + if (havePackage2KeySource) + { + Aes.DecryptEcb128(s.Package2KeySource, s.Package2Keys[i], s.MasterKeys[i]); + } + } + } + + private static void DeriveNcaHeaderKey(KeySet s) + { + if (s.HeaderKekSource.IsEmpty() || s.HeaderKeySource.IsEmpty() || s.MasterKeys[0].IsEmpty()) return; + + var headerKek = new AesKey(); + + GenerateKek(s.MasterKeys[0], s.HeaderKekSource, headerKek, s.AesKekGenerationSource, + s.AesKeyGenerationSource); + Aes.DecryptEcb128(s.HeaderKeySource, s.HeaderKey, headerKek); + } + + public static void DeriveSdCardKeys(KeySet s) + { + var sdKek = new AesKey(); + var tempKey = new AesXtsKey(); + GenerateKek(s.MasterKeys[0], s.SdCardKekSource, sdKek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int k = 0; k < KeySet.SdCardKeyIdCount; k++) + { + for (int i = 0; i < 4; i++) + { + tempKey.Data64[i] = s.SdCardKeySources[k].Data64[i] ^ s.SdCardEncryptionSeed.Data64[i & 1]; + } + + tempKey.Data64[0] = s.SdCardKeySources[k].Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[1] = s.SdCardKeySources[k].Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + tempKey.Data64[2] = s.SdCardKeySources[k].Data64[2] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[3] = s.SdCardKeySources[k].Data64[3] ^ s.SdCardEncryptionSeed.Data64[1]; + + Aes.DecryptEcb128(tempKey, s.SdCardEncryptionKeys[k], sdKek); + } + + // Derive sd card save key + if (!s.SeedUniqueSaveMacKekSource.IsEmpty() && !s.SeedUniqueSaveMacKeySource.IsEmpty()) + { + var keySource = new AesKey(); + + keySource.Data64[0] = s.SeedUniqueSaveMacKeySource.Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + keySource.Data64[1] = s.SeedUniqueSaveMacKeySource.Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + + GenerateKek(s.MasterKeys[0], s.SeedUniqueSaveMacKekSource, sdKek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(keySource, s.SeedUniqueSaveMacKey, sdKek); + } + } + + private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, + ReadOnlySpan kekSeed, ReadOnlySpan keySeed) + { + var kek = new AesKey(); + var srcKek = new AesKey(); + + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); + + if (!keySeed.IsEmpty) + { + Aes.DecryptEcb128(keySeed, dest, srcKek); + } + else + { + srcKek.Data.CopyTo(dest); + } + } + } +} diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index b8011338..a984670c 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -5,7 +5,7 @@ using LibHac.Boot; using LibHac.Common.FixedArrays; using LibHac.Crypto; using LibHac.FsSrv; -using Aes = LibHac.Crypto.Aes; +using LibHac.Util; namespace LibHac.Common.Keys { @@ -20,9 +20,9 @@ namespace LibHac.Common.Keys /// /// The number of keyblobs that were used for < 6.2.0 crypto /// - private const int UsedKeyBlobCount = 6; - private const int SdCardKeyIdCount = 3; - private const int KeyRevisionCount = 0x20; + internal const int UsedKeyBlobCount = 6; + internal const int SdCardKeyIdCount = 3; + internal const int KeyRevisionCount = 0x20; private AllKeys _keys; private Mode _mode = Mode.Prod; @@ -105,75 +105,79 @@ namespace LibHac.Common.Keys // Todo: Make a separate type? Not actually an AES-XTS key, but it's still the same shape. public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; + public Span NcaHeaderSigningKeys => RsaSigningKeys.NcaHeaderSigningKeys.Items; + public Span AcidSigningKeys => RsaSigningKeys.AcidSigningKeys.Items; + public ref RsaKey Package2SigningKey => ref RsaSigningKeys.Package2SigningKey; + public ref RsaFullKey BetaNca0KeyAreaKey => ref RsaKeys.BetaNca0KeyAreaKey; + private RsaSigningKeyParameters _rsaSigningKeyParamsDev; private RsaSigningKeyParameters _rsaSigningKeyParamsProd; private RsaKeyParameters _rsaKeyParams; public RSAParameters ETicketExtKeyRsa { get; set; } - public Span NcaHeaderSigningKeys + public Span NcaHeaderSigningKeyParams { get { - ref Array2? keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; + ref Optional> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; - if (keys is null) + if (!keys.HasValue) { - keys = new Array2(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[1]); + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in NcaHeaderSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in NcaHeaderSigningKeys[1]); } return keys.Value.Items; } } - public Span AcidSigningKeys + public Span AcidSigningKeyParams { get { - ref Array2? keys = ref RsaSigningKeyParams.AcidSigningKeys; + ref Optional> keys = ref RsaSigningKeyParams.AcidSigningKeys; - if (keys is null) + if (!keys.HasValue) { - keys = new Array2(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[1]); + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in AcidSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in AcidSigningKeys[1]); } return keys.Value.Items; } } - public ref RSAParameters Package2SigningKey + public ref RSAParameters Package2SigningKeyParams { get { - ref Array1? keys = ref RsaSigningKeyParams.Package2SigningKey; + ref Optional keys = ref RsaSigningKeyParams.Package2SigningKey; - if (keys is null) + if (!keys.HasValue) { - keys = new Array1(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.Package2SigningKey); + keys.Set(new RSAParameters()); + keys.Value = CreateRsaParameters(in Package2SigningKey); } - return ref keys.Value[0]; + return ref keys.Value; } } - public ref RSAParameters BetaNca0KeyAreaKey + public ref RSAParameters BetaNca0KeyAreaKeyParams { get { - ref Array1? keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; + ref Optional keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; - if (keys is null) + if (!keys.HasValue) { - keys = new Array1(); - keys.Value[0] = CreateRsaParameters(in RsaKeys.BetaNca0KeyAreaKey); + keys.Set(CreateRsaParameters(in BetaNca0KeyAreaKey)); } - return ref keys.Value[0]; + return ref keys.Value; } } @@ -186,391 +190,19 @@ namespace LibHac.Common.Keys DeriveSdCardKeys(); } - public void SetMode(Mode mode) - { - _mode = mode; - } - - public void DeriveKeys(IProgressReport logger = null) - { - DeriveKeyBlobKeys(); - DecryptKeyBlobs(logger); - ReadKeyBlobs(); - - Derive620Keys(); - Derive620MasterKeks(); - DeriveMarikoMasterKeks(); - DeriveMasterKeys(); - PopulateOldMasterKeys(); - - DerivePerConsoleKeys(); - DerivePerFirmwareKeys(); - DeriveNcaHeaderKey(); - DeriveSdCardKeys(); - } - - private void DeriveKeyBlobKeys() - { - if (SecureBootKey.IsEmpty() || TsecKey.IsEmpty()) return; - - bool haveKeyBlobMacKeySource = !MasterKeySource.IsEmpty(); - var temp = new AesKey(); - - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobKeySources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(KeyBlobKeySources[i], temp, TsecKey); - Aes.DecryptEcb128(temp, KeyBlobKeys[i], SecureBootKey); - - if (!haveKeyBlobMacKeySource) continue; - - Aes.DecryptEcb128(KeyBlobMacKeySource, KeyBlobMacKeys[i], KeyBlobKeys[i]); - } - } - - private void DecryptKeyBlobs(IProgressReport logger = null) - { - var cmac = new AesCmac(); - - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobKeys[i].IsEmpty() || KeyBlobMacKeys[i].IsEmpty() || EncryptedKeyBlobs[i].IsEmpty()) - { - continue; - } - - Aes.CalculateCmac(cmac, EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), KeyBlobMacKeys[i]); - - if (!Utilities.SpansEqual(cmac, EncryptedKeyBlobs[i].Cmac)) - { - logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); - } - - Aes.DecryptCtr128(EncryptedKeyBlobs[i].Bytes.Slice(0x20), KeyBlobs[i].Bytes, KeyBlobKeys[i], - EncryptedKeyBlobs[i].Counter); - } - } - - private void ReadKeyBlobs() - { - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobs[i].IsEmpty()) continue; - - MasterKeks[i] = KeyBlobs[i].MasterKek; - Package1Keys[i] = KeyBlobs[i].Package1Key; - } - } - - private void Derive620Keys() - { - bool haveTsecRootKek = !TsecRootKek.IsEmpty(); - bool havePackage1MacKek = !Package1MacKek.IsEmpty(); - bool havePackage1Kek = !Package1Kek.IsEmpty(); - - for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) - { - if (TsecAuthSignatures[i - UsedKeyBlobCount].IsEmpty()) - continue; - - if (haveTsecRootKek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - TsecRootKeys[i - UsedKeyBlobCount], TsecRootKek); - } - - if (havePackage1MacKek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - Package1MacKeys[i], Package1MacKek); - } - - if (havePackage1Kek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - Package1Keys[i], Package1Kek); - } - } - } - - private void Derive620MasterKeks() - { - for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) - { - // Key revisions >= 8 all use the same TSEC root key - int tsecRootKeyIndex = Math.Min(i, 8) - UsedKeyBlobCount; - if (TsecRootKeys[tsecRootKeyIndex].IsEmpty() || MasterKekSources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(MasterKekSources[i], MasterKeks[i], TsecRootKeys[tsecRootKeyIndex]); - } - } - - private void DeriveMarikoMasterKeks() - { - if (MarikoKek.IsEmpty()) return; - - for (int i = 0; i < KeyRevisionCount; i++) - { - if (MarikoMasterKekSources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(MarikoMasterKekSources[i], MasterKeks[i], MarikoKek); - } - } - - private void DeriveMasterKeys() - { - if (MasterKeySource.IsEmpty()) return; - - for (int i = 0; i < KeyRevisionCount; i++) - { - if (MasterKeks[i].IsEmpty()) continue; - - Aes.DecryptEcb128(MasterKeySource, MasterKeys[i], MasterKeks[i]); - } - } - - private void PopulateOldMasterKeys() - { - // Find the newest master key we have - int newestMasterKey = -1; - - for (int i = MasterKeyVectors.Length - 1; i >= 0; i--) - { - if (!MasterKeys[i].IsEmpty()) - { - newestMasterKey = i; - break; - } - } - - if (newestMasterKey == -1) - return; - - // Don't populate old master keys unless the newest master key is valid - if (!TestKeyGeneration(newestMasterKey)) - return; - - // Decrypt all previous master keys - for (int i = newestMasterKey; i > 0; i--) - { - Aes.DecryptEcb128(MasterKeyVectors[i], MasterKeys[i - 1], MasterKeys[i]); - } - } + public void SetMode(Mode mode) => _mode = mode; /// - /// Check if the master key of the specified generation is correct. + /// Returns a new containing any keys that have been compiled into the library. /// - /// The generation to test. - /// if the key is correct. - private bool TestKeyGeneration(int generation) + /// The created , + public static KeySet CreateDefaultKeySet() { - // Decrypt the vector chain until we get Master Key 0 - AesKey key = MasterKeys[generation]; - - for (int i = generation; i > 0; i--) - { - Aes.DecryptEcb128(MasterKeyVectors[i], key, key); - } - - // Decrypt the zeros with Master Key 0 - Aes.DecryptEcb128(MasterKeyVectors[0], key, key); - - // If we don't get zeros, MasterKeys[generation] is incorrect - return key.IsEmpty(); + return DefaultKeySet.CreateDefaultKeySet(); } - private ReadOnlySpan MasterKeyVectors => - MemoryMarshal.Cast(_mode == Mode.Dev ? MasterKeyVectorsDev : MasterKeyVectorsProd); - - private static ReadOnlySpan MasterKeyVectorsDev => new byte[] - { - 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. - 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. - 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. - 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. - 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. - 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. - 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. - 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. - 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. - 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. - 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 // Master key 09 encrypted with Master key 0A. - }; - - private static ReadOnlySpan MasterKeyVectorsProd => new byte[] - { - 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. - 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. - 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. - 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. - 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. - 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. - 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. - 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. - 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. - 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. - 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A // Master key 09 encrypted with Master key 0A. - }; - - private void DerivePerConsoleKeys() - { - // Todo: Dev and newer key generations - var kek = new AesKey(); - - // Derive the device key - if (!PerConsoleKeySource.IsEmpty() && !KeyBlobKeys[0].IsEmpty()) - { - Aes.DecryptEcb128(PerConsoleKeySource, DeviceKey, KeyBlobKeys[0]); - } - - // Derive device-unique save keys - for (int i = 0; i < DeviceUniqueSaveMacKeySources.Length; i++) - { - if (!DeviceUniqueSaveMacKekSource.IsEmpty() && !DeviceUniqueSaveMacKeySources[i].IsEmpty() && - !DeviceKey.IsEmpty()) - { - GenerateKek(DeviceKey, DeviceUniqueSaveMacKekSource, kek, AesKekGenerationSource, null); - Aes.DecryptEcb128(DeviceUniqueSaveMacKeySources[i], DeviceUniqueSaveMacKeys[i], kek); - } - } - - // Derive BIS keys - if (DeviceKey.IsEmpty() - || BisKekSource.IsEmpty() - || AesKekGenerationSource.IsEmpty() - || AesKeyGenerationSource.IsEmpty() - || RetailSpecificAesKeySource.IsEmpty()) - { - return; - } - - // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 - if (BisKeySources[3].IsEmpty() && !BisKeySources[2].IsEmpty()) - { - BisKeySources[3] = BisKeySources[2]; - } - - Aes.DecryptEcb128(RetailSpecificAesKeySource, kek, DeviceKey); - if (!BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(BisKeySources[0], BisKeys[0], kek); - - GenerateKek(DeviceKey, BisKekSource, kek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int i = 1; i < 4; i++) - { - if (!BisKeySources[i].IsEmpty()) - Aes.DecryptEcb128(BisKeySources[i], BisKeys[i], kek); - } - } - - private void DerivePerFirmwareKeys() - { - bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty(); - bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty(); - bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty(); - bool haveTitleKekSource = !TitleKekSource.IsEmpty(); - bool havePackage2KeySource = !Package2KeySource.IsEmpty(); - - for (int i = 0; i < KeyRevisionCount; i++) - { - if (MasterKeys[i].IsEmpty()) - { - continue; - } - - if (haveKakSource0) - { - GenerateKek(MasterKeys[i], KeyAreaKeyApplicationSource, KeyAreaKeys[i][0], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource1) - { - GenerateKek(MasterKeys[i], KeyAreaKeyOceanSource, KeyAreaKeys[i][1], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource2) - { - GenerateKek(MasterKeys[i], KeyAreaKeySystemSource, KeyAreaKeys[i][2], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveTitleKekSource) - { - Aes.DecryptEcb128(TitleKekSource, TitleKeks[i], MasterKeys[i]); - } - - if (havePackage2KeySource) - { - Aes.DecryptEcb128(Package2KeySource, Package2Keys[i], MasterKeys[i]); - } - } - } - - private void DeriveNcaHeaderKey() - { - if (HeaderKekSource.IsEmpty() || HeaderKeySource.IsEmpty() || MasterKeys[0].IsEmpty()) return; - - var headerKek = new AesKey(); - - GenerateKek(MasterKeys[0], HeaderKekSource, headerKek, AesKekGenerationSource, - AesKeyGenerationSource); - Aes.DecryptEcb128(HeaderKeySource, HeaderKey, headerKek); - } - - public void DeriveSdCardKeys() - { - var sdKek = new AesKey(); - var tempKey = new AesXtsKey(); - GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int k = 0; k < SdCardKeyIdCount; k++) - { - for (int i = 0; i < 4; i++) - { - tempKey.Data64[i] = SdCardKeySources[k].Data64[i] ^ SdCardEncryptionSeed.Data64[i & 1]; - } - - tempKey.Data64[0] = SdCardKeySources[k].Data64[0] ^ SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[1] = SdCardKeySources[k].Data64[1] ^ SdCardEncryptionSeed.Data64[1]; - tempKey.Data64[2] = SdCardKeySources[k].Data64[2] ^ SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[3] = SdCardKeySources[k].Data64[3] ^ SdCardEncryptionSeed.Data64[1]; - - Aes.DecryptEcb128(tempKey, SdCardEncryptionKeys[k], sdKek); - } - - // Derive sd card save key - if (!SeedUniqueSaveMacKekSource.IsEmpty() && !SeedUniqueSaveMacKeySource.IsEmpty()) - { - var keySource = new AesKey(); - - keySource.Data64[0] = SeedUniqueSaveMacKeySource.Data64[0] ^ SdCardEncryptionSeed.Data64[0]; - keySource.Data64[1] = SeedUniqueSaveMacKeySource.Data64[1] ^ SdCardEncryptionSeed.Data64[1]; - - GenerateKek(MasterKeys[0], SeedUniqueSaveMacKekSource, sdKek, AesKekGenerationSource, null); - Aes.DecryptEcb128(keySource, SeedUniqueSaveMacKey, sdKek); - } - } - - private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, - ReadOnlySpan kekSeed, ReadOnlySpan keySeed) - { - var kek = new AesKey(); - var srcKek = new AesKey(); - - Aes.DecryptEcb128(kekSeed, kek, key); - Aes.DecryptEcb128(src, srcKek, kek); - - if (!keySeed.IsEmpty) - { - Aes.DecryptEcb128(keySeed, dest, srcKek); - } - else - { - srcKek.Data.CopyTo(dest); - } - } + public void DeriveKeys(IProgressReport logger = null) => KeyDerivation.DeriveAllKeys(this, logger); + public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); private static RSAParameters CreateRsaParameters(in RsaKey key) { @@ -598,14 +230,14 @@ namespace LibHac.Common.Keys private struct RsaSigningKeyParameters { - public Array2? NcaHeaderSigningKeys; - public Array2? AcidSigningKeys; - public Array1? Package2SigningKey; + public Optional> NcaHeaderSigningKeys; + public Optional> AcidSigningKeys; + public Optional Package2SigningKey; } private struct RsaKeyParameters { - public Array1? BetaNca0KeyAreaKey; + public Optional BetaNca0KeyAreaKey; } } diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 99257c78..67ad92fa 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -633,7 +633,7 @@ namespace LibHac.FsSystem.NcaUtils Span keyArea = Header.GetKeyArea(); var decKeyArea = new byte[0x100]; - if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKey, out _)) + if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKeyParams, out _)) { Nca0KeyArea = decKeyArea; } @@ -711,7 +711,7 @@ namespace LibHac.FsSystem.NcaUtils public Validity VerifyHeaderSignature() { - return Header.VerifySignature1(KeySet.NcaHeaderSigningKeys[0].Modulus); + return Header.VerifySignature1(KeySet.NcaHeaderSigningKeyParams[0].Modulus); } internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion) diff --git a/src/LibHac/LibHac.csproj b/src/LibHac/LibHac.csproj index e9e23fad..a38292a4 100644 --- a/src/LibHac/LibHac.csproj +++ b/src/LibHac/LibHac.csproj @@ -40,6 +40,7 @@ + diff --git a/src/LibHac/Npdm/Acid.cs b/src/LibHac/Npdm/Acid.cs index e62d31b3..be7017a6 100644 --- a/src/LibHac/Npdm/Acid.cs +++ b/src/LibHac/Npdm/Acid.cs @@ -46,7 +46,7 @@ namespace LibHac.Npdm reader.BaseStream.Position = offset + 0x100; byte[] signatureData = reader.ReadBytes(Size); SignatureValidity = - CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeys[0].Modulus); + CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeyParams[0].Modulus); } reader.BaseStream.Position = offset + 0x208; diff --git a/src/LibHac/Package2.cs b/src/LibHac/Package2.cs index 6b2eda82..e722df92 100644 --- a/src/LibHac/Package2.cs +++ b/src/LibHac/Package2.cs @@ -154,7 +154,7 @@ namespace LibHac Signature = reader.ReadBytes(0x100); byte[] sigData = reader.ReadBytes(0x100); - SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKey.Modulus); + SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKeyParams.Modulus); reader.BaseStream.Position -= 0x100; Counter = reader.ReadBytes(0x10); diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs new file mode 100644 index 00000000..c2964d8d --- /dev/null +++ b/src/LibHac/Util/Optional.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; +using LibHac.Diag; + +namespace LibHac.Util +{ + public struct Optional + { + private bool _hasValue; + private T _value; + + public bool HasValue => _hasValue; + public ref T Value + { + get + { + Assert.AssertTrue(_hasValue); + // It's beautiful + return ref MemoryMarshal.CreateSpan(ref _value, 1)[0]; + } + } + + public void Set(in T value) + { + _value = value; + _hasValue = true; + } + + public void Clear() + { + _hasValue = false; + _value = default; + } + } +} From fa79db2285fd3c6d40adc8496e1bd22ea57be186 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Wed, 7 Oct 2020 17:54:29 -0700 Subject: [PATCH 08/16] Split ExternalKeyReader --- src/LibHac/Common/Keys/DefaultKeySet.cs | 112 +++++- src/LibHac/Common/Keys/ExternalKeyReader.cs | 421 +------------------- src/LibHac/Common/Keys/ExternalKeyWriter.cs | 173 ++++++++ src/LibHac/Common/Keys/KeyInfo.cs | 166 ++++++++ src/hactoolnet/Program.cs | 10 +- 5 files changed, 457 insertions(+), 425 deletions(-) create mode 100644 src/LibHac/Common/Keys/ExternalKeyWriter.cs create mode 100644 src/LibHac/Common/Keys/KeyInfo.cs diff --git a/src/LibHac/Common/Keys/DefaultKeySet.cs b/src/LibHac/Common/Keys/DefaultKeySet.cs index e6e4d818..af270fde 100644 --- a/src/LibHac/Common/Keys/DefaultKeySet.cs +++ b/src/LibHac/Common/Keys/DefaultKeySet.cs @@ -1,10 +1,17 @@ -using System.Runtime.CompilerServices; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Type = LibHac.Common.Keys.KeyInfo.KeyType; + namespace LibHac.Common.Keys { internal static partial class DefaultKeySet { + /// + /// Creates a that contains any keys that have been embedded in the library. + /// + /// The created . public static KeySet CreateDefaultKeySet() { var keySet = new KeySet(); @@ -67,5 +74,108 @@ namespace LibHac.Common.Keys return keySet; } + + /// + /// Creates a of the of all keys that are loadable by default. + /// + /// The created list. + public static List CreateKeyList() + { + // Update this value if more keys are added + var keys = new List(70); + + // Keys with a group value of -1 are keys that will be read but not written. + // This is for compatibility since some keys had other names in the past. + + // TSEC secrets aren't public yet, so the TSEC root keys will be treated as + // root keys even though they're derived. + + keys.Add(new KeyInfo(10, Type.DeviceRoot, "secure_boot_key", (set, i) => set.SecureBootKey)); + keys.Add(new KeyInfo(11, Type.DeviceRoot, "tsec_key", (set, i) => set.TsecKey)); + keys.Add(new KeyInfo(12, Type.DeviceDrvd, "device_key", (set, i) => set.DeviceKey)); + + keys.Add(new KeyInfo(20, Type.CommonRoot, "tsec_root_kek", (set, i) => set.TsecRootKek)); + keys.Add(new KeyInfo(21, Type.CommonRoot, "package1_mac_kek", (set, i) => set.Package1MacKek)); + keys.Add(new KeyInfo(22, Type.CommonRoot, "package1_kek", (set, i) => set.Package1Kek)); + + keys.Add(new KeyInfo(30, Type.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i])); + + keys.Add(new KeyInfo(40, Type.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i])); + + keys.Add(new KeyInfo(50, Type.CommonSeed, "keyblob_mac_key_source", (set, i) => set.KeyBlobMacKeySource)); + keys.Add(new KeyInfo(51, Type.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i])); + + keys.Add(new KeyInfo(55, Type.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i])); + + keys.Add(new KeyInfo(60, Type.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i])); + + keys.Add(new KeyInfo(70, Type.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(80, Type.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes)); + + keys.Add(new KeyInfo(90, Type.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i])); + + keys.Add(new KeyInfo(100, Type.CommonRoot, "mariko_bek", (set, i) => set.MarikoBek)); + keys.Add(new KeyInfo(101, Type.CommonRoot, "mariko_kek", (set, i) => set.MarikoKek)); + + keys.Add(new KeyInfo(110, Type.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); + keys.Add(new KeyInfo(120, Type.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); + keys.Add(new KeyInfo(130, Type.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); + keys.Add(new KeyInfo(140, Type.CommonSeed, "master_key_source", (set, i) => set.MasterKeySource)); + keys.Add(new KeyInfo(150, Type.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); + + keys.Add(new KeyInfo(160, Type.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); + keys.Add(new KeyInfo(170, Type.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i])); + keys.Add(new KeyInfo(180, Type.CommonSeed, "package2_key_source", (set, i) => set.Package2KeySource)); + keys.Add(new KeyInfo(190, Type.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i])); + + keys.Add(new KeyInfo(200, Type.CommonSeed, "bis_kek_source", (set, i) => set.BisKekSource)); + keys.Add(new KeyInfo(201, Type.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i])); + + keys.Add(new KeyInfo(205, Type.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i])); + + keys.Add(new KeyInfo(210, Type.CommonSeed, "per_console_key_source", (set, i) => set.PerConsoleKeySource)); + keys.Add(new KeyInfo(211, Type.CommonSeed, "retail_specific_aes_key_source", (set, i) => set.RetailSpecificAesKeySource)); + keys.Add(new KeyInfo(212, Type.CommonSeed, "aes_kek_generation_source", (set, i) => set.AesKekGenerationSource)); + keys.Add(new KeyInfo(213, Type.CommonSeed, "aes_key_generation_source", (set, i) => set.AesKeyGenerationSource)); + keys.Add(new KeyInfo(214, Type.CommonSeed, "titlekek_source", (set, i) => set.TitleKekSource)); + + keys.Add(new KeyInfo(220, Type.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i])); + + keys.Add(new KeyInfo(230, Type.CommonSeed, "header_kek_source", (set, i) => set.HeaderKekSource)); + keys.Add(new KeyInfo(231, Type.CommonSeed, "header_key_source", (set, i) => set.HeaderKeySource)); + keys.Add(new KeyInfo(232, Type.CommonDrvd, "header_key", (set, i) => set.HeaderKey)); + + keys.Add(new KeyInfo(240, Type.CommonSeed, "key_area_key_application_source", (set, i) => set.KeyAreaKeyApplicationSource)); + keys.Add(new KeyInfo(241, Type.CommonSeed, "key_area_key_ocean_source", (set, i) => set.KeyAreaKeyOceanSource)); + keys.Add(new KeyInfo(242, Type.CommonSeed, "key_area_key_system_source", (set, i) => set.KeyAreaKeySystemSource)); + + keys.Add(new KeyInfo(250, Type.CommonSeed, "save_mac_kek_source", (set, i) => set.DeviceUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(251, Type.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i])); + keys.Add(new KeyInfo(252, Type.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i])); + keys.Add(new KeyInfo(-01, Type.CommonSeed, "save_mac_key_source", (set, i) => set.DeviceUniqueSaveMacKeySources[0])); + + keys.Add(new KeyInfo(253, Type.CommonSeed, "save_mac_sd_card_kek_source", (set, i) => set.SeedUniqueSaveMacKekSource)); + keys.Add(new KeyInfo(254, Type.CommonSeed, "save_mac_sd_card_key_source", (set, i) => set.SeedUniqueSaveMacKeySource)); + keys.Add(new KeyInfo(255, Type.DeviceDrvd, "save_mac_sd_card_key", (set, i) => set.SeedUniqueSaveMacKey)); + + keys.Add(new KeyInfo(260, Type.DeviceRoot, "sd_seed", (set, i) => set.SdCardEncryptionSeed)); + + keys.Add(new KeyInfo(261, Type.CommonSeed, "sd_card_kek_source", (set, i) => set.SdCardKekSource)); + keys.Add(new KeyInfo(262, Type.CommonSeed, "sd_card_save_key_source", (set, i) => set.SdCardKeySources[0])); + keys.Add(new KeyInfo(263, Type.CommonSeed, "sd_card_nca_key_source", (set, i) => set.SdCardKeySources[1])); + keys.Add(new KeyInfo(264, Type.CommonSeed, "sd_card_custom_storage_key_source", (set, i) => set.SdCardKeySources[2])); + + keys.Add(new KeyInfo(270, Type.CommonSeedDiff, "xci_header_key", (set, i) => set.XciHeaderKey)); + + keys.Add(new KeyInfo(280, Type.CommonRoot, "eticket_rsa_kek", (set, i) => set.ETicketRsaKek)); + keys.Add(new KeyInfo(281, Type.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek)); + + keys.Add(new KeyInfo(290, Type.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); + keys.Add(new KeyInfo(300, Type.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1])); + keys.Add(new KeyInfo(310, Type.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2])); + + return keys; + } } } diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 29f47ea6..10fe6a1d 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text; -using LibHac.Diag; using LibHac.Fs; using LibHac.Spl; @@ -12,140 +9,6 @@ namespace LibHac.Common.Keys { public static class ExternalKeyReader { - [DebuggerDisplay("{" + nameof(Name) + "}")] - public readonly struct KeyInfo - { - public readonly string Name; - public readonly KeyGetter Getter; - public readonly int Group; - public readonly KeyRangeType RangeType; - public readonly KeyType Type; - public readonly byte RangeStart; - public readonly byte RangeEnd; - - public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0); - - public delegate Span KeyGetter(KeySet keySet, int i); - - public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc) - { - Assert.AssertTrue(IsKeyTypeValid(type)); - - Name = name; - RangeType = KeyRangeType.Single; - Type = type; - RangeStart = default; - RangeEnd = default; - Group = group; - Getter = retrieveFunc; - } - - public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc) - { - Assert.AssertTrue(IsKeyTypeValid(type)); - - Name = name; - RangeType = KeyRangeType.Range; - Type = type; - RangeStart = rangeStart; - RangeEnd = rangeEnd; - Group = group; - Getter = retrieveFunc; - } - - public bool Matches(string keyName, out int keyIndex, out bool isDev) - { - keyIndex = default; - isDev = default; - - return RangeType switch - { - KeyRangeType.Single => MatchesSingle(keyName, out isDev), - KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev), - _ => false - }; - } - - private bool MatchesSingle(string keyName, out bool isDev) - { - Assert.Equal((int)KeyRangeType.Single, (int)RangeType); - - isDev = false; - - if (keyName.Length == NameLength + 4) - { - // Might be a dev key. Check if "_dev" comes after the base key name - if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) - return false; - - isDev = true; - } - else if (keyName.Length != NameLength) - { - return false; - } - - // Check if the base name matches - if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) - return false; - - return true; - } - - private bool MatchesRangedKey(string keyName, ref int keyIndex, out bool isDev) - { - Assert.Equal((int)KeyRangeType.Range, (int)RangeType); - - isDev = false; - - // Check if this is an explicit dev key - if (keyName.Length == Name.Length + 7) - { - // Check if "_dev" comes after the base key name - if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) - return false; - - isDev = true; - } - // Not a dev key. Check that the length of the key name with the trailing index matches - else if (keyName.Length != Name.Length + 3) - return false; - - // Check if the name before the "_XX" index matches - if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) - return false; - - // The name should have an underscore before the index value - if (keyName[keyName.Length - 3] != '_') - return false; - - byte index = default; - - // Try to get the index of the key name - if (!keyName.AsSpan(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) - return false; - - // Check if the index is in this key's range - if (index < RangeStart || index >= RangeEnd) - return false; - - keyIndex = index; - return true; - } - - private static bool IsKeyTypeValid(KeyType type) - { - // Make sure the type has exactly one flag set for each type - KeyType type1 = type & (KeyType.Common | KeyType.Device); - KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived); - - bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device; - bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived; - - return isValid1 && isValid2; - } - } - // Contains info from a specific key being read from a file [DebuggerDisplay("{" + nameof(Name) + "}")] private struct SpecificKeyInfo @@ -164,32 +27,6 @@ namespace LibHac.Common.Keys } } - public enum KeyRangeType : byte - { - Single, - Range - } - - [Flags] - public enum KeyType : byte - { - Common = 1 << 0, - Device = 1 << 1, - Root = 1 << 2, - Seed = 1 << 3, - Derived = 1 << 4, - - /// Specifies that a seed is different in prod and dev. - DifferentDev = 1 << 5, - - CommonRoot = Common | Root, - CommonSeed = Common | Seed, - CommonSeedDiff = Common | Seed | DifferentDev, - CommonDrvd = Common | Derived, - DeviceRoot = Device | Root, - DeviceDrvd = Device | Derived, - } - private const int TitleKeySize = 0x10; /// @@ -204,7 +41,7 @@ namespace LibHac.Common.Keys public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) { - List keyInfos = CreateKeyList(); + List keyInfos = DefaultKeySet.CreateKeyList(); if (filename != null) { @@ -263,7 +100,7 @@ namespace LibHac.Common.Keys /// The where the loaded keys will be placed. /// A containing the keys to load. /// A list of all the keys that will be loaded into the key set. - /// will create a list containing all loadable keys. + /// will create a list containing all loadable keys. /// An optional logger that key-parsing errors will be written to. public static void ReadMainKeys(KeySet keySet, TextReader keyFileReader, List keyList, IProgressReport logger = null) @@ -386,259 +223,5 @@ namespace LibHac.Common.Keys info = default; return false; } - - public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, KeyType filter, bool isDev) - { - if (keys.Count == 0) return; - - string devSuffix = isDev ? "_dev" : string.Empty; - int maxNameLength = keys.Max(x => x.NameLength); - int currentGroup = 0; - - // Todo: Better filtering - bool FilterMatches(KeyInfo keyInfo) - { - KeyType filter1 = filter & (KeyType.Common | KeyType.Device); - KeyType filter2 = filter & (KeyType.Root | KeyType.Seed | KeyType.Derived); - KeyType filter3 = filter & KeyType.DifferentDev; - - // Skip sub-filters that have no flags set - return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && - (filter2 == 0 || (filter2 & keyInfo.Type) != 0) && - filter3 == (filter3 & keyInfo.Type) || - isDev && keyInfo.Type.HasFlag(KeyType.DifferentDev); - } - - bool isFirstPrint = true; - - foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches) - .OrderBy(x => x.Group).ThenBy(x => x.Name)) - { - bool isNewGroup = false; - - if (info.Group == currentGroup + 1) - { - currentGroup = info.Group; - } - else if (info.Group > currentGroup) - { - // Don't update the current group yet because if this key is empty and the next key - // is in the same group, we need to be able to know to add a blank line before printing it. - isNewGroup = !isFirstPrint; - } - - if (info.RangeType == KeyRangeType.Single) - { - Span key = info.Getter(keySet, 0); - if (key.IsEmpty()) - continue; - - if (isNewGroup) - { - sb.AppendLine(); - } - - string keyName = $"{info.Name}{devSuffix}"; - - string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; - sb.AppendLine(line); - isFirstPrint = false; - currentGroup = info.Group; - } - else if (info.RangeType == KeyRangeType.Range) - { - bool hasPrintedKey = false; - - for (int i = info.RangeStart; i < info.RangeEnd; i++) - { - Span key = info.Getter(keySet, i); - if (key.IsEmpty()) - continue; - - if (hasPrintedKey == false) - { - if (isNewGroup) - { - sb.AppendLine(); - } - - hasPrintedKey = true; - } - - string keyName = $"{info.Name}{devSuffix}_{i:x2}"; - - string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; - sb.AppendLine(line); - isFirstPrint = false; - currentGroup = info.Group; - } - } - } - } - - public static string PrintTitleKeys(KeySet keySet) - { - var sb = new StringBuilder(); - - foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList() - .OrderBy(x => x.rightsId.ToString())) - { - string line = $"{kv.rightsId} = {kv.key}"; - sb.AppendLine(line); - } - - return sb.ToString(); - } - - public static string PrintCommonKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived, false); - return sb.ToString(); - } - - public static string PrintDeviceKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Device, false); - return sb.ToString(); - } - - public static string PrintAllKeys(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, CreateKeyList(), 0, false); - return sb.ToString(); - } - - public static string PrintAllSeeds(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Seed, false); - - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - sb.AppendLine(); - keySet.SetMode(KeySet.Mode.Dev); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Seed | KeyType.DifferentDev, true); - keySet.SetMode(KeySet.Mode.Prod); - } - return sb.ToString(); - } - - public static string PrintCommonKeysWithDev(KeySet keySet) - { - var sb = new StringBuilder(); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Seed | KeyType.Derived, false); - - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - sb.AppendLine(); - keySet.SetMode(KeySet.Mode.Dev); - PrintKeys(keySet, sb, CreateKeyList(), KeyType.Common | KeyType.Root | KeyType.Derived, true); - keySet.SetMode(KeySet.Mode.Prod); - } - - return sb.ToString(); - } - - public static List CreateKeyList() - { - // Update this value if more keys are added - var keys = new List(70); - - // Keys with a group value of -1 are keys that will be read but not written. - // This is for compatibility since some keys had other names in the past. - - // TSEC secrets aren't public yet, so the TSEC root keys will be treated as - // root keys even though they're derived. - - keys.Add(new KeyInfo(10, KeyType.DeviceRoot, "secure_boot_key", (set, i) => set.SecureBootKey)); - keys.Add(new KeyInfo(11, KeyType.DeviceRoot, "tsec_key", (set, i) => set.TsecKey)); - keys.Add(new KeyInfo(12, KeyType.DeviceDrvd, "device_key", (set, i) => set.DeviceKey)); - - keys.Add(new KeyInfo(20, KeyType.CommonRoot, "tsec_root_kek", (set, i) => set.TsecRootKek)); - keys.Add(new KeyInfo(21, KeyType.CommonRoot, "package1_mac_kek", (set, i) => set.Package1MacKek)); - keys.Add(new KeyInfo(22, KeyType.CommonRoot, "package1_kek", (set, i) => set.Package1Kek)); - - keys.Add(new KeyInfo(30, KeyType.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i])); - - keys.Add(new KeyInfo(40, KeyType.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i])); - - keys.Add(new KeyInfo(50, KeyType.CommonSeed, "keyblob_mac_key_source", (set, i) => set.KeyBlobMacKeySource)); - keys.Add(new KeyInfo(51, KeyType.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i])); - - keys.Add(new KeyInfo(55, KeyType.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i])); - - keys.Add(new KeyInfo(60, KeyType.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i])); - - keys.Add(new KeyInfo(70, KeyType.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes)); - - keys.Add(new KeyInfo(80, KeyType.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes)); - - keys.Add(new KeyInfo(90, KeyType.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i])); - - keys.Add(new KeyInfo(100, KeyType.CommonRoot, "mariko_bek", (set, i) => set.MarikoBek)); - keys.Add(new KeyInfo(101, KeyType.CommonRoot, "mariko_kek", (set, i) => set.MarikoKek)); - - keys.Add(new KeyInfo(110, KeyType.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i])); - keys.Add(new KeyInfo(120, KeyType.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i])); - keys.Add(new KeyInfo(130, KeyType.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i])); - keys.Add(new KeyInfo(140, KeyType.CommonSeed, "master_key_source", (set, i) => set.MasterKeySource)); - keys.Add(new KeyInfo(150, KeyType.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i])); - - keys.Add(new KeyInfo(160, KeyType.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i])); - keys.Add(new KeyInfo(170, KeyType.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i])); - keys.Add(new KeyInfo(180, KeyType.CommonSeed, "package2_key_source", (set, i) => set.Package2KeySource)); - keys.Add(new KeyInfo(190, KeyType.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i])); - - keys.Add(new KeyInfo(200, KeyType.CommonSeed, "bis_kek_source", (set, i) => set.BisKekSource)); - keys.Add(new KeyInfo(201, KeyType.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i])); - - keys.Add(new KeyInfo(205, KeyType.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i])); - - keys.Add(new KeyInfo(210, KeyType.CommonSeed, "per_console_key_source", (set, i) => set.PerConsoleKeySource)); - keys.Add(new KeyInfo(211, KeyType.CommonSeed, "retail_specific_aes_key_source", (set, i) => set.RetailSpecificAesKeySource)); - keys.Add(new KeyInfo(212, KeyType.CommonSeed, "aes_kek_generation_source", (set, i) => set.AesKekGenerationSource)); - keys.Add(new KeyInfo(213, KeyType.CommonSeed, "aes_key_generation_source", (set, i) => set.AesKeyGenerationSource)); - keys.Add(new KeyInfo(214, KeyType.CommonSeed, "titlekek_source", (set, i) => set.TitleKekSource)); - - keys.Add(new KeyInfo(220, KeyType.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i])); - - keys.Add(new KeyInfo(230, KeyType.CommonSeed, "header_kek_source", (set, i) => set.HeaderKekSource)); - keys.Add(new KeyInfo(231, KeyType.CommonSeed, "header_key_source", (set, i) => set.HeaderKeySource)); - keys.Add(new KeyInfo(232, KeyType.CommonDrvd, "header_key", (set, i) => set.HeaderKey)); - - keys.Add(new KeyInfo(240, KeyType.CommonSeed, "key_area_key_application_source", (set, i) => set.KeyAreaKeyApplicationSource)); - keys.Add(new KeyInfo(241, KeyType.CommonSeed, "key_area_key_ocean_source", (set, i) => set.KeyAreaKeyOceanSource)); - keys.Add(new KeyInfo(242, KeyType.CommonSeed, "key_area_key_system_source", (set, i) => set.KeyAreaKeySystemSource)); - - keys.Add(new KeyInfo(250, KeyType.CommonSeed, "save_mac_kek_source", (set, i) => set.DeviceUniqueSaveMacKekSource)); - keys.Add(new KeyInfo(251, KeyType.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i])); - keys.Add(new KeyInfo(252, KeyType.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i])); - keys.Add(new KeyInfo(-01, KeyType.CommonSeed, "save_mac_key_source", (set, i) => set.DeviceUniqueSaveMacKeySources[0])); - - keys.Add(new KeyInfo(253, KeyType.CommonSeed, "save_mac_sd_card_kek_source", (set, i) => set.SeedUniqueSaveMacKekSource)); - keys.Add(new KeyInfo(254, KeyType.CommonSeed, "save_mac_sd_card_key_source", (set, i) => set.SeedUniqueSaveMacKeySource)); - keys.Add(new KeyInfo(255, KeyType.DeviceDrvd, "save_mac_sd_card_key", (set, i) => set.SeedUniqueSaveMacKey)); - - keys.Add(new KeyInfo(260, KeyType.DeviceRoot, "sd_seed", (set, i) => set.SdCardEncryptionSeed)); - - keys.Add(new KeyInfo(261, KeyType.CommonSeed, "sd_card_kek_source", (set, i) => set.SdCardKekSource)); - keys.Add(new KeyInfo(262, KeyType.CommonSeed, "sd_card_save_key_source", (set, i) => set.SdCardKeySources[0])); - keys.Add(new KeyInfo(263, KeyType.CommonSeed, "sd_card_nca_key_source", (set, i) => set.SdCardKeySources[1])); - keys.Add(new KeyInfo(264, KeyType.CommonSeed, "sd_card_custom_storage_key_source", (set, i) => set.SdCardKeySources[2])); - - keys.Add(new KeyInfo(270, KeyType.CommonSeedDiff, "xci_header_key", (set, i) => set.XciHeaderKey)); - - keys.Add(new KeyInfo(280, KeyType.CommonRoot, "eticket_rsa_kek", (set, i) => set.ETicketRsaKek)); - keys.Add(new KeyInfo(281, KeyType.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek)); - - keys.Add(new KeyInfo(290, KeyType.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0])); - keys.Add(new KeyInfo(300, KeyType.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1])); - keys.Add(new KeyInfo(310, KeyType.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2])); - - return keys; - } } } diff --git a/src/LibHac/Common/Keys/ExternalKeyWriter.cs b/src/LibHac/Common/Keys/ExternalKeyWriter.cs new file mode 100644 index 00000000..be3b18ce --- /dev/null +++ b/src/LibHac/Common/Keys/ExternalKeyWriter.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using LibHac.Fs; +using LibHac.Spl; + +using Type = LibHac.Common.Keys.KeyInfo.KeyType; +using RangeType = LibHac.Common.Keys.KeyInfo.KeyRangeType; + +namespace LibHac.Common.Keys +{ + public static class ExternalKeyWriter + { + + public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, Type filter, bool isDev) + { + if (keys.Count == 0) return; + + string devSuffix = isDev ? "_dev" : string.Empty; + int maxNameLength = keys.Max(x => x.NameLength); + int currentGroup = 0; + + // Todo: Better filtering + bool FilterMatches(KeyInfo keyInfo) + { + Type filter1 = filter & (Type.Common | Type.Device); + Type filter2 = filter & (Type.Root | Type.Seed | Type.Derived); + Type filter3 = filter & Type.DifferentDev; + + // Skip sub-filters that have no flags set + return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) && + (filter2 == 0 || (filter2 & keyInfo.Type) != 0) && + filter3 == (filter3 & keyInfo.Type) || + isDev && keyInfo.Type.HasFlag(Type.DifferentDev); + } + + bool isFirstPrint = true; + + foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches) + .OrderBy(x => x.Group).ThenBy(x => x.Name)) + { + bool isNewGroup = false; + + if (info.Group == currentGroup + 1) + { + currentGroup = info.Group; + } + else if (info.Group > currentGroup) + { + // Don't update the current group yet because if this key is empty and the next key + // is in the same group, we need to be able to know to add a blank line before printing it. + isNewGroup = !isFirstPrint; + } + + if (info.RangeType == RangeType.Single) + { + Span key = info.Getter(keySet, 0); + if (key.IsEmpty()) + continue; + + if (isNewGroup) + { + sb.AppendLine(); + } + + string keyName = $"{info.Name}{devSuffix}"; + + string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; + sb.AppendLine(line); + isFirstPrint = false; + currentGroup = info.Group; + } + else if (info.RangeType == RangeType.Range) + { + bool hasPrintedKey = false; + + for (int i = info.RangeStart; i < info.RangeEnd; i++) + { + Span key = info.Getter(keySet, i); + if (key.IsEmpty()) + continue; + + if (hasPrintedKey == false) + { + if (isNewGroup) + { + sb.AppendLine(); + } + + hasPrintedKey = true; + } + + string keyName = $"{info.Name}{devSuffix}_{i:x2}"; + + string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}"; + sb.AppendLine(line); + isFirstPrint = false; + currentGroup = info.Group; + } + } + } + } + + public static string PrintTitleKeys(KeySet keySet) + { + var sb = new StringBuilder(); + + foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList() + .OrderBy(x => x.rightsId.ToString())) + { + string line = $"{kv.rightsId} = {kv.key}"; + sb.AppendLine(line); + } + + return sb.ToString(); + } + + public static string PrintCommonKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, + false); + return sb.ToString(); + } + + public static string PrintDeviceKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Device, false); + return sb.ToString(); + } + + public static string PrintAllKeys(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), 0, false); + return sb.ToString(); + } + + public static string PrintAllSeeds(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed, false); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed | Type.DifferentDev, true); + keySet.SetMode(KeySet.Mode.Prod); + } + return sb.ToString(); + } + + public static string PrintCommonKeysWithDev(KeySet keySet) + { + var sb = new StringBuilder(); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, + false); + + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true); + keySet.SetMode(KeySet.Mode.Prod); + } + + return sb.ToString(); + } + } +} diff --git a/src/LibHac/Common/Keys/KeyInfo.cs b/src/LibHac/Common/Keys/KeyInfo.cs new file mode 100644 index 00000000..7b984392 --- /dev/null +++ b/src/LibHac/Common/Keys/KeyInfo.cs @@ -0,0 +1,166 @@ +using System; +using System.Diagnostics; +using LibHac.Diag; + +namespace LibHac.Common.Keys +{ + [DebuggerDisplay("{" + nameof(Name) + "}")] + public readonly struct KeyInfo + { + public enum KeyRangeType : byte + { + Single, + Range + } + + [Flags] + public enum KeyType : byte + { + Common = 1 << 0, + Device = 1 << 1, + Root = 1 << 2, + Seed = 1 << 3, + Derived = 1 << 4, + + /// Specifies that a seed is different in prod and dev. + DifferentDev = 1 << 5, + + CommonRoot = Common | Root, + CommonSeed = Common | Seed, + CommonSeedDiff = Common | Seed | DifferentDev, + CommonDrvd = Common | Derived, + DeviceRoot = Device | Root, + DeviceDrvd = Device | Derived, + } + + public readonly string Name; + public readonly KeyGetter Getter; + public readonly int Group; + public readonly KeyRangeType RangeType; + public readonly KeyType Type; + public readonly byte RangeStart; + public readonly byte RangeEnd; + + public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0); + + public delegate Span KeyGetter(KeySet keySet, int i); + + public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc) + { + Assert.AssertTrue(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Single; + Type = type; + RangeStart = default; + RangeEnd = default; + Group = group; + Getter = retrieveFunc; + } + + public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc) + { + Assert.AssertTrue(IsKeyTypeValid(type)); + + Name = name; + RangeType = KeyRangeType.Range; + Type = type; + RangeStart = rangeStart; + RangeEnd = rangeEnd; + Group = group; + Getter = retrieveFunc; + } + + public bool Matches(string keyName, out int keyIndex, out bool isDev) + { + keyIndex = default; + isDev = default; + + return RangeType switch + { + KeyRangeType.Single => MatchesSingle(keyName, out isDev), + KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev), + _ => false + }; + } + + private bool MatchesSingle(string keyName, out bool isDev) + { + Assert.Equal((int)KeyRangeType.Single, (int)RangeType); + + isDev = false; + + if (keyName.Length == NameLength + 4) + { + // Might be a dev key. Check if "_dev" comes after the base key name + if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + return false; + + isDev = true; + } + else if (keyName.Length != NameLength) + { + return false; + } + + // Check if the base name matches + if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + return false; + + return true; + } + + private bool MatchesRangedKey(string keyName, ref int keyIndex, out bool isDev) + { + Assert.Equal((int)KeyRangeType.Range, (int)RangeType); + + isDev = false; + + // Check if this is an explicit dev key + if (keyName.Length == Name.Length + 7) + { + // Check if "_dev" comes after the base key name + if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + return false; + + isDev = true; + } + // Not a dev key. Check that the length of the key name with the trailing index matches + else if (keyName.Length != Name.Length + 3) + return false; + + // Check if the name before the "_XX" index matches + if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + return false; + + // The name should have an underscore before the index value + if (keyName[keyName.Length - 3] != '_') + return false; + + byte index = default; + + // Try to get the index of the key name + if (!keyName.AsSpan(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) + return false; + + // Check if the index is in this key's range + if (index < RangeStart || index >= RangeEnd) + return false; + + keyIndex = index; + return true; + } + + private static bool IsKeyTypeValid(KeyType type) + { + // Make sure the type has exactly one flag set for each type + KeyType type1 = type & (KeyType.Common | KeyType.Device); + KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived); + + bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device; + bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived; + + return isValid1 && isValid2; + } + } +} diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index f33f67bb..352231f6 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -208,7 +208,7 @@ namespace hactoolnet private static void ProcessKeygen(Context ctx) { - Console.WriteLine(ExternalKeyReader.PrintAllKeys(ctx.KeySet)); + Console.WriteLine(ExternalKeyWriter.PrintAllKeys(ctx.KeySet)); if (ctx.Options.OutDir != null) { @@ -216,14 +216,14 @@ namespace hactoolnet string dir = ctx.Options.OutDir; Directory.CreateDirectory(dir); - File.WriteAllText(Path.Combine(dir, keyFileName), ExternalKeyReader.PrintCommonKeys(ctx.KeySet)); - File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyReader.PrintDeviceKeys(ctx.KeySet)); - File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyReader.PrintTitleKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, keyFileName), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyWriter.PrintDeviceKeys(ctx.KeySet)); + File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyWriter.PrintTitleKeys(ctx.KeySet)); if (!ctx.Options.UseDevKeys) { File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), - ExternalKeyReader.PrintCommonKeysWithDev(ctx.KeySet)); + ExternalKeyWriter.PrintCommonKeysWithDev(ctx.KeySet)); } } } From a9632c8d0016b01fef0ac64b37ff1cd4d118012e Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 10 Oct 2020 17:19:40 -0700 Subject: [PATCH 09/16] Rewrite the key file parser --- build/CodeGen/IncludedKeys.txt | 4 + build/CodeGen/Stage2/KeysCodeGen.cs | 6 +- src/LibHac/Common/Keys/ExternalKeyReader.cs | 380 +++++++++++++++----- src/LibHac/Common/Keys/KeyInfo.cs | 16 +- src/LibHac/Common/Keys/KeySet.cs | 6 + src/LibHac/Common/StringUtils.cs | 6 + src/LibHac/FsSrv/ExternalKeySet.cs | 5 + 7 files changed, 328 insertions(+), 95 deletions(-) diff --git a/build/CodeGen/IncludedKeys.txt b/build/CodeGen/IncludedKeys.txt index 3441482d..94ee69b7 100644 --- a/build/CodeGen/IncludedKeys.txt +++ b/build/CodeGen/IncludedKeys.txt @@ -18,6 +18,10 @@ mariko_master_kek_source_08 = 5C24E3B8B4F700C23CFD0ACE13C3DC23 mariko_master_kek_source_09 = 8669F00987C805AEB57B4874DE62A613 mariko_master_kek_source_0a = 0E440CEDB436C03FAA1DAEBF62B10982 +mariko_master_kek_source_dev_06 = CC974C462A0CB0A6C9C0B7BE302EC368 +mariko_master_kek_source_dev_07 = 86BD1D7650DF6DFA2C7D3322ABF18218 +mariko_master_kek_source_dev_08 = A3B1E0A958A2267F40BF5BBB87330B66 +mariko_master_kek_source_dev_09 = 82729165403B9D6660D01B3D4DA570E1 mariko_master_kek_source_dev_0a = F937CF9ABD86BBA99C9E03C4FCBC3BCE master_key_source = D8A2410AC6C59001C61D6A267C513F3C diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs index f969b4a3..89bf7489 100644 --- a/build/CodeGen/Stage2/KeysCodeGen.cs +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -91,10 +91,10 @@ namespace LibHacBuild.CodeGen.Stage2 var keySet = new KeySet(); // Populate the key set with all the keys in IncludedKeys.txt - using (var reader = new StreamReader(Common.GetResource(InputMainKeyFileName))) + using (Stream keyFile = Common.GetResource(InputMainKeyFileName)) { - List list = ExternalKeyReader.CreateKeyList(); - ExternalKeyReader.ReadMainKeys(keySet, reader, list); + List list = KeySet.CreateKeyInfoList(); + ExternalKeyReader.ReadMainKeys(keySet, keyFile, list); } // Recover all the RSA key parameters and write the key to the key set diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 10fe6a1d..dff25718 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using LibHac.Fs; using LibHac.Spl; @@ -45,20 +46,20 @@ namespace LibHac.Common.Keys if (filename != null) { - using var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)); - ReadMainKeys(keySet, reader, keyInfos, logger); + using var storage = new FileStream(filename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } if (consoleKeysFilename != null) { - using var reader = new StreamReader(new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read)); - ReadMainKeys(keySet, reader, keyInfos, logger); + using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); } if (titleKeysFilename != null) { - using var reader = new StreamReader(new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read)); - ReadTitleKeys(keySet, reader, logger); + using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); + ReadTitleKeys(keySet, storage, logger); } keySet.DeriveKeys(logger); @@ -98,57 +99,65 @@ namespace LibHac.Common.Keys /// Missing keys will not be derived. /// /// The where the loaded keys will be placed. - /// A containing the keys to load. + /// A containing the keys to load. /// A list of all the keys that will be loaded into the key set. /// will create a list containing all loadable keys. /// An optional logger that key-parsing errors will be written to. - public static void ReadMainKeys(KeySet keySet, TextReader keyFileReader, List keyList, - IProgressReport logger = null) + public static void ReadMainKeys(KeySet keySet, Stream reader, List keyList, + IProgressReport logger = null) { - if (keyFileReader == null) return; + if (reader == null) return; - // Todo: Improve key parsing - string line; - while ((line = keyFileReader.ReadLine()) != null) + using var streamReader = new StreamReader(reader); + Span buffer = stackalloc char[1024]; + var ctx = new KvPairReaderContext(streamReader, buffer); + + while (true) { - string[] a = line.Split(',', '='); - if (a.Length != 2) continue; + ReaderStatus status = GetKeyValuePair(ref ctx); - string keyName = a[0].Trim(); - string valueStr = a[1].Trim(); - - if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, keyName)) + if (status == ReaderStatus.Error) { - logger?.LogMessage($"Failed to match key {keyName}"); - continue; + logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); } - - Span key; - - // Get the dev key in the key set if needed. - if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + else if (status == ReaderStatus.ReadKey) { - keySet.SetMode(KeySet.Mode.Dev); - key = info.Key.Getter(keySet, info.Index); - keySet.SetMode(KeySet.Mode.Prod); + if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, ctx.CurrentKey)) + { + logger?.LogMessage($"Failed to match key {ctx.CurrentKey.ToString()}"); + continue; + } + + Span key; + + // Get the dev key in the key set if needed. + if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + key = info.Key.Getter(keySet, info.Index); + keySet.SetMode(KeySet.Mode.Prod); + } + else + { + key = info.Key.Getter(keySet, info.Index); + } + + if (ctx.CurrentValue.Length != key.Length * 2) + { + logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentValue.Length}. Must be {key.Length * 2} hex digits."); + continue; + } + + if (!Utilities.TryToBytes(ctx.CurrentValue, key)) + { + key.Clear(); + + logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has an invalid value. Must be {key.Length * 2} hex digits."); + } } - else + else if (status == ReaderStatus.Finished) { - key = info.Key.Getter(keySet, info.Index); - } - - if (valueStr.Length != key.Length * 2) - { - logger?.LogMessage( - $"Key {keyName} had incorrect size {valueStr.Length}. Must be {key.Length * 2} hex digits."); - continue; - } - - if (!Utilities.TryToBytes(valueStr, key)) - { - key.Clear(); - - logger?.LogMessage($"Key {keyName} had an invalid value. Must be {key.Length * 2} hex digits."); + break; } } } @@ -157,59 +166,262 @@ namespace LibHac.Common.Keys /// Loads title keys from a into an existing . /// /// The where the loaded keys will be placed. - /// A containing the keys to load. + /// A containing the keys to load. /// An optional logger that key-parsing errors will be written to. - public static void ReadTitleKeys(KeySet keySet, TextReader keyFileReader, IProgressReport logger = null) + public static void ReadTitleKeys(KeySet keySet, Stream reader, IProgressReport logger = null) { - if (keyFileReader == null) return; + if (reader == null) return; - // Todo: Improve key parsing - string line; - while ((line = keyFileReader.ReadLine()) != null) + using var streamReader = new StreamReader(reader); + Span buffer = stackalloc char[1024]; + var ctx = new KvPairReaderContext(streamReader, buffer); + + keySet.ExternalKeySet.EnsureCapacity((int)reader.Length / 67); + + while (true) { - string[] splitLine; + ReaderStatus status = GetKeyValuePair(ref ctx); - // Some people use pipes as delimiters - if (line.Contains('|')) + if (status == ReaderStatus.Error) { - splitLine = line.Split('|'); + logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\""); + Debugger.Break(); } - else + else if (status == ReaderStatus.ReadKey) { - splitLine = line.Split(',', '='); + if (ctx.CurrentKey.Length != TitleKeySize * 2) + { + logger?.LogMessage($"Rights ID {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentKey.Length}. (Expected {TitleKeySize * 2})"); + continue; + } + + if (ctx.CurrentValue.Length != TitleKeySize * 2) + { + logger?.LogMessage($"Title key {ctx.CurrentValue.ToString()} has incorrect size {ctx.CurrentValue.Length}. (Expected {TitleKeySize * 2})"); + continue; + } + + var rightsId = new RightsId(); + var titleKey = new AccessKey(); + + if (!Utilities.TryToBytes(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId))) + { + logger?.LogMessage($"Invalid rights ID \"{ctx.CurrentKey.ToString()}\" in title key file"); + continue; + } + + if (!Utilities.TryToBytes(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey))) + { + logger?.LogMessage($"Invalid title key \"{ctx.CurrentValue.ToString()}\" in title key file"); + continue; + } + + keySet.ExternalKeySet.Add(rightsId, titleKey).ThrowIfFailure(); } - - if (splitLine.Length < 2) continue; - - if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) + else if (status == ReaderStatus.Finished) { - logger?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); - continue; + break; } - - if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) - { - logger?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); - continue; - } - - if (rightsId.Length != TitleKeySize) - { - logger?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); - continue; - } - - if (titleKey.Length != TitleKeySize) - { - logger?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); - continue; - } - - keySet.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); } } - private static bool TryGetKeyInfo(out SpecificKeyInfo info, List keyList, string keyName) + private ref struct KvPairReaderContext + { + public TextReader Reader; + public Span Buffer; + public Span CurrentKey; + public Span CurrentValue; + public int BufferPos; + public bool NeedFillBuffer; + + public KvPairReaderContext(TextReader reader, Span buffer) + { + Reader = reader; + Buffer = buffer; + CurrentKey = default; + CurrentValue = default; + BufferPos = buffer.Length; + NeedFillBuffer = true; + } + } + + private enum ReaderStatus + { + ReadKey, + NoKeyRead, + Finished, + Error + } + + private enum ReaderState + { + Initial, + Key, + WhiteSpace1, + Delimiter, + Value, + WhiteSpace2, + End, + } + + private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) + { + Span buffer = reader.Buffer; + + if (reader.NeedFillBuffer) + { + // Move unread text to the front of the buffer + buffer.Slice(reader.BufferPos).CopyTo(buffer); + + int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos)); + + if (charsRead == 0) + { + return ReaderStatus.Finished; + } + + buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); + + reader.NeedFillBuffer = false; + reader.BufferPos = 0; + } + + // Skip any empty lines + while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos])) + { + reader.BufferPos++; + } + + var state = ReaderState.Initial; + int keyOffset = -1; + int keyLength = -1; + int valueOffset = -1; + int valueLength = -1; + int i; + + for (i = reader.BufferPos; i < buffer.Length; i++) + { + char c = buffer[i]; + + switch (state) + { + case ReaderState.Initial when IsWhiteSpace(c): + continue; + case ReaderState.Initial when IsValidNameChar(c): + state = ReaderState.Key; + ToLower(ref buffer[i]); + keyOffset = i; + continue; + case ReaderState.Key when IsValidNameChar(c): + ToLower(ref buffer[i]); + continue; + case ReaderState.Key when IsWhiteSpace(c): + state = ReaderState.WhiteSpace1; + keyLength = i - keyOffset; + continue; + case ReaderState.Key when IsDelimiter(c): + state = ReaderState.Delimiter; + keyLength = i - keyOffset; + continue; + case ReaderState.WhiteSpace1 when IsWhiteSpace(c): + continue; + case ReaderState.WhiteSpace1 when IsDelimiter(c): + state = ReaderState.Delimiter; + continue; + case ReaderState.Delimiter when IsWhiteSpace(c): + continue; + case ReaderState.Delimiter when StringUtils.IsHexDigit((byte)c): + state = ReaderState.Value; + valueOffset = i; + continue; + case ReaderState.Value when IsEndOfLine(c): + state = ReaderState.End; + valueLength = i - valueOffset; + continue; + case ReaderState.Value when IsWhiteSpace(c): + state = ReaderState.WhiteSpace2; + valueLength = i - valueOffset; + continue; + case ReaderState.Value when StringUtils.IsHexDigit((byte)c): + continue; + case ReaderState.WhiteSpace2 when IsWhiteSpace(c): + continue; + case ReaderState.WhiteSpace2 when IsEndOfLine(c): + state = ReaderState.End; + continue; + case ReaderState.End when IsEndOfLine(c): + continue; + case ReaderState.End when !IsEndOfLine(c): + break; + } + + // We've exited the state machine for one reason or another + break; + } + + // If we successfully read both the key and value + if (state == ReaderState.End || state == ReaderState.WhiteSpace2) + { + reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); + reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength); + reader.BufferPos = i; + + return ReaderStatus.ReadKey; + } + + // We either ran out of buffer or hit an error reading the key-value pair. + // Advance to the end of the line if possible. + while (i < buffer.Length && !IsEndOfLine(buffer[i])) + { + i++; + } + + // We don't have a complete line. Return that the buffer needs to be refilled. + if (i == buffer.Length) + { + reader.NeedFillBuffer = true; + return ReaderStatus.NoKeyRead; + } + + // If we hit a line with an error, it'll be returned as "CurrentKey" in the reader context + reader.CurrentKey = buffer.Slice(reader.BufferPos, i - reader.BufferPos); + reader.BufferPos = i; + + return ReaderStatus.Error; + + static bool IsWhiteSpace(char c) + { + return c == ' ' || c == '\t'; + } + + static bool IsDelimiter(char c) + { + return c == '=' || c == ','; + } + + static bool IsEndOfLine(char c) + { + return c == '\0' || c == '\r' || c == '\n'; + } + + static void ToLower(ref char c) + { + // The only characters we need to worry about are underscores and alphanumerics + // Both lowercase and numbers have bit 5 set, so they're both treated the same + if (c != '_') + { + c |= (char)0b100000; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidNameChar(char c) + { + return (c | 0x20u) - 'a' <= 'z' - 'a' || (uint)(c - '0') <= 9 || c == '_'; + } + + private static bool TryGetKeyInfo(out SpecificKeyInfo info, List keyList, ReadOnlySpan keyName) { for (int i = 0; i < keyList.Count; i++) { diff --git a/src/LibHac/Common/Keys/KeyInfo.cs b/src/LibHac/Common/Keys/KeyInfo.cs index 7b984392..09d2287f 100644 --- a/src/LibHac/Common/Keys/KeyInfo.cs +++ b/src/LibHac/Common/Keys/KeyInfo.cs @@ -71,7 +71,7 @@ namespace LibHac.Common.Keys Getter = retrieveFunc; } - public bool Matches(string keyName, out int keyIndex, out bool isDev) + public bool Matches(ReadOnlySpan keyName, out int keyIndex, out bool isDev) { keyIndex = default; isDev = default; @@ -84,7 +84,7 @@ namespace LibHac.Common.Keys }; } - private bool MatchesSingle(string keyName, out bool isDev) + private bool MatchesSingle(ReadOnlySpan keyName, out bool isDev) { Assert.Equal((int)KeyRangeType.Single, (int)RangeType); @@ -93,7 +93,7 @@ namespace LibHac.Common.Keys if (keyName.Length == NameLength + 4) { // Might be a dev key. Check if "_dev" comes after the base key name - if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) return false; isDev = true; @@ -104,13 +104,13 @@ namespace LibHac.Common.Keys } // Check if the base name matches - if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) return false; return true; } - private bool MatchesRangedKey(string keyName, ref int keyIndex, out bool isDev) + private bool MatchesRangedKey(ReadOnlySpan keyName, ref int keyIndex, out bool isDev) { Assert.Equal((int)KeyRangeType.Range, (int)RangeType); @@ -120,7 +120,7 @@ namespace LibHac.Common.Keys if (keyName.Length == Name.Length + 7) { // Check if "_dev" comes after the base key name - if (!keyName.AsSpan(Name.Length, 4).SequenceEqual("_dev")) + if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev")) return false; isDev = true; @@ -130,7 +130,7 @@ namespace LibHac.Common.Keys return false; // Check if the name before the "_XX" index matches - if (!keyName.AsSpan(0, Name.Length).SequenceEqual(Name)) + if (!keyName.Slice(0, Name.Length).SequenceEqual(Name)) return false; // The name should have an underscore before the index value @@ -140,7 +140,7 @@ namespace LibHac.Common.Keys byte index = default; // Try to get the index of the key name - if (!keyName.AsSpan(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) + if (!keyName.Slice(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) return false; // Check if the index is in this key's range diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index a984670c..490809fb 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Security.Cryptography; using LibHac.Boot; @@ -201,6 +202,11 @@ namespace LibHac.Common.Keys return DefaultKeySet.CreateDefaultKeySet(); } + public static List CreateKeyInfoList() + { + return DefaultKeySet.CreateKeyList(); + } + public void DeriveKeys(IProgressReport logger = null) => KeyDerivation.DeriveAllKeys(this, logger); public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); diff --git a/src/LibHac/Common/StringUtils.cs b/src/LibHac/Common/StringUtils.cs index bc2173c5..4832e46f 100644 --- a/src/LibHac/Common/StringUtils.cs +++ b/src/LibHac/Common/StringUtils.cs @@ -178,5 +178,11 @@ namespace LibHac.Common { return (uint)(c - (byte)'0') <= 9; } + + public static bool IsHexDigit(byte c) + { + return (uint)(c - (byte)'0') <= 9 || + (c | 0x20u) - (byte)'a' <= 'f' - 'a'; + } } } diff --git a/src/LibHac/FsSrv/ExternalKeySet.cs b/src/LibHac/FsSrv/ExternalKeySet.cs index e5e32497..877f10c2 100644 --- a/src/LibHac/FsSrv/ExternalKeySet.cs +++ b/src/LibHac/FsSrv/ExternalKeySet.cs @@ -93,5 +93,10 @@ namespace LibHac.FsSrv ExternalKeys.TrimExcess(newCapacity); } } + + public void EnsureCapacity(int capacity) + { + ExternalKeys.EnsureCapacity(capacity); + } } } From b5dabe78f5635b9bc17df255f3edb83b940547ae Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 10 Oct 2020 20:17:07 -0700 Subject: [PATCH 10/16] Replace hex string converter and move StringUtils --- .../Core/DeliveryCacheFileMetaAccessor.cs | 1 + .../Service/DeliveryCacheStorageService.cs | 2 +- src/LibHac/Bcat/Digest.cs | 1 + src/LibHac/Bcat/DirectoryName.cs | 1 + src/LibHac/Bcat/FileName.cs | 1 + src/LibHac/Boot/KeyBlob.cs | 1 + src/LibHac/Boot/Package1.cs | 1 + src/LibHac/Common/Buffer.cs | 1 + src/LibHac/Common/Id128.cs | 1 + src/LibHac/Common/Key128.cs | 1 + src/LibHac/Common/Keys/ExternalKeyReader.cs | 9 +- src/LibHac/Common/Keys/ExternalKeyWriter.cs | 1 + src/LibHac/Common/Keys/KeyInfo.cs | 3 +- src/LibHac/Common/PathBuilder.cs | 1 + src/LibHac/Common/U8Span.cs | 1 + src/LibHac/Common/U8SpanMutable.cs | 1 + src/LibHac/Common/U8String.cs | 1 + src/LibHac/Common/U8StringBuilder.cs | 1 + src/LibHac/Common/U8StringMutable.cs | 1 + src/LibHac/Crypto/KeyTypes.cs | 1 + src/LibHac/Fs/FileSystemClient.cs | 1 + src/LibHac/Fs/InMemoryFileSystem.cs | 1 + src/LibHac/Fs/RightsId.cs | 1 + src/LibHac/Fs/Shim/Bis.cs | 1 + src/LibHac/Fs/Shim/ContentStorage.cs | 1 + src/LibHac/Fs/Shim/Host.cs | 1 + src/LibHac/FsSrv/FileSystemProxy.cs | 1 + src/LibHac/FsSrv/FileSystemProxyCore.cs | 1 + src/LibHac/FsSrv/PathNormalizer.cs | 1 + src/LibHac/FsSrv/Sf/FspPath.cs | 1 + src/LibHac/FsSystem/AesXtsDirectory.cs | 3 +- src/LibHac/FsSystem/ConcatenationDirectory.cs | 5 +- .../FsSystem/ConcatenationFileSystem.cs | 1 + .../FsSystem/DirectorySaveDataFileSystem.cs | 1 + src/LibHac/FsSystem/DirectoryUtils.cs | 1 + src/LibHac/FsSystem/FileSystemExtensions.cs | 1 + src/LibHac/FsSystem/FsPath.cs | 1 + src/LibHac/FsSystem/LayeredFileSystem.cs | 1 + src/LibHac/FsSystem/LocalDirectory.cs | 4 +- src/LibHac/FsSystem/NcaUtils/NcaHeader.cs | 1 + src/LibHac/FsSystem/PartitionDirectory.cs | 2 +- .../FsSystem/PartitionFileSystemCore.cs | 1 + .../FsSystem/PartitionFileSystemMetaCore.cs | 1 + src/LibHac/FsSystem/PathTools.cs | 1 + .../RomFs/HierarchicalRomFileTable.cs | 13 +- src/LibHac/FsSystem/RomFs/RomFsDirectory.cs | 2 +- .../Save/HierarchicalSaveFileTable.cs | 5 +- src/LibHac/FsSystem/Save/SaveDataDirectory.cs | 2 +- src/LibHac/FsSystem/Save/SaveFsList.cs | 2 +- src/LibHac/FsSystem/SubdirectoryFileSystem.cs | 1 + src/LibHac/FsSystem/Utility.cs | 1 + src/LibHac/Kvdb/BoundedString.cs | 1 + src/LibHac/ResultNameResolver.cs | 1 + src/LibHac/Sm/ServiceName.cs | 1 + src/LibHac/SwitchFs.cs | 1 + src/LibHac/Util/Impl/HexConverter.cs | 233 ++++++++++++++++++ src/LibHac/{Common => Util}/StringUtils.cs | 79 +++++- src/LibHac/Utilities.cs | 162 ------------ src/hactoolnet/ProcessNax0.cs | 3 +- src/hactoolnet/ProcessPfs.cs | 1 + src/hactoolnet/Program.cs | 1 + tests/LibHac.Tests/AesCmac.cs | 1 + tests/LibHac.Tests/AesXts.cs | 1 + tests/LibHac.Tests/CryptoTests/RspReader.cs | 1 + .../IFileSystemTests.IDirectory.cs | 1 + .../LibHac.Tests/Fs/LayeredFileSystemTests.cs | 1 + tests/LibHac.Tests/Fs/PathToolTests.cs | 1 + tests/LibHac.Tests/PathToolsTests.cs | 1 + 68 files changed, 394 insertions(+), 187 deletions(-) create mode 100644 src/LibHac/Util/Impl/HexConverter.cs rename src/LibHac/{Common => Util}/StringUtils.cs (66%) diff --git a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs index da3af499..dc0c6819 100644 --- a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaAccessor.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.Bcat.Detail.Service.Core { diff --git a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs b/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs index 27699454..65aee83f 100644 --- a/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs +++ b/src/LibHac/Bcat/Detail/Service/DeliveryCacheStorageService.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using LibHac.Bcat.Detail.Ipc; using LibHac.Bcat.Detail.Service.Core; -using LibHac.Common; +using LibHac.Util; namespace LibHac.Bcat.Detail.Service { diff --git a/src/LibHac/Bcat/Digest.cs b/src/LibHac/Bcat/Digest.cs index cec171c9..a73e26d9 100644 --- a/src/LibHac/Bcat/Digest.cs +++ b/src/LibHac/Bcat/Digest.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Bcat { diff --git a/src/LibHac/Bcat/DirectoryName.cs b/src/LibHac/Bcat/DirectoryName.cs index 009f276e..46ded68b 100644 --- a/src/LibHac/Bcat/DirectoryName.cs +++ b/src/LibHac/Bcat/DirectoryName.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Bcat { diff --git a/src/LibHac/Bcat/FileName.cs b/src/LibHac/Bcat/FileName.cs index 6911a7eb..7a5b66c5 100644 --- a/src/LibHac/Bcat/FileName.cs +++ b/src/LibHac/Bcat/FileName.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Bcat { diff --git a/src/LibHac/Boot/KeyBlob.cs b/src/LibHac/Boot/KeyBlob.cs index 8ad397fe..fc006a2c 100644 --- a/src/LibHac/Boot/KeyBlob.cs +++ b/src/LibHac/Boot/KeyBlob.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Crypto; +using LibHac.Util; namespace LibHac.Boot { diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs index 7b39fd7b..0e93c426 100644 --- a/src/LibHac/Boot/Package1.cs +++ b/src/LibHac/Boot/Package1.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common.Keys; using LibHac.Diag; +using LibHac.Util; namespace LibHac.Boot { diff --git a/src/LibHac/Common/Buffer.cs b/src/LibHac/Common/Buffer.cs index 071fdc36..81ff725b 100644 --- a/src/LibHac/Common/Buffer.cs +++ b/src/LibHac/Common/Buffer.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/Id128.cs b/src/LibHac/Common/Id128.cs index 9bb2eeec..3473131f 100644 --- a/src/LibHac/Common/Id128.cs +++ b/src/LibHac/Common/Id128.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/Key128.cs b/src/LibHac/Common/Key128.cs index 10123b8d..dc211a22 100644 --- a/src/LibHac/Common/Key128.cs +++ b/src/LibHac/Common/Key128.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index dff25718..de44f7a8 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -5,6 +5,7 @@ using System.IO; using System.Runtime.CompilerServices; using LibHac.Fs; using LibHac.Spl; +using LibHac.Util; namespace LibHac.Common.Keys { @@ -148,7 +149,7 @@ namespace LibHac.Common.Keys continue; } - if (!Utilities.TryToBytes(ctx.CurrentValue, key)) + if (!StringUtils.TryFromHexString(ctx.CurrentValue, key)) { key.Clear(); @@ -204,13 +205,13 @@ namespace LibHac.Common.Keys var rightsId = new RightsId(); var titleKey = new AccessKey(); - if (!Utilities.TryToBytes(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId))) + if (!StringUtils.TryFromHexString(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId))) { logger?.LogMessage($"Invalid rights ID \"{ctx.CurrentKey.ToString()}\" in title key file"); continue; } - if (!Utilities.TryToBytes(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey))) + if (!StringUtils.TryFromHexString(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey))) { logger?.LogMessage($"Invalid title key \"{ctx.CurrentValue.ToString()}\" in title key file"); continue; @@ -261,7 +262,7 @@ namespace LibHac.Common.Keys Delimiter, Value, WhiteSpace2, - End, + End } private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) diff --git a/src/LibHac/Common/Keys/ExternalKeyWriter.cs b/src/LibHac/Common/Keys/ExternalKeyWriter.cs index be3b18ce..3faa290d 100644 --- a/src/LibHac/Common/Keys/ExternalKeyWriter.cs +++ b/src/LibHac/Common/Keys/ExternalKeyWriter.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using LibHac.Fs; using LibHac.Spl; +using LibHac.Util; using Type = LibHac.Common.Keys.KeyInfo.KeyType; using RangeType = LibHac.Common.Keys.KeyInfo.KeyRangeType; diff --git a/src/LibHac/Common/Keys/KeyInfo.cs b/src/LibHac/Common/Keys/KeyInfo.cs index 09d2287f..ba27dc56 100644 --- a/src/LibHac/Common/Keys/KeyInfo.cs +++ b/src/LibHac/Common/Keys/KeyInfo.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using LibHac.Diag; +using LibHac.Util; namespace LibHac.Common.Keys { @@ -140,7 +141,7 @@ namespace LibHac.Common.Keys byte index = default; // Try to get the index of the key name - if (!keyName.Slice(keyName.Length - 2, 2).TryToBytes(SpanHelpers.AsSpan(ref index))) + if (!StringUtils.TryFromHexString(keyName.Slice(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index))) return false; // Check if the index is in this key's range diff --git a/src/LibHac/Common/PathBuilder.cs b/src/LibHac/Common/PathBuilder.cs index afdd78e7..35998732 100644 --- a/src/LibHac/Common/PathBuilder.cs +++ b/src/LibHac/Common/PathBuilder.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/U8Span.cs b/src/LibHac/Common/U8Span.cs index 25daf063..c9b68c83 100644 --- a/src/LibHac/Common/U8Span.cs +++ b/src/LibHac/Common/U8Span.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/U8SpanMutable.cs b/src/LibHac/Common/U8SpanMutable.cs index 7d782ff2..0cd7ea46 100644 --- a/src/LibHac/Common/U8SpanMutable.cs +++ b/src/LibHac/Common/U8SpanMutable.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/U8String.cs b/src/LibHac/Common/U8String.cs index 56d60eaf..af81643c 100644 --- a/src/LibHac/Common/U8String.cs +++ b/src/LibHac/Common/U8String.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/U8StringBuilder.cs b/src/LibHac/Common/U8StringBuilder.cs index 088e46bc..1b07bc48 100644 --- a/src/LibHac/Common/U8StringBuilder.cs +++ b/src/LibHac/Common/U8StringBuilder.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Buffers.Text; using System.Diagnostics; using System.Runtime.CompilerServices; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Common/U8StringMutable.cs b/src/LibHac/Common/U8StringMutable.cs index c808014f..b041aeea 100644 --- a/src/LibHac/Common/U8StringMutable.cs +++ b/src/LibHac/Common/U8StringMutable.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using LibHac.Util; namespace LibHac.Common { diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs index ff076f65..17c2d396 100644 --- a/src/LibHac/Crypto/KeyTypes.cs +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Crypto { diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 0d42e86f..2de35327 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -8,6 +8,7 @@ using LibHac.Fs.Fsa; using LibHac.FsSrv; using LibHac.FsSrv.Sf; using LibHac.FsSystem; +using LibHac.Util; namespace LibHac.Fs { diff --git a/src/LibHac/Fs/InMemoryFileSystem.cs b/src/LibHac/Fs/InMemoryFileSystem.cs index f6513919..7fdb791e 100644 --- a/src/LibHac/Fs/InMemoryFileSystem.cs +++ b/src/LibHac/Fs/InMemoryFileSystem.cs @@ -4,6 +4,7 @@ using System.IO; using LibHac.Common; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Util; namespace LibHac.Fs { diff --git a/src/LibHac/Fs/RightsId.cs b/src/LibHac/Fs/RightsId.cs index 120eec89..ba5216af 100644 --- a/src/LibHac/Fs/RightsId.cs +++ b/src/LibHac/Fs/RightsId.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Fs { diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index 44230811..3cae5f21 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -4,6 +4,7 @@ using LibHac.Common; using LibHac.Fs.Fsa; using LibHac.FsSrv; using LibHac.FsSystem; +using LibHac.Util; using static LibHac.Fs.CommonMountNames; namespace LibHac.Fs.Shim diff --git a/src/LibHac/Fs/Shim/ContentStorage.cs b/src/LibHac/Fs/Shim/ContentStorage.cs index 0d70994b..7283cc77 100644 --- a/src/LibHac/Fs/Shim/ContentStorage.cs +++ b/src/LibHac/Fs/Shim/ContentStorage.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs.Fsa; using LibHac.FsSrv; +using LibHac.Util; namespace LibHac.Fs.Shim { diff --git a/src/LibHac/Fs/Shim/Host.cs b/src/LibHac/Fs/Shim/Host.cs index 3b31a8c1..e75080df 100644 --- a/src/LibHac/Fs/Shim/Host.cs +++ b/src/LibHac/Fs/Shim/Host.cs @@ -5,6 +5,7 @@ using LibHac.Common; using LibHac.Fs.Fsa; using LibHac.FsSrv; using LibHac.FsSystem; +using LibHac.Util; using static LibHac.Fs.CommonMountNames; namespace LibHac.Fs.Shim diff --git a/src/LibHac/FsSrv/FileSystemProxy.cs b/src/LibHac/FsSrv/FileSystemProxy.cs index 2c2adb1a..75d266be 100644 --- a/src/LibHac/FsSrv/FileSystemProxy.cs +++ b/src/LibHac/FsSrv/FileSystemProxy.cs @@ -10,6 +10,7 @@ using LibHac.FsSystem; using LibHac.Kvdb; using LibHac.Ncm; using LibHac.Spl; +using LibHac.Util; namespace LibHac.FsSrv { diff --git a/src/LibHac/FsSrv/FileSystemProxyCore.cs b/src/LibHac/FsSrv/FileSystemProxyCore.cs index e677bde6..a216178b 100644 --- a/src/LibHac/FsSrv/FileSystemProxyCore.cs +++ b/src/LibHac/FsSrv/FileSystemProxyCore.cs @@ -9,6 +9,7 @@ using LibHac.FsSystem; using LibHac.FsSrv.Creators; using LibHac.FsSystem.NcaUtils; using LibHac.Spl; +using LibHac.Util; using RightsId = LibHac.Fs.RightsId; namespace LibHac.FsSrv diff --git a/src/LibHac/FsSrv/PathNormalizer.cs b/src/LibHac/FsSrv/PathNormalizer.cs index 43ad4983..8e9f50a9 100644 --- a/src/LibHac/FsSrv/PathNormalizer.cs +++ b/src/LibHac/FsSrv/PathNormalizer.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Util; namespace LibHac.FsSrv { diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs index 49b3e20b..fe314ada 100644 --- a/src/LibHac/FsSrv/Sf/FspPath.cs +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSrv.Sf { diff --git a/src/LibHac/FsSystem/AesXtsDirectory.cs b/src/LibHac/FsSystem/AesXtsDirectory.cs index aa881d48..afd88c3d 100644 --- a/src/LibHac/FsSystem/AesXtsDirectory.cs +++ b/src/LibHac/FsSystem/AesXtsDirectory.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { @@ -38,7 +39,7 @@ namespace LibHac.FsSystem } else { - string entryName = Utilities.GetUtf8StringNullTerminated(entry.Name); + string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name); entry.Size = GetAesXtsFileSize(PathTools.Combine(Path.ToString(), entryName).ToU8Span()); } } diff --git a/src/LibHac/FsSystem/ConcatenationDirectory.cs b/src/LibHac/FsSystem/ConcatenationDirectory.cs index 4f4cdd18..e8883b8e 100644 --- a/src/LibHac/FsSystem/ConcatenationDirectory.cs +++ b/src/LibHac/FsSystem/ConcatenationDirectory.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { @@ -63,7 +64,7 @@ namespace LibHac.FsSystem if (!Mode.HasFlag(OpenDirectoryMode.NoFileSize)) { - string entryName = Utilities.GetUtf8StringNullTerminated(entry.Name); + string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name); string entryFullPath = PathTools.Combine(_path.ToString(), entryName); rc = ParentFileSystem.GetConcatenationFileSize(out long fileSize, entryFullPath.ToU8Span()); @@ -122,7 +123,7 @@ namespace LibHac.FsSystem } else { - string name = Utilities.GetUtf8StringNullTerminated(entry.Name); + string name = StringUtils.NullTerminatedUtf8ToString(entry.Name); var fullPath = PathTools.Combine(_path.ToString(), name).ToU8Span(); return ParentFileSystem.IsConcatenationFile(fullPath); diff --git a/src/LibHac/FsSystem/ConcatenationFileSystem.cs b/src/LibHac/FsSystem/ConcatenationFileSystem.cs index 5a254faa..e8294e29 100644 --- a/src/LibHac/FsSystem/ConcatenationFileSystem.cs +++ b/src/LibHac/FsSystem/ConcatenationFileSystem.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index 2ee1ddfa..10b5f9e7 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/DirectoryUtils.cs b/src/LibHac/FsSystem/DirectoryUtils.cs index 2f26bafe..95ccb02b 100644 --- a/src/LibHac/FsSystem/DirectoryUtils.cs +++ b/src/LibHac/FsSystem/DirectoryUtils.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/FileSystemExtensions.cs b/src/LibHac/FsSystem/FileSystemExtensions.cs index 849056b5..33be725c 100644 --- a/src/LibHac/FsSystem/FileSystemExtensions.cs +++ b/src/LibHac/FsSystem/FileSystemExtensions.cs @@ -5,6 +5,7 @@ using System.IO; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/FsPath.cs b/src/LibHac/FsSystem/FsPath.cs index e11c8c84..ab311210 100644 --- a/src/LibHac/FsSystem/FsPath.cs +++ b/src/LibHac/FsSystem/FsPath.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/LayeredFileSystem.cs b/src/LibHac/FsSystem/LayeredFileSystem.cs index ccb5916d..d536ab17 100644 --- a/src/LibHac/FsSystem/LayeredFileSystem.cs +++ b/src/LibHac/FsSystem/LayeredFileSystem.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/LocalDirectory.cs b/src/LibHac/FsSystem/LocalDirectory.cs index 6d2dd864..9afe2bd3 100644 --- a/src/LibHac/FsSystem/LocalDirectory.cs +++ b/src/LibHac/FsSystem/LocalDirectory.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { @@ -35,7 +35,7 @@ namespace LibHac.FsSystem if (!CanReturnEntry(isDir, Mode)) continue; - ReadOnlySpan name = Utilities.GetUtf8Bytes(localEntry.Name); + ReadOnlySpan name = StringUtils.StringToUtf8(localEntry.Name); DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File; long length = isDir ? 0 : ((FileInfo)localEntry).Length; diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index 60945f30..9c45b79a 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -7,6 +7,7 @@ using LibHac.Common.Keys; using LibHac.Crypto; using LibHac.Diag; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem.NcaUtils { diff --git a/src/LibHac/FsSystem/PartitionDirectory.cs b/src/LibHac/FsSystem/PartitionDirectory.cs index 03550613..65522627 100644 --- a/src/LibHac/FsSystem/PartitionDirectory.cs +++ b/src/LibHac/FsSystem/PartitionDirectory.cs @@ -1,9 +1,9 @@ using System; using System.IO; using System.Text; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/PartitionFileSystemCore.cs b/src/LibHac/FsSystem/PartitionFileSystemCore.cs index 01f03440..9ee82613 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemCore.cs @@ -5,6 +5,7 @@ using LibHac.Crypto; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem.Detail; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs index a4f6b913..514382a9 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem.Detail; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/PathTools.cs b/src/LibHac/FsSystem/PathTools.cs index 79bbf1ea..d930fea8 100644 --- a/src/LibHac/FsSystem/PathTools.cs +++ b/src/LibHac/FsSystem/PathTools.cs @@ -4,6 +4,7 @@ using System.IO.Enumeration; using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs b/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs index afefa22e..361ed3ef 100644 --- a/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs +++ b/src/LibHac/FsSystem/RomFs/HierarchicalRomFileTable.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem.RomFs { @@ -83,7 +84,7 @@ namespace LibHac.FsSystem.RomFs public bool TryOpenFile(string path, out T fileInfo) { - FindPathRecursive(Utilities.GetUtf8Bytes(path), out RomEntryKey key); + FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); if (FileTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -116,7 +117,7 @@ namespace LibHac.FsSystem.RomFs /// otherwise, . public bool TryOpenDirectory(string path, out FindPosition position) { - FindPathRecursive(Utilities.GetUtf8Bytes(path), out RomEntryKey key); + FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key); if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -169,7 +170,7 @@ namespace LibHac.FsSystem.RomFs position.NextFile = entry.NextSibling; info = entry.Info; - name = Utilities.GetUtf8String(nameBytes); + name = StringUtils.Utf8ToString(nameBytes); return true; } @@ -193,7 +194,7 @@ namespace LibHac.FsSystem.RomFs ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span nameBytes); position.NextDirectory = entry.NextSibling; - name = Utilities.GetUtf8String(nameBytes); + name = StringUtils.Utf8ToString(nameBytes); return true; } @@ -207,7 +208,7 @@ namespace LibHac.FsSystem.RomFs public void AddFile(string path, ref T fileInfo) { path = PathTools.Normalize(path); - ReadOnlySpan pathBytes = Utilities.GetUtf8Bytes(path); + ReadOnlySpan pathBytes = StringUtils.StringToUtf8(path); if (path == "/") throw new ArgumentException("Path cannot be empty"); @@ -223,7 +224,7 @@ namespace LibHac.FsSystem.RomFs { path = PathTools.Normalize(path); - CreateDirectoryRecursive(Utilities.GetUtf8Bytes(path)); + CreateDirectoryRecursive(StringUtils.StringToUtf8(path)); } /// diff --git a/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs b/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs index 6f2aaae8..635d5047 100644 --- a/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs +++ b/src/LibHac/FsSystem/RomFs/RomFsDirectory.cs @@ -1,8 +1,8 @@ using System; using System.Text; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem.RomFs { diff --git a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs index 75375bf7..31a4764d 100644 --- a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs +++ b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs @@ -3,6 +3,7 @@ using System.IO; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem.Save { @@ -59,7 +60,7 @@ namespace LibHac.FsSystem.Save position.NextFile = entry.NextSibling; info = entry.Value; - name = Utilities.GetUtf8StringNullTerminated(nameBytes); + name = StringUtils.NullTerminatedUtf8ToString(nameBytes); return true; } @@ -85,7 +86,7 @@ namespace LibHac.FsSystem.Save position.NextDirectory = entry.NextSibling; - name = Utilities.GetUtf8StringNullTerminated(nameBytes); + name = StringUtils.NullTerminatedUtf8ToString(nameBytes); return true; } diff --git a/src/LibHac/FsSystem/Save/SaveDataDirectory.cs b/src/LibHac/FsSystem/Save/SaveDataDirectory.cs index 81e6ec6f..074b657e 100644 --- a/src/LibHac/FsSystem/Save/SaveDataDirectory.cs +++ b/src/LibHac/FsSystem/Save/SaveDataDirectory.cs @@ -1,8 +1,8 @@ using System; using System.Text; -using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem.Save { diff --git a/src/LibHac/FsSystem/Save/SaveFsList.cs b/src/LibHac/FsSystem/Save/SaveFsList.cs index add73f18..8af53db0 100644 --- a/src/LibHac/FsSystem/Save/SaveFsList.cs +++ b/src/LibHac/FsSystem/Save/SaveFsList.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using LibHac.Common; using LibHac.Fs; +using LibHac.Util; namespace LibHac.FsSystem.Save { diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index 8eb4c861..7d117d92 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs index a38efa5c..6da63cc5 100644 --- a/src/LibHac/FsSystem/Utility.cs +++ b/src/LibHac/FsSystem/Utility.cs @@ -5,6 +5,7 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; namespace LibHac.FsSystem { diff --git a/src/LibHac/Kvdb/BoundedString.cs b/src/LibHac/Kvdb/BoundedString.cs index a94ca2ce..0a687966 100644 --- a/src/LibHac/Kvdb/BoundedString.cs +++ b/src/LibHac/Kvdb/BoundedString.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac.Kvdb { diff --git a/src/LibHac/ResultNameResolver.cs b/src/LibHac/ResultNameResolver.cs index 2a685223..2aee430e 100644 --- a/src/LibHac/ResultNameResolver.cs +++ b/src/LibHac/ResultNameResolver.cs @@ -4,6 +4,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Util; namespace LibHac { diff --git a/src/LibHac/Sm/ServiceName.cs b/src/LibHac/Sm/ServiceName.cs index 78653184..3304d4ff 100644 --- a/src/LibHac/Sm/ServiceName.cs +++ b/src/LibHac/Sm/ServiceName.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using LibHac.Common; +using LibHac.Util; namespace LibHac.Sm { diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index ed52dda7..71572bab 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -12,6 +12,7 @@ using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.Save; using LibHac.Ncm; using LibHac.Ns; +using LibHac.Util; namespace LibHac { diff --git a/src/LibHac/Util/Impl/HexConverter.cs b/src/LibHac/Util/Impl/HexConverter.cs new file mode 100644 index 00000000..7e0caa0c --- /dev/null +++ b/src/LibHac/Util/Impl/HexConverter.cs @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace LibHac.Util.Impl +{ + internal static class HexConverter + { + public enum Casing : uint + { + // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. + Upper = 0, + + // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. + // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) + // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, + // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) + // don't have the 0x20 bit set, so ORing them maps to + // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. + Lower = 0x2020U, + } + + // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], + // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then + // subtract this integer from a constant minuend as shown below. + // + // [ 1000 1001 1000 1001 ] + // - [ 0000 HHHH 0000 LLLL ] + // ========================= + // [ *YYY **** *ZZZ **** ] + // + // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. + // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. + // (We don't care about the value of asterisked bits.) + // + // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). + // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). + // => hex := nibble + 55. + // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. + // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. + + // The commented out code below is code that directly implements the logic described above. + + // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); + // uint difference = 0x8989U - packedOriginalValues; + // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values + // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; + + // The code below is equivalent to the commented out code above but has been tweaked + // to allow codegen to make some extra optimizations. + + // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. + // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. + + // Finally, write to the output buffer starting with the *highest* index so that codegen can + // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) + + // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is + // writing to a span of known length (or the caller has already checked the bounds of the + // furthest access). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToBytesBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (byte)packedResult; + buffer[startingIndex] = (byte)(packedResult >> 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (char)(packedResult & 0xFF); + buffer[startingIndex] = (char)(packedResult >> 8); + } + + public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Casing casing = Casing.Upper) + { + Debug.Assert(chars.Length >= bytes.Length * 2); + + for (int pos = 0; pos < bytes.Length; ++pos) + { + ToCharsBuffer(bytes[pos], chars, pos * 2, casing); + } + } + + public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = Casing.Upper) + { + fixed (byte* bytesPtr = bytes) + { + // Todo: Make lambda static in C# 9 + return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) => + { + var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length); + EncodeToUtf16(ros, chars, args.casing); + }); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharUpper(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('A' - ('9' + 1)); + } + + return (char)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharLower(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('a' - ('9' + 1)); + } + + return (char)value; + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) + { + return TryDecodeFromUtf16(chars, bytes, out _); + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes, out int charsProcessed) + { + Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); + Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); + + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. + if ((byteLo | byteHi) == 0xFF) + break; + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + i++; + + charsProcessed = i; + return (byteLo | byteHi) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromChar(int c) + { + return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromUpperChar(int c) + { + return c > 71 ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromLowerChar(int c) + { + if ((uint)(c - '0') <= '9' - '0') + return c - '0'; + + if ((uint)(c - 'a') <= 'f' - 'a') + return c - 'a' + 10; + + return 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexChar(int c) + { + return FromChar(c) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexUpperChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexLowerChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); + } + + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + public static ReadOnlySpan CharToHexLookup => new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + }; + } +} \ No newline at end of file diff --git a/src/LibHac/Common/StringUtils.cs b/src/LibHac/Util/StringUtils.cs similarity index 66% rename from src/LibHac/Common/StringUtils.cs rename to src/LibHac/Util/StringUtils.cs index 4832e46f..b20e2b62 100644 --- a/src/LibHac/Common/StringUtils.cs +++ b/src/LibHac/Util/StringUtils.cs @@ -1,7 +1,8 @@ using System; using System.Text; +using LibHac.Util.Impl; -namespace LibHac.Common +namespace LibHac.Util { public static class StringUtils { @@ -159,11 +160,23 @@ namespace LibHac.Common return iDest; } + public static ReadOnlySpan StringToUtf8(string value) + { + return Encoding.UTF8.GetBytes(value).AsSpan(); + } + public static string Utf8ToString(ReadOnlySpan value) { return Encoding.UTF8.GetString(value); } + public static string NullTerminatedUtf8ToString(ReadOnlySpan value) + { + int length = GetLength(value); + + return Encoding.UTF8.GetString(value.Slice(0, length)); + } + public static string Utf8ZToString(ReadOnlySpan value) { return Utf8ToString(value.Slice(0, GetLength(value))); @@ -184,5 +197,69 @@ namespace LibHac.Common return (uint)(c - (byte)'0') <= 9 || (c | 0x20u) - (byte)'a' <= 'f' - 'a'; } + + public static bool TryFromHexString(ReadOnlySpan chars, Span outputBytes) + { + if ((uint)chars.Length % 2 != 0) + return false; + + uint bytesLength = (uint)chars.Length / 2; + + if ((uint)outputBytes.Length >= bytesLength) + { + Span bytes = outputBytes.Slice(0, (int)bytesLength); + return HexConverter.TryDecodeFromUtf16(chars, bytes); + } + + return false; + } + + public static byte[] ToBytes(this string input) + { + return FromHexString(input); + } + + public static byte[] FromHexString(string input) + { + if ((uint)input.Length % 2 != 0) + throw new FormatException("Hex input must be a multiple of 2."); + + var result = new byte[input.Length >> 1]; + + if (!HexConverter.TryDecodeFromUtf16(input, result)) + { + throw new FormatException("Hex input contains invalid characters."); + } + + return result; + } + + public static void FromHexString(ReadOnlySpan chars, Span outputBytes) + { + if ((uint)chars.Length % 2 != 0) + throw new FormatException("Hex input must be a multiple of 2."); + + uint bytesLength = (uint)chars.Length / 2; + + if ((uint)outputBytes.Length >= bytesLength) + { + Span bytes = outputBytes.Slice(0, (int)bytesLength); + + if (!HexConverter.TryDecodeFromUtf16(chars, bytes)) + { + throw new FormatException("Hex input contains invalid characters."); + } + } + + throw new ArgumentException("Buffer is not large enough to fit the input hex string.", nameof(outputBytes)); + } + + public static string ToHexString(this byte[] bytes) => ToHexString((ReadOnlySpan)bytes); + public static string ToHexString(this Span bytes) => ToHexString((ReadOnlySpan)bytes); + + public static string ToHexString(this ReadOnlySpan bytes) + { + return HexConverter.ToString(bytes); + } } } diff --git a/src/LibHac/Utilities.cs b/src/LibHac/Utilities.cs index 1b6a5a78..ad8f0061 100644 --- a/src/LibHac/Utilities.cs +++ b/src/LibHac/Utilities.cs @@ -67,27 +67,6 @@ namespace LibHac return a1.SequenceEqual(a2); } - public static ReadOnlySpan GetUtf8Bytes(string value) - { - return Encoding.UTF8.GetBytes(value).AsSpan(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetUtf8String(ReadOnlySpan value) - { - return Encoding.UTF8.GetString(value); - } - - public static string GetUtf8StringNullTerminated(ReadOnlySpan value) - { - int i; - for (i = 0; i < value.Length && value[i] != 0; i++) { } - - value = value.Slice(0, i); - - return Encoding.UTF8.GetString(value); - } - public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan)array).IsEmpty(); public static bool IsEmpty(this Span span) => ((ReadOnlySpan)span).IsEmpty(); @@ -234,147 +213,6 @@ namespace LibHac return Encoding.UTF8.GetString(reader.ReadBytes(size), 0, size); } - private static bool TryHexToInt(char c, out int value) - { - switch (c) - { - case '0': - value = 0; break; - case '1': - value = 1; break; - case '2': - value = 2; break; - case '3': - value = 3; break; - case '4': - value = 4; break; - case '5': - value = 5; break; - case '6': - value = 6; break; - case '7': - value = 7; break; - case '8': - value = 8; break; - case '9': - value = 9; break; - case 'a': - case 'A': - value = 10; break; - case 'b': - case 'B': - value = 11; break; - case 'c': - case 'C': - value = 12; break; - case 'd': - case 'D': - value = 13; break; - case 'e': - case 'E': - value = 14; break; - case 'f': - case 'F': - value = 15; break; - default: - value = 0; - return false; - } - - return true; - } - - private static readonly byte[,] ByteLookup = { - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, - {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0} - }; - - public static byte[] ToBytes(this string input) - { - var result = new byte[(input.Length + 1) >> 1]; - int lastcell = result.Length - 1; - int lastchar = input.Length - 1; - for (int i = 0; i < input.Length; i++) - { - if (!TryHexToInt(input[lastchar - i], out int hexInt)) - { - throw new FormatException($"Unrecognized hex char {input[lastchar - i]}"); - } - - result[lastcell - (i >> 1)] |= ByteLookup[i & 1, hexInt]; - } - return result; - } - - public static bool TryToBytes(this string input, out byte[] bytes) - { - var result = new byte[(input.Length + 1) >> 1]; - int lastcell = result.Length - 1; - int lastchar = input.Length - 1; - for (int i = 0; i < input.Length; i++) - { - if (!TryHexToInt(input[lastchar - i], out int hexInt)) - { - bytes = null; - return false; - } - - result[lastcell - (i >> 1)] |= ByteLookup[i & 1, hexInt]; - } - bytes = result; - return true; - } - - public static bool TryToBytes(this ReadOnlySpan input, Span output) - { - if (input.Length != output.Length * 2) - return false; - - int lastcell = output.Length - 1; - int lastchar = input.Length - 1; - for (int i = 0; i < input.Length; i++) - { - if (!TryHexToInt(input[lastchar - i], out int hexInt)) - { - return false; - } - - output[lastcell - (i >> 1)] |= ByteLookup[i & 1, hexInt]; - } - - return true; - } - - private static readonly uint[] Lookup32 = CreateLookup32(); - - private static uint[] CreateLookup32() - { - var result = new uint[256]; - for (int i = 0; i < 256; i++) - { - string s = i.ToString("X2"); - result[i] = s[0] + ((uint)s[1] << 16); - } - return result; - } - - public static string ToHexString(this byte[] bytes) => ToHexString(bytes.AsSpan()); - - public static string ToHexString(this Span bytes) => ToHexString((ReadOnlySpan)bytes); - - public static string ToHexString(this ReadOnlySpan bytes) - { - uint[] lookup32 = Lookup32; - var result = new char[bytes.Length * 2]; - for (int i = 0; i < bytes.Length; i++) - { - uint val = lookup32[bytes[i]]; - result[2 * i] = (char)val; - result[2 * i + 1] = (char)(val >> 16); - } - return new string(result); - } - public static long MediaToReal(long media) { return MediaSize * media; diff --git a/src/hactoolnet/ProcessNax0.cs b/src/hactoolnet/ProcessNax0.cs index 5507fe85..22c2cb52 100644 --- a/src/hactoolnet/ProcessNax0.cs +++ b/src/hactoolnet/ProcessNax0.cs @@ -6,6 +6,7 @@ using LibHac.Common; using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Util; using static hactoolnet.Print; namespace hactoolnet @@ -67,7 +68,7 @@ namespace hactoolnet AesXtsFileHeader header = xtsFile.Header; uint magic = header.Magic; - PrintItem(sb, colLen, " Magic:", Utilities.GetUtf8String(SpanHelpers.AsReadOnlyByteSpan(in magic))); + PrintItem(sb, colLen, " Magic:", StringUtils.Utf8ToString(SpanHelpers.AsReadOnlyByteSpan(in magic))); PrintItem(sb, colLen, " Content Type:", GetContentType(contentType)); PrintItem(sb, colLen, " Content Size:", $"{header.Size:x12}"); PrintItem(sb, colLen, " Header HMAC:", header.Signature); diff --git a/src/hactoolnet/ProcessPfs.cs b/src/hactoolnet/ProcessPfs.cs index b8334432..f2f474ad 100644 --- a/src/hactoolnet/ProcessPfs.cs +++ b/src/hactoolnet/ProcessPfs.cs @@ -4,6 +4,7 @@ using System.Text; using LibHac; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Util; using static hactoolnet.Print; namespace hactoolnet diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 352231f6..2b0dfe6c 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -4,6 +4,7 @@ using System.Text; using LibHac; using LibHac.Common.Keys; using LibHac.Fs; +using LibHac.Util; namespace hactoolnet { diff --git a/tests/LibHac.Tests/AesCmac.cs b/tests/LibHac.Tests/AesCmac.cs index 52e2dd2d..a04c13e5 100644 --- a/tests/LibHac.Tests/AesCmac.cs +++ b/tests/LibHac.Tests/AesCmac.cs @@ -1,5 +1,6 @@ using System; using LibHac.Crypto; +using LibHac.Util; using Xunit; namespace LibHac.Tests diff --git a/tests/LibHac.Tests/AesXts.cs b/tests/LibHac.Tests/AesXts.cs index df7a9629..ef36380d 100644 --- a/tests/LibHac.Tests/AesXts.cs +++ b/tests/LibHac.Tests/AesXts.cs @@ -1,5 +1,6 @@ using System.Linq; using LibHac.FsSystem; +using LibHac.Util; using Xunit; namespace LibHac.Tests diff --git a/tests/LibHac.Tests/CryptoTests/RspReader.cs b/tests/LibHac.Tests/CryptoTests/RspReader.cs index 47601eda..7ec0819d 100644 --- a/tests/LibHac.Tests/CryptoTests/RspReader.cs +++ b/tests/LibHac.Tests/CryptoTests/RspReader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using LibHac.Util; using Xunit; namespace LibHac.Tests.CryptoTests diff --git a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs index e5463fb2..0d3c9bc5 100644 --- a/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs +++ b/tests/LibHac.Tests/Fs/IFileSystemTestBase/IFileSystemTests.IDirectory.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Util; using Xunit; namespace LibHac.Tests.Fs.IFileSystemTestBase diff --git a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs index 0c9e3d22..7544595b 100644 --- a/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/LayeredFileSystemTests.cs @@ -3,6 +3,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Util; using Xunit; namespace LibHac.Tests.Fs diff --git a/tests/LibHac.Tests/Fs/PathToolTests.cs b/tests/LibHac.Tests/Fs/PathToolTests.cs index 24818b11..1b5b0b8e 100644 --- a/tests/LibHac.Tests/Fs/PathToolTests.cs +++ b/tests/LibHac.Tests/Fs/PathToolTests.cs @@ -1,5 +1,6 @@ using LibHac.Common; using LibHac.Fs; +using LibHac.Util; using Xunit; namespace LibHac.Tests.Fs diff --git a/tests/LibHac.Tests/PathToolsTests.cs b/tests/LibHac.Tests/PathToolsTests.cs index 16d59c18..d6284594 100644 --- a/tests/LibHac.Tests/PathToolsTests.cs +++ b/tests/LibHac.Tests/PathToolsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LibHac.Common; using LibHac.FsSystem; +using LibHac.Util; using Xunit; namespace LibHac.Tests From 8f011387a05587aa1578fbe132cc585122221b30 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 11 Oct 2020 01:26:43 -0700 Subject: [PATCH 11/16] Improve key parser performance a bit --- src/LibHac/Common/Keys/ExternalKeyReader.cs | 26 ++++++++++++++++----- src/LibHac/Util/StringUtils.cs | 3 +-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index de44f7a8..8c530728 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -177,6 +177,8 @@ namespace LibHac.Common.Keys Span buffer = stackalloc char[1024]; var ctx = new KvPairReaderContext(streamReader, buffer); + // Estimate the number of keys by assuming each line is about 69 bytes. + // Subtract 2 from that so we estimate slightly high. keySet.ExternalKeySet.EnsureCapacity((int)reader.Length / 67); while (true) @@ -310,11 +312,18 @@ namespace LibHac.Common.Keys continue; case ReaderState.Initial when IsValidNameChar(c): state = ReaderState.Key; - ToLower(ref buffer[i]); keyOffset = i; - continue; - case ReaderState.Key when IsValidNameChar(c): - ToLower(ref buffer[i]); + + // Skip the next few rounds through the state machine since we know we should be + // encountering a string of name characters + do + { + ToLower(ref buffer[i]); + i++; + } while (i < buffer.Length && IsValidNameChar(buffer[i])); + + // Decrement so we can process this character the next round through the state machine + i--; continue; case ReaderState.Key when IsWhiteSpace(c): state = ReaderState.WhiteSpace1; @@ -334,6 +343,13 @@ namespace LibHac.Common.Keys case ReaderState.Delimiter when StringUtils.IsHexDigit((byte)c): state = ReaderState.Value; valueOffset = i; + + do + { + i++; + } while (i < buffer.Length && !IsEndOfLine(buffer[i]) && !IsWhiteSpace(buffer[i])); + + i--; continue; case ReaderState.Value when IsEndOfLine(c): state = ReaderState.End; @@ -343,8 +359,6 @@ namespace LibHac.Common.Keys state = ReaderState.WhiteSpace2; valueLength = i - valueOffset; continue; - case ReaderState.Value when StringUtils.IsHexDigit((byte)c): - continue; case ReaderState.WhiteSpace2 when IsWhiteSpace(c): continue; case ReaderState.WhiteSpace2 when IsEndOfLine(c): diff --git a/src/LibHac/Util/StringUtils.cs b/src/LibHac/Util/StringUtils.cs index b20e2b62..aea3333d 100644 --- a/src/LibHac/Util/StringUtils.cs +++ b/src/LibHac/Util/StringUtils.cs @@ -194,8 +194,7 @@ namespace LibHac.Util public static bool IsHexDigit(byte c) { - return (uint)(c - (byte)'0') <= 9 || - (c | 0x20u) - (byte)'a' <= 'f' - 'a'; + return HexConverter.CharToHexLookup[c] != 0xFF; } public static bool TryFromHexString(ReadOnlySpan chars, Span outputBytes) From c39895080be078e5330c713b9c6fb055b4c7d14a Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 11 Oct 2020 22:24:53 -0700 Subject: [PATCH 12/16] hactoolnet: Always read both prod and dev key sets --- src/LibHac/Common/Keys/ExternalKeyReader.cs | 62 +++++++++++++++++- src/LibHac/Common/Keys/ExternalKeyWriter.cs | 15 ++--- src/hactoolnet/Program.cs | 72 ++++++++++++++------- 3 files changed, 117 insertions(+), 32 deletions(-) diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 8c530728..5267726d 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -65,6 +65,61 @@ namespace LibHac.Common.Keys keySet.DeriveKeys(logger); + // Dev keys can be read from prod key files, so derive any missing keys if necessary. + if (keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + keySet.DeriveKeys(logger); + keySet.SetMode(KeySet.Mode.Prod); + } + } + + /// + /// Loads keys from key files into an existing . Missing keys will be + /// derived from existing keys if possible. Any file names will be skipped. + /// + /// The where the loaded keys will be placed. + /// The path of the file containing common prod keys. Can be . + /// The path of the file containing common dev keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + public static void ReadKeyFile(KeySet keySet, string prodKeysFilename = null, string devKeysFilename = null, + string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) + { + KeySet.Mode originalMode = keySet.CurrentMode; + List keyInfos = DefaultKeySet.CreateKeyList(); + + if (prodKeysFilename != null) + { + keySet.SetMode(KeySet.Mode.Prod); + using var storage = new FileStream(prodKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); + } + + if (devKeysFilename != null) + { + keySet.SetMode(KeySet.Mode.Dev); + using var storage = new FileStream(devKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); + } + + keySet.SetMode(originalMode); + + if (consoleKeysFilename != null) + { + using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read); + ReadMainKeys(keySet, storage, keyInfos, logger); + } + + if (titleKeysFilename != null) + { + using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read); + ReadTitleKeys(keySet, storage, logger); + } + + keySet.DeriveKeys(logger); + // Dev keys can read from prod key files, so derive any missing keys if necessary. if (keySet.CurrentMode == KeySet.Mode.Prod) { @@ -283,7 +338,12 @@ namespace LibHac.Common.Keys return ReaderStatus.Finished; } - buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); + // ReadBlock will only read less than the buffer size if there's nothing left to read + if (charsRead != reader.BufferPos) + { + buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); + reader.Buffer = buffer; + } reader.NeedFillBuffer = false; reader.BufferPos = 0; diff --git a/src/LibHac/Common/Keys/ExternalKeyWriter.cs b/src/LibHac/Common/Keys/ExternalKeyWriter.cs index 3faa290d..c284935e 100644 --- a/src/LibHac/Common/Keys/ExternalKeyWriter.cs +++ b/src/LibHac/Common/Keys/ExternalKeyWriter.cs @@ -13,7 +13,6 @@ namespace LibHac.Common.Keys { public static class ExternalKeyWriter { - public static void PrintKeys(KeySet keySet, StringBuilder sb, List keys, Type filter, bool isDev) { if (keys.Count == 0) return; @@ -156,18 +155,18 @@ namespace LibHac.Common.Keys public static string PrintCommonKeysWithDev(KeySet keySet) { + KeySet.Mode originalMode = keySet.CurrentMode; var sb = new StringBuilder(); + + keySet.SetMode(KeySet.Mode.Prod); PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived, false); - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - sb.AppendLine(); - keySet.SetMode(KeySet.Mode.Dev); - PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true); - keySet.SetMode(KeySet.Mode.Prod); - } + sb.AppendLine(); + keySet.SetMode(KeySet.Mode.Dev); + PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true); + keySet.SetMode(originalMode); return sb.ToString(); } } diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 2b0dfe6c..1a3dcbd2 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -85,7 +85,7 @@ namespace hactoolnet Result.SetLogger(resultLogger); } - OpenKeyset(ctx); + OpenKeySet(ctx); if (ctx.Options.RunCustom) { @@ -168,43 +168,65 @@ namespace hactoolnet } } - private static void OpenKeyset(Context ctx) + private static void OpenKeySet(Context ctx) { - string keyFileName = ctx.Options.UseDevKeys ? "dev.keys" : "prod.keys"; - #if CORERT_NO_REFLECTION string home = HomeFolder.GetFolderPath(Environment.SpecialFolder.UserProfile); #else string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); #endif - string homeKeyFile = Path.Combine(home, ".switch", keyFileName); string homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys"); string homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys"); - string keyFile = ctx.Options.Keyfile; + + string prodKeyFile = Path.Combine(home, ".switch", "prod.keys"); + string devKeyFile = Path.Combine(home, ".switch", "dev.keys"); string titleKeyFile = ctx.Options.TitleKeyFile; string consoleKeyFile = ctx.Options.ConsoleKeyFile; - if (keyFile == null && File.Exists(homeKeyFile)) - { - keyFile = homeKeyFile; - } + // Check if the files from the command line exist + if (titleKeyFile != null && !File.Exists(titleKeyFile)) + titleKeyFile = null; + + if (consoleKeyFile != null && !File.Exists(consoleKeyFile)) + consoleKeyFile = null; + + if (!File.Exists(prodKeyFile)) + prodKeyFile = null; + + if (!File.Exists(devKeyFile)) + devKeyFile = null; + + // Check the home directory if no existing key files were specified + if (consoleKeyFile == null && File.Exists(homeConsoleKeyFile)) + consoleKeyFile = homeConsoleKeyFile; if (titleKeyFile == null && File.Exists(homeTitleKeyFile)) - { titleKeyFile = homeTitleKeyFile; - } - if (consoleKeyFile == null && File.Exists(homeConsoleKeyFile)) + var keySet = KeySet.CreateDefaultKeySet(); + + // If the user specifies a key file then only load that file into the mode they specified, + // otherwise load both prod.keys and dev.keys. + // Todo: Should we add a way that both dev-only key files and mixed prod/dev key files + // can both be loaded when specifying a key file in dev mode? + if (ctx.Options.Keyfile != null && File.Exists(ctx.Options.Keyfile)) { - consoleKeyFile = homeConsoleKeyFile; + keySet.SetMode(ctx.Options.KeyMode); + ExternalKeyReader.ReadKeyFile(keySet, ctx.Options.Keyfile, titleKeyFile, consoleKeyFile, ctx.Logger); + } + else + { + ExternalKeyReader.ReadKeyFile(keySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, ctx.Logger); } - ctx.KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile, ctx.Logger, ctx.Options.KeyMode); + keySet.SetMode(ctx.Options.KeyMode); if (ctx.Options.SdSeed != null) { - ctx.KeySet.SetSdSeed(ctx.Options.SdSeed.ToBytes()); + keySet.SetSdSeed(ctx.Options.SdSeed.ToBytes()); } + + ctx.KeySet = keySet; } private static void ProcessKeygen(Context ctx) @@ -213,19 +235,23 @@ namespace hactoolnet if (ctx.Options.OutDir != null) { - string keyFileName = ctx.Options.UseDevKeys ? "dev.keys" : "prod.keys"; + KeySet.Mode originalMode = ctx.KeySet.CurrentMode; + string dir = ctx.Options.OutDir; Directory.CreateDirectory(dir); - File.WriteAllText(Path.Combine(dir, keyFileName), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); + ctx.KeySet.SetMode(KeySet.Mode.Prod); + File.WriteAllText(Path.Combine(dir, "prod.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); + + ctx.KeySet.SetMode(KeySet.Mode.Dev); + File.WriteAllText(Path.Combine(dir, "dev.keys"), ExternalKeyWriter.PrintCommonKeys(ctx.KeySet)); + + ctx.KeySet.SetMode(originalMode); File.WriteAllText(Path.Combine(dir, "console.keys"), ExternalKeyWriter.PrintDeviceKeys(ctx.KeySet)); File.WriteAllText(Path.Combine(dir, "title.keys"), ExternalKeyWriter.PrintTitleKeys(ctx.KeySet)); - if (!ctx.Options.UseDevKeys) - { - File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), - ExternalKeyWriter.PrintCommonKeysWithDev(ctx.KeySet)); - } + File.WriteAllText(Path.Combine(dir, "prod+dev.keys"), + ExternalKeyWriter.PrintCommonKeysWithDev(ctx.KeySet)); } } From b6499a6c12e3f99c06ff3ced01bdb3c351f436d1 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 11 Oct 2020 23:47:26 -0700 Subject: [PATCH 13/16] Rename IsEmpty to IsZeros Renames the IsEmpty() functions that check if an array is all zeros. This helps avoid confusion because Span has an IsEmpty property that returns true if the span's length is 0. The change actually revealed a tiny bug in KeyDerivation where the property was accidentally used instead of the function. --- build/CodeGen/Stage2/KeysCodeGen.cs | 2 +- src/LibHac/Boot/KeyBlob.cs | 4 +- src/LibHac/Boot/Package1.cs | 2 +- src/LibHac/Common/Keys/ExternalKeyWriter.cs | 4 +- src/LibHac/Common/Keys/KeyDerivation.cs | 72 +++++++++---------- src/LibHac/Crypto/KeyTypes.cs | 8 +-- .../FsSystem/IntegrityVerificationStorage.cs | 6 +- src/LibHac/FsSystem/NcaUtils/Nca.cs | 8 +-- src/LibHac/FsSystem/NcaUtils/NcaHeader.cs | 2 +- .../FsSystem/Save/SaveDataFileSystem.cs | 2 +- src/LibHac/Utilities.cs | 6 +- src/LibHac/XciHeader.cs | 2 +- 12 files changed, 59 insertions(+), 59 deletions(-) diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs index 89bf7489..4077ba5a 100644 --- a/build/CodeGen/Stage2/KeysCodeGen.cs +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -61,7 +61,7 @@ namespace LibHacBuild.CodeGen.Stage2 sb.AppendSpacerLine(); sb.Append($"private static ReadOnlySpan {name} => new byte[]"); - if (data.IsEmpty()) + if (data.IsZeros()) { sb.AppendLine(" { };"); return; diff --git a/src/LibHac/Boot/KeyBlob.cs b/src/LibHac/Boot/KeyBlob.cs index fc006a2c..ca1d485a 100644 --- a/src/LibHac/Boot/KeyBlob.cs +++ b/src/LibHac/Boot/KeyBlob.cs @@ -30,7 +30,7 @@ namespace LibHac.Boot public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() + public readonly bool IsZeros() { ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); @@ -70,7 +70,7 @@ namespace LibHac.Boot public readonly ReadOnlySpan ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() + public readonly bool IsZeros() { ReadOnlySpan ulongSpan = MemoryMarshal.Cast(ReadOnlyBytes); diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs index 0e93c426..93660663 100644 --- a/src/LibHac/Boot/Package1.cs +++ b/src/LibHac/Boot/Package1.cs @@ -354,7 +354,7 @@ namespace LibHac.Boot // MarikoOemHeader must be read first private bool IsMarikoImpl() { - return MarikoOemHeader.AesMac.IsEmpty() && MarikoOemHeader.Reserved.IsEmpty(); + return MarikoOemHeader.AesMac.IsZeros() && MarikoOemHeader.Reserved.IsZeros(); } /// diff --git a/src/LibHac/Common/Keys/ExternalKeyWriter.cs b/src/LibHac/Common/Keys/ExternalKeyWriter.cs index c284935e..0351846c 100644 --- a/src/LibHac/Common/Keys/ExternalKeyWriter.cs +++ b/src/LibHac/Common/Keys/ExternalKeyWriter.cs @@ -56,7 +56,7 @@ namespace LibHac.Common.Keys if (info.RangeType == RangeType.Single) { Span key = info.Getter(keySet, 0); - if (key.IsEmpty()) + if (key.IsZeros()) continue; if (isNewGroup) @@ -78,7 +78,7 @@ namespace LibHac.Common.Keys for (int i = info.RangeStart; i < info.RangeEnd; i++) { Span key = info.Getter(keySet, i); - if (key.IsEmpty()) + if (key.IsZeros()) continue; if (hasPrintedKey == false) diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs index 13bb44d8..d01b8cdc 100644 --- a/src/LibHac/Common/Keys/KeyDerivation.cs +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -26,14 +26,14 @@ namespace LibHac.Common.Keys private static void DeriveKeyBlobKeys(KeySet s) { - if (s.SecureBootKey.IsEmpty() || s.TsecKey.IsEmpty()) return; + if (s.SecureBootKey.IsZeros() || s.TsecKey.IsZeros()) return; - bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsEmpty(); + bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsZeros(); var temp = new AesKey(); for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - if (s.KeyBlobKeySources[i].IsEmpty()) continue; + if (s.KeyBlobKeySources[i].IsZeros()) continue; Aes.DecryptEcb128(s.KeyBlobKeySources[i], temp, s.TsecKey); Aes.DecryptEcb128(temp, s.KeyBlobKeys[i], s.SecureBootKey); @@ -50,7 +50,7 @@ namespace LibHac.Common.Keys for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - if (s.KeyBlobKeys[i].IsEmpty() || s.KeyBlobMacKeys[i].IsEmpty() || s.EncryptedKeyBlobs[i].IsEmpty()) + if (s.KeyBlobKeys[i].IsZeros() || s.KeyBlobMacKeys[i].IsZeros() || s.EncryptedKeyBlobs[i].IsZeros()) { continue; } @@ -71,7 +71,7 @@ namespace LibHac.Common.Keys { for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) { - if (s.KeyBlobs[i].IsEmpty()) continue; + if (s.KeyBlobs[i].IsZeros()) continue; s.MasterKeks[i] = s.KeyBlobs[i].MasterKek; s.Package1Keys[i] = s.KeyBlobs[i].Package1Key; @@ -80,13 +80,13 @@ namespace LibHac.Common.Keys private static void Derive620Keys(KeySet s) { - bool haveTsecRootKek = !s.TsecRootKek.IsEmpty(); - bool havePackage1MacKek = !s.Package1MacKek.IsEmpty(); - bool havePackage1Kek = !s.Package1Kek.IsEmpty(); + bool haveTsecRootKek = !s.TsecRootKek.IsZeros(); + bool havePackage1MacKek = !s.Package1MacKek.IsZeros(); + bool havePackage1Kek = !s.Package1Kek.IsZeros(); for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) { - if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsEmpty()) + if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsZeros()) continue; if (haveTsecRootKek) @@ -114,7 +114,7 @@ namespace LibHac.Common.Keys { // Key revisions >= 8 all use the same TSEC root key int tsecRootKeyIndex = Math.Min(i, 8) - KeySet.UsedKeyBlobCount; - if (s.TsecRootKeys[tsecRootKeyIndex].IsEmpty() || s.MasterKekSources[i].IsEmpty()) continue; + if (s.TsecRootKeys[tsecRootKeyIndex].IsZeros() || s.MasterKekSources[i].IsZeros()) continue; Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]); } @@ -122,11 +122,11 @@ namespace LibHac.Common.Keys private static void DeriveMarikoMasterKeks(KeySet s) { - if (s.MarikoKek.IsEmpty()) return; + if (s.MarikoKek.IsZeros()) return; for (int i = 0; i < KeySet.KeyRevisionCount; i++) { - if (s.MarikoMasterKekSources[i].IsEmpty()) continue; + if (s.MarikoMasterKekSources[i].IsZeros()) continue; Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek); } @@ -134,11 +134,11 @@ namespace LibHac.Common.Keys private static void DeriveMasterKeys(KeySet s) { - if (s.MasterKeySource.IsEmpty()) return; + if (s.MasterKeySource.IsZeros()) return; for (int i = 0; i < KeySet.KeyRevisionCount; i++) { - if (s.MasterKeks[i].IsEmpty()) continue; + if (s.MasterKeks[i].IsZeros()) continue; Aes.DecryptEcb128(s.MasterKeySource, s.MasterKeys[i], s.MasterKeks[i]); } @@ -153,7 +153,7 @@ namespace LibHac.Common.Keys for (int i = keyVectors.Length - 1; i >= 0; i--) { - if (!s.MasterKeys[i].IsEmpty()) + if (!s.MasterKeys[i].IsZeros()) { newestMasterKey = i; break; @@ -196,7 +196,7 @@ namespace LibHac.Common.Keys Aes.DecryptEcb128(keyVectors[0], key, key); // If we don't get zeros, MasterKeys[generation] is incorrect - return key.IsEmpty(); + return key.IsZeros(); } private static ReadOnlySpan MasterKeyVectors(KeySet s) => @@ -240,7 +240,7 @@ namespace LibHac.Common.Keys var kek = new AesKey(); // Derive the device key - if (!s.PerConsoleKeySource.IsEmpty() && !s.KeyBlobKeys[0].IsEmpty()) + if (!s.PerConsoleKeySource.IsZeros() && !s.KeyBlobKeys[0].IsZeros()) { Aes.DecryptEcb128(s.PerConsoleKeySource, s.DeviceKey, s.KeyBlobKeys[0]); } @@ -248,8 +248,8 @@ namespace LibHac.Common.Keys // Derive device-unique save keys for (int i = 0; i < s.DeviceUniqueSaveMacKeySources.Length; i++) { - if (!s.DeviceUniqueSaveMacKekSource.IsEmpty() && !s.DeviceUniqueSaveMacKeySources[i].IsEmpty() && - !s.DeviceKey.IsEmpty()) + if (!s.DeviceUniqueSaveMacKekSource.IsZeros() && !s.DeviceUniqueSaveMacKeySources[i].IsZeros() && + !s.DeviceKey.IsZeros()) { GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null); Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek); @@ -257,44 +257,44 @@ namespace LibHac.Common.Keys } // Derive BIS keys - if (s.DeviceKey.IsEmpty() - || s.BisKekSource.IsEmpty() - || s.AesKekGenerationSource.IsEmpty() - || s.AesKeyGenerationSource.IsEmpty() - || s.RetailSpecificAesKeySource.IsEmpty()) + if (s.DeviceKey.IsZeros() + || s.BisKekSource.IsZeros() + || s.AesKekGenerationSource.IsZeros() + || s.AesKeyGenerationSource.IsZeros() + || s.RetailSpecificAesKeySource.IsZeros()) { return; } // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 - if (s.BisKeySources[3].IsEmpty() && !s.BisKeySources[2].IsEmpty()) + if (s.BisKeySources[3].IsZeros() && !s.BisKeySources[2].IsZeros()) { s.BisKeySources[3] = s.BisKeySources[2]; } Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey); - if (!s.BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); + if (!s.BisKeySources[0].IsZeros()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); GenerateKek(s.DeviceKey, s.BisKekSource, kek, s.AesKekGenerationSource, s.AesKeyGenerationSource); for (int i = 1; i < 4; i++) { - if (!s.BisKeySources[i].IsEmpty()) + if (!s.BisKeySources[i].IsZeros()) Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek); } } private static void DerivePerFirmwareKeys(KeySet s) { - bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsEmpty(); - bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsEmpty(); - bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsEmpty(); - bool haveTitleKekSource = !s.TitleKekSource.IsEmpty(); - bool havePackage2KeySource = !s.Package2KeySource.IsEmpty(); + bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros(); + bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros(); + bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsZeros(); + bool haveTitleKekSource = !s.TitleKekSource.IsZeros(); + bool havePackage2KeySource = !s.Package2KeySource.IsZeros(); for (int i = 0; i < KeySet.KeyRevisionCount; i++) { - if (s.MasterKeys[i].IsEmpty()) + if (s.MasterKeys[i].IsZeros()) { continue; } @@ -331,7 +331,7 @@ namespace LibHac.Common.Keys private static void DeriveNcaHeaderKey(KeySet s) { - if (s.HeaderKekSource.IsEmpty() || s.HeaderKeySource.IsEmpty() || s.MasterKeys[0].IsEmpty()) return; + if (s.HeaderKekSource.IsZeros() || s.HeaderKeySource.IsZeros() || s.MasterKeys[0].IsZeros()) return; var headerKek = new AesKey(); @@ -362,7 +362,7 @@ namespace LibHac.Common.Keys } // Derive sd card save key - if (!s.SeedUniqueSaveMacKekSource.IsEmpty() && !s.SeedUniqueSaveMacKeySource.IsEmpty()) + if (!s.SeedUniqueSaveMacKekSource.IsZeros() && !s.SeedUniqueSaveMacKeySource.IsZeros()) { var keySource = new AesKey(); @@ -383,7 +383,7 @@ namespace LibHac.Common.Keys Aes.DecryptEcb128(kekSeed, kek, key); Aes.DecryptEcb128(src, srcKek, kek); - if (!keySeed.IsEmpty) + if (!keySeed.IsZeros()) { Aes.DecryptEcb128(keySeed, dest, srcKek); } diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs index 17c2d396..a0efe4b1 100644 --- a/src/LibHac/Crypto/KeyTypes.cs +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -22,7 +22,7 @@ namespace LibHac.Crypto public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; public static implicit operator Span(in AesKey value) => Unsafe.AsRef(in value).Data; @@ -58,7 +58,7 @@ namespace LibHac.Crypto public static implicit operator ReadOnlySpan(in AesXtsKey value) => value.DataRo; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0; + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0; public override readonly string ToString() => DataRo.ToHexString(); } @@ -78,7 +78,7 @@ namespace LibHac.Crypto public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; public static implicit operator Span(in AesIv value) => Unsafe.AsRef(in value).Data; public static implicit operator ReadOnlySpan(in AesIv value) => value.DataRo; @@ -105,7 +105,7 @@ namespace LibHac.Crypto public readonly ReadOnlySpan DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool IsEmpty() => (DataRo64[0] | DataRo64[1]) == 0; + public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0; public static implicit operator Span(in AesCmac value) => Unsafe.AsRef(in value).Data; public static implicit operator ReadOnlySpan(in AesCmac value) => value.DataRo; diff --git a/src/LibHac/FsSystem/IntegrityVerificationStorage.cs b/src/LibHac/FsSystem/IntegrityVerificationStorage.cs index f59e955e..08290340 100644 --- a/src/LibHac/FsSystem/IntegrityVerificationStorage.cs +++ b/src/LibHac/FsSystem/IntegrityVerificationStorage.cs @@ -63,7 +63,7 @@ namespace LibHac.FsSystem if (Type == IntegrityStorageType.Save) { - if (Utilities.IsEmpty(hashBuffer)) + if (Utilities.IsZeros(hashBuffer)) { destination.Clear(); BlockValidities[blockIndex] = Validity.Valid; @@ -144,7 +144,7 @@ namespace LibHac.FsSystem source.CopyTo(dataBuffer); byte[] hash = DoHash(dataBuffer, 0, toWrite); - if (Type == IntegrityStorageType.Save && source.IsEmpty()) + if (Type == IntegrityStorageType.Save && source.IsZeros()) { Array.Clear(hash, 0, DigestSize); } @@ -207,7 +207,7 @@ namespace LibHac.FsSystem long hashPos = i * DigestSize; HashStorage.Read(hashPos, digest).ThrowIfFailure(); - if (!Utilities.IsEmpty(digest)) continue; + if (!Utilities.IsZeros(digest)) continue; int dataOffset = i * SectorSize; BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, dataOffset, SectorSize); diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 67ad92fa..85650784 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -45,7 +45,7 @@ namespace LibHac.FsSystem.NcaUtils int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration); byte[] keyAreaKey = KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].DataRo.ToArray(); - if (keyAreaKey.IsEmpty()) + if (keyAreaKey.IsZeros()) { string keyName = $"key_area_key_{KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}"; throw new MissingKeyException("Unable to decrypt NCA section.", keyName, KeyType.Common); @@ -73,7 +73,7 @@ namespace LibHac.FsSystem.NcaUtils throw new MissingKeyException("Missing NCA title key.", rightsId.ToString(), KeyType.Title); } - if (titleKek.IsEmpty()) + if (titleKek.IsZeros()) { string keyName = $"titlekek_{keyRevision:x2}"; throw new MissingKeyException("Unable to decrypt title key.", keyName, KeyType.Common); @@ -112,10 +112,10 @@ namespace LibHac.FsSystem.NcaUtils if (Header.HasRightsId) { return KeySet.ExternalKeySet.Contains(new RightsId(Header.RightsId)) && - !KeySet.TitleKeks[keyRevision].IsEmpty(); + !KeySet.TitleKeks[keyRevision].IsZeros(); } - return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsEmpty(); + return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsZeros(); } public bool SectionExists(NcaSectionType type) diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index 9c45b79a..521d8e03 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -107,7 +107,7 @@ namespace LibHac.FsSystem.NcaUtils public Span RightsId => _header.Span.Slice(NcaHeaderStruct.RightsIdOffset, NcaHeaderStruct.RightsIdSize); - public bool HasRightsId => !Utilities.IsEmpty(RightsId); + public bool HasRightsId => !Utilities.IsZeros(RightsId); public Span GetKeyArea() { diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index ffdb6029..50abb275 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -262,7 +262,7 @@ namespace LibHac.FsSystem.Save headerStream.Position = 0x108; headerStream.Write(hash, 0, hash.Length); - if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsEmpty()) return ResultFs.PreconditionViolation.Log(); + if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsZeros()) return ResultFs.PreconditionViolation.Log(); var cmacData = new byte[0x200]; var cmac = new byte[0x10]; diff --git a/src/LibHac/Utilities.cs b/src/LibHac/Utilities.cs index ad8f0061..25c29df8 100644 --- a/src/LibHac/Utilities.cs +++ b/src/LibHac/Utilities.cs @@ -67,10 +67,10 @@ namespace LibHac return a1.SequenceEqual(a2); } - public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan)array).IsEmpty(); - public static bool IsEmpty(this Span span) => ((ReadOnlySpan)span).IsEmpty(); + public static bool IsZeros(this byte[] array) => ((ReadOnlySpan)array).IsZeros(); + public static bool IsZeros(this Span span) => ((ReadOnlySpan)span).IsZeros(); - public static bool IsEmpty(this ReadOnlySpan span) + public static bool IsZeros(this ReadOnlySpan span) { for (int i = 0; i < span.Length; i++) { diff --git a/src/LibHac/XciHeader.cs b/src/LibHac/XciHeader.cs index b893c375..90ff047b 100644 --- a/src/LibHac/XciHeader.cs +++ b/src/LibHac/XciHeader.cs @@ -108,7 +108,7 @@ namespace LibHac SelKey = reader.ReadInt32(); LimAreaPage = reader.ReadInt32(); - if (keySet != null && !keySet.XciHeaderKey.IsEmpty()) + if (keySet != null && !keySet.XciHeaderKey.IsZeros()) { byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize); var decHeader = new byte[EncryptedHeaderSize]; From 9bb2c3a8436c77f830dd5e0a2d4fd81e12c73cd9 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 13 Oct 2020 20:55:48 -0700 Subject: [PATCH 14/16] Support more key parser situations Support comments, ignoring lines that are too long, properly reading the last line in a file. Probably some bug fixes too. --- src/LibHac/Common/Keys/ExternalKeyReader.cs | 175 +++++++++++++++----- src/LibHac/Common/Keys/KeyDerivation.cs | 4 +- 2 files changed, 134 insertions(+), 45 deletions(-) diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 5267726d..34c14cc6 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using LibHac.Diag; using LibHac.Fs; using LibHac.Spl; using LibHac.Util; @@ -11,6 +12,8 @@ namespace LibHac.Common.Keys { public static class ExternalKeyReader { + private const int ReadBufferSize = 1024; + // Contains info from a specific key being read from a file [DebuggerDisplay("{" + nameof(Name) + "}")] private struct SpecificKeyInfo @@ -165,7 +168,7 @@ namespace LibHac.Common.Keys if (reader == null) return; using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[1024]; + Span buffer = stackalloc char[ReadBufferSize]; var ctx = new KvPairReaderContext(streamReader, buffer); while (true) @@ -229,7 +232,7 @@ namespace LibHac.Common.Keys if (reader == null) return; using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[1024]; + Span buffer = stackalloc char[ReadBufferSize]; var ctx = new KvPairReaderContext(streamReader, buffer); // Estimate the number of keys by assuming each line is about 69 bytes. @@ -291,6 +294,8 @@ namespace LibHac.Common.Keys public Span CurrentValue; public int BufferPos; public bool NeedFillBuffer; + public bool HasReadEndOfFile; + public bool SkipNextLine; public KvPairReaderContext(TextReader reader, Span buffer) { @@ -300,6 +305,8 @@ namespace LibHac.Common.Keys CurrentValue = default; BufferPos = buffer.Length; NeedFillBuffer = true; + HasReadEndOfFile = false; + SkipNextLine = false; } } @@ -307,25 +314,36 @@ namespace LibHac.Common.Keys { ReadKey, NoKeyRead, + ReadComment, Finished, + LineTooLong, Error } private enum ReaderState { Initial, + Comment, Key, WhiteSpace1, Delimiter, Value, WhiteSpace2, - End + Success, + CommentSuccess, + Error } private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) { Span buffer = reader.Buffer; + if (reader.BufferPos == buffer.Length && reader.HasReadEndOfFile) + { + // There is no more text to parse. Return that we've finished. + return ReaderStatus.Finished; + } + if (reader.NeedFillBuffer) { // Move unread text to the front of the buffer @@ -333,22 +351,32 @@ namespace LibHac.Common.Keys int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos)); - if (charsRead == 0) - { - return ReaderStatus.Finished; - } - // ReadBlock will only read less than the buffer size if there's nothing left to read if (charsRead != reader.BufferPos) { buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); reader.Buffer = buffer; + reader.HasReadEndOfFile = true; } reader.NeedFillBuffer = false; reader.BufferPos = 0; } + if (reader.SkipNextLine) + { + while (reader.BufferPos < buffer.Length && !IsEndOfLine(buffer[reader.BufferPos])) + { + reader.BufferPos++; + } + + // Stop skipping once we reach a new line + if (reader.BufferPos < buffer.Length) + { + reader.SkipNextLine = false; + } + } + // Skip any empty lines while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos])) { @@ -385,6 +413,14 @@ namespace LibHac.Common.Keys // Decrement so we can process this character the next round through the state machine i--; continue; + case ReaderState.Initial when c == '#': + state = ReaderState.Comment; + keyOffset = i; + continue; + case ReaderState.Initial when IsEndOfLine(c): + // The line was empty. Update the buffer position to indicate a new line + reader.BufferPos = i; + continue; case ReaderState.Key when IsWhiteSpace(c): state = ReaderState.WhiteSpace1; keyLength = i - keyOffset; @@ -412,9 +448,9 @@ namespace LibHac.Common.Keys i--; continue; case ReaderState.Value when IsEndOfLine(c): - state = ReaderState.End; + state = ReaderState.Success; valueLength = i - valueOffset; - continue; + break; case ReaderState.Value when IsWhiteSpace(c): state = ReaderState.WhiteSpace2; valueLength = i - valueOffset; @@ -422,20 +458,89 @@ namespace LibHac.Common.Keys case ReaderState.WhiteSpace2 when IsWhiteSpace(c): continue; case ReaderState.WhiteSpace2 when IsEndOfLine(c): - state = ReaderState.End; - continue; - case ReaderState.End when IsEndOfLine(c): - continue; - case ReaderState.End when !IsEndOfLine(c): + state = ReaderState.Success; break; + case ReaderState.Comment when IsEndOfLine(c): + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + break; + case ReaderState.Comment: + continue; + + // If none of the expected characters were found while in these states, the + // line is considered invalid. + case ReaderState.Initial: + case ReaderState.Key: + case ReaderState.WhiteSpace1: + case ReaderState.Delimiter: + state = ReaderState.Error; + continue; + case ReaderState.Error when !IsEndOfLine(c): + continue; } // We've exited the state machine for one reason or another break; } + // First check if hit the end of the buffer or read the entire buffer without seeing a new line + if (i == buffer.Length && !reader.HasReadEndOfFile) + { + reader.NeedFillBuffer = true; + + // If the entire buffer is part of a single long line + if (reader.BufferPos == 0 || reader.SkipNextLine) + { + reader.BufferPos = i; + + // The line might continue past the end of the current buffer, so skip the + // remainder of the line after the buffer is refilled. + reader.SkipNextLine = true; + return ReaderStatus.LineTooLong; + } + + return ReaderStatus.NoKeyRead; + } + + // The only way we should exit the loop in the "Value" or "WhiteSpace2" state is if we reached + // the end of the buffer in that state, meaning i == buffer.Length. + // Running out of buffer when we haven't read the end of the file will have been handled by the + // previous "if" block. If we get to this point in those states, we should be at the very end + // of the file which will be treated as the end of a line. + if (state == ReaderState.Value || state == ReaderState.WhiteSpace2) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + // WhiteSpace2 will have already set this value + if (state == ReaderState.Value) + valueLength = i - valueOffset; + + state = ReaderState.Success; + } + + // Same situation as the two above states + if (state == ReaderState.Comment) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + } + + // Same as the above states except the final line was empty or whitespace. + if (state == ReaderState.Initial) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + reader.BufferPos = i; + return ReaderStatus.NoKeyRead; + } + // If we successfully read both the key and value - if (state == ReaderState.End || state == ReaderState.WhiteSpace2) + if (state == ReaderState.Success) { reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength); @@ -444,40 +549,24 @@ namespace LibHac.Common.Keys return ReaderStatus.ReadKey; } - // We either ran out of buffer or hit an error reading the key-value pair. - // Advance to the end of the line if possible. - while (i < buffer.Length && !IsEndOfLine(buffer[i])) + if (state == ReaderState.CommentSuccess) { - i++; + reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); + reader.BufferPos = i; + + return ReaderStatus.ReadComment; } - // We don't have a complete line. Return that the buffer needs to be refilled. - if (i == buffer.Length) - { - reader.NeedFillBuffer = true; - return ReaderStatus.NoKeyRead; - } - - // If we hit a line with an error, it'll be returned as "CurrentKey" in the reader context - reader.CurrentKey = buffer.Slice(reader.BufferPos, i - reader.BufferPos); + // A bad line was encountered if we're in any of the other states + // Return the line as "CurrentKey" + reader.CurrentKey = reader.Buffer.Slice(reader.BufferPos, i - reader.BufferPos); reader.BufferPos = i; return ReaderStatus.Error; - static bool IsWhiteSpace(char c) - { - return c == ' ' || c == '\t'; - } - - static bool IsDelimiter(char c) - { - return c == '=' || c == ','; - } - - static bool IsEndOfLine(char c) - { - return c == '\0' || c == '\r' || c == '\n'; - } + static bool IsWhiteSpace(char c) => c == ' ' || c == '\t'; + static bool IsDelimiter(char c) => c == '=' || c == ','; + static bool IsEndOfLine(char c) => c == '\0' || c == '\r' || c == '\n'; static void ToLower(ref char c) { diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs index d01b8cdc..22c3b77d 100644 --- a/src/LibHac/Common/Keys/KeyDerivation.cs +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -19,7 +19,7 @@ namespace LibHac.Common.Keys PopulateOldMasterKeys(keySet); DerivePerConsoleKeys(keySet); - DerivePerFirmwareKeys(keySet); + DerivePerGenerationKeys(keySet); DeriveNcaHeaderKey(keySet); DeriveSdCardKeys(keySet); } @@ -284,7 +284,7 @@ namespace LibHac.Common.Keys } } - private static void DerivePerFirmwareKeys(KeySet s) + private static void DerivePerGenerationKeys(KeySet s) { bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros(); bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros(); From c99f04970f51654ac166af9082b569e379c1bad5 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 13 Oct 2020 21:01:21 -0700 Subject: [PATCH 15/16] Update the key info page --- .gitignore | 3 +- KEYS.md | 259 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 165 insertions(+), 97 deletions(-) diff --git a/.gitignore b/.gitignore index aecebd82..934b03dc 100644 --- a/.gitignore +++ b/.gitignore @@ -269,5 +269,4 @@ global.json # Files generated at build time ResultNameResolver.Generated.cs -DefaultKeySet.Generated.cs -ResultNameResolver.Generated.cs \ No newline at end of file +DefaultKeySet.Generated.cs \ No newline at end of file diff --git a/KEYS.md b/KEYS.md index bca7006f..4a298cb5 100644 --- a/KEYS.md +++ b/KEYS.md @@ -2,104 +2,136 @@ Keys are required for decrypting most of the file formats used by the Nintendo Switch. -Keysets are stored as text files, and are loaded from `$HOME/.switch`. These 3 filenames are automatically read: -`prod.keys` - Contains common keys usedy by all Switch devices. -`console.keys` - Contains console-unique keys. +Key sets are stored as text files, and are loaded from `$HOME/.switch`. On Windows this path is usually `C:\Users\\.switch`. + +These 4 filenames are automatically read: +`prod.keys` - Contains keys shared by all retail Switch devices. +`dev.keys` - Contains keys shared by all development Switch devices. Optional. +`console.keys` - Contains console-unique keys. Optional. `title.keys` - Contains game-specific keys. -#### XTS-AES keys note +## Obtaining keys -The Switch uses 128-bit XTS-AES for decrypting the built-in storage (BIS), NCA header and the SD card contents. -This encryption method uses 2 128-bit keys: a "data" or "cipher" key, and a "tweak" key. +Keys can be obtained from a Switch that can run homebrew. The easiest way is to use [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM). See an up-to-date Switch homebrew guide for details. -In the keyfile these are stored as one 256-bit key with the data key first, followed by the tweak key. +After running Lockpick_RCM `/switch/prod.keys` and `/switch/title.keys` should be on your SD card. Copy these two files to the `.switch` directory specified above. -## Keyfile format +# Key file details +Dumping keys from a Switch is all that is needed for LibHac. -`prod.keys` and `console.keys` should be in the following format with one key per line: +The following section contains some additional information on keys, documentation on the key file format and a list of supported keys. + +## Key file format + +`prod.keys`, `dev.keys` and `console.keys` should be in the following format with one key per line: `key_name = hexadecimal_key_value` +Each line must contain fewer than 1024 characters. + e.g. (Not actual keys) ``` -master_key_00 = 63C9FCB338CDE3D037D29BB66F897C6B -master_key_01 = 4636CB976DFE95095C1F55151A8326C6 -header_key_source = 343795270AAD5D19EBE2956C9BC71F4C41836B21DC6ACD7BACD4F6AF4816692C +master_key_00 = 496620796F752772652072656164696E +master_key_01 = 6720746869732C20796F752772652061 +header_key_source = 206E657264AD5D19EBE2956C9BC71F4C41836B21DC6ACD7BACD4F6AF4816692C ``` -#### Title Keys +### Title keys `title.keys` should be in the following format with one key per line: -`rights_id,hexadecimal_key_value`. +`rights_id = hexadecimal_key_value`. e.g. (Not actual keys) ``` -01000000000100000000000000000003,B4A1F5575D7D8A81624ED36D4E4BD8FD -01000000000108000000000000000003,C8AD76F8C78E241ADFEE6EB12E33F1BD -01000000000108000000000000000004,F9C8EAD30BB594434E4AF62C483CD796 +01000000000100000000000000000003 = 68747470733A2F2F7777772E796F7574 +01000000000108000000000000000003 = 7562652E636F6D2F77617463683F763D +01000000000108000000000000000004 = 64517734773957675863513F4C696248 ``` -## Keyfile templates +### Dev keys + +Keys from `dev.keys` will always be loaded as dev keys. +Dev keys may also be loaded from `prod.keys`, allowing both key sets to be in the same file. +Because both key sets use the same key sources, only a small number of root keys are needed to derive each set. + +Key names that have `_dev` after the main key name but before the key index will be loaded as dev keys. + +e.g. (Not actual keys) +``` +master_key_0a = B6B0F17AC61696120A15FFD41A529CBE +master_key_dev_0a = 154A07EAFC50C6328A66C4FD2CDB277A +xci_header_key_dev = 118BA87386A242FA9DCCB06853E7A9F6 +``` + +## Key system + +This is meant to be a basic overview of the concepts used by the Switch's content key system. + +### Key generations +In a nutshell, the Switch's OS contains key sources or seeds. +These seeds are useless on their own, but given a "master key" they can be used to generate the actual content keys. +This master key is the root from which all content keys are derived. +Retail and development Switches have different master keys. + +The Switch uses what are called "key generations" (As in the noun, not the verb). +Each generation has its own master key which results in a different set of content keys for each one. +Content files are encrypted with the keys from the most recent generation. +e.g. A game built for system version 6.2.0 will be encrypted with the keys for 6.2.0. Older system versions would be unable to decrypt the content. + +### Root keys +Root keys are the keys used to derive other keys. +Erista (original Switch hardware version) and Mariko (second hardware version) have different root keys. +Both these root keys are used to derive the same master key which will then derive other keys. + +The current root key for Erista is `tsec_root_key_02`, and the key for Mariko is `mariko_kek`. +The main purpose of these keys is to generate the master key, so they're not really necessary for decrypting content. + +These root keys, with proper security, are supposed to be hardware secrets, unable to be accessed by software. + +Package1 is the only content that is not encrypted with these root keys or their derivatives. +Each Erista package1 is encrypted with its own unique key, and every Mariko package1 is encrypted with `mariko_bek`. + +## Key file templates This template contains the keys needed to derive all the keys used by hactoolnet, although not all of them are needed for every task. +In fact, more than 99% of all content can be decrypted by providing only the most recent master key. -Fill out the template with the actual keys to get a working keyfile. +LibHac contains the key sources that keys are derived from. Only a small number of root keys need to be provided, although any keys will be loaded from the key file if present. + +Providing the following keys will enable decryption of all retail content. +Every one of these keys also has a dev version. Providing them will enable decryption of all dev content. ``` -master_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -master_key_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# Only the latest master key is needed to decrypt the vast majority of Switch content. +master_key_0a = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_mac_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -keyblob_key_source_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# Package1 keys are used to decrypt package1, the first part of the OS loaded during boot. +package1_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package1_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package1_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package1_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package1_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package1_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# The XCI header key will decrypt the gamecard info in an XCI. Not usually needed. +xci_header_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -package2_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# Methods of obtaining the keys below are not publicly available as of Oct. 2020, +# but they're included anyway for completion's sake -aes_kek_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -aes_key_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -titlekek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# Keys for Erista package1 since firmware 6.2.0. +package1_key_06 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_07 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_08 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_09 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +package1_key_0a = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -key_area_key_application_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -key_area_key_ocean_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -key_area_key_system_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -sd_card_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -sd_card_save_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -sd_card_nca_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -sd_card_custom_storage_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -header_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -header_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -xci_header_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -retail_specific_aes_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -per_console_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -eticket_rsa_kek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -bis_key_source_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -bis_key_source_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -bis_key_source_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -bis_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -save_mac_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -save_mac_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -save_mac_sd_card_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -save_mac_sd_card_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# The Mariko boot encryption key (BEK) is used to decrypt Mariko package1. +# The Mariko key encryption key (KEK) is used to derive master keys on Mariko Switches. +# All content keys are the same on both Switch versions except for package1 keys. +# Together the Mariko BEK and KEK are enough to derive all current content keys and all +# content keys in the forseeable future except for Erista package1. +mariko_bek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +mariko_kek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` ### Console-unique keys @@ -111,7 +143,7 @@ tsec_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX secure_boot_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX sd_seed = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -# The below keys can be derived from tsec_key and secure_boot_key +# These keys can be derived from tsec_key and secure_boot_key device_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bis_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bis_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -119,6 +151,13 @@ bis_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bis_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` +#### XTS-AES keys note + +The Switch uses 128-bit XTS-AES for decrypting the built-in storage (BIS), NCA header and the SD card contents. +This encryption method uses 2 128-bit keys: a "data" or "cipher" key, and a "tweak" key. + +In the key file these are stored as one 256-bit key with the data key first, followed by the tweak key. + ## Complete key list Below is a complete list of keys that are currently recognized. \## represents a hexadecimal number between 00 and 1F @@ -126,42 +165,69 @@ Below is a complete list of keys that are currently recognized. ### Common keys ``` -master_key_source +tsec_root_kek +package1_mac_kek +package1_kek +tsec_auth_signature_## +tsec_root_key_## + keyblob_mac_key_source +keyblob_key_source_## +keyblob_## + +mariko_bek +mariko_kek +mariko_aes_class_key_## +mariko_master_kek_source_## + +master_kek_source_## +master_kek_## +master_key_source +master_key_## + +package1_key_## +package1_mac_key_## package2_key_source -aes_kek_generation_source -aes_key_generation_source -key_area_key_application_source -key_area_key_ocean_source -key_area_key_system_source -titlekek_source -header_kek_source -header_key_source -sd_card_kek_source -sd_card_nca_key_source -sd_card_save_key_source -retail_specific_aes_key_source -per_console_key_source +package2_key_## + bis_kek_source bis_key_source_00 bis_key_source_01 bis_key_source_02 -save_mac_kek_source -save_mac_key_source +bis_key_source_03 -header_key -xci_header_key -eticket_rsa_kek - -master_key_## -package1_key_## -package2_key_## +per_console_key_source +retail_specific_aes_key_source +aes_kek_generation_source +aes_key_generation_source +titlekek_source titlekek_## + +header_kek_source +header_key_source +header_key + +key_area_key_application_source +key_area_key_ocean_source +key_area_key_system_source key_area_key_application_## key_area_key_ocean_## key_area_key_system_## -keyblob_key_source_## -keyblob_## + +save_mac_kek_source +save_mac_key_source_00 +save_mac_key_source_01 +save_mac_sd_card_kek_source +save_mac_sd_card_key_source + +sd_card_kek_source +sd_card_save_key_source +sd_card_nca_key_source +sd_card_custom_storage_key_source + +xci_header_key +eticket_rsa_kek +ssl_rsa_kek ``` ### Console-unique keys @@ -174,10 +240,13 @@ bis_key_00 bis_key_01 bis_key_02 bis_key_03 +save_mac_key_00 +save_mac_key_01 keyblob_key_## keyblob_mac_key_## encrypted_keyblob_## sd_seed +save_mac_sd_card_key ``` From 9f81b933e8d6627a2b815ed061f7ef313b3ed54c Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 13 Oct 2020 21:34:08 -0700 Subject: [PATCH 16/16] KeySet.DeriveKeys now derives both sets automatically --- build/CodeGen/Stage2/KeysCodeGen.cs | 2 +- src/LibHac/Common/Keys/ExternalKeyReader.cs | 16 ---------------- src/LibHac/Common/Keys/KeySet.cs | 14 +++++++++++++- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs index 4077ba5a..c9eee6e7 100644 --- a/build/CodeGen/Stage2/KeysCodeGen.cs +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -91,7 +91,7 @@ namespace LibHacBuild.CodeGen.Stage2 var keySet = new KeySet(); // Populate the key set with all the keys in IncludedKeys.txt - using (Stream keyFile = Common.GetResource(InputMainKeyFileName)) + using (Stream keyFile = GetResource(InputMainKeyFileName)) { List list = KeySet.CreateKeyInfoList(); ExternalKeyReader.ReadMainKeys(keySet, keyFile, list); diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 34c14cc6..cbe476ff 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -67,14 +67,6 @@ namespace LibHac.Common.Keys } keySet.DeriveKeys(logger); - - // Dev keys can be read from prod key files, so derive any missing keys if necessary. - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - keySet.SetMode(KeySet.Mode.Dev); - keySet.DeriveKeys(logger); - keySet.SetMode(KeySet.Mode.Prod); - } } /// @@ -122,14 +114,6 @@ namespace LibHac.Common.Keys } keySet.DeriveKeys(logger); - - // Dev keys can read from prod key files, so derive any missing keys if necessary. - if (keySet.CurrentMode == KeySet.Mode.Prod) - { - keySet.SetMode(KeySet.Mode.Dev); - keySet.DeriveKeys(logger); - keySet.SetMode(KeySet.Mode.Prod); - } } /// diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index 490809fb..79b7e32c 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -207,7 +207,19 @@ namespace LibHac.Common.Keys return DefaultKeySet.CreateKeyList(); } - public void DeriveKeys(IProgressReport logger = null) => KeyDerivation.DeriveAllKeys(this, logger); + public void DeriveKeys(IProgressReport logger = null) + { + Mode originalMode = CurrentMode; + + SetMode(Mode.Prod); + KeyDerivation.DeriveAllKeys(this, logger); + + SetMode(Mode.Dev); + KeyDerivation.DeriveAllKeys(this, logger); + + SetMode(originalMode); + } + public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); private static RSAParameters CreateRsaParameters(in RsaKey key)