From fa79db2285fd3c6d40adc8496e1bd22ea57be186 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Wed, 7 Oct 2020 17:54:29 -0700 Subject: [PATCH] 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)); } } }