From 5127db3b4d59066126a1f8c8f7fdf4841a8c58b9 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 19 Sep 2014 10:49:56 -0700 Subject: [PATCH] Provide compatibility functions for PKCS#12 parsing. In order to minimise the upstream diffs needed for bits of Android to build with BoringSSL, this change implements the old style PKCS#12 functions as wrappers around the modern parser. The function to read all the contents of a BIO could almost be a utility function but I'll wait until there are two uses for it first. The important change from the original functions is that these will always read the complete buffer/BIO/FILE passed in. Based on a survey of uses of d2i_PKCS12 that I found, this appears to be universally what callers want anyway. Change-Id: I3f5b84e710b161d975f91f4d16c83d44371368d1 Reviewed-on: https://boringssl-review.googlesource.com/1791 Reviewed-by: Adam Langley --- crypto/pkcs8/pkcs12_test.c | 45 +++++++++++- crypto/pkcs8/pkcs8.c | 139 ++++++++++++++++++++++++++++++++++++- include/openssl/base.h | 1 + include/openssl/pkcs8.h | 35 ++++++++++ tool/pkcs12.cc | 2 +- tool/tool.cc | 4 +- 6 files changed, 220 insertions(+), 6 deletions(-) diff --git a/crypto/pkcs8/pkcs12_test.c b/crypto/pkcs8/pkcs12_test.c index 6aea1ebc..2292b778 100644 --- a/crypto/pkcs8/pkcs12_test.c +++ b/crypto/pkcs8/pkcs12_test.c @@ -705,13 +705,56 @@ static int test(const char *name, const uint8_t *der, size_t der_len) { return 1; } +static int test_compat(const uint8_t *der, size_t der_len) { + PKCS12 *p12; + X509 *cert = NULL; + STACK_OF(X509) *ca_certs = NULL; + EVP_PKEY *key; + BIO *bio; + + bio = BIO_new_mem_buf((void*) der, der_len); + + p12 = d2i_PKCS12_bio(bio, NULL); + if (p12 == NULL) { + fprintf(stderr, "PKCS12_parse failed.\n"); + BIO_print_errors_fp(stderr); + return 0; + } + BIO_free(bio); + + if (!PKCS12_parse(p12, "foo", &key, &cert, &ca_certs)) { + fprintf(stderr, "PKCS12_parse failed.\n"); + BIO_print_errors_fp(stderr); + return 0; + } + + if (key == NULL || cert == NULL) { + fprintf(stderr, "Bad result from PKCS12_parse.\n"); + return 0; + } + + EVP_PKEY_free(key); + X509_free(cert); + + if (sk_X509_num(ca_certs) != 0) { + fprintf(stderr, "Bad result from PKCS12_parse.\n"); + return 0; + } + sk_X509_free(ca_certs); + + PKCS12_free(p12); + + return 1; +} + int main(int argc, char **argv) { CRYPTO_library_init(); ERR_load_crypto_strings(); if (!test("OpenSSL", kOpenSSL, sizeof(kOpenSSL)) || !test("NSS", kNSS, sizeof(kNSS)) || - !test("Windows", kWindows, sizeof(kWindows))) { + !test("Windows", kWindows, sizeof(kWindows)) || + !test_compat(kWindows, sizeof(kWindows))) { return 1; } diff --git a/crypto/pkcs8/pkcs8.c b/crypto/pkcs8/pkcs8.c index 915767e7..58e400dd 100644 --- a/crypto/pkcs8/pkcs8.c +++ b/crypto/pkcs8/pkcs8.c @@ -55,8 +55,12 @@ #include +#include +#include + #include #include +#include #include #include #include @@ -64,8 +68,6 @@ #include #include -#include - #include "../bytestring/internal.h" #include "../evp/internal.h" @@ -879,6 +881,7 @@ int PKCS12_get_key_and_certs(EVP_PKEY **out_key, STACK_OF(X509) *out_certs, /* See ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12v1.pdf, section * four. */ if (!CBS_get_asn1(&in, &pfx, CBS_ASN1_SEQUENCE) || + CBS_len(&in) != 0 || !CBS_get_asn1_uint64(&pfx, &version)) { OPENSSL_PUT_ERROR(PKCS8, PKCS12_parse, PKCS8_R_BAD_PKCS12_DATA); goto err; @@ -1017,3 +1020,135 @@ err: } void PKCS12_PBE_add(){}; + +struct pkcs12_st { + uint8_t *ber_bytes; + size_t ber_len; +}; + +PKCS12* d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes, size_t ber_len) { + PKCS12 *p12; + + /* out_p12 must be NULL because we don't export the PKCS12 structure. */ + assert(out_p12 == NULL); + + p12 = OPENSSL_malloc(sizeof(PKCS12)); + if (!p12) { + return NULL; + } + + p12->ber_bytes = OPENSSL_malloc(ber_len); + if (!p12->ber_bytes) { + OPENSSL_free(p12); + return NULL; + } + + memcpy(p12->ber_bytes, *ber_bytes, ber_len); + p12->ber_len = ber_len; + *ber_bytes += ber_len; + + return p12; +} + +PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12) { + size_t used = 0; + BUF_MEM *buf; + const uint8_t *dummy; + static const size_t kMaxSize = 256 * 1024; + PKCS12 *ret = NULL; + + buf = BUF_MEM_new(); + if (buf == NULL) { + return NULL; + } + if (BUF_MEM_grow(buf, 8192) == 0) { + goto out; + } + + for (;;) { + int n = BIO_read(bio, &buf->data[used], buf->length - used); + if (n < 0) { + goto out; + } + + if (n == 0) { + break; + } + used += n; + + if (used < buf->length) { + continue; + } + + if (buf->length > kMaxSize || + BUF_MEM_grow(buf, buf->length * 2) == 0) { + goto out; + } + } + + dummy = (uint8_t*) buf->data; + ret = d2i_PKCS12(out_p12, &dummy, used); + +out: + BUF_MEM_free(buf); + return ret; +} + +PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12) { + BIO *bio; + PKCS12 *ret; + + bio = BIO_new_fp(fp, 0 /* don't take ownership */); + if (!bio) { + return NULL; + } + + ret = d2i_PKCS12_bio(bio, out_p12); + BIO_free(bio); + return ret; +} + +int PKCS12_parse(const PKCS12 *p12, const char *password, EVP_PKEY **out_pkey, + X509 **out_cert, STACK_OF(X509) **out_ca_certs) { + CBS ber_bytes; + STACK_OF(X509) *ca_certs = NULL; + char ca_certs_alloced = 0; + + if (out_ca_certs != NULL && *out_ca_certs != NULL) { + ca_certs = *out_ca_certs; + } + + if (!ca_certs) { + ca_certs = sk_X509_new_null(); + if (ca_certs == NULL) { + return 0; + } + ca_certs_alloced = 1; + } + + CBS_init(&ber_bytes, p12->ber_bytes, p12->ber_len); + if (!PKCS12_get_key_and_certs(out_pkey, ca_certs, &ber_bytes, password)) { + if (ca_certs_alloced) { + sk_X509_free(ca_certs); + } + return 0; + } + + *out_cert = NULL; + if (sk_X509_num(ca_certs) > 0) { + *out_cert = sk_X509_shift(ca_certs); + } + + if (out_ca_certs) { + *out_ca_certs = ca_certs; + } else { + sk_X509_pop_free(ca_certs, X509_free); + } + + return 1; +} + +void PKCS12_free(PKCS12 *p12) { + OPENSSL_free(p12->ber_bytes); + OPENSSL_free(p12); +} diff --git a/include/openssl/base.h b/include/openssl/base.h index 079b1c4b..52cb1e94 100644 --- a/include/openssl/base.h +++ b/include/openssl/base.h @@ -192,6 +192,7 @@ typedef struct hmac_ctx_st HMAC_CTX; typedef struct md4_state_st MD4_CTX; typedef struct md5_state_st MD5_CTX; typedef struct pkcs8_priv_key_info_st PKCS8_PRIV_KEY_INFO; +typedef struct pkcs12_st PKCS12; typedef struct rand_meth_st RAND_METHOD; typedef struct rsa_meth_st RSA_METHOD; typedef struct rsa_st RSA; diff --git a/include/openssl/pkcs8.h b/include/openssl/pkcs8.h index 26f15e7a..8735387f 100644 --- a/include/openssl/pkcs8.h +++ b/include/openssl/pkcs8.h @@ -59,6 +59,8 @@ #include +#include + #include #if defined(__cplusplus) @@ -129,9 +131,42 @@ OPENSSL_EXPORT int PKCS12_get_key_and_certs(EVP_PKEY **out_key, STACK_OF(X509) *out_certs, CBS *in, const char *password); + +/* Deprecated functions. */ + /* PKCS12_PBE_add does nothing. It exists for compatibility with OpenSSL. */ OPENSSL_EXPORT void PKCS12_PBE_add(); +/* d2i_PKCS12 is a dummy function that copies |*ber_bytes| into a + * |PKCS12| structure. The |out_p12| argument must be NULL. On exit, + * |*ber_bytes| will be advanced by |ber_len|. It returns a fresh |PKCS12| + * structure or NULL on error. + * + * Note: unlike other d2i functions, |d2i_PKCS12| will always consume |ber_len| + * bytes.*/ +OPENSSL_EXPORT PKCS12 *d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes, + size_t ber_len); + +/* d2i_PKCS12_bio acts like |d2i_PKCS12| but reads from a |BIO|. */ +OPENSSL_EXPORT PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12); + +/* d2i_PKCS12_fp acts like |d2i_PKCS12| but reads from a |FILE|. */ +OPENSSL_EXPORT PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12); + +/* PKCS12_parse calls |PKCS12_get_key_and_certs| on the ASN.1 data stored in + * |p12|. The |out_pkey| and |out_cert| arguments must not be NULL and, on + * successful exit, the private key and first certificate will be stored in + * them. The |out_ca_certs| argument may be NULL but, if not, then any extra + * certificates will be appended to |*out_ca_certs|. If |*out_ca_certs| is NULL + * then it will be set to a freshly allocated stack containing the extra certs. + * + * It returns one on success and zero on error. */ +OPENSSL_EXPORT int PKCS12_parse(const PKCS12 *p12, const char *password, + EVP_PKEY **out_pkey, X509 **out_cert, + STACK_OF(X509) **out_ca_certs); + +/* PKCS12_free frees |p12| and its contents. */ +OPENSSL_EXPORT void PKCS12_free(PKCS12 *p12); #if defined(__cplusplus) } /* extern C */ diff --git a/tool/pkcs12.cc b/tool/pkcs12.cc index 10ff630f..d35ba0bc 100644 --- a/tool/pkcs12.cc +++ b/tool/pkcs12.cc @@ -40,7 +40,7 @@ static const struct argument kArguments[] = { }, }; -bool PKCS12(const std::vector &args) { +bool DoPKCS12(const std::vector &args) { std::map args_map; if (!ParseKeyValueArguments(&args_map, args, kArguments) || diff --git a/tool/tool.cc b/tool/tool.cc index bf79a47b..a0866d7c 100644 --- a/tool/tool.cc +++ b/tool/tool.cc @@ -20,7 +20,7 @@ bool Client(const std::vector &args); -bool PKCS12(const std::vector &args); +bool DoPKCS12(const std::vector &args); bool Speed(const std::vector &args); static void usage(const char *name) { @@ -45,7 +45,7 @@ int main(int argc, char **argv) { } else if (tool == "s_client" || tool == "client") { return !Client(args); } else if (tool == "pkcs12") { - return !PKCS12(args); + return !DoPKCS12(args); } else { usage(argv[0]); return 1;