mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
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:
commit
122e83defe
4
.gitignore
vendored
4
.gitignore
vendored
@ -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
259
KEYS.md
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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
100
build/CodeGen/Common.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
60
build/CodeGen/IncludedKeys.txt
Normal file
60
build/CodeGen/IncludedKeys.txt
Normal 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
|
@ -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;
|
393
build/CodeGen/Stage2/KeysCodeGen.cs
Normal file
393
build/CodeGen/Stage2/KeysCodeGen.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
26
build/CodeGen/Stage2/RunStage2.cs
Normal file
26
build/CodeGen/Stage2/RunStage2.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
27
build/CodeGen/_buildCodeGen.csproj
Normal file
27
build/CodeGen/_buildCodeGen.csproj
Normal 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>
|
@ -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.
|
@ -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>
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Bcat
|
||||
{
|
||||
|
94
src/LibHac/Boot/KeyBlob.cs
Normal file
94
src/LibHac/Boot/KeyBlob.cs
Normal 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
549
src/LibHac/Boot/Package1.cs
Normal 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'
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
22
src/LibHac/Common/FixedArrays/Array1.cs
Normal file
22
src/LibHac/Common/FixedArrays/Array1.cs
Normal 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;
|
||||
}
|
||||
}
|
33
src/LibHac/Common/FixedArrays/Array12.cs
Normal file
33
src/LibHac/Common/FixedArrays/Array12.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array12<T>
|
||||
{
|
||||
public const int Length = 12;
|
||||
|
||||
private T _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;
|
||||
}
|
||||
}
|
23
src/LibHac/Common/FixedArrays/Array2.cs
Normal file
23
src/LibHac/Common/FixedArrays/Array2.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array2<T>
|
||||
{
|
||||
public const int Length = 2;
|
||||
|
||||
private T _item1;
|
||||
private T _item2;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
|
||||
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array2<T> value) => value.ItemsRo;
|
||||
}
|
||||
}
|
24
src/LibHac/Common/FixedArrays/Array3.cs
Normal file
24
src/LibHac/Common/FixedArrays/Array3.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array3<T>
|
||||
{
|
||||
public const int Length = 3;
|
||||
|
||||
private T _item1;
|
||||
private T _item2;
|
||||
private T _item3;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
|
||||
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array3<T> value) => value.ItemsRo;
|
||||
}
|
||||
}
|
53
src/LibHac/Common/FixedArrays/Array32.cs
Normal file
53
src/LibHac/Common/FixedArrays/Array32.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array32<T>
|
||||
{
|
||||
public const int Length = 32;
|
||||
|
||||
private T _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;
|
||||
}
|
||||
}
|
25
src/LibHac/Common/FixedArrays/Array4.cs
Normal file
25
src/LibHac/Common/FixedArrays/Array4.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common.FixedArrays
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Array4<T>
|
||||
{
|
||||
public const int Length = 4;
|
||||
|
||||
private T _item1;
|
||||
private T _item2;
|
||||
private T _item3;
|
||||
private T _item4;
|
||||
|
||||
public ref T this[int i] => ref Items[i];
|
||||
|
||||
public Span<T> Items => SpanHelpers.CreateSpan(ref _item1, Length);
|
||||
public readonly ReadOnlySpan<T> ItemsRo => SpanHelpers.CreateReadOnlySpan(in _item1, Length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<T>(in Array4<T> value) => value.ItemsRo;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
19
src/LibHac/Common/Keys/DefaultKeySet.Empty.cs
Normal file
19
src/LibHac/Common/Keys/DefaultKeySet.Empty.cs
Normal 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[] { };
|
||||
}
|
||||
}
|
181
src/LibHac/Common/Keys/DefaultKeySet.cs
Normal file
181
src/LibHac/Common/Keys/DefaultKeySet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
587
src/LibHac/Common/Keys/ExternalKeyReader.cs
Normal file
587
src/LibHac/Common/Keys/ExternalKeyReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
173
src/LibHac/Common/Keys/ExternalKeyWriter.cs
Normal file
173
src/LibHac/Common/Keys/ExternalKeyWriter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
396
src/LibHac/Common/Keys/KeyDerivation.cs
Normal file
396
src/LibHac/Common/Keys/KeyDerivation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
167
src/LibHac/Common/Keys/KeyInfo.cs
Normal file
167
src/LibHac/Common/Keys/KeyInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
384
src/LibHac/Common/Keys/KeySet.cs
Normal file
384
src/LibHac/Common/Keys/KeySet.cs
Normal 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 < 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;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
166
src/LibHac/Crypto/KeyTypes.cs
Normal file
166
src/LibHac/Crypto/KeyTypes.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.FsSrv.Sf;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -2,6 +2,7 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Fs.Shim
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -93,5 +93,10 @@ namespace LibHac.FsSrv
|
||||
ExternalKeys.TrimExcess(newCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureCapacity(int capacity)
|
||||
{
|
||||
ExternalKeys.EnsureCapacity(capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using LibHac.FsSystem;
|
||||
using LibHac.Kvdb;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -2,6 +2,7 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSrv
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
83
src/LibHac/FsSystem/AesCbcStorage.cs
Normal file
83
src/LibHac/FsSystem/AesCbcStorage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using System.IO;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using LibHac.Crypto;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem.Detail;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem.Detail;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.IO.Enumeration;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
|
@ -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
10
src/LibHac/KeyType.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace LibHac
|
||||
{
|
||||
public enum KeyType
|
||||
{
|
||||
None,
|
||||
Common,
|
||||
Unique,
|
||||
Title
|
||||
}
|
||||
}
|
@ -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 < 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
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using LibHac.Common;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.Sm
|
||||
{
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user