Add support for loading external titlekeys file (title.keys)

This commit is contained in:
Michael Scire 2018-07-23 22:16:26 -07:00
parent 809b5442ff
commit 37e368b87b
8 changed files with 181 additions and 49 deletions

View file

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

View file

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

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

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

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

View file

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

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

View file

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