From 8de2d8989d1765153cc6746721fe374f986e703f Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Fri, 8 Dec 2023 15:45:12 +0100 Subject: [PATCH] feat(core/prodtest): Check device certificate chain integrity using authority key identifier. [no changelog] --- core/embed/prodtest/optiga_prodtest.c | 144 +++++++++++++++++++++----- crypto/der.h | 1 + 2 files changed, 119 insertions(+), 26 deletions(-) diff --git a/core/embed/prodtest/optiga_prodtest.c b/core/embed/prodtest/optiga_prodtest.c index 4e20b8c9b..669690182 100644 --- a/core/embed/prodtest/optiga_prodtest.c +++ b/core/embed/prodtest/optiga_prodtest.c @@ -51,6 +51,18 @@ static const optiga_metadata_item KEY_USE_SIGN = { static const optiga_metadata_item TYPE_PTFBIND = { (const uint8_t[]){OPTIGA_DATA_TYPE_PTFBIND}, 1}; +// Identifier of context-specific constructed tag 3, which is used for +// extensions in X.509. +#define DER_X509_EXTENSIONS 0xa3 + +// Identifier of context-specific primitive tag 0, which is used for +// keyIdentifier in authorityKeyIdentifier. +#define DER_X509_KEY_IDENTIFIER 0x80 + +// DER-encoded object identifier of the authority key identifier extension +// (id-ce-authorityKeyIdentifier). +const uint8_t OID_AUTHORITY_KEY_IDENTIFIER[] = {0x06, 0x03, 0x55, 0x1d, 0x23}; + static bool optiga_paired(void) { const char *details = ""; @@ -540,26 +552,90 @@ static const uint8_t ECDSA_WITH_SHA256[] = { }; // 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, - }, -}; +static bool get_cert_extensions(DER_ITEM *tbs_cert, DER_ITEM *extensions) { + // Find the certificate extensions in the tbsCertificate. + DER_ITEM cert_item = {0}; + while (der_read_item(&tbs_cert->buf, &cert_item)) { + if (cert_item.id == DER_X509_EXTENSIONS) { + // Open the extensions sequence. + return der_read_item(&cert_item.buf, extensions) && + extensions->id == DER_SEQUENCE; + } + } + return false; +} + +static bool get_extension_value(const uint8_t *extension_oid, + size_t extension_oid_size, DER_ITEM *extensions, + DER_ITEM *extension_value) { + // Find the extension with the given OID. + DER_ITEM extension = {0}; + while (der_read_item(&extensions->buf, &extension)) { + DER_ITEM extension_id = {0}; + if (der_read_item(&extension.buf, &extension_id) && + extension_id.buf.size == extension_oid_size && + memcmp(extension_id.buf.data, extension_oid, extension_oid_size) == 0) { + // Find the extension's extnValue, skipping the optional critical flag. + while (der_read_item(&extension.buf, extension_value)) { + if (extension_value->id == DER_OCTET_STRING) { + return true; + } + } + memzero(extension_value, sizeof(DER_ITEM)); + return false; + } + } + return false; +} + +static bool get_authority_key_digest(DER_ITEM *tbs_cert, + const uint8_t **authority_key_digest) { + DER_ITEM extensions = {0}; + if (!get_cert_extensions(tbs_cert, &extensions)) { + vcp_println("ERROR get_authority_key_digest, extensions not found."); + return false; + } + + // Find the authority key identifier extension's extnValue. + DER_ITEM extension_value = {0}; + if (!get_extension_value(OID_AUTHORITY_KEY_IDENTIFIER, + sizeof(OID_AUTHORITY_KEY_IDENTIFIER), &extensions, + &extension_value)) { + vcp_println( + "ERROR get_authority_key_digest, authority key identifier extension " + "not found."); + return false; + } + + // Open the AuthorityKeyIdentifier sequence. + DER_ITEM auth_key_id = {0}; + if (!der_read_item(&extension_value.buf, &auth_key_id) || + auth_key_id.id != DER_SEQUENCE) { + vcp_println( + "ERROR get_authority_key_digest, failed to open authority key " + "identifier extnValue."); + return false; + } + + // Find the keyIdentifier field. + DER_ITEM key_id = {0}; + if (!der_read_item(&auth_key_id.buf, &key_id) || + key_id.id != DER_X509_KEY_IDENTIFIER) { + vcp_println( + "ERROR get_authority_key_digest, failed to find keyIdentifier field."); + return false; + } + + // Return the pointer to the keyIdentifier data. + if (buffer_remaining(&key_id.buf) != SHA1_DIGEST_LENGTH || + !buffer_ptr(&key_id.buf, authority_key_digest)) { + vcp_println( + "ERROR get_authority_key_digest, invalid length of keyIdentifier."); + return false; + } + + return true; +} 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 @@ -593,6 +669,10 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) { return false; } + // This will be populated with a pointer to the key identifier data of the + // AuthorityKeyIdentifier extension from the last certificate in the chain. + const uint8_t *authority_key_digest = NULL; + BUFFER_READER chain_reader = {0}; buffer_reader_init(&chain_reader, chain, chain_size); int cert_count = 0; @@ -652,6 +732,13 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) { return false; } + // Get the authority key identifier from the last certificate. + if (buffer_remaining(&chain_reader) == 0 && + !get_authority_key_digest(&tbs_cert, &authority_key_digest)) { + // Error returned by get_authority_key_digest(). + return false; + } + // Prepare the hash of tbsCertificate for the next signature verification. sha256_Raw(tbs_cert.buf.data, tbs_cert.buf.size, digest); @@ -689,13 +776,18 @@ bool check_device_cert_chain(const uint8_t *chain, size_t chain_size) { } } - // 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) == + // Verify that the signature of the last certificate in the chain matches its + // own AuthorityKeyIdentifier to verify the integrity of the certificate data. + uint8_t pub_key[65] = {0}; + uint8_t pub_key_digest[SHA1_DIGEST_LENGTH] = {0}; + for (int recid = 0; recid < 4; ++recid) { + if (ecdsa_recover_pub_from_sig(&nist256p1, pub_key, sig, digest, recid) == 0) { - return true; + sha1_Raw(pub_key, sizeof(pub_key), pub_key_digest); + if (memcmp(authority_key_digest, pub_key_digest, + sizeof(pub_key_digest)) == 0) { + return true; + } } } diff --git a/crypto/der.h b/crypto/der.h index ed3bc1afe..5620b3907 100644 --- a/crypto/der.h +++ b/crypto/der.h @@ -33,6 +33,7 @@ #define DER_SEQUENCE 0x30 #define DER_INTEGER 0x02 #define DER_BIT_STRING 0x03 +#define DER_OCTET_STRING 0x04 // Struct representing a DER-encoded ASN.1 data value. typedef struct {