diff --git a/.gitignore b/.gitignore index 961d8cf4..aecebd82 100644 --- a/.gitignore +++ b/.gitignore @@ -267,4 +267,7 @@ global.json !tests/LibHac.Tests/CryptoTests/TestVectors/* **/DisasmoBin/ +# Files generated at build time +ResultNameResolver.Generated.cs +DefaultKeySet.Generated.cs ResultNameResolver.Generated.cs \ No newline at end of file diff --git a/LibHac.sln b/LibHac.sln index 1a9b8892..ce921fd1 100644 --- a/LibHac.sln +++ b/LibHac.sln @@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibHac.Tests", "tests\LibHa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{C7150117-90B8-4083-8141-BBC35C9F44F6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_buildCodeGen", "build\CodeGen\_buildCodeGen.csproj", "{93B175E1-F12F-4A8C-85CA-CAC74691102A}" + ProjectSection(ProjectDependencies) = postProject + {FFCA6C31-D9D4-4ED8-A06D-0CC6B94422B8} = {FFCA6C31-D9D4-4ED8-A06D-0CC6B94422B8} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +36,8 @@ Global {679C89BD-5FDF-4CC2-9129-ABABD759035B}.Release|Any CPU.Build.0 = Release|Any CPU {C7150117-90B8-4083-8141-BBC35C9F44F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7150117-90B8-4083-8141-BBC35C9F44F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93B175E1-F12F-4A8C-85CA-CAC74691102A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93B175E1-F12F-4A8C-85CA-CAC74691102A}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/Build.cs b/build/Build.cs index 69106473..bf44a41a 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -8,7 +8,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using ICSharpCode.SharpZipLib.Zip; -using LibHacBuild.CodeGen; +using LibHacBuild.CodeGen.Stage1; using Nuke.Common; using Nuke.Common.CI.AppVeyor; using Nuke.Common.Git; @@ -54,6 +54,8 @@ namespace LibHacBuild Project LibHacTestProject => _solution.GetProject("LibHac.Tests").NotNull(); Project HactoolnetProject => _solution.GetProject("hactoolnet").NotNull(); + Project CodeGenProject => _solution.GetProject("_buildCodeGen").NotNull(); + private bool HasGitDir { get; set; } private string NativeRuntime { get; set; } @@ -196,6 +198,7 @@ namespace LibHacBuild .Executes(() => { ResultCodeGen.Run(); + RunCodegenStage2(); }); Target Compile => _ => _ @@ -668,5 +671,24 @@ namespace LibHacBuild { return XmlTasks.XmlPeekSingle(LibHacProject.Path, "/Project/PropertyGroup/VersionPrefix", null); } + + public void RunCodegenStage2() + { + Logger.Normal("\nBuilding stage 2 codegen project."); + + DotNetRunSettings settings = new DotNetRunSettings() + .SetProjectFile(CodeGenProject.Path); + // .SetLogOutput(false); + + try + { + DotNetRun(settings); + Logger.Normal(); + } + catch (ProcessException) + { + Logger.Error("\nError running stage 2 codegen. Skipping...\n"); + } + } } } diff --git a/build/CodeGen/Common.cs b/build/CodeGen/Common.cs new file mode 100644 index 00000000..8960dd7a --- /dev/null +++ b/build/CodeGen/Common.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Nuke.Common; + +namespace LibHacBuild.CodeGen +{ + public static class Common + { + public static string GetHeader() + { + string nl = Environment.NewLine; + return + "//-----------------------------------------------------------------------------" + nl + + "// This file was automatically generated." + nl + + "// Changes to this file will be lost when the file is regenerated." + nl + + "//" + nl + + "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + + "// repo and run the build script." + nl + + "//" + nl + + "// The script can be run with the \"codegen\" option to run only the" + nl + + "// code generation portion of the build." + nl + + "//-----------------------------------------------------------------------------"; + } + + // Write the file only if it has changed + // Preserve the UTF-8 BOM usage if the file already exists + public static void WriteOutput(string relativePath, string text) + { + if (string.IsNullOrWhiteSpace(relativePath)) + return; + + string rootPath = FindProjectDirectory(); + string fullPath = Path.Combine(rootPath, relativePath); + + // Default is true because Visual Studio saves .cs files with the BOM by default + bool hasBom = true; + byte[] bom = Encoding.UTF8.GetPreamble(); + byte[] oldFile = null; + + if (File.Exists(fullPath)) + { + oldFile = File.ReadAllBytes(fullPath); + + if (oldFile.Length >= 3) + hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); + } + + // Make line endings the same on Windows and Unix + if (Environment.NewLine == "\n") + { + text = text.Replace("\n", "\r\n"); + } + + byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); + + if (oldFile?.SequenceEqual(newFile) == true) + { + Logger.Normal($"{relativePath} is already up-to-date"); + return; + } + + Logger.Normal($"Generated file {relativePath}"); + File.WriteAllBytes(fullPath, newFile); + } + + public static Stream GetResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + string path = $"LibHacBuild.CodeGen.{name}"; + + Stream stream = assembly.GetManifestResourceStream(path); + if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); + + return stream; + } + + public static string FindProjectDirectory() + { + string currentDir = Environment.CurrentDirectory; + + while (currentDir != null) + { + if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) + { + break; + } + + currentDir = Path.GetDirectoryName(currentDir); + } + + if (currentDir == null) + throw new DirectoryNotFoundException("Unable to find project directory."); + + return Path.Combine(currentDir, "src"); + } + } +} diff --git a/build/CodeGen/IncludedKeys.txt b/build/CodeGen/IncludedKeys.txt new file mode 100644 index 00000000..3441482d --- /dev/null +++ b/build/CodeGen/IncludedKeys.txt @@ -0,0 +1,56 @@ +keyblob_mac_key_source = 59C7FB6FBE9BBE87656B15C0537336A5 +keyblob_key_source_00 = DF206F594454EFDC7074483B0DED9FD3 +keyblob_key_source_01 = 0C25615D684CEB421C2379EA822512AC +keyblob_key_source_02 = 337685EE884AAE0AC28AFD7D63C0433B +keyblob_key_source_03 = 2D1F4880EDECED3E3CF248B5657DF7BE +keyblob_key_source_04 = BB5A01F988AFF5FC6CFF079E133C3980 +keyblob_key_source_05 = D8CCE1266A353FCC20F32D3B517DE9C0 + +master_kek_source_06 = 374B772959B4043081F6E58C6D36179A +master_kek_source_07 = 9A3EA9ABFD56461C9BF6487F5CFA095C +master_kek_source_08 = DEDCE339308816F8AE97ADEC642D4141 +master_kek_source_09 = 1AEC11822B32387A2BEDBA01477E3B67 +master_kek_source_0a = 303F027ED838ECD7932534B530EBCA7A + +mariko_master_kek_source_06 = 1E80B8173EC060AA11BE1A4AA66FE4AE +mariko_master_kek_source_07 = 940867BD0A00388411D31ADBDD8DF18A +mariko_master_kek_source_08 = 5C24E3B8B4F700C23CFD0ACE13C3DC23 +mariko_master_kek_source_09 = 8669F00987C805AEB57B4874DE62A613 +mariko_master_kek_source_0a = 0E440CEDB436C03FAA1DAEBF62B10982 + +mariko_master_kek_source_dev_0a = F937CF9ABD86BBA99C9E03C4FCBC3BCE + +master_key_source = D8A2410AC6C59001C61D6A267C513F3C + +package2_key_source = FB8B6A9C7900C849EFD24D854D30A0C7 + +bis_kek_source = 34C1A0C48258F8B4FA9E5E6ADAFC7E4F +bis_key_source_00 = F83F386E2CD2CA32A89AB9AA29BFC7487D92B03AA8BFDEE1A74C3B6E35CB7106 +bis_key_source_01 = 41003049DDCCC065647A7EB41EED9C5F44424EDAB49DFCD98777249ADC9F7CA4 +bis_key_source_02 = 52C2E9EB09E3EE2932A10C1FB6A0926C4D12E14B2A474C1C09CB0359F015F4E4 +bis_key_source_03 = 52C2E9EB09E3EE2932A10C1FB6A0926C4D12E14B2A474C1C09CB0359F015F4E4 + +per_console_key_source = 4F025F0EB66D110EDC327D4186C2F478 +retail_specific_aes_key_source = E2D6B87A119CB880E822888A46FBA195 +aes_kek_generation_source = 4D870986C45D20722FBA1053DA92E8A9 +aes_key_generation_source = 89615EE05C31B6805FE58F3DA24F7AA8 +titlekek_source = 1EDC7B3B60E6B4D878B81715985E629B + +header_kek_source = 1F12913A4ACBF00D4CDE3AF6D523882A +header_key_source = 5A3ED84FDEC0D82631F7E25D197BF5D01C9B7BFAF628183D71F64D73F150B9D2 + +key_area_key_application_source = 7F59971E629F36A13098066F2144C30D +key_area_key_ocean_source = 327D36085AD1758DAB4E6FBAA555D882 +key_area_key_system_source = 8745F1BBA6BE79647D048BA67B5FDA4A + +save_mac_kek_source = D89C236EC9124E43C82B038743F9CF1B +save_mac_key_source_00 = E4CD3D4AD50F742845A487E5A063EA1F +save_mac_key_source_01 = EC249895656ADF4AA066B9880AC82C4C + +save_mac_sd_card_kek_source = 0489EF5D326E1A59C4B7AB8C367AAB17 +save_mac_sd_card_key_source = 6F645947C56146F9FFA045D595332918 + +sd_card_kek_source = 88358D9C629BA1A00147DBE0621B5432 +sd_card_save_key_source = 2449B722726703A81965E6E3EA582FDD9A951517B16E8F7F1F68263152EA296A +sd_card_nca_key_source = 5841A284935B56278B8E1FC518E99F2B67C793F0F24FDED075495DCA006D99C2 +sd_card_custom_storage_key_source = 370C345E12E4CEFE21B58E64DB52AF354F2CA5A3FC999A47C03EE004485B2FD0 \ No newline at end of file diff --git a/build/CodeGen/ResultCodegen.cs b/build/CodeGen/Stage1/ResultCodegen.cs similarity index 83% rename from build/CodeGen/ResultCodegen.cs rename to build/CodeGen/Stage1/ResultCodegen.cs index 184ad316..936f42c7 100644 --- a/build/CodeGen/ResultCodegen.cs +++ b/build/CodeGen/Stage1/ResultCodegen.cs @@ -4,15 +4,14 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using CsvHelper; using CsvHelper.Configuration; -using Nuke.Common; +using static LibHacBuild.CodeGen.Common; -namespace LibHacBuild.CodeGen +namespace LibHacBuild.CodeGen.Stage1 { public static class ResultCodeGen { @@ -282,63 +281,6 @@ namespace LibHacBuild.CodeGen return doc; } - private static string GetHeader() - { - string nl = Environment.NewLine; - return - "//-----------------------------------------------------------------------------" + nl + - "// This file was automatically generated." + nl + - "// Changes to this file will be lost when the file is regenerated." + nl + - "//" + nl + - "// To change this file, modify /build/CodeGen/results.csv at the root of this" + nl + - "// repo and run the build script." + nl + - "//" + nl + - "// The script can be run with the \"codegen\" option to run only the" + nl + - "// code generation portion of the build." + nl + - "//-----------------------------------------------------------------------------"; - } - - // Write the file only if it has changed - // Preserve the UTF-8 BOM usage if the file already exists - private static void WriteOutput(string relativePath, string text) - { - if (string.IsNullOrWhiteSpace(relativePath)) - return; - - string rootPath = FindProjectDirectory(); - string fullPath = Path.Combine(rootPath, relativePath); - - // Default is true because Visual Studio saves .cs files with the BOM by default - bool hasBom = true; - byte[] bom = Encoding.UTF8.GetPreamble(); - byte[] oldFile = null; - - if (File.Exists(fullPath)) - { - oldFile = File.ReadAllBytes(fullPath); - - if (oldFile.Length >= 3) - hasBom = oldFile.AsSpan(0, 3).SequenceEqual(bom); - } - - // Make line endings the same on Windows and Unix - if (Environment.NewLine == "\n") - { - text = text.Replace("\n", "\r\n"); - } - - byte[] newFile = (hasBom ? bom : new byte[0]).Concat(Encoding.UTF8.GetBytes(text)).ToArray(); - - if (oldFile?.SequenceEqual(newFile) == true) - { - Logger.Normal($"{relativePath} is already up-to-date"); - return; - } - - Logger.Normal($"Generated file {relativePath}"); - File.WriteAllBytes(fullPath, newFile); - } - private static byte[] BuildArchive(ModuleInfo[] modules) { var builder = new ResultArchiveBuilder(); @@ -409,37 +351,6 @@ namespace LibHacBuild.CodeGen } } - private static Stream GetResource(string name) - { - var assembly = Assembly.GetExecutingAssembly(); - string path = $"LibHacBuild.CodeGen.{name}"; - - Stream stream = assembly.GetManifestResourceStream(path); - if (stream == null) throw new FileNotFoundException($"Resource {path} was not found."); - - return stream; - } - - private static string FindProjectDirectory() - { - string currentDir = Environment.CurrentDirectory; - - while (currentDir != null) - { - if (File.Exists(Path.Combine(currentDir, "LibHac.sln"))) - { - break; - } - - currentDir = Path.GetDirectoryName(currentDir); - } - - if (currentDir == null) - throw new DirectoryNotFoundException("Unable to find project directory."); - - return Path.Combine(currentDir, "src"); - } - private static int EstimateCilSize(ResultInfo result) { int size = 0; diff --git a/build/CodeGen/Stage2/KeysCodeGen.cs b/build/CodeGen/Stage2/KeysCodeGen.cs new file mode 100644 index 00000000..f969b4a3 --- /dev/null +++ b/build/CodeGen/Stage2/KeysCodeGen.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Crypto; +using static LibHacBuild.CodeGen.Common; + +namespace LibHacBuild.CodeGen.Stage2 +{ + public static class KeysCodeGen + { + private static string InputMainKeyFileName = "IncludedKeys.txt"; + private static string GeneratedFilePath = "LibHac/Common/Keys/DefaultKeySet.Generated.cs"; + + public static void Run() + { + KeySet keySet = CreateKeySet(); + + WriteOutput(GeneratedFilePath, BuildDefaultKeySetFile(keySet)); + } + + private static string BuildDefaultKeySetFile(KeySet keySet) + { + var sb = new IndentingStringBuilder(); + + sb.AppendLine(GetHeader()); + sb.AppendLine(); + + sb.AppendLine("using System;"); + sb.AppendLine(); + + sb.AppendLine("namespace LibHac.Common.Keys"); + sb.AppendLineAndIncrease("{"); + + sb.AppendLine("internal static partial class DefaultKeySet"); + sb.AppendLineAndIncrease("{"); + + 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)); + BuildArray(sb, "StoredKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysDev)); + BuildArray(sb, "StoredKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._storedKeysProd)); + BuildArray(sb, "DerivedKeysDev", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysDev)); + BuildArray(sb, "DerivedKeysProd", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._derivedKeysProd)); + BuildArray(sb, "DeviceKeys", SpanHelpers.AsReadOnlyByteSpan(in keySet.KeyStruct._deviceKeys)); + 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)); + + sb.DecreaseAndAppendLine("}"); + sb.DecreaseAndAppendLine("}"); + + return sb.ToString(); + } + + private static void BuildArray(IndentingStringBuilder sb, string name, ReadOnlySpan data) + { + sb.AppendSpacerLine(); + sb.Append($"private static ReadOnlySpan {name} => new byte[]"); + + if (data.IsEmpty()) + { + sb.AppendLine(" { };"); + return; + } + + sb.AppendLine(); + sb.AppendLineAndIncrease("{"); + + for (int i = 0; i < data.Length; i++) + { + if (i % 16 != 0) sb.Append(" "); + sb.Append($"0x{data[i]:x2}"); + + if (i != data.Length - 1) + { + sb.Append(","); + if (i % 16 == 15) sb.AppendLine(); + } + } + + sb.AppendLine(); + sb.DecreaseAndAppendLine("};"); + } + + private static KeySet CreateKeySet() + { + var keySet = new KeySet(); + + // Populate the key set with all the keys in IncludedKeys.txt + using (var reader = new StreamReader(Common.GetResource(InputMainKeyFileName))) + { + List list = ExternalKeyReader.CreateKeyList(); + ExternalKeyReader.ReadMainKeys(keySet, reader, list); + } + + // Recover all the RSA key parameters and write the key to the key set + RSAParameters betaNca0Params = + Rsa.RecoverParameters(BetaNca0Modulus, StandardPublicExponent, BetaNca0Exponent); + + betaNca0Params.D.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PrivateExponent.Data); + betaNca0Params.DP.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dp.Data); + betaNca0Params.DQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Dq.Data); + betaNca0Params.Exponent.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.PublicExponent.Data); + betaNca0Params.InverseQ.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.InverseQ.Data); + betaNca0Params.Modulus.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Modulus.Data); + betaNca0Params.P.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.P.Data); + betaNca0Params.Q.AsSpan().CopyTo(keySet.BetaNca0KeyAreaKey.Q.Data); + + // First populate the prod RSA keys + keySet.SetMode(KeySet.Mode.Prod); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Prod.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Prod.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Prod.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Prod.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusProd.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + // Populate the dev RSA keys + keySet.SetMode(KeySet.Mode.Dev); + + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.NcaHeaderSigningKeys[1].PublicExponent.Data); + NcaHdrFixedKeyModulus0Dev.CopyTo(keySet.NcaHeaderSigningKeys[0].Modulus.Data); + NcaHdrFixedKeyModulus1Dev.CopyTo(keySet.NcaHeaderSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[0].PublicExponent.Data); + StandardPublicExponent.CopyTo(keySet.AcidSigningKeys[1].PublicExponent.Data); + AcidFixedKeyModulus0Dev.CopyTo(keySet.AcidSigningKeys[0].Modulus.Data); + AcidFixedKeyModulus1Dev.CopyTo(keySet.AcidSigningKeys[1].Modulus.Data); + + StandardPublicExponent.CopyTo(keySet.Package2SigningKey.PublicExponent.Data); + Package2FixedKeyModulusDev.CopyTo(keySet.Package2SigningKey.Modulus.Data); + + return keySet; + } + + private static ReadOnlySpan StandardPublicExponent => new byte[] + { + 0x01, 0x00, 0x01 + }; + + private static ReadOnlySpan BetaNca0Modulus => new byte[] + { + 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, + 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, + 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, + 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, + 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, + 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, + 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, + 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, + 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, + 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, + 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, + 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, + 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, + 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, + 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, + 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 + }; + + private static ReadOnlySpan BetaNca0Exponent => new byte[] + { + 0x3C, 0x66, 0x37, 0x44, 0x26, 0xAC, 0x63, 0xD1, 0x30, 0xE6, 0xD4, 0x68, 0xF9, 0xC4, 0xF0, 0xFA, + 0x03, 0x16, 0xC6, 0x32, 0x81, 0xB0, 0x94, 0xC9, 0xF1, 0x26, 0xC5, 0xE2, 0x2D, 0xF4, 0xB6, 0x3E, + 0xEB, 0x3D, 0x82, 0x18, 0xA7, 0xC9, 0x8B, 0xD1, 0x03, 0xDD, 0xF2, 0x09, 0x60, 0x02, 0x12, 0xFA, + 0x8F, 0xE1, 0xA0, 0xF2, 0x82, 0xDC, 0x3D, 0x74, 0x01, 0xBA, 0x02, 0xF0, 0x8D, 0x5B, 0x89, 0x00, + 0xD1, 0x0B, 0x8F, 0x1C, 0x4A, 0xF3, 0x6E, 0x96, 0x8E, 0x03, 0x19, 0xF0, 0x19, 0x66, 0x58, 0xE9, + 0xB2, 0x24, 0x37, 0x4B, 0x0A, 0xC6, 0x06, 0x91, 0xBA, 0x92, 0x64, 0x13, 0x5F, 0xF1, 0x4A, 0xBC, + 0xAB, 0x61, 0xE5, 0x20, 0x08, 0x62, 0xB7, 0x8E, 0x4D, 0x20, 0x30, 0xA7, 0x42, 0x0B, 0x53, 0x58, + 0xEC, 0xBB, 0x70, 0xCB, 0x2A, 0x56, 0xC7, 0x0C, 0x8B, 0x89, 0xFB, 0x47, 0x6E, 0x58, 0x9C, 0xDD, + 0xB2, 0xE5, 0x4F, 0x49, 0x52, 0x0B, 0xD9, 0x96, 0x30, 0x8D, 0xDE, 0xC9, 0x0F, 0x6A, 0x82, 0xC7, + 0xE8, 0x20, 0xB6, 0xB3, 0x95, 0xDD, 0xEB, 0xDF, 0xF7, 0x25, 0x23, 0x6B, 0xF8, 0x5B, 0xD4, 0x81, + 0x7A, 0xBC, 0x94, 0x13, 0x30, 0x59, 0x28, 0xC8, 0xC9, 0x3A, 0x5D, 0xCC, 0x8D, 0xFD, 0x1A, 0xE1, + 0xCB, 0xA4, 0x1D, 0xD4, 0x45, 0xF1, 0xBF, 0x87, 0x6C, 0x0E, 0xB1, 0x44, 0xC7, 0x88, 0x62, 0x2B, + 0x43, 0xAD, 0x75, 0xE6, 0x69, 0xFF, 0xD3, 0x39, 0xF5, 0x7F, 0x2A, 0xA2, 0x5F, 0x7A, 0x5E, 0xE6, + 0xEF, 0xCB, 0x2F, 0x2C, 0x90, 0xE6, 0x4B, 0x2D, 0x94, 0x62, 0xE8, 0xEC, 0x54, 0x7B, 0x94, 0xB7, + 0xFB, 0x72, 0x05, 0xFB, 0xB3, 0x23, 0xCA, 0xF8, 0xD4, 0x5C, 0xF6, 0xAC, 0x7D, 0xEC, 0x47, 0xC6, + 0xD3, 0x22, 0x5D, 0x7C, 0x15, 0xDD, 0x48, 0xE9, 0xBF, 0xA8, 0x99, 0x33, 0x02, 0x79, 0xD3, 0x65 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus0Prod => new byte[] + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Prod => new byte[] + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Prod => new byte[] + { + 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Prod => new byte[] + { + 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 + }; + + private static ReadOnlySpan Package2FixedKeyModulusProd => new byte[] + { + 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, + 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, + 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, + 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, + 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, + 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, + 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, + 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, + 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, + 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, + 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, + 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, + 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, + 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, + 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, + 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus0Dev => new byte[] + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }; + + private static ReadOnlySpan NcaHdrFixedKeyModulus1Dev => new byte[] + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + }; + + private static ReadOnlySpan AcidFixedKeyModulus0Dev => new byte[] + { + 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 + }; + + private static ReadOnlySpan AcidFixedKeyModulus1Dev => new byte[] + { + 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B + }; + + private static ReadOnlySpan Package2FixedKeyModulusDev => new byte[] + { + 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, + 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, + 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, + 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, + 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, + 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, + 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, + 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, + 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, + 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, + 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, + 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, + 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, + 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, + 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, + 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B + }; + } +} diff --git a/build/CodeGen/Stage2/RunStage2.cs b/build/CodeGen/Stage2/RunStage2.cs new file mode 100644 index 00000000..b339b91d --- /dev/null +++ b/build/CodeGen/Stage2/RunStage2.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using Octokit; + +namespace LibHacBuild.CodeGen.Stage2 +{ + // Some codegen depends on classes in LibHac. + // The part that does is split out into a separate project so the main build project + // doesn't depend on LibHac. + public static class RunStage2 + { + private const string SolutionFileName = "LibHac.sln"; + public static int Main(string[] args) + { + if (!File.Exists(SolutionFileName)) + { + Console.Error.WriteLine($"Could not find the solution file {SolutionFileName}."); + return 1; + } + + KeysCodeGen.Run(); + + return 0; + } + } +} diff --git a/build/CodeGen/_buildCodeGen.csproj b/build/CodeGen/_buildCodeGen.csproj new file mode 100644 index 00000000..f4811b2d --- /dev/null +++ b/build/CodeGen/_buildCodeGen.csproj @@ -0,0 +1,27 @@ + + + + Exe + netcoreapp3.1 + false + LibHacBuild.CodeGen + False + CS0649;CS0169 + false + + + + + + + + + + + + + + + + + diff --git a/build/_build.csproj b/build/_build.csproj index 8e372242..aa32db29 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -22,6 +22,9 @@ + + + diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs index 187256ac..9e53ad65 100644 --- a/src/LibHac/Boot/Package2StorageReader.cs +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -143,7 +143,7 @@ namespace LibHac.Boot Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes); if (rc.IsFailure()) return rc; - return _header.VerifySignature(_keySet.Package2SigningKey.Modulus, metaBytes); + return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes); } /// diff --git a/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs new file mode 100644 index 00000000..fda79d5c --- /dev/null +++ b/src/LibHac/Common/Keys/DefaultKeySet.Empty.cs @@ -0,0 +1,19 @@ +using System; + +namespace LibHac.Common.Keys +{ + internal static partial class DefaultKeySet + { + private static ReadOnlySpan RootKeysDev => new byte[] { }; + private static ReadOnlySpan RootKeysProd => new byte[] { }; + private static ReadOnlySpan KeySeeds => new byte[] { }; + private static ReadOnlySpan StoredKeysDev => new byte[] { }; + private static ReadOnlySpan StoredKeysProd => new byte[] { }; + private static ReadOnlySpan DerivedKeysDev => new byte[] { }; + private static ReadOnlySpan DerivedKeysProd => new byte[] { }; + private static ReadOnlySpan DeviceKeys => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysDev => new byte[] { }; + private static ReadOnlySpan RsaSigningKeysProd => new byte[] { }; + private static ReadOnlySpan RsaKeys => new byte[] { }; + } +} diff --git a/src/LibHac/Common/Keys/DefaultKeySet.cs b/src/LibHac/Common/Keys/DefaultKeySet.cs new file mode 100644 index 00000000..e6e4d818 --- /dev/null +++ b/src/LibHac/Common/Keys/DefaultKeySet.cs @@ -0,0 +1,71 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.Keys +{ + internal static partial class DefaultKeySet + { + public static KeySet CreateDefaultKeySet() + { + var keySet = new KeySet(); + + // Fill the key set with any key structs included in the library. + if (RootKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rootKeysDev = MemoryMarshal.Cast(RootKeysDev)[0]; + } + + if (RootKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rootKeysProd = MemoryMarshal.Cast(RootKeysProd)[0]; + } + + if (KeySeeds.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._keySeeds = MemoryMarshal.Cast(KeySeeds)[0]; + } + + if (StoredKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysDev = MemoryMarshal.Cast(StoredKeysDev)[0]; + } + + if (StoredKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._storedKeysProd = MemoryMarshal.Cast(StoredKeysProd)[0]; + } + + if (DerivedKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysDev = MemoryMarshal.Cast(DerivedKeysDev)[0]; + } + + if (DerivedKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._derivedKeysProd = MemoryMarshal.Cast(DerivedKeysProd)[0]; + } + + if (DeviceKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._deviceKeys = MemoryMarshal.Cast(DeviceKeys)[0]; + } + + if (RsaSigningKeysDev.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysDev = MemoryMarshal.Cast(RsaSigningKeysDev)[0]; + } + + if (RsaSigningKeysProd.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaSigningKeysProd = MemoryMarshal.Cast(RsaSigningKeysProd)[0]; + } + + if (RsaKeys.Length == Unsafe.SizeOf()) + { + keySet.KeyStruct._rsaKeys = MemoryMarshal.Cast(RsaKeys)[0]; + } + + return keySet; + } + } +} diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index b6fafc3d..29f47ea6 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -192,17 +192,41 @@ namespace LibHac.Common.Keys private const int TitleKeySize = 0x10; + /// + /// Loads keys from key files into an existing . Missing keys will be + /// derived from existing keys if possible. Any file names will be skipped. + /// + /// The where the loaded keys will be placed. + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null) { List keyInfos = CreateKeyList(); - if (filename != null) ReadMainKeys(keySet, filename, keyInfos, logger); - if (consoleKeysFilename != null) ReadMainKeys(keySet, consoleKeysFilename, keyInfos, logger); - if (titleKeysFilename != null) ReadTitleKeys(keySet, titleKeysFilename, logger); + if (filename != null) + { + using var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)); + ReadMainKeys(keySet, reader, keyInfos, logger); + } + + if (consoleKeysFilename != null) + { + using var reader = new StreamReader(new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read)); + ReadMainKeys(keySet, reader, keyInfos, logger); + } + + if (titleKeysFilename != null) + { + using var reader = new StreamReader(new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read)); + ReadTitleKeys(keySet, reader, logger); + } keySet.DeriveKeys(logger); + // Dev keys can read from prod key files, so derive any missing keys if necessary. if (keySet.CurrentMode == KeySet.Mode.Prod) { keySet.SetMode(KeySet.Mode.Dev); @@ -211,10 +235,20 @@ namespace LibHac.Common.Keys } } + /// + /// Creates a new initialized with the key files specified and any keys included in the library. + /// Missing keys will be derived from existing keys if possible. Any file names will be skipped. + /// + /// The path of the file containing common keys. Can be . + /// The path of the file containing title keys. Can be . + /// The path of the file containing device-unique keys. Can be . + /// An optional logger that key-parsing errors will be written to. + /// Specifies whether the keys being read are dev or prod keys. + /// The created . public static KeySet ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null, KeySet.Mode mode = KeySet.Mode.Prod) { - var keySet = new KeySet(); + var keySet = KeySet.CreateDefaultKeySet(); keySet.SetMode(mode); ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger); @@ -222,106 +256,119 @@ namespace LibHac.Common.Keys return keySet; } - private static void ReadMainKeys(KeySet keySet, string filename, List keyList, IProgressReport logger = null) + /// + /// Loads non-title keys from a into an existing . + /// Missing keys will not be derived. + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// A list of all the keys that will be loaded into the key set. + /// will create a list containing all loadable keys. + /// An optional logger that key-parsing errors will be written to. + public static void ReadMainKeys(KeySet keySet, TextReader keyFileReader, List keyList, + IProgressReport logger = null) { - if (filename == null) return; + if (keyFileReader == null) return; - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + // Todo: Improve key parsing + string line; + while ((line = keyFileReader.ReadLine()) != null) { - 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 SpecificKeyInfo info, keyList, keyName)) { - string[] a = line.Split(',', '='); - if (a.Length != 2) continue; + logger?.LogMessage($"Failed to match key {keyName}"); + continue; + } - string keyName = a[0].Trim(); - string valueStr = a[1].Trim(); + Span key; - if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, keyName)) - { - logger?.LogMessage($"Failed to match key {keyName}"); - continue; - } + // Get the dev key in the key set if needed. + if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) + { + keySet.SetMode(KeySet.Mode.Dev); + key = info.Key.Getter(keySet, info.Index); + keySet.SetMode(KeySet.Mode.Prod); + } + else + { + key = info.Key.Getter(keySet, info.Index); + } - Span key; + if (valueStr.Length != key.Length * 2) + { + logger?.LogMessage( + $"Key {keyName} had incorrect size {valueStr.Length}. Must be {key.Length * 2} hex digits."); + continue; + } - // Get the dev key in the key set if needed. - if (info.IsDev && keySet.CurrentMode == KeySet.Mode.Prod) - { - keySet.SetMode(KeySet.Mode.Dev); - key = info.Key.Getter(keySet, info.Index); - keySet.SetMode(KeySet.Mode.Prod); - } - else - { - key = info.Key.Getter(keySet, info.Index); - } + if (!Utilities.TryToBytes(valueStr, key)) + { + key.Clear(); - 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."); - } + 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) + /// + /// Loads title keys from a into an existing . + /// + /// The where the loaded keys will be placed. + /// A containing the keys to load. + /// An optional logger that key-parsing errors will be written to. + public static void ReadTitleKeys(KeySet keySet, TextReader keyFileReader, IProgressReport logger = null) { - if (filename == null) return; + if (keyFileReader == null) return; - using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read))) + // Todo: Improve key parsing + string line; + while ((line = keyFileReader.ReadLine()) != null) { - string line; - while ((line = reader.ReadLine()) != null) + string[] splitLine; + + // Some people use pipes as delimiters + if (line.Contains('|')) { - 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(); + splitLine = line.Split('|'); } + else + { + splitLine = line.Split(',', '='); + } + + if (splitLine.Length < 2) continue; + + if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId)) + { + logger?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file"); + continue; + } + + if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey)) + { + logger?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file"); + continue; + } + + if (rightsId.Length != TitleKeySize) + { + logger?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})"); + continue; + } + + if (titleKey.Length != TitleKeySize) + { + logger?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})"); + continue; + } + + keySet.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure(); } } diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs new file mode 100644 index 00000000..13bb44d8 --- /dev/null +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -0,0 +1,396 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Crypto; + +namespace LibHac.Common.Keys +{ + internal static class KeyDerivation + { + public static void DeriveAllKeys(KeySet keySet, IProgressReport logger = null) + { + DeriveKeyBlobKeys(keySet); + DecryptKeyBlobs(keySet, logger); + ReadKeyBlobs(keySet); + + Derive620Keys(keySet); + Derive620MasterKeks(keySet); + DeriveMarikoMasterKeks(keySet); + DeriveMasterKeys(keySet); + PopulateOldMasterKeys(keySet); + + DerivePerConsoleKeys(keySet); + DerivePerFirmwareKeys(keySet); + DeriveNcaHeaderKey(keySet); + DeriveSdCardKeys(keySet); + } + + private static void DeriveKeyBlobKeys(KeySet s) + { + if (s.SecureBootKey.IsEmpty() || s.TsecKey.IsEmpty()) return; + + bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsEmpty(); + var temp = new AesKey(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobKeySources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.KeyBlobKeySources[i], temp, s.TsecKey); + Aes.DecryptEcb128(temp, s.KeyBlobKeys[i], s.SecureBootKey); + + if (!haveKeyBlobMacKeySource) continue; + + Aes.DecryptEcb128(s.KeyBlobMacKeySource, s.KeyBlobMacKeys[i], s.KeyBlobKeys[i]); + } + } + + private static void DecryptKeyBlobs(KeySet s, IProgressReport logger = null) + { + var cmac = new AesCmac(); + + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobKeys[i].IsEmpty() || s.KeyBlobMacKeys[i].IsEmpty() || s.EncryptedKeyBlobs[i].IsEmpty()) + { + continue; + } + + Aes.CalculateCmac(cmac, s.EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), s.KeyBlobMacKeys[i]); + + if (!Utilities.SpansEqual(cmac, s.EncryptedKeyBlobs[i].Cmac)) + { + logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); + } + + Aes.DecryptCtr128(s.EncryptedKeyBlobs[i].Bytes.Slice(0x20), s.KeyBlobs[i].Bytes, s.KeyBlobKeys[i], + s.EncryptedKeyBlobs[i].Counter); + } + } + + private static void ReadKeyBlobs(KeySet s) + { + for (int i = 0; i < KeySet.UsedKeyBlobCount; i++) + { + if (s.KeyBlobs[i].IsEmpty()) continue; + + s.MasterKeks[i] = s.KeyBlobs[i].MasterKek; + s.Package1Keys[i] = s.KeyBlobs[i].Package1Key; + } + } + + private static void Derive620Keys(KeySet s) + { + bool haveTsecRootKek = !s.TsecRootKek.IsEmpty(); + bool havePackage1MacKek = !s.Package1MacKek.IsEmpty(); + bool havePackage1Kek = !s.Package1Kek.IsEmpty(); + + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) + { + if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsEmpty()) + continue; + + if (haveTsecRootKek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], + s.TsecRootKeys[i - KeySet.UsedKeyBlobCount], s.TsecRootKek); + } + + if (havePackage1MacKek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1MacKeys[i], + s.Package1MacKek); + } + + if (havePackage1Kek) + { + Aes.EncryptEcb128(s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount], s.Package1Keys[i], s.Package1Kek); + } + } + } + + private static void Derive620MasterKeks(KeySet s) + { + for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++) + { + // Key revisions >= 8 all use the same TSEC root key + int tsecRootKeyIndex = Math.Min(i, 8) - KeySet.UsedKeyBlobCount; + if (s.TsecRootKeys[tsecRootKeyIndex].IsEmpty() || s.MasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]); + } + } + + private static void DeriveMarikoMasterKeks(KeySet s) + { + if (s.MarikoKek.IsEmpty()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MarikoMasterKekSources[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek); + } + } + + private static void DeriveMasterKeys(KeySet s) + { + if (s.MasterKeySource.IsEmpty()) return; + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeks[i].IsEmpty()) continue; + + Aes.DecryptEcb128(s.MasterKeySource, s.MasterKeys[i], s.MasterKeks[i]); + } + } + + private static void PopulateOldMasterKeys(KeySet s) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Find the newest master key we have + int newestMasterKey = -1; + + for (int i = keyVectors.Length - 1; i >= 0; i--) + { + if (!s.MasterKeys[i].IsEmpty()) + { + newestMasterKey = i; + break; + } + } + + if (newestMasterKey == -1) + return; + + // Don't populate old master keys unless the newest master key is valid + if (!TestKeyGeneration(s, newestMasterKey)) + return; + + // Decrypt all previous master keys + for (int i = newestMasterKey; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], s.MasterKeys[i - 1], s.MasterKeys[i]); + } + } + + /// + /// Check if the master key of the specified generation is correct. + /// + /// The to test. + /// The generation to test. + /// if the key is correct. + private static bool TestKeyGeneration(KeySet s, int generation) + { + ReadOnlySpan keyVectors = MasterKeyVectors(s); + + // Decrypt the vector chain until we get Master Key 0 + AesKey key = s.MasterKeys[generation]; + + for (int i = generation; i > 0; i--) + { + Aes.DecryptEcb128(keyVectors[i], key, key); + } + + // Decrypt the zeros with Master Key 0 + Aes.DecryptEcb128(keyVectors[0], key, key); + + // If we don't get zeros, MasterKeys[generation] is incorrect + return key.IsEmpty(); + } + + private static ReadOnlySpan MasterKeyVectors(KeySet s) => + MemoryMarshal.Cast(s.CurrentMode == KeySet.Mode.Dev + ? MasterKeyVectorsDev + : MasterKeyVectorsProd); + + private static ReadOnlySpan MasterKeyVectorsDev => new byte[] + { + 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. + 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. + 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. + 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. + 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. + 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. + 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. + 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. + 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. + 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. + 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 // Master key 09 encrypted with Master key 0A. + }; + + private static ReadOnlySpan MasterKeyVectorsProd => new byte[] + { + 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. + 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. + 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. + 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. + 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. + 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. + 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. + 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. + 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. + 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. + 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A // Master key 09 encrypted with Master key 0A. + }; + + private static void DerivePerConsoleKeys(KeySet s) + { + // Todo: Dev and newer key generations + var kek = new AesKey(); + + // Derive the device key + if (!s.PerConsoleKeySource.IsEmpty() && !s.KeyBlobKeys[0].IsEmpty()) + { + Aes.DecryptEcb128(s.PerConsoleKeySource, s.DeviceKey, s.KeyBlobKeys[0]); + } + + // Derive device-unique save keys + for (int i = 0; i < s.DeviceUniqueSaveMacKeySources.Length; i++) + { + if (!s.DeviceUniqueSaveMacKekSource.IsEmpty() && !s.DeviceUniqueSaveMacKeySources[i].IsEmpty() && + !s.DeviceKey.IsEmpty()) + { + GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek); + } + } + + // Derive BIS keys + if (s.DeviceKey.IsEmpty() + || s.BisKekSource.IsEmpty() + || s.AesKekGenerationSource.IsEmpty() + || s.AesKeyGenerationSource.IsEmpty() + || s.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 (s.BisKeySources[3].IsEmpty() && !s.BisKeySources[2].IsEmpty()) + { + s.BisKeySources[3] = s.BisKeySources[2]; + } + + Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey); + if (!s.BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(s.BisKeySources[0], s.BisKeys[0], kek); + + GenerateKek(s.DeviceKey, s.BisKekSource, kek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int i = 1; i < 4; i++) + { + if (!s.BisKeySources[i].IsEmpty()) + Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek); + } + } + + private static void DerivePerFirmwareKeys(KeySet s) + { + bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsEmpty(); + bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsEmpty(); + bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsEmpty(); + bool haveTitleKekSource = !s.TitleKekSource.IsEmpty(); + bool havePackage2KeySource = !s.Package2KeySource.IsEmpty(); + + for (int i = 0; i < KeySet.KeyRevisionCount; i++) + { + if (s.MasterKeys[i].IsEmpty()) + { + continue; + } + + if (haveKakSource0) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyApplicationSource, s.KeyAreaKeys[i][0], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveKakSource1) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeyOceanSource, s.KeyAreaKeys[i][1], s.AesKekGenerationSource, + s.AesKeyGenerationSource); + } + + if (haveKakSource2) + { + GenerateKek(s.MasterKeys[i], s.KeyAreaKeySystemSource, s.KeyAreaKeys[i][2], + s.AesKekGenerationSource, s.AesKeyGenerationSource); + } + + if (haveTitleKekSource) + { + Aes.DecryptEcb128(s.TitleKekSource, s.TitleKeks[i], s.MasterKeys[i]); + } + + if (havePackage2KeySource) + { + Aes.DecryptEcb128(s.Package2KeySource, s.Package2Keys[i], s.MasterKeys[i]); + } + } + } + + private static void DeriveNcaHeaderKey(KeySet s) + { + if (s.HeaderKekSource.IsEmpty() || s.HeaderKeySource.IsEmpty() || s.MasterKeys[0].IsEmpty()) return; + + var headerKek = new AesKey(); + + GenerateKek(s.MasterKeys[0], s.HeaderKekSource, headerKek, s.AesKekGenerationSource, + s.AesKeyGenerationSource); + Aes.DecryptEcb128(s.HeaderKeySource, s.HeaderKey, headerKek); + } + + public static void DeriveSdCardKeys(KeySet s) + { + var sdKek = new AesKey(); + var tempKey = new AesXtsKey(); + GenerateKek(s.MasterKeys[0], s.SdCardKekSource, sdKek, s.AesKekGenerationSource, s.AesKeyGenerationSource); + + for (int k = 0; k < KeySet.SdCardKeyIdCount; k++) + { + for (int i = 0; i < 4; i++) + { + tempKey.Data64[i] = s.SdCardKeySources[k].Data64[i] ^ s.SdCardEncryptionSeed.Data64[i & 1]; + } + + tempKey.Data64[0] = s.SdCardKeySources[k].Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[1] = s.SdCardKeySources[k].Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + tempKey.Data64[2] = s.SdCardKeySources[k].Data64[2] ^ s.SdCardEncryptionSeed.Data64[0]; + tempKey.Data64[3] = s.SdCardKeySources[k].Data64[3] ^ s.SdCardEncryptionSeed.Data64[1]; + + Aes.DecryptEcb128(tempKey, s.SdCardEncryptionKeys[k], sdKek); + } + + // Derive sd card save key + if (!s.SeedUniqueSaveMacKekSource.IsEmpty() && !s.SeedUniqueSaveMacKeySource.IsEmpty()) + { + var keySource = new AesKey(); + + keySource.Data64[0] = s.SeedUniqueSaveMacKeySource.Data64[0] ^ s.SdCardEncryptionSeed.Data64[0]; + keySource.Data64[1] = s.SeedUniqueSaveMacKeySource.Data64[1] ^ s.SdCardEncryptionSeed.Data64[1]; + + GenerateKek(s.MasterKeys[0], s.SeedUniqueSaveMacKekSource, sdKek, s.AesKekGenerationSource, null); + Aes.DecryptEcb128(keySource, s.SeedUniqueSaveMacKey, sdKek); + } + } + + private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, + ReadOnlySpan kekSeed, ReadOnlySpan keySeed) + { + var kek = new AesKey(); + var srcKek = new AesKey(); + + Aes.DecryptEcb128(kekSeed, kek, key); + Aes.DecryptEcb128(src, srcKek, kek); + + if (!keySeed.IsEmpty) + { + Aes.DecryptEcb128(keySeed, dest, srcKek); + } + else + { + srcKek.Data.CopyTo(dest); + } + } + } +} diff --git a/src/LibHac/Common/Keys/KeySet.cs b/src/LibHac/Common/Keys/KeySet.cs index b8011338..a984670c 100644 --- a/src/LibHac/Common/Keys/KeySet.cs +++ b/src/LibHac/Common/Keys/KeySet.cs @@ -5,7 +5,7 @@ using LibHac.Boot; using LibHac.Common.FixedArrays; using LibHac.Crypto; using LibHac.FsSrv; -using Aes = LibHac.Crypto.Aes; +using LibHac.Util; namespace LibHac.Common.Keys { @@ -20,9 +20,9 @@ namespace LibHac.Common.Keys /// /// The number of keyblobs that were used for < 6.2.0 crypto /// - private const int UsedKeyBlobCount = 6; - private const int SdCardKeyIdCount = 3; - private const int KeyRevisionCount = 0x20; + internal const int UsedKeyBlobCount = 6; + internal const int SdCardKeyIdCount = 3; + internal const int KeyRevisionCount = 0x20; private AllKeys _keys; private Mode _mode = Mode.Prod; @@ -105,75 +105,79 @@ namespace LibHac.Common.Keys // Todo: Make a separate type? Not actually an AES-XTS key, but it's still the same shape. public Span SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items; + public Span NcaHeaderSigningKeys => RsaSigningKeys.NcaHeaderSigningKeys.Items; + public Span AcidSigningKeys => RsaSigningKeys.AcidSigningKeys.Items; + public ref RsaKey Package2SigningKey => ref RsaSigningKeys.Package2SigningKey; + public ref RsaFullKey BetaNca0KeyAreaKey => ref RsaKeys.BetaNca0KeyAreaKey; + private RsaSigningKeyParameters _rsaSigningKeyParamsDev; private RsaSigningKeyParameters _rsaSigningKeyParamsProd; private RsaKeyParameters _rsaKeyParams; public RSAParameters ETicketExtKeyRsa { get; set; } - public Span NcaHeaderSigningKeys + public Span NcaHeaderSigningKeyParams { get { - ref Array2? keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; + ref Optional> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys; - if (keys is null) + if (!keys.HasValue) { - keys = new Array2(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[1]); + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in NcaHeaderSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in NcaHeaderSigningKeys[1]); } return keys.Value.Items; } } - public Span AcidSigningKeys + public Span AcidSigningKeyParams { get { - ref Array2? keys = ref RsaSigningKeyParams.AcidSigningKeys; + ref Optional> keys = ref RsaSigningKeyParams.AcidSigningKeys; - if (keys is null) + if (!keys.HasValue) { - keys = new Array2(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[0]); - keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[1]); + keys.Set(new Array2()); + keys.Value[0] = CreateRsaParameters(in AcidSigningKeys[0]); + keys.Value[1] = CreateRsaParameters(in AcidSigningKeys[1]); } return keys.Value.Items; } } - public ref RSAParameters Package2SigningKey + public ref RSAParameters Package2SigningKeyParams { get { - ref Array1? keys = ref RsaSigningKeyParams.Package2SigningKey; + ref Optional keys = ref RsaSigningKeyParams.Package2SigningKey; - if (keys is null) + if (!keys.HasValue) { - keys = new Array1(); - keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.Package2SigningKey); + keys.Set(new RSAParameters()); + keys.Value = CreateRsaParameters(in Package2SigningKey); } - return ref keys.Value[0]; + return ref keys.Value; } } - public ref RSAParameters BetaNca0KeyAreaKey + public ref RSAParameters BetaNca0KeyAreaKeyParams { get { - ref Array1? keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; + ref Optional keys = ref _rsaKeyParams.BetaNca0KeyAreaKey; - if (keys is null) + if (!keys.HasValue) { - keys = new Array1(); - keys.Value[0] = CreateRsaParameters(in RsaKeys.BetaNca0KeyAreaKey); + keys.Set(CreateRsaParameters(in BetaNca0KeyAreaKey)); } - return ref keys.Value[0]; + return ref keys.Value; } } @@ -186,391 +190,19 @@ namespace LibHac.Common.Keys DeriveSdCardKeys(); } - public void SetMode(Mode mode) - { - _mode = mode; - } - - public void DeriveKeys(IProgressReport logger = null) - { - DeriveKeyBlobKeys(); - DecryptKeyBlobs(logger); - ReadKeyBlobs(); - - Derive620Keys(); - Derive620MasterKeks(); - DeriveMarikoMasterKeks(); - DeriveMasterKeys(); - PopulateOldMasterKeys(); - - DerivePerConsoleKeys(); - DerivePerFirmwareKeys(); - DeriveNcaHeaderKey(); - DeriveSdCardKeys(); - } - - private void DeriveKeyBlobKeys() - { - if (SecureBootKey.IsEmpty() || TsecKey.IsEmpty()) return; - - bool haveKeyBlobMacKeySource = !MasterKeySource.IsEmpty(); - var temp = new AesKey(); - - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobKeySources[i].IsEmpty()) continue; - - Aes.DecryptEcb128(KeyBlobKeySources[i], temp, TsecKey); - Aes.DecryptEcb128(temp, KeyBlobKeys[i], SecureBootKey); - - if (!haveKeyBlobMacKeySource) continue; - - Aes.DecryptEcb128(KeyBlobMacKeySource, KeyBlobMacKeys[i], KeyBlobKeys[i]); - } - } - - private void DecryptKeyBlobs(IProgressReport logger = null) - { - var cmac = new AesCmac(); - - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobKeys[i].IsEmpty() || KeyBlobMacKeys[i].IsEmpty() || EncryptedKeyBlobs[i].IsEmpty()) - { - continue; - } - - Aes.CalculateCmac(cmac, EncryptedKeyBlobs[i].Bytes.Slice(0x10, 0xA0), KeyBlobMacKeys[i]); - - if (!Utilities.SpansEqual(cmac, EncryptedKeyBlobs[i].Cmac)) - { - logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?"); - } - - Aes.DecryptCtr128(EncryptedKeyBlobs[i].Bytes.Slice(0x20), KeyBlobs[i].Bytes, KeyBlobKeys[i], - EncryptedKeyBlobs[i].Counter); - } - } - - private void ReadKeyBlobs() - { - for (int i = 0; i < UsedKeyBlobCount; i++) - { - if (KeyBlobs[i].IsEmpty()) continue; - - MasterKeks[i] = KeyBlobs[i].MasterKek; - Package1Keys[i] = KeyBlobs[i].Package1Key; - } - } - - private void Derive620Keys() - { - bool haveTsecRootKek = !TsecRootKek.IsEmpty(); - bool havePackage1MacKek = !Package1MacKek.IsEmpty(); - bool havePackage1Kek = !Package1Kek.IsEmpty(); - - for (int i = UsedKeyBlobCount; i < KeyRevisionCount; i++) - { - if (TsecAuthSignatures[i - UsedKeyBlobCount].IsEmpty()) - continue; - - if (haveTsecRootKek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - TsecRootKeys[i - UsedKeyBlobCount], TsecRootKek); - } - - if (havePackage1MacKek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - Package1MacKeys[i], Package1MacKek); - } - - if (havePackage1Kek) - { - Aes.EncryptEcb128(TsecAuthSignatures[i - UsedKeyBlobCount], - Package1Keys[i], Package1Kek); - } - } - } - - 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 PopulateOldMasterKeys() - { - // Find the newest master key we have - int newestMasterKey = -1; - - for (int i = MasterKeyVectors.Length - 1; i >= 0; i--) - { - if (!MasterKeys[i].IsEmpty()) - { - newestMasterKey = i; - break; - } - } - - if (newestMasterKey == -1) - return; - - // Don't populate old master keys unless the newest master key is valid - if (!TestKeyGeneration(newestMasterKey)) - return; - - // Decrypt all previous master keys - for (int i = newestMasterKey; i > 0; i--) - { - Aes.DecryptEcb128(MasterKeyVectors[i], MasterKeys[i - 1], MasterKeys[i]); - } - } + public void SetMode(Mode mode) => _mode = mode; /// - /// Check if the master key of the specified generation is correct. + /// Returns a new containing any keys that have been compiled into the library. /// - /// The generation to test. - /// if the key is correct. - private bool TestKeyGeneration(int generation) + /// The created , + public static KeySet CreateDefaultKeySet() { - // Decrypt the vector chain until we get Master Key 0 - AesKey key = MasterKeys[generation]; - - for (int i = generation; i > 0; i--) - { - Aes.DecryptEcb128(MasterKeyVectors[i], key, key); - } - - // Decrypt the zeros with Master Key 0 - Aes.DecryptEcb128(MasterKeyVectors[0], key, key); - - // If we don't get zeros, MasterKeys[generation] is incorrect - return key.IsEmpty(); + return DefaultKeySet.CreateDefaultKeySet(); } - private ReadOnlySpan MasterKeyVectors => - MemoryMarshal.Cast(_mode == Mode.Dev ? MasterKeyVectorsDev : MasterKeyVectorsProd); - - private static ReadOnlySpan MasterKeyVectorsDev => new byte[] - { - 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE, // Zeroes encrypted with Master Key 00. - 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23, // Master key 00 encrypted with Master key 01. - 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D, // Master key 01 encrypted with Master key 02. - 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3, // Master key 02 encrypted with Master key 03. - 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA, // Master key 03 encrypted with Master key 04. - 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC, // Master key 04 encrypted with Master key 05. - 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E, // Master key 05 encrypted with Master key 06. - 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19, // Master key 06 encrypted with Master key 07. - 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04, // Master key 07 encrypted with Master key 08. - 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE, // Master key 08 encrypted with Master key 09. - 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 // Master key 09 encrypted with Master key 0A. - }; - - private static ReadOnlySpan MasterKeyVectorsProd => new byte[] - { - 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D, // Zeroes encrypted with Master Key 00. - 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD, // Master key 00 encrypted with Master key 01. - 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72, // Master key 01 encrypted with Master key 02. - 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07, // Master key 02 encrypted with Master key 03. - 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9, // Master key 03 encrypted with Master key 04. - 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE, // Master key 04 encrypted with Master key 05. - 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57, // Master key 05 encrypted with Master key 06. - 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F, // Master key 06 encrypted with Master key 07. - 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29, // Master key 07 encrypted with Master key 08. - 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80, // Master key 08 encrypted with Master key 09. - 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A // Master key 09 encrypted with Master key 0A. - }; - - private void DerivePerConsoleKeys() - { - // Todo: Dev and newer key generations - var kek = new AesKey(); - - // Derive the device key - if (!PerConsoleKeySource.IsEmpty() && !KeyBlobKeys[0].IsEmpty()) - { - Aes.DecryptEcb128(PerConsoleKeySource, DeviceKey, KeyBlobKeys[0]); - } - - // Derive device-unique save keys - for (int i = 0; i < DeviceUniqueSaveMacKeySources.Length; i++) - { - if (!DeviceUniqueSaveMacKekSource.IsEmpty() && !DeviceUniqueSaveMacKeySources[i].IsEmpty() && - !DeviceKey.IsEmpty()) - { - GenerateKek(DeviceKey, DeviceUniqueSaveMacKekSource, kek, AesKekGenerationSource, null); - Aes.DecryptEcb128(DeviceUniqueSaveMacKeySources[i], DeviceUniqueSaveMacKeys[i], kek); - } - } - - // Derive BIS keys - if (DeviceKey.IsEmpty() - || BisKekSource.IsEmpty() - || AesKekGenerationSource.IsEmpty() - || AesKeyGenerationSource.IsEmpty() - || RetailSpecificAesKeySource.IsEmpty()) - { - return; - } - - // If the user doesn't provide bis_key_source_03 we can assume it's the same as bis_key_source_02 - if (BisKeySources[3].IsEmpty() && !BisKeySources[2].IsEmpty()) - { - BisKeySources[3] = BisKeySources[2]; - } - - Aes.DecryptEcb128(RetailSpecificAesKeySource, kek, DeviceKey); - if (!BisKeySources[0].IsEmpty()) Aes.DecryptEcb128(BisKeySources[0], BisKeys[0], kek); - - GenerateKek(DeviceKey, BisKekSource, kek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int i = 1; i < 4; i++) - { - if (!BisKeySources[i].IsEmpty()) - Aes.DecryptEcb128(BisKeySources[i], BisKeys[i], kek); - } - } - - private void DerivePerFirmwareKeys() - { - bool haveKakSource0 = !KeyAreaKeyApplicationSource.IsEmpty(); - bool haveKakSource1 = !KeyAreaKeyOceanSource.IsEmpty(); - bool haveKakSource2 = !KeyAreaKeySystemSource.IsEmpty(); - bool haveTitleKekSource = !TitleKekSource.IsEmpty(); - bool havePackage2KeySource = !Package2KeySource.IsEmpty(); - - for (int i = 0; i < KeyRevisionCount; i++) - { - if (MasterKeys[i].IsEmpty()) - { - continue; - } - - if (haveKakSource0) - { - GenerateKek(MasterKeys[i], KeyAreaKeyApplicationSource, KeyAreaKeys[i][0], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource1) - { - GenerateKek(MasterKeys[i], KeyAreaKeyOceanSource, KeyAreaKeys[i][1], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveKakSource2) - { - GenerateKek(MasterKeys[i], KeyAreaKeySystemSource, KeyAreaKeys[i][2], - AesKekGenerationSource, AesKeyGenerationSource); - } - - if (haveTitleKekSource) - { - Aes.DecryptEcb128(TitleKekSource, TitleKeks[i], MasterKeys[i]); - } - - if (havePackage2KeySource) - { - Aes.DecryptEcb128(Package2KeySource, Package2Keys[i], MasterKeys[i]); - } - } - } - - private void DeriveNcaHeaderKey() - { - if (HeaderKekSource.IsEmpty() || HeaderKeySource.IsEmpty() || MasterKeys[0].IsEmpty()) return; - - var headerKek = new AesKey(); - - GenerateKek(MasterKeys[0], HeaderKekSource, headerKek, AesKekGenerationSource, - AesKeyGenerationSource); - Aes.DecryptEcb128(HeaderKeySource, HeaderKey, headerKek); - } - - public void DeriveSdCardKeys() - { - var sdKek = new AesKey(); - var tempKey = new AesXtsKey(); - GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource); - - for (int k = 0; k < SdCardKeyIdCount; k++) - { - for (int i = 0; i < 4; i++) - { - tempKey.Data64[i] = SdCardKeySources[k].Data64[i] ^ SdCardEncryptionSeed.Data64[i & 1]; - } - - tempKey.Data64[0] = SdCardKeySources[k].Data64[0] ^ SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[1] = SdCardKeySources[k].Data64[1] ^ SdCardEncryptionSeed.Data64[1]; - tempKey.Data64[2] = SdCardKeySources[k].Data64[2] ^ SdCardEncryptionSeed.Data64[0]; - tempKey.Data64[3] = SdCardKeySources[k].Data64[3] ^ SdCardEncryptionSeed.Data64[1]; - - Aes.DecryptEcb128(tempKey, SdCardEncryptionKeys[k], sdKek); - } - - // Derive sd card save key - if (!SeedUniqueSaveMacKekSource.IsEmpty() && !SeedUniqueSaveMacKeySource.IsEmpty()) - { - var keySource = new AesKey(); - - keySource.Data64[0] = SeedUniqueSaveMacKeySource.Data64[0] ^ SdCardEncryptionSeed.Data64[0]; - keySource.Data64[1] = SeedUniqueSaveMacKeySource.Data64[1] ^ SdCardEncryptionSeed.Data64[1]; - - GenerateKek(MasterKeys[0], SeedUniqueSaveMacKekSource, sdKek, AesKekGenerationSource, null); - Aes.DecryptEcb128(keySource, SeedUniqueSaveMacKey, sdKek); - } - } - - private static void GenerateKek(ReadOnlySpan key, ReadOnlySpan src, Span dest, - ReadOnlySpan kekSeed, ReadOnlySpan keySeed) - { - var kek = new AesKey(); - var srcKek = new AesKey(); - - Aes.DecryptEcb128(kekSeed, kek, key); - Aes.DecryptEcb128(src, srcKek, kek); - - if (!keySeed.IsEmpty) - { - Aes.DecryptEcb128(keySeed, dest, srcKek); - } - else - { - srcKek.Data.CopyTo(dest); - } - } + public void DeriveKeys(IProgressReport logger = null) => KeyDerivation.DeriveAllKeys(this, logger); + public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this); private static RSAParameters CreateRsaParameters(in RsaKey key) { @@ -598,14 +230,14 @@ namespace LibHac.Common.Keys private struct RsaSigningKeyParameters { - public Array2? NcaHeaderSigningKeys; - public Array2? AcidSigningKeys; - public Array1? Package2SigningKey; + public Optional> NcaHeaderSigningKeys; + public Optional> AcidSigningKeys; + public Optional Package2SigningKey; } private struct RsaKeyParameters { - public Array1? BetaNca0KeyAreaKey; + public Optional BetaNca0KeyAreaKey; } } diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 99257c78..67ad92fa 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -633,7 +633,7 @@ namespace LibHac.FsSystem.NcaUtils Span keyArea = Header.GetKeyArea(); var decKeyArea = new byte[0x100]; - if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKey, out _)) + if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKeyParams, out _)) { Nca0KeyArea = decKeyArea; } @@ -711,7 +711,7 @@ namespace LibHac.FsSystem.NcaUtils public Validity VerifyHeaderSignature() { - return Header.VerifySignature1(KeySet.NcaHeaderSigningKeys[0].Modulus); + return Header.VerifySignature1(KeySet.NcaHeaderSigningKeyParams[0].Modulus); } internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion) diff --git a/src/LibHac/LibHac.csproj b/src/LibHac/LibHac.csproj index e9e23fad..a38292a4 100644 --- a/src/LibHac/LibHac.csproj +++ b/src/LibHac/LibHac.csproj @@ -40,6 +40,7 @@ + diff --git a/src/LibHac/Npdm/Acid.cs b/src/LibHac/Npdm/Acid.cs index e62d31b3..be7017a6 100644 --- a/src/LibHac/Npdm/Acid.cs +++ b/src/LibHac/Npdm/Acid.cs @@ -46,7 +46,7 @@ namespace LibHac.Npdm reader.BaseStream.Position = offset + 0x100; byte[] signatureData = reader.ReadBytes(Size); SignatureValidity = - CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeys[0].Modulus); + CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeyParams[0].Modulus); } reader.BaseStream.Position = offset + 0x208; diff --git a/src/LibHac/Package2.cs b/src/LibHac/Package2.cs index 6b2eda82..e722df92 100644 --- a/src/LibHac/Package2.cs +++ b/src/LibHac/Package2.cs @@ -154,7 +154,7 @@ namespace LibHac Signature = reader.ReadBytes(0x100); byte[] sigData = reader.ReadBytes(0x100); - SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKey.Modulus); + SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKeyParams.Modulus); reader.BaseStream.Position -= 0x100; Counter = reader.ReadBytes(0x10); diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs new file mode 100644 index 00000000..c2964d8d --- /dev/null +++ b/src/LibHac/Util/Optional.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; +using LibHac.Diag; + +namespace LibHac.Util +{ + public struct Optional + { + private bool _hasValue; + private T _value; + + public bool HasValue => _hasValue; + public ref T Value + { + get + { + Assert.AssertTrue(_hasValue); + // It's beautiful + return ref MemoryMarshal.CreateSpan(ref _value, 1)[0]; + } + } + + public void Set(in T value) + { + _value = value; + _hasValue = true; + } + + public void Clear() + { + _hasValue = false; + _value = default; + } + } +}