diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index 621517d29e..927693ad3c 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -24,6 +24,7 @@ FEATURES_WANTED = ["input", "sbu", "sd_card", "rdb_led", "usb", "consumption_mas CCFLAGS_MOD = '' CPPPATH_MOD = [] CPPDEFINES_MOD = [ + 'AES_128', 'USE_INSECURE_PRNG', ] SOURCE_MOD = [] @@ -50,11 +51,24 @@ CPPPATH_MOD += [ 'vendor/trezor-storage', ] SOURCE_MOD += [ + 'vendor/trezor-crypto/aes/aes_modes.c', + 'vendor/trezor-crypto/aes/aesccm.c', + 'vendor/trezor-crypto/aes/aescrypt.c', + 'vendor/trezor-crypto/aes/aeskey.c', + 'vendor/trezor-crypto/aes/aestab.c', + 'vendor/trezor-crypto/bignum.c', 'vendor/trezor-crypto/chacha_drbg.c', 'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c', + 'vendor/trezor-crypto/ecdsa.c', + 'vendor/trezor-crypto/hmac.c', + 'vendor/trezor-crypto/hmac_drbg.c', 'vendor/trezor-crypto/memzero.c', + 'vendor/trezor-crypto/nist256p1.c', 'vendor/trezor-crypto/rand.c', + 'vendor/trezor-crypto/rfc6979.c', + 'vendor/trezor-crypto/secp256k1.c', 'vendor/trezor-crypto/sha2.c', + 'vendor/trezor-crypto/tls_prf.c', ] # modtrezorui @@ -80,8 +94,14 @@ SOURCE_PRODTEST = [ 'embed/prodtest/startup.s', 'embed/prodtest/header.S', 'embed/prodtest/main.c', + 'embed/prodtest/prodtest_common.c', ] +if TREZOR_MODEL in ('R',): + SOURCE_PRODTEST += [ + 'embed/prodtest/optiga_prodtest.c', + ] + # fonts tools.add_font('NORMAL', FONT_NORMAL, CPPDEFINES_MOD, SOURCE_MOD) tools.add_font('BOLD', FONT_BOLD, CPPDEFINES_MOD, SOURCE_MOD) diff --git a/core/embed/prodtest/main.c b/core/embed/prodtest/main.c index 399a8d9bcd..98d3f028ad 100644 --- a/core/embed/prodtest/main.c +++ b/core/embed/prodtest/main.c @@ -28,17 +28,24 @@ #include "display.h" #include "flash.h" #include "i2c.h" -#include "mini_printf.h" #include "model.h" +#include "mpu.h" +#include "prodtest_common.h" #include "random_delays.h" -#include "rng.h" #include "sbu.h" #include "sdcard.h" #include "secbool.h" #include "touch.h" #include "usb.h" +#ifdef USE_OPTIGA +#include "optiga_commands.h" +#include "optiga_prodtest.h" +#include "optiga_transport.h" +#endif + #include "memzero.h" +#include "stm32f4xx_ll_utils.h" #ifdef TREZOR_MODEL_T #define MODEL_IDENTIFIER "TREZOR2-" @@ -46,8 +53,6 @@ #define MODEL_IDENTIFIER "T2B1-" #endif -enum { VCP_IFACE = 0x00 }; - static secbool startswith(const char *s, const char *prefix) { return sectrue * (0 == strncmp(s, prefix, strlen(prefix))); } @@ -57,11 +62,6 @@ static void vcp_intr(void) { ensure(secfalse, "vcp_intr"); } -static void vcp_puts(const char *s, size_t len) { - int r = usb_vcp_write_blocking(VCP_IFACE, (const uint8_t *)s, len, -1); - (void)r; -} - static char vcp_getchar(void) { uint8_t c = 0; int r = usb_vcp_read_blocking(VCP_IFACE, &c, 1, -1); @@ -91,16 +91,6 @@ static void vcp_readline(char *buf, size_t len) { } } -static void vcp_printf(const char *fmt, ...) { - static char buf[128]; - va_list va; - va_start(va, fmt); - int r = mini_vsnprintf(buf, sizeof(buf), fmt, va); - va_end(va); - vcp_puts(buf, r); - vcp_puts("\r\n", 2); -} - static void usb_init_all(void) { enum { VCP_PACKET_LEN = 64, @@ -160,7 +150,7 @@ static void draw_border(int width, int padding) { static void test_border(void) { draw_border(2, 0); - vcp_printf("OK"); + vcp_println("OK"); } static void test_display(const char *colors) { @@ -185,10 +175,10 @@ static void test_display(const char *colors) { c = 0xFFFF; break; } - display_bar(i * w, 0, i * w + w, 240, c); + display_bar(i * w, 0, i * w + w, DISPLAY_RESY, c); } display_refresh(); - vcp_printf("OK"); + vcp_println("OK"); } #ifdef USE_BUTTON @@ -196,13 +186,13 @@ static void test_display(const char *colors) { static secbool test_btn_press(uint32_t deadline, uint32_t btn) { while (button_read() != (btn | BTN_EVT_DOWN)) { if (HAL_GetTick() > deadline) { - vcp_printf("ERROR TIMEOUT"); + vcp_println("ERROR TIMEOUT"); return secfalse; } } while (button_read() != (btn | BTN_EVT_UP)) { if (HAL_GetTick() > deadline) { - vcp_printf("ERROR TIMEOUT"); + vcp_println("ERROR TIMEOUT"); return secfalse; } } @@ -231,7 +221,7 @@ static secbool test_btn_all(uint32_t deadline) { break; } if (HAL_GetTick() > deadline) { - vcp_printf("ERROR TIMEOUT"); + vcp_println("ERROR TIMEOUT"); return secfalse; } } @@ -254,7 +244,7 @@ static secbool test_btn_all(uint32_t deadline) { break; } if (HAL_GetTick() > deadline) { - vcp_printf("ERROR TIMEOUT"); + vcp_println("ERROR TIMEOUT"); return secfalse; } } @@ -268,21 +258,21 @@ static void test_button(const char *args) { timeout = args[5] - '0'; uint32_t deadline = HAL_GetTick() + timeout * 1000; secbool r = test_btn_press(deadline, BTN_LEFT); - if (r == sectrue) vcp_printf("OK"); + if (r == sectrue) vcp_println("OK"); } if (startswith(args, "RIGHT ")) { timeout = args[6] - '0'; uint32_t deadline = HAL_GetTick() + timeout * 1000; secbool r = test_btn_press(deadline, BTN_RIGHT); - if (r == sectrue) vcp_printf("OK"); + if (r == sectrue) vcp_println("OK"); } if (startswith(args, "BOTH ")) { timeout = args[5] - '0'; uint32_t deadline = HAL_GetTick() + timeout * 1000; secbool r = test_btn_all(deadline); - if (r == sectrue) vcp_printf("OK"); + if (r == sectrue) vcp_println("OK"); } } @@ -335,9 +325,9 @@ static void test_touch(const char *args) { if (touch_click_timeout(&evt, timeout * 1000)) { uint16_t x = touch_unpack_x(evt); uint16_t y = touch_unpack_y(evt); - vcp_printf("OK %d %d", x, y); + vcp_println("OK %d %d", x, y); } else { - vcp_printf("ERROR TIMEOUT"); + vcp_println("ERROR TIMEOUT"); } display_clear(); display_refresh(); @@ -377,7 +367,7 @@ static void test_pwm(const char *args) { display_backlight(v); display_refresh(); - vcp_printf("OK"); + vcp_println("OK"); } #ifdef USE_SD_CARD @@ -387,13 +377,13 @@ static void test_sd(void) { static uint32_t buf2[BLOCK_SIZE / sizeof(uint32_t)]; if (sectrue != sdcard_is_present()) { - vcp_printf("ERROR NOCARD"); + vcp_println("ERROR NOCARD"); return; } ensure(sdcard_power_on(), NULL); if (sectrue != sdcard_read_blocks(buf1, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) { - vcp_printf("ERROR sdcard_read_blocks (0)"); + vcp_println("ERROR sdcard_read_blocks (0)"); goto power_off; } for (int j = 1; j <= 2; j++) { @@ -402,21 +392,21 @@ static void test_sd(void) { } if (sectrue != sdcard_write_blocks(buf1, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) { - vcp_printf("ERROR sdcard_write_blocks (%d)", j); + vcp_println("ERROR sdcard_write_blocks (%d)", j); goto power_off; } HAL_Delay(1000); if (sectrue != sdcard_read_blocks(buf2, 0, BLOCK_SIZE / SDCARD_BLOCK_SIZE)) { - vcp_printf("ERROR sdcard_read_blocks (%d)", j); + vcp_println("ERROR sdcard_read_blocks (%d)", j); goto power_off; } if (0 != memcmp(buf1, buf2, sizeof(buf1))) { - vcp_printf("ERROR DATA MISMATCH"); + vcp_println("ERROR DATA MISMATCH"); goto power_off; } } - vcp_printf("OK"); + vcp_println("OK"); power_off: sdcard_power_off(); @@ -436,7 +426,7 @@ static void test_wipe(void) { display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY / 2 + 10, "WIPED", -1, FONT_BOLD, COLOR_WHITE, COLOR_BLACK); display_refresh(); - vcp_printf("OK"); + vcp_println("OK"); } #ifdef USE_SBU @@ -444,7 +434,7 @@ static void test_sbu(const char *args) { secbool sbu1 = sectrue * (args[0] == '1'); secbool sbu2 = sectrue * (args[1] == '1'); sbu_set(sbu1, sbu2); - vcp_printf("OK"); + vcp_println("OK"); } #endif @@ -463,9 +453,9 @@ static void test_otp_read(void) { // use (null) for empty data if (data[0] == 0x00) { - vcp_printf("OK (null)"); + vcp_println("OK (null)"); } else { - vcp_printf("OK %s", (const char *)data); + vcp_println("OK %s", (const char *)data); } } @@ -477,10 +467,23 @@ static void test_otp_write(const char *args) { sizeof(data)), NULL); ensure(flash_otp_lock(FLASH_OTP_BLOCK_BATCH), NULL); - vcp_printf("OK"); + vcp_println("OK"); } static void test_otp_write_device_variant(const char *args) { +#ifdef USE_OPTIGA + optiga_locked_status status = get_optiga_locked_status(); + if (status == OPTIGA_LOCKED_FALSE) { + vcp_println("ERROR NOT LOCKED"); + return; + } + + if (status != OPTIGA_LOCKED_TRUE) { + // Error reported by get_optiga_locked_status(). + return; + } +#endif + volatile char data[32]; memzero((char *)data, sizeof(data)); data[0] = 1; @@ -513,7 +516,17 @@ static void test_otp_write_device_variant(const char *args) { (const uint8_t *)data, sizeof(data)), NULL); ensure(flash_otp_lock(FLASH_OTP_BLOCK_DEVICE_VARIANT), NULL); - vcp_printf("OK"); + vcp_println("OK"); +} + +void cpuid_read(void) { + uint32_t cpuid[3]; + cpuid[0] = LL_GetUID_Word0(); + cpuid[1] = LL_GetUID_Word1(); + cpuid[2] = LL_GetUID_Word2(); + + vcp_print("OK "); + vcp_println_hex((uint8_t *)cpuid, sizeof(cpuid)); } #define BACKLIGHT_NORMAL 150 @@ -528,8 +541,10 @@ int main(void) { #ifdef USE_BUTTON button_init(); #endif -#ifdef USE_TOUCH +#ifdef USE_I2C i2c_init(); +#endif +#ifdef USE_TOUCH touch_init(); #endif #ifdef USE_SBU @@ -537,6 +552,15 @@ int main(void) { #endif usb_init_all(); +#ifdef USE_OPTIGA + optiga_init(); + optiga_open_application(); + pair_optiga(); +#endif + + mpu_config_prodtest(); + drop_privileges(); + display_clear(); draw_border(1, 3); @@ -551,13 +575,17 @@ int main(void) { display_fade(0, BACKLIGHT_NORMAL, 1000); - char line[128]; + char line[2048]; // expecting hundreds of bytes represented as hexadecimal + // characters for (;;) { vcp_readline(line, sizeof(line)); if (startswith(line, "PING")) { - vcp_printf("OK"); + vcp_println("OK"); + + } else if (startswith(line, "CPUID READ")) { + cpuid_read(); } else if (startswith(line, "BORDER")) { test_border(); @@ -584,6 +612,29 @@ int main(void) { #ifdef USE_SBU } else if (startswith(line, "SBU ")) { test_sbu(line + 4); +#endif +#ifdef USE_OPTIGA + } else if (startswith(line, "OPTIGAID READ")) { + optigaid_read(); + } else if (startswith(line, "CERTINF READ")) { + cert_read(OID_CERT_INF); + } else if (startswith(line, "CERTDEV WRITE ")) { + cert_write(OID_CERT_DEV, line + 14); + } else if (startswith(line, "CERTDEV READ")) { + cert_read(OID_CERT_DEV); + } else if (startswith(line, "CERTFIDO WRITE ")) { + cert_write(OID_CERT_FIDO, line + 15); + } else if (startswith(line, "CERTFIDO READ")) { + cert_read(OID_CERT_FIDO); + } else if (startswith(line, "KEYFIDO WRITE ")) { + keyfido_write(line + 14); + } else if (startswith(line, "KEYFIDO READ")) { + pubkey_read(OID_KEY_FIDO); + } else if (startswith(line, "LOCK")) { + optiga_lock(); + } else if (startswith(line, "CHECK LOCKED")) { + check_locked(); + #endif } else if (startswith(line, "OTP READ")) { @@ -599,7 +650,7 @@ int main(void) { test_wipe(); } else { - vcp_printf("UNKNOWN"); + vcp_println("UNKNOWN"); } } diff --git a/core/embed/prodtest/optiga_prodtest.c b/core/embed/prodtest/optiga_prodtest.c new file mode 100644 index 0000000000..0fba782e94 --- /dev/null +++ b/core/embed/prodtest/optiga_prodtest.c @@ -0,0 +1,500 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 "optiga_prodtest.h" +#include "aes/aes.h" +#include "ecdsa.h" +#include "memzero.h" +#include "nist256p1.h" +#include "optiga_commands.h" +#include "optiga_transport.h" +#include "prodtest_common.h" +#include "rand.h" +#include "secret.h" +#include "sha2.h" + +typedef enum { + OPTIGA_PAIRING_UNPAIRED = 0, + OPTIGA_PAIRING_PAIRED, + OPTIGA_PAIRING_ERR_RNG, + OPTIGA_PAIRING_ERR_READ, + OPTIGA_PAIRING_ERR_HANDSHAKE, +} optiga_pairing; + +static optiga_pairing optiga_pairing_state = OPTIGA_PAIRING_UNPAIRED; + +// Data object access conditions. +static const optiga_metadata_item ACCESS_PAIRED = + OPTIGA_ACCESS_CONDITION(OPTIGA_ACCESS_COND_CONF, OID_KEY_PAIRING); +static const optiga_metadata_item KEY_USE_SIGN = { + (const uint8_t[]){OPTIGA_KEY_USAGE_SIGN}, 1}; +static const optiga_metadata_item TYPE_PTFBIND = { + (const uint8_t[]){OPTIGA_DATA_TYPE_PTFBIND}, 1}; + +static bool optiga_paired(void) { + const char *details = ""; + + switch (optiga_pairing_state) { + case OPTIGA_PAIRING_PAIRED: + return true; + case OPTIGA_PAIRING_ERR_RNG: + details = "optiga_get_random error"; + break; + case OPTIGA_PAIRING_ERR_READ: + details = "failed to read pairing secret"; + break; + case OPTIGA_PAIRING_ERR_HANDSHAKE: + details = "optiga_sec_chan_handshake"; + break; + default: + break; + } + + vcp_println("ERROR Optiga not paired (%s).", details); + return false; +} + +static bool set_metadata(uint16_t oid, const optiga_metadata *metadata) { + uint8_t serialized[258] = {0}; + size_t size = 0; + optiga_result ret = optiga_serialize_metadata(metadata, serialized, + sizeof(serialized), &size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_serialize_metadata error %d for OID 0x%04x.", ret, + oid); + return false; + } + + optiga_set_data_object(oid, true, serialized, size); + + ret = + optiga_get_data_object(oid, true, serialized, sizeof(serialized), &size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_get_metadata error %d for OID 0x%04x.", ret, oid); + return false; + } + + optiga_metadata metadata_stored = {0}; + ret = optiga_parse_metadata(serialized, size, &metadata_stored); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_parse_metadata error %d.", ret); + return false; + } + + if (!optiga_compare_metadata(metadata, &metadata_stored)) { + vcp_println("ERROR optiga_compare_metadata failed."); + return false; + } + + return true; +} + +void pair_optiga(void) { + // The pairing key may already be written and locked. The success of the + // pairing procedure is determined by optiga_sec_chan_handshake(). Therefore + // it is OK for some of the intermediate operations to fail. + + // Enable writing the pairing secret to OPTIGA. + optiga_metadata metadata = {0}; + metadata.change = OPTIGA_ACCESS_ALWAYS; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + metadata.data_type = TYPE_PTFBIND; + set_metadata(OID_KEY_PAIRING, &metadata); // Ignore result. + + // Generate pairing secret. + uint8_t secret[SECRET_OPTIGA_KEY_LEN] = {0}; + optiga_result ret = optiga_get_random(secret, sizeof(secret)); + if (OPTIGA_SUCCESS != ret) { + optiga_pairing_state = OPTIGA_PAIRING_ERR_RNG; + return; + } + + // Store pairing secret. + ret = optiga_set_data_object(OID_KEY_PAIRING, false, secret, sizeof(secret)); + if (OPTIGA_SUCCESS == ret) { + secret_erase(); + secret_write_header(); + secret_write(secret, SECRET_OPTIGA_KEY_OFFSET, SECRET_OPTIGA_KEY_LEN); + } + + // Verify whether the secret was stored correctly in flash and OPTIGA. + memzero(secret, sizeof(secret)); + if (secret_read(secret, SECRET_OPTIGA_KEY_OFFSET, SECRET_OPTIGA_KEY_LEN) != + sectrue) { + optiga_pairing_state = OPTIGA_PAIRING_ERR_READ; + return; + } + + ret = optiga_sec_chan_handshake(secret, sizeof(secret)); + memzero(secret, sizeof(secret)); + if (OPTIGA_SUCCESS != ret) { + optiga_pairing_state = OPTIGA_PAIRING_ERR_HANDSHAKE; + return; + } + + optiga_pairing_state = OPTIGA_PAIRING_PAIRED; + return; +} + +void optiga_lock(void) { + if (!optiga_paired()) return; + + // Delete trust anchor. + optiga_result ret = + optiga_set_data_object(OID_TRUST_ANCHOR, false, (const uint8_t *)"\0", 1); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_set_data error %d for 0x%04x.", ret, + OID_TRUST_ANCHOR); + return; + } + + // Set data object metadata. + optiga_metadata metadata = {0}; + + // Set metadata for device certificate. + memzero(&metadata, sizeof(metadata)); + metadata.lcso = OPTIGA_LCS_OPERATIONAL; + metadata.change = OPTIGA_ACCESS_NEVER; + metadata.read = OPTIGA_ACCESS_ALWAYS; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + if (!set_metadata(OID_CERT_DEV, &metadata)) { + return; + } + + // Set metadata for FIDO attestation certificate. + memzero(&metadata, sizeof(metadata)); + metadata.lcso = OPTIGA_LCS_OPERATIONAL; + metadata.change = OPTIGA_ACCESS_NEVER; + metadata.read = OPTIGA_ACCESS_ALWAYS; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + if (!set_metadata(OID_CERT_FIDO, &metadata)) { + return; + } + + // Set metadata for device private key. + memzero(&metadata, sizeof(metadata)); + metadata.lcso = OPTIGA_LCS_OPERATIONAL; + metadata.change = OPTIGA_ACCESS_NEVER; + metadata.read = OPTIGA_ACCESS_NEVER; + metadata.execute = ACCESS_PAIRED; + metadata.key_usage = KEY_USE_SIGN; + if (!set_metadata(OID_KEY_DEV, &metadata)) { + return; + } + + // Set metadata for FIDO attestation private key. + memzero(&metadata, sizeof(metadata)); + metadata.lcso = OPTIGA_LCS_OPERATIONAL; + metadata.change = OPTIGA_ACCESS_NEVER; + metadata.read = OPTIGA_ACCESS_NEVER; + metadata.execute = ACCESS_PAIRED; + metadata.key_usage = KEY_USE_SIGN; + if (!set_metadata(OID_KEY_FIDO, &metadata)) { + return; + } + + // Set metadata for pairing key. + memzero(&metadata, sizeof(metadata)); + metadata.lcso = OPTIGA_LCS_OPERATIONAL; + metadata.change = OPTIGA_ACCESS_NEVER; + metadata.read = OPTIGA_ACCESS_NEVER; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + metadata.data_type = TYPE_PTFBIND; + if (!set_metadata(OID_KEY_PAIRING, &metadata)) { + return; + } + + vcp_println("OK"); +} + +optiga_locked_status get_optiga_locked_status(void) { + if (!optiga_paired()) return OPTIGA_LOCKED_ERROR; + + const uint16_t oids[] = {OID_CERT_DEV, OID_CERT_FIDO, OID_KEY_DEV, + OID_KEY_FIDO, OID_KEY_PAIRING}; + + optiga_metadata locked_metadata = {0}; + locked_metadata.lcso = OPTIGA_LCS_OPERATIONAL; + for (size_t i = 0; i < sizeof(oids) / sizeof(oids[0]); ++i) { + uint8_t metadata_buffer[258] = {0}; + size_t metadata_size = 0; + optiga_result ret = + optiga_get_data_object(oids[i], true, metadata_buffer, + sizeof(metadata_buffer), &metadata_size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_get_metadata error %d for OID 0x%04x.", ret, + oids[i]); + return OPTIGA_LOCKED_ERROR; + } + + optiga_metadata stored_metadata = {0}; + ret = + optiga_parse_metadata(metadata_buffer, metadata_size, &stored_metadata); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_parse_metadata error %d.", ret); + return OPTIGA_LOCKED_ERROR; + } + + if (!optiga_compare_metadata(&locked_metadata, &stored_metadata)) { + return OPTIGA_LOCKED_FALSE; + } + } + + return OPTIGA_LOCKED_TRUE; +} + +void check_locked(void) { + switch (get_optiga_locked_status()) { + case OPTIGA_LOCKED_TRUE: + vcp_println("OK YES"); + break; + case OPTIGA_LOCKED_FALSE: + vcp_println("OK NO"); + break; + default: + // Error reported by get_optiga_locked_status(). + break; + } +} + +void optigaid_read(void) { + if (!optiga_paired()) return; + + uint8_t optiga_id[27] = {0}; + size_t optiga_id_size = 0; + + optiga_result ret = + optiga_get_data_object(OPTIGA_OID_COPROC_UID, false, optiga_id, + sizeof(optiga_id), &optiga_id_size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret, + OPTIGA_OID_COPROC_UID); + return; + } + + vcp_print("OK "); + vcp_println_hex(optiga_id, optiga_id_size); +} + +void cert_read(uint16_t oid) { + if (!optiga_paired()) return; + + static uint8_t cert[2048] = {0}; + size_t cert_size = 0; + optiga_result ret = + optiga_get_data_object(oid, false, cert, sizeof(cert), &cert_size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret, oid); + return; + } + + size_t offset = 0; + if (cert[0] == 0xC0) { + // TLS identity certificate chain. + size_t tls_identity_size = (cert[1] << 8) + cert[2]; + size_t cert_chain_size = (cert[3] << 16) + (cert[4] << 8) + cert[5]; + size_t first_cert_size = (cert[6] << 16) + (cert[7] << 8) + cert[8]; + if (tls_identity_size + 3 > cert_size || + cert_chain_size + 3 > tls_identity_size || + first_cert_size > cert_chain_size) { + vcp_println("ERROR invalid TLS identity in 0x%04x.", oid); + return; + } + offset = 9; + cert_size = first_cert_size; + } + + if (cert_size == 0) { + vcp_println("ERROR no certificate in 0x%04x.", oid); + return; + } + + vcp_print("OK "); + vcp_println_hex(cert + offset, cert_size); +} + +void cert_write(uint16_t oid, char *data) { + if (!optiga_paired()) return; + + // Enable writing to the certificate slot. + optiga_metadata metadata = {0}; + metadata.change = OPTIGA_ACCESS_ALWAYS; + set_metadata(oid, &metadata); // Ignore result. + + uint8_t data_bytes[1024]; + + int len = get_from_hex(data_bytes, sizeof(data_bytes), data); + if (len < 0) { + vcp_println("ERROR Hexadecimal decoding error %d.", len); + return; + } + + optiga_result ret = optiga_set_data_object(oid, false, data_bytes, len); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_set_data error %d for 0x%04x.", ret, oid); + return; + } + + vcp_println("OK"); +} + +void pubkey_read(uint16_t oid) { + if (!optiga_paired()) return; + + // Enable key agreement usage. + + optiga_metadata metadata = {0}; + uint8_t key_usage = OPTIGA_KEY_USAGE_KEYAGREE; + metadata.key_usage.ptr = &key_usage; + metadata.key_usage.len = 1; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + + if (!set_metadata(oid, &metadata)) { + return; + } + + // Execute ECDH with base point to get the x-coordinate of the public key. + static const uint8_t BASE_POINT[] = { + 0x03, 0x42, 0x00, 0x04, 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, + 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, + 0x2d, 0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96, + 0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a, + 0x7c, 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, + 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5}; + uint8_t public_key[32] = {0}; + size_t public_key_size = 0; + optiga_result ret = + optiga_calc_ssec(OPTIGA_CURVE_P256, oid, BASE_POINT, sizeof(BASE_POINT), + public_key, sizeof(public_key), &public_key_size); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_calc_ssec error %d.", ret); + return; + } + + vcp_print("OK "); + vcp_println_hex(public_key, public_key_size); +} + +void keyfido_write(char *data) { + if (!optiga_paired()) return; + + const size_t EPH_PUB_KEY_SIZE = 33; + const size_t PAYLOAD_SIZE = 32; + const size_t CIPHERTEXT_OFFSET = EPH_PUB_KEY_SIZE; + const size_t EXPECTED_SIZE = EPH_PUB_KEY_SIZE + PAYLOAD_SIZE; + + // Enable key agreement usage for device key. + + optiga_metadata metadata = {0}; + uint8_t key_usage = OPTIGA_KEY_USAGE_KEYAGREE; + metadata.key_usage.ptr = &key_usage; + metadata.key_usage.len = 1; + metadata.execute = OPTIGA_ACCESS_ALWAYS; + + if (!set_metadata(OID_KEY_DEV, &metadata)) { + return; + } + + // Read encrypted FIDO attestation private key. + + uint8_t data_bytes[EXPECTED_SIZE]; + int len = get_from_hex(data_bytes, sizeof(data_bytes), data); + if (len < 0) { + vcp_println("ERROR Hexadecimal decoding error %d.", len); + return; + } + + if (len != EXPECTED_SIZE) { + vcp_println("ERROR Unexpected input length."); + return; + } + + // Expand sender's ephemeral public key. + uint8_t public_key[3 + 65] = {0x03, 0x42, 0x00}; + if (ecdsa_uncompress_pubkey(&nist256p1, data_bytes, &public_key[3]) != 1) { + vcp_println("ERROR Failed to decode public key."); + return; + } + + // Execute ECDH with device private key. + uint8_t secret[32] = {0}; + size_t secret_size = 0; + optiga_result ret = optiga_calc_ssec(OPTIGA_CURVE_P256, OID_KEY_DEV, + public_key, sizeof(public_key), secret, + sizeof(secret), &secret_size); + if (OPTIGA_SUCCESS != ret) { + memzero(secret, sizeof(secret)); + vcp_println("ERROR optiga_calc_ssec error %d.", ret); + return; + } + + // Hash the shared secret. Use the result as the decryption key. + sha256_Raw(secret, secret_size, secret); + aes_decrypt_ctx ctx = {0}; + AES_RETURN aes_ret = aes_decrypt_key256(secret, &ctx); + if (EXIT_SUCCESS != aes_ret) { + vcp_println("ERROR aes_decrypt_key256 error."); + memzero(&ctx, sizeof(ctx)); + memzero(secret, sizeof(secret)); + return; + } + + // Decrypt the FIDO attestation key. + uint8_t fido_key[PAYLOAD_SIZE]; + + // The IV is intentionally all-zero, which is not a problem, because the + // encryption key is unique for each ciphertext. + uint8_t iv[AES_BLOCK_SIZE] = {0}; + aes_ret = aes_cbc_decrypt(&data_bytes[CIPHERTEXT_OFFSET], fido_key, + sizeof(fido_key), iv, &ctx); + memzero(&ctx, sizeof(ctx)); + memzero(secret, sizeof(secret)); + if (EXIT_SUCCESS != aes_ret) { + memzero(fido_key, sizeof(fido_key)); + vcp_println("ERROR aes_cbc_decrypt error."); + return; + } + + // Write trust anchor certificate to OID 0xE0E8 + ret = optiga_set_trust_anchor(); + if (OPTIGA_SUCCESS != ret) { + memzero(fido_key, sizeof(fido_key)); + vcp_println("ERROR optiga_set_trust_anchor error %d.", ret); + return; + } + + // Set change access condition for the FIDO key to Int(0xE0E8), so that we + // can write the FIDO key using the trust anchor in OID 0xE0E8. + memzero(&metadata, sizeof(metadata)); + metadata.change.ptr = (const uint8_t *)"\x21\xe0\xe8"; + metadata.change.len = 3; + if (!set_metadata(OID_KEY_FIDO, &metadata)) { + return; + } + + // Store the FIDO attestation key. + ret = optiga_set_priv_key(OID_KEY_FIDO, fido_key); + memzero(fido_key, sizeof(fido_key)); + if (OPTIGA_SUCCESS != ret) { + vcp_println("ERROR optiga_set_priv_key error %d.", ret); + return; + } + + vcp_println("OK"); +} diff --git a/core/embed/prodtest/optiga_prodtest.h b/core/embed/prodtest/optiga_prodtest.h new file mode 100644 index 0000000000..516e1b8459 --- /dev/null +++ b/core/embed/prodtest/optiga_prodtest.h @@ -0,0 +1,51 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 PRODTEST_OPTIGA_PRODTESTS_H +#define PRODTEST_OPTIGA_PRODTEST_H + +#include +#include +#include + +#define OID_CERT_INF OPTIGA_OID_CERT + 0 +#define OID_CERT_DEV OPTIGA_OID_CERT + 1 +#define OID_CERT_FIDO OPTIGA_OID_CERT + 2 +#define OID_KEY_DEV OPTIGA_OID_ECC_KEY + 0 +#define OID_KEY_FIDO OPTIGA_OID_ECC_KEY + 2 +#define OID_KEY_PAIRING OPTIGA_OID_PTFBIND_SECRET +#define OID_TRUST_ANCHOR OPTIGA_OID_CA_CERT + 0 + +typedef enum { + OPTIGA_LOCKED_TRUE, + OPTIGA_LOCKED_FALSE, + OPTIGA_LOCKED_ERROR, +} optiga_locked_status; + +void pair_optiga(void); +void optigaid_read(void); +void cert_read(uint16_t oid); +void cert_write(uint16_t oid, char *data); +void keyfido_write(char *data); +void pubkey_read(uint16_t oid); +void optiga_lock(void); +optiga_locked_status get_optiga_locked_status(void); +void check_locked(void); + +#endif diff --git a/core/embed/prodtest/prodtest_common.c b/core/embed/prodtest/prodtest_common.c new file mode 100644 index 0000000000..a56607a0c2 --- /dev/null +++ b/core/embed/prodtest/prodtest_common.c @@ -0,0 +1,102 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 "prodtest_common.h" +#include "mini_printf.h" +#include "usb.h" + +void vcp_puts(const char *s, size_t len) { + int r = usb_vcp_write_blocking(VCP_IFACE, (const uint8_t *)s, len, -1); + (void)r; +} + +void vcp_print(const char *fmt, ...) { + static char buf[128]; + va_list va; + va_start(va, fmt); + int r = mini_vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + vcp_puts(buf, r); +} + +void vcp_println(const char *fmt, ...) { + static char buf[128]; + va_list va; + va_start(va, fmt); + int r = mini_vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + vcp_puts(buf, r); + vcp_puts("\r\n", 2); +} + +void vcp_println_hex(uint8_t *data, uint16_t len) { + for (int i = 0; i < len; i++) { + vcp_print("%02X", data[i]); + } + vcp_puts("\r\n", 2); +} + +static uint16_t get_byte_from_hex(const char **hex) { + uint8_t result = 0; + + // Skip whitespace. + while (**hex == ' ') { + *hex += 1; + } + + for (int i = 0; i < 2; i++) { + result <<= 4; + char c = **hex; + if (c >= '0' && c <= '9') { + result |= c - '0'; + } else if (c >= 'A' && c <= 'F') { + result |= c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + result |= c - 'a' + 10; + } else if (c == '\0') { + return 0x100; + } else { + return 0xFFFF; + } + *hex += 1; + } + return result; +} + +int get_from_hex(uint8_t *buf, uint16_t buf_len, const char *hex) { + int len = 0; + uint16_t b = get_byte_from_hex(&hex); + for (len = 0; len < buf_len && b <= 0xff; ++len) { + buf[len] = b; + b = get_byte_from_hex(&hex); + } + + if (b == 0x100) { + // Success. + return len; + } + + if (b > 0xff) { + // Non-hexadecimal character. + return -1; + } + + // Buffer too small. + return -2; +} diff --git a/core/embed/prodtest/prodtest_common.h b/core/embed/prodtest/prodtest_common.h new file mode 100644 index 0000000000..bd0f33747d --- /dev/null +++ b/core/embed/prodtest/prodtest_common.h @@ -0,0 +1,34 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 PRODTEST_COMMON_H +#define PRODTEST_COMMON_H + +#include +#include + +enum { VCP_IFACE = 0x00 }; + +void vcp_puts(const char *s, size_t len); +void vcp_print(const char *fmt, ...); +void vcp_println(const char *fmt, ...); +void vcp_println_hex(uint8_t *data, uint16_t len); +int get_from_hex(uint8_t *buf, uint16_t buf_len, const char *hex); + +#endif diff --git a/core/embed/trezorhal/mpu.h b/core/embed/trezorhal/mpu.h index 9d30af77be..11db675596 100644 --- a/core/embed/trezorhal/mpu.h +++ b/core/embed/trezorhal/mpu.h @@ -23,5 +23,6 @@ void mpu_config_off(void); void mpu_config_bootloader(void); void mpu_config_firmware(void); +void mpu_config_prodtest(void); #endif diff --git a/core/embed/trezorhal/optiga/optiga_commands.c b/core/embed/trezorhal/optiga/optiga_commands.c index d2b13866d7..5ba30f526a 100644 --- a/core/embed/trezorhal/optiga/optiga_commands.c +++ b/core/embed/trezorhal/optiga/optiga_commands.c @@ -39,8 +39,10 @@ static size_t tx_size = 0; // TODO change to operational \x07 const optiga_metadata_item OPTIGA_LCS_OPERATIONAL = {(const uint8_t *)"\x01", 1}; -const optiga_metadata_item OPTIGA_ACCESS_ALWAYS = {(const uint8_t *)"\x00", 1}; -const optiga_metadata_item OPTIGA_ACCESS_NEVER = {(const uint8_t *)"\xFF", 1}; +const optiga_metadata_item OPTIGA_ACCESS_ALWAYS = { + (const uint8_t[]){OPTIGA_ACCESS_COND_ALW}, 1}; +const optiga_metadata_item OPTIGA_ACCESS_NEVER = { + (const uint8_t[]){OPTIGA_ACCESS_COND_NEV}, 1}; const optiga_metadata_item OPTIGA_VERSION_DEFAULT = { (const uint8_t *)"\xC1\x02\x00\x00", 4}; @@ -265,7 +267,7 @@ optiga_result optiga_get_error_code(uint8_t *error_code) { *(ptr++) = 0x00; // get data write_uint16(&ptr, tx_size - 4); - write_uint16(&ptr, 0xf1c2); // error code data object OID + write_uint16(&ptr, OPTIGA_OID_ERROR_CODE); optiga_result ret = optiga_execute_command(tx_buffer, tx_size, tx_buffer, sizeof(tx_buffer), &tx_size); diff --git a/core/embed/trezorhal/optiga_commands.h b/core/embed/trezorhal/optiga_commands.h index 4a520c4ffb..fc9709b837 100644 --- a/core/embed/trezorhal/optiga_commands.h +++ b/core/embed/trezorhal/optiga_commands.h @@ -25,6 +25,19 @@ #include #include "optiga_common.h" +// Data object identifiers. +typedef enum { + OPTIGA_OID_COPROC_UID = 0xE0C2, // Coprocessor UID. + OPTIGA_OID_CERT = 0xE0E0, // Public key certificates [1-4]. + OPTIGA_OID_CA_CERT = 0xE0E8, // Root CA public key certificates [1-2]. + OPTIGA_OID_COUNTER = 0xE120, // Monotonic counters [1-4]. + OPTIGA_OID_ECC_KEY = 0xE0F0, // Private ECC keys [1-4]. + OPTIGA_OID_PTFBIND_SECRET = 0xE140, // Shared platform binding secret. + OPTIGA_OID_ERROR_CODE = 0xF1C2, // Command error code. + OPTIGA_OID_DATA = 0xF1D0, // Arbitrary 140 B data objects [1-12]. + OPTIGA_OID_BIG_DATA = 0xF1E0, // Arbitrary 1500 B data objects [1-2]. +} optiga_oid; + // ECC curve identifiers. typedef enum { OPTIGA_CURVE_P256 = 0x03, // NIST P256 ECC key. @@ -79,6 +92,16 @@ typedef enum { OPTIGA_DATA_TYPE_AUTOREF = 0x31, // Secret for verifying external entity. } optiga_data_type; +// Access conditions. +typedef enum { + OPTIGA_ACCESS_COND_ALW = 0x00, // Always. + OPTIGA_ACCESS_COND_CONF = 0x20, // Confidentiality protection required. + OPTIGA_ACCESS_COND_INT = 0x21, // Integrity protection required. + OPTIGA_ACCESS_COND_AUTO = 0x23, // Authorization required. + OPTIGA_ACCESS_COND_LUC = 0x40, // Usage limited by counter. + OPTIGA_ACCESS_COND_NEV = 0xFF, // Never. +} optiga_access_cond; + typedef struct { const uint8_t *ptr; uint16_t len; @@ -99,6 +122,9 @@ typedef struct { optiga_metadata_item reset_type; // F0 - Factory reset type. } optiga_metadata; +#define OPTIGA_ACCESS_CONDITION(ac_id, oid) \ + { (const uint8_t[]){ac_id, oid >> 8, oid & 0xff}, 3 } + extern const optiga_metadata_item OPTIGA_LCS_OPERATIONAL; extern const optiga_metadata_item OPTIGA_ACCESS_ALWAYS; extern const optiga_metadata_item OPTIGA_ACCESS_NEVER; diff --git a/core/embed/trezorhal/stm32f4/mpu.c b/core/embed/trezorhal/stm32f4/mpu.c index c7c2676fa2..265adc54e8 100644 --- a/core/embed/trezorhal/stm32f4/mpu.c +++ b/core/embed/trezorhal/stm32f4/mpu.c @@ -194,3 +194,98 @@ void mpu_config_firmware(void) { __asm__ volatile("dsb"); __asm__ volatile("isb"); } + +void mpu_config_prodtest(void) { + // Disable MPU + HAL_MPU_Disable(); + + // Note: later entries overwrite previous ones + + // // Boardloader (0x08000000 - 0x0800BFFF, 48 KiB, read-only, execute never) + // MPU->RNR = MPU_REGION_NUMBER0; + // MPU->RBAR = FLASH_BASE; + // MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + // LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_PRIV_RO_URO | + // MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xC0); + + // Secret area (0x08100000 - 0x08103FFF, 16 KiB, read-write, execute never) + // MPU->RNR = MPU_REGION_NUMBER0; + // MPU->RBAR = FLASH_BASE + 0x100000; + // MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + // LL_MPU_REGION_SIZE_16KB | LL_MPU_REGION_FULL_ACCESS | + // MPU_RASR_XN_Msk; + + // Bootloader (0x08020000 - 0x0803FFFF, 64 KiB, read-only) + MPU->RNR = MPU_REGION_NUMBER1; + MPU->RBAR = FLASH_BASE + 0x20000; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_PRIV_RO_URO; + + // Firmware (0x08040000 - 0x080FFFFF, 6 * 128 KiB = 1024 KiB except 2/8 at + // start = 768 KiB, read-only) + MPU->RNR = MPU_REGION_NUMBER2; + MPU->RBAR = FLASH_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + LL_MPU_REGION_SIZE_1MB | LL_MPU_REGION_FULL_ACCESS | + MPU_SUBREGION_DISABLE(0x03); + + // Firmware extra (0x08120000 - 0x081FFFFF, 7 * 128 KiB = 1024 KiB except 1/8 + // at start = 896 KiB, read-only) + MPU->RNR = MPU_REGION_NUMBER3; + MPU->RBAR = FLASH_BASE + 0x100000; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + LL_MPU_REGION_SIZE_1MB | LL_MPU_REGION_FULL_ACCESS | + MPU_SUBREGION_DISABLE(0x01); + + // SRAM (0x20000000 - 0x2002FFFF, 192 KiB = 256 KiB except 2/8 at end, + // read-write, execute never) + MPU->RNR = MPU_REGION_NUMBER4; + MPU->RBAR = SRAM_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | + LL_MPU_REGION_SIZE_256KB | LL_MPU_REGION_FULL_ACCESS | + MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xC0); + +#ifdef USE_SDRAM + // Peripherals (0x40000000 - 0x5FFFFFFF, read-write, execute never) + // SDRAM (0xC0000000 - 0xDFFFFFFF, read-write, execute never) + MPU->RNR = MPU_REGION_NUMBER5; + MPU->RBAR = 0; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_PERIPH | + LL_MPU_REGION_SIZE_4GB | LL_MPU_REGION_FULL_ACCESS | + MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xBB); +#else + // Peripherals (0x40000000 - 0x5FFFFFFF, read-write, execute never) + // External RAM (0x60000000 - 0x7FFFFFFF, read-write, execute never) + MPU->RNR = MPU_REGION_NUMBER5; + MPU->RBAR = PERIPH_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_PERIPH | + LL_MPU_REGION_SIZE_1GB | LL_MPU_REGION_FULL_ACCESS | + MPU_RASR_XN_Msk; +#endif + +#if defined STM32F427xx || defined STM32F429xx + // CCMRAM (0x10000000 - 0x1000FFFF, read-write, execute never) + MPU->RNR = MPU_REGION_NUMBER6; + MPU->RBAR = CCMDATARAM_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | + LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | + MPU_RASR_XN_Msk; +#elif STM32F405xx + // no CCMRAM +#else +#error Unsupported MCU +#endif + + // OTP (0x1FFF7800 - 0x1FFF7C00, read-write, execute never) + MPU->RNR = MPU_REGION_NUMBER7; + MPU->RBAR = FLASH_OTP_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | + LL_MPU_REGION_SIZE_1KB | LL_MPU_REGION_FULL_ACCESS | + MPU_RASR_XN_Msk; + + // Enable MPU + HAL_MPU_Enable(LL_MPU_CTRL_HARDFAULT_NMI); + + __asm__ volatile("dsb"); + __asm__ volatile("isb"); +}