Embed key sources in the library

Embedded keys are generated at build-time from the IncludedKeys.txt file under the build directory.

A separate codegen build project was created because generating the embedded keys requires the same LibHac that is being built.

_buildCodeGen.csproj is located under CodeGen because NUKE doesn't like having two build projects in the same directory.
This commit is contained in:
Alex Barney 2020-10-06 18:15:32 -07:00
parent 770406e9c2
commit bac541947f
21 changed files with 1342 additions and 594 deletions

3
.gitignore vendored
View file

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

View file

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

View file

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

100
build/CodeGen/Common.cs Normal file
View file

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

View file

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

View file

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

View file

@ -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<byte> data)
{
sb.AppendSpacerLine();
sb.Append($"private static ReadOnlySpan<byte> {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<ExternalKeyReader.KeyInfo> 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<byte> StandardPublicExponent => new byte[]
{
0x01, 0x00, 0x01
};
private static ReadOnlySpan<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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
};
}
}

View file

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

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>LibHacBuild.CodeGen</RootNamespace>
<IsPackable>False</IsPackable>
<NoWarn>CS0649;CS0169</NoWarn>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="*.cs" />
<Compile Include="Stage2\*.cs" />
<EmbeddedResource Include="*.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="0.24.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\LibHac\LibHac.csproj" />
</ItemGroup>
</Project>

View file

@ -22,6 +22,9 @@
<NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
<None Remove="*.csproj.DotSettings;*.ref.*.txt" />
<EmbeddedResource Include="CodeGen\*.csv" />
<Compile Remove="CodeGen\Stage2\**" />
<Compile Remove="CodeGen\bin\**;CodeGen\obj\**" />
<None Remove="CodeGen\bin\**;CodeGen\obj\**" />
</ItemGroup>
</Project>

View file

@ -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);
}
/// <summary>

View file

@ -0,0 +1,19 @@
using System;
namespace LibHac.Common.Keys
{
internal static partial class DefaultKeySet
{
private static ReadOnlySpan<byte> RootKeysDev => new byte[] { };
private static ReadOnlySpan<byte> RootKeysProd => new byte[] { };
private static ReadOnlySpan<byte> KeySeeds => new byte[] { };
private static ReadOnlySpan<byte> StoredKeysDev => new byte[] { };
private static ReadOnlySpan<byte> StoredKeysProd => new byte[] { };
private static ReadOnlySpan<byte> DerivedKeysDev => new byte[] { };
private static ReadOnlySpan<byte> DerivedKeysProd => new byte[] { };
private static ReadOnlySpan<byte> DeviceKeys => new byte[] { };
private static ReadOnlySpan<byte> RsaSigningKeysDev => new byte[] { };
private static ReadOnlySpan<byte> RsaSigningKeysProd => new byte[] { };
private static ReadOnlySpan<byte> RsaKeys => new byte[] { };
}
}

View file

@ -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<RootKeys>())
{
keySet.KeyStruct._rootKeysDev = MemoryMarshal.Cast<byte, RootKeys>(RootKeysDev)[0];
}
if (RootKeysProd.Length == Unsafe.SizeOf<RootKeys>())
{
keySet.KeyStruct._rootKeysProd = MemoryMarshal.Cast<byte, RootKeys>(RootKeysProd)[0];
}
if (KeySeeds.Length == Unsafe.SizeOf<KeySeeds>())
{
keySet.KeyStruct._keySeeds = MemoryMarshal.Cast<byte, KeySeeds>(KeySeeds)[0];
}
if (StoredKeysDev.Length == Unsafe.SizeOf<StoredKeys>())
{
keySet.KeyStruct._storedKeysDev = MemoryMarshal.Cast<byte, StoredKeys>(StoredKeysDev)[0];
}
if (StoredKeysProd.Length == Unsafe.SizeOf<StoredKeys>())
{
keySet.KeyStruct._storedKeysProd = MemoryMarshal.Cast<byte, StoredKeys>(StoredKeysProd)[0];
}
if (DerivedKeysDev.Length == Unsafe.SizeOf<DerivedKeys>())
{
keySet.KeyStruct._derivedKeysDev = MemoryMarshal.Cast<byte, DerivedKeys>(DerivedKeysDev)[0];
}
if (DerivedKeysProd.Length == Unsafe.SizeOf<DerivedKeys>())
{
keySet.KeyStruct._derivedKeysProd = MemoryMarshal.Cast<byte, DerivedKeys>(DerivedKeysProd)[0];
}
if (DeviceKeys.Length == Unsafe.SizeOf<DeviceKeys>())
{
keySet.KeyStruct._deviceKeys = MemoryMarshal.Cast<byte, DeviceKeys>(DeviceKeys)[0];
}
if (RsaSigningKeysDev.Length == Unsafe.SizeOf<RsaSigningKeys>())
{
keySet.KeyStruct._rsaSigningKeysDev = MemoryMarshal.Cast<byte, RsaSigningKeys>(RsaSigningKeysDev)[0];
}
if (RsaSigningKeysProd.Length == Unsafe.SizeOf<RsaSigningKeys>())
{
keySet.KeyStruct._rsaSigningKeysProd = MemoryMarshal.Cast<byte, RsaSigningKeys>(RsaSigningKeysProd)[0];
}
if (RsaKeys.Length == Unsafe.SizeOf<RsaKeys>())
{
keySet.KeyStruct._rsaKeys = MemoryMarshal.Cast<byte, RsaKeys>(RsaKeys)[0];
}
return keySet;
}
}
}

View file

@ -192,17 +192,41 @@ namespace LibHac.Common.Keys
private const int TitleKeySize = 0x10;
/// <summary>
/// Loads keys from key files into an existing <see cref="KeySet"/>. Missing keys will be
/// derived from existing keys if possible. Any <see langword="null"/> file names will be skipped.
/// </summary>
/// <param name="keySet">The <see cref="KeySet"/> where the loaded keys will be placed.</param>
/// <param name="filename">The path of the file containing common keys. Can be <see langword="null"/>.</param>
/// <param name="titleKeysFilename">The path of the file containing title keys. Can be <see langword="null"/>.</param>
/// <param name="consoleKeysFilename">The path of the file containing device-unique keys. Can be <see langword="null"/>.</param>
/// <param name="logger">An optional logger that key-parsing errors will be written to.</param>
public static void ReadKeyFile(KeySet keySet, string filename, string titleKeysFilename = null,
string consoleKeysFilename = null, IProgressReport logger = null)
{
List<KeyInfo> keyInfos = CreateKeyList();
if (filename != null) ReadMainKeys(keySet, filename, keyInfos, logger);
if (consoleKeysFilename != null) ReadMainKeys(keySet, consoleKeysFilename, keyInfos, logger);
if (titleKeysFilename != null) ReadTitleKeys(keySet, titleKeysFilename, logger);
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
}
}
/// <summary>
/// Creates a new <see cref="KeySet"/> initialized with the key files specified and any keys included in the library.
/// Missing keys will be derived from existing keys if possible. Any <see langword="null"/> file names will be skipped.
/// </summary>
/// <param name="filename">The path of the file containing common keys. Can be <see langword="null"/>.</param>
/// <param name="titleKeysFilename">The path of the file containing title keys. Can be <see langword="null"/>.</param>
/// <param name="consoleKeysFilename">The path of the file containing device-unique keys. Can be <see langword="null"/>.</param>
/// <param name="logger">An optional logger that key-parsing errors will be written to.</param>
/// <param name="mode">Specifies whether the keys being read are dev or prod keys.</param>
/// <returns>The created <see cref="KeySet"/>.</returns>
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<KeyInfo> keyList, IProgressReport logger = null)
/// <summary>
/// Loads non-title keys from a <see cref="TextReader"/> into an existing <see cref="KeySet"/>.
/// Missing keys will not be derived.
/// </summary>
/// <param name="keySet">The <see cref="KeySet"/> where the loaded keys will be placed.</param>
/// <param name="keyFileReader">A <see cref="TextReader"/> containing the keys to load.</param>
/// <param name="keyList">A list of all the keys that will be loaded into the key set.
/// <see cref="CreateKeyList"/> will create a list containing all loadable keys.</param>
/// <param name="logger">An optional logger that key-parsing errors will be written to.</param>
public static void ReadMainKeys(KeySet keySet, TextReader keyFileReader, List<KeyInfo> 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<byte> 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<byte> 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)
/// <summary>
/// Loads title keys from a <see cref="TextReader"/> into an existing <see cref="KeySet"/>.
/// </summary>
/// <param name="keySet">The <see cref="KeySet"/> where the loaded keys will be placed.</param>
/// <param name="keyFileReader">A <see cref="TextReader"/> containing the keys to load.</param>
/// <param name="logger">An optional logger that key-parsing errors will be written to.</param>
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();
}
}

View file

@ -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<byte>(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<AesKey> 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]);
}
}
/// <summary>
/// Check if the master key of the specified generation is correct.
/// </summary>
/// <param name="s">The <see cref="KeySet"/> to test.</param>
/// <param name="generation">The generation to test.</param>
/// <returns><see langword="true"/> if the key is correct.</returns>
private static bool TestKeyGeneration(KeySet s, int generation)
{
ReadOnlySpan<AesKey> 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<AesKey> MasterKeyVectors(KeySet s) =>
MemoryMarshal.Cast<byte, AesKey>(s.CurrentMode == KeySet.Mode.Dev
? MasterKeyVectorsDev
: MasterKeyVectorsProd);
private static ReadOnlySpan<byte> 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<byte> 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<byte> key, ReadOnlySpan<byte> src, Span<byte> dest,
ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> keySeed)
{
var kek = new AesKey();
var srcKek = new AesKey();
Aes.DecryptEcb128(kekSeed, kek, key);
Aes.DecryptEcb128(src, srcKek, kek);
if (!keySeed.IsEmpty)
{
Aes.DecryptEcb128(keySeed, dest, srcKek);
}
else
{
srcKek.Data.CopyTo(dest);
}
}
}
}

View file

@ -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
/// <summary>
/// The number of keyblobs that were used for &lt; 6.2.0 crypto
/// </summary>
private const int UsedKeyBlobCount = 6;
private const int SdCardKeyIdCount = 3;
private const int KeyRevisionCount = 0x20;
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<AesXtsKey> SdCardEncryptionKeys => _keys._deviceKeys.SdCardEncryptionKeys.Items;
public Span<RsaKey> NcaHeaderSigningKeys => RsaSigningKeys.NcaHeaderSigningKeys.Items;
public Span<RsaKey> 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<RSAParameters> NcaHeaderSigningKeys
public Span<RSAParameters> NcaHeaderSigningKeyParams
{
get
{
ref Array2<RSAParameters>? keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys;
ref Optional<Array2<RSAParameters>> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys;
if (keys is null)
if (!keys.HasValue)
{
keys = new Array2<RSAParameters>();
keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[0]);
keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.NcaHeaderSigningKeys[1]);
keys.Set(new Array2<RSAParameters>());
keys.Value[0] = CreateRsaParameters(in NcaHeaderSigningKeys[0]);
keys.Value[1] = CreateRsaParameters(in NcaHeaderSigningKeys[1]);
}
return keys.Value.Items;
}
}
public Span<RSAParameters> AcidSigningKeys
public Span<RSAParameters> AcidSigningKeyParams
{
get
{
ref Array2<RSAParameters>? keys = ref RsaSigningKeyParams.AcidSigningKeys;
ref Optional<Array2<RSAParameters>> keys = ref RsaSigningKeyParams.AcidSigningKeys;
if (keys is null)
if (!keys.HasValue)
{
keys = new Array2<RSAParameters>();
keys.Value[0] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[0]);
keys.Value[1] = CreateRsaParameters(in RsaSigningKeys.AcidSigningKeys[1]);
keys.Set(new Array2<RSAParameters>());
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<RSAParameters>? keys = ref RsaSigningKeyParams.Package2SigningKey;
ref Optional<RSAParameters> keys = ref RsaSigningKeyParams.Package2SigningKey;
if (keys is null)
if (!keys.HasValue)
{
keys = new Array1<RSAParameters>();
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<RSAParameters>? keys = ref _rsaKeyParams.BetaNca0KeyAreaKey;
ref Optional<RSAParameters> keys = ref _rsaKeyParams.BetaNca0KeyAreaKey;
if (keys is null)
if (!keys.HasValue)
{
keys = new Array1<RSAParameters>();
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<byte>(cmac, EncryptedKeyBlobs[i].Cmac))
{
logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?");
}
Aes.DecryptCtr128(EncryptedKeyBlobs[i].Bytes.Slice(0x20), KeyBlobs[i].Bytes, KeyBlobKeys[i],
EncryptedKeyBlobs[i].Counter);
}
}
private void ReadKeyBlobs()
{
for (int i = 0; i < UsedKeyBlobCount; i++)
{
if (KeyBlobs[i].IsEmpty()) continue;
MasterKeks[i] = KeyBlobs[i].MasterKek;
Package1Keys[i] = KeyBlobs[i].Package1Key;
}
}
private void 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;
/// <summary>
/// Check if the master key of the specified generation is correct.
/// Returns a new <see cref="KeySet"/> containing any keys that have been compiled into the library.
/// </summary>
/// <param name="generation">The generation to test.</param>
/// <returns><see langword="true"/> if the key is correct.</returns>
private bool TestKeyGeneration(int generation)
/// <returns>The created <see cref="KeySet"/>,</returns>
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<AesKey> MasterKeyVectors =>
MemoryMarshal.Cast<byte, AesKey>(_mode == Mode.Dev ? MasterKeyVectorsDev : MasterKeyVectorsProd);
private static ReadOnlySpan<byte> 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<byte> 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<byte> key, ReadOnlySpan<byte> src, Span<byte> dest,
ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> keySeed)
{
var kek = new AesKey();
var srcKek = new AesKey();
Aes.DecryptEcb128(kekSeed, kek, key);
Aes.DecryptEcb128(src, srcKek, kek);
if (!keySeed.IsEmpty)
{
Aes.DecryptEcb128(keySeed, dest, srcKek);
}
else
{
srcKek.Data.CopyTo(dest);
}
}
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<RSAParameters>? NcaHeaderSigningKeys;
public Array2<RSAParameters>? AcidSigningKeys;
public Array1<RSAParameters>? Package2SigningKey;
public Optional<Array2<RSAParameters>> NcaHeaderSigningKeys;
public Optional<Array2<RSAParameters>> AcidSigningKeys;
public Optional<RSAParameters> Package2SigningKey;
}
private struct RsaKeyParameters
{
public Array1<RSAParameters>? BetaNca0KeyAreaKey;
public Optional<RSAParameters> BetaNca0KeyAreaKey;
}
}

View file

@ -633,7 +633,7 @@ namespace LibHac.FsSystem.NcaUtils
Span<byte> 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)

View file

@ -40,6 +40,7 @@
<ItemGroup>
<Compile Condition="Exists('ResultNameResolver.Generated.cs')" Remove="ResultNameResolver.Archive.cs" />
<Compile Condition="Exists('Common\Keys\DefaultKeySet.Generated.cs')" Remove="Common\Keys\DefaultKeySet.Empty.cs" />
</ItemGroup>
<ItemGroup>

View file

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

View file

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

View file

@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
using LibHac.Diag;
namespace LibHac.Util
{
public struct Optional<T>
{
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;
}
}
}