mirror of
https://github.com/SciresM/hactool
synced 2024-11-10 06:34:14 +00:00
Add support for loading external titlekeys file (title.keys)
This commit is contained in:
parent
809b5442ff
commit
37e368b87b
8 changed files with 181 additions and 49 deletions
62
extkeys.c
62
extkeys.c
|
@ -2,6 +2,7 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "pki.h"
|
||||
#include "aes.h"
|
||||
#include "extkeys.h"
|
||||
|
||||
/**
|
||||
|
@ -173,6 +174,24 @@ void parse_hex_key(unsigned char *key, const char *hex, unsigned int len) {
|
|||
}
|
||||
}
|
||||
|
||||
void extkeys_parse_titlekeys(hactool_settings_t *settings, FILE *f) {
|
||||
char *key, *value;
|
||||
int ret;
|
||||
|
||||
while ((ret = get_kv(f, &key, &value)) != 1 && ret != -2) {
|
||||
if (ret == 0) {
|
||||
if (key == NULL || value == NULL) {
|
||||
continue;
|
||||
}
|
||||
unsigned char rights_id[0x10];
|
||||
unsigned char titlekey[0x10];
|
||||
parse_hex_key(rights_id, key, sizeof(rights_id));
|
||||
parse_hex_key(titlekey, value, sizeof(titlekey));
|
||||
settings_add_titlekey(settings, rights_id, titlekey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f) {
|
||||
char *key, *value;
|
||||
int ret;
|
||||
|
@ -335,3 +354,46 @@ void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
int settings_has_titlekey(hactool_settings_t *settings, const unsigned char *rights_id) {
|
||||
return settings_get_titlekey(settings, rights_id) != NULL;
|
||||
}
|
||||
|
||||
void settings_add_titlekey(hactool_settings_t *settings, const unsigned char *rights_id, const unsigned char *titlekey) {
|
||||
if (settings_has_titlekey(settings, rights_id)) {
|
||||
fprintf(stderr, "Error: Rights ID ");
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
fprintf(stderr, "%02X", rights_id[i]);
|
||||
}
|
||||
fprintf(stderr, " already has a corresponding titlekey!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Ensure enough space for keys. */
|
||||
if (settings->known_titlekeys.count == 0) {
|
||||
settings->known_titlekeys.titlekeys = malloc(1 * sizeof(titlekey_entry_t));
|
||||
|
||||
} else if ((settings->known_titlekeys.count & (settings->known_titlekeys.count + 1)) == 0) {
|
||||
settings->known_titlekeys.titlekeys = realloc(settings->known_titlekeys.titlekeys, 2 * (settings->known_titlekeys.count + 1) * sizeof(titlekey_entry_t));
|
||||
}
|
||||
if (settings->known_titlekeys.titlekeys == NULL) {
|
||||
fprintf(stderr, "Failed to allocate titlekey list!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
titlekey_entry_t *new_key = &settings->known_titlekeys.titlekeys[settings->known_titlekeys.count++];
|
||||
|
||||
memcpy(new_key->rights_id, rights_id, 0x10);
|
||||
memcpy(new_key->titlekey, titlekey, 0x10);
|
||||
}
|
||||
|
||||
titlekey_entry_t *settings_get_titlekey(hactool_settings_t *settings, const unsigned char *rights_id) {
|
||||
for (unsigned int i = 0; i < settings->known_titlekeys.count; i++) {
|
||||
if (memcmp(settings->known_titlekeys.titlekeys[i].rights_id, rights_id, 0x10) == 0) {
|
||||
return &settings->known_titlekeys.titlekeys[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,5 +9,10 @@
|
|||
void parse_hex_key(unsigned char *key, const char *hex, unsigned int len);
|
||||
void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f);
|
||||
|
||||
void extkeys_parse_titlekeys(hactool_settings_t *settings, FILE *f);
|
||||
|
||||
int settings_has_titlekey(hactool_settings_t *settings, const unsigned char *rights_id);
|
||||
void settings_add_titlekey(hactool_settings_t *settings, const unsigned char *rights_id, const unsigned char *titlekey);
|
||||
titlekey_entry_t *settings_get_titlekey(hactool_settings_t *settings, const unsigned char *rights_id);
|
||||
|
||||
#endif
|
50
main.c
50
main.c
|
@ -112,6 +112,7 @@ int main(int argc, char **argv) {
|
|||
memset(input_name, 0, sizeof(input_name));
|
||||
filepath_init(&keypath);
|
||||
nca_ctx.tool_ctx = &tool_ctx;
|
||||
nca_ctx.is_cli_target = true;
|
||||
|
||||
nca_ctx.tool_ctx->file_type = FILETYPE_NCA;
|
||||
base_ctx.file_type = FILETYPE_NCA;
|
||||
|
@ -252,12 +253,12 @@ int main(int argc, char **argv) {
|
|||
filepath_set(&nca_ctx.tool_ctx->settings.romfs_dir_path.path, optarg);
|
||||
break;
|
||||
case 12:
|
||||
parse_hex_key(nca_ctx.tool_ctx->settings.titlekey, optarg, 16);
|
||||
nca_ctx.tool_ctx->settings.has_titlekey = 1;
|
||||
parse_hex_key(nca_ctx.tool_ctx->settings.cli_titlekey, optarg, 16);
|
||||
nca_ctx.tool_ctx->settings.has_cli_titlekey = 1;
|
||||
break;
|
||||
case 13:
|
||||
parse_hex_key(nca_ctx.tool_ctx->settings.contentkey, optarg, 16);
|
||||
nca_ctx.tool_ctx->settings.has_contentkey = 1;
|
||||
parse_hex_key(nca_ctx.tool_ctx->settings.cli_contentkey, optarg, 16);
|
||||
nca_ctx.tool_ctx->settings.has_cli_contentkey = 1;
|
||||
break;
|
||||
case 14:
|
||||
nca_ctx.tool_ctx->action |= ACTION_LISTROMFS;
|
||||
|
@ -291,6 +292,7 @@ int main(int argc, char **argv) {
|
|||
nca_init(nca_ctx.tool_ctx->base_nca_ctx);
|
||||
base_ctx.file = nca_ctx.tool_ctx->base_file;
|
||||
nca_ctx.tool_ctx->base_nca_ctx->file = base_ctx.file;
|
||||
nca_ctx.tool_ctx->base_nca_ctx->is_cli_target = false;
|
||||
break;
|
||||
case 17:
|
||||
tool_ctx.settings.out_dir_path.enabled = 1;
|
||||
|
@ -378,40 +380,12 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
/* Try to populate default keyfile. */
|
||||
/* Use $HOME/.switch/prod.keys if it exists */
|
||||
char *home = getenv("HOME");
|
||||
if (home == NULL)
|
||||
home = getenv("USERPROFILE");
|
||||
if (keypath.valid == VALIDITY_INVALID) {
|
||||
if (home != NULL) {
|
||||
filepath_set(&keypath, home);
|
||||
filepath_append(&keypath, ".switch");
|
||||
filepath_append(&keypath, "%s.keys", (tool_ctx.action & ACTION_DEV) ? "dev" : "prod");
|
||||
}
|
||||
}
|
||||
|
||||
/* Load external keys, if relevant. */
|
||||
FILE *keyfile = NULL;
|
||||
if (keypath.valid == VALIDITY_VALID) {
|
||||
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
||||
}
|
||||
|
||||
/* If $HOME/.switch/prod.keys don't exist, try using $XDG_CONFIG_HOME */
|
||||
if (keyfile == NULL) {
|
||||
char *xdgconfig = getenv("XDG_CONFIG_HOME");
|
||||
if (xdgconfig != NULL)
|
||||
filepath_set(&keypath, xdgconfig);
|
||||
else if (home != NULL) {
|
||||
filepath_set(&keypath, home);
|
||||
filepath_append(&keypath, ".config");
|
||||
}
|
||||
/* Keypath contains xdg config. Add switch/%s.keys */
|
||||
filepath_append(&keypath, "switch");
|
||||
filepath_append(&keypath, "%s.keys", (tool_ctx.action & ACTION_DEV) ? "dev" : "prod");
|
||||
}
|
||||
|
||||
if (keyfile == NULL && keypath.valid == VALIDITY_VALID) {
|
||||
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
||||
keyfile = open_key_file((tool_ctx.action & ACTION_DEV) ? "dev" : "prod");
|
||||
}
|
||||
|
||||
if (keyfile != NULL) {
|
||||
|
@ -426,6 +400,12 @@ int main(int argc, char **argv) {
|
|||
pki_derive_keys(&tool_ctx.settings.keyset);
|
||||
fclose(keyfile);
|
||||
}
|
||||
|
||||
/* Try to load titlekeys. */
|
||||
FILE *titlekeyfile = open_key_file("title");
|
||||
if (titlekeyfile != NULL) {
|
||||
extkeys_parse_titlekeys(&tool_ctx.settings, titlekeyfile);
|
||||
}
|
||||
|
||||
if (optind == argc - 1) {
|
||||
/* Copy input file. */
|
||||
|
@ -663,6 +643,10 @@ int main(int argc, char **argv) {
|
|||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (tool_ctx.settings.known_titlekeys.titlekeys != NULL) {
|
||||
free(tool_ctx.settings.known_titlekeys.titlekeys);
|
||||
}
|
||||
|
||||
if (tool_ctx.file != NULL) {
|
||||
fclose(tool_ctx.file);
|
||||
|
|
44
nca.c
44
nca.c
|
@ -5,6 +5,7 @@
|
|||
#include "sha.h"
|
||||
#include "rsa.h"
|
||||
#include "utils.h"
|
||||
#include "extkeys.h"
|
||||
#include "filepath.h"
|
||||
|
||||
/* Initialize the context. */
|
||||
|
@ -207,7 +208,7 @@ size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
|
|||
}
|
||||
if ((read = fread(buffer, 1, count, ctx->file)) != count) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
aes_setiv(ctx->aes, ctx->ctr, 16);
|
||||
aes_decrypt(ctx->aes, buffer, buffer, count);
|
||||
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + count);
|
||||
|
@ -423,11 +424,14 @@ void nca_process(nca_ctx_t *ctx) {
|
|||
nca_decrypt_key_area(ctx);
|
||||
} else {
|
||||
/* Decrypt title key. */
|
||||
if (ctx->tool_ctx->settings.has_titlekey) {
|
||||
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.titlekeks[ctx->crypto_type], 16, AES_MODE_ECB);
|
||||
aes_decrypt(aes_ctx, ctx->tool_ctx->settings.dec_titlekey, ctx->tool_ctx->settings.titlekey, 0x10);
|
||||
free_aes_ctx(aes_ctx);
|
||||
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.titlekeks[ctx->crypto_type], 16, AES_MODE_ECB);
|
||||
if (ctx->is_cli_target && ctx->tool_ctx->settings.has_cli_titlekey) {
|
||||
aes_decrypt(aes_ctx, ctx->tool_ctx->settings.dec_cli_titlekey, ctx->tool_ctx->settings.cli_titlekey, 0x10);
|
||||
} else if (settings_has_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id)) {
|
||||
titlekey_entry_t *entry = settings_get_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id);
|
||||
aes_decrypt(aes_ctx, entry->dec_titlekey, entry->titlekey, 0x10);
|
||||
}
|
||||
free_aes_ctx(aes_ctx);
|
||||
}
|
||||
|
||||
/* Parse sections. */
|
||||
|
@ -475,11 +479,22 @@ void nca_process(nca_ctx_t *ctx) {
|
|||
ctx->section_contexts[i].is_decrypted = 1;
|
||||
}
|
||||
|
||||
if (ctx->tool_ctx->settings.has_contentkey) {
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.contentkey, 16, AES_MODE_CTR);
|
||||
if (ctx->is_cli_target && ctx->tool_ctx->settings.has_cli_contentkey) {
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.cli_contentkey, 16, AES_MODE_CTR);
|
||||
} else {
|
||||
if (ctx->has_rights_id) {
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.dec_titlekey, 16, AES_MODE_CTR);
|
||||
if (ctx->is_cli_target && ctx->tool_ctx->settings.has_cli_titlekey) {
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.dec_cli_titlekey, 16, AES_MODE_CTR);
|
||||
} else if (settings_has_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id)) {
|
||||
titlekey_entry_t *entry = settings_get_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id);
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(entry->dec_titlekey, 16, AES_MODE_CTR);
|
||||
} else {
|
||||
if (i == 0) {
|
||||
printf("[WARN] Unable to match rights id to titlekey. Update title.keys?\n");
|
||||
}
|
||||
unsigned char fallback[0x10] = {0};
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(fallback, 16, AES_MODE_CTR);
|
||||
}
|
||||
} else {
|
||||
if (ctx->section_contexts[i].crypt_type == CRYPT_CTR || ctx->section_contexts[i].crypt_type == CRYPT_BKTR) {
|
||||
ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[2], 16, AES_MODE_CTR);
|
||||
|
@ -721,6 +736,7 @@ void nca_print_sections(nca_ctx_t *ctx) {
|
|||
printf(" Size: 0x%012"PRIx64"\n", ctx->section_contexts[i].size);
|
||||
printf(" Partition Type: %s\n", nca_get_section_type(&ctx->section_contexts[i]));
|
||||
if (!(ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA)) {
|
||||
nca_update_ctr(ctx->section_contexts[i].ctr, ctx->section_contexts[i].offset);
|
||||
memdump(stdout, " Section CTR: ", &ctx->section_contexts[i].ctr, 16);
|
||||
}
|
||||
switch (ctx->section_contexts[i].type) {
|
||||
|
@ -783,9 +799,15 @@ void nca_print(nca_ctx_t *ctx) {
|
|||
|
||||
if (ctx->has_rights_id) {
|
||||
memdump(stdout, "Rights ID: ", &ctx->header.rights_id, 0x10);
|
||||
if (ctx->tool_ctx->settings.has_titlekey) {
|
||||
memdump(stdout, "Titlekey (Encrypted) ", ctx->tool_ctx->settings.titlekey, 0x10);
|
||||
memdump(stdout, "Titlekey (Decrypted) ", ctx->tool_ctx->settings.dec_titlekey, 0x10);
|
||||
if (ctx->is_cli_target && ctx->tool_ctx->settings.has_cli_titlekey) {
|
||||
memdump(stdout, "Titlekey (Encrypted) (From CLI) ", ctx->tool_ctx->settings.cli_titlekey, 0x10);
|
||||
memdump(stdout, "Titlekey (Decrypted) (From CLI) ", ctx->tool_ctx->settings.dec_cli_titlekey, 0x10);
|
||||
} else if (settings_has_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id)) {
|
||||
titlekey_entry_t *entry = settings_get_titlekey(&ctx->tool_ctx->settings, ctx->header.rights_id);
|
||||
memdump(stdout, "Titlekey (Encrypted) ", entry->titlekey, 0x10);
|
||||
memdump(stdout, "Titlekey (Decrypted) ", entry->dec_titlekey, 0x10);
|
||||
} else {
|
||||
printf("Titlekey: Unknown\n");
|
||||
}
|
||||
} else {
|
||||
printf("Key Area Encryption Key: %"PRIx8"\n", ctx->header.kaek_ind);
|
||||
|
|
1
nca.h
1
nca.h
|
@ -169,6 +169,7 @@ typedef struct nca_ctx {
|
|||
unsigned char crypto_type;
|
||||
int has_rights_id;
|
||||
int is_decrypted;
|
||||
int is_cli_target;
|
||||
enum nca_version format_version;
|
||||
validity_t fixed_sig_validity;
|
||||
validity_t npdm_sig_validity;
|
||||
|
|
20
settings.h
20
settings.h
|
@ -54,12 +54,24 @@ typedef struct {
|
|||
} override_filepath_t;
|
||||
|
||||
typedef struct {
|
||||
nca_keyset_t keyset;
|
||||
int has_titlekey;
|
||||
unsigned char rights_id[0x10];
|
||||
unsigned char titlekey[0x10];
|
||||
unsigned char dec_titlekey[0x10];
|
||||
int has_contentkey;
|
||||
unsigned char contentkey[0x10];
|
||||
} titlekey_entry_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int count;
|
||||
titlekey_entry_t *titlekeys;
|
||||
} known_titlekeys_t;
|
||||
|
||||
typedef struct {
|
||||
nca_keyset_t keyset;
|
||||
int has_cli_titlekey;
|
||||
unsigned char cli_titlekey[0x10];
|
||||
unsigned char dec_cli_titlekey[0x10];
|
||||
known_titlekeys_t known_titlekeys;
|
||||
int has_cli_contentkey;
|
||||
unsigned char cli_contentkey[0x10];
|
||||
int has_sdseed;
|
||||
unsigned char sdseed[0x10];
|
||||
unsigned char keygen_sbk[0x10];
|
||||
|
|
44
utils.c
44
utils.c
|
@ -193,7 +193,49 @@ const char *get_key_revision_summary(uint8_t key_rev) {
|
|||
return "3.0.1-3.0.2";
|
||||
case 3:
|
||||
return "4.0.0-4.1.0";
|
||||
case 4:
|
||||
return "5.0.0-5.1.0";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FILE *open_key_file(const char *prefix) {
|
||||
filepath_t keypath;
|
||||
filepath_init(&keypath);
|
||||
/* Use $HOME/.switch/prod.keys if it exists */
|
||||
char *home = getenv("HOME");
|
||||
if (home == NULL)
|
||||
home = getenv("USERPROFILE");
|
||||
if (home != NULL) {
|
||||
filepath_set(&keypath, home);
|
||||
filepath_append(&keypath, ".switch");
|
||||
filepath_append(&keypath, "%s.keys", prefix);
|
||||
}
|
||||
|
||||
/* Load external keys, if relevant. */
|
||||
FILE *keyfile = NULL;
|
||||
if (keypath.valid == VALIDITY_VALID) {
|
||||
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
||||
}
|
||||
|
||||
/* If $HOME/.switch/prod.keys don't exist, try using $XDG_CONFIG_HOME */
|
||||
if (keyfile == NULL) {
|
||||
char *xdgconfig = getenv("XDG_CONFIG_HOME");
|
||||
if (xdgconfig != NULL)
|
||||
filepath_set(&keypath, xdgconfig);
|
||||
else if (home != NULL) {
|
||||
filepath_set(&keypath, home);
|
||||
filepath_append(&keypath, ".config");
|
||||
}
|
||||
/* Keypath contains xdg config. Add switch/%s.keys */
|
||||
filepath_append(&keypath, "switch");
|
||||
filepath_append(&keypath, "%s.keys", prefix);
|
||||
}
|
||||
|
||||
if (keyfile == NULL && keypath.valid == VALIDITY_VALID) {
|
||||
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
||||
}
|
||||
|
||||
return keyfile;
|
||||
}
|
||||
|
|
4
utils.h
4
utils.h
|
@ -42,6 +42,7 @@ void save_buffer_to_directory_file(void *buf, uint64_t size, struct filepath *di
|
|||
|
||||
const char *get_key_revision_summary(uint8_t key_rev);
|
||||
|
||||
FILE *open_key_file(const char *prefix);
|
||||
|
||||
validity_t check_memory_hash_table(FILE *f_in, unsigned char *hash_table, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block);
|
||||
validity_t check_file_hash_table(FILE *f_in, uint64_t hash_ofs, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block);
|
||||
|
@ -51,6 +52,9 @@ inline int fseeko64(FILE *__stream, long long __off, int __whence)
|
|||
{
|
||||
return _fseeki64(__stream, __off, __whence);
|
||||
}
|
||||
#elif __MINGW32__
|
||||
/* MINGW32 does not have 64-bit offsets even with large file support. */
|
||||
extern int fseeko64 (FILE *__stream, _off64_t __off, int __whence);
|
||||
#else
|
||||
/* off_t is 64-bit with large file support */
|
||||
#define fseeko64 fseek
|
||||
|
|
Loading…
Reference in a new issue