diff --git a/exosphere/lp0fw/src/lp0.c b/exosphere/lp0fw/src/lp0.c index 565af3b79..5aaf46987 100644 --- a/exosphere/lp0fw/src/lp0.c +++ b/exosphere/lp0fw/src/lp0.c @@ -25,6 +25,7 @@ #include "cluster.h" #include "flow.h" #include "timer.h" +#include "secmon.h" void reboot(void) { /* Write MAIN_RST */ @@ -81,7 +82,7 @@ void lp0_entry_main(warmboot_metadata_t *meta) { /* Initialize the CPU cluster. */ cluster_initialize_cpu(); - /* TODO: decrypt_restore_secmon_to_tzram(); */ + secmon_restore_to_tzram(target_firmware); /* Power on the CPU cluster. */ cluster_power_on_cpu(); @@ -95,5 +96,3 @@ void lp0_entry_main(warmboot_metadata_t *meta) { FLOW_CTLR_HALT_COP_EVENTS_0 = halt_val; } } - - diff --git a/exosphere/lp0fw/src/se.c b/exosphere/lp0fw/src/se.c new file mode 100644 index 000000000..cb4f1ab98 --- /dev/null +++ b/exosphere/lp0fw/src/se.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "utils.h" +#include "lp0.h" +#include "se.h" + +static void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size); + +/* Initialize a SE linked list. */ +static void __attribute__((__noinline__)) ll_init(volatile se_ll_t *ll, void *buffer, size_t size) { + ll->num_entries = 0; /* 1 Entry. */ + + if (buffer != NULL) { + ll->addr_info.address = (uint32_t) buffer; + ll->addr_info.size = (uint32_t) size; + } else { + ll->addr_info.address = 0; + ll->addr_info.size = 0; + } +} + +void se_check_error_status_reg(void) { + if (se_get_regs()->ERR_STATUS_REG) { + reboot(); + } +} + +void se_check_for_error(void) { + volatile tegra_se_t *se = se_get_regs(); + if (se->INT_STATUS_REG & 0x10000 || se->FLAGS_REG & 3 || se->ERR_STATUS_REG) { + reboot(); + } +} + +void se_verify_flags_cleared(void) { + if (se_get_regs()->FLAGS_REG & 3) { + reboot(); + } +} + +void clear_aes_keyslot(unsigned int keyslot) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_AES_MAX) { + reboot(); + } + + /* Zero out the whole keyslot and IV. */ + for (unsigned int i = 0; i < 0x10; i++) { + se->AES_KEYTABLE_ADDR = (keyslot << 4) | i; + se->AES_KEYTABLE_DATA = 0; + } +} + +void clear_rsa_keyslot(unsigned int keyslot) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_RSA_MAX) { + reboot(); + } + + /* Zero out the whole keyslot. */ + for (unsigned int i = 0; i < 0x40; i++) { + /* Select Keyslot Modulus[i] */ + se->RSA_KEYTABLE_ADDR = (keyslot << 7) | i | 0x40; + se->RSA_KEYTABLE_DATA = 0; + } + for (unsigned int i = 0; i < 0x40; i++) { + /* Select Keyslot Expontent[i] */ + se->RSA_KEYTABLE_ADDR = (keyslot << 7) | i; + se->RSA_KEYTABLE_DATA = 0; + } +} + +void clear_aes_keyslot_iv(unsigned int keyslot) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_AES_MAX) { + reboot(); + } + + for (size_t i = 0; i < (0x10 >> 2); i++) { + se->AES_KEYTABLE_ADDR = (keyslot << 4) | 8 | i; + se->AES_KEYTABLE_DATA = 0; + } +} + + +void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size) { + volatile tegra_se_t *se = se_get_regs(); + se_ll_t in_ll; + se_ll_t out_ll; + + ll_init(&in_ll, (void *)src, src_size); + ll_init(&out_ll, dst, dst_size); + + /* Set the LLs. */ + se->IN_LL_ADDR_REG = (uint32_t)(&in_ll); + se->OUT_LL_ADDR_REG = (uint32_t) (&out_ll); + + /* Set registers for operation. */ + se->ERR_STATUS_REG = se->ERR_STATUS_REG; + se->INT_STATUS_REG = se->INT_STATUS_REG; + se->OPERATION_REG = op; + + while (!(se->INT_STATUS_REG & 0x10)) { /* Wait a while */ } + se_check_for_error(); +} + +/* Secure AES Functionality. */ +void se_perform_aes_block_operation(void *dst, size_t dst_size, const void *src, size_t src_size) { + uint8_t block[0x10] = {0}; + + if (src_size > sizeof(block) || dst_size > sizeof(block)) { + reboot(); + } + + /* Load src data into block. */ + if (src_size != 0) { + memcpy(block, src, src_size); + } + + /* Trigger AES operation. */ + se_get_regs()->BLOCK_COUNT_REG = 0; + trigger_se_blocking_op(OP_START, block, sizeof(block), block, sizeof(block)); + + /* Copy output data into dst. */ + if (dst_size != 0) { + memcpy(dst, block, dst_size); + } +} + +void se_aes_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int config_high) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) { + reboot(); + } + + /* Set configuration high (256-bit vs 128-bit) based on parameter. */ + se->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY) | (config_high << 16); + se->CRYPTO_REG = keyslot << 24 | 0x100; + se_perform_aes_block_operation(dst, 0x10, src, 0x10); +} + +void se_aes_ecb_decrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) { + reboot(); + } + + se->CONFIG_REG = (ALG_AES_DEC | DST_MEMORY); + se->CRYPTO_REG = keyslot << 24; + se_perform_aes_block_operation(dst, 0x10, src, 0x10); +} + +void shift_left_xor_rb(uint8_t *key) { + uint8_t prev_high_bit = 0; + for (unsigned int i = 0; i < 0x10; i++) { + uint8_t cur_byte = key[0xF - i]; + key[0xF - i] = (cur_byte << 1) | (prev_high_bit); + prev_high_bit = cur_byte >> 7; + } + if (prev_high_bit) { + key[0xF] ^= 0x87; + } +} + +void se_compute_aes_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size, unsigned int config_high) { + volatile tegra_se_t *se = se_get_regs(); + + if (keyslot >= KEYSLOT_AES_MAX) { + reboot(); + } + + /* Generate the derived key, to be XOR'd with final output block. */ + uint8_t ALIGN(16) derived_key[0x10] = {0}; + se_aes_ecb_encrypt_block(keyslot, derived_key, sizeof(derived_key), derived_key, sizeof(derived_key), config_high); + shift_left_xor_rb(derived_key); + if (data_size & 0xF) { + shift_left_xor_rb(derived_key); + } + + se->CONFIG_REG = (ALG_AES_ENC | DST_HASHREG) | (config_high << 16); + se->CRYPTO_REG = (keyslot << 24) | (0x145); + clear_aes_keyslot_iv(keyslot); + + unsigned int num_blocks = (data_size + 0xF) >> 4; + /* Handle aligned blocks. */ + if (num_blocks > 1) { + se->BLOCK_COUNT_REG = num_blocks - 2; + trigger_se_blocking_op(OP_START, NULL, 0, data, data_size); + se->CRYPTO_REG |= 0x80; + } + + /* Create final block. */ + uint8_t ALIGN(16) last_block[0x10] = {0}; + if (data_size & 0xF) { + memcpy(last_block, data + (data_size & ~0xF), data_size & 0xF); + last_block[data_size & 0xF] = 0x80; /* Last block = data || 100...0 */ + } else if (data_size >= 0x10) { + memcpy(last_block, data + data_size - 0x10, 0x10); + } + + for (unsigned int i = 0; i < 0x10; i++) { + last_block[i] ^= derived_key[i]; + } + + /* Perform last operation. */ + se->BLOCK_COUNT_REG = 0; + trigger_se_blocking_op(OP_START, NULL, 0, last_block, sizeof(last_block)); + + /* Copy output CMAC. */ + for (unsigned int i = 0; i < (cmac_size >> 2); i++) { + ((uint32_t *)cmac)[i] = ((volatile uint32_t *)se->HASH_RESULT_REG)[i]; + } +} + +void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size) { + se_compute_aes_cmac(keyslot, cmac, cmac_size, data, data_size, 0x202); +} diff --git a/exosphere/lp0fw/src/se.h b/exosphere/lp0fw/src/se.h new file mode 100644 index 000000000..b3e79f6e5 --- /dev/null +++ b/exosphere/lp0fw/src/se.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EXOSPHERE_WARMBOOT_BIN_SE_H +#define EXOSPHERE_WARMBOOT_BIN_SE_H + +#define SE_BASE 0x70012000 +#define MAKE_SE_REG(n) MAKE_REG32(SE_BASE + n) + +#define KEYSLOT_SWITCH_LP0TZRAMKEY 0x2 +#define KEYSLOT_SWITCH_SRKGENKEY 0x8 +#define KEYSLOT_SWITCH_PACKAGE2KEY 0x8 +#define KEYSLOT_SWITCH_TEMPKEY 0x9 +#define KEYSLOT_SWITCH_SESSIONKEY 0xA +#define KEYSLOT_SWITCH_RNGKEY 0xB +#define KEYSLOT_SWITCH_MASTERKEY 0xC +#define KEYSLOT_SWITCH_DEVICEKEY 0xD + +/* This keyslot was added in 4.0.0. */ +#define KEYSLOT_SWITCH_4XNEWDEVICEKEYGENKEY 0xD +#define KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY 0xE +#define KEYSLOT_SWITCH_4XOLDDEVICEKEY 0xF + +/* This keyslot was added in 5.0.0. */ +#define KEYSLOT_SWITCH_5XNEWDEVICEKEYGENKEY 0xA + +#define KEYSLOT_AES_MAX 0x10 +#define KEYSLOT_RSA_MAX 0x2 + +#define KEYSIZE_AES_MAX 0x20 +#define KEYSIZE_RSA_MAX 0x100 + +#define ALG_SHIFT (12) +#define ALG_DEC_SHIFT (8) +#define ALG_NOP (0 << ALG_SHIFT) +#define ALG_AES_ENC (1 << ALG_SHIFT) +#define ALG_AES_DEC ((1 << ALG_DEC_SHIFT) | ALG_NOP) +#define ALG_RNG (2 << ALG_SHIFT) +#define ALG_SHA (3 << ALG_SHIFT) +#define ALG_RSA (4 << ALG_SHIFT) + +#define DST_SHIFT (2) +#define DST_MEMORY (0 << DST_SHIFT) +#define DST_HASHREG (1 << DST_SHIFT) +#define DST_KEYTAB (2 << DST_SHIFT) +#define DST_SRK (3 << DST_SHIFT) +#define DST_RSAREG (4 << DST_SHIFT) + +#define ENCMODE_SHIFT (24) +#define DECMODE_SHIFT (16) +#define ENCMODE_SHA256 (5 << ENCMODE_SHIFT) + +#define HASH_DISABLE (0x0) +#define HASH_ENABLE (0x1) + +#define OP_ABORT 0 +#define OP_START 1 +#define OP_RESTART 2 +#define OP_CTX_SAVE 3 +#define OP_RESTART_IN 4 + +#define CTX_SAVE_SRC_SHIFT 29 +#define CTX_SAVE_SRC_STICKY_BITS (0 << CTX_SAVE_SRC_SHIFT) +#define CTX_SAVE_SRC_KEYTABLE_AES (2 << CTX_SAVE_SRC_SHIFT) +#define CTX_SAVE_SRC_KEYTABLE_RSA (1 << CTX_SAVE_SRC_SHIFT) +#define CTX_SAVE_SRC_MEM (4 << CTX_SAVE_SRC_SHIFT) +#define CTX_SAVE_SRC_SRK (6 << CTX_SAVE_SRC_SHIFT) + +#define CTX_SAVE_KEY_LOW_BITS 0 +#define CTX_SAVE_KEY_HIGH_BITS 1 +#define CTX_SAVE_KEY_ORIGINAL_IV 2 +#define CTX_SAVE_KEY_UPDATED_IV 3 + +#define CTX_SAVE_STICKY_BIT_INDEX_SHIFT 24 +#define CTX_SAVE_KEY_INDEX_SHIFT 8 +#define CTX_SAVE_RSA_KEY_INDEX_SHIFT 16 +#define CTX_SAVE_RSA_KEY_BLOCK_INDEX_SHIFT 12 + +#define RSA_2048_BYTES 0x100 + +typedef struct { + uint32_t _0x0; + uint32_t _0x4; + uint32_t OPERATION_REG; + uint32_t INT_ENABLE_REG; + uint32_t INT_STATUS_REG; + uint32_t CONFIG_REG; + uint32_t IN_LL_ADDR_REG; + uint32_t _0x1C; + uint32_t _0x20; + uint32_t OUT_LL_ADDR_REG; + uint32_t _0x28; + uint32_t _0x2C; + uint8_t HASH_RESULT_REG[0x20]; + uint8_t _0x50[0x20]; + uint32_t CONTEXT_SAVE_CONFIG_REG; + uint8_t _0x74[0x18C]; + uint32_t SHA_CONFIG_REG; + uint32_t SHA_MSG_LENGTH_REG; + uint32_t _0x208; + uint32_t _0x20C; + uint32_t _0x210; + uint32_t SHA_MSG_LEFT_REG; + uint32_t _0x218; + uint32_t _0x21C; + uint32_t _0x220; + uint32_t _0x224; + uint8_t _0x228[0x58]; + uint32_t AES_KEY_READ_DISABLE_REG; + uint32_t AES_KEYSLOT_FLAGS[0x10]; + uint8_t _0x2C4[0x3C]; + uint32_t _0x300; + uint32_t CRYPTO_REG; + uint32_t CRYPTO_CTR_REG[4]; + uint32_t BLOCK_COUNT_REG; + uint32_t AES_KEYTABLE_ADDR; + uint32_t AES_KEYTABLE_DATA; + uint32_t _0x324; + uint32_t _0x328; + uint32_t _0x32C; + uint32_t CRYPTO_KEYTABLE_DST_REG; + uint8_t _0x334[0xC]; + uint32_t RNG_CONFIG_REG; + uint32_t RNG_SRC_CONFIG_REG; + uint32_t RNG_RESEED_INTERVAL_REG; + uint8_t _0x34C[0xB4]; + uint32_t RSA_CONFIG; + uint32_t RSA_KEY_SIZE_REG; + uint32_t RSA_EXP_SIZE_REG; + uint32_t RSA_KEY_READ_DISABLE_REG; + uint32_t RSA_KEYSLOT_FLAGS[2]; + uint32_t _0x418; + uint32_t _0x41C; + uint32_t RSA_KEYTABLE_ADDR; + uint32_t RSA_KEYTABLE_DATA; + uint8_t RSA_OUTPUT[0x100]; + uint8_t _0x528[0x2D8]; + uint32_t FLAGS_REG; + uint32_t ERR_STATUS_REG; + uint32_t _0x808; + uint32_t SPARE_0; + uint32_t _0x810; + uint32_t _0x814; + uint32_t _0x818; + uint32_t _0x81C; + uint8_t _0x820[0x17E0]; +} tegra_se_t; + +typedef struct { + uint32_t address; + uint32_t size; +} se_addr_info_t; + +typedef struct { + uint32_t num_entries; /* Set to total entries - 1 */ + se_addr_info_t addr_info; /* This should really be an array...but for our use case it works. */ +} se_ll_t; + +static inline volatile tegra_se_t *se_get_regs(void) { + return (volatile tegra_se_t *)SE_BASE; +} + +void se_check_error_status_reg(void); +void se_check_for_error(void); + +void se_verify_flags_cleared(void); + +void clear_aes_keyslot(unsigned int keyslot); +void clear_rsa_keyslot(unsigned int keyslot); +void clear_aes_keyslot_iv(unsigned int keyslot); + +void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size); + +#endif diff --git a/exosphere/lp0fw/src/secmon.c b/exosphere/lp0fw/src/secmon.c new file mode 100644 index 000000000..1fe3ea78a --- /dev/null +++ b/exosphere/lp0fw/src/secmon.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "utils.h" +#include "lp0.h" +#include "secmon.h" +#include "se.h" +#include "pmc.h" + +/* "private" functions. */ +static bool secmon_should_clear_aes_keyslot(unsigned int keyslot); +static void secmon_clear_unused_keyslots(void); + +void secmon_restore_to_tzram(const uint32_t target_firmware) { + /* Newer warmboot binaries clear the untouched keyslots for safety. */ + if (target_firmware >= ATMOSPHERE_TARGET_FIRMWARE_500) { + secmon_clear_unused_keyslots(); + } + + /* TODO: stuff */ +} + +bool secmon_should_clear_aes_keyslot(unsigned int keyslot) { + static const uint8_t saved_keyslots[6] = { + KEYSLOT_SWITCH_LP0TZRAMKEY, + KEYSLOT_SWITCH_SESSIONKEY, + KEYSLOT_SWITCH_RNGKEY, + KEYSLOT_SWITCH_MASTERKEY, + KEYSLOT_SWITCH_DEVICEKEY, + KEYSLOT_SWITCH_4XOLDDEVICEKEY + }; + + for (unsigned int i = 0; i < sizeof(saved_keyslots)/sizeof(saved_keyslots[0]); i++) { + if (keyslot == saved_keyslots[i]) { + return false; + } + } + return true; +} + +void secmon_clear_unused_keyslots(void) { + /* Clear unused keyslots. */ + for (unsigned int i = 0; i < KEYSLOT_AES_MAX; i++) { + if (secmon_should_clear_aes_keyslot(i)) { + clear_aes_keyslot(i); + } + clear_aes_keyslot_iv(i); + } + for (unsigned int i = 0; i < KEYSLOT_RSA_MAX; i++) { + clear_rsa_keyslot(i); + } +} \ No newline at end of file diff --git a/exosphere/lp0fw/src/secmon.h b/exosphere/lp0fw/src/secmon.h new file mode 100644 index 000000000..71daac914 --- /dev/null +++ b/exosphere/lp0fw/src/secmon.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EXOSPHERE_WARMBOOT_BIN_SECMON_H +#define EXOSPHERE_WARMBOOT_BIN_SECMON_H + +#include + +#include "utils.h" + +void secmon_restore_to_tzram(const uint32_t target_firmware); + +#endif