From 37e368b87bf2deccc4d21783e28557b240a20602 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 23 Jul 2018 22:16:26 -0700 Subject: [PATCH] Add support for loading external titlekeys file (title.keys) --- extkeys.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ extkeys.h | 5 +++++ main.c | 50 +++++++++++++++---------------------------- nca.c | 44 ++++++++++++++++++++++++++++---------- nca.h | 1 + settings.h | 20 ++++++++++++++---- utils.c | 44 +++++++++++++++++++++++++++++++++++++- utils.h | 4 ++++ 8 files changed, 181 insertions(+), 49 deletions(-) diff --git a/extkeys.c b/extkeys.c index 7ef94d7..7dc30e9 100644 --- a/extkeys.c +++ b/extkeys.c @@ -2,6 +2,7 @@ #include #include #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; +} + diff --git a/extkeys.h b/extkeys.h index 09ee7c8..52b0190 100644 --- a/extkeys.h +++ b/extkeys.h @@ -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 \ No newline at end of file diff --git a/main.c b/main.c index 0c2791c..0de3281 100644 --- a/main.c +++ b/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); diff --git a/nca.c b/nca.c index 16e3d1a..ff156b0 100644 --- a/nca.c +++ b/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); diff --git a/nca.h b/nca.h index cca33e8..c1dccc9 100644 --- a/nca.h +++ b/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; diff --git a/settings.h b/settings.h index a00755c..7fe5052 100644 --- a/settings.h +++ b/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]; diff --git a/utils.c b/utils.c index 5d3208b..aa90c51 100644 --- a/utils.c +++ b/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"; } -} \ No newline at end of file +} + +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; +} diff --git a/utils.h b/utils.h index 47d55e2..e44826f 100644 --- a/utils.h +++ b/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