From 754df0dcd9492807481fb74e8e8f9239eec06ebb Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 31 Jan 2023 23:12:48 -0700 Subject: [PATCH] Tweak how the eticket rsa key pair is handled --- build/CodeGen/Stage2/KeysCodeGen.cs | 4 +- src/LibHac/Common/Keys/DefaultKeySet.Empty.cs | 2 + src/LibHac/Common/Keys/DefaultKeySet.cs | 12 ++- src/LibHac/Common/Keys/KeySet.cs | 27 ++++++- src/LibHac/Crypto/KeyTypes.cs | 32 +++----- src/LibHac/Tools/Es/Ticket.cs | 11 +-- .../CryptoTests/TypeLayoutTests.cs | 74 +++++++++++++++++++ .../LibHac.Tests/CryptoTests/TypeSizeTests.cs | 33 --------- 8 files changed, 129 insertions(+), 66 deletions(-) create mode 100644 tests/LibHac.Tests/CryptoTests/TypeLayoutTests.cs delete mode 100644 tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs index ecb8abdf..add61f45 100644 --- a/build/CodeGen/Stage2/KeysCodeGen.cs +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -37,6 +37,7 @@ public static class KeysCodeGen sb.AppendLine("internal static partial class DefaultKeySet"); sb.AppendLineAndIncrease("{"); + BuildArray(sb, "TsecSecrets", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.TsecSecrets)); BuildArray(sb, "RootKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.RootKeysDev)); BuildArray(sb, "RootKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.RootKeysProd)); BuildArray(sb, "KeySeeds", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.KeySeeds)); @@ -50,6 +51,7 @@ public static class KeysCodeGen BuildArray(sb, "RsaSigningKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.RsaSigningKeysDev)); BuildArray(sb, "RsaSigningKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.RsaSigningKeysProd)); BuildArray(sb, "RsaKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.RsaKeys)); + BuildArray(sb, "DeviceRsaKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct.DeviceRsaKeys)); sb.DecreaseAndAppend("}"); @@ -147,7 +149,7 @@ public static class KeysCodeGen private static ReadOnlySpan StandardPublicExponent => new byte[] { - 0x01, 0x00, 0x01 + 0x00, 0x01, 0x00, 0x01 }; private static ReadOnlySpan BetaNca0Modulus => new byte[] diff --git a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs index 944293b1..82e4fe59 100644 --- a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs +++ b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs @@ -4,6 +4,7 @@ namespace LibHac.Common.Keys; internal static partial class DefaultKeySet { + private static ReadOnlySpan TsecSecrets => new byte[] { }; private static ReadOnlySpan RootKeysDev => new byte[] { }; private static ReadOnlySpan RootKeysProd => new byte[] { }; private static ReadOnlySpan KeySeeds => new byte[] { }; @@ -17,4 +18,5 @@ internal static partial class DefaultKeySet private static ReadOnlySpan RsaSigningKeysDev => new byte[] { }; private static ReadOnlySpan RsaSigningKeysProd => new byte[] { }; private static ReadOnlySpan RsaKeys => new byte[] { }; + private static ReadOnlySpan DeviceRsaKeys => new byte[] { }; } \ No newline at end of file diff --git a/src/LibHac/Common/Keys/DefaultKeySet.cs b/src/LibHac/Common/Keys/DefaultKeySet.cs index 9d1a0c8d..32e9fca1 100644 --- a/src/LibHac/Common/Keys/DefaultKeySet.cs +++ b/src/LibHac/Common/Keys/DefaultKeySet.cs @@ -17,6 +17,11 @@ internal static partial class DefaultKeySet // Fill the key set with any key structs included in the library. // This is split into multiple parts so the binary size isn't increased when providing only some keys. + if (TsecSecrets.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct.TsecSecrets = SpanHelpers.AsReadOnlyStruct(TsecSecrets); + } + if (RootKeysDev.Length == Unsafe.SizeOf()) { keySet.KeyStruct.RootKeysDev = SpanHelpers.AsReadOnlyStruct(RootKeysDev); @@ -82,6 +87,11 @@ internal static partial class DefaultKeySet keySet.KeyStruct.RsaKeys = SpanHelpers.AsReadOnlyStruct(RsaKeys); } + if (DeviceRsaKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct.DeviceRsaKeys = SpanHelpers.AsReadOnlyStruct(DeviceRsaKeys); + } + return keySet; } @@ -183,7 +193,7 @@ internal static partial class DefaultKeySet keys.Add(new KeyInfo(280, Type.CommonRoot, "eticket_rsa_kek", (set, _) => set.ETicketRsaKek)); keys.Add(new KeyInfo(281, Type.CommonRoot, "ssl_rsa_kek", (set, _) => set.SslRsaKek)); - keys.Add(new KeyInfo(282, Type.DeviceDrvd, "eticket_rsa_keypair", (set, _) => set.ETicketRsaKeyPair)); + keys.Add(new KeyInfo(282, Type.DeviceDrvd, "eticket_rsa_keypair", (set, _) => SpanHelpers.AsByteSpan(ref set.ETicketRsaKey))); keys.Add(new KeyInfo(290, Type.CommonDrvd, "key_area_key_application", 0, KeyRevisionCount, (set, i) => set.KeyAreaKeys[i][0])); keys.Add(new KeyInfo(300, Type.CommonDrvd, "key_area_key_ocean", 0, KeyRevisionCount, (set, i) => set.KeyAreaKeys[i][1])); diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index e9ba29f7..a474305c 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -46,6 +46,7 @@ public class KeySet private ref DerivedDeviceKeys DerivedDeviceKeys => ref _mode == Mode.Dev ? ref _keys.DerivedDeviceKeysDev : ref _keys.DerivedDeviceKeysProd; private ref RsaSigningKeys RsaSigningKeys => ref _mode == Mode.Dev ? ref _keys.RsaSigningKeysDev : ref _keys.RsaSigningKeysProd; private ref RsaKeys RsaKeys => ref _keys.RsaKeys; + private ref DeviceRsaKeys DeviceRsaKeys => ref _keys.DeviceRsaKeys; private ref RsaSigningKeyParameters RsaSigningKeyParams => ref _mode == Mode.Dev ? ref _rsaSigningKeyParamsDev @@ -124,12 +125,12 @@ public class KeySet public Span AcidSigningKeys => RsaSigningKeys.AcidSigningKeys.Items; public ref RsaKey Package2SigningKey => ref RsaSigningKeys.Package2SigningKey; public ref RsaFullKey BetaNca0KeyAreaKey => ref RsaKeys.BetaNca0KeyAreaKey; + public ref RsaKeyPair ETicketRsaKey => ref DeviceRsaKeys.ETicketRsaKey; private RsaSigningKeyParameters _rsaSigningKeyParamsDev; private RsaSigningKeyParameters _rsaSigningKeyParamsProd; private RsaKeyParameters _rsaKeyParams; - public ref RsaKeyPair ETicketRsaKeyPair => ref DerivedDeviceKeys.ETicketRsaKeyPair; public Span NcaHeaderSigningKeyParams { @@ -196,6 +197,22 @@ public class KeySet } } + public ref RSAParameters ETicketRsaKeyParams + { + get + { + ref Optional keys = ref _rsaKeyParams.ETicketRsaKey; + + if (!keys.HasValue && !ETicketRsaKey.PublicExponent.ItemsRo.IsZeros()) + { + RSAParameters rsaParams = Rsa.RecoverParameters(ETicketRsaKey.Modulus, ETicketRsaKey.PublicExponent, ETicketRsaKey.PrivateExponent); + keys.Set(rsaParams); + } + + return ref keys.Value; + } + } + public void SetSdSeed(ReadOnlySpan sdSeed) { if (sdSeed.Length != 0x10) @@ -270,6 +287,7 @@ public class KeySet private struct RsaKeyParameters { public Optional BetaNca0KeyAreaKey; + public Optional ETicketRsaKey; } } @@ -289,6 +307,7 @@ public struct AllKeys public RsaSigningKeys RsaSigningKeysDev; public RsaSigningKeys RsaSigningKeysProd; public RsaKeys RsaKeys; + public DeviceRsaKeys DeviceRsaKeys; } public struct TsecSecrets @@ -389,7 +408,6 @@ public struct DerivedDeviceKeys public Array2 DeviceUniqueSaveMacKeys; public AesKey SeedUniqueSaveMacKey; public Array3 SdCardEncryptionKeys; - public RsaKeyPair ETicketRsaKeyPair; } public struct RsaSigningKeys @@ -402,4 +420,9 @@ public struct RsaSigningKeys public struct RsaKeys { public RsaFullKey BetaNca0KeyAreaKey; +} + +public struct DeviceRsaKeys +{ + public RsaKeyPair ETicketRsaKey; } \ No newline at end of file diff --git a/src/LibHac/Crypto/KeyTypes.cs b/src/LibHac/Crypto/KeyTypes.cs index d2a4a58c..5b5e086a 100644 --- a/src/LibHac/Crypto/KeyTypes.cs +++ b/src/LibHac/Crypto/KeyTypes.cs @@ -115,43 +115,31 @@ public struct AesCmac #endif } +[StructLayout(LayoutKind.Sequential)] public struct RsaFullKey { - // ReSharper disable once UnassignedField.Global public Array256 PrivateExponent; + public Array256 Modulus; + public Array4 PublicExponent; public Array128 Dp; public Array128 Dq; - public Array3 PublicExponent; public Array128 InverseQ; - public Array256 Modulus; public Array128 P; public Array128 Q; } +[StructLayout(LayoutKind.Sequential)] public struct RsaKey { public Array256 Modulus; - public Array3 PublicExponent; + public Array4 PublicExponent; } -[StructLayout(LayoutKind.Explicit, Size = Size)] +[StructLayout(LayoutKind.Sequential)] public struct RsaKeyPair { - private const int Size = 0x210; - - [FieldOffset(0)] private byte _byte; - [FieldOffset(0)] private ulong _ulong; - - [FieldOffset(0)] public Array256 PrivateExponent; - [FieldOffset(0x100)] public Array256 Modulus; - [FieldOffset(0x200)] public Array4 PublicExponent; - [FieldOffset(0x204)] public Array12 Reserved; - - public Span Data => SpanHelpers.CreateSpan(ref _byte, Size); - public readonly ReadOnlySpan DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size); - - public static implicit operator Span(in RsaKeyPair value) => Unsafe.AsRef(in value).Data; - public static implicit operator ReadOnlySpan(in RsaKeyPair value) => value.DataRo; - - public readonly override string ToString() => DataRo.ToHexString(); + public Array256 PrivateExponent; + public Array256 Modulus; + public Array4 PublicExponent; + public Array12 Reserved; } \ No newline at end of file diff --git a/src/LibHac/Tools/Es/Ticket.cs b/src/LibHac/Tools/Es/Ticket.cs index bf3474db..19c6dc45 100644 --- a/src/LibHac/Tools/Es/Ticket.cs +++ b/src/LibHac/Tools/Es/Ticket.cs @@ -1,9 +1,7 @@ using System; using System.IO; -using System.Security.Cryptography; using LibHac.Common; using LibHac.Common.Keys; -using LibHac.Crypto; using LibHac.Tools.Crypto; using LibHac.Util; @@ -159,11 +157,10 @@ public class Ticket return commonKey; } - RSAParameters rsaParameters = Rsa.RecoverParameters( - keySet.ETicketRsaKeyPair.Modulus, - keySet.ETicketRsaKeyPair.PublicExponent, - keySet.ETicketRsaKeyPair.PrivateExponent); - return CryptoOld.DecryptRsaOaep(TitleKeyBlock, rsaParameters); + if (keySet.ETicketRsaKey.PublicExponent.ItemsRo.IsZeros()) + return null; + + return CryptoOld.DecryptRsaOaep(TitleKeyBlock, keySet.ETicketRsaKeyParams); } } diff --git a/tests/LibHac.Tests/CryptoTests/TypeLayoutTests.cs b/tests/LibHac.Tests/CryptoTests/TypeLayoutTests.cs new file mode 100644 index 00000000..776c6643 --- /dev/null +++ b/tests/LibHac.Tests/CryptoTests/TypeLayoutTests.cs @@ -0,0 +1,74 @@ +using System.Runtime.CompilerServices; +using LibHac.Crypto; +using Xunit; +using static LibHac.Tests.Common.Layout; + +namespace LibHac.Tests.CryptoTests; + +public class TypeLayoutTests +{ + [Fact] + public static void RsaFullKey_Layout() + { + var s = new RsaFullKey(); + + Assert.Equal(0x484, Unsafe.SizeOf()); + + Assert.Equal(0x000, GetOffset(in s, in s.PrivateExponent)); + Assert.Equal(0x100, GetOffset(in s, in s.Modulus)); + Assert.Equal(0x200, GetOffset(in s, in s.PublicExponent)); + Assert.Equal(0x204, GetOffset(in s, in s.Dp)); + Assert.Equal(0x284, GetOffset(in s, in s.Dq)); + Assert.Equal(0x304, GetOffset(in s, in s.InverseQ)); + Assert.Equal(0x384, GetOffset(in s, in s.P)); + Assert.Equal(0x404, GetOffset(in s, in s.Q)); + } + + [Fact] + public static void RsaKeyPair_Layout() + { + var s = new RsaKeyPair(); + + Assert.Equal(0x210, Unsafe.SizeOf()); + + Assert.Equal(0x000, GetOffset(in s, in s.PrivateExponent)); + Assert.Equal(0x100, GetOffset(in s, in s.Modulus)); + Assert.Equal(0x200, GetOffset(in s, in s.PublicExponent)); + Assert.Equal(0x204, GetOffset(in s, in s.Reserved)); + } + + [Fact] + public static void RsaKey_Layout() + { + var s = new RsaKey(); + + Assert.Equal(0x104, Unsafe.SizeOf()); + + Assert.Equal(0x000, GetOffset(in s, in s.Modulus)); + Assert.Equal(0x100, GetOffset(in s, in s.PublicExponent)); + } + + [Fact] + public static void AesKey_Layout() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + + [Fact] + public static void AesXtsKey_Layout() + { + Assert.Equal(0x20, Unsafe.SizeOf()); + } + + [Fact] + public static void AesIv_Layout() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } + + [Fact] + public static void AesCmac_Layout() + { + Assert.Equal(0x10, Unsafe.SizeOf()); + } +} \ No newline at end of file diff --git a/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs b/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs deleted file mode 100644 index b8cea75e..00000000 --- a/tests/LibHac.Tests/CryptoTests/TypeSizeTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ReSharper disable InconsistentNaming -using System.Runtime.CompilerServices; -using LibHac.Crypto; -using Xunit; - -namespace LibHac.Tests.CryptoTests; - -public class TypeSizeTests -{ - [Fact] - public static void AesKeySizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf()); - } - - [Fact] - public static void AesXtsKeySizeIs0x20() - { - Assert.Equal(0x20, Unsafe.SizeOf()); - } - - [Fact] - public static void AesIvSizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf()); - } - - [Fact] - public static void AesCmacSizeIs0x10() - { - Assert.Equal(0x10, Unsafe.SizeOf()); - } -} \ No newline at end of file