diff --git a/exosphere/sealedkeys.c b/exosphere/sealedkeys.c index c01f9c529..93d9c3c60 100644 --- a/exosphere/sealedkeys.c +++ b/exosphere/sealedkeys.c @@ -9,7 +9,7 @@ const uint8_t g_titlekey_seal_key_source[0x10] = { }; -const uint8_t g_sealed_key_sources[CRYPTOUSECASE_MAX][0x10] = { +const uint8_t g_seal_key_sources[CRYPTOUSECASE_MAX][0x10] = { {0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6}, {0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06}, {0xC7, 0x3F, 0x73, 0x60, 0xB7, 0xB9, 0x9D, 0x74, 0x0A, 0xF8, 0x35, 0x60, 0x1A, 0x18, 0x74, 0x63}, @@ -51,7 +51,7 @@ void seal_key(void *dst, size_t dst_size, const void *src, size_t src_size, unsi } - seal_key_internal(dst, src, g_sealed_key_sources[usecase]); + seal_key_internal(dst, src, g_seal_key_sources[usecase]); } void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned int usecase) { @@ -59,5 +59,5 @@ void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned panic(); } - seal_key_internal(dst, src, g_sealed_key_sources[usecase]); + seal_key_internal(dst, src, g_seal_key_sources[usecase]); } \ No newline at end of file diff --git a/exosphere/smc_user.c b/exosphere/smc_user.c index 2278b0c92..5d9f279a4 100644 --- a/exosphere/smc_user.c +++ b/exosphere/smc_user.c @@ -335,8 +335,8 @@ uint32_t user_unwrap_rsa_wrapped_titlekey(smc_args_t *args) { set_exp_mod_done(0); - /* Expected salt occupies args->X[3] to args->X[6]. */ - tkey_set_expected_salt(&args->X[3]); + /* Expected db prefix occupies args->X[3] to args->X[6]. */ + tkey_set_expected_db_prefix(&args->X[3]); tkey_set_master_key_rev(master_key_rev); diff --git a/exosphere/titlekey.c b/exosphere/titlekey.c new file mode 100644 index 000000000..299683737 --- /dev/null +++ b/exosphere/titlekey.c @@ -0,0 +1,155 @@ +#include + +#include "utils.h" + +#include "titlekey.h" +#include "masterkey.h" +#include "se.h" + +uint64_t g_tkey_expected_db_prefix[4]; +unsigned int g_tkey_master_key_rev = MASTERKEY_REVISION_MAX; + +/* Set the expected db prefix. */ +void tkey_set_expected_db_prefix(uint64_t *db_prefix) { + for (unsigned int i = 0; i < 4; i++) { + g_tkey_expected_db_prefix[i] = db_prefix[i]; + } +} + +void tkey_set_master_key_rev(unsigned int master_key_rev) { + if (master_key_rev >= MASTERKEY_REVISION_MAX) { + panic(); + } +} + +/* Reference for MGF1 can be found here: https://en.wikipedia.org/wiki/Mask_generation_function#MGF1 */ +void calculate_mgf1_and_xor(void *masked, size_t masked_size, const void *seed, size_t seed_size) { + uint8_t cur_hash[0x20]; + uint8_t hash_buf[0xE4]; + if (seed_size >= 0xE0) { + panic(); + } + + size_t hash_buf_size = seed_size + 4; + memcpy(hash_buf, seed, seed_size); + + uint32_t round = 0; + + uint8_t *p_out = (uint8_t *)masked; + + while (masked_size) { + size_t cur_size = masked_size; + if (cur_size > 0x20) { + cur_size = 0x20; + } + + hash_buf[seed_size + 0] = (uint8_t)((round >> 24) & 0xFF); + hash_buf[seed_size + 1] = (uint8_t)((round >> 16) & 0xFF); + hash_buf[seed_size + 2] = (uint8_t)((round >> 8) & 0xFF); + hash_buf[seed_size + 3] = (uint8_t)((round >> 0) & 0xFF); + round++; + + cache_flush(hash_buf, hash_buf + hash_buf_size); + se_calculate_sha256(cur_hash, hash_buf, hash_buf_size); + + for (unsigned int i = 0; i < cur_size; i++) { + *p_out ^= cur_hash[i]; + p_out++; + } + + masked_size -= cur_size; + } +} + +size_t tkey_rsa_unwrap(void *dst, size_t dst_size, void *src, size_t src_size) { + if (src_size != 0x100) { + panic(); + } + + /* RSA Wrapped titlekeys butcher the RSA-PSS primitives. */ + /* Message is of the form prefix || maskedSalt || maskedDB. */ + /* maskedSalt = salt ^ MGF1(maskedDB) */ + /* maskedDB = DB ^ MGF1(salt) */ + /* Salt is random and not validated in any way. */ + /* DB is of the form expected_prefix || 00....01 || wrapped_titlekey. */ + /* expected_prefix is, in practice, a constant in es .rodata. */ + /* I have no idea why Nintendo did this, it should be either nonconstant (in tik) or in tz .rodata. */ + /* However, to keep their API we have to put up with their bizarre choices... */ + + uint8_t *message = (uint8_t *)src; + + /* Prefix should always be zero. */ + if (*message != 0) { + return 0; + } + + + uint8_t *salt = message + 1; + uint8_t *db = message + 0x21; + + /* This will be passed to smc_unwrap_rsa_wrapped_titlekey. */ + uint8_t *expected_db_prefix = (uint8_t *)(&g_tkey_expected_db_prefix[0]); + + /* Unmask the salt. */ + calculate_mgf1_and_xor(salt, 0x20, db, 0xDF); + /* Unmask the DB. */ + calculate_mgf1_and_xor(db, 0xDF, salt, 0x20); + + /* Validate expected salt. */ + for (unsigned int i = 0; i < 0x20; i++) { + if (expected_db_prefix[i] != db[i]) { + return 0; + } + } + + /* Don't validate salt from message[1:0x21] at all. */ + + /* Advance pointer to DB, since we've validated the salt prefix. */ + db += 0x20; + + /* DB must be of the form 0000...01 || wrapped_titlekey */ + if (*db != 0) { + return 0; + } + + /* Locate wrapped_titlekey inside DB. */ + size_t wrapped_key_offset_in_db = 0; + while (wrapped_key_offset_in_db < 0xBF) { + if (db[wrapped_key_offset_in_db] == 0) { + wrapped_key_offset_in_db++; + } else if (db[wrapped_key_offset_in_db] == 1) { + wrapped_key_offset_in_db++; + break; + } else { + /* Invalid wrapped titlekey prefix. */ + return 0; + } + } + + /* Validate size... */ + size_t wrapped_titlekey_size = 0xBF - wrapped_key_offset_in_db; + if (wrapped_titlekey_size > dst_size || wrapped_titlekey_size == 0) { + return 0; + } + + /* Extract the wrapped key. */ + memcpy(dst, &db[wrapped_key_offset_in_db], wrapped_titlekey_size); + return wrapped_key_offset_in_db; +} + +void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size) { + if (g_tkey_master_key_rev >= MASTERKEY_REVISION_MAX || dst_size != 0x10 || src_size != 0x10) { + panic(); + } + + const uint8_t titlekek_source[0x10] = { + 0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B + }; + + /* Generate the appropriate titlekek into keyslot 9. */ + unsigned int master_keyslot = mkey_get_keyslot(g_tkey_master_key_rev); + decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, master_keyslot, titlekek_source, 0x10); + + /* Unwrap the titlekey using the titlekek. */ + se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, dst, 0x10, src, 0x10); +} diff --git a/exosphere/titlekey.h b/exosphere/titlekey.h index 93c2547ed..195539844 100644 --- a/exosphere/titlekey.h +++ b/exosphere/titlekey.h @@ -3,13 +3,11 @@ #include -void tkey_set_expected_salt(uint64_t *salt); +void tkey_set_expected_db_prefix(uint64_t *db_prefix); void tkey_set_master_key_rev(unsigned int master_key_rev); size_t tkey_rsa_unwrap(void *dst, size_t dst_size, void *src, size_t src_size); void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size); -void tkey_seal(void *dst, size_t dst_size, const void *src, size_t src_size); -void tkey_unseal(unsigned int keyslot, const void *src, size_t src_size); #endif \ No newline at end of file