Merge pull request #166 from Thealexbarney/package1

Update package1 reader and key set handlers

- Support all Erista and Mariko package1 formats
- Rewrite KeySet class to use less memory. Key members are now byte spans instead of arrays, and the entire key set is memcpy-able
- Allow putting prod and dev keys in the same file
- Allow embedding keys in LibHac at build time
- Rewrite the key parser to be faster and use less memory
- Update KEYS.md
This commit is contained in:
Alex Barney 2020-10-14 16:20:03 -05:00 committed by GitHub
commit 122e83defe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 4809 additions and 1429 deletions

4
.gitignore vendored
View File

@ -267,4 +267,6 @@ global.json
!tests/LibHac.Tests/CryptoTests/TestVectors/*
**/DisasmoBin/
ResultNameResolver.Generated.cs
# Files generated at build time
ResultNameResolver.Generated.cs
DefaultKeySet.Generated.cs

259
KEYS.md
View File

@ -2,104 +2,136 @@
Keys are required for decrypting most of the file formats used by the Nintendo Switch.
Keysets are stored as text files, and are loaded from `$HOME/.switch`. These 3 filenames are automatically read:
`prod.keys` - Contains common keys usedy by all Switch devices.
`console.keys` - Contains console-unique keys.
Key sets are stored as text files, and are loaded from `$HOME/.switch`. On Windows this path is usually `C:\Users\<your_username>\.switch`.
These 4 filenames are automatically read:
`prod.keys` - Contains keys shared by all retail Switch devices.
`dev.keys` - Contains keys shared by all development Switch devices. Optional.
`console.keys` - Contains console-unique keys. Optional.
`title.keys` - Contains game-specific keys.
#### XTS-AES keys note
## Obtaining keys
The Switch uses 128-bit XTS-AES for decrypting the built-in storage (BIS), NCA header and the SD card contents.
This encryption method uses 2 128-bit keys: a "data" or "cipher" key, and a "tweak" key.
Keys can be obtained from a Switch that can run homebrew. The easiest way is to use [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM). See an up-to-date Switch homebrew guide for details.
In the keyfile these are stored as one 256-bit key with the data key first, followed by the tweak key.
After running Lockpick_RCM `/switch/prod.keys` and `/switch/title.keys` should be on your SD card. Copy these two files to the `.switch` directory specified above.
## Keyfile format
# Key file details
Dumping keys from a Switch is all that is needed for LibHac.
`prod.keys` and `console.keys` should be in the following format with one key per line:
The following section contains some additional information on keys, documentation on the key file format and a list of supported keys.
## Key file format
`prod.keys`, `dev.keys` and `console.keys` should be in the following format with one key per line:
`key_name = hexadecimal_key_value`
Each line must contain fewer than 1024 characters.
e.g. (Not actual keys)
```
master_key_00 = 63C9FCB338CDE3D037D29BB66F897C6B
master_key_01 = 4636CB976DFE95095C1F55151A8326C6
header_key_source = 343795270AAD5D19EBE2956C9BC71F4C41836B21DC6ACD7BACD4F6AF4816692C
master_key_00 = 496620796F752772652072656164696E
master_key_01 = 6720746869732C20796F752772652061
header_key_source = 206E657264AD5D19EBE2956C9BC71F4C41836B21DC6ACD7BACD4F6AF4816692C
```
#### Title Keys
### Title keys
`title.keys` should be in the following format with one key per line:
`rights_id,hexadecimal_key_value`.
`rights_id = hexadecimal_key_value`.
e.g. (Not actual keys)
```
01000000000100000000000000000003,B4A1F5575D7D8A81624ED36D4E4BD8FD
01000000000108000000000000000003,C8AD76F8C78E241ADFEE6EB12E33F1BD
01000000000108000000000000000004,F9C8EAD30BB594434E4AF62C483CD796
01000000000100000000000000000003 = 68747470733A2F2F7777772E796F7574
01000000000108000000000000000003 = 7562652E636F6D2F77617463683F763D
01000000000108000000000000000004 = 64517734773957675863513F4C696248
```
## Keyfile templates
### Dev keys
Keys from `dev.keys` will always be loaded as dev keys.
Dev keys may also be loaded from `prod.keys`, allowing both key sets to be in the same file.
Because both key sets use the same key sources, only a small number of root keys are needed to derive each set.
Key names that have `_dev` after the main key name but before the key index will be loaded as dev keys.
e.g. (Not actual keys)
```
master_key_0a = B6B0F17AC61696120A15FFD41A529CBE
master_key_dev_0a = 154A07EAFC50C6328A66C4FD2CDB277A
xci_header_key_dev = 118BA87386A242FA9DCCB06853E7A9F6
```
## Key system
This is meant to be a basic overview of the concepts used by the Switch's content key system.
### Key generations
In a nutshell, the Switch's OS contains key sources or seeds.
These seeds are useless on their own, but given a "master key" they can be used to generate the actual content keys.
This master key is the root from which all content keys are derived.
Retail and development Switches have different master keys.
The Switch uses what are called "key generations" (As in the noun, not the verb).
Each generation has its own master key which results in a different set of content keys for each one.
Content files are encrypted with the keys from the most recent generation.
e.g. A game built for system version 6.2.0 will be encrypted with the keys for 6.2.0. Older system versions would be unable to decrypt the content.
### Root keys
Root keys are the keys used to derive other keys.
Erista (original Switch hardware version) and Mariko (second hardware version) have different root keys.
Both these root keys are used to derive the same master key which will then derive other keys.
The current root key for Erista is `tsec_root_key_02`, and the key for Mariko is `mariko_kek`.
The main purpose of these keys is to generate the master key, so they're not really necessary for decrypting content.
These root keys, with proper security, are supposed to be hardware secrets, unable to be accessed by software.
Package1 is the only content that is not encrypted with these root keys or their derivatives.
Each Erista package1 is encrypted with its own unique key, and every Mariko package1 is encrypted with `mariko_bek`.
## Key file templates
This template contains the keys needed to derive all the keys used by hactoolnet, although not all of them are needed for every task.
In fact, more than 99% of all content can be decrypted by providing only the most recent master key.
Fill out the template with the actual keys to get a working keyfile.
LibHac contains the key sources that keys are derived from. Only a small number of root keys need to be provided, although any keys will be loaded from the key file if present.
Providing the following keys will enable decryption of all retail content.
Every one of these keys also has a dev version. Providing them will enable decryption of all dev content.
```
master_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Only the latest master key is needed to decrypt the vast majority of Switch content.
master_key_0a = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_mac_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
keyblob_key_source_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Package1 keys are used to decrypt package1, the first part of the OS loaded during boot.
package1_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# The XCI header key will decrypt the gamecard info in an XCI. Not usually needed.
xci_header_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package2_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Methods of obtaining the keys below are not publicly available as of Oct. 2020,
# but they're included anyway for completion's sake
aes_kek_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
aes_key_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
titlekek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Keys for Erista package1 since firmware 6.2.0.
package1_key_06 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_07 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_08 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_09 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package1_key_0a = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_application_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_ocean_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_system_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sd_card_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sd_card_save_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sd_card_nca_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sd_card_custom_storage_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
header_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
header_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
xci_header_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
retail_specific_aes_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
per_console_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
eticket_rsa_kek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_source_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_source_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_source_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
save_mac_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
save_mac_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
save_mac_sd_card_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
save_mac_sd_card_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# The Mariko boot encryption key (BEK) is used to decrypt Mariko package1.
# The Mariko key encryption key (KEK) is used to derive master keys on Mariko Switches.
# All content keys are the same on both Switch versions except for package1 keys.
# Together the Mariko BEK and KEK are enough to derive all current content keys and all
# content keys in the forseeable future except for Erista package1.
mariko_bek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
mariko_kek = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
### Console-unique keys
@ -111,7 +143,7 @@ tsec_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
secure_boot_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sd_seed = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# The below keys can be derived from tsec_key and secure_boot_key
# These keys can be derived from tsec_key and secure_boot_key
device_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@ -119,6 +151,13 @@ bis_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bis_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
#### XTS-AES keys note
The Switch uses 128-bit XTS-AES for decrypting the built-in storage (BIS), NCA header and the SD card contents.
This encryption method uses 2 128-bit keys: a "data" or "cipher" key, and a "tweak" key.
In the key file these are stored as one 256-bit key with the data key first, followed by the tweak key.
## Complete key list
Below is a complete list of keys that are currently recognized.
\## represents a hexadecimal number between 00 and 1F
@ -126,42 +165,69 @@ Below is a complete list of keys that are currently recognized.
### Common keys
```
master_key_source
tsec_root_kek
package1_mac_kek
package1_kek
tsec_auth_signature_##
tsec_root_key_##
keyblob_mac_key_source
keyblob_key_source_##
keyblob_##
mariko_bek
mariko_kek
mariko_aes_class_key_##
mariko_master_kek_source_##
master_kek_source_##
master_kek_##
master_key_source
master_key_##
package1_key_##
package1_mac_key_##
package2_key_source
aes_kek_generation_source
aes_key_generation_source
key_area_key_application_source
key_area_key_ocean_source
key_area_key_system_source
titlekek_source
header_kek_source
header_key_source
sd_card_kek_source
sd_card_nca_key_source
sd_card_save_key_source
retail_specific_aes_key_source
per_console_key_source
package2_key_##
bis_kek_source
bis_key_source_00
bis_key_source_01
bis_key_source_02
save_mac_kek_source
save_mac_key_source
bis_key_source_03
header_key
xci_header_key
eticket_rsa_kek
master_key_##
package1_key_##
package2_key_##
per_console_key_source
retail_specific_aes_key_source
aes_kek_generation_source
aes_key_generation_source
titlekek_source
titlekek_##
header_kek_source
header_key_source
header_key
key_area_key_application_source
key_area_key_ocean_source
key_area_key_system_source
key_area_key_application_##
key_area_key_ocean_##
key_area_key_system_##
keyblob_key_source_##
keyblob_##
save_mac_kek_source
save_mac_key_source_00
save_mac_key_source_01
save_mac_sd_card_kek_source
save_mac_sd_card_key_source
sd_card_kek_source
sd_card_save_key_source
sd_card_nca_key_source
sd_card_custom_storage_key_source
xci_header_key
eticket_rsa_kek
ssl_rsa_kek
```
### Console-unique keys
@ -174,10 +240,13 @@ bis_key_00
bis_key_01
bis_key_02
bis_key_03
save_mac_key_00
save_mac_key_01
keyblob_key_##
keyblob_mac_key_##
encrypted_keyblob_##
sd_seed
save_mac_sd_card_key
```

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,60 @@
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_06 = CC974C462A0CB0A6C9C0B7BE302EC368
mariko_master_kek_source_dev_07 = 86BD1D7650DF6DFA2C7D3322ABF18218
mariko_master_kek_source_dev_08 = A3B1E0A958A2267F40BF5BBB87330B66
mariko_master_kek_source_dev_09 = 82729165403B9D6660D01B3D4DA570E1
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.IsZeros())
{
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 (Stream keyFile = GetResource(InputMainKeyFileName))
{
List<KeyInfo> list = KeySet.CreateKeyInfoList();
ExternalKeyReader.ReadMainKeys(keySet, keyFile, 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

@ -467,3 +467,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
428,1031,,InvalidPackage2MetaPayloadsOverlap,
428,1032,,InvalidPackage2MetaEntryPointNotFound,
428,1033,,InvalidPackage2PayloadCorrupted,
428,1040,1059,InvalidPackage1,
428,1041,,InvalidPackage1SectionSize,
428,1042,,InvalidPackage1MarikoBodySize,
428,1043,,InvalidPackage1Pk11Size,

Can't render this file because it has a wrong number of fields in line 203.

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

@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.Bcat.Detail.Service.Core
{

View File

@ -2,7 +2,7 @@
using System.Diagnostics;
using LibHac.Bcat.Detail.Ipc;
using LibHac.Bcat.Detail.Service.Core;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Bcat.Detail.Service
{

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Bcat
{

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Bcat
{

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Bcat
{

View File

@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Crypto;
using LibHac.Util;
namespace LibHac.Boot
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = 0xB0)]
public struct EncryptedKeyBlob
{
#if DEBUG
[FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1;
[FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2;
[FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3;
[FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4;
[FieldOffset(0x80)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy5;
[FieldOffset(0xA0)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer16 _dummy6;
#endif
[FieldOffset(0x00)] public AesCmac Cmac;
[FieldOffset(0x10)] public AesIv Counter;
public Span<byte> Payload => Bytes.Slice(0x20, Unsafe.SizeOf<KeyBlob>());
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public readonly ReadOnlySpan<byte> ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros()
{
ReadOnlySpan<ulong> ulongSpan = MemoryMarshal.Cast<byte, ulong>(ReadOnlyBytes);
for (int i = 0; i < ulongSpan.Length; i++)
{
if (ulongSpan[i] != 0)
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<byte>(in EncryptedKeyBlob value)
{
return SpanHelpers.AsReadOnlyByteSpan(in value);
}
public override readonly string ToString() => ReadOnlyBytes.ToHexString();
}
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = 0x90)]
public struct KeyBlob
{
#if DEBUG
[FieldOffset(0x00)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy1;
[FieldOffset(0x20)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy2;
[FieldOffset(0x40)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy3;
[FieldOffset(0x60)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Buffer32 _dummy4;
#endif
[FieldOffset(0x00)] public AesKey MasterKek;
[FieldOffset(0x80)] public AesKey Package1Key;
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
public readonly ReadOnlySpan<byte> ReadOnlyBytes => SpanHelpers.AsReadOnlyByteSpan(in this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros()
{
ReadOnlySpan<ulong> ulongSpan = MemoryMarshal.Cast<byte, ulong>(ReadOnlyBytes);
for (int i = 0; i < ulongSpan.Length; i++)
{
if (ulongSpan[i] != 0)
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<byte>(in KeyBlob value)
{
return SpanHelpers.AsReadOnlyByteSpan(in value);
}
public override readonly string ToString() => ReadOnlyBytes.ToHexString();
}
}

549
src/LibHac/Boot/Package1.cs Normal file
View File

@ -0,0 +1,549 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common.Keys;
using LibHac.Diag;
using LibHac.Util;
namespace LibHac.Boot
{
[StructLayout(LayoutKind.Explicit, Size = 0x170)]
public struct Package1MarikoOemHeader
{
[FieldOffset(0x000)] private byte _aesMac;
[FieldOffset(0x010)] private byte _rsaSig;
[FieldOffset(0x110)] private byte _salt;
[FieldOffset(0x130)] private byte _hash;
[FieldOffset(0x150)] public int Version;
[FieldOffset(0x154)] public int Size;
[FieldOffset(0x158)] public int LoadAddress;
[FieldOffset(0x15C)] public int EntryPoint;
[FieldOffset(0x160)] private byte _reserved;
public ReadOnlySpan<byte> AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10);
public ReadOnlySpan<byte> RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100);
public ReadOnlySpan<byte> Salt => SpanHelpers.CreateSpan(ref _salt, 0x20);
public ReadOnlySpan<byte> Hash => SpanHelpers.CreateSpan(ref _hash, 0x20);
public ReadOnlySpan<byte> Reserved => SpanHelpers.CreateSpan(ref _reserved, 0x10);
}
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
public struct Package1MetaData
{
[FieldOffset(0x00)] public uint LoaderHash;
[FieldOffset(0x04)] public uint SecureMonitorHash;
[FieldOffset(0x08)] public uint BootloaderHash;
[FieldOffset(0x10)] private byte _buildDate;
[FieldOffset(0x1E)] public byte KeyGeneration;
[FieldOffset(0x1F)] public byte Version;
public U8Span BuildDate => new U8Span(SpanHelpers.CreateSpan(ref _buildDate, 0xE));
public ReadOnlySpan<byte> Iv => SpanHelpers.CreateSpan(ref _buildDate, 0x10);
}
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
public struct Package1Stage1Footer
{
[FieldOffset(0x00)] public int Pk11Size;
[FieldOffset(0x10)] private byte _iv;
public ReadOnlySpan<byte> Iv => SpanHelpers.CreateSpan(ref _iv, 0x10);
}
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
public struct Package1Pk11Header
{
public const uint ExpectedMagic = 0x31314B50; // PK11
[FieldOffset(0x00)] public uint Magic;
[FieldOffset(0x04)] public int WarmBootSize;
[FieldOffset(0x08)] public int WarmBootOffset;
[FieldOffset(0x10)] public int BootloaderSize;
[FieldOffset(0x14)] public int BootloaderOffset;
[FieldOffset(0x18)] public int SecureMonitorSize;
[FieldOffset(0x1C)] public int SecureMonitorOffset;
}
public enum Package1Section
{
Bootloader,
SecureMonitor,
WarmBoot
}
public class Package1
{
private const int LegacyStage1Size = 0x4000;
private const int ModernStage1Size = 0x7000;
private const int MarikoWarmBootPlainTextSectionSize = 0x330;
private IStorage BaseStorage { get; set; }
private KeySet KeySet { get; set; }
public bool IsModern { get; private set; }
public bool IsMariko { get; private set; }
/// <summary>
/// Returns <see langword="true"/> if the package1 can be decrypted.
/// </summary>
public bool IsDecrypted { get; private set; }
public byte KeyRevision { get; private set; }
public int Pk11Size { get; private set; }
private IStorage Pk11Storage { get; set; }
private IStorage BodyStorage { get; set; }
private Package1MarikoOemHeader _marikoOemHeader;
private Package1MetaData _metaData;
private Package1Stage1Footer _stage1Footer;
private Package1Pk11Header _pk11Header;
private Buffer16 _pk11Mac;
public ref readonly Package1MarikoOemHeader MarikoOemHeader => ref _marikoOemHeader;
public ref readonly Package1MetaData MetaData => ref _metaData;
public ref readonly Package1Stage1Footer Stage1Footer => ref _stage1Footer;
public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header;
public ref readonly Buffer16 Pk11Mac => ref _pk11Mac;
public Result Initialize(KeySet keySet, IStorage storage)
{
KeySet = keySet;
BaseStorage = storage;
// Read what might be a mariko header and check if it actually is a mariko header
Result rc = BaseStorage.Read(0, SpanHelpers.AsByteSpan(ref _marikoOemHeader));
if (rc.IsFailure()) return rc;
IsMariko = IsMarikoImpl();
if (IsMariko)
{
rc = InitMarikoBodyStorage();
if (rc.IsFailure()) return rc;
}
else
{
BodyStorage = BaseStorage;
rc = BodyStorage.Read(0, SpanHelpers.AsByteSpan(ref _metaData));
if (rc.IsFailure()) return rc;
}
rc = ParseStage1();
if (rc.IsFailure()) return rc;
rc = ReadPk11Header();
if (rc.IsFailure()) return rc;
if (!IsMariko && IsModern)
{
rc = ReadModernEristaMac();
if (rc.IsFailure()) return rc;
}
rc = SetPk11Storage();
if (rc.IsFailure()) return rc;
// Make sure the PK11 section sizes add up to the expected size
if (IsDecrypted && !VerifyPk11Sizes())
{
return ResultLibHac.InvalidPackage1Pk11Size.Log();
}
return Result.Success;
}
/// <summary>
/// Read the encrypted section of a Mariko Package1 and try to decrypt it.
/// </summary>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultLibHac.InvalidPackage1MarikoBodySize"/>: The package1 is
/// too small or the size in the OEM header is incorrect.</returns>
private Result InitMarikoBodyStorage()
{
// Body must be large enough to hold at least one metadata struct
if (MarikoOemHeader.Size < Unsafe.SizeOf<Package1MetaData>())
return ResultLibHac.InvalidPackage1MarikoBodySize.Log();
// Verify the body storage size is not smaller than the size in the header
Result rc = BaseStorage.GetSize(out long totalSize);
if (rc.IsFailure()) return rc;
long bodySize = totalSize - Unsafe.SizeOf<Package1MarikoOemHeader>();
if (bodySize < MarikoOemHeader.Size)
return ResultLibHac.InvalidPackage1MarikoBodySize.Log();
// Create body SubStorage and metadata buffers
var bodySubStorage = new SubStorage(BaseStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(), bodySize);
Span<Package1MetaData> metaData = stackalloc Package1MetaData[2];
Span<byte> metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]);
Span<byte> metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]);
// Read both the plaintext metadata and encrypted metadata
rc = bodySubStorage.Read(0, MemoryMarshal.Cast<Package1MetaData, byte>(metaData));
if (rc.IsFailure()) return rc;
// Set the body storage and decrypted metadata
_metaData = metaData[0];
BodyStorage = bodySubStorage;
// The plaintext metadata is followed by an encrypted copy
// If these two match then the body is already decrypted
IsDecrypted = metaData1.SequenceEqual(metaData2);
if (IsDecrypted)
{
return Result.Success;
}
// If encrypted, check if the body can be decrypted
Crypto.Aes.DecryptCbc128(metaData2, metaData2, KeySet.MarikoBek, _metaData.Iv);
IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData));
// Get a decrypted body storage if we have the correct key
if (IsDecrypted)
{
var decStorage = new AesCbcStorage(bodySubStorage, KeySet.MarikoBek, _metaData.Iv, true);
BodyStorage = new CachedStorage(decStorage, 0x4000, 1, true);
}
return Result.Success;
}
private Result ParseStage1()
{
// Erista package1ldr is stored unencrypted, so we can always directly read the size
// field at the end of package1ldr.
// Mariko package1ldr is stored encrypted. If we're able to decrypt it,
// directly read the size field at the end of package1ldr.
IsModern = !IsLegacyImpl();
int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size;
if (IsMariko && !IsDecrypted)
{
// If we're not able to decrypt the Mariko package1ldr, calculate the size by subtracting
// the known package1ldr size from the total size in the OEM header.
Pk11Size = MarikoOemHeader.Size - stage1Size;
return Result.Success;
}
// Read the package1ldr footer
int footerOffset = stage1Size - Unsafe.SizeOf<Package1Stage1Footer>();
Result rc = BodyStorage.Read(footerOffset, SpanHelpers.AsByteSpan(ref _stage1Footer));
if (rc.IsFailure()) return rc;
// Get the PK11 size from the field in the unencrypted stage 1 footer
Pk11Size = _stage1Footer.Pk11Size;
return Result.Success;
}
private Result ReadPk11Header()
{
int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size;
return BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header));
}
private Result ReadModernEristaMac()
{
return BaseStorage.Read(ModernStage1Size + Pk11Size, _pk11Mac.Bytes);
}
private Result SetPk11Storage()
{
// Read the PK11 header from the body storage
int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size;
Result rc = BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header));
if (rc.IsFailure()) return rc;
// Check if PK11 is already decrypted, creating the PK11 storage if it is
IsDecrypted = _pk11Header.Magic == Package1Pk11Header.ExpectedMagic;
if (IsDecrypted)
{
Pk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size);
return Result.Success;
}
var encPk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size);
// See if we have an Erista package1 key that can decrypt this PK11
if (!IsMariko && TryFindEristaKeyRevision())
{
IsDecrypted = true;
IStorage decPk11Storage;
if (IsModern)
{
decPk11Storage = new AesCbcStorage(encPk11Storage, KeySet.Package1Keys[KeyRevision],
_stage1Footer.Iv, true);
}
else
{
decPk11Storage = new Aes128CtrStorage(encPk11Storage,
KeySet.Package1Keys[KeyRevision].DataRo.ToArray(), _stage1Footer.Iv.ToArray(), true);
}
Pk11Storage = new CachedStorage(decPk11Storage, 0x4000, 1, true);
return Result.Success;
}
// We can't decrypt the PK11. Set Pk11Storage to the encrypted PK11 storage
Pk11Storage = encPk11Storage;
return Result.Success;
}
private delegate void Decryptor(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false);
private bool TryFindEristaKeyRevision()
{
// Package1 has no indication of which key it's encrypted with,
// so we must test each known key to find one that works
var decHeader = new Package1Pk11Header();
int start = IsModern ? 6 : 0;
int end = IsModern ? 0x20 : 6;
Decryptor decryptor = IsModern ? Crypto.Aes.DecryptCbc128 : (Decryptor)Crypto.Aes.DecryptCtr128;
for (int i = start; i < end; i++)
{
decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader),
KeySet.Package1Keys[i], _stage1Footer.Iv);
if (decHeader.Magic == Package1Pk11Header.ExpectedMagic)
{
KeyRevision = (byte)i;
_pk11Header = decHeader;
return true;
}
}
return false;
}
private bool VerifyPk11Sizes()
{
Assert.AssertTrue(IsDecrypted);
int pk11Size = Unsafe.SizeOf<Package1Pk11Header>() + GetSectionSize(Package1Section.WarmBoot) +
GetSectionSize(Package1Section.Bootloader) + GetSectionSize(Package1Section.SecureMonitor);
pk11Size = Utilities.AlignUp(pk11Size, 0x10);
return pk11Size == Pk11Size;
}
// MetaData must be read first
private bool IsLegacyImpl()
{
return _metaData.Version < 0xE || StringUtils.Compare(_metaData.BuildDate, LegacyDateCutoff) < 0;
}
// MarikoOemHeader must be read first
private bool IsMarikoImpl()
{
return MarikoOemHeader.AesMac.IsZeros() && MarikoOemHeader.Reserved.IsZeros();
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the entire package1, decrypting any encrypted data.
/// </summary>
/// <returns>If the package1 can be decrypted, an <see cref="IStorage"/>
/// of the package1, <see langword="null"/>.</returns>
public IStorage OpenDecryptedPackage1Storage()
{
if (!IsDecrypted)
return null;
var storages = new List<IStorage>();
if (IsMariko)
{
int metaSize = Unsafe.SizeOf<Package1MetaData>();
// The metadata at the start of the body is unencrypted, so don't take its data from the decrypted
// body storage
storages.Add(new SubStorage(BaseStorage, 0, Unsafe.SizeOf<Package1MarikoOemHeader>() + metaSize));
storages.Add(new SubStorage(BodyStorage, metaSize, _marikoOemHeader.Size - metaSize));
}
else
{
int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size;
storages.Add(new SubStorage(BaseStorage, 0, stage1Size));
storages.Add(Pk11Storage);
if (IsModern)
{
storages.Add(new MemoryStorage(_pk11Mac.Bytes.ToArray()));
}
}
return new ConcatenationStorage(storages, true);
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the warmboot section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// warmboot section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot);
/// <summary>
/// Opens an <see cref="IStorage"/> of the bootloader section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// bootloader section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader);
/// <summary>
/// Opens an <see cref="IStorage"/> of the secure monitor section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// secure monitor section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor);
/// <summary>
/// Opens an <see cref="IStorage"/> for the specified <see cref="Package1Section"/>.
/// </summary>
/// <param name="sectionType">The section to open.</param>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of that
/// section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenSectionStorage(Package1Section sectionType)
{
if (!IsDecrypted)
return null;
int offset = Unsafe.SizeOf<Package1Pk11Header>() + GetSectionOffset(sectionType);
int size = GetSectionSize(sectionType);
return new SubStorage(Pk11Storage, offset, size);
}
public IStorage OpenDecryptedWarmBootStorage()
{
if (!IsDecrypted)
return null;
IStorage warmBootStorage = OpenWarmBootStorage();
// Only Mariko warmboot storage is encrypted
if (!IsMariko)
{
return warmBootStorage;
}
int size = GetSectionSize(Package1Section.WarmBoot);
int encryptedSectionSize = size - MarikoWarmBootPlainTextSectionSize;
var plainTextSection = new SubStorage(warmBootStorage, 0, MarikoWarmBootPlainTextSectionSize);
var encryptedSubStorage =
new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize);
var zeroIv = new Buffer16();
IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, KeySet.MarikoBek, zeroIv.Bytes, true);
decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true);
return new ConcatenationStorage(new List<IStorage> { plainTextSection, decryptedSection }, true);
}
public int GetSectionSize(Package1Section sectionType)
{
if (!IsDecrypted)
return 0;
return sectionType switch
{
Package1Section.Bootloader => _pk11Header.BootloaderSize,
Package1Section.SecureMonitor => _pk11Header.SecureMonitorSize,
Package1Section.WarmBoot => _pk11Header.WarmBootSize,
_ => 0
};
}
public int GetSectionOffset(Package1Section sectionType)
{
if (!IsDecrypted)
return 0;
switch (GetSectionIndex(sectionType))
{
case 0:
return 0;
case 1:
return GetSectionSize(GetSectionType(0));
case 2:
return GetSectionSize(GetSectionType(0)) + GetSectionSize(GetSectionType(1));
default:
return -1;
}
}
private int GetSectionIndex(Package1Section sectionType)
{
if (_metaData.Version >= 0x07)
{
return sectionType switch
{
Package1Section.Bootloader => 0,
Package1Section.SecureMonitor => 1,
Package1Section.WarmBoot => 2,
_ => -1,
};
}
if (_metaData.Version >= 0x02)
{
return sectionType switch
{
Package1Section.Bootloader => 1,
Package1Section.SecureMonitor => 2,
Package1Section.WarmBoot => 0,
_ => -1,
};
}
return sectionType switch
{
Package1Section.Bootloader => 1,
Package1Section.SecureMonitor => 0,
Package1Section.WarmBoot => 2,
_ => -1,
};
}
private Package1Section GetSectionType(int index)
{
if (GetSectionIndex(Package1Section.Bootloader) == index)
return Package1Section.Bootloader;
if (GetSectionIndex(Package1Section.SecureMonitor) == index)
return Package1Section.SecureMonitor;
if (GetSectionIndex(Package1Section.WarmBoot) == index)
return Package1Section.WarmBoot;
return (Package1Section)(-1);
}
private static ReadOnlySpan<byte> LegacyDateCutoff => // 20181107
new[]
{
(byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7'
};
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Crypto;
using LibHac.Fs;
using LibHac.FsSystem;
@ -19,27 +20,27 @@ namespace LibHac.Boot
private IStorage _storage;
private Package2Header _header;
private Keyset _keyset;
private byte[] _key;
private KeySet _keySet;
private AesKey _key;
public ref readonly Package2Header Header => ref _header;
/// <summary>
/// Initializes the <see cref="Package2StorageReader"/>.
/// </summary>
/// <param name="keyset">The keyset to use for decrypting the package.</param>
/// <param name="keySet">The keyset to use for decrypting the package.</param>
/// <param name="storage">An <see cref="IStorage"/> of the encrypted package2.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public Result Initialize(Keyset keyset, IStorage storage)
public Result Initialize(KeySet keySet, IStorage storage)
{
Result rc = storage.Read(0, SpanHelpers.AsByteSpan(ref _header));
if (rc.IsFailure()) return rc;
_key = keyset.Package2Keys[_header.Meta.KeyGeneration];
_key = keySet.Package2Keys[_header.Meta.KeyGeneration];
DecryptHeader(_key, ref _header.Meta, ref _header.Meta);
_storage = storage;
_keyset = keyset;
_keySet = keySet;
return Result.Success;
}
@ -69,7 +70,7 @@ namespace LibHac.Boot
}
byte[] iv = _header.Meta.PayloadIvs[index].Bytes.ToArray();
payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key, iv, true), 0x4000, 1, true);
payloadStorage = new CachedStorage(new Aes128CtrStorage(payloadSubStorage, _key.DataRo.ToArray(), iv, true), 0x4000, 1, true);
return Result.Success;
}
@ -142,7 +143,7 @@ namespace LibHac.Boot
Result rc = _storage.Read(Package2Header.SignatureSize, metaBytes);
if (rc.IsFailure()) return rc;
return _header.VerifySignature(_keyset.Package2FixedKeyModulus, metaBytes);
return _header.VerifySignature(_keySet.Package2SigningKeyParams.Modulus, metaBytes);
}
/// <summary>
@ -230,7 +231,7 @@ namespace LibHac.Boot
byte[] iv = _header.Meta.HeaderIv.Bytes.ToArray();
Utilities.IncrementByteArray(iv);
storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key, iv, true), 0x100, 1, true));
storages.Add(new CachedStorage(new Aes128CtrStorage(encMetaStorage, _key.DataRo.ToArray(), iv, true), 0x100, 1, true));
// Open all the payloads
for (int i = 0; i < Package2Header.PayloadCount; i++)
@ -252,7 +253,7 @@ namespace LibHac.Boot
return Result.Success;
}
private void DecryptHeader(byte[] key, ref Package2Meta source, ref Package2Meta dest)
private void DecryptHeader(ReadOnlySpan<byte> key, ref Package2Meta source, ref Package2Meta dest)
{
Buffer16 iv = source.HeaderIv;

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Util;
namespace LibHac.Common
{

View File

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

View File

@ -0,0 +1,33 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array12<T>
{
public const int Length = 12;
private T _item01;
private T _item02;
private T _item03;
private T _item04;
private T _item05;
private T _item06;
private T _item07;
private T _item08;
private T _item09;
private T _item10;
private T _item11;
private T _item12;
public ref T this[int i] => ref Items[i];
public Span<T> Items => SpanHelpers.CreateSpan(ref _item01, Length);
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array12<T> value) => value.ItemsRo;
}
}

View File

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

View File

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

View File

@ -0,0 +1,53 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common.FixedArrays
{
[StructLayout(LayoutKind.Sequential)]
public struct Array32<T>
{
public const int Length = 32;
private T _item01;
private T _item02;
private T _item03;
private T _item04;
private T _item05;
private T _item06;
private T _item07;
private T _item08;
private T _item09;
private T _item10;
private T _item11;
private T _item12;
private T _item13;
private T _item14;
private T _item15;
private T _item16;
private T _item17;
private T _item18;
private T _item19;
private T _item20;
private T _item21;
private T _item22;
private T _item23;
private T _item24;
private T _item25;
private T _item26;
private T _item27;
private T _item28;
private T _item29;
private T _item30;
private T _item31;
private T _item32;
public ref T this[int i] => ref Items[i];
public Span<T> Items => SpanHelpers.CreateSpan(ref _item01, Length);
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item01, Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(in Array32<T> value) => value.ItemsRo;
}
}

View File

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

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Util;
namespace LibHac.Common
{

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,181 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Type = LibHac.Common.Keys.KeyInfo.KeyType;
namespace LibHac.Common.Keys
{
internal static partial class DefaultKeySet
{
/// <summary>
/// Creates a <see cref="KeySet"/> that contains any keys that have been embedded in the library.
/// </summary>
/// <returns>The created <see cref="KeySet"/>.</returns>
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;
}
/// <summary>
/// Creates a <see cref="List{T}"/> of the <see cref="KeyInfo"/> of all keys that are loadable by default.
/// </summary>
/// <returns>The created list.</returns>
public static List<KeyInfo> CreateKeyList()
{
// Update this value if more keys are added
var keys = new List<KeyInfo>(70);
// Keys with a group value of -1 are keys that will be read but not written.
// This is for compatibility since some keys had other names in the past.
// TSEC secrets aren't public yet, so the TSEC root keys will be treated as
// root keys even though they're derived.
keys.Add(new KeyInfo(10, Type.DeviceRoot, "secure_boot_key", (set, i) => set.SecureBootKey));
keys.Add(new KeyInfo(11, Type.DeviceRoot, "tsec_key", (set, i) => set.TsecKey));
keys.Add(new KeyInfo(12, Type.DeviceDrvd, "device_key", (set, i) => set.DeviceKey));
keys.Add(new KeyInfo(20, Type.CommonRoot, "tsec_root_kek", (set, i) => set.TsecRootKek));
keys.Add(new KeyInfo(21, Type.CommonRoot, "package1_mac_kek", (set, i) => set.Package1MacKek));
keys.Add(new KeyInfo(22, Type.CommonRoot, "package1_kek", (set, i) => set.Package1Kek));
keys.Add(new KeyInfo(30, Type.CommonRoot, "tsec_auth_signature", 0, 0x20, (set, i) => set.TsecAuthSignatures[i]));
keys.Add(new KeyInfo(40, Type.CommonRoot, "tsec_root_key", 0, 0x20, (set, i) => set.TsecRootKeys[i]));
keys.Add(new KeyInfo(50, Type.CommonSeed, "keyblob_mac_key_source", (set, i) => set.KeyBlobMacKeySource));
keys.Add(new KeyInfo(51, Type.CommonSeed, "keyblob_key_source", 0, 6, (set, i) => set.KeyBlobKeySources[i]));
keys.Add(new KeyInfo(55, Type.DeviceDrvd, "keyblob_key", 0, 6, (set, i) => set.KeyBlobKeys[i]));
keys.Add(new KeyInfo(60, Type.DeviceDrvd, "keyblob_mac_key", 0, 6, (set, i) => set.KeyBlobMacKeys[i]));
keys.Add(new KeyInfo(70, Type.DeviceRoot, "encrypted_keyblob", 0, 6, (set, i) => set.EncryptedKeyBlobs[i].Bytes));
keys.Add(new KeyInfo(80, Type.CommonRoot, "keyblob", 0, 6, (set, i) => set.KeyBlobs[i].Bytes));
keys.Add(new KeyInfo(90, Type.CommonSeed, "master_kek_source", 6, 0x20, (set, i) => set.MasterKekSources[i]));
keys.Add(new KeyInfo(100, Type.CommonRoot, "mariko_bek", (set, i) => set.MarikoBek));
keys.Add(new KeyInfo(101, Type.CommonRoot, "mariko_kek", (set, i) => set.MarikoKek));
keys.Add(new KeyInfo(110, Type.CommonRoot, "mariko_aes_class_key", 0, 0xC, (set, i) => set.MarikoAesClassKeys[i]));
keys.Add(new KeyInfo(120, Type.CommonSeedDiff, "mariko_master_kek_source", 0, 0x20, (set, i) => set.MarikoMasterKekSources[i]));
keys.Add(new KeyInfo(130, Type.CommonDrvd, "master_kek", 0, 0x20, (set, i) => set.MasterKeks[i]));
keys.Add(new KeyInfo(140, Type.CommonSeed, "master_key_source", (set, i) => set.MasterKeySource));
keys.Add(new KeyInfo(150, Type.CommonDrvd, "master_key", 0, 0x20, (set, i) => set.MasterKeys[i]));
keys.Add(new KeyInfo(160, Type.CommonDrvd, "package1_key", 0, 0x20, (set, i) => set.Package1Keys[i]));
keys.Add(new KeyInfo(170, Type.CommonDrvd, "package1_mac_key", 6, 0x20, (set, i) => set.Package1MacKeys[i]));
keys.Add(new KeyInfo(180, Type.CommonSeed, "package2_key_source", (set, i) => set.Package2KeySource));
keys.Add(new KeyInfo(190, Type.CommonDrvd, "package2_key", 0, 0x20, (set, i) => set.Package2Keys[i]));
keys.Add(new KeyInfo(200, Type.CommonSeed, "bis_kek_source", (set, i) => set.BisKekSource));
keys.Add(new KeyInfo(201, Type.CommonSeed, "bis_key_source", 0, 4, (set, i) => set.BisKeySources[i]));
keys.Add(new KeyInfo(205, Type.DeviceDrvd, "bis_key", 0, 4, (set, i) => set.BisKeys[i]));
keys.Add(new KeyInfo(210, Type.CommonSeed, "per_console_key_source", (set, i) => set.PerConsoleKeySource));
keys.Add(new KeyInfo(211, Type.CommonSeed, "retail_specific_aes_key_source", (set, i) => set.RetailSpecificAesKeySource));
keys.Add(new KeyInfo(212, Type.CommonSeed, "aes_kek_generation_source", (set, i) => set.AesKekGenerationSource));
keys.Add(new KeyInfo(213, Type.CommonSeed, "aes_key_generation_source", (set, i) => set.AesKeyGenerationSource));
keys.Add(new KeyInfo(214, Type.CommonSeed, "titlekek_source", (set, i) => set.TitleKekSource));
keys.Add(new KeyInfo(220, Type.CommonDrvd, "titlekek", 0, 0x20, (set, i) => set.TitleKeks[i]));
keys.Add(new KeyInfo(230, Type.CommonSeed, "header_kek_source", (set, i) => set.HeaderKekSource));
keys.Add(new KeyInfo(231, Type.CommonSeed, "header_key_source", (set, i) => set.HeaderKeySource));
keys.Add(new KeyInfo(232, Type.CommonDrvd, "header_key", (set, i) => set.HeaderKey));
keys.Add(new KeyInfo(240, Type.CommonSeed, "key_area_key_application_source", (set, i) => set.KeyAreaKeyApplicationSource));
keys.Add(new KeyInfo(241, Type.CommonSeed, "key_area_key_ocean_source", (set, i) => set.KeyAreaKeyOceanSource));
keys.Add(new KeyInfo(242, Type.CommonSeed, "key_area_key_system_source", (set, i) => set.KeyAreaKeySystemSource));
keys.Add(new KeyInfo(250, Type.CommonSeed, "save_mac_kek_source", (set, i) => set.DeviceUniqueSaveMacKekSource));
keys.Add(new KeyInfo(251, Type.CommonSeed, "save_mac_key_source", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeySources[i]));
keys.Add(new KeyInfo(252, Type.DeviceDrvd, "save_mac_key", 0, 2, (set, i) => set.DeviceUniqueSaveMacKeys[i]));
keys.Add(new KeyInfo(-01, Type.CommonSeed, "save_mac_key_source", (set, i) => set.DeviceUniqueSaveMacKeySources[0]));
keys.Add(new KeyInfo(253, Type.CommonSeed, "save_mac_sd_card_kek_source", (set, i) => set.SeedUniqueSaveMacKekSource));
keys.Add(new KeyInfo(254, Type.CommonSeed, "save_mac_sd_card_key_source", (set, i) => set.SeedUniqueSaveMacKeySource));
keys.Add(new KeyInfo(255, Type.DeviceDrvd, "save_mac_sd_card_key", (set, i) => set.SeedUniqueSaveMacKey));
keys.Add(new KeyInfo(260, Type.DeviceRoot, "sd_seed", (set, i) => set.SdCardEncryptionSeed));
keys.Add(new KeyInfo(261, Type.CommonSeed, "sd_card_kek_source", (set, i) => set.SdCardKekSource));
keys.Add(new KeyInfo(262, Type.CommonSeed, "sd_card_save_key_source", (set, i) => set.SdCardKeySources[0]));
keys.Add(new KeyInfo(263, Type.CommonSeed, "sd_card_nca_key_source", (set, i) => set.SdCardKeySources[1]));
keys.Add(new KeyInfo(264, Type.CommonSeed, "sd_card_custom_storage_key_source", (set, i) => set.SdCardKeySources[2]));
keys.Add(new KeyInfo(270, Type.CommonSeedDiff, "xci_header_key", (set, i) => set.XciHeaderKey));
keys.Add(new KeyInfo(280, Type.CommonRoot, "eticket_rsa_kek", (set, i) => set.ETicketRsaKek));
keys.Add(new KeyInfo(281, Type.CommonRoot, "ssl_rsa_kek", (set, i) => set.SslRsaKek));
keys.Add(new KeyInfo(290, Type.CommonDrvd, "key_area_key_application", 0, 0x20, (set, i) => set.KeyAreaKeys[i][0]));
keys.Add(new KeyInfo(300, Type.CommonDrvd, "key_area_key_ocean", 0, 0x20, (set, i) => set.KeyAreaKeys[i][1]));
keys.Add(new KeyInfo(310, Type.CommonDrvd, "key_area_key_system", 0, 0x20, (set, i) => set.KeyAreaKeys[i][2]));
return keys;
}
}
}

View File

@ -0,0 +1,587 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Spl;
using LibHac.Util;
namespace LibHac.Common.Keys
{
public static class ExternalKeyReader
{
private const int ReadBufferSize = 1024;
// Contains info from a specific key being read from a file
[DebuggerDisplay("{" + nameof(Name) + "}")]
private struct SpecificKeyInfo
{
public KeyInfo Key;
public int Index;
public bool IsDev;
public string Name => Key.Name;
public SpecificKeyInfo(KeyInfo info, int index, bool isDev)
{
Key = info;
Index = index;
IsDev = isDev;
}
}
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 = DefaultKeySet.CreateKeyList();
if (filename != null)
{
using var storage = new FileStream(filename, FileMode.Open, FileAccess.Read);
ReadMainKeys(keySet, storage, keyInfos, logger);
}
if (consoleKeysFilename != null)
{
using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read);
ReadMainKeys(keySet, storage, keyInfos, logger);
}
if (titleKeysFilename != null)
{
using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read);
ReadTitleKeys(keySet, storage, logger);
}
keySet.DeriveKeys(logger);
}
/// <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="prodKeysFilename">The path of the file containing common prod keys. Can be <see langword="null"/>.</param>
/// <param name="devKeysFilename">The path of the file containing common dev 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 prodKeysFilename = null, string devKeysFilename = null,
string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null)
{
KeySet.Mode originalMode = keySet.CurrentMode;
List<KeyInfo> keyInfos = DefaultKeySet.CreateKeyList();
if (prodKeysFilename != null)
{
keySet.SetMode(KeySet.Mode.Prod);
using var storage = new FileStream(prodKeysFilename, FileMode.Open, FileAccess.Read);
ReadMainKeys(keySet, storage, keyInfos, logger);
}
if (devKeysFilename != null)
{
keySet.SetMode(KeySet.Mode.Dev);
using var storage = new FileStream(devKeysFilename, FileMode.Open, FileAccess.Read);
ReadMainKeys(keySet, storage, keyInfos, logger);
}
keySet.SetMode(originalMode);
if (consoleKeysFilename != null)
{
using var storage = new FileStream(consoleKeysFilename, FileMode.Open, FileAccess.Read);
ReadMainKeys(keySet, storage, keyInfos, logger);
}
if (titleKeysFilename != null)
{
using var storage = new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read);
ReadTitleKeys(keySet, storage, logger);
}
keySet.DeriveKeys(logger);
}
/// <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 = KeySet.CreateDefaultKeySet();
keySet.SetMode(mode);
ReadKeyFile(keySet, filename, titleKeysFilename, consoleKeysFilename, logger);
return keySet;
}
/// <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="reader">A <see cref="Stream"/> 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="DefaultKeySet.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, Stream reader, List<KeyInfo> keyList,
IProgressReport logger = null)
{
if (reader == null) return;
using var streamReader = new StreamReader(reader);
Span<char> buffer = stackalloc char[ReadBufferSize];
var ctx = new KvPairReaderContext(streamReader, buffer);
while (true)
{
ReaderStatus status = GetKeyValuePair(ref ctx);
if (status == ReaderStatus.Error)
{
logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\"");
}
else if (status == ReaderStatus.ReadKey)
{
if (!TryGetKeyInfo(out SpecificKeyInfo info, keyList, ctx.CurrentKey))
{
logger?.LogMessage($"Failed to match key {ctx.CurrentKey.ToString()}");
continue;
}
Span<byte> key;
// 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 (ctx.CurrentValue.Length != key.Length * 2)
{
logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentValue.Length}. Must be {key.Length * 2} hex digits.");
continue;
}
if (!StringUtils.TryFromHexString(ctx.CurrentValue, key))
{
key.Clear();
logger?.LogMessage($"Key {ctx.CurrentKey.ToString()} has an invalid value. Must be {key.Length * 2} hex digits.");
}
}
else if (status == ReaderStatus.Finished)
{
break;
}
}
}
/// <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="reader">A <see cref="Stream"/> 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, Stream reader, IProgressReport logger = null)
{
if (reader == null) return;
using var streamReader = new StreamReader(reader);
Span<char> buffer = stackalloc char[ReadBufferSize];
var ctx = new KvPairReaderContext(streamReader, buffer);
// Estimate the number of keys by assuming each line is about 69 bytes.
// Subtract 2 from that so we estimate slightly high.
keySet.ExternalKeySet.EnsureCapacity((int)reader.Length / 67);
while (true)
{
ReaderStatus status = GetKeyValuePair(ref ctx);
if (status == ReaderStatus.Error)
{
logger?.LogMessage($"Invalid line in key data: \"{ctx.CurrentKey.ToString()}\"");
Debugger.Break();
}
else if (status == ReaderStatus.ReadKey)
{
if (ctx.CurrentKey.Length != TitleKeySize * 2)
{
logger?.LogMessage($"Rights ID {ctx.CurrentKey.ToString()} has incorrect size {ctx.CurrentKey.Length}. (Expected {TitleKeySize * 2})");
continue;
}
if (ctx.CurrentValue.Length != TitleKeySize * 2)
{
logger?.LogMessage($"Title key {ctx.CurrentValue.ToString()} has incorrect size {ctx.CurrentValue.Length}. (Expected {TitleKeySize * 2})");
continue;
}
var rightsId = new RightsId();
var titleKey = new AccessKey();
if (!StringUtils.TryFromHexString(ctx.CurrentKey, SpanHelpers.AsByteSpan(ref rightsId)))
{
logger?.LogMessage($"Invalid rights ID \"{ctx.CurrentKey.ToString()}\" in title key file");
continue;
}
if (!StringUtils.TryFromHexString(ctx.CurrentValue, SpanHelpers.AsByteSpan(ref titleKey)))
{
logger?.LogMessage($"Invalid title key \"{ctx.CurrentValue.ToString()}\" in title key file");
continue;
}
keySet.ExternalKeySet.Add(rightsId, titleKey).ThrowIfFailure();
}
else if (status == ReaderStatus.Finished)
{
break;
}
}
}
private ref struct KvPairReaderContext
{
public TextReader Reader;
public Span<char> Buffer;
public Span<char> CurrentKey;
public Span<char> CurrentValue;
public int BufferPos;
public bool NeedFillBuffer;
public bool HasReadEndOfFile;
public bool SkipNextLine;
public KvPairReaderContext(TextReader reader, Span<char> buffer)
{
Reader = reader;
Buffer = buffer;
CurrentKey = default;
CurrentValue = default;
BufferPos = buffer.Length;
NeedFillBuffer = true;
HasReadEndOfFile = false;
SkipNextLine = false;
}
}
private enum ReaderStatus
{
ReadKey,
NoKeyRead,
ReadComment,
Finished,
LineTooLong,
Error
}
private enum ReaderState
{
Initial,
Comment,
Key,
WhiteSpace1,
Delimiter,
Value,
WhiteSpace2,
Success,
CommentSuccess,
Error
}
private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader)
{
Span<char> buffer = reader.Buffer;
if (reader.BufferPos == buffer.Length && reader.HasReadEndOfFile)
{
// There is no more text to parse. Return that we've finished.
return ReaderStatus.Finished;
}
if (reader.NeedFillBuffer)
{
// Move unread text to the front of the buffer
buffer.Slice(reader.BufferPos).CopyTo(buffer);
int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos));
// ReadBlock will only read less than the buffer size if there's nothing left to read
if (charsRead != reader.BufferPos)
{
buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead);
reader.Buffer = buffer;
reader.HasReadEndOfFile = true;
}
reader.NeedFillBuffer = false;
reader.BufferPos = 0;
}
if (reader.SkipNextLine)
{
while (reader.BufferPos < buffer.Length && !IsEndOfLine(buffer[reader.BufferPos]))
{
reader.BufferPos++;
}
// Stop skipping once we reach a new line
if (reader.BufferPos < buffer.Length)
{
reader.SkipNextLine = false;
}
}
// Skip any empty lines
while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos]))
{
reader.BufferPos++;
}
var state = ReaderState.Initial;
int keyOffset = -1;
int keyLength = -1;
int valueOffset = -1;
int valueLength = -1;
int i;
for (i = reader.BufferPos; i < buffer.Length; i++)
{
char c = buffer[i];
switch (state)
{
case ReaderState.Initial when IsWhiteSpace(c):
continue;
case ReaderState.Initial when IsValidNameChar(c):
state = ReaderState.Key;
keyOffset = i;
// Skip the next few rounds through the state machine since we know we should be
// encountering a string of name characters
do
{
ToLower(ref buffer[i]);
i++;
} while (i < buffer.Length && IsValidNameChar(buffer[i]));
// Decrement so we can process this character the next round through the state machine
i--;
continue;
case ReaderState.Initial when c == '#':
state = ReaderState.Comment;
keyOffset = i;
continue;
case ReaderState.Initial when IsEndOfLine(c):
// The line was empty. Update the buffer position to indicate a new line
reader.BufferPos = i;
continue;
case ReaderState.Key when IsWhiteSpace(c):
state = ReaderState.WhiteSpace1;
keyLength = i - keyOffset;
continue;
case ReaderState.Key when IsDelimiter(c):
state = ReaderState.Delimiter;
keyLength = i - keyOffset;
continue;
case ReaderState.WhiteSpace1 when IsWhiteSpace(c):
continue;
case ReaderState.WhiteSpace1 when IsDelimiter(c):
state = ReaderState.Delimiter;
continue;
case ReaderState.Delimiter when IsWhiteSpace(c):
continue;
case ReaderState.Delimiter when StringUtils.IsHexDigit((byte)c):
state = ReaderState.Value;
valueOffset = i;
do
{
i++;
} while (i < buffer.Length && !IsEndOfLine(buffer[i]) && !IsWhiteSpace(buffer[i]));
i--;
continue;
case ReaderState.Value when IsEndOfLine(c):
state = ReaderState.Success;
valueLength = i - valueOffset;
break;
case ReaderState.Value when IsWhiteSpace(c):
state = ReaderState.WhiteSpace2;
valueLength = i - valueOffset;
continue;
case ReaderState.WhiteSpace2 when IsWhiteSpace(c):
continue;
case ReaderState.WhiteSpace2 when IsEndOfLine(c):
state = ReaderState.Success;
break;
case ReaderState.Comment when IsEndOfLine(c):
keyLength = i - keyOffset;
state = ReaderState.CommentSuccess;
break;
case ReaderState.Comment:
continue;
// If none of the expected characters were found while in these states, the
// line is considered invalid.
case ReaderState.Initial:
case ReaderState.Key:
case ReaderState.WhiteSpace1:
case ReaderState.Delimiter:
state = ReaderState.Error;
continue;
case ReaderState.Error when !IsEndOfLine(c):
continue;
}
// We've exited the state machine for one reason or another
break;
}
// First check if hit the end of the buffer or read the entire buffer without seeing a new line
if (i == buffer.Length && !reader.HasReadEndOfFile)
{
reader.NeedFillBuffer = true;
// If the entire buffer is part of a single long line
if (reader.BufferPos == 0 || reader.SkipNextLine)
{
reader.BufferPos = i;
// The line might continue past the end of the current buffer, so skip the
// remainder of the line after the buffer is refilled.
reader.SkipNextLine = true;
return ReaderStatus.LineTooLong;
}
return ReaderStatus.NoKeyRead;
}
// The only way we should exit the loop in the "Value" or "WhiteSpace2" state is if we reached
// the end of the buffer in that state, meaning i == buffer.Length.
// Running out of buffer when we haven't read the end of the file will have been handled by the
// previous "if" block. If we get to this point in those states, we should be at the very end
// of the file which will be treated as the end of a line.
if (state == ReaderState.Value || state == ReaderState.WhiteSpace2)
{
Assert.AssertTrue(i == buffer.Length);
Assert.AssertTrue(reader.HasReadEndOfFile);
// WhiteSpace2 will have already set this value
if (state == ReaderState.Value)
valueLength = i - valueOffset;
state = ReaderState.Success;
}
// Same situation as the two above states
if (state == ReaderState.Comment)
{
Assert.AssertTrue(i == buffer.Length);
Assert.AssertTrue(reader.HasReadEndOfFile);
keyLength = i - keyOffset;
state = ReaderState.CommentSuccess;
}
// Same as the above states except the final line was empty or whitespace.
if (state == ReaderState.Initial)
{
Assert.AssertTrue(i == buffer.Length);
Assert.AssertTrue(reader.HasReadEndOfFile);
reader.BufferPos = i;
return ReaderStatus.NoKeyRead;
}
// If we successfully read both the key and value
if (state == ReaderState.Success)
{
reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength);
reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength);
reader.BufferPos = i;
return ReaderStatus.ReadKey;
}
if (state == ReaderState.CommentSuccess)
{
reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength);
reader.BufferPos = i;
return ReaderStatus.ReadComment;
}
// A bad line was encountered if we're in any of the other states
// Return the line as "CurrentKey"
reader.CurrentKey = reader.Buffer.Slice(reader.BufferPos, i - reader.BufferPos);
reader.BufferPos = i;
return ReaderStatus.Error;
static bool IsWhiteSpace(char c) => c == ' ' || c == '\t';
static bool IsDelimiter(char c) => c == '=' || c == ',';
static bool IsEndOfLine(char c) => c == '\0' || c == '\r' || c == '\n';
static void ToLower(ref char c)
{
// The only characters we need to worry about are underscores and alphanumerics
// Both lowercase and numbers have bit 5 set, so they're both treated the same
if (c != '_')
{
c |= (char)0b100000;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidNameChar(char c)
{
return (c | 0x20u) - 'a' <= 'z' - 'a' || (uint)(c - '0') <= 9 || c == '_';
}
private static bool TryGetKeyInfo(out SpecificKeyInfo info, List<KeyInfo> keyList, ReadOnlySpan<char> keyName)
{
for (int i = 0; i < keyList.Count; i++)
{
if (keyList[i].Matches(keyName, out int keyIndex, out bool isDev))
{
info = new SpecificKeyInfo(keyList[i], keyIndex, isDev);
return true;
}
}
info = default;
return false;
}
}
}

View File

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LibHac.Fs;
using LibHac.Spl;
using LibHac.Util;
using Type = LibHac.Common.Keys.KeyInfo.KeyType;
using RangeType = LibHac.Common.Keys.KeyInfo.KeyRangeType;
namespace LibHac.Common.Keys
{
public static class ExternalKeyWriter
{
public static void PrintKeys(KeySet keySet, StringBuilder sb, List<KeyInfo> keys, Type filter, bool isDev)
{
if (keys.Count == 0) return;
string devSuffix = isDev ? "_dev" : string.Empty;
int maxNameLength = keys.Max(x => x.NameLength);
int currentGroup = 0;
// Todo: Better filtering
bool FilterMatches(KeyInfo keyInfo)
{
Type filter1 = filter & (Type.Common | Type.Device);
Type filter2 = filter & (Type.Root | Type.Seed | Type.Derived);
Type filter3 = filter & Type.DifferentDev;
// Skip sub-filters that have no flags set
return (filter1 == 0 || (filter1 & keyInfo.Type) != 0) &&
(filter2 == 0 || (filter2 & keyInfo.Type) != 0) &&
filter3 == (filter3 & keyInfo.Type) ||
isDev && keyInfo.Type.HasFlag(Type.DifferentDev);
}
bool isFirstPrint = true;
foreach (KeyInfo info in keys.Where(x => x.Group >= 0).Where(FilterMatches)
.OrderBy(x => x.Group).ThenBy(x => x.Name))
{
bool isNewGroup = false;
if (info.Group == currentGroup + 1)
{
currentGroup = info.Group;
}
else if (info.Group > currentGroup)
{
// Don't update the current group yet because if this key is empty and the next key
// is in the same group, we need to be able to know to add a blank line before printing it.
isNewGroup = !isFirstPrint;
}
if (info.RangeType == RangeType.Single)
{
Span<byte> key = info.Getter(keySet, 0);
if (key.IsZeros())
continue;
if (isNewGroup)
{
sb.AppendLine();
}
string keyName = $"{info.Name}{devSuffix}";
string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}";
sb.AppendLine(line);
isFirstPrint = false;
currentGroup = info.Group;
}
else if (info.RangeType == RangeType.Range)
{
bool hasPrintedKey = false;
for (int i = info.RangeStart; i < info.RangeEnd; i++)
{
Span<byte> key = info.Getter(keySet, i);
if (key.IsZeros())
continue;
if (hasPrintedKey == false)
{
if (isNewGroup)
{
sb.AppendLine();
}
hasPrintedKey = true;
}
string keyName = $"{info.Name}{devSuffix}_{i:x2}";
string line = $"{keyName.PadRight(maxNameLength)} = {key.ToHexString()}";
sb.AppendLine(line);
isFirstPrint = false;
currentGroup = info.Group;
}
}
}
}
public static string PrintTitleKeys(KeySet keySet)
{
var sb = new StringBuilder();
foreach ((RightsId rightsId, AccessKey key) kv in keySet.ExternalKeySet.ToList()
.OrderBy(x => x.rightsId.ToString()))
{
string line = $"{kv.rightsId} = {kv.key}";
sb.AppendLine(line);
}
return sb.ToString();
}
public static string PrintCommonKeys(KeySet keySet)
{
var sb = new StringBuilder();
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived,
false);
return sb.ToString();
}
public static string PrintDeviceKeys(KeySet keySet)
{
var sb = new StringBuilder();
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Device, false);
return sb.ToString();
}
public static string PrintAllKeys(KeySet keySet)
{
var sb = new StringBuilder();
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), 0, false);
return sb.ToString();
}
public static string PrintAllSeeds(KeySet keySet)
{
var sb = new StringBuilder();
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed, false);
if (keySet.CurrentMode == KeySet.Mode.Prod)
{
sb.AppendLine();
keySet.SetMode(KeySet.Mode.Dev);
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Seed | Type.DifferentDev, true);
keySet.SetMode(KeySet.Mode.Prod);
}
return sb.ToString();
}
public static string PrintCommonKeysWithDev(KeySet keySet)
{
KeySet.Mode originalMode = keySet.CurrentMode;
var sb = new StringBuilder();
keySet.SetMode(KeySet.Mode.Prod);
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Seed | Type.Derived,
false);
sb.AppendLine();
keySet.SetMode(KeySet.Mode.Dev);
PrintKeys(keySet, sb, DefaultKeySet.CreateKeyList(), Type.Common | Type.Root | Type.Derived, true);
keySet.SetMode(originalMode);
return sb.ToString();
}
}
}

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);
DerivePerGenerationKeys(keySet);
DeriveNcaHeaderKey(keySet);
DeriveSdCardKeys(keySet);
}
private static void DeriveKeyBlobKeys(KeySet s)
{
if (s.SecureBootKey.IsZeros() || s.TsecKey.IsZeros()) return;
bool haveKeyBlobMacKeySource = !s.MasterKeySource.IsZeros();
var temp = new AesKey();
for (int i = 0; i < KeySet.UsedKeyBlobCount; i++)
{
if (s.KeyBlobKeySources[i].IsZeros()) 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].IsZeros() || s.KeyBlobMacKeys[i].IsZeros() || s.EncryptedKeyBlobs[i].IsZeros())
{
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].IsZeros()) 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.IsZeros();
bool havePackage1MacKek = !s.Package1MacKek.IsZeros();
bool havePackage1Kek = !s.Package1Kek.IsZeros();
for (int i = KeySet.UsedKeyBlobCount; i < KeySet.KeyRevisionCount; i++)
{
if (s.TsecAuthSignatures[i - KeySet.UsedKeyBlobCount].IsZeros())
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].IsZeros() || s.MasterKekSources[i].IsZeros()) continue;
Aes.DecryptEcb128(s.MasterKekSources[i], s.MasterKeks[i], s.TsecRootKeys[tsecRootKeyIndex]);
}
}
private static void DeriveMarikoMasterKeks(KeySet s)
{
if (s.MarikoKek.IsZeros()) return;
for (int i = 0; i < KeySet.KeyRevisionCount; i++)
{
if (s.MarikoMasterKekSources[i].IsZeros()) continue;
Aes.DecryptEcb128(s.MarikoMasterKekSources[i], s.MasterKeks[i], s.MarikoKek);
}
}
private static void DeriveMasterKeys(KeySet s)
{
if (s.MasterKeySource.IsZeros()) return;
for (int i = 0; i < KeySet.KeyRevisionCount; i++)
{
if (s.MasterKeks[i].IsZeros()) 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].IsZeros())
{
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.IsZeros();
}
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.IsZeros() && !s.KeyBlobKeys[0].IsZeros())
{
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.IsZeros() && !s.DeviceUniqueSaveMacKeySources[i].IsZeros() &&
!s.DeviceKey.IsZeros())
{
GenerateKek(s.DeviceKey, s.DeviceUniqueSaveMacKekSource, kek, s.AesKekGenerationSource, null);
Aes.DecryptEcb128(s.DeviceUniqueSaveMacKeySources[i], s.DeviceUniqueSaveMacKeys[i], kek);
}
}
// Derive BIS keys
if (s.DeviceKey.IsZeros()
|| s.BisKekSource.IsZeros()
|| s.AesKekGenerationSource.IsZeros()
|| s.AesKeyGenerationSource.IsZeros()
|| s.RetailSpecificAesKeySource.IsZeros())
{
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].IsZeros() && !s.BisKeySources[2].IsZeros())
{
s.BisKeySources[3] = s.BisKeySources[2];
}
Aes.DecryptEcb128(s.RetailSpecificAesKeySource, kek, s.DeviceKey);
if (!s.BisKeySources[0].IsZeros()) 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].IsZeros())
Aes.DecryptEcb128(s.BisKeySources[i], s.BisKeys[i], kek);
}
}
private static void DerivePerGenerationKeys(KeySet s)
{
bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros();
bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros();
bool haveKakSource2 = !s.KeyAreaKeySystemSource.IsZeros();
bool haveTitleKekSource = !s.TitleKekSource.IsZeros();
bool havePackage2KeySource = !s.Package2KeySource.IsZeros();
for (int i = 0; i < KeySet.KeyRevisionCount; i++)
{
if (s.MasterKeys[i].IsZeros())
{
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.IsZeros() || s.HeaderKeySource.IsZeros() || s.MasterKeys[0].IsZeros()) 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.IsZeros() && !s.SeedUniqueSaveMacKeySource.IsZeros())
{
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.IsZeros())
{
Aes.DecryptEcb128(keySeed, dest, srcKek);
}
else
{
srcKek.Data.CopyTo(dest);
}
}
}
}

View File

@ -0,0 +1,167 @@
using System;
using System.Diagnostics;
using LibHac.Diag;
using LibHac.Util;
namespace LibHac.Common.Keys
{
[DebuggerDisplay("{" + nameof(Name) + "}")]
public readonly struct KeyInfo
{
public enum KeyRangeType : byte
{
Single,
Range
}
[Flags]
public enum KeyType : byte
{
Common = 1 << 0,
Device = 1 << 1,
Root = 1 << 2,
Seed = 1 << 3,
Derived = 1 << 4,
/// <summary>Specifies that a seed is different in prod and dev.</summary>
DifferentDev = 1 << 5,
CommonRoot = Common | Root,
CommonSeed = Common | Seed,
CommonSeedDiff = Common | Seed | DifferentDev,
CommonDrvd = Common | Derived,
DeviceRoot = Device | Root,
DeviceDrvd = Device | Derived,
}
public readonly string Name;
public readonly KeyGetter Getter;
public readonly int Group;
public readonly KeyRangeType RangeType;
public readonly KeyType Type;
public readonly byte RangeStart;
public readonly byte RangeEnd;
public int NameLength => Name.Length + (RangeType == KeyRangeType.Range ? 3 : 0);
public delegate Span<byte> KeyGetter(KeySet keySet, int i);
public KeyInfo(int group, KeyType type, string name, KeyGetter retrieveFunc)
{
Assert.AssertTrue(IsKeyTypeValid(type));
Name = name;
RangeType = KeyRangeType.Single;
Type = type;
RangeStart = default;
RangeEnd = default;
Group = group;
Getter = retrieveFunc;
}
public KeyInfo(int group, KeyType type, string name, byte rangeStart, byte rangeEnd, KeyGetter retrieveFunc)
{
Assert.AssertTrue(IsKeyTypeValid(type));
Name = name;
RangeType = KeyRangeType.Range;
Type = type;
RangeStart = rangeStart;
RangeEnd = rangeEnd;
Group = group;
Getter = retrieveFunc;
}
public bool Matches(ReadOnlySpan<char> keyName, out int keyIndex, out bool isDev)
{
keyIndex = default;
isDev = default;
return RangeType switch
{
KeyRangeType.Single => MatchesSingle(keyName, out isDev),
KeyRangeType.Range => MatchesRangedKey(keyName, ref keyIndex, out isDev),
_ => false
};
}
private bool MatchesSingle(ReadOnlySpan<char> keyName, out bool isDev)
{
Assert.Equal((int)KeyRangeType.Single, (int)RangeType);
isDev = false;
if (keyName.Length == NameLength + 4)
{
// Might be a dev key. Check if "_dev" comes after the base key name
if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev"))
return false;
isDev = true;
}
else if (keyName.Length != NameLength)
{
return false;
}
// Check if the base name matches
if (!keyName.Slice(0, Name.Length).SequenceEqual(Name))
return false;
return true;
}
private bool MatchesRangedKey(ReadOnlySpan<char> keyName, ref int keyIndex, out bool isDev)
{
Assert.Equal((int)KeyRangeType.Range, (int)RangeType);
isDev = false;
// Check if this is an explicit dev key
if (keyName.Length == Name.Length + 7)
{
// Check if "_dev" comes after the base key name
if (!keyName.Slice(Name.Length, 4).SequenceEqual("_dev"))
return false;
isDev = true;
}
// Not a dev key. Check that the length of the key name with the trailing index matches
else if (keyName.Length != Name.Length + 3)
return false;
// Check if the name before the "_XX" index matches
if (!keyName.Slice(0, Name.Length).SequenceEqual(Name))
return false;
// The name should have an underscore before the index value
if (keyName[keyName.Length - 3] != '_')
return false;
byte index = default;
// Try to get the index of the key name
if (!StringUtils.TryFromHexString(keyName.Slice(keyName.Length - 2, 2), SpanHelpers.AsSpan(ref index)))
return false;
// Check if the index is in this key's range
if (index < RangeStart || index >= RangeEnd)
return false;
keyIndex = index;
return true;
}
private static bool IsKeyTypeValid(KeyType type)
{
// Make sure the type has exactly one flag set for each type
KeyType type1 = type & (KeyType.Common | KeyType.Device);
KeyType type2 = type & (KeyType.Root | KeyType.Seed | KeyType.Derived);
bool isValid1 = type1 == KeyType.Common || type1 == KeyType.Device;
bool isValid2 = type2 == KeyType.Root || type2 == KeyType.Seed || type2 == KeyType.Derived;
return isValid1 && isValid2;
}
}
}

View File

@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using LibHac.Boot;
using LibHac.Common.FixedArrays;
using LibHac.Crypto;
using LibHac.FsSrv;
using LibHac.Util;
namespace LibHac.Common.Keys
{
public class KeySet
{
public enum Mode
{
Dev,
Prod
}
/// <summary>
/// The number of keyblobs that were used for &lt; 6.2.0 crypto
/// </summary>
internal const int UsedKeyBlobCount = 6;
internal const int SdCardKeyIdCount = 3;
internal const int KeyRevisionCount = 0x20;
private AllKeys _keys;
private Mode _mode = Mode.Prod;
public ref AllKeys KeyStruct => ref _keys;
public Mode CurrentMode => _mode;
private ref RootKeys RootKeys => ref _mode == Mode.Dev ? ref _keys._rootKeysDev : ref _keys._rootKeysProd;
private ref StoredKeys StoredKeys => ref _mode == Mode.Dev ? ref _keys._storedKeysDev : ref _keys._storedKeysProd;
private ref DerivedKeys DerivedKeys => ref _mode == Mode.Dev ? ref _keys._derivedKeysDev : ref _keys._derivedKeysProd;
private ref RsaSigningKeys RsaSigningKeys => ref _mode == Mode.Dev ? ref _keys._rsaSigningKeysDev : ref _keys._rsaSigningKeysProd;
private ref RsaKeys RsaKeys => ref _keys._rsaKeys;
private ref RsaSigningKeyParameters RsaSigningKeyParams => ref _mode == Mode.Dev
? ref _rsaSigningKeyParamsDev
: ref _rsaSigningKeyParamsProd;
public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet();
public Span<AesKey> MarikoAesClassKeys => RootKeys.MarikoAesClassKeys.Items;
public ref AesKey MarikoKek => ref RootKeys.MarikoKek;
public ref AesKey MarikoBek => ref RootKeys.MarikoBek;
public Span<KeyBlob> KeyBlobs => RootKeys.KeyBlobs.Items;
public Span<AesKey> KeyBlobKeySources => _keys._keySeeds.KeyBlobKeySources.Items;
public ref AesKey KeyBlobMacKeySource => ref _keys._keySeeds.KeyBlobMacKeySource;
public ref AesKey TsecRootKek => ref RootKeys.TsecRootKek;
public ref AesKey Package1MacKek => ref RootKeys.Package1MacKek;
public ref AesKey Package1Kek => ref RootKeys.Package1Kek;
public Span<AesKey> TsecAuthSignatures => RootKeys.TsecAuthSignatures.Items;
public Span<AesKey> TsecRootKeys => RootKeys.TsecRootKeys.Items;
public Span<AesKey> MasterKekSources => _keys._keySeeds.MasterKekSources.Items;
public Span<AesKey> MarikoMasterKekSources => _mode == Mode.Dev
? _keys._keySeeds.MarikoMasterKekSources_dev.Items
: _keys._keySeeds.MarikoMasterKekSources.Items;
public Span<AesKey> MasterKeks => DerivedKeys.MasterKeks.Items;
public ref AesKey MasterKeySource => ref _keys._keySeeds.MasterKeySource;
public Span<AesKey> MasterKeys => DerivedKeys.MasterKeys.Items;
public Span<AesKey> Package1MacKeys => DerivedKeys.Package1MacKeys.Items;
public Span<AesKey> Package1Keys => DerivedKeys.Package1Keys.Items;
public Span<AesKey> Package2Keys => DerivedKeys.Package2Keys.Items;
public ref AesKey Package2KeySource => ref _keys._keySeeds.Package2KeySource;
public ref AesKey PerConsoleKeySource => ref _keys._keySeeds.PerConsoleKeySource;
public ref AesKey RetailSpecificAesKeySource => ref _keys._keySeeds.RetailSpecificAesKeySource;
public ref AesKey BisKekSource => ref _keys._keySeeds.BisKekSource;
public Span<AesXtsKey> BisKeySources => _keys._keySeeds.BisKeySources.Items;
public ref AesKey AesKekGenerationSource => ref _keys._keySeeds.AesKekGenerationSource;
public ref AesKey AesKeyGenerationSource => ref _keys._keySeeds.AesKeyGenerationSource;
public ref AesKey KeyAreaKeyApplicationSource => ref _keys._keySeeds.KeyAreaKeyApplicationSource;
public ref AesKey KeyAreaKeyOceanSource => ref _keys._keySeeds.KeyAreaKeyOceanSource;
public ref AesKey KeyAreaKeySystemSource => ref _keys._keySeeds.KeyAreaKeySystemSource;
public ref AesKey TitleKekSource => ref _keys._keySeeds.TitleKekSource;
public ref AesKey HeaderKekSource => ref _keys._keySeeds.HeaderKekSource;
public ref AesKey SdCardKekSource => ref _keys._keySeeds.SdCardKekSource;
public Span<AesXtsKey> SdCardKeySources => _keys._keySeeds.SdCardKeySources.Items;
public ref AesKey DeviceUniqueSaveMacKekSource => ref _keys._keySeeds.DeviceUniqueSaveMacKekSource;
public Span<AesKey> DeviceUniqueSaveMacKeySources => _keys._keySeeds.DeviceUniqueSaveMacKeySources.Items;
public ref AesKey SeedUniqueSaveMacKekSource => ref _keys._keySeeds.SeedUniqueSaveMacKekSource;
public ref AesKey SeedUniqueSaveMacKeySource => ref _keys._keySeeds.SeedUniqueSaveMacKeySource;
public ref AesXtsKey HeaderKeySource => ref _keys._keySeeds.HeaderKeySource;
public ref AesXtsKey HeaderKey => ref DerivedKeys.HeaderKey;
public Span<AesKey> TitleKeks => DerivedKeys.TitleKeks.Items;
public Span<Array3<AesKey>> KeyAreaKeys => DerivedKeys.KeyAreaKeys.Items;
public ref AesKey XciHeaderKey => ref StoredKeys.XciHeaderKey;
public ref AesKey ETicketRsaKek => ref DerivedKeys.ETicketRsaKek;
public ref AesKey SslRsaKek => ref DerivedKeys.SslRsaKek;
public ref AesKey SecureBootKey => ref _keys._deviceKeys.SecureBootKey;
public ref AesKey TsecKey => ref _keys._deviceKeys.TsecKey;
public Span<AesKey> KeyBlobKeys => _keys._deviceKeys.KeyBlobKeys.Items;
public Span<AesKey> KeyBlobMacKeys => _keys._deviceKeys.KeyBlobMacKeys.Items;
public Span<EncryptedKeyBlob> EncryptedKeyBlobs => _keys._deviceKeys.EncryptedKeyBlobs.Items;
public ref AesKey DeviceKey => ref _keys._deviceKeys.DeviceKey;
public Span<AesXtsKey> BisKeys => _keys._deviceKeys.BisKeys.Items;
public Span<AesKey> DeviceUniqueSaveMacKeys => _keys._deviceKeys.DeviceUniqueSaveMacKeys.Items;
public ref AesKey SeedUniqueSaveMacKey => ref _keys._deviceKeys.SeedUniqueSaveMacKey;
public ref AesKey SdCardEncryptionSeed => ref _keys._deviceKeys.SdCardEncryptionSeed;
// 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> NcaHeaderSigningKeyParams
{
get
{
ref Optional<Array2<RSAParameters>> keys = ref RsaSigningKeyParams.NcaHeaderSigningKeys;
if (!keys.HasValue)
{
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> AcidSigningKeyParams
{
get
{
ref Optional<Array2<RSAParameters>> keys = ref RsaSigningKeyParams.AcidSigningKeys;
if (!keys.HasValue)
{
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 Package2SigningKeyParams
{
get
{
ref Optional<RSAParameters> keys = ref RsaSigningKeyParams.Package2SigningKey;
if (!keys.HasValue)
{
keys.Set(new RSAParameters());
keys.Value = CreateRsaParameters(in Package2SigningKey);
}
return ref keys.Value;
}
}
public ref RSAParameters BetaNca0KeyAreaKeyParams
{
get
{
ref Optional<RSAParameters> keys = ref _rsaKeyParams.BetaNca0KeyAreaKey;
if (!keys.HasValue)
{
keys.Set(CreateRsaParameters(in BetaNca0KeyAreaKey));
}
return ref keys.Value;
}
}
public void SetSdSeed(ReadOnlySpan<byte> sdSeed)
{
if (sdSeed.Length != 0x10)
throw new ArgumentException("Sd card encryption seed must be 16 bytes long.");
sdSeed.CopyTo(SdCardEncryptionSeed);
DeriveSdCardKeys();
}
public void SetMode(Mode mode) => _mode = mode;
/// <summary>
/// Returns a new <see cref="KeySet"/> containing any keys that have been compiled into the library.
/// </summary>
/// <returns>The created <see cref="KeySet"/>,</returns>
public static KeySet CreateDefaultKeySet()
{
return DefaultKeySet.CreateDefaultKeySet();
}
public static List<KeyInfo> CreateKeyInfoList()
{
return DefaultKeySet.CreateKeyList();
}
public void DeriveKeys(IProgressReport logger = null)
{
Mode originalMode = CurrentMode;
SetMode(Mode.Prod);
KeyDerivation.DeriveAllKeys(this, logger);
SetMode(Mode.Dev);
KeyDerivation.DeriveAllKeys(this, logger);
SetMode(originalMode);
}
public void DeriveSdCardKeys() => KeyDerivation.DeriveSdCardKeys(this);
private static RSAParameters CreateRsaParameters(in RsaKey key)
{
return new RSAParameters
{
Exponent = key.PublicExponent.DataRo.ToArray(),
Modulus = key.Modulus.DataRo.ToArray()
};
}
private static RSAParameters CreateRsaParameters(in RsaFullKey key)
{
return new RSAParameters
{
D = key.PrivateExponent.DataRo.ToArray(),
DP = key.Dp.DataRo.ToArray(),
DQ = key.Dq.DataRo.ToArray(),
Exponent = key.PublicExponent.DataRo.ToArray(),
InverseQ = key.InverseQ.DataRo.ToArray(),
Modulus = key.Modulus.DataRo.ToArray(),
P = key.P.DataRo.ToArray(),
Q = key.Q.DataRo.ToArray()
};
}
private struct RsaSigningKeyParameters
{
public Optional<Array2<RSAParameters>> NcaHeaderSigningKeys;
public Optional<Array2<RSAParameters>> AcidSigningKeys;
public Optional<RSAParameters> Package2SigningKey;
}
private struct RsaKeyParameters
{
public Optional<RSAParameters> BetaNca0KeyAreaKey;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct AllKeys
{
public RootKeys _rootKeysDev;
public RootKeys _rootKeysProd;
public KeySeeds _keySeeds;
public StoredKeys _storedKeysDev;
public StoredKeys _storedKeysProd;
public DerivedKeys _derivedKeysDev;
public DerivedKeys _derivedKeysProd;
public DeviceKeys _deviceKeys;
public RsaSigningKeys _rsaSigningKeysDev;
public RsaSigningKeys _rsaSigningKeysProd;
public RsaKeys _rsaKeys;
}
[StructLayout(LayoutKind.Sequential)]
public struct RootKeys
{
// Mariko keys. The AES class keys are currently unused.
public AesKey MarikoKek;
public AesKey MarikoBek;
public Array12<AesKey> MarikoAesClassKeys;
// The key blobs are technically derived from the encrypted key blobs and their keys,
// however those keys are device-unique. The decrypted key blobs are basically the common root
// keys used by pre-6.2.0 Erista.
public Array32<KeyBlob> KeyBlobs;
// Used by TSEC in >= 6.2.0 Erista firmware
public Array32<AesKey> TsecAuthSignatures;
public AesKey TsecRootKek;
public AesKey Package1MacKek;
public AesKey Package1Kek;
// Derived by TSEC. This is the first public root key for >= 6.2.0 Erista
public Array32<AesKey> TsecRootKeys;
}
[StructLayout(LayoutKind.Sequential)]
public struct KeySeeds
{
public Array32<AesKey> KeyBlobKeySources;
public AesKey KeyBlobMacKeySource;
public Array32<AesKey> MasterKekSources;
public Array32<AesKey> MarikoMasterKekSources;
public Array32<AesKey> MarikoMasterKekSources_dev;
public AesKey MasterKeySource;
public AesKey Package2KeySource;
public AesKey PerConsoleKeySource;
public AesKey RetailSpecificAesKeySource;
public AesKey BisKekSource;
public Array4<AesXtsKey> BisKeySources;
public AesKey AesKekGenerationSource;
public AesKey AesKeyGenerationSource;
public AesKey KeyAreaKeyApplicationSource;
public AesKey KeyAreaKeyOceanSource;
public AesKey KeyAreaKeySystemSource;
public AesKey TitleKekSource;
public AesKey HeaderKekSource;
public AesKey SdCardKekSource;
public Array3<AesXtsKey> SdCardKeySources;
public AesKey DeviceUniqueSaveMacKekSource;
public Array2<AesKey> DeviceUniqueSaveMacKeySources;
public AesKey SeedUniqueSaveMacKekSource;
public AesKey SeedUniqueSaveMacKeySource;
public AesXtsKey HeaderKeySource;
}
/// <summary>
/// Holds keys that are stored directly in Horizon programs.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct StoredKeys
{
public AesKey XciHeaderKey;
}
[StructLayout(LayoutKind.Sequential)]
public struct DerivedKeys
{
public Array32<AesKey> MasterKeks;
public Array32<AesKey> MasterKeys;
public Array32<AesKey> Package1MacKeys;
public Array32<AesKey> Package1Keys;
public Array32<AesKey> Package2Keys;
public Array32<Array3<AesKey>> KeyAreaKeys;
public Array32<AesKey> TitleKeks;
public AesXtsKey HeaderKey;
public AesKey ETicketRsaKek;
public AesKey SslRsaKek;
}
[StructLayout(LayoutKind.Sequential)]
public struct DeviceKeys
{
public AesKey SecureBootKey;
public AesKey TsecKey;
public Array32<AesKey> KeyBlobKeys;
public Array32<AesKey> KeyBlobMacKeys;
public Array32<EncryptedKeyBlob> EncryptedKeyBlobs;
public AesKey DeviceKey;
public Array4<AesXtsKey> BisKeys;
public Array2<AesKey> DeviceUniqueSaveMacKeys;
public AesKey SeedUniqueSaveMacKey;
public AesKey SdCardEncryptionSeed;
public Array3<AesXtsKey> SdCardEncryptionKeys;
}
[StructLayout(LayoutKind.Sequential)]
public struct RsaSigningKeys
{
public Array2<RsaKey> NcaHeaderSigningKeys;
public Array2<RsaKey> AcidSigningKeys;
public RsaKey Package2SigningKey;
}
[StructLayout(LayoutKind.Sequential)]
public struct RsaKeys
{
public RsaFullKey BetaNca0KeyAreaKey;
}
}

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -83,5 +83,14 @@ namespace LibHac.Common
public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032);
/// <summary>Error code: 2428-1033; Inner value: 0x813ac</summary>
public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033);
/// <summary>Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac</summary>
public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); }
/// <summary>Error code: 2428-1041; Inner value: 0x823ac</summary>
public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041);
/// <summary>Error code: 2428-1042; Inner value: 0x825ac</summary>
public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042);
/// <summary>Error code: 2428-1043; Inner value: 0x827ac</summary>
public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043);
}
}

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Text;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -3,6 +3,7 @@ using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Text;
using LibHac.Util;
namespace LibHac.Common
{

View File

@ -1,5 +1,6 @@
// ReSharper disable AssignmentIsFullyDiscarded
using System;
using System.Runtime.CompilerServices;
using LibHac.Diag;
#if HAS_INTRINSICS
using LibHac.Crypto.Detail;
@ -133,6 +134,7 @@ namespace LibHac.Crypto
cipher.Transform(input, output);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void DecryptEcb128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
bool preferDotNetCrypto = false)
{

View File

@ -0,0 +1,166 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Crypto
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct AesKey
{
private const int Size = 0x10;
[FieldOffset(0)] private byte _byte;
[FieldOffset(0)] private ulong _ulong;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0;
public static implicit operator Span<byte>(in AesKey value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(in AesKey value) => value.DataRo;
public override readonly string ToString() => DataRo.ToHexString();
#if DEBUG
[FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1;
#endif
}
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct AesXtsKey
{
private const int Size = 0x20;
[FieldOffset(0)] private byte _byte;
[FieldOffset(0)] private ulong _ulong;
[FieldOffset(0)] public AesKey DataKey;
[FieldOffset(0)] public AesKey TweakKey;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong));
public Span<AesKey> SubKeys => SpanHelpers.CreateSpan(ref DataKey, Size / Unsafe.SizeOf<AesKey>());
public static implicit operator Span<byte>(in AesXtsKey value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(in AesXtsKey value) => value.DataRo;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1] | DataRo64[2] | DataRo64[3]) == 0;
public override readonly string ToString() => DataRo.ToHexString();
}
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct AesIv
{
private const int Size = 0x10;
[FieldOffset(0)] private byte _byte;
[FieldOffset(0)] private ulong _ulong;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0;
public static implicit operator Span<byte>(in AesIv value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(in AesIv value) => value.DataRo;
public override readonly string ToString() => DataRo.ToHexString();
#if DEBUG
[FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1;
#endif
}
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct AesCmac
{
private const int Size = 0x10;
[FieldOffset(0)] private byte _byte;
[FieldOffset(0)] private ulong _ulong;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, Size);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, Size);
public Span<ulong> Data64 => SpanHelpers.CreateSpan(ref _ulong, Size / sizeof(ulong));
public readonly ReadOnlySpan<ulong> DataRo64 => SpanHelpers.CreateReadOnlySpan(in _ulong, Size / sizeof(ulong));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsZeros() => (DataRo64[0] | DataRo64[1]) == 0;
public static implicit operator Span<byte>(in AesCmac value) => Unsafe.AsRef(in value).Data;
public static implicit operator ReadOnlySpan<byte>(in AesCmac value) => value.DataRo;
public override readonly string ToString() => DataRo.ToHexString();
#if DEBUG
[FieldOffset(8)] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1;
#endif
}
[StructLayout(LayoutKind.Sequential)]
public struct RsaFullKey
{
public Data100 PrivateExponent;
public Data80 Dp;
public Data80 Dq;
public Data3 PublicExponent;
public Data80 InverseQ;
public Data100 Modulus;
public Data80 P;
public Data80 Q;
}
[StructLayout(LayoutKind.Sequential)]
public struct RsaKey
{
public Data100 Modulus;
public Data3 PublicExponent;
}
[StructLayout(LayoutKind.Explicit, Size = 0x100)]
public struct Data100
{
[FieldOffset(0)] private byte _byte;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, 0x100);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x100);
}
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
public struct Data80
{
[FieldOffset(0)] private byte _byte;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, 0x80);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 0x80);
}
[StructLayout(LayoutKind.Explicit, Size = 3)]
public struct Data3
{
[FieldOffset(0)] private byte _byte;
public Span<byte> Data => SpanHelpers.CreateSpan(ref _byte, 3);
public readonly ReadOnlySpan<byte> DataRo => SpanHelpers.CreateReadOnlySpan(in _byte, 3);
}
}

View File

@ -61,7 +61,14 @@ namespace LibHac.Crypto
BigInteger dq = d % (q - BigInteger.One);
BigInteger inverseQ = Utilities.ModInverse(q, p);
int modLen = n.ToByteArray().Length;
byte[] nBytes = n.ToByteArray();
int modLen = nBytes.Length;
if (nBytes[^1] == 0)
{
modLen--;
}
int halfModLen = (modLen + 1) / 2;
return new RSAParameters
@ -122,44 +129,43 @@ namespace LibHac.Crypto
bool cracked = false;
BigInteger y = BigInteger.Zero;
using (var rng = RandomNumberGenerator.Create())
var rng = new Random(0);
for (int i = 0; i < 100 && !cracked; i++)
{
for (int i = 0; i < 100 && !cracked; i++)
BigInteger g;
do
{
BigInteger g;
rng.NextBytes(rndBuf);
g = Utilities.GetBigInteger(rndBuf);
}
while (g >= n);
do
y = BigInteger.ModPow(g, r, n);
if (y.IsOne || y == nMinusOne)
{
i--;
continue;
}
for (BigInteger j = BigInteger.One; j < t; j++)
{
BigInteger x = BigInteger.ModPow(y, two, n);
if (x.IsOne)
{
rng.GetBytes(rndBuf);
g = Utilities.GetBigInteger(rndBuf);
}
while (g >= n);
y = BigInteger.ModPow(g, r, n);
if (y.IsOne || y == nMinusOne)
{
i--;
continue;
cracked = true;
break;
}
for (BigInteger j = BigInteger.One; j < t; j++)
if (x == nMinusOne)
{
BigInteger x = BigInteger.ModPow(y, two, n);
if (x.IsOne)
{
cracked = true;
break;
}
if (x == nMinusOne)
{
break;
}
y = x;
break;
}
y = x;
}
}

View File

@ -8,6 +8,7 @@ using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.Fs
{

View File

@ -4,6 +4,7 @@ using System.IO;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.Fs
{

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Fs
{

View File

@ -4,6 +4,7 @@ using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Util;
using static LibHac.Fs.CommonMountNames;
namespace LibHac.Fs.Shim

View File

@ -2,6 +2,7 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.Util;
namespace LibHac.Fs.Shim
{

View File

@ -5,6 +5,7 @@ using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Util;
using static LibHac.Fs.CommonMountNames;
namespace LibHac.Fs.Shim

View File

@ -1,4 +1,5 @@
using System;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -7,11 +8,11 @@ namespace LibHac.FsSrv.Creators
{
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
{
private Keyset Keyset { get; }
private KeySet KeySet { get; }
public EncryptedFileSystemCreator(Keyset keyset)
public EncryptedFileSystemCreator(KeySet keySet)
{
Keyset = keyset;
KeySet = keySet;
}
public Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId,
@ -25,9 +26,10 @@ namespace LibHac.FsSrv.Creators
}
// todo: "proper" key generation instead of a lazy hack
Keyset.SetSdSeed(encryptionSeed.ToArray());
KeySet.SetSdSeed(encryptionSeed.ToArray());
encryptedFileSystem = new AesXtsFileSystem(baseFileSystem, Keyset.SdCardKeys[(int)keyId], 0x4000);
encryptedFileSystem = new AesXtsFileSystem(baseFileSystem,
KeySet.SdCardEncryptionKeys[(int) keyId].DataRo.ToArray(), 0x4000);
return Result.Success;
}

View File

@ -1,5 +1,6 @@
using System;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -9,11 +10,11 @@ namespace LibHac.FsSrv.Creators
{
public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
{
private Keyset Keyset { get; }
private KeySet KeySet { get; }
public SaveDataFileSystemCreator(Keyset keyset)
public SaveDataFileSystemCreator(KeySet keySet)
{
Keyset = keyset;
KeySet = keySet;
}
public Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode)
@ -61,7 +62,7 @@ namespace LibHac.FsSrv.Creators
if (rc.IsFailure()) return rc;
var saveDataStorage = new DisposingFileStorage(saveDataFile);
fileSystem = new SaveDataFileSystem(Keyset, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false);
fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false);
// Todo: ISaveDataExtraDataAccessor

View File

@ -1,4 +1,5 @@
using System;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -11,11 +12,11 @@ namespace LibHac.FsSrv.Creators
{
// ReSharper disable once UnusedMember.Local
private bool IsEnabledProgramVerification { get; set; }
private Keyset Keyset { get; }
private KeySet KeySet { get; }
public StorageOnNcaCreator(Keyset keyset)
public StorageOnNcaCreator(KeySet keySet)
{
Keyset = keyset;
KeySet = keySet;
}
// todo: Implement NcaReader and other Nca classes
@ -52,7 +53,7 @@ namespace LibHac.FsSrv.Creators
public Result OpenNca(out Nca nca, IStorage ncaStorage)
{
nca = new Nca(Keyset, ncaStorage);
nca = new Nca(KeySet, ncaStorage);
return Result.Success;
}

View File

@ -1,4 +1,5 @@
using LibHac.Fs.Fsa;
using LibHac.Common.Keys;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Creators;
namespace LibHac.FsSrv
@ -10,23 +11,23 @@ namespace LibHac.FsSrv
public EmulatedGameCard GameCard { get; set; }
public EmulatedSdCard SdCard { get; set; }
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, Keyset keyset)
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet)
{
var creators = new FileSystemCreators();
var gameCard = new EmulatedGameCard(keyset);
var gameCard = new EmulatedGameCard(keySet);
var sdCard = new EmulatedSdCard();
var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard);
creators.RomFileSystemCreator = new RomFileSystemCreator();
creators.PartitionFileSystemCreator = new PartitionFileSystemCreator();
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keyset);
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keyset);
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keySet);
creators.GameCardStorageCreator = gcStorageCreator;
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keyset);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem);
creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(sdCard, rootFileSystem);

View File

@ -1,4 +1,5 @@
using System;
using LibHac.Common.Keys;
using LibHac.Fs;
namespace LibHac.FsSrv
@ -9,13 +10,13 @@ namespace LibHac.FsSrv
private int Handle { get; set; }
private XciHeader CardHeader { get; set; }
private Xci CardImage { get; set; }
private Keyset Keyset { get; set; }
private KeySet KeySet { get; set; }
public EmulatedGameCard() { }
public EmulatedGameCard(Keyset keyset)
public EmulatedGameCard(KeySet keySet)
{
Keyset = keyset;
KeySet = keySet;
}
public GameCardHandle GetGameCardHandle()
{
@ -38,7 +39,7 @@ namespace LibHac.FsSrv
CardImageStorage = cardImageStorage;
CardImage = new Xci(Keyset, cardImageStorage);
CardImage = new Xci(KeySet, cardImageStorage);
CardHeader = CardImage.Header;
}

View File

@ -93,5 +93,10 @@ namespace LibHac.FsSrv
ExternalKeys.TrimExcess(newCapacity);
}
}
public void EnsureCapacity(int capacity)
{
ExternalKeys.EnsureCapacity(capacity);
}
}
}

View File

@ -10,6 +10,7 @@ using LibHac.FsSystem;
using LibHac.Kvdb;
using LibHac.Ncm;
using LibHac.Spl;
using LibHac.Util;
namespace LibHac.FsSrv
{

View File

@ -9,6 +9,7 @@ using LibHac.FsSystem;
using LibHac.FsSrv.Creators;
using LibHac.FsSystem.NcaUtils;
using LibHac.Spl;
using LibHac.Util;
using RightsId = LibHac.Fs.RightsId;
namespace LibHac.FsSrv

View File

@ -2,6 +2,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.FsSrv
{

View File

@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSrv.Sf
{

View File

@ -0,0 +1,83 @@
using LibHac.Crypto;
using LibHac.Fs;
using System;
namespace LibHac.FsSystem
{
public class AesCbcStorage : SectorStorage
{
private const int BlockSize = 0x10;
private readonly byte[] _key;
private readonly byte[] _iv;
private readonly long _size;
public AesCbcStorage(IStorage baseStorage, ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv,
bool leaveOpen) : base(baseStorage, BlockSize, leaveOpen)
{
if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long");
if (iv.Length != BlockSize) throw new ArgumentException(nameof(iv), $"Counter must be {BlockSize} bytes long");
_key = key.ToArray();
_iv = iv.ToArray();
baseStorage.GetSize(out _size).ThrowIfFailure();
}
protected override Result DoRead(long offset, Span<byte> destination)
{
if (!IsRangeValid(offset, destination.Length, _size))
return ResultFs.OutOfRange.Log();
Result rc = base.DoRead(offset, destination);
if (rc.IsFailure()) return rc;
rc = GetDecryptor(out ICipher cipher, offset);
if (rc.IsFailure()) return rc;
cipher.Transform(destination, destination);
return Result.Success;
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source)
{
return ResultFs.UnsupportedOperation.Log();
}
protected override Result DoFlush()
{
return Result.Success;
}
protected override Result DoSetSize(long size)
{
return ResultFs.UnsupportedOperation.Log();
}
private Result GetDecryptor(out ICipher decryptor, long offset)
{
if (offset == 0)
{
// Use the IV directly
decryptor = Aes.CreateCbcDecryptor(_key, _iv);
return Result.Success;
}
decryptor = default;
// Need to get the output of the previous block
Span<byte> prevBlock = stackalloc byte[BlockSize];
Result rc = BaseStorage.Read(offset - BlockSize, prevBlock);
if (rc.IsFailure()) return rc;
ICipher tmpDecryptor = Aes.CreateCbcDecryptor(_key, _iv);
tmpDecryptor.Transform(prevBlock, prevBlock);
decryptor = tmpDecryptor;
return Result.Success;
}
}
}

View File

@ -2,6 +2,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
@ -38,7 +39,7 @@ namespace LibHac.FsSystem
}
else
{
string entryName = Utilities.GetUtf8StringNullTerminated(entry.Name);
string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name);
entry.Size = GetAesXtsFileSize(PathTools.Combine(Path.ToString(), entryName).ToU8Span());
}
}

View File

@ -145,7 +145,7 @@ namespace LibHac.FsSystem
Blocks.AddFirst(node);
}
return node.Value;
return node!.Value;
}
// An inactive node shouldn't be null, but we'll fix it if it is anyway

View File

@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
@ -63,7 +64,7 @@ namespace LibHac.FsSystem
if (!Mode.HasFlag(OpenDirectoryMode.NoFileSize))
{
string entryName = Utilities.GetUtf8StringNullTerminated(entry.Name);
string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name);
string entryFullPath = PathTools.Combine(_path.ToString(), entryName);
rc = ParentFileSystem.GetConcatenationFileSize(out long fileSize, entryFullPath.ToU8Span());
@ -122,7 +123,7 @@ namespace LibHac.FsSystem
}
else
{
string name = Utilities.GetUtf8StringNullTerminated(entry.Name);
string name = StringUtils.NullTerminatedUtf8ToString(entry.Name);
var fullPath = PathTools.Combine(_path.ToString(), name).ToU8Span();
return ParentFileSystem.IsConcatenationFile(fullPath);

View File

@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -2,6 +2,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -2,6 +2,7 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -5,6 +5,7 @@ using System.IO;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -63,7 +63,7 @@ namespace LibHac.FsSystem
if (Type == IntegrityStorageType.Save)
{
if (Utilities.IsEmpty(hashBuffer))
if (Utilities.IsZeros(hashBuffer))
{
destination.Clear();
BlockValidities[blockIndex] = Validity.Valid;
@ -144,7 +144,7 @@ namespace LibHac.FsSystem
source.CopyTo(dataBuffer);
byte[] hash = DoHash(dataBuffer, 0, toWrite);
if (Type == IntegrityStorageType.Save && source.IsEmpty())
if (Type == IntegrityStorageType.Save && source.IsZeros())
{
Array.Clear(hash, 0, DigestSize);
}
@ -207,7 +207,7 @@ namespace LibHac.FsSystem
long hashPos = i * DigestSize;
HashStorage.Read(hashPos, digest).ThrowIfFailure();
if (!Utilities.IsEmpty(digest)) continue;
if (!Utilities.IsZeros(digest)) continue;
int dataOffset = i * SectorSize;
BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, dataOffset, SectorSize);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
@ -35,7 +35,7 @@ namespace LibHac.FsSystem
if (!CanReturnEntry(isDir, Mode)) continue;
ReadOnlySpan<byte> name = Utilities.GetUtf8Bytes(localEntry.Name);
ReadOnlySpan<byte> name = StringUtils.StringToUtf8(localEntry.Name);
DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File;
long length = isDir ? 0 : ((FileInfo)localEntry).Length;

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
@ -14,7 +15,7 @@ namespace LibHac.FsSystem.NcaUtils
{
public class Nca
{
private Keyset Keyset { get; }
private KeySet KeySet { get; }
private bool IsEncrypted => Header.IsEncrypted;
private byte[] Nca0KeyArea { get; set; }
@ -24,11 +25,11 @@ namespace LibHac.FsSystem.NcaUtils
public NcaHeader Header { get; }
public Nca(Keyset keyset, IStorage storage)
public Nca(KeySet keySet, IStorage storage)
{
Keyset = keyset;
KeySet = keySet;
BaseStorage = storage;
Header = new NcaHeader(keyset, storage);
Header = new NcaHeader(keySet, storage);
}
public byte[] GetDecryptedKey(int index)
@ -42,11 +43,11 @@ namespace LibHac.FsSystem.NcaUtils
}
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
byte[] keyAreaKey = Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex];
byte[] keyAreaKey = KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].DataRo.ToArray();
if (keyAreaKey.IsEmpty())
if (keyAreaKey.IsZeros())
{
string keyName = $"key_area_key_{Keyset.KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}";
string keyName = $"key_area_key_{KakNames[Header.KeyAreaKeyIndex]}_{keyRevision:x2}";
throw new MissingKeyException("Unable to decrypt NCA section.", keyName, KeyType.Common);
}
@ -58,19 +59,21 @@ namespace LibHac.FsSystem.NcaUtils
return decryptedKey;
}
private static readonly string[] KakNames = { "application", "ocean", "system" };
public byte[] GetDecryptedTitleKey()
{
int keyRevision = Utilities.GetMasterKeyRevision(Header.KeyGeneration);
byte[] titleKek = Keyset.TitleKeks[keyRevision];
byte[] titleKek = KeySet.TitleKeks[keyRevision].DataRo.ToArray();
var rightsId = new RightsId(Header.RightsId);
if (Keyset.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure())
if (KeySet.ExternalKeySet.Get(rightsId, out AccessKey accessKey).IsFailure())
{
throw new MissingKeyException("Missing NCA title key.", rightsId.ToString(), KeyType.Title);
}
if (titleKek.IsEmpty())
if (titleKek.IsZeros())
{
string keyName = $"titlekek_{keyRevision:x2}";
throw new MissingKeyException("Unable to decrypt title key.", keyName, KeyType.Common);
@ -108,11 +111,11 @@ namespace LibHac.FsSystem.NcaUtils
if (Header.HasRightsId)
{
return Keyset.ExternalKeySet.Contains(new RightsId(Header.RightsId)) &&
!Keyset.TitleKeks[keyRevision].IsEmpty();
return KeySet.ExternalKeySet.Contains(new RightsId(Header.RightsId)) &&
!KeySet.TitleKeks[keyRevision].IsZeros();
}
return !Keyset.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsEmpty();
return !KeySet.KeyAreaKeys[keyRevision][Header.KeyAreaKeyIndex].IsZeros();
}
public bool SectionExists(NcaSectionType type)
@ -586,13 +589,13 @@ namespace LibHac.FsSystem.NcaUtils
switch (Header.Version)
{
case 3:
header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
break;
case 2:
header = OpenNca2Header(headerSize, !openEncrypted);
break;
case 0:
header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
break;
default:
throw new NotSupportedException("Unsupported NCA version");
@ -606,11 +609,11 @@ namespace LibHac.FsSystem.NcaUtils
const int sectorSize = NcaHeader.HeaderSectorSize;
var sources = new List<IStorage>();
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true));
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true));
for (int i = 0x400; i < size; i += sectorSize)
{
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true));
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), KeySet.HeaderKey, sectorSize, true, decrypting), 1, true));
}
return new ConcatenationStorage(sources, true);
@ -630,7 +633,7 @@ namespace LibHac.FsSystem.NcaUtils
Span<byte> keyArea = Header.GetKeyArea();
var decKeyArea = new byte[0x100];
if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, Keyset.Nca0RsaKeyAreaKey, out _))
if (CryptoOld.DecryptRsaOaep(keyArea, decKeyArea, KeySet.BetaNca0KeyAreaKeyParams, out _))
{
Nca0KeyArea = decKeyArea;
}
@ -708,7 +711,7 @@ namespace LibHac.FsSystem.NcaUtils
public Validity VerifyHeaderSignature()
{
return Header.VerifySignature1(Keyset.NcaHdrFixedKeyModulus);
return Header.VerifySignature1(KeySet.NcaHeaderSigningKeyParams[0].Modulus);
}
internal void GenerateAesCounter(int sectionIndex, Ncm.ContentType type, int minorVersion)

View File

@ -3,9 +3,11 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem.NcaUtils
{
@ -21,9 +23,9 @@ namespace LibHac.FsSystem.NcaUtils
public NcaVersion FormatVersion { get; }
public bool IsEncrypted { get; }
public NcaHeader(Keyset keyset, IStorage headerStorage)
public NcaHeader(KeySet keySet, IStorage headerStorage)
{
(byte[] header, bool isEncrypted) = DecryptHeader(keyset, headerStorage);
(byte[] header, bool isEncrypted) = DecryptHeader(keySet, headerStorage);
_header = header;
IsEncrypted = isEncrypted;
@ -105,7 +107,7 @@ namespace LibHac.FsSystem.NcaUtils
public Span<byte> RightsId => _header.Span.Slice(NcaHeaderStruct.RightsIdOffset, NcaHeaderStruct.RightsIdSize);
public bool HasRightsId => !Utilities.IsEmpty(RightsId);
public bool HasRightsId => !Utilities.IsZeros(RightsId);
public Span<byte> GetKeyArea()
{
@ -195,7 +197,7 @@ namespace LibHac.FsSystem.NcaUtils
return (long)blockIndex * BlockSize;
}
private static (byte[] header, bool isEncrypted) DecryptHeader(Keyset keyset, IStorage storage)
private static (byte[] header, bool isEncrypted) DecryptHeader(KeySet keySet, IStorage storage)
{
var buf = new byte[HeaderSize];
storage.Read(0, buf).ThrowIfFailure();
@ -212,8 +214,8 @@ namespace LibHac.FsSystem.NcaUtils
return (buf, false);
}
byte[] key1 = keyset.HeaderKey.AsSpan(0, 0x10).ToArray();
byte[] key2 = keyset.HeaderKey.AsSpan(0x10, 0x10).ToArray();
byte[] key1 = keySet.HeaderKey.SubKeys[0].DataRo.ToArray();
byte[] key2 = keySet.HeaderKey.SubKeys[1].DataRo.ToArray();
var transform = new Aes128XtsTransform(key1, key2, true);

View File

@ -1,9 +1,9 @@
using System;
using System.IO;
using System.Text;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -5,6 +5,7 @@ using LibHac.Crypto;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.Detail;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem.Detail;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -4,6 +4,7 @@ using System.IO.Enumeration;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem.RomFs
{
@ -83,7 +84,7 @@ namespace LibHac.FsSystem.RomFs
public bool TryOpenFile(string path, out T fileInfo)
{
FindPathRecursive(Utilities.GetUtf8Bytes(path), out RomEntryKey key);
FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key);
if (FileTable.TryGetValue(ref key, out RomKeyValuePair<FileRomEntry> keyValuePair))
{
@ -116,7 +117,7 @@ namespace LibHac.FsSystem.RomFs
/// otherwise, <see langword="false"/>.</returns>
public bool TryOpenDirectory(string path, out FindPosition position)
{
FindPathRecursive(Utilities.GetUtf8Bytes(path), out RomEntryKey key);
FindPathRecursive(StringUtils.StringToUtf8(path), out RomEntryKey key);
if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
{
@ -169,7 +170,7 @@ namespace LibHac.FsSystem.RomFs
position.NextFile = entry.NextSibling;
info = entry.Info;
name = Utilities.GetUtf8String(nameBytes);
name = StringUtils.Utf8ToString(nameBytes);
return true;
}
@ -193,7 +194,7 @@ namespace LibHac.FsSystem.RomFs
ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span<byte> nameBytes);
position.NextDirectory = entry.NextSibling;
name = Utilities.GetUtf8String(nameBytes);
name = StringUtils.Utf8ToString(nameBytes);
return true;
}
@ -207,7 +208,7 @@ namespace LibHac.FsSystem.RomFs
public void AddFile(string path, ref T fileInfo)
{
path = PathTools.Normalize(path);
ReadOnlySpan<byte> pathBytes = Utilities.GetUtf8Bytes(path);
ReadOnlySpan<byte> pathBytes = StringUtils.StringToUtf8(path);
if (path == "/") throw new ArgumentException("Path cannot be empty");
@ -223,7 +224,7 @@ namespace LibHac.FsSystem.RomFs
{
path = PathTools.Normalize(path);
CreateDirectoryRecursive(Utilities.GetUtf8Bytes(path));
CreateDirectoryRecursive(StringUtils.StringToUtf8(path));
}
/// <summary>

View File

@ -1,8 +1,8 @@
using System;
using System.Text;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem.RomFs
{

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using LibHac.Common.Keys;
using LibHac.Crypto;
using LibHac.Fs;
@ -37,7 +38,7 @@ namespace LibHac.FsSystem.Save
public byte[] Data { get; }
public Header(IStorage storage, Keyset keyset)
public Header(IStorage storage, KeySet keySet)
{
MainStorage = storage;
MainHeader = MainStorage.Slice(0x100, 0x200);
@ -86,14 +87,14 @@ namespace LibHac.FsSystem.Save
Sha256.GenerateSha256Hash(Data.AsSpan(0x300, 0x3d00), actualHeaderHash);
HeaderHashValidity = Utilities.SpansEqual(Layout.Hash, actualHeaderHash) ? Validity.Valid : Validity.Invalid;
SignatureValidity = ValidateSignature(keyset);
SignatureValidity = ValidateSignature(keySet);
}
private Validity ValidateSignature(Keyset keyset)
private Validity ValidateSignature(KeySet keySet)
{
Span<byte> calculatedCmac = stackalloc byte[0x10];
Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keyset.SaveMacKey);
Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keySet.DeviceUniqueSaveMacKeys[0]);
return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid;
}

View File

@ -3,6 +3,7 @@ using System.IO;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem.Save
{
@ -59,7 +60,7 @@ namespace LibHac.FsSystem.Save
position.NextFile = entry.NextSibling;
info = entry.Value;
name = Utilities.GetUtf8StringNullTerminated(nameBytes);
name = StringUtils.NullTerminatedUtf8ToString(nameBytes);
return true;
}
@ -85,7 +86,7 @@ namespace LibHac.FsSystem.Save
position.NextDirectory = entry.NextSibling;
name = Utilities.GetUtf8StringNullTerminated(nameBytes);
name = StringUtils.NullTerminatedUtf8ToString(nameBytes);
return true;
}

View File

@ -1,8 +1,8 @@
using System;
using System.Text;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem.Save
{

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Crypto;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@ -28,16 +29,16 @@ namespace LibHac.FsSystem.Save
public HierarchicalIntegrityVerificationStorage CoreDataIvfcStorage { get; }
public HierarchicalIntegrityVerificationStorage FatIvfcStorage { get; }
private Keyset Keyset { get; }
private KeySet KeySet { get; }
public SaveDataFileSystem(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen)
public SaveDataFileSystem(KeySet keySet, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen)
{
BaseStorage = storage;
LeaveOpen = leaveOpen;
Keyset = keyset;
KeySet = keySet;
var headerA = new Header(BaseStorage, keyset);
var headerB = new Header(BaseStorage.Slice(0x4000), keyset);
var headerA = new Header(BaseStorage, keySet);
var headerB = new Header(BaseStorage.Slice(0x4000), keySet);
if (headerA.HeaderHashValidity == Validity.Valid)
{
@ -238,12 +239,12 @@ namespace LibHac.FsSystem.Save
protected override Result DoCommit()
{
Result result = Commit(Keyset);
Result result = Commit(KeySet);
return SaveResults.ConvertToExternalResult(result).LogConverted(result);
}
public Result Commit(Keyset keyset)
public Result Commit(KeySet keySet)
{
CoreDataIvfcStorage.Flush();
FatIvfcStorage?.Flush();
@ -261,7 +262,7 @@ namespace LibHac.FsSystem.Save
headerStream.Position = 0x108;
headerStream.Write(hash, 0, hash.Length);
if (keyset == null || keyset.SaveMacKey.IsEmpty()) return ResultFs.PreconditionViolation.Log();
if (keySet == null || keySet.DeviceUniqueSaveMacKeys[0].IsZeros()) return ResultFs.PreconditionViolation.Log();
var cmacData = new byte[0x200];
var cmac = new byte[0x10];
@ -269,7 +270,7 @@ namespace LibHac.FsSystem.Save
headerStream.Position = 0x100;
headerStream.Read(cmacData, 0, 0x200);
Aes.CalculateCmac(cmac, cmacData, keyset.SaveMacKey);
Aes.CalculateCmac(cmac, cmacData, keySet.DeviceUniqueSaveMacKeys[0]);
headerStream.Position = 0;
headerStream.Write(cmac, 0, 0x10);

View File

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.FsSystem.Save
{

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

View File

@ -5,6 +5,7 @@ using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{

10
src/LibHac/KeyType.cs Normal file
View File

@ -0,0 +1,10 @@
namespace LibHac
{
public enum KeyType
{
None,
Common,
Unique,
Title
}
}

View File

@ -1,878 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using LibHac.Crypto;
using LibHac.Fs;
using LibHac.FsSrv;
using LibHac.Spl;
using Aes = LibHac.Crypto.Aes;
namespace LibHac
{
public class Keyset
{
/// <summary>
/// The number of keyblobs that were used for &lt; 6.2.0 crypto
/// </summary>
private const int UsedKeyblobCount = 6;
private const int SdCardKeyIdCount = 3;
public byte[][] KeyblobKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] KeyblobMacKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] EncryptedKeyblobs { get; } = Utilities.CreateJaggedByteArray(0x20, 0xB0);
public byte[][] Keyblobs { get; } = Utilities.CreateJaggedByteArray(0x20, 0x90);
public byte[][] KeyblobKeySources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[] KeyblobMacKeySource { get; } = new byte[0x10];
public byte[][] TsecRootKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] MasterKekSources { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] MasterKeks { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[] MasterKeySource { get; } = new byte[0x10];
public byte[][] MasterKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] Package1Keys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][] Package2Keys { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[] Package2KeySource { get; } = new byte[0x10];
public byte[] AesKekGenerationSource { get; } = new byte[0x10];
public byte[] AesKeyGenerationSource { get; } = new byte[0x10];
public byte[] KeyAreaKeyApplicationSource { get; } = new byte[0x10];
public byte[] KeyAreaKeyOceanSource { get; } = new byte[0x10];
public byte[] KeyAreaKeySystemSource { get; } = new byte[0x10];
public byte[] SaveMacKekSource { get; } = new byte[0x10];
public byte[] SaveMacSdCardKekSource { get; } = new byte[0x10];
public byte[] SaveMacKeySource { get; } = new byte[0x10];
public byte[] SaveMacSdCardKeySource { get; } = new byte[0x10];
public byte[] TitleKekSource { get; } = new byte[0x10];
public byte[] HeaderKekSource { get; } = new byte[0x10];
public byte[] SdCardKekSource { get; } = new byte[0x10];
public byte[][] SdCardKeySources { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public byte[] HeaderKeySource { get; } = new byte[0x20];
public byte[] HeaderKey { get; } = new byte[0x20];
public byte[] XciHeaderKey { get; } = new byte[0x10];
public byte[][] TitleKeks { get; } = Utilities.CreateJaggedByteArray(0x20, 0x10);
public byte[][][] KeyAreaKeys { get; } = Utilities.CreateJaggedByteArray(0x20, 3, 0x10);
public byte[] EticketRsaKek { get; } = new byte[0x10];
public byte[] RetailSpecificAesKeySource { get; } = new byte[0x10];
public byte[] PerConsoleKeySource { get; } = new byte[0x10];
public byte[] BisKekSource { get; } = new byte[0x10];
public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20);
public byte[] SslRsaKek { get; } = new byte[0x10];
// Device-specific keys
public byte[] SecureBootKey { get; } = new byte[0x10];
public byte[] TsecKey { get; } = new byte[0x10];
public byte[] DeviceKey { get; } = new byte[0x10];
public byte[][] BisKeys { get; } = Utilities.CreateJaggedByteArray(4, 0x20);
public byte[] SaveMacKey { get; } = new byte[0x10];
public byte[] SaveMacSdCardKey { get; } = new byte[0x10];
public byte[] SdSeed { get; } = new byte[0x10];
public byte[][] SdCardKeySourcesSpecific { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public byte[][] SdCardKeys { get; } = Utilities.CreateJaggedByteArray(SdCardKeyIdCount, 0x20);
public RSAParameters EticketExtKeyRsa { get; set; }
private RSAParameters? _nca0RsaKeyAreaKey;
public RSAParameters Nca0RsaKeyAreaKey
{
// Lazily init this key
get
{
if (_nca0RsaKeyAreaKey.HasValue)
return _nca0RsaKeyAreaKey.Value;
_nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, BetaNca0Exponent, new byte[] { 1, 0, 1 });
return _nca0RsaKeyAreaKey.Value;
}
}
public bool KeysetForDev;
public byte[] NcaHdrFixedKeyModulus
{
get
{
if (KeysetForDev)
{
return NcaHdrFixedKeyModulusDev;
}
else
{
return NcaHdrFixedKeyModulusProd;
}
}
}
public byte[] AcidFixedKeyModulus
{
get
{
if (KeysetForDev)
{
return AcidFixedKeyModulusDev;
}
else
{
return AcidFixedKeyModulusProd;
}
}
}
public byte[] Package2FixedKeyModulus
{
get
{
if (KeysetForDev)
{
return Package2FixedKeyModulusDev;
}
else
{
return Package2FixedKeyModulusProd;
}
}
}
public readonly byte[] BetaNca0Modulus =
{
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
};
public readonly byte[] BetaNca0Exponent =
{
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 readonly byte[] NcaHdrFixedKeyModulusProd =
{
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 readonly byte[] AcidFixedKeyModulusProd =
{
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 readonly byte[] Package2FixedKeyModulusProd =
{
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 readonly byte[] NcaHdrFixedKeyModulusDev =
{
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 readonly byte[] AcidFixedKeyModulusDev =
{
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 readonly byte[] Package2FixedKeyModulusDev =
{
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
};
public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet();
public void SetSdSeed(byte[] sdseed)
{
Array.Copy(sdseed, SdSeed, SdSeed.Length);
DeriveSdCardKeys();
}
public void DeriveKeys(IProgressReport logger = null)
{
DeriveKeyblobKeys();
DecryptKeyblobs(logger);
ReadKeyblobs();
Derive620MasterKeks();
DeriveMasterKeys();
DerivePerConsoleKeys();
DerivePerFirmwareKeys();
DeriveNcaHeaderKey();
DeriveSdCardKeys();
}
private void DeriveKeyblobKeys()
{
if (SecureBootKey.IsEmpty() || TsecKey.IsEmpty()) return;
bool haveKeyblobMacKeySource = !MasterKeySource.IsEmpty();
var temp = new byte[0x10];
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 byte[0x10];
var expectedCmac = new byte[0x10];
var counter = new byte[0x10];
for (int i = 0; i < UsedKeyblobCount; i++)
{
if (KeyblobKeys[i].IsEmpty() || KeyblobMacKeys[i].IsEmpty() || EncryptedKeyblobs[i].IsEmpty())
{
continue;
}
Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10);
Aes.CalculateCmac(cmac, EncryptedKeyblobs[i].AsSpan(0x10, 0xA0), KeyblobMacKeys[i]);
if (!Utilities.ArraysEqual(cmac, expectedCmac))
{
logger?.LogMessage($"Warning: Keyblob MAC {i:x2} is invalid. Are SBK/TSEC key correct?");
}
Array.Copy(EncryptedKeyblobs[i], 0x10, counter, 0, 0x10);
Aes.DecryptCtr128(EncryptedKeyblobs[i].AsSpan(0x20), Keyblobs[i], KeyblobKeys[i], counter);
}
}
private void ReadKeyblobs()
{
for (int i = 0; i < UsedKeyblobCount; i++)
{
if (Keyblobs[i].IsEmpty()) continue;
Array.Copy(Keyblobs[i], 0x80, Package1Keys[i], 0, 0x10);
Array.Copy(Keyblobs[i], MasterKeks[i], 0x10);
}
}
private void Derive620MasterKeks()
{
for (int i = UsedKeyblobCount; i < 0x20; i++)
{
if (TsecRootKeys[i - UsedKeyblobCount].IsEmpty() || MasterKekSources[i].IsEmpty()) continue;
Aes.DecryptEcb128(MasterKekSources[i], MasterKeks[i], TsecRootKeys[i - UsedKeyblobCount]);
}
}
private void DeriveMasterKeys()
{
if (MasterKeySource.IsEmpty()) return;
for (int i = 0; i < 0x20; i++)
{
if (MasterKeks[i].IsEmpty()) continue;
Aes.DecryptEcb128(MasterKeySource, MasterKeys[i], MasterKeks[i]);
}
}
private void DerivePerConsoleKeys()
{
var kek = new byte[0x10];
// Derive the device key
if (!PerConsoleKeySource.IsEmpty() && !KeyblobKeys[0].IsEmpty())
{
Aes.DecryptEcb128(PerConsoleKeySource, DeviceKey, KeyblobKeys[0]);
}
// Derive save key
if (!SaveMacKekSource.IsEmpty() && !SaveMacKeySource.IsEmpty() && !DeviceKey.IsEmpty())
{
GenerateKek(DeviceKey, SaveMacKekSource, kek, AesKekGenerationSource, null);
Aes.DecryptEcb128(SaveMacKeySource, SaveMacKey, 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 (BisKeySource[3].IsEmpty() && !BisKeySource[2].IsEmpty())
{
Array.Copy(BisKeySource[2], BisKeySource[3], 0x20);
}
Aes.DecryptEcb128(RetailSpecificAesKeySource, kek, DeviceKey);
if (!BisKeySource[0].IsEmpty()) Aes.DecryptEcb128(BisKeySource[0], BisKeys[0], kek);
GenerateKek(DeviceKey, BisKekSource, kek, AesKekGenerationSource, AesKeyGenerationSource);
for (int i = 1; i < 4; i++)
{
if (!BisKeySource[i].IsEmpty()) Aes.DecryptEcb128(BisKeySource[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 < 0x20; 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 byte[0x10];
GenerateKek(MasterKeys[0], HeaderKekSource, headerKek, AesKekGenerationSource,
AesKeyGenerationSource);
Aes.DecryptEcb128(HeaderKeySource, HeaderKey, headerKek);
}
public void DeriveSdCardKeys()
{
var sdKek = new byte[0x10];
GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource);
for (int k = 0; k < SdCardKeyIdCount; k++)
{
for (int i = 0; i < 0x20; i++)
{
SdCardKeySourcesSpecific[k][i] = (byte)(SdCardKeySources[k][i] ^ SdSeed[i & 0xF]);
}
}
for (int k = 0; k < SdCardKeyIdCount; k++)
{
Aes.DecryptEcb128(SdCardKeySourcesSpecific[k], SdCardKeys[k], sdKek);
}
// Derive sd card save key
if (!SaveMacSdCardKekSource.IsEmpty() && !SaveMacSdCardKeySource.IsEmpty())
{
var keySource = new byte[0x10];
for (int i = 0; i < 0x10; i++)
{
keySource[i] = (byte)(SaveMacSdCardKeySource[i] ^ SdSeed[i]);
}
GenerateKek(MasterKeys[0], SaveMacSdCardKekSource, sdKek, AesKekGenerationSource, null);
Aes.DecryptEcb128(keySource, SaveMacSdCardKey, sdKek);
}
}
internal static readonly string[] KakNames = { "application", "ocean", "system" };
public static int GetMasterKeyRevisionFromKeyGeneration(int keyGeneration)
{
if (keyGeneration == 0) return 0;
return keyGeneration - 1;
}
private static void GenerateKek(ReadOnlySpan<byte> key, ReadOnlySpan<byte> src, Span<byte> dest, ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> keySeed)
{
Span<byte> kek = stackalloc byte[0x10];
Span<byte> srcKek = stackalloc byte[0x10];
Aes.DecryptEcb128(kekSeed, kek, key);
Aes.DecryptEcb128(src, srcKek, kek);
if (!keySeed.IsEmpty)
{
Aes.DecryptEcb128(keySeed, dest, srcKek);
}
else
{
srcKek.CopyTo(dest);
}
}
}
public static class ExternalKeyReader
{
private const int TitleKeySize = 0x10;
public static void ReadKeyFile(Keyset keyset, string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null)
{
Dictionary<string, KeyValue> keyDictionary = CreateFullKeyDictionary();
if (filename != null) ReadMainKeys(keyset, filename, keyDictionary, logger);
if (consoleKeysFilename != null) ReadMainKeys(keyset, consoleKeysFilename, keyDictionary, logger);
if (titleKeysFilename != null) ReadTitleKeys(keyset, titleKeysFilename, logger);
keyset.ExternalKeySet.TrimExcess();
keyset.DeriveKeys(logger);
}
public static Keyset ReadKeyFile(string filename, string titleKeysFilename = null, string consoleKeysFilename = null, IProgressReport logger = null, bool dev = false)
{
var keyset = new Keyset();
keyset.KeysetForDev = dev;
ReadKeyFile(keyset, filename, titleKeysFilename, consoleKeysFilename, logger);
return keyset;
}
public static void LoadConsoleKeys(this Keyset keyset, string filename, IProgressReport logger = null)
{
Dictionary<string, KeyValue> uniqueKeyDictionary = CreateUniqueKeyDictionary();
foreach (KeyValue key in uniqueKeyDictionary.Values)
{
byte[] keyBytes = key.GetKey(keyset);
Array.Clear(keyBytes, 0, keyBytes.Length);
}
ReadMainKeys(keyset, filename, uniqueKeyDictionary, logger);
keyset.DeriveKeys();
}
private static void ReadMainKeys(Keyset keyset, string filename, Dictionary<string, KeyValue> keyDict, IProgressReport logger = null)
{
if (filename == null) return;
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] a = line.Split(',', '=');
if (a.Length != 2) continue;
string key = a[0].Trim();
string valueStr = a[1].Trim();
if (!keyDict.TryGetValue(key, out KeyValue kv))
{
logger?.LogMessage($"Failed to match key {key}");
continue;
}
byte[] value = valueStr.ToBytes();
if (value.Length != kv.Size)
{
logger?.LogMessage($"Key {key} had incorrect size {value.Length}. (Expected {kv.Size})");
continue;
}
byte[] dest = kv.GetKey(keyset);
Array.Copy(value, dest, value.Length);
}
}
}
private static void ReadTitleKeys(Keyset keyset, string filename, IProgressReport progress = null)
{
if (filename == null) return;
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] splitLine;
// Some people use pipes as delimiters
if (line.Contains('|'))
{
splitLine = line.Split('|');
}
else
{
splitLine = line.Split(',', '=');
}
if (splitLine.Length < 2) continue;
if (!splitLine[0].Trim().TryToBytes(out byte[] rightsId))
{
progress?.LogMessage($"Invalid rights ID \"{splitLine[0].Trim()}\" in title key file");
continue;
}
if (!splitLine[1].Trim().TryToBytes(out byte[] titleKey))
{
progress?.LogMessage($"Invalid title key \"{splitLine[1].Trim()}\" in title key file");
continue;
}
if (rightsId.Length != TitleKeySize)
{
progress?.LogMessage($"Rights ID {rightsId.ToHexString()} had incorrect size {rightsId.Length}. (Expected {TitleKeySize})");
continue;
}
if (titleKey.Length != TitleKeySize)
{
progress?.LogMessage($"Title key {titleKey.ToHexString()} had incorrect size {titleKey.Length}. (Expected {TitleKeySize})");
continue;
}
keyset.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure();
}
}
}
public static string PrintKeys(Keyset keyset, Dictionary<string, KeyValue> dict)
{
if (dict.Count == 0) return string.Empty;
var sb = new StringBuilder();
int maxNameLength = dict.Values.Max(x => x.Name.Length);
int currentGroup = 0;
foreach (KeyValue keySlot in dict.Values.Where(x => x.Group >= 0).OrderBy(x => x.Group).ThenBy(x => x.Name))
{
byte[] key = keySlot.GetKey(keyset);
if (key.IsEmpty()) continue;
if (keySlot.Group > currentGroup)
{
if (currentGroup > 0) sb.AppendLine();
currentGroup = keySlot.Group;
}
string line = $"{keySlot.Name.PadRight(maxNameLength)} = {key.ToHexString()}";
sb.AppendLine(line);
}
return sb.ToString();
}
public static string PrintCommonKeys(Keyset keyset)
{
return PrintKeys(keyset, CreateCommonKeyDictionary());
}
public static string PrintUniqueKeys(Keyset keyset)
{
return PrintKeys(keyset, CreateUniqueKeyDictionary());
}
public static string PrintAllKeys(Keyset keyset)
{
return PrintKeys(keyset, CreateFullKeyDictionary());
}
public static string PrintTitleKeys(Keyset keyset)
{
var sb = new StringBuilder();
foreach ((RightsId rightsId, AccessKey key) kv in keyset.ExternalKeySet.ToList().OrderBy(x => x.rightsId.ToString()))
{
string line = $"{kv.rightsId} = {kv.key}";
sb.AppendLine(line);
}
return sb.ToString();
}
public static Dictionary<string, KeyValue> CreateCommonKeyDictionary()
{
return CreateCommonKeyList().ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase);
}
public static Dictionary<string, KeyValue> CreateUniqueKeyDictionary()
{
return CreateUniqueKeyList().ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase);
}
public static Dictionary<string, KeyValue> CreateFullKeyDictionary()
{
List<KeyValue> commonKeys = CreateCommonKeyList();
List<KeyValue> uniqueKeys = CreateUniqueKeyList();
return uniqueKeys.Concat(commonKeys).ToDictionary(k => k.Name, k => k, StringComparer.OrdinalIgnoreCase);
}
private static List<KeyValue> CreateCommonKeyList()
{
var keys = new List<KeyValue>
{
new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource),
new KeyValue("master_key_source", 0x10, 60, set => set.MasterKeySource),
new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource),
new KeyValue("aes_kek_generation_source", 0x10, 70, set => set.AesKekGenerationSource),
new KeyValue("aes_key_generation_source", 0x10, 70, set => set.AesKeyGenerationSource),
new KeyValue("bis_kek_source", 0x10, 80, set => set.BisKekSource),
new KeyValue("retail_specific_aes_key_source", 0x10, 90, set => set.RetailSpecificAesKeySource),
new KeyValue("per_console_key_source", 0x10, 90, set => set.PerConsoleKeySource),
new KeyValue("header_kek_source", 0x10, 100, set => set.HeaderKekSource),
new KeyValue("header_key_source", 0x20, 100, set => set.HeaderKeySource),
new KeyValue("key_area_key_application_source", 0x10, 100, set => set.KeyAreaKeyApplicationSource),
new KeyValue("key_area_key_ocean_source", 0x10, 100, set => set.KeyAreaKeyOceanSource),
new KeyValue("key_area_key_system_source", 0x10, 100, set => set.KeyAreaKeySystemSource),
new KeyValue("titlekek_source", 0x10, 100, set => set.TitleKekSource),
new KeyValue("save_mac_kek_source", 0x10, 110, set => set.SaveMacKekSource),
new KeyValue("save_mac_sd_card_kek_source", 0x10, 110, set => set.SaveMacSdCardKekSource),
new KeyValue("save_mac_key_source", 0x10, 110, set => set.SaveMacKeySource),
new KeyValue("save_mac_sd_card_key_source", 0x10, 110, set => set.SaveMacSdCardKeySource),
new KeyValue("sd_card_kek_source", 0x10, 110, set => set.SdCardKekSource),
new KeyValue("sd_card_save_key_source", 0x20, 110, set => set.SdCardKeySources[0]),
new KeyValue("sd_card_nca_key_source", 0x20, 110, set => set.SdCardKeySources[1]),
new KeyValue("sd_card_custom_storage_key_source", 0x20, 110, set => set.SdCardKeySources[2]),
new KeyValue("eticket_rsa_kek", 0x10, 120, set => set.EticketRsaKek),
new KeyValue("ssl_rsa_kek", 0x10, 120, set => set.SslRsaKek),
new KeyValue("xci_header_key", 0x10, 130, set => set.XciHeaderKey),
new KeyValue("header_key", 0x20, 220, set => set.HeaderKey)
};
for (int slot = 0; slot < 0x20; slot++)
{
int i = slot;
keys.Add(new KeyValue($"keyblob_key_source_{i:x2}", 0x10, 0, set => set.KeyblobKeySources[i]));
keys.Add(new KeyValue($"keyblob_{i:x2}", 0x90, 10, set => set.Keyblobs[i]));
keys.Add(new KeyValue($"tsec_root_key_{i:x2}", 0x10, 20, set => set.TsecRootKeys[i]));
keys.Add(new KeyValue($"master_kek_source_{i:x2}", 0x10, 30, set => set.MasterKekSources[i]));
keys.Add(new KeyValue($"master_kek_{i:x2}", 0x10, 40, set => set.MasterKeks[i]));
keys.Add(new KeyValue($"package1_key_{i:x2}", 0x10, 50, set => set.Package1Keys[i]));
keys.Add(new KeyValue($"master_key_{i:x2}", 0x10, 200, set => set.MasterKeys[i]));
keys.Add(new KeyValue($"package2_key_{i:x2}", 0x10, 210, set => set.Package2Keys[i]));
keys.Add(new KeyValue($"titlekek_{i:x2}", 0x10, 230, set => set.TitleKeks[i]));
keys.Add(new KeyValue($"key_area_key_application_{i:x2}", 0x10, 240, set => set.KeyAreaKeys[i][0]));
keys.Add(new KeyValue($"key_area_key_ocean_{i:x2}", 0x10, 250, set => set.KeyAreaKeys[i][1]));
keys.Add(new KeyValue($"key_area_key_system_{i:x2}", 0x10, 260, set => set.KeyAreaKeys[i][2]));
}
for (int slot = 0; slot < 4; slot++)
{
int i = slot;
keys.Add(new KeyValue($"bis_key_source_{i:x2}", 0x20, 80, set => set.BisKeySource[i]));
}
return keys;
}
private static List<KeyValue> CreateUniqueKeyList()
{
var keys = new List<KeyValue>
{
new KeyValue("secure_boot_key", 0x10, 0, set => set.SecureBootKey),
new KeyValue("tsec_key", 0x10, 0, set => set.TsecKey),
new KeyValue("sd_seed", 0x10, 10, set => set.SdSeed),
new KeyValue("device_key", 0x10, 40, set => set.DeviceKey),
new KeyValue("save_mac_key", 0x10, 60, set => set.SaveMacKey),
new KeyValue("save_mac_sd_card_key", 0x10, 60, set => set.SaveMacSdCardKey)
};
for (int slot = 0; slot < 0x20; slot++)
{
int i = slot;
keys.Add(new KeyValue($"keyblob_mac_key_{i:x2}", 0x10, 20, set => set.KeyblobMacKeys[i]));
keys.Add(new KeyValue($"keyblob_key_{i:x2}", 0x10, 30, set => set.KeyblobKeys[i]));
keys.Add(new KeyValue($"encrypted_keyblob_{i:x2}", 0xB0, 100, set => set.EncryptedKeyblobs[i]));
}
for (int slot = 0; slot < 4; slot++)
{
int i = slot;
keys.Add(new KeyValue($"bis_key_{i:x2}", 0x20, 50, set => set.BisKeys[i]));
}
return keys;
}
public class KeyValue
{
public readonly string Name;
public readonly int Size;
public readonly int Group;
public readonly Func<Keyset, byte[]> GetKey;
public KeyValue(string name, int size, int group, Func<Keyset, byte[]> retrieveFunc)
{
Name = name;
Size = size;
Group = group;
GetKey = retrieveFunc;
}
}
}
public enum KeyType
{
None,
Common,
Unique,
Title
}
}

View File

@ -2,6 +2,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Kvdb
{

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

@ -1,6 +1,7 @@
// ReSharper disable UnusedVariable
using System;
using System.IO;
using LibHac.Common.Keys;
namespace LibHac.Npdm
{
@ -23,7 +24,7 @@ namespace LibHac.Npdm
public Acid(Stream stream, int offset) : this(stream, offset, null) { }
public Acid(Stream stream, int offset, Keyset keyset)
public Acid(Stream stream, int offset, KeySet keySet)
{
stream.Seek(offset, SeekOrigin.Begin);
@ -40,11 +41,12 @@ namespace LibHac.Npdm
Size = reader.ReadInt32();
if (keyset != null)
if (keySet != null)
{
reader.BaseStream.Position = offset + 0x100;
byte[] signatureData = reader.ReadBytes(Size);
SignatureValidity = CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keyset.AcidFixedKeyModulus);
SignatureValidity =
CryptoOld.Rsa2048PssVerify(signatureData, Rsa2048Signature, keySet.AcidSigningKeyParams[0].Modulus);
}
reader.BaseStream.Position = offset + 0x208;

View File

@ -2,6 +2,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
using LibHac.Common.Keys;
namespace LibHac.Npdm
{
@ -27,7 +28,7 @@ namespace LibHac.Npdm
public NpdmBinary(Stream stream) : this(stream, null) { }
public NpdmBinary(Stream stream, Keyset keyset)
public NpdmBinary(Stream stream, KeySet keySet)
{
var reader = new BinaryReader(stream);
@ -74,7 +75,7 @@ namespace LibHac.Npdm
int acidSize = reader.ReadInt32();
Aci0 = new Aci0(stream, aci0Offset);
AciD = new Acid(stream, acidOffset, keyset);
AciD = new Acid(stream, acidOffset, keySet);
}
}
}

View File

@ -1,10 +1,12 @@
using System;
using System.IO;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.FsSystem;
namespace LibHac
{
[Obsolete("This class has been deprecated. LibHac.Boot.Package1 should be used instead.")]
public class Package1
{
private const uint Pk11Magic = 0x31314B50; // PK11
@ -19,7 +21,7 @@ namespace LibHac
private IStorage Storage { get; }
public Package1(Keyset keyset, IStorage storage)
public Package1(KeySet keySet, IStorage storage)
{
Storage = storage;
var reader = new BinaryReader(storage.AsStream());
@ -40,7 +42,7 @@ namespace LibHac
for (int i = 0; i < 0x20; i++)
{
var dec = new Aes128CtrStorage(encStorage, keyset.Package1Keys[i], Counter, true);
var dec = new Aes128CtrStorage(encStorage, keySet.Package1Keys[i].DataRo.ToArray(), Counter, true);
dec.Read(0, decBuffer).ThrowIfFailure();
if (BitConverter.ToUInt32(decBuffer, 0) == Pk11Magic)

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.FsSystem;
@ -18,15 +19,15 @@ namespace LibHac
private IStorage Storage { get; }
public Package2(Keyset keyset, IStorage storage)
public Package2(KeySet keySet, IStorage storage)
{
Storage = storage;
IStorage headerStorage = Storage.Slice(0, 0x200);
KeyRevision = FindKeyGeneration(keyset, headerStorage);
Key = keyset.Package2Keys[KeyRevision];
KeyRevision = FindKeyGeneration(keySet, headerStorage);
Key = keySet.Package2Keys[KeyRevision].DataRo.ToArray();
Header = new Package2Header(headerStorage, keyset, KeyRevision);
Header = new Package2Header(headerStorage, keySet, KeyRevision);
PackageSize = BitConverter.ToInt32(Header.Counter, 0) ^ BitConverter.ToInt32(Header.Counter, 8) ^
BitConverter.ToInt32(Header.Counter, 12);
@ -106,7 +107,7 @@ namespace LibHac
return new CachedStorage(new Aes128CtrStorage(encStorage, Key, Header.SectionCounters[1], true), 0x4000, 4, true);
}
private int FindKeyGeneration(Keyset keyset, IStorage storage)
private int FindKeyGeneration(KeySet keySet, IStorage storage)
{
var counter = new byte[0x10];
var decBuffer = new byte[0x10];
@ -115,7 +116,8 @@ namespace LibHac
for (int i = 0; i < 0x20; i++)
{
var dec = new Aes128CtrStorage(storage.Slice(0x100), keyset.Package2Keys[i], counter, false);
var dec = new Aes128CtrStorage(storage.Slice(0x100), keySet.Package2Keys[i].DataRo.ToArray(), counter,
false);
dec.Read(0x50, decBuffer).ThrowIfFailure();
if (BitConverter.ToUInt32(decBuffer, 0) == Pk21Magic)
@ -145,14 +147,14 @@ namespace LibHac
public Validity SignatureValidity { get; }
public Package2Header(IStorage storage, Keyset keyset, int keyGeneration)
public Package2Header(IStorage storage, KeySet keySet, int keyGeneration)
{
var reader = new BinaryReader(storage.AsStream());
byte[] key = keyset.Package2Keys[keyGeneration];
byte[] key = keySet.Package2Keys[keyGeneration].DataRo.ToArray();
Signature = reader.ReadBytes(0x100);
byte[] sigData = reader.ReadBytes(0x100);
SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keyset.Package2FixedKeyModulus);
SignatureValidity = CryptoOld.Rsa2048PssVerify(sigData, Signature, keySet.Package2SigningKeyParams.Modulus);
reader.BaseStream.Position -= 0x100;
Counter = reader.ReadBytes(0x10);

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Util;
namespace LibHac
{

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using LibHac.Common;
using LibHac.Util;
namespace LibHac.Sm
{

View File

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -11,12 +12,13 @@ using LibHac.FsSystem.NcaUtils;
using LibHac.FsSystem.Save;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Util;
namespace LibHac
{
public class SwitchFs : IDisposable
{
public Keyset Keyset { get; }
public KeySet KeySet { get; }
public IFileSystem ContentFs { get; }
public IFileSystem SaveFs { get; }
@ -25,9 +27,9 @@ namespace LibHac
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
public SwitchFs(Keyset keyset, IFileSystem contentFileSystem, IFileSystem saveFileSystem)
public SwitchFs(KeySet keySet, IFileSystem contentFileSystem, IFileSystem saveFileSystem)
{
Keyset = keyset;
KeySet = keySet;
ContentFs = contentFileSystem;
SaveFs = saveFileSystem;
@ -38,7 +40,7 @@ namespace LibHac
CreateApplications();
}
public static SwitchFs OpenSdCard(Keyset keyset, IAttributeFileSystem fileSystem)
public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem)
{
var concatFs = new ConcatenationFileSystem(fileSystem);
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Nintendo/Contents".ToU8String()).ThrowIfFailure();
@ -47,15 +49,15 @@ namespace LibHac
if (fileSystem.DirectoryExists("/Nintendo/save"))
{
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem saveDirFs, concatFs, "/Nintendo/save".ToU8String()).ThrowIfFailure();
encSaveFs = new AesXtsFileSystem(saveDirFs, keyset.SdCardKeys[0], 0x4000);
encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000);
}
var encContentFs = new AesXtsFileSystem(contentDirFs, keyset.SdCardKeys[1], 0x4000);
var encContentFs = new AesXtsFileSystem(contentDirFs, keySet.SdCardEncryptionKeys[1].DataRo.ToArray(), 0x4000);
return new SwitchFs(keyset, encContentFs, encSaveFs);
return new SwitchFs(keySet, encContentFs, encSaveFs);
}
public static SwitchFs OpenNandPartition(Keyset keyset, IAttributeFileSystem fileSystem)
public static SwitchFs OpenNandPartition(KeySet keySet, IAttributeFileSystem fileSystem)
{
var concatFs = new ConcatenationFileSystem(fileSystem);
SubdirectoryFileSystem saveDirFs = null;
@ -67,12 +69,12 @@ namespace LibHac
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Contents".ToU8String()).ThrowIfFailure();
return new SwitchFs(keyset, contentDirFs, saveDirFs);
return new SwitchFs(keySet, contentDirFs, saveDirFs);
}
public static SwitchFs OpenNcaDirectory(Keyset keyset, IFileSystem fileSystem)
public static SwitchFs OpenNcaDirectory(KeySet keySet, IFileSystem fileSystem)
{
return new SwitchFs(keyset, fileSystem, null);
return new SwitchFs(keySet, fileSystem, null);
}
private void OpenAllNcas()
@ -88,7 +90,7 @@ namespace LibHac
{
ContentFs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
nca = new SwitchFsNca(new Nca(Keyset, ncaFile.AsStorage()));
nca = new SwitchFsNca(new Nca(KeySet, ncaFile.AsStorage()));
nca.NcaId = GetNcaFilename(fileEntry.Name, nca);
string extension = nca.Nca.Header.ContentType == NcaContentType.Meta ? ".cnmt.nca" : ".nca";
@ -126,7 +128,7 @@ namespace LibHac
{
SaveFs.OpenFile(out IFile file, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
save = new SaveDataFileSystem(Keyset, file.AsStorage(), IntegrityCheckLevel.None, true);
save = new SaveDataFileSystem(KeySet, file.AsStorage(), IntegrityCheckLevel.None, true);
}
catch (Exception ex)
{

Some files were not shown because too many files have changed in this diff Show More