diff options
author | Adam Langley <agl@chromium.org> | 2018-08-07 10:32:19 -0700 |
---|---|---|
committer | Eric Biggers <ebiggers@google.com> | 2018-09-05 21:32:22 -0700 |
commit | bdebc45b4527d64109723ad5753fa514bac47c9f (patch) | |
tree | b56d7aa6e1cf469e6bf887f2fddae1b807120a3f | |
parent | 0aa29563ad4b6668edac56414877d204397c36a4 (diff) | |
download | fsverity-utils-bdebc45b4527d64109723ad5753fa514bac47c9f.tar.gz |
Add support for BoringSSL.
BoringSSL (mostly) does not support PKCS#7. Thus lay down / parse the
PKCS#7 directly when building with it.
Signed-off-by: Adam Langley <agl@chromium.org>
(EB: coding style tweaks)
Signed-off-by: Eric Biggers <ebiggers@google.com>
-rw-r--r-- | sign.c | 320 |
1 files changed, 243 insertions, 77 deletions
@@ -36,22 +36,6 @@ error_msg_openssl(const char *format, ...) ERR_print_errors_fp(stderr); } -static BIO *new_mem_buf(const void *buf, size_t size) -{ - BIO *bio; - - ASSERT(size <= INT_MAX); - /* - * Prior to OpenSSL 1.1.0, BIO_new_mem_buf() took a non-const pointer, - * despite still marking the resulting bio as read-only. So cast away - * the const to avoid a compiler warning with older OpenSSL versions. - */ - bio = BIO_new_mem_buf((void *)buf, size); - if (!bio) - error_msg_openssl("out of memory"); - return bio; -} - /* Read a PEM PKCS#8 formatted private key */ static EVP_PKEY *read_private_key(const char *keyfile) { @@ -123,22 +107,166 @@ compare_fsverity_digest(const void *data, size_t size, return NULL; } -/* - * Sign the specified @data_to_sign of length @data_size bytes using the private - * key in @keyfile, the certificate in @certfile, and the hash algorithm - * @hash_alg. Returns the DER-formatted PKCS#7 signature, with the signed data - * included (not detached), in @sig_ret and @sig_size_ret. - */ -static bool sign_data(const void *data_to_sign, size_t data_size, - const char *keyfile, const char *certfile, - const struct fsverity_hash_alg *hash_alg, - void **sig_ret, int *sig_size_ret) +#ifdef OPENSSL_IS_BORINGSSL + +static bool sign_pkcs7(const void *data_to_sign, size_t data_size, + EVP_PKEY *pkey, X509 *cert, const EVP_MD *md, + void **sig_ret, int *sig_size_ret) +{ + CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo, + null, content_info, issuer_and_serial, signed_data, + wrapped_signed_data, signer_infos, signer_info, sign_algo, + signature; + EVP_MD_CTX md_ctx; + u8 *name_der = NULL, *sig = NULL, *pkcs7_data = NULL; + size_t pkcs7_data_len, sig_len; + int name_der_len, sig_nid; + bool ok = false; + + EVP_MD_CTX_init(&md_ctx); + BIGNUM *serial = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), NULL); + + if (!CBB_init(&out, 1024)) { + error_msg("out of memory"); + goto out; + } + + name_der_len = i2d_X509_NAME(X509_get_subject_name(cert), &name_der); + if (name_der_len < 0) { + error_msg_openssl("i2d_X509_NAME failed"); + goto out; + } + + if (!EVP_DigestSignInit(&md_ctx, NULL, md, NULL, pkey)) { + error_msg_openssl("EVP_DigestSignInit failed"); + goto out; + } + + sig_len = EVP_PKEY_size(pkey); + sig = xmalloc(sig_len); + if (!EVP_DigestSign(&md_ctx, sig, &sig_len, data_to_sign, data_size)) { + error_msg_openssl("EVP_DigestSign failed"); + goto out; + } + + sig_nid = EVP_PKEY_id(pkey); + /* To mirror OpenSSL behaviour, always use |NID_rsaEncryption| with RSA + * rather than the combined hash+pkey NID. */ + if (sig_nid != NID_rsaEncryption) { + OBJ_find_sigid_by_algs(&sig_nid, EVP_MD_type(md), + EVP_PKEY_id(pkey)); + } + + // See https://tools.ietf.org/html/rfc2315#section-7 + if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) || + !OBJ_nid2cbb(&outer_seq, NID_pkcs7_signed) || + !CBB_add_asn1(&outer_seq, &wrapped_seq, CBS_ASN1_CONTEXT_SPECIFIC | + CBS_ASN1_CONSTRUCTED | 0) || + // See https://tools.ietf.org/html/rfc2315#section-9.1 + !CBB_add_asn1(&wrapped_seq, &seq, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1_uint64(&seq, 1 /* version */) || + !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) || + !CBB_add_asn1(&digest_algos_set, &digest_algo, CBS_ASN1_SEQUENCE) || + !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) || + !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || + !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) || + !OBJ_nid2cbb(&content_info, NID_pkcs7_data) || + !CBB_add_asn1( + &content_info, &signed_data, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBB_add_asn1(&signed_data, &wrapped_signed_data, + CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&wrapped_signed_data, (const u8 *)data_to_sign, + data_size) || + !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) || + !CBB_add_asn1(&signer_infos, &signer_info, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1_uint64(&signer_info, 1 /* version */) || + !CBB_add_asn1(&signer_info, &issuer_and_serial, + CBS_ASN1_SEQUENCE) || + !CBB_add_bytes(&issuer_and_serial, name_der, name_der_len) || + !BN_marshal_asn1(&issuer_and_serial, serial) || + !CBB_add_asn1(&signer_info, &digest_algo, CBS_ASN1_SEQUENCE) || + !OBJ_nid2cbb(&digest_algo, EVP_MD_type(md)) || + !CBB_add_asn1(&digest_algo, &null, CBS_ASN1_NULL) || + !CBB_add_asn1(&signer_info, &sign_algo, CBS_ASN1_SEQUENCE) || + !OBJ_nid2cbb(&sign_algo, sig_nid) || + !CBB_add_asn1(&sign_algo, &null, CBS_ASN1_NULL) || + !CBB_add_asn1(&signer_info, &signature, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&signature, sig, sig_len) || + !CBB_finish(&out, &pkcs7_data, &pkcs7_data_len)) { + error_msg_openssl("failed to construct PKCS#7 data"); + goto out; + } + + *sig_ret = xmemdup(pkcs7_data, pkcs7_data_len); + *sig_size_ret = pkcs7_data_len; + ok = true; +out: + BN_free(serial); + EVP_MD_CTX_cleanup(&md_ctx); + CBB_cleanup(&out); + free(sig); + OPENSSL_free(name_der); + OPENSSL_free(pkcs7_data); + return ok; +} + +static const char * +compare_fsverity_digest_pkcs7(const void *sig, size_t sig_len, + const u8 *expected_measurement, + const struct fsverity_hash_alg *hash_alg) +{ + CBS in, content_info, content_type, wrapped_signed_data, signed_data, + content, wrapped_data, data; + u64 version; + + CBS_init(&in, sig, sig_len); + if (!CBS_get_asn1(&in, &content_info, CBS_ASN1_SEQUENCE) || + !CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT) || + (OBJ_cbs2nid(&content_type) != NID_pkcs7_signed) || + !CBS_get_asn1( + &content_info, &wrapped_signed_data, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBS_get_asn1(&wrapped_signed_data, &signed_data, + CBS_ASN1_SEQUENCE) || + !CBS_get_asn1_uint64(&signed_data, &version) || + (version < 1) || + !CBS_get_asn1(&signed_data, NULL /* digests */, CBS_ASN1_SET) || + !CBS_get_asn1(&signed_data, &content, CBS_ASN1_SEQUENCE) || + !CBS_get_asn1(&content, &content_type, CBS_ASN1_OBJECT) || + (OBJ_cbs2nid(&content_type) != NID_pkcs7_data) || + !CBS_get_asn1(&content, &wrapped_data, CBS_ASN1_CONTEXT_SPECIFIC | + CBS_ASN1_CONSTRUCTED | 0) || + !CBS_get_asn1(&wrapped_data, &data, CBS_ASN1_OCTETSTRING)) { + return "invalid PKCS#7 data"; + } + + return compare_fsverity_digest(CBS_data(&data), CBS_len(&data), + expected_measurement, hash_alg); +} + +#else /* OPENSSL_IS_BORINGSSL */ + +static BIO *new_mem_buf(const void *buf, size_t size) +{ + BIO *bio; + + ASSERT(size <= INT_MAX); + /* + * Prior to OpenSSL 1.1.0, BIO_new_mem_buf() took a non-const pointer, + * despite still marking the resulting bio as read-only. So cast away + * the const to avoid a compiler warning with older OpenSSL versions. + */ + bio = BIO_new_mem_buf((void *)buf, size); + if (!bio) + error_msg_openssl("out of memory"); + return bio; +} + +static bool sign_pkcs7(const void *data_to_sign, size_t data_size, + EVP_PKEY *pkey, X509 *cert, const EVP_MD *md, + void **sig_ret, int *sig_size_ret) { - EVP_PKEY *pkey = NULL; - X509 *cert = NULL; - BIO *bio = NULL; - PKCS7 *p7 = NULL; - const EVP_MD *md; /* * PKCS#7 signing flags: * @@ -159,27 +287,10 @@ static bool sign_data(const void *data_to_sign, size_t data_size, PKCS7_PARTIAL; void *sig; int sig_size; + BIO *bio = NULL; + PKCS7 *p7 = NULL; bool ok = false; - pkey = read_private_key(keyfile); - if (!pkey) - goto out; - - cert = read_certificate(certfile); - if (!cert) - goto out; - - OpenSSL_add_all_digests(); - ASSERT(hash_alg->cryptographic); - md = EVP_get_digestbyname(hash_alg->name); - if (!md) { - fprintf(stderr, - "Warning: '%s' algorithm not found in OpenSSL library.\n" - " Falling back to SHA-256 signature.\n", - hash_alg->name); - md = EVP_sha256(); - } - bio = new_mem_buf(data_to_sign, data_size); if (!bio) goto out; @@ -217,13 +328,91 @@ static bool sign_data(const void *data_to_sign, size_t data_size, *sig_size_ret = sig_size; ok = true; out: - EVP_PKEY_free(pkey); - X509_free(cert); PKCS7_free(p7); BIO_free(bio); return ok; } +static const char * +compare_fsverity_digest_pkcs7(const void *sig, size_t sig_len, + const u8 *expected_measurement, + const struct fsverity_hash_alg *hash_alg) +{ + BIO *bio = NULL; + PKCS7 *p7 = NULL; + const char *reason = NULL; + + bio = new_mem_buf(sig, sig_len); + if (!bio) + return "out of memory"; + + p7 = d2i_PKCS7_bio(bio, NULL); + if (!p7) { + reason = "failed to decode PKCS#7 signature"; + goto out; + } + + if (OBJ_obj2nid(p7->type) != NID_pkcs7_signed || + OBJ_obj2nid(p7->d.sign->contents->type) != NID_pkcs7_data) { + reason = "unexpected PKCS#7 content type"; + } else { + const ASN1_OCTET_STRING *o = p7->d.sign->contents->d.data; + + reason = compare_fsverity_digest(o->data, o->length, + expected_measurement, + hash_alg); + } +out: + BIO_free(bio); + PKCS7_free(p7); + return reason; +} + +#endif /* !OPENSSL_IS_BORINGSSL */ + +/* + * Sign the specified @data_to_sign of length @data_size bytes using the private + * key in @keyfile, the certificate in @certfile, and the hash algorithm + * @hash_alg. Returns the DER-formatted PKCS#7 signature, with the signed data + * included (not detached), in @sig_ret and @sig_size_ret. + */ +static bool sign_data(const void *data_to_sign, size_t data_size, + const char *keyfile, const char *certfile, + const struct fsverity_hash_alg *hash_alg, + void **sig_ret, int *sig_size_ret) +{ + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + const EVP_MD *md; + bool ok = false; + + pkey = read_private_key(keyfile); + if (!pkey) + goto out; + + cert = read_certificate(certfile); + if (!cert) + goto out; + + OpenSSL_add_all_digests(); + ASSERT(hash_alg->cryptographic); + md = EVP_get_digestbyname(hash_alg->name); + if (!md) { + fprintf(stderr, + "Warning: '%s' algorithm not found in OpenSSL library.\n" + " Falling back to SHA-256 signature.\n", + hash_alg->name); + md = EVP_sha256(); + } + + ok = sign_pkcs7(data_to_sign, data_size, pkey, cert, md, + sig_ret, sig_size_ret); +out: + EVP_PKEY_free(pkey); + X509_free(cert); + return ok; +} + /* * Read a file measurement signature in PKCS#7 DER format from @signature_file, * validate that the signed data matches the expected measurement, then return @@ -237,8 +426,6 @@ static bool read_signature(const char *signature_file, struct filedes file = { .fd = -1 }; u64 filesize; void *sig = NULL; - BIO *bio = NULL; - PKCS7 *p7 = NULL; bool ok = false; const char *reason; @@ -258,27 +445,8 @@ static bool read_signature(const char *signature_file, if (!full_read(&file, sig, filesize)) goto out; - bio = new_mem_buf(sig, filesize); - if (!bio) - goto out; - - p7 = d2i_PKCS7_bio(bio, NULL); - if (!p7) { - error_msg_openssl("failed to decode PKCS#7 signature from '%s'", - signature_file); - goto out; - } - - if (OBJ_obj2nid(p7->type) != NID_pkcs7_signed || - OBJ_obj2nid(p7->d.sign->contents->type) != NID_pkcs7_data) { - reason = "unexpected PKCS#7 content type"; - } else { - const ASN1_OCTET_STRING *o = p7->d.sign->contents->d.data; - - reason = compare_fsverity_digest(o->data, o->length, - expected_measurement, - hash_alg); - } + reason = compare_fsverity_digest_pkcs7(sig, filesize, + expected_measurement, hash_alg); if (reason) { error_msg("signed file measurement from '%s' is invalid (%s)", signature_file, reason); @@ -294,8 +462,6 @@ static bool read_signature(const char *signature_file, out: filedes_close(&file); free(sig); - BIO_free(bio); - PKCS7_free(p7); return ok; } |