| // 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. |
| |
| // This is a DiceGenerateCertificate implementation that uses boringssl for |
| // crypto and certificate generation. The algorithms used are SHA512, |
| // HKDF-SHA512, and Ed25519-SHA512. |
| |
| #include <stdint.h> |
| |
| #include "dice/dice.h" |
| #include "dice/ops.h" |
| #include "dice/utils.h" |
| #include "openssl/asn1.h" |
| #include "openssl/asn1t.h" |
| #include "openssl/bn.h" |
| #include "openssl/curve25519.h" |
| #include "openssl/evp.h" |
| #include "openssl/is_boringssl.h" |
| #include "openssl/objects.h" |
| #include "openssl/x509.h" |
| #include "openssl/x509v3.h" |
| |
| #define DICE_MAX_EXTENSION_SIZE 2048 |
| |
| typedef struct DiceExtensionAsn1 { |
| ASN1_OCTET_STRING* code_hash; |
| ASN1_OCTET_STRING* code_descriptor; |
| ASN1_OCTET_STRING* config_hash; |
| ASN1_OCTET_STRING* config_descriptor; |
| ASN1_OCTET_STRING* authority_hash; |
| ASN1_OCTET_STRING* authority_descriptor; |
| ASN1_ENUMERATED* mode; |
| } DiceExtensionAsn1; |
| |
| // clang-format off |
| ASN1_SEQUENCE(DiceExtensionAsn1) = { |
| ASN1_EXP_OPT(DiceExtensionAsn1, code_hash, ASN1_OCTET_STRING, 0), |
| ASN1_EXP_OPT(DiceExtensionAsn1, code_descriptor, ASN1_OCTET_STRING, 1), |
| ASN1_EXP_OPT(DiceExtensionAsn1, config_hash, ASN1_OCTET_STRING, 2), |
| ASN1_EXP_OPT(DiceExtensionAsn1, config_descriptor, ASN1_OCTET_STRING, 3), |
| ASN1_EXP_OPT(DiceExtensionAsn1, authority_hash, ASN1_OCTET_STRING, 4), |
| ASN1_EXP_OPT(DiceExtensionAsn1, authority_descriptor, ASN1_OCTET_STRING, 5), |
| ASN1_EXP_OPT(DiceExtensionAsn1, mode, ASN1_ENUMERATED, 6), |
| } ASN1_SEQUENCE_END(DiceExtensionAsn1) |
| DECLARE_ASN1_FUNCTIONS(DiceExtensionAsn1) |
| IMPLEMENT_ASN1_FUNCTIONS(DiceExtensionAsn1) |
| |
| static DiceResult AddStandardFields(X509* x509, const uint8_t subject_id[DICE_ID_SIZE], |
| const uint8_t authority_id[DICE_ID_SIZE]) { |
| // clang-format on |
| DiceResult result = kDiceResultOk; |
| |
| // Initialize variables that are cleaned up on 'goto out'. |
| ASN1_INTEGER* serial = NULL; |
| BIGNUM* serial_bn = NULL; |
| X509_NAME* issuer_name = NULL; |
| X509_NAME* subject_name = NULL; |
| ASN1_TIME* not_before = NULL; |
| ASN1_TIME* not_after = NULL; |
| |
| serial = ASN1_INTEGER_new(); |
| if (!serial) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| issuer_name = X509_NAME_new(); |
| if (!issuer_name) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| subject_name = X509_NAME_new(); |
| if (!subject_name) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| not_before = ASN1_TIME_new(); |
| if (!not_before) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| not_after = ASN1_TIME_new(); |
| if (!not_after) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| if (!X509_set_version(x509, 2)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| serial_bn = BN_bin2bn(subject_id, DICE_ID_SIZE, NULL); |
| if (!serial_bn) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| BN_to_ASN1_INTEGER(serial_bn, serial); |
| if (!X509_set_serialNumber(x509, serial)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| uint8_t id_hex[40]; |
| DiceHexEncode(authority_id, DICE_ID_SIZE, id_hex, sizeof(id_hex)); |
| if (!X509_NAME_add_entry_by_NID(issuer_name, NID_serialNumber, MBSTRING_UTF8, |
| id_hex, sizeof(id_hex), 0, 0)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_set_issuer_name(x509, issuer_name)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| DiceHexEncode(subject_id, DICE_ID_SIZE, id_hex, sizeof(id_hex)); |
| if (!X509_NAME_add_entry_by_NID(subject_name, NID_serialNumber, MBSTRING_UTF8, |
| id_hex, sizeof(id_hex), 0, 0)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_set_subject_name(x509, subject_name)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // '180322235959Z' is the date of publication of the DICE specification. Here |
| // it's used as a somewhat arbitrary backstop. |
| if (!ASN1_TIME_set_string(not_before, "180322235959Z")) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_set_notBefore(x509, not_before)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| // '99991231235959Z' is suggested by RFC 5280 in cases where expiry is not |
| // meaningful. Basically, the certificate never expires. |
| if (!ASN1_TIME_set_string(not_after, "99991231235959Z")) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_set_notAfter(x509, not_after)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| out: |
| if (serial) { |
| ASN1_INTEGER_free(serial); |
| } |
| if (serial_bn) { |
| BN_free(serial_bn); |
| } |
| if (issuer_name) { |
| X509_NAME_free(issuer_name); |
| } |
| if (subject_name) { |
| X509_NAME_free(subject_name); |
| } |
| if (not_before) { |
| ASN1_TIME_free(not_before); |
| } |
| if (not_after) { |
| ASN1_TIME_free(not_after); |
| } |
| return result; |
| } |
| |
| static DiceResult AddStandardExtensions( |
| X509* x509, const uint8_t subject_id[DICE_ID_SIZE], |
| const uint8_t authority_id[DICE_ID_SIZE]) { |
| DiceResult result = kDiceResultOk; |
| |
| // Initialize variables that are cleaned up on 'goto out'. |
| AUTHORITY_KEYID* authority_key_id = NULL; |
| ASN1_OCTET_STRING* subject_key_id = NULL; |
| ASN1_BIT_STRING* key_usage = NULL; |
| BASIC_CONSTRAINTS* basic_constraints = NULL; |
| X509_EXTENSION* authority_key_id_ext = NULL; |
| X509_EXTENSION* subject_key_id_ext = NULL; |
| X509_EXTENSION* key_usage_ext = NULL; |
| X509_EXTENSION* basic_constraints_ext = NULL; |
| |
| // The authority key identifier extension contains the same raw authority id |
| // that appears in the issuer name. |
| authority_key_id = AUTHORITY_KEYID_new(); |
| if (!authority_key_id) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| authority_key_id->keyid = ASN1_OCTET_STRING_new(); |
| if (!authority_key_id->keyid) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(authority_key_id->keyid, authority_id, |
| DICE_ID_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // The subject key identifier extension contains the same raw subject id that |
| // appears in the serial number and the subject name. |
| subject_key_id = ASN1_OCTET_STRING_new(); |
| if (!subject_key_id) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(subject_key_id, subject_id, DICE_ID_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // The key usage extension contains only "keyCertSign". |
| key_usage = ASN1_BIT_STRING_new(); |
| if (!key_usage) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| ASN1_BIT_STRING_set_bit(key_usage, 5 /*keyCertSign*/, 1); |
| |
| // The basic constraints specify this is a CA with unspecified pathlen. |
| basic_constraints = BASIC_CONSTRAINTS_new(); |
| if (!basic_constraints) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| basic_constraints->ca = 1; |
| |
| // Encode all the extension objects. |
| authority_key_id_ext = X509V3_EXT_i2d(NID_authority_key_identifier, |
| /*crit=*/0, authority_key_id); |
| if (!authority_key_id_ext) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| subject_key_id_ext = X509V3_EXT_i2d(NID_subject_key_identifier, |
| /*crit=*/0, subject_key_id); |
| if (!subject_key_id_ext) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| key_usage_ext = X509V3_EXT_i2d(NID_key_usage, /*crit=*/1, key_usage); |
| if (!key_usage_ext) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| basic_constraints_ext = X509V3_EXT_i2d(NID_basic_constraints, |
| /*crit=*/1, basic_constraints); |
| if (!basic_constraints_ext) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // Add all the extensions to the given X509 object. |
| if (!X509_add_ext(x509, authority_key_id_ext, -1)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_add_ext(x509, subject_key_id_ext, -1)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_add_ext(x509, key_usage_ext, -1)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_add_ext(x509, basic_constraints_ext, -1)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| out: |
| if (authority_key_id) { |
| AUTHORITY_KEYID_free(authority_key_id); |
| } |
| if (subject_key_id) { |
| ASN1_OCTET_STRING_free(subject_key_id); |
| } |
| if (key_usage) { |
| ASN1_BIT_STRING_free(key_usage); |
| } |
| if (basic_constraints) { |
| BASIC_CONSTRAINTS_free(basic_constraints); |
| } |
| if (authority_key_id_ext) { |
| X509_EXTENSION_free(authority_key_id_ext); |
| } |
| if (subject_key_id_ext) { |
| X509_EXTENSION_free(subject_key_id_ext); |
| } |
| if (key_usage_ext) { |
| X509_EXTENSION_free(key_usage_ext); |
| } |
| if (basic_constraints_ext) { |
| X509_EXTENSION_free(basic_constraints_ext); |
| } |
| return result; |
| } |
| |
| static DiceResult GetDiceExtensionData(const DiceInputValues* input_values, |
| size_t buffer_size, uint8_t* buffer, |
| size_t* actual_size) { |
| DiceResult result = kDiceResultOk; |
| |
| DiceExtensionAsn1* asn1 = DiceExtensionAsn1_new(); |
| if (!asn1) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // Allocate required fields. Optional fields will be allocated as needed. |
| asn1->code_hash = ASN1_OCTET_STRING_new(); |
| if (!asn1->code_hash) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| asn1->config_descriptor = ASN1_OCTET_STRING_new(); |
| if (!asn1->config_descriptor) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| asn1->authority_hash = ASN1_OCTET_STRING_new(); |
| if (!asn1->authority_hash) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| asn1->mode = ASN1_ENUMERATED_new(); |
| if (!asn1->mode) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| // Encode code input. |
| if (!ASN1_OCTET_STRING_set(asn1->code_hash, input_values->code_hash, |
| DICE_HASH_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (input_values->code_descriptor_size > 0) { |
| asn1->code_descriptor = ASN1_OCTET_STRING_new(); |
| if (!asn1->code_descriptor) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(asn1->code_descriptor, |
| input_values->code_descriptor, |
| input_values->code_descriptor_size)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| } |
| |
| // Encode configuration inputs. |
| if (input_values->config_type == kDiceConfigTypeDescriptor) { |
| // The 'descriptor' type means the configuration input is in the descriptor |
| // field and the hash of this was used as the DICE input. In the extension |
| // both are stored. |
| uint8_t hash_buffer[DICE_HASH_SIZE]; |
| asn1->config_hash = ASN1_OCTET_STRING_new(); |
| if (!asn1->config_hash) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(asn1->config_descriptor, |
| input_values->config_descriptor, |
| input_values->config_descriptor_size)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set( |
| asn1->config_hash, |
| SHA512(input_values->config_descriptor, |
| input_values->config_descriptor_size, hash_buffer), |
| DICE_HASH_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| } else if (input_values->config_type == kDiceConfigTypeInline) { |
| // The 'inline' type means the configuration value is 64 bytes and was used |
| // directly as the DICE input. In the extension this value is stored in the |
| // descriptor and the hash is omitted. |
| if (!ASN1_OCTET_STRING_set(asn1->config_descriptor, |
| input_values->config_value, |
| DICE_INLINE_CONFIG_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| } else { |
| result = kDiceResultInvalidInput; |
| goto out; |
| } |
| |
| // Encode authority input. |
| if (!ASN1_OCTET_STRING_set(asn1->authority_hash, input_values->authority_hash, |
| DICE_HASH_SIZE)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (input_values->authority_descriptor_size > 0) { |
| asn1->authority_descriptor = ASN1_OCTET_STRING_new(); |
| if (!asn1->authority_descriptor) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(asn1->authority_descriptor, |
| input_values->authority_descriptor, |
| input_values->authority_descriptor_size)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| } |
| |
| // Encode mode input. |
| if (!ASN1_ENUMERATED_set(asn1->mode, input_values->mode)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| *actual_size = i2d_DiceExtensionAsn1(asn1, NULL); |
| if (buffer_size < *actual_size) { |
| result = kDiceResultBufferTooSmall; |
| goto out; |
| } |
| i2d_DiceExtensionAsn1(asn1, &buffer); |
| |
| out: |
| if (asn1) { |
| DiceExtensionAsn1_free(asn1); |
| } |
| return result; |
| } |
| |
| static DiceResult AddDiceExtension(const DiceInputValues* input_values, |
| X509* x509) { |
| const char* kDiceExtensionOid = "1.3.6.1.4.1.11129.2.1.24"; |
| |
| // Initialize variables that are cleaned up on 'goto out'. |
| ASN1_OBJECT* oid = NULL; |
| ASN1_OCTET_STRING* octets = NULL; |
| X509_EXTENSION* extension = NULL; |
| |
| uint8_t extension_buffer[DICE_MAX_EXTENSION_SIZE]; |
| size_t extension_size = 0; |
| DiceResult result = |
| GetDiceExtensionData(input_values, sizeof(extension_buffer), |
| extension_buffer, &extension_size); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| |
| oid = OBJ_txt2obj(kDiceExtensionOid, 1); |
| if (!oid) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| octets = ASN1_OCTET_STRING_new(); |
| if (!octets) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!ASN1_OCTET_STRING_set(octets, extension_buffer, extension_size)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| extension = |
| X509_EXTENSION_create_by_OBJ(/*ex=*/NULL, oid, /*crit=*/1, octets); |
| if (!extension) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| if (!X509_add_ext(x509, extension, -1)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| out: |
| if (oid) { |
| ASN1_OBJECT_free(oid); |
| } |
| if (octets) { |
| ASN1_OCTET_STRING_free(octets); |
| } |
| if (extension) { |
| X509_EXTENSION_free(extension); |
| } |
| return result; |
| } |
| |
| static DiceResult GetIdFromKey(void* context, const EVP_PKEY* key, |
| uint8_t id[DICE_ID_SIZE]) { |
| uint8_t raw_public_key[32]; |
| size_t raw_public_key_size = sizeof(raw_public_key); |
| if (!EVP_PKEY_get_raw_public_key(key, raw_public_key, &raw_public_key_size)) { |
| return kDiceResultPlatformError; |
| } |
| return DiceDeriveCdiCertificateId(context, raw_public_key, |
| raw_public_key_size, id); |
| } |
| |
| DiceResult DiceGenerateCertificate( |
| void* context, |
| const uint8_t subject_private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE], |
| const uint8_t authority_private_key_seed[DICE_PRIVATE_KEY_SEED_SIZE], |
| const DiceInputValues* input_values, size_t certificate_buffer_size, |
| uint8_t* certificate, size_t* certificate_actual_size) { |
| DiceResult result = kDiceResultOk; |
| |
| // Initialize variables that are cleaned up on 'goto out'. |
| X509* x509 = NULL; |
| EVP_PKEY* authority_key = NULL; |
| EVP_PKEY* subject_key = NULL; |
| |
| x509 = X509_new(); |
| if (!x509) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| authority_key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, |
| authority_private_key_seed, |
| DICE_PRIVATE_KEY_SEED_SIZE); |
| if (!authority_key) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| subject_key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, |
| subject_private_key_seed, |
| DICE_PRIVATE_KEY_SEED_SIZE); |
| if (!subject_key) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| if (!X509_set_pubkey(x509, subject_key)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| |
| uint8_t authority_id[DICE_ID_SIZE]; |
| result = GetIdFromKey(context, authority_key, authority_id); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| uint8_t subject_id[DICE_ID_SIZE]; |
| result = GetIdFromKey(context, subject_key, subject_id); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| |
| result = AddStandardFields(x509, subject_id, authority_id); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| result = AddStandardExtensions(x509, subject_id, authority_id); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| result = AddDiceExtension(input_values, x509); |
| if (result != kDiceResultOk) { |
| goto out; |
| } |
| if (!X509_sign(x509, authority_key, NULL /*ED25519 always uses SHA-512*/)) { |
| result = kDiceResultPlatformError; |
| goto out; |
| } |
| *certificate_actual_size = i2d_X509(x509, NULL); |
| if (*certificate_actual_size > certificate_buffer_size) { |
| result = kDiceResultBufferTooSmall; |
| goto out; |
| } |
| *certificate_actual_size = i2d_X509(x509, &certificate); |
| out: |
| if (x509) { |
| X509_free(x509); |
| } |
| if (authority_key) { |
| EVP_PKEY_free(authority_key); |
| } |
| if (subject_key) { |
| EVP_PKEY_free(subject_key); |
| } |
| return result; |
| } |