| // Copyright 2020 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "dice/test_utils.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <memory> |
| |
| #include "cose/cose.h" |
| #include "dice/boringssl_ecdsa_utils.h" |
| #include "dice/dice.h" |
| #include "dice/utils.h" |
| #include "openssl/asn1.h" |
| #include "openssl/bn.h" |
| #include "openssl/curve25519.h" |
| #include "openssl/evp.h" |
| #include "openssl/hmac.h" |
| #include "openssl/is_boringssl.h" |
| #include "openssl/mem.h" |
| #include "openssl/sha.h" |
| #include "openssl/x509.h" |
| #include "openssl/x509_vfy.h" |
| #include "openssl/x509v3.h" |
| #include "pw_string/format.h" |
| |
| // The largest possible public key size among ECDSA P-384, P-256, and ED25519 |
| #define MAX_PUBLIC_KEY_SIZE 96 |
| |
| namespace { |
| |
| // A scoped pointer for cn_cbor. |
| struct CborDeleter { |
| void operator()(cn_cbor* c) { cn_cbor_free(c); } |
| }; |
| using ScopedCbor = std::unique_ptr<cn_cbor, CborDeleter>; |
| |
| const char* GetCertTypeStr(dice::test::CertificateType cert_type) { |
| switch (cert_type) { |
| case dice::test::CertificateType_Cbor: |
| return "CBOR"; |
| case dice::test::CertificateType_X509: |
| return "X509"; |
| } |
| return ""; |
| } |
| |
| const char* GetKeyTypeStr(dice::test::KeyType key_type) { |
| switch (key_type) { |
| case dice::test::KeyType_Ed25519: |
| return "Ed25519"; |
| case dice::test::KeyType_P256: |
| return "P256"; |
| case dice::test::KeyType_P384: |
| return "P384"; |
| } |
| return ""; |
| } |
| |
| bssl::UniquePtr<X509> ParseX509Certificate(const uint8_t* certificate, |
| size_t certificate_size) { |
| const uint8_t* asn1 = certificate; |
| return bssl::UniquePtr<X509>( |
| d2i_X509(/*X509=*/nullptr, &asn1, certificate_size)); |
| } |
| |
| void DumpToFile(const char* filename, const uint8_t* data, size_t size) { |
| FILE* fp = fopen(filename, "w"); |
| if (fp) { |
| fwrite(data, size, 1, fp); |
| fclose(fp); |
| } else { |
| printf("WARNING: Failed to dump to file.\n"); |
| } |
| } |
| |
| // A simple hmac-drbg to help with deterministic ecdsa. |
| class HmacSha512Drbg { |
| public: |
| HmacSha512Drbg(const uint8_t seed[32]) { |
| Init(); |
| Update(seed, 32); |
| } |
| ~HmacSha512Drbg() { HMAC_CTX_cleanup(&ctx_); } |
| |
| // Populates |num_bytes| random bytes into |buffer|. |
| void GetBytes(size_t num_bytes, uint8_t* buffer) { |
| size_t bytes_written = 0; |
| while (bytes_written < num_bytes) { |
| size_t bytes_to_copy = num_bytes - bytes_written; |
| if (bytes_to_copy > 64) { |
| bytes_to_copy = 64; |
| } |
| Hmac(v_, v_); |
| memcpy(&buffer[bytes_written], v_, bytes_to_copy); |
| bytes_written += bytes_to_copy; |
| } |
| Update0(); |
| } |
| |
| private: |
| void Init() { |
| memset(k_, 0, 64); |
| memset(v_, 1, 64); |
| HMAC_CTX_init(&ctx_); |
| } |
| |
| void Hmac(uint8_t in[64], uint8_t out[64]) { |
| HmacStart(); |
| HmacUpdate(in, 64); |
| HmacFinish(out); |
| } |
| |
| void HmacStart() { |
| HMAC_Init_ex(&ctx_, k_, 64, EVP_sha512(), nullptr /* impl */); |
| } |
| |
| void HmacUpdate(const uint8_t* data, size_t data_size) { |
| HMAC_Update(&ctx_, data, data_size); |
| } |
| |
| void HmacUpdateByte(uint8_t byte) { HmacUpdate(&byte, 1); } |
| |
| void HmacFinish(uint8_t out[64]) { |
| unsigned int out_len = 64; |
| HMAC_Final(&ctx_, out, &out_len); |
| } |
| |
| void Update(const uint8_t* data, size_t data_size) { |
| HmacStart(); |
| HmacUpdate(v_, 64); |
| HmacUpdateByte(0x00); |
| if (data_size > 0) { |
| HmacUpdate(data, data_size); |
| } |
| HmacFinish(k_); |
| Hmac(v_, v_); |
| if (data_size > 0) { |
| HmacStart(); |
| HmacUpdate(v_, 64); |
| HmacUpdateByte(0x01); |
| HmacUpdate(data, data_size); |
| HmacFinish(k_); |
| Hmac(v_, v_); |
| } |
| } |
| |
| void Update0() { Update(nullptr, 0); } |
| |
| uint8_t k_[64]; |
| uint8_t v_[64]; |
| HMAC_CTX ctx_; |
| }; |
| |
| bssl::UniquePtr<EVP_PKEY> KeyFromRawKey( |
| const uint8_t raw_key[DICE_PRIVATE_KEY_SEED_SIZE], |
| dice::test::KeyType key_type, uint8_t raw_public_key[MAX_PUBLIC_KEY_SIZE], |
| size_t* raw_public_key_size) { |
| if (key_type == dice::test::KeyType_Ed25519) { |
| bssl::UniquePtr<EVP_PKEY> key( |
| EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, /*unused=*/nullptr, |
| raw_key, DICE_PRIVATE_KEY_SEED_SIZE)); |
| *raw_public_key_size = 32; |
| EVP_PKEY_get_raw_public_key(key.get(), raw_public_key, raw_public_key_size); |
| return key; |
| } else if (key_type == dice::test::KeyType_P256) { |
| bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); |
| const EC_GROUP* group = EC_KEY_get0_group(key.get()); |
| bssl::UniquePtr<EC_POINT> pub(EC_POINT_new(group)); |
| // Match the algorithm described in RFC6979 and seed with the raw key. |
| HmacSha512Drbg drbg(raw_key); |
| while (true) { |
| uint8_t tmp[32]; |
| drbg.GetBytes(32, tmp); |
| bssl::UniquePtr<BIGNUM> candidate(BN_bin2bn(tmp, 32, /*ret=*/nullptr)); |
| if (BN_cmp(candidate.get(), EC_GROUP_get0_order(group)) < 0 && |
| !BN_is_zero(candidate.get())) { |
| // Candidate is suitable. |
| EC_POINT_mul(group, pub.get(), candidate.get(), /*q=*/nullptr, |
| /*m=*/nullptr, |
| /*ctx=*/nullptr); |
| EC_KEY_set_public_key(key.get(), pub.get()); |
| EC_KEY_set_private_key(key.get(), candidate.get()); |
| break; |
| } |
| } |
| bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); |
| EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()); |
| *raw_public_key_size = |
| EC_POINT_point2oct(group, pub.get(), POINT_CONVERSION_COMPRESSED, |
| raw_public_key, 33, /*ctx=*/nullptr); |
| return pkey; |
| } else if (key_type == dice::test::KeyType_P384) { |
| const size_t kPublicKeySize = 96; |
| const size_t kPrivateKeySize = 48; |
| uint8_t pk[kPrivateKeySize]; |
| P384KeypairFromSeed(raw_public_key, pk, raw_key); |
| *raw_public_key_size = kPublicKeySize; |
| |
| bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_secp384r1)); |
| BIGNUM* x = BN_new(); |
| BN_bin2bn(&raw_public_key[0], kPublicKeySize / 2, x); |
| BIGNUM* y = BN_new(); |
| BN_bin2bn(&raw_public_key[kPublicKeySize / 2], kPublicKeySize / 2, y); |
| EC_KEY_set_public_key_affine_coordinates(key.get(), x, y); |
| BN_clear_free(y); |
| BN_clear_free(x); |
| bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); |
| EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()); |
| return pkey; |
| } |
| |
| printf("ERROR: Unsupported key type.\n"); |
| return nullptr; |
| } |
| |
| void CreateX509UdsCertificate(EVP_PKEY* key, const uint8_t id[DICE_ID_SIZE], |
| uint8_t certificate[dice::test::kTestCertSize], |
| size_t* certificate_size) { |
| bssl::UniquePtr<X509> x509(X509_new()); |
| X509_set_version(x509.get(), 2); |
| |
| bssl::UniquePtr<ASN1_INTEGER> serial(ASN1_INTEGER_new()); |
| ASN1_INTEGER_set_uint64(serial.get(), 1); |
| X509_set_serialNumber(x509.get(), serial.get()); |
| |
| uint8_t id_hex[40]; |
| DiceHexEncode(id, DICE_ID_SIZE, id_hex, sizeof(id_hex)); |
| bssl::UniquePtr<X509_NAME> issuer_name(X509_NAME_new()); |
| X509_NAME_add_entry_by_NID(issuer_name.get(), NID_serialNumber, MBSTRING_UTF8, |
| id_hex, sizeof(id_hex), 0, 0); |
| X509_set_issuer_name(x509.get(), issuer_name.get()); |
| X509_set_subject_name(x509.get(), issuer_name.get()); |
| |
| bssl::UniquePtr<ASN1_TIME> not_before(ASN1_TIME_new()); |
| ASN1_TIME_set_string(not_before.get(), "180322235959Z"); |
| X509_set_notBefore(x509.get(), not_before.get()); |
| bssl::UniquePtr<ASN1_TIME> not_after(ASN1_TIME_new()); |
| ASN1_TIME_set_string(not_after.get(), "99991231235959Z"); |
| X509_set_notAfter(x509.get(), not_after.get()); |
| |
| bssl::UniquePtr<ASN1_OCTET_STRING> subject_key_id(ASN1_OCTET_STRING_new()); |
| ASN1_OCTET_STRING_set(subject_key_id.get(), id, DICE_ID_SIZE); |
| bssl::UniquePtr<X509_EXTENSION> subject_key_id_ext(X509V3_EXT_i2d( |
| NID_subject_key_identifier, /*crit=*/0, subject_key_id.get())); |
| X509_add_ext(x509.get(), subject_key_id_ext.get(), /*loc=*/-1); |
| |
| bssl::UniquePtr<AUTHORITY_KEYID> authority_key_id(AUTHORITY_KEYID_new()); |
| authority_key_id->keyid = ASN1_OCTET_STRING_dup(subject_key_id.get()); |
| bssl::UniquePtr<X509_EXTENSION> authority_key_id_ext(X509V3_EXT_i2d( |
| NID_authority_key_identifier, /*crit=*/0, authority_key_id.get())); |
| X509_add_ext(x509.get(), authority_key_id_ext.get(), /*loc=*/-1); |
| |
| bssl::UniquePtr<ASN1_BIT_STRING> key_usage(ASN1_BIT_STRING_new()); |
| ASN1_BIT_STRING_set_bit(key_usage.get(), 5 /*keyCertSign*/, 1); |
| bssl::UniquePtr<X509_EXTENSION> key_usage_ext( |
| X509V3_EXT_i2d(NID_key_usage, /*crit=*/1, key_usage.get())); |
| X509_add_ext(x509.get(), key_usage_ext.get(), /*loc=*/-1); |
| |
| bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints(BASIC_CONSTRAINTS_new()); |
| basic_constraints->ca = 1; |
| bssl::UniquePtr<X509_EXTENSION> basic_constraints_ext(X509V3_EXT_i2d( |
| NID_basic_constraints, /*crit=*/1, basic_constraints.get())); |
| X509_add_ext(x509.get(), basic_constraints_ext.get(), /*loc=*/-1); |
| |
| X509_set_pubkey(x509.get(), key); |
| // ED25519 always uses SHA-512 so md must be NULL. |
| const EVP_MD* md = |
| (EVP_PKEY_id(key) == EVP_PKEY_ED25519) ? nullptr : EVP_sha512(); |
| X509_sign(x509.get(), key, md); |
| if (i2d_X509(x509.get(), /*out=*/nullptr) <= |
| static_cast<int>(dice::test::kTestCertSize)) { |
| uint8_t* p = certificate; |
| *certificate_size = i2d_X509(x509.get(), &p); |
| } else { |
| *certificate_size = 0; |
| } |
| } |
| |
| bool VerifyX509CertificateChain(const uint8_t* root_certificate, |
| size_t root_certificate_size, |
| const dice::test::DiceStateForTest states[], |
| size_t num_dice_states, bool is_partial_chain) { |
| bssl::UniquePtr<STACK_OF(X509)> trusted_certs(sk_X509_new_null()); |
| bssl::PushToStack(trusted_certs.get(), |
| bssl::UpRef(ParseX509Certificate(root_certificate, |
| root_certificate_size))); |
| bssl::UniquePtr<STACK_OF(X509)> untrusted_certs(sk_X509_new_null()); |
| for (size_t i = 0; i < num_dice_states - 1; ++i) { |
| bssl::PushToStack(untrusted_certs.get(), |
| bssl::UpRef(ParseX509Certificate( |
| states[i].certificate, states[i].certificate_size))); |
| } |
| bssl::UniquePtr<X509> leaf_cert( |
| ParseX509Certificate(states[num_dice_states - 1].certificate, |
| states[num_dice_states - 1].certificate_size)); |
| bssl::UniquePtr<X509_STORE> x509_store(X509_STORE_new()); |
| bssl::UniquePtr<X509_STORE_CTX> x509_store_ctx(X509_STORE_CTX_new()); |
| X509_STORE_CTX_init(x509_store_ctx.get(), x509_store.get(), leaf_cert.get(), |
| untrusted_certs.get()); |
| X509_STORE_CTX_trusted_stack(x509_store_ctx.get(), trusted_certs.get()); |
| X509_VERIFY_PARAM* param = X509_VERIFY_PARAM_new(); |
| X509_VERIFY_PARAM_set_time(param, 1577923199 /*1/1/2020*/); |
| X509_VERIFY_PARAM_set_depth(param, 10); |
| if (is_partial_chain) { |
| X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_PARTIAL_CHAIN); |
| } |
| // Boringssl doesn't support custom extensions, so ignore them. |
| X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_IGNORE_CRITICAL); |
| X509_STORE_CTX_set0_param(x509_store_ctx.get(), param); |
| return (1 == X509_verify_cert(x509_store_ctx.get())); |
| } |
| |
| void CreateEd25519CborUdsCertificate( |
| const uint8_t private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE], |
| const uint8_t id[DICE_ID_SIZE], |
| uint8_t certificate[dice::test::kTestCertSize], size_t* certificate_size) { |
| const uint8_t kProtectedAttributesCbor[3] = { |
| 0xa1 /* map(1) */, 0x01 /* alg(1) */, 0x27 /* EdDSA(-8) */}; |
| const int64_t kCwtIssuerLabel = 1; |
| const int64_t kCwtSubjectLabel = 2; |
| const int64_t kUdsPublicKeyLabel = -4670552; |
| const int64_t kUdsKeyUsageLabel = -4670553; |
| const uint8_t kKeyUsageCertSign = 32; // Bit 5. |
| |
| // Public key encoded as a COSE_Key. |
| uint8_t public_key[32]; |
| uint8_t bssl_private_key[64]; |
| ED25519_keypair_from_seed(public_key, bssl_private_key, private_key_seed); |
| cn_cbor_errback error; |
| ScopedCbor public_key_cbor(cn_cbor_map_create(&error)); |
| // kty = okp |
| cn_cbor_mapput_int(public_key_cbor.get(), 1, cn_cbor_int_create(1, &error), |
| &error); |
| // crv = ed25519 |
| cn_cbor_mapput_int(public_key_cbor.get(), -1, cn_cbor_int_create(6, &error), |
| &error); |
| // x = public_key |
| cn_cbor_mapput_int(public_key_cbor.get(), -2, |
| cn_cbor_data_create(public_key, 32, &error), &error); |
| uint8_t encoded_public_key[100]; |
| size_t encoded_public_key_size = |
| cn_cbor_encoder_write(encoded_public_key, 0, 100, public_key_cbor.get()); |
| |
| // Simple CWT payload with issuer, subject, and use the same subject public |
| // key field as a CDI certificate to make verification easy. |
| char id_hex[41]; |
| DiceHexEncode(id, DICE_ID_SIZE, id_hex, sizeof(id_hex)); |
| id_hex[40] = '\0'; |
| ScopedCbor cwt(cn_cbor_map_create(&error)); |
| cn_cbor_mapput_int(cwt.get(), kCwtIssuerLabel, |
| cn_cbor_string_create(id_hex, &error), &error); |
| cn_cbor_mapput_int(cwt.get(), kCwtSubjectLabel, |
| cn_cbor_string_create(id_hex, &error), &error); |
| cn_cbor_mapput_int( |
| cwt.get(), kUdsPublicKeyLabel, |
| cn_cbor_data_create(encoded_public_key, encoded_public_key_size, &error), |
| &error); |
| uint8_t key_usage_byte = kKeyUsageCertSign; |
| cn_cbor_mapput_int(cwt.get(), kUdsKeyUsageLabel, |
| cn_cbor_data_create(&key_usage_byte, 1, &error), &error); |
| uint8_t payload[dice::test::kTestCertSize]; |
| size_t payload_size = |
| cn_cbor_encoder_write(payload, 0, dice::test::kTestCertSize, cwt.get()); |
| |
| // Signature over COSE Sign1 TBS. |
| ScopedCbor tbs_cbor(cn_cbor_array_create(&error)); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_string_create("Signature1", &error), &error); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_data_create(kProtectedAttributesCbor, 3, &error), |
| &error); |
| cn_cbor_array_append(tbs_cbor.get(), cn_cbor_data_create(NULL, 0, &error), |
| &error); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_data_create(payload, payload_size, &error), |
| &error); |
| uint8_t tbs[dice::test::kTestCertSize]; |
| size_t tbs_size = |
| cn_cbor_encoder_write(tbs, 0, dice::test::kTestCertSize, tbs_cbor.get()); |
| uint8_t signature[64]; |
| ED25519_sign(signature, tbs, tbs_size, bssl_private_key); |
| |
| // COSE Sign1. |
| ScopedCbor sign1(cn_cbor_array_create(&error)); |
| cn_cbor_array_append(sign1.get(), |
| cn_cbor_data_create(kProtectedAttributesCbor, 3, &error), |
| &error); |
| cn_cbor_array_append(sign1.get(), cn_cbor_map_create(&error), &error); |
| cn_cbor_array_append( |
| sign1.get(), cn_cbor_data_create(payload, payload_size, &error), &error); |
| cn_cbor_array_append(sign1.get(), cn_cbor_data_create(signature, 64, &error), |
| &error); |
| *certificate_size = cn_cbor_encoder_write( |
| certificate, 0, dice::test::kTestCertSize, sign1.get()); |
| } |
| |
| void CreateP384CborUdsCertificate( |
| const uint8_t private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE], |
| const uint8_t id[DICE_ID_SIZE], |
| uint8_t certificate[dice::test::kTestCertSize], size_t* certificate_size) { |
| const int64_t kCwtIssuerLabel = 1; |
| const int64_t kCwtSubjectLabel = 2; |
| const int64_t kUdsPublicKeyLabel = -4670552; |
| const int64_t kUdsKeyUsageLabel = -4670553; |
| const uint8_t kKeyUsageCertSign = 32; // Bit 5. |
| const uint8_t kProtectedAttributesCbor[4] = { |
| 0xa1 /* map(1) */, 0x01 /* alg(1) */, 0x38, 0x22 /* ES384(-34) */}; |
| const size_t kPublicKeySize = 96; |
| const size_t kPrivateKeySize = 48; |
| const size_t kSignatureSize = 96; |
| |
| // Public key encoded as a COSE_Key. |
| uint8_t public_key[kPublicKeySize]; |
| uint8_t private_key[kPrivateKeySize]; |
| P384KeypairFromSeed(public_key, private_key, private_key_seed); |
| cn_cbor_errback error; |
| ScopedCbor public_key_cbor(cn_cbor_map_create(&error)); |
| // kty = ec2 |
| cn_cbor_mapput_int(public_key_cbor.get(), 1, cn_cbor_int_create(2, &error), |
| &error); |
| // crv = P-384 |
| cn_cbor_mapput_int(public_key_cbor.get(), -1, cn_cbor_int_create(2, &error), |
| &error); |
| // x = public_key X |
| cn_cbor_mapput_int( |
| public_key_cbor.get(), -2, |
| cn_cbor_data_create(&public_key[0], kPublicKeySize / 2, &error), &error); |
| // y = public_key Y |
| cn_cbor_mapput_int(public_key_cbor.get(), -3, |
| cn_cbor_data_create(&public_key[kPublicKeySize / 2], |
| kPublicKeySize / 2, &error), |
| &error); |
| uint8_t encoded_public_key[200]; |
| size_t encoded_public_key_size = |
| cn_cbor_encoder_write(encoded_public_key, 0, 200, public_key_cbor.get()); |
| |
| // Simple CWT payload with issuer, subject, and use the same subject public |
| // key field as a CDI certificate to make verification easy. |
| char id_hex[41]; |
| DiceHexEncode(id, DICE_ID_SIZE, id_hex, sizeof(id_hex)); |
| id_hex[40] = '\0'; |
| ScopedCbor cwt(cn_cbor_map_create(&error)); |
| cn_cbor_mapput_int(cwt.get(), kCwtIssuerLabel, |
| cn_cbor_string_create(id_hex, &error), &error); |
| cn_cbor_mapput_int(cwt.get(), kCwtSubjectLabel, |
| cn_cbor_string_create(id_hex, &error), &error); |
| cn_cbor_mapput_int( |
| cwt.get(), kUdsPublicKeyLabel, |
| cn_cbor_data_create(encoded_public_key, encoded_public_key_size, &error), |
| &error); |
| uint8_t key_usage_byte = kKeyUsageCertSign; |
| cn_cbor_mapput_int(cwt.get(), kUdsKeyUsageLabel, |
| cn_cbor_data_create(&key_usage_byte, 1, &error), &error); |
| uint8_t payload[dice::test::kTestCertSize]; |
| size_t payload_size = |
| cn_cbor_encoder_write(payload, 0, dice::test::kTestCertSize, cwt.get()); |
| |
| // Signature over COSE Sign1 TBS. |
| ScopedCbor tbs_cbor(cn_cbor_array_create(&error)); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_string_create("Signature1", &error), &error); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_data_create(kProtectedAttributesCbor, 4, &error), |
| &error); |
| cn_cbor_array_append(tbs_cbor.get(), cn_cbor_data_create(NULL, 0, &error), |
| &error); |
| cn_cbor_array_append(tbs_cbor.get(), |
| cn_cbor_data_create(payload, payload_size, &error), |
| &error); |
| uint8_t tbs[dice::test::kTestCertSize]; |
| size_t tbs_size = |
| cn_cbor_encoder_write(tbs, 0, dice::test::kTestCertSize, tbs_cbor.get()); |
| uint8_t signature[kSignatureSize]; |
| P384Sign(signature, tbs, tbs_size, private_key); |
| |
| // COSE Sign1. |
| ScopedCbor sign1(cn_cbor_array_create(&error)); |
| cn_cbor_array_append(sign1.get(), |
| cn_cbor_data_create(kProtectedAttributesCbor, 4, &error), |
| &error); |
| cn_cbor_array_append(sign1.get(), cn_cbor_map_create(&error), &error); |
| cn_cbor_array_append( |
| sign1.get(), cn_cbor_data_create(payload, payload_size, &error), &error); |
| cn_cbor_array_append(sign1.get(), |
| cn_cbor_data_create(signature, kSignatureSize, &error), |
| &error); |
| *certificate_size = cn_cbor_encoder_write( |
| certificate, 0, dice::test::kTestCertSize, sign1.get()); |
| } |
| |
| void CreateCborUdsCertificate( |
| const uint8_t private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE], |
| dice::test::KeyType key_type, const uint8_t id[DICE_ID_SIZE], |
| uint8_t certificate[dice::test::kTestCertSize], size_t* certificate_size) { |
| switch (key_type) { |
| case dice::test::KeyType_Ed25519: |
| CreateEd25519CborUdsCertificate(private_key_seed, id, certificate, |
| certificate_size); |
| break; |
| case dice::test::KeyType_P256: |
| printf( |
| "Error: encountered unsupported KeyType P256 when creating CBOR UDS " |
| "certificate\n"); |
| break; |
| case dice::test::KeyType_P384: |
| CreateP384CborUdsCertificate(private_key_seed, id, certificate, |
| certificate_size); |
| break; |
| } |
| } |
| |
| ScopedCbor ExtractCwtFromCborCertificate(const uint8_t* certificate, |
| size_t certificate_size) { |
| cn_cbor_errback error; |
| ScopedCbor sign1(cn_cbor_decode(certificate, certificate_size, &error)); |
| if (!sign1 || sign1->type != CN_CBOR_ARRAY || sign1->length != 4) { |
| return nullptr; |
| } |
| cn_cbor* payload = cn_cbor_index(sign1.get(), 2); |
| if (!payload || payload->type != CN_CBOR_BYTES) { |
| return nullptr; |
| } |
| ScopedCbor cwt(cn_cbor_decode(payload->v.bytes, payload->length, &error)); |
| if (!cwt || cwt->type != CN_CBOR_MAP) { |
| return nullptr; |
| } |
| return cwt; |
| } |
| |
| ScopedCbor ExtractPublicKeyFromCwt(const cn_cbor* cwt) { |
| cn_cbor_errback error; |
| cn_cbor* key_bytes = cn_cbor_mapget_int(cwt, -4670552); |
| if (!key_bytes || key_bytes->type != CN_CBOR_BYTES) { |
| return nullptr; |
| } |
| ScopedCbor key(cn_cbor_decode(key_bytes->v.bytes, key_bytes->length, &error)); |
| if (key && key->type != CN_CBOR_MAP) { |
| return nullptr; |
| } |
| return key; |
| } |
| |
| bool ExtractIdsFromCwt(const cn_cbor* cwt, char authority_id_hex[40], |
| char subject_id_hex[40]) { |
| cn_cbor* authority_id_cbor = cn_cbor_mapget_int(cwt, 1); |
| cn_cbor* subject_id_cbor = cn_cbor_mapget_int(cwt, 2); |
| if (!authority_id_cbor || !subject_id_cbor) { |
| return false; |
| } |
| if (authority_id_cbor->type != CN_CBOR_TEXT || |
| authority_id_cbor->length != 40 || |
| subject_id_cbor->type != CN_CBOR_TEXT || subject_id_cbor->length != 40) { |
| return false; |
| } |
| memcpy(authority_id_hex, authority_id_cbor->v.str, 40); |
| memcpy(subject_id_hex, subject_id_cbor->v.str, 40); |
| return true; |
| } |
| |
| bool ExtractKeyUsageFromCwt(const cn_cbor* cwt, uint64_t* key_usage) { |
| cn_cbor* key_usage_bytes = cn_cbor_mapget_int(cwt, -4670553); |
| if (!key_usage_bytes || key_usage_bytes->type != CN_CBOR_BYTES) { |
| return false; |
| } |
| // The highest key usage bit defined in RFC 5280 is 8. |
| if (key_usage_bytes->length > 2) { |
| return false; |
| } |
| if (key_usage_bytes->length == 0) { |
| *key_usage = 0; |
| return true; |
| } |
| *key_usage = key_usage_bytes->v.bytes[0]; |
| if (key_usage_bytes->length == 2) { |
| uint64_t tmp = key_usage_bytes->v.bytes[1]; |
| *key_usage += tmp >> 8; |
| } |
| return true; |
| } |
| |
| bool ValidateCborCertificateCdiFields(const cn_cbor* cwt, |
| bool expect_cdi_certificate) { |
| cn_cbor* code_hash_bytes = cn_cbor_mapget_int(cwt, -4670545); |
| cn_cbor* code_desc_bytes = cn_cbor_mapget_int(cwt, -4670546); |
| cn_cbor* conf_hash_bytes = cn_cbor_mapget_int(cwt, -4670547); |
| cn_cbor* conf_desc_bytes = cn_cbor_mapget_int(cwt, -4670548); |
| cn_cbor* auth_hash_bytes = cn_cbor_mapget_int(cwt, -4670549); |
| cn_cbor* auth_desc_bytes = cn_cbor_mapget_int(cwt, -4670550); |
| cn_cbor* mode_bytes = cn_cbor_mapget_int(cwt, -4670551); |
| if (!expect_cdi_certificate) { |
| return (!code_hash_bytes && !code_desc_bytes && !conf_hash_bytes && |
| !conf_desc_bytes && !auth_hash_bytes && !auth_desc_bytes && |
| !mode_bytes); |
| } |
| if (!code_hash_bytes || !conf_desc_bytes || !auth_hash_bytes || !mode_bytes) { |
| return false; |
| } |
| if (code_hash_bytes->length != 64) { |
| return false; |
| } |
| if (conf_hash_bytes) { |
| if (conf_hash_bytes->length != 64) { |
| return false; |
| } |
| } else if (conf_desc_bytes->length != 64) { |
| return false; |
| } |
| if (auth_hash_bytes->length != 64) { |
| return false; |
| } |
| if (mode_bytes->length != 1) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool VerifyCoseSign1Signature(const uint8_t* certificate, |
| size_t certificate_size, |
| const uint8_t* external_aad, |
| size_t external_aad_size, |
| const cn_cbor* authority_public_key) { |
| // Use the COSE-C library to decode and validate. |
| cose_errback error; |
| int struct_type = 0; |
| HCOSE_SIGN1 sign1 = (HCOSE_SIGN1)COSE_Decode( |
| certificate, certificate_size, &struct_type, COSE_sign1_object, &error); |
| if (!sign1) { |
| return false; |
| } |
| COSE_Sign1_SetExternal(sign1, external_aad, external_aad_size, &error); |
| bool result = COSE_Sign1_validate(sign1, authority_public_key, &error); |
| COSE_Sign1_Free(sign1); |
| if (!result) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool VerifySingleCborCertificate(const uint8_t* certificate, |
| size_t certificate_size, |
| const cn_cbor* authority_public_key, |
| const char authority_id_hex[40], |
| bool expect_cdi_certificate, |
| ScopedCbor* subject_public_key, |
| char subject_id_hex[40]) { |
| if (!VerifyCoseSign1Signature(certificate, certificate_size, /*aad=*/NULL, |
| /*aad_size=*/0, authority_public_key)) { |
| return false; |
| } |
| |
| ScopedCbor cwt(ExtractCwtFromCborCertificate(certificate, certificate_size)); |
| if (!cwt) { |
| return false; |
| } |
| char actual_authority_id[40]; |
| char tmp_subject_id_hex[40]; |
| if (!ExtractIdsFromCwt(cwt.get(), actual_authority_id, tmp_subject_id_hex)) { |
| return false; |
| } |
| if (0 != memcmp(authority_id_hex, actual_authority_id, 40)) { |
| return false; |
| } |
| memcpy(subject_id_hex, tmp_subject_id_hex, 40); |
| *subject_public_key = ExtractPublicKeyFromCwt(cwt.get()); |
| if (!subject_public_key) { |
| return false; |
| } |
| uint64_t key_usage = 0; |
| const uint64_t kKeyUsageCertSign = 1 << 5; // Bit 5. |
| if (!ExtractKeyUsageFromCwt(cwt.get(), &key_usage)) { |
| return false; |
| } |
| if (key_usage != kKeyUsageCertSign) { |
| return false; |
| } |
| if (!ValidateCborCertificateCdiFields(cwt.get(), expect_cdi_certificate)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool VerifyCborCertificateChain(const uint8_t* root_certificate, |
| size_t root_certificate_size, |
| const dice::test::DiceStateForTest states[], |
| size_t num_dice_states, bool is_partial_chain) { |
| ScopedCbor root_cwt = |
| ExtractCwtFromCborCertificate(root_certificate, root_certificate_size); |
| if (!root_cwt) { |
| return false; |
| } |
| ScopedCbor authority_public_key = ExtractPublicKeyFromCwt(root_cwt.get()); |
| if (!authority_public_key) { |
| return false; |
| } |
| char expected_authority_id_hex[40]; |
| char not_used[40]; |
| if (!ExtractIdsFromCwt(root_cwt.get(), not_used, expected_authority_id_hex)) { |
| return false; |
| } |
| if (!is_partial_chain) { |
| // We can't verify the root certificate in a partial chain, we can only |
| // check that its public key certifies the other certificates. But with a |
| // full chain, we can expect the root to be self-signed. |
| if (!VerifySingleCborCertificate( |
| root_certificate, root_certificate_size, authority_public_key.get(), |
| expected_authority_id_hex, /*expect_cdi_certificate=*/false, |
| &authority_public_key, expected_authority_id_hex)) { |
| return false; |
| } |
| } |
| for (size_t i = 0; i < num_dice_states; ++i) { |
| if (!VerifySingleCborCertificate( |
| states[i].certificate, states[i].certificate_size, |
| authority_public_key.get(), expected_authority_id_hex, |
| /*expect_cdi_certificate=*/true, &authority_public_key, |
| expected_authority_id_hex)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace dice { |
| namespace test { |
| |
| void DumpState(CertificateType cert_type, KeyType key_type, const char* suffix, |
| const DiceStateForTest& state) { |
| char filename[100]; |
| pw::string::Format(filename, "_attest_cdi_%s.bin", suffix); |
| DumpToFile(filename, state.cdi_attest, DICE_CDI_SIZE); |
| pw::string::Format(filename, "_seal_cdi_%s.bin", suffix); |
| DumpToFile(filename, state.cdi_seal, DICE_CDI_SIZE); |
| pw::string::Format(filename, "_%s_%s_cert_%s.cert", GetCertTypeStr(cert_type), |
| GetKeyTypeStr(key_type), suffix); |
| DumpToFile(filename, state.certificate, state.certificate_size); |
| } |
| |
| void DeriveFakeInputValue(const char* seed, size_t length, uint8_t* output) { |
| union { |
| uint8_t buffer[64]; |
| uint64_t counter; |
| } context; |
| SHA512(reinterpret_cast<const uint8_t*>(seed), strlen(seed), context.buffer); |
| size_t output_pos = 0; |
| while (output_pos < length) { |
| uint8_t tmp[64]; |
| SHA512(context.buffer, 64, tmp); |
| context.counter++; |
| size_t remaining = length - output_pos; |
| size_t to_copy = remaining < 64 ? remaining : 64; |
| memcpy(&output[output_pos], tmp, to_copy); |
| output_pos += to_copy; |
| } |
| } |
| |
| void CreateFakeUdsCertificate(void* context, const uint8_t uds[32], |
| CertificateType cert_type, KeyType key_type, |
| uint8_t certificate[kTestCertSize], |
| size_t* certificate_size) { |
| uint8_t raw_key[DICE_PRIVATE_KEY_SEED_SIZE]; |
| DiceDeriveCdiPrivateKeySeed(context, uds, raw_key); |
| |
| uint8_t raw_public_key[MAX_PUBLIC_KEY_SIZE]; |
| size_t raw_public_key_size = 0; |
| bssl::UniquePtr<EVP_PKEY> key( |
| KeyFromRawKey(raw_key, key_type, raw_public_key, &raw_public_key_size)); |
| |
| uint8_t id[DICE_ID_SIZE]; |
| DiceDeriveCdiCertificateId(context, raw_public_key, raw_public_key_size, id); |
| |
| if (cert_type == CertificateType_X509) { |
| CreateX509UdsCertificate(key.get(), id, certificate, certificate_size); |
| } else { |
| CreateCborUdsCertificate(raw_key, key_type, id, certificate, |
| certificate_size); |
| } |
| |
| char filename[100]; |
| pw::string::Format(filename, "_%s_%s_uds_cert.cert", |
| GetCertTypeStr(cert_type), GetKeyTypeStr(key_type)); |
| DumpToFile(filename, certificate, *certificate_size); |
| } |
| |
| [[maybe_unused]] bool VerifyCoseSign1( |
| const uint8_t* certificate, size_t certificate_size, |
| const uint8_t* external_aad, size_t external_aad_size, |
| const uint8_t* encoded_public_key, size_t encoded_public_key_size, |
| const uint8_t* expected_cwt, size_t expected_cwt_size) { |
| cn_cbor_errback error; |
| ScopedCbor public_key( |
| cn_cbor_decode(encoded_public_key, encoded_public_key_size, &error)); |
| if (!public_key) { |
| return false; |
| } |
| |
| if (!VerifyCoseSign1Signature(certificate, certificate_size, external_aad, |
| external_aad_size, public_key.get())) { |
| return false; |
| } |
| |
| ScopedCbor sign1(cn_cbor_decode(certificate, certificate_size, &error)); |
| if (!sign1 || sign1->type != CN_CBOR_ARRAY || sign1->length != 4) { |
| return false; |
| } |
| cn_cbor* payload = cn_cbor_index(sign1.get(), 2); |
| if (!payload || payload->type != CN_CBOR_BYTES) { |
| return false; |
| } |
| |
| if (payload->length != expected_cwt_size) { |
| return false; |
| } |
| |
| if (memcmp(payload->v.bytes, expected_cwt, expected_cwt_size) != 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool VerifyCertificateChain(CertificateType cert_type, |
| const uint8_t* root_certificate, |
| size_t root_certificate_size, |
| const DiceStateForTest states[], |
| size_t num_dice_states, bool is_partial_chain) { |
| switch (cert_type) { |
| case CertificateType_Cbor: |
| return VerifyCborCertificateChain(root_certificate, root_certificate_size, |
| states, num_dice_states, |
| is_partial_chain); |
| case CertificateType_X509: |
| return VerifyX509CertificateChain(root_certificate, root_certificate_size, |
| states, num_dice_states, |
| is_partial_chain); |
| } |
| return false; |
| } |
| } // namespace test |
| } // namespace dice |