mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add a new KeySet class
This commit is contained in:
parent
61ce892697
commit
f7030aef4f
16 changed files with 1385 additions and 0 deletions
93
src/LibHac/Boot/KeyBlob.cs
Normal file
93
src/LibHac/Boot/KeyBlob.cs
Normal 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();
|
||||
}
|
||||
}
|
33
src/LibHac/Common/FixedArrays/Array12.cs
Normal file
33
src/LibHac/Common/FixedArrays/Array12.cs
Normal 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;
|
||||
}
|
||||
}
|
23
src/LibHac/Common/FixedArrays/Array2.cs
Normal file
23
src/LibHac/Common/FixedArrays/Array2.cs
Normal 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;
|
||||
}
|
||||
}
|
24
src/LibHac/Common/FixedArrays/Array3.cs
Normal file
24
src/LibHac/Common/FixedArrays/Array3.cs
Normal 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;
|
||||
}
|
||||
}
|
53
src/LibHac/Common/FixedArrays/Array32.cs
Normal file
53
src/LibHac/Common/FixedArrays/Array32.cs
Normal 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;
|
||||
}
|
||||
}
|
25
src/LibHac/Common/FixedArrays/Array4.cs
Normal file
25
src/LibHac/Common/FixedArrays/Array4.cs
Normal 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;
|
||||
}
|
||||
}
|
475
src/LibHac/Common/Keys/ExternalKeyReader.cs
Normal file
475
src/LibHac/Common/Keys/ExternalKeyReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
438
src/LibHac/Common/Keys/KeySet.cs
Normal file
438
src/LibHac/Common/Keys/KeySet.cs
Normal 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 < 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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
118
src/LibHac/Crypto/KeyTypes.cs
Normal file
118
src/LibHac/Crypto/KeyTypes.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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]));
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
22
tests/LibHac.Tests/Boot/TypeSizeTests.cs
Normal file
22
tests/LibHac.Tests/Boot/TypeSizeTests.cs
Normal 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>());
|
||||
}
|
||||
}
|
||||
}
|
34
tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs
Normal file
34
tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs
Normal 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>());
|
||||
}
|
||||
}
|
||||
}
|
16
tests/LibHac.Tests/Spl/TypeSizeTests.cs
Normal file
16
tests/LibHac.Tests/Spl/TypeSizeTests.cs
Normal 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>());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue