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 <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
This commit is contained in:
David Benjamin 2018-06-15 17:31:19 -04:00 committed by CQ bot account: commit-bot@chromium.org
parent eaf0a17db8
commit 3815720cf3
5 changed files with 364 additions and 5 deletions

View File

@ -113,9 +113,7 @@
#include <openssl/dsa.h> #include <openssl/dsa.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/pem.h> #include <openssl/pem.h>
/* #include <openssl/pkcs7.h>
* #include <openssl/pkcs7.h>
*/
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/x509.h> #include <openssl/x509.h>
@ -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_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(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 * We treat RSA or DSA private keys as a special case. For private keys we

View File

@ -493,7 +493,6 @@ static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) {
EXPECT_EQ(0u, CBS_len(&pkcs7)); EXPECT_EQ(0u, CBS_len(&pkcs7));
ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs2.get())); ASSERT_EQ(sk_X509_num(certs.get()), sk_X509_num(certs2.get()));
for (size_t i = 0; i < sk_X509_num(certs.get()); i++) { for (size_t i = 0; i < sk_X509_num(certs.get()); i++) {
X509 *a = sk_X509_value(certs.get(), i); X509 *a = sk_X509_value(certs.get(), i);
X509 *b = sk_X509_value(certs2.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<uint8_t> free_result2_data(result2_data); bssl::UniquePtr<uint8_t> free_result2_data(result2_data);
EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len)); 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> 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<uint8_t> 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<uint8_t> 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) { 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)); EXPECT_EQ(0u, CBS_len(&pkcs7));
ASSERT_EQ(sk_X509_CRL_num(crls.get()), sk_X509_CRL_num(crls.get())); 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++) { 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 *a = sk_X509_CRL_value(crls.get(), i);
X509_CRL *b = sk_X509_CRL_value(crls2.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<uint8_t> free_result2_data(result2_data); bssl::UniquePtr<uint8_t> free_result2_data(result2_data);
EXPECT_EQ(Bytes(result_data, result_len), Bytes(result2_data, result2_len)); 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> 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) { static void TestPEMCerts(const char *pem) {

View File

@ -26,6 +26,7 @@
#include <openssl/x509.h> #include <openssl/x509.h>
#include "internal.h" #include "internal.h"
#include "../internal.h"
int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) { 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) { int PKCS7_bundle_CRLs(CBB *out, const STACK_OF(X509_CRL) *crls) {
return pkcs7_bundle(out, pkcs7_bundle_crls_cb, 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, &copy) ||
!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(&copy2) - CBS_len(cbs);
ret->ber_bytes = BUF_memdup(CBS_data(&copy2), 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;
}

View File

@ -63,6 +63,7 @@
#include <openssl/digest.h> #include <openssl/digest.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/stack.h> #include <openssl/stack.h>
#include <openssl/pkcs7.h>
#include <openssl/x509.h> #include <openssl/x509.h>
/* For compatibility with open-iscsi, which assumes that it can get /* 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(X509_CRL, X509_CRL)
DECLARE_PEM_rw(PKCS7, PKCS7)
DECLARE_PEM_rw(PKCS8, X509_SIG) DECLARE_PEM_rw(PKCS8, X509_SIG)
DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO) DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO)

View File

@ -82,8 +82,129 @@ OPENSSL_EXPORT int PKCS7_get_PEM_CRLs(STACK_OF(X509_CRL) *out_crls,
BIO *pem_bio); 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) #if defined(__cplusplus)
} // extern C } // extern C
extern "C++" {
namespace bssl {
BORINGSSL_MAKE_DELETER(PKCS7, PKCS7_free)
} // namespace bssl
} // extern C++
#endif #endif
#define PKCS7_R_BAD_PKCS7_VERSION 100 #define PKCS7_R_BAD_PKCS7_VERSION 100