From 3815720cf31339b1739f8ad1c21205105412c6b5 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Fri, 15 Jun 2018 17:31:19 -0400 Subject: [PATCH] Add a bunch of compatibility functions for PKCS#7. The full library is a bit much, but this is enough to appease most of cryptography.io. Change-Id: I1bb0d83744c4550d5fe23c5c98cfd7e36b17fcc9 Reviewed-on: https://boringssl-review.googlesource.com/29365 Reviewed-by: Adam Langley Commit-Queue: Adam Langley CQ-Verified: CQ bot account: commit-bot@chromium.org --- crypto/pem/pem_all.c | 5 +- crypto/pkcs7/pkcs7_test.cc | 75 ++++++++++++++++- crypto/pkcs7/pkcs7_x509.c | 166 +++++++++++++++++++++++++++++++++++++ include/openssl/pem.h | 2 + include/openssl/pkcs7.h | 121 +++++++++++++++++++++++++++ 5 files changed, 364 insertions(+), 5 deletions(-) diff --git a/crypto/pem/pem_all.c b/crypto/pem/pem_all.c index e94ff265..6b408831 100644 --- a/crypto/pem/pem_all.c +++ b/crypto/pem/pem_all.c @@ -113,9 +113,7 @@ #include #include #include -/* - * #include - */ +#include #include #include @@ -127,6 +125,7 @@ IMPLEMENT_PEM_rw(X509_REQ, X509_REQ, PEM_STRING_X509_REQ, X509_REQ) IMPLEMENT_PEM_write(X509_REQ_NEW, X509_REQ, PEM_STRING_X509_REQ_OLD, X509_REQ) IMPLEMENT_PEM_rw(X509_CRL, X509_CRL, PEM_STRING_X509_CRL, X509_CRL) +IMPLEMENT_PEM_rw(PKCS7, PKCS7, PEM_STRING_PKCS7, PKCS7) /* * We treat RSA or DSA private keys as a special case. For private keys we diff --git a/crypto/pkcs7/pkcs7_test.cc b/crypto/pkcs7/pkcs7_test.cc index e146ede6..1ac9af2b 100644 --- a/crypto/pkcs7/pkcs7_test.cc +++ b/crypto/pkcs7/pkcs7_test.cc @@ -493,7 +493,6 @@ static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) { EXPECT_EQ(0u, CBS_len(&pkcs7)); ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs2.get())); - for (size_t i = 0; i < sk_X509_num(certs.get()); i++) { X509 *a = sk_X509_value(certs.get(), i); X509 *b = sk_X509_value(certs2.get(), i); @@ -506,6 +505,50 @@ static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) { bssl::UniquePtr free_result2_data(result2_data); EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len)); + + // Parse with the legacy API instead. + const uint8_t *ptr = der_bytes; + bssl::UniquePtr pkcs7_obj(d2i_PKCS7(nullptr, &ptr, der_len)); + ASSERT_TRUE(pkcs7_obj); + EXPECT_EQ(ptr, der_bytes + der_len); + + ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get())); + const STACK_OF(X509) *certs3 = pkcs7_obj->d.sign->cert; + ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs3)); + for (size_t i = 0; i < sk_X509_num(certs.get()); i++) { + X509 *a = sk_X509_value(certs.get(), i); + X509 *b = sk_X509_value(certs3, i); + ASSERT_EQ(0, X509_cmp(a, b)); + } + + // Serialize the original object. This should echo back the original saved + // bytes. + uint8_t *result3_data = nullptr; + int result3_len = i2d_PKCS7(pkcs7_obj.get(), &result3_data); + ASSERT_GT(result3_len, 0); + bssl::UniquePtr free_result3_data(result3_data); + EXPECT_EQ(Bytes(der_bytes, der_len), Bytes(result3_data, result3_len)); + + // Make a new object with the legacy API. + pkcs7_obj.reset( + PKCS7_sign(nullptr, nullptr, certs.get(), nullptr, PKCS7_DETACHED)); + ASSERT_TRUE(pkcs7_obj); + + ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get())); + const STACK_OF(X509) *certs4 = pkcs7_obj->d.sign->cert; + ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs4)); + for (size_t i = 0; i < sk_X509_num(certs.get()); i++) { + X509 *a = sk_X509_value(certs.get(), i); + X509 *b = sk_X509_value(certs4, i); + ASSERT_EQ(0, X509_cmp(a, b)); + } + + // This new object should serialize canonically. + uint8_t *result4_data = nullptr; + int result4_len = i2d_PKCS7(pkcs7_obj.get(), &result4_data); + ASSERT_GT(result4_len, 0); + bssl::UniquePtr free_result4_data(result4_data); + EXPECT_EQ(Bytes(result_data, result_len), Bytes(result4_data, result4_len)); } static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) { @@ -532,7 +575,6 @@ static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) { EXPECT_EQ(0u, CBS_len(&pkcs7)); ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls.get())); - for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) { X509_CRL *a = sk_X509_CRL_value(crls.get(), i); X509_CRL *b = sk_X509_CRL_value(crls2.get(), i); @@ -545,6 +587,35 @@ static void TestCRLReparse(const uint8_t *der_bytes, size_t der_len) { bssl::UniquePtr free_result2_data(result2_data); EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len)); + + // Parse with the legacy API instead. + const uint8_t *ptr = der_bytes; + bssl::UniquePtr pkcs7_obj(d2i_PKCS7(nullptr, &ptr, der_len)); + ASSERT_TRUE(pkcs7_obj); + EXPECT_EQ(ptr, der_bytes + der_len); + + ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get())); + const STACK_OF(X509_CRL) *crls3 = pkcs7_obj->d.sign->crl; + ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls3)); + for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) { + X509_CRL *a = sk_X509_CRL_value(crls.get(), i); + X509_CRL *b = sk_X509_CRL_value(crls3, i); + ASSERT_EQ(0, X509_CRL_cmp(a, b)); + } + + ptr = result_data; + pkcs7_obj.reset(d2i_PKCS7(nullptr, &ptr, result_len)); + ASSERT_TRUE(pkcs7_obj); + EXPECT_EQ(ptr, result_data + result_len); + + ASSERT_TRUE(PKCS7_type_is_signed(pkcs7_obj.get())); + const STACK_OF(X509_CRL) *crls4 = pkcs7_obj->d.sign->crl; + ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls4)); + for (size_t i = 0; i < sk_X509_CRL_num(crls.get()); i++) { + X509_CRL *a = sk_X509_CRL_value(crls.get(), i); + X509_CRL *b = sk_X509_CRL_value(crls4, i); + ASSERT_EQ(0, X509_CRL_cmp(a, b)); + } } static void TestPEMCerts(const char *pem) { diff --git a/crypto/pkcs7/pkcs7_x509.c b/crypto/pkcs7/pkcs7_x509.c index 7f83405b..a2a6b46a 100644 --- a/crypto/pkcs7/pkcs7_x509.c +++ b/crypto/pkcs7/pkcs7_x509.c @@ -26,6 +26,7 @@ #include #include "internal.h" +#include "../internal.h" int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) { @@ -227,3 +228,168 @@ static int pkcs7_bundle_crls_cb(CBB *out, const void *arg) { int PKCS7_bundle_CRLs(CBB *out, const STACK_OF(X509_CRL) *crls) { return pkcs7_bundle(out, pkcs7_bundle_crls_cb, crls); } + +static PKCS7 *pkcs7_new(CBS *cbs) { + PKCS7 *ret = OPENSSL_malloc(sizeof(PKCS7)); + if (ret == NULL) { + return NULL; + } + OPENSSL_memset(ret, 0, sizeof(PKCS7)); + ret->type = (ASN1_OBJECT *)OBJ_nid2obj(NID_pkcs7_signed); + ret->d.sign = OPENSSL_malloc(sizeof(PKCS7_SIGNED)); + if (ret->d.sign == NULL) { + goto err; + } + ret->d.sign->cert = sk_X509_new_null(); + ret->d.sign->crl = sk_X509_CRL_new_null(); + CBS copy = *cbs, copy2 = *cbs; + if (ret->d.sign->cert == NULL || ret->d.sign->crl == NULL || + !PKCS7_get_certificates(ret->d.sign->cert, ©) || + !PKCS7_get_CRLs(ret->d.sign->crl, cbs)) { + goto err; + } + + if (sk_X509_num(ret->d.sign->cert) == 0) { + sk_X509_free(ret->d.sign->cert); + ret->d.sign->cert = NULL; + } + + if (sk_X509_CRL_num(ret->d.sign->crl) == 0) { + sk_X509_CRL_free(ret->d.sign->crl); + ret->d.sign->crl = NULL; + } + + ret->ber_len = CBS_len(©2) - CBS_len(cbs); + ret->ber_bytes = BUF_memdup(CBS_data(©2), ret->ber_len); + if (ret->ber_bytes == NULL) { + goto err; + } + + return ret; + +err: + PKCS7_free(ret); + return NULL; +} + +PKCS7 *d2i_PKCS7(PKCS7 **out, const uint8_t **inp, + size_t len) { + CBS cbs; + CBS_init(&cbs, *inp, len); + PKCS7 *ret = pkcs7_new(&cbs); + if (ret == NULL) { + return NULL; + } + *inp = CBS_data(&cbs); + if (out != NULL) { + PKCS7_free(*out); + *out = ret; + } + return ret; +} + +PKCS7 *d2i_PKCS7_bio(BIO *bio, PKCS7 **out) { + // Use a generous bound, to allow for PKCS#7 files containing large root sets. + static const size_t kMaxSize = 4 * 1024 * 1024; + uint8_t *data; + size_t len; + if (!BIO_read_asn1(bio, &data, &len, kMaxSize)) { + return NULL; + } + + CBS cbs; + CBS_init(&cbs, data, len); + PKCS7 *ret = pkcs7_new(&cbs); + OPENSSL_free(data); + if (out != NULL && ret != NULL) { + PKCS7_free(*out); + *out = ret; + } + return ret; +} + +int i2d_PKCS7(const PKCS7 *p7, uint8_t **out) { + if (p7->ber_len > INT_MAX) { + OPENSSL_PUT_ERROR(PKCS8, ERR_R_OVERFLOW); + return -1; + } + + if (out == NULL) { + return (int)p7->ber_len; + } + + if (*out == NULL) { + *out = OPENSSL_malloc(p7->ber_len); + if (*out == NULL) { + OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE); + return -1; + } + OPENSSL_memcpy(*out, p7->ber_bytes, p7->ber_len); + } else { + OPENSSL_memcpy(*out, p7->ber_bytes, p7->ber_len); + *out += p7->ber_len; + } + return (int)p7->ber_len; +} + +int i2d_PKCS7_bio(BIO *bio, const PKCS7 *p7) { + size_t written = 0; + while (written < p7->ber_len) { + size_t todo = p7->ber_len - written; + int len = todo > INT_MAX ? INT_MAX : (int)todo; + int ret = BIO_write(bio, p7->ber_bytes + written, len); + if (ret <= 0) { + return 0; + } + written += (size_t)ret; + } + return 1; +} + +void PKCS7_free(PKCS7 *p7) { + if (p7 == NULL) { + return; + } + + OPENSSL_free(p7->ber_bytes); + ASN1_OBJECT_free(p7->type); + // We only supported signed data. + if (p7->d.sign != NULL) { + sk_X509_pop_free(p7->d.sign->cert, X509_free); + sk_X509_CRL_pop_free(p7->d.sign->crl, X509_CRL_free); + OPENSSL_free(p7->d.sign); + } + OPENSSL_free(p7); +} + +// We only support signed data, so these getters are no-ops. +int PKCS7_type_is_data(const PKCS7 *p7) { return 0; } +int PKCS7_type_is_digest(const PKCS7 *p7) { return 0; } +int PKCS7_type_is_encrypted(const PKCS7 *p7) { return 0; } +int PKCS7_type_is_enveloped(const PKCS7 *p7) { return 0; } +int PKCS7_type_is_signed(const PKCS7 *p7) { return 1; } +int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7) { return 0; } + +PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey, STACK_OF(X509) *certs, + BIO *data, int flags) { + if (sign_cert != NULL || pkey != NULL || flags != PKCS7_DETACHED) { + OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return NULL; + } + + uint8_t *der; + size_t len; + CBB cbb; + if (!CBB_init(&cbb, 2048) || + !PKCS7_bundle_certificates(&cbb, certs) || + !CBB_finish(&cbb, &der, &len)) { + CBB_cleanup(&cbb); + return NULL; + } + + CBS cbs; + CBS_init(&cbs, der, len); + PKCS7 *ret = pkcs7_new(&cbs); + OPENSSL_free(der); + return ret; +} diff --git a/include/openssl/pem.h b/include/openssl/pem.h index 4868e12f..a43ca0de 100644 --- a/include/openssl/pem.h +++ b/include/openssl/pem.h @@ -63,6 +63,7 @@ #include #include #include +#include #include /* For compatibility with open-iscsi, which assumes that it can get @@ -329,6 +330,7 @@ DECLARE_PEM_write(X509_REQ_NEW, X509_REQ) DECLARE_PEM_rw(X509_CRL, X509_CRL) +DECLARE_PEM_rw(PKCS7, PKCS7) DECLARE_PEM_rw(PKCS8, X509_SIG) DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO) diff --git a/include/openssl/pkcs7.h b/include/openssl/pkcs7.h index 71a74c80..52b649c2 100644 --- a/include/openssl/pkcs7.h +++ b/include/openssl/pkcs7.h @@ -82,8 +82,129 @@ OPENSSL_EXPORT int PKCS7_get_PEM_CRLs(STACK_OF(X509_CRL) *out_crls, BIO *pem_bio); +// Deprecated functions. +// +// These functions are a compatibility layer over a subset of OpenSSL's PKCS#7 +// API. It intentionally does not implement the whole thing, only the minimum +// needed to build cryptography.io. + +typedef struct { + STACK_OF(X509) *cert; + STACK_OF(X509_CRL) *crl; +} PKCS7_SIGNED; + +typedef struct { + STACK_OF(X509) *cert; + STACK_OF(X509_CRL) *crl; +} PKCS7_SIGN_ENVELOPE; + +typedef void PKCS7_ENVELOPE; +typedef void PKCS7_DIGEST; +typedef void PKCS7_ENCRYPT; + +typedef struct { + uint8_t *ber_bytes; + size_t ber_len; + + // Unlike OpenSSL, the following fields are immutable. They filled in when the + // object is parsed and ignored in serialization. + ASN1_OBJECT *type; + union { + char *ptr; + ASN1_OCTET_STRING *data; + PKCS7_SIGNED *sign; + PKCS7_ENVELOPE *enveloped; + PKCS7_SIGN_ENVELOPE *signed_and_enveloped; + PKCS7_DIGEST *digest; + PKCS7_ENCRYPT *encrypted; + ASN1_TYPE *other; + } d; +} PKCS7; + +// d2i_PKCS7 parses a BER-encoded, PKCS#7 signed data ContentInfo structure from +// |len| bytes at |*inp|. If |out| is not NULL then, on exit, a pointer to the +// result is in |*out|. Note that, even if |*out| is already non-NULL on entry, +// it will not be written to. Rather, a fresh |PKCS7| is allocated and the +// previous one is freed. On successful exit, |*inp| is advanced past the BER +// structure. It returns the result or NULL on error. +OPENSSL_EXPORT PKCS7 *d2i_PKCS7(PKCS7 **out, const uint8_t **inp, + size_t len); + +// d2i_PKCS7_bio behaves like |d2i_PKCS7| but reads the input from |bio|. If +// the length of the object is indefinite the full contents of |bio| are read. +// +// If the function fails then some unknown amount of data may have been read +// from |bio|. +OPENSSL_EXPORT PKCS7 *d2i_PKCS7_bio(BIO *bio, PKCS7 **out); + +// i2d_PKCS7 is a dummy function which copies the contents of |p7|. If |out| is +// not NULL then the result is written to |*out| and |*out| is advanced just +// past the output. It returns the number of bytes in the result, whether +// written or not, or a negative value on error. +OPENSSL_EXPORT int i2d_PKCS7(const PKCS7 *p7, uint8_t **out); + +// i2d_PKCS7_bio writes |p7| to |bio|. It returns one on success and zero on +// error. +OPENSSL_EXPORT int i2d_PKCS7_bio(BIO *bio, const PKCS7 *p7); + +// PKCS7_free releases memory associated with |p7|. +OPENSSL_EXPORT void PKCS7_free(PKCS7 *p7); + +// PKCS7_type_is_data returns zero. +OPENSSL_EXPORT int PKCS7_type_is_data(const PKCS7 *p7); + +// PKCS7_type_is_digest returns zero. +OPENSSL_EXPORT int PKCS7_type_is_digest(const PKCS7 *p7); + +// PKCS7_type_is_encrypted returns zero. +OPENSSL_EXPORT int PKCS7_type_is_encrypted(const PKCS7 *p7); + +// PKCS7_type_is_enveloped returns zero. +OPENSSL_EXPORT int PKCS7_type_is_enveloped(const PKCS7 *p7); + +// PKCS7_type_is_signed returns one. (We only supporte signed data +// ContentInfos.) +OPENSSL_EXPORT int PKCS7_type_is_signed(const PKCS7 *p7); + +// PKCS7_type_is_signedAndEnveloped returns zero. +OPENSSL_EXPORT int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7); + +// PKCS7_DETACHED indicates that the PKCS#7 file specifies its data externally. +#define PKCS7_DETACHED 0x40 + +// The following flags cause |PKCS7_sign| to fail. +#define PKCS7_TEXT 0x1 +#define PKCS7_NOCERTS 0x2 +#define PKCS7_NOSIGS 0x4 +#define PKCS7_NOCHAIN 0x8 +#define PKCS7_NOINTERN 0x10 +#define PKCS7_NOVERIFY 0x20 +#define PKCS7_BINARY 0x80 +#define PKCS7_NOATTR 0x100 +#define PKCS7_NOSMIMECAP 0x200 +#define PKCS7_STREAM 0x1000 + +// PKCS7_sign assembles |certs| into a PKCS#7 signed data ContentInfo with +// external data and no signatures. It returns a newly-allocated |PKCS7| on +// success or NULL on error. |sign_cert| and |pkey| must be NULL. |data| is +// ignored. |flags| must be equal to |PKCS7_DETACHED|. +// +// Note this function only implements a subset of the corresponding OpenSSL +// function. It is provided for backwards compatibility only. +OPENSSL_EXPORT PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey, + STACK_OF(X509) *certs, BIO *data, int flags); + + #if defined(__cplusplus) } // extern C + +extern "C++" { +namespace bssl { + +BORINGSSL_MAKE_DELETER(PKCS7, PKCS7_free) + +} // namespace bssl +} // extern C++ #endif #define PKCS7_R_BAD_PKCS7_VERSION 100