|  | // 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 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]) { | 
|  | // 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; | 
|  | } | 
|  | (void)authority_public_key; | 
|  | bool result = COSE_Sign1_validate(sign1, authority_public_key, &error); | 
|  | COSE_Sign1_Free(sign1); | 
|  | if (!result) { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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 |