diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index ec71b3cc7..7e66eb938 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -57,8 +57,10 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/aes/aeskey.c', 'vendor/trezor-crypto/aes/aestab.c', 'vendor/trezor-crypto/bignum.c', + 'vendor/trezor-crypto/buffer.c', 'vendor/trezor-crypto/chacha_drbg.c', 'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c', + 'vendor/trezor-crypto/der.c', 'vendor/trezor-crypto/ecdsa.c', 'vendor/trezor-crypto/hmac.c', 'vendor/trezor-crypto/hmac_drbg.c', diff --git a/core/embed/prodtest/optiga_prodtest.c b/core/embed/prodtest/optiga_prodtest.c index c59a0553a..927fc5b1a 100644 --- a/core/embed/prodtest/optiga_prodtest.c +++ b/core/embed/prodtest/optiga_prodtest.c @@ -17,12 +17,16 @@ * along with this program. If not, see . */ -#include "optiga_prodtest.h" +#include + #include "aes/aes.h" +#include "buffer.h" +#include "der.h" #include "ecdsa.h" #include "memzero.h" #include "nist256p1.h" #include "optiga_commands.h" +#include "optiga_prodtest.h" #include "optiga_transport.h" #include "prodtest_common.h" #include "rand.h" @@ -295,7 +299,7 @@ void optigaid_read(void) { void cert_read(uint16_t oid) { if (!optiga_paired()) return; - static uint8_t cert[2048] = {0}; + static uint8_t cert[OPTIGA_MAX_CERT_SIZE] = {0}; size_t cert_size = 0; optiga_result ret = optiga_get_data_object(oid, false, cert, sizeof(cert), &cert_size); @@ -337,7 +341,7 @@ void cert_write(uint16_t oid, char *data) { metadata.change = OPTIGA_META_ACCESS_ALWAYS; set_metadata(oid, &metadata); // Ignore result. - uint8_t data_bytes[1024]; + uint8_t data_bytes[OPTIGA_MAX_CERT_SIZE]; int len = get_from_hex(data_bytes, sizeof(data_bytes), data); if (len < 0) { @@ -351,6 +355,21 @@ void cert_write(uint16_t oid, char *data) { return; } + // Verify that the certificate was written correctly. + static uint8_t cert[OPTIGA_MAX_CERT_SIZE] = {0}; + size_t cert_size = 0; + ret = optiga_get_data_object(oid, false, cert, sizeof(cert), &cert_size); + if (OPTIGA_SUCCESS != ret || cert_size != len || + memcmp(data_bytes, cert, len) != 0) { + vcp_println("ERROR optiga_get_data_object error %d for 0x%04x.", ret, oid); + return; + } + + if (oid == OID_CERT_DEV && !check_device_cert_chain(cert, cert_size)) { + // Error returned by check_device_cert_chain(). + return; + } + vcp_println("OK"); } @@ -512,3 +531,165 @@ void sec_read(void) { vcp_print("OK "); vcp_println_hex(&sec, sizeof(sec)); } + +// clang-format off +static const uint8_t ECDSA_WITH_SHA256[] = { + 0x30, 0x0a, // a sequence of 10 bytes + 0x06, 0x08, // an OID of 8 bytes + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, +}; +// clang-format on + +static const uint8_t ROOT_PUBLIC_KEYS[][65] = { + { + // Production root public key. + 0x04, 0xca, 0x97, 0x48, 0x0a, 0xc0, 0xd7, 0xb1, 0xe6, 0xef, 0xaf, + 0xe5, 0x18, 0xcd, 0x43, 0x3c, 0xec, 0x2b, 0xf8, 0xab, 0x98, 0x22, + 0xd7, 0x6e, 0xaf, 0xd3, 0x43, 0x63, 0xb5, 0x5d, 0x63, 0xe6, 0x03, + 0x80, 0xbf, 0xf2, 0x0a, 0xcc, 0x75, 0xcd, 0xe0, 0x3c, 0xff, 0xcb, + 0x50, 0xab, 0x6f, 0x8c, 0xe7, 0x0c, 0x87, 0x8e, 0x37, 0xeb, 0xc5, + 0x8f, 0xf7, 0xcc, 0xa0, 0xa8, 0x3b, 0x16, 0xb1, 0x5f, 0xa5, + }, + { + // Development root public key. + 0x04, 0x7f, 0x77, 0x36, 0x8d, 0xea, 0x2d, 0x4d, 0x61, 0xe9, 0x89, + 0xf4, 0x74, 0xa5, 0x67, 0x23, 0xc3, 0x21, 0x2d, 0xac, 0xf8, 0xa8, + 0x08, 0xd8, 0x79, 0x55, 0x95, 0xef, 0x38, 0x44, 0x14, 0x27, 0xc4, + 0x38, 0x9b, 0xc4, 0x54, 0xf0, 0x20, 0x89, 0xd7, 0xf0, 0x8b, 0x87, + 0x30, 0x05, 0xe4, 0xc2, 0x8d, 0x43, 0x24, 0x68, 0x99, 0x78, 0x71, + 0xc0, 0xbf, 0x28, 0x6f, 0xd3, 0x86, 0x1e, 0x21, 0xe9, 0x6a, + }, +}; + +bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) { + // Checks the integrity of the device certificate chain to ensure that the + // certificate data was not corrupted in transport and that the device + // certificate belongs to this device. THIS IS NOT A FULL VERIFICATION OF THE + // CERTIFICATE CHAIN. + + // Generate a P-256 signature using the device private key. + uint8_t digest[SHA256_DIGEST_LENGTH] = {1}; + uint8_t der_sig[72] = {DER_SEQUENCE}; + size_t der_sig_size = 0; + if (optiga_calc_sign(OID_KEY_DEV, digest, sizeof(digest), &der_sig[2], + sizeof(der_sig) - 2, &der_sig_size) != OPTIGA_SUCCESS) { + vcp_println("ERROR check_device_cert_chain, optiga_calc_sign."); + return false; + } + der_sig[1] = der_sig_size; + + uint8_t sig[64] = {0}; + if (ecdsa_sig_from_der(der_sig, der_sig_size + 2, sig) != 0) { + vcp_println("ERROR check_device_cert_chain, ecdsa_sig_from_der."); + return false; + } + + BUFFER_READER chain_reader = {0}; + buffer_reader_init(&chain_reader, chain, chain_size); + int cert_count = 0; + while (buffer_remaining(&chain_reader) > 0) { + // Read the next certificate in the chain. + cert_count += 1; + DER_ITEM cert = {0}; + if (!der_read_item(&chain_reader, &cert) || cert.id != DER_SEQUENCE) { + vcp_println("ERROR check_device_cert_chain, der_read_item 1, cert %d.", + cert_count); + return false; + } + + // Read the tbsCertificate. + DER_ITEM tbs_cert = {0}; + if (!der_read_item(&cert.buf, &tbs_cert)) { + vcp_println("ERROR check_device_cert_chain, der_read_item 2, cert %d.", + cert_count); + return false; + } + + // Read the Subject Public Key Info. + DER_ITEM pub_key_info = {0}; + for (int i = 0; i < 7; ++i) { + if (!der_read_item(&tbs_cert.buf, &pub_key_info)) { + vcp_println("ERROR check_device_cert_chain, der_read_item 3, cert %d.", + cert_count); + return false; + } + } + + // Read the public key. + DER_ITEM pub_key = {0}; + uint8_t unused_bits = 0; + const uint8_t *pub_key_bytes = NULL; + for (int i = 0; i < 2; ++i) { + if (!der_read_item(&pub_key_info.buf, &pub_key)) { + vcp_println("ERROR check_device_cert_chain, der_read_item 4, cert %d.", + cert_count); + return false; + } + } + + if (!buffer_get(&pub_key.buf, &unused_bits) || + buffer_remaining(&pub_key.buf) != 65 || + !buffer_ptr(&pub_key.buf, &pub_key_bytes)) { + vcp_println("ERROR check_device_cert_chain, reading public key, cert %d.", + cert_count); + return false; + } + + // Verify the previous signature. + if (ecdsa_verify_digest(&nist256p1, pub_key_bytes, sig, digest) != 0) { + vcp_println( + "ERROR check_device_cert_chain, ecdsa_verify_digest, cert %d.", + cert_count); + return false; + } + + // Prepare the hash of tbsCertificate for the next signature verification. + sha256_Raw(tbs_cert.buf.data, tbs_cert.buf.size, digest); + + // Read the signatureAlgorithm and ensure it matches ECDSA_WITH_SHA256. + DER_ITEM sig_alg = {0}; + if (!der_read_item(&cert.buf, &sig_alg) || + sig_alg.buf.size != sizeof(ECDSA_WITH_SHA256) || + memcmp(ECDSA_WITH_SHA256, sig_alg.buf.data, + sizeof(ECDSA_WITH_SHA256)) != 0) { + vcp_println( + "ERROR check_device_cert_chain, checking signatureAlgorithm, cert " + "%d.", + cert_count); + return false; + } + + // Read the signatureValue. + DER_ITEM sig_val = {0}; + if (!der_read_item(&cert.buf, &sig_val) || sig_val.id != DER_BIT_STRING || + !buffer_get(&sig_val.buf, &unused_bits) || unused_bits != 0) { + vcp_println( + "ERROR check_device_cert_chain, reading signatureValue, cert %d.", + cert_count); + return false; + } + + // Extract the signature for the next signature verification. + const uint8_t *sig_bytes = NULL; + if (!buffer_ptr(&sig_val.buf, &sig_bytes) || + ecdsa_sig_from_der(sig_bytes, buffer_remaining(&sig_val.buf), sig) != + 0) { + vcp_println("ERROR check_device_cert_chain, ecdsa_sig_from_der, cert %d.", + cert_count); + return false; + } + } + + // Verify that the last certificate in the chain is valid for one of the known + // root public keys. + for (int i = 0; i < sizeof(ROOT_PUBLIC_KEYS) / sizeof(ROOT_PUBLIC_KEYS[0]); + ++i) { + if (ecdsa_verify_digest(&nist256p1, ROOT_PUBLIC_KEYS[i], sig, digest) == + 0) { + return true; + } + } + + vcp_println("ERROR check_device_cert_chain, ecdsa_verify_digest root."); + return false; +} diff --git a/core/embed/prodtest/optiga_prodtest.h b/core/embed/prodtest/optiga_prodtest.h index 6c4f34e80..313349f37 100644 --- a/core/embed/prodtest/optiga_prodtest.h +++ b/core/embed/prodtest/optiga_prodtest.h @@ -48,5 +48,6 @@ void optiga_lock(void); optiga_locked_status get_optiga_locked_status(void); void check_locked(void); void sec_read(void); +bool check_device_cert_chain(const uint8_t *chain, size_t chain_size); #endif diff --git a/core/embed/trezorhal/optiga_commands.h b/core/embed/trezorhal/optiga_commands.h index 5b0fda968..cf5b6fc81 100644 --- a/core/embed/trezorhal/optiga_commands.h +++ b/core/embed/trezorhal/optiga_commands.h @@ -130,6 +130,7 @@ typedef struct { #define OPTIGA_MAX_METADATA_SIZE 44 #define OPTIGA_RANDOM_MIN_SIZE 8 #define OPTIGA_RANDOM_MAX_SIZE 256 +#define OPTIGA_MAX_CERT_SIZE 1728 #define OPTIGA_ACCESS_CONDITION(ac_id, oid) \ { (const uint8_t[]){ac_id, oid >> 8, oid & 0xff}, 3 } diff --git a/crypto/buffer.c b/crypto/buffer.c index 9b6d34eac..1306224aa 100644 --- a/crypto/buffer.c +++ b/crypto/buffer.c @@ -46,6 +46,16 @@ size_t buffer_remaining(BUFFER_READER *buf) { return buf->size - buf->pos; } +bool buffer_ptr(BUFFER_READER *buf, const uint8_t **ptr) { + if ((buf->data == NULL) || (buf->pos > buf->size)) { + return false; + } + + *ptr = &buf->data[buf->pos]; + + return true; +} + bool buffer_peek(const BUFFER_READER *buf, uint8_t *byte) { if ((buf->data == NULL) || (buf->pos >= buf->size)) { return false; diff --git a/crypto/buffer.h b/crypto/buffer.h index 6be46dcf2..cfc9cd3da 100644 --- a/crypto/buffer.h +++ b/crypto/buffer.h @@ -46,6 +46,7 @@ typedef struct { void buffer_reader_init(BUFFER_READER *buf, const uint8_t *data, size_t size); void buffer_writer_init(BUFFER_WRITER *buf, uint8_t *data, size_t size); size_t __wur buffer_remaining(BUFFER_READER *buf); +bool __wur buffer_ptr(BUFFER_READER *buf, const uint8_t **ptr); bool __wur buffer_peek(const BUFFER_READER *buf, uint8_t *byte); bool __wur buffer_get(BUFFER_READER *buf, uint8_t *byte); bool __wur buffer_seek(BUFFER_READER *buf, size_t pos); diff --git a/crypto/der.h b/crypto/der.h index b6c84033c..ed3bc1afe 100644 --- a/crypto/der.h +++ b/crypto/der.h @@ -32,6 +32,7 @@ #define DER_SEQUENCE 0x30 #define DER_INTEGER 0x02 +#define DER_BIT_STRING 0x03 // Struct representing a DER-encoded ASN.1 data value. typedef struct {