Add a new KeySet class

This commit is contained in:
Alex Barney 2020-10-01 18:06:32 -07:00
parent 61ce892697
commit f7030aef4f
16 changed files with 1385 additions and 0 deletions

View file

@ -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<byte> Payload => Bytes.Slice(0x20, Unsafe.SizeOf<KeyBlob>());
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public readonly ReadOnlySpan<byte> ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsEmpty()
{
ReadOnlySpan<ulong> ulongSpan = MemoryMarshal.Cast<byte, ulong>(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<byte>(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<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public readonly ReadOnlySpan<byte> ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsEmpty()
{
ReadOnlySpan<ulong> ulongSpan = MemoryMarshal.Cast<byte, ulong>(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<byte>(in KeyBlob value)
{
return SpanHelpers.AsReadOnlyByteSpan(in value);
}
public override readonly string ToString() => ReadOnlyBytes.ToHexString();
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array12<T>
{
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<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
public readonly ReadOnlySpan<T> ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array12<T> value) => value.ReadOnlyItems;
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array2<T>
{
public const int Length = 2;
private T _item1;
private T _item2;
public ref T this[int i] => ref Items[i];
public Span<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
public readonly ReadOnlySpan<T> ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array2<T> value) => value.ReadOnlyItems;
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array3<T>
{
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<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
public readonly ReadOnlySpan<T> ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array3<T> value) => value.ReadOnlyItems;
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array32<T>
{
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<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
public readonly ReadOnlySpan<T> ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array32<T> value) => value.ReadOnlyItems;
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array4<T>
{
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<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
public readonly ReadOnlySpan<T> ReadOnlyItems => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array4<T> value) => value.ReadOnlyItems;
}
}

View file

@ -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<byte> 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<KeyInfo> 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<KeyInfo> 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<byte> 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<KeyInfo> 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<KeyInfo> 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<byte> 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<byte> 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<KeyInfo> CreateKeyList()
{
// Update this value if more keys are added
var keys = new List<KeyInfo>(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;
}
}
}

View file

@ -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
{
/// <summary>
/// The number of keyblobs that were used for &lt; 6.2.0 crypto
/// </summary>
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<AesKey> MarikoAesClassKeys => _keys._rootKeys.MarikoAesClassKeys.Items;
public ref AesKey MarikoKek => ref _keys._rootKeys.MarikoKek;
public ref AesKey MarikoBek => ref _keys._rootKeys.MarikoBek;
public Span<KeyBlob> KeyBlobs => _keys._rootKeys.KeyBlobs.Items;
public Span<AesKey> 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<AesKey> TsecAuthSignatures => _keys._rootKeys.TsecAuthSignatures.Items;
public Span<AesKey> TsecRootKeys => _keys._rootKeys.TsecRootKeys.Items;
public Span<AesKey> MasterKekSources => _keys._keySeeds.MasterKekSources.Items;
public Span<AesKey> MarikoMasterKekSources => _keys._keySeeds.MarikoMasterKekSources.Items;
public Span<AesKey> MasterKeks => _keys._derivedKeys.MasterKeks.Items;
public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource;
public Span<AesKey> MasterKeys => _keys._derivedKeys.MasterKeys.Items;
public Span<AesKey> Package1MacKeys => _keys._derivedKeys.Package1MacKeys.Items;
public Span<AesKey> Package1Keys => _keys._derivedKeys.Package1Keys.Items;
public Span<AesKey> 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<AesXtsKey> 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<AesXtsKey> SdCardKeySources => _keys._keySeeds.SdCardKeySources.Items;
public ref AesKey DeviceUniqueSaveMacKekSource => ref _keys._keySeeds.DeviceUniqueSaveMacKekSource;
public Span<AesKey> 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<AesKey> TitleKeks => _keys._derivedKeys.TitleKeks.Items;
public Span<Array3<AesKey>> 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<AesKey> KeyBlobKeys => _keys._deviceKeys.KeyBlobKeys.Items;
public Span<AesKey> KeyBlobMacKeys => _keys._deviceKeys.KeyBlobMacKeys.Items;
public Span<EncryptedKeyBlob> EncryptedKeyBlobs => _keys._deviceKeys.EncryptedKeyBlobs.Items;
public ref AesKey DeviceKey => ref _keys._deviceKeys.DeviceKey;
public Span<AesXtsKey> BisKeys => _keys._deviceKeys.BisKeys.Items;
public Span<AesKey> DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items;
public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey;
public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed;
public Span<AesXtsKey> SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items;
public void SetSdSeed(ReadOnlySpan<byte> 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<byte>(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<byte> key, ReadOnlySpan<byte> src, Span<byte> dest,
ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> 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<AesKey> MarikoAesClassKeys;
public AesKey MarikoKek;
public AesKey MarikoBek;
public Array32<KeyBlob> KeyBlobs;
public AesKey TsecRootKek;
public AesKey Package1MacKek;
public AesKey Package1Kek;
public Array32<AesKey> TsecAuthSignatures;
public Array32<AesKey> TsecRootKeys;
public AesKey XciHeaderKey;
}
[StructLayout(LayoutKind.Sequential)]
public struct KeySeeds
{
public Array32<AesKey> KeyBlobKeySources;
public AesKey KeyBlobMacKeySource;
public Array32<AesKey> MasterKekSources;
public Array32<AesKey> MarikoMasterKekSources;
public AesKey MasterKeySource;
public AesKey Package2KeySource;
public AesKey PerConsoleKeySource;
public AesKey RetailSpecificAesKeySource;
public AesKey BisKekSource;
public Array4<AesXtsKey> 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<AesXtsKey> SdCardKeySources;
public AesKey DeviceUniqueSaveMacKekSource;
public Array2<AesKey> DeviceUniqueSaveMacKeySources;
public AesKey SeedUniqueSaveMacKekSource;
public AesKey SeedUniqueSaveMacKeySource;
public AesXtsKey HeaderKeySource;
}
[StructLayout(LayoutKind.Sequential)]
public struct DerivedKeys
{
public Array32<AesKey> MasterKeks;
public Array32<AesKey> MasterKeys;
public Array32<AesKey> Package1MacKeys;
public Array32<AesKey> Package1Keys;
public Array32<AesKey> Package2Keys;
public Array32<Array3<AesKey>> KeyAreaKeys;
public Array32<AesKey> TitleKeks;
public AesXtsKey HeaderKey;
public AesKey EticketRsaKek;
public AesKey SslRsaKek;
}
[StructLayout(LayoutKind.Sequential)]
public struct DeviceKeys
{
public AesKey SecureBootKey;
public AesKey TsecKey;
public Array32<AesKey> KeyBlobKeys;
public Array32<AesKey> KeyBlobMacKeys;
public Array32<EncryptedKeyBlob> EncryptedKeyBlobs;
public AesKey DeviceKey;
public Array4<AesXtsKey> BisKeys;
public Array2<AesKey> DeviceUniqueSaveMacKeys;
public AesKey SeedUniqueSaveMacKey;
public AesKey SdCardEncryptionSeed;
public Array3<AesXtsKey> SdCardEncryptionKeys;
}
}

View file

@ -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<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
bool preferDotNetCrypto = false)
{

View file

@ -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<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> 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<byte>(in AesKey value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(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<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong));
public Span<AesKey> SubKeys => SpanHelpers.CreateSpan(ref DataKey, Size / Unsafe.SizeOf<AesKey>());
public static implicit operator Span<byte>(in AesXtsKey value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(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<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> 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<byte>(in AesIv value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(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<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> 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<byte>(in AesCmac value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(in AesCmac value) => value.DataRo;
public override readonly string ToString() => DataRo.ToHexString();
#if DEBUG
[FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1;
#endif
}
}

View file

@ -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]));

View file

@ -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

View file

@ -62,6 +62,11 @@ namespace LibHac
return a1.SequenceEqual(a2);
}
public static bool SpansEqual<T>(ReadOnlySpan<T> a1, ReadOnlySpan<T> a2) where T : IEquatable<T>
{
return a1.SequenceEqual(a2);
}
public static ReadOnlySpan<byte> GetUtf8Bytes(string value)
{
return Encoding.UTF8.GetBytes(value).AsSpan();
@ -84,6 +89,7 @@ namespace LibHac
}
public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan<byte>)array).IsEmpty();
public static bool IsEmpty(this Span<byte> span) => ((ReadOnlySpan<byte>)span).IsEmpty();
public static bool IsEmpty(this ReadOnlySpan<byte> span)
{
@ -319,6 +325,26 @@ namespace LibHac
return true;
}
public static bool TryToBytes(this ReadOnlySpan<char> input, Span<byte> 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()

View file

@ -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<EncryptedKeyBlob>());
}
[Fact]
public static void KeyBlobSizeIs0x90()
{
Assert.Equal(0x90, Unsafe.SizeOf<KeyBlob>());
}
}
}

View file

@ -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<AesKey>());
}
[Fact]
public static void AesXtsKeySizeIs0x20()
{
Assert.Equal(0x20, Unsafe.SizeOf<AesXtsKey>());
}
[Fact]
public static void AesIvSizeIs0x10()
{
Assert.Equal(0x10, Unsafe.SizeOf<AesIv>());
}
[Fact]
public static void AesCmacSizeIs0x10()
{
Assert.Equal(0x10, Unsafe.SizeOf<Crypto.AesCmac>());
}
}
}

View file

@ -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<AccessKey>());
}
}
}