From f7030aef4fac2dd31406972c6a9327b43d03476b Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 1 Oct 2020 18:06:32 -0700 Subject: [PATCH] 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()); + } + } +}