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 <agl@google.com>
diff --git a/crypto/pkcs8/pkcs12_test.c b/crypto/pkcs8/pkcs12_test.c
index 6aea1eb..2292b77 100644
--- a/crypto/pkcs8/pkcs12_test.c
+++ b/crypto/pkcs8/pkcs12_test.c
@@ -705,13 +705,56 @@
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 915767e..58e400d 100644
--- a/crypto/pkcs8/pkcs8.c
+++ b/crypto/pkcs8/pkcs8.c
@@ -55,8 +55,12 @@
#include <openssl/pkcs8.h>
+#include <assert.h>
+#include <limits.h>
+
#include <openssl/asn1.h>
#include <openssl/bn.h>
+#include <openssl/buf.h>
#include <openssl/cipher.h>
#include <openssl/digest.h>
#include <openssl/err.h>
@@ -64,8 +68,6 @@
#include <openssl/mem.h>
#include <openssl/x509.h>
-#include <limits.h>
-
#include "../bytestring/internal.h"
#include "../evp/internal.h"
@@ -879,6 +881,7 @@
/* 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 @@
}
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 079b1c4..52cb1e9 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -192,6 +192,7 @@
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 26f15e7..8735387 100644
--- a/include/openssl/pkcs8.h
+++ b/include/openssl/pkcs8.h
@@ -59,6 +59,8 @@
#include <openssl/base.h>
+#include <stdio.h>
+
#include <openssl/x509.h>
#if defined(__cplusplus)
@@ -129,9 +131,42 @@
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 10ff630..d35ba0b 100644
--- a/tool/pkcs12.cc
+++ b/tool/pkcs12.cc
@@ -40,7 +40,7 @@
},
};
-bool PKCS12(const std::vector<std::string> &args) {
+bool DoPKCS12(const std::vector<std::string> &args) {
std::map<std::string, std::string> args_map;
if (!ParseKeyValueArguments(&args_map, args, kArguments) ||
diff --git a/tool/tool.cc b/tool/tool.cc
index bf79a47..a0866d7 100644
--- a/tool/tool.cc
+++ b/tool/tool.cc
@@ -20,7 +20,7 @@
bool Client(const std::vector<std::string> &args);
-bool PKCS12(const std::vector<std::string> &args);
+bool DoPKCS12(const std::vector<std::string> &args);
bool Speed(const std::vector<std::string> &args);
static void usage(const char *name) {
@@ -45,7 +45,7 @@
} 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;