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>
diff --git a/crypto/pem/pem_all.c b/crypto/pem/pem_all.c
index e94ff26..6b40883 100644
--- a/crypto/pem/pem_all.c
+++ b/crypto/pem/pem_all.c
@@ -113,9 +113,7 @@
 #include <openssl/dsa.h>
 #include <openssl/evp.h>
 #include <openssl/pem.h>
-/*
- * #include <openssl/pkcs7.h>
- */
+#include <openssl/pkcs7.h>
 #include <openssl/rsa.h>
 #include <openssl/x509.h>
 
@@ -127,6 +125,7 @@
 
 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 e146ede..1ac9af2 100644
--- a/crypto/pkcs7/pkcs7_test.cc
+++ b/crypto/pkcs7/pkcs7_test.cc
@@ -493,7 +493,6 @@
   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 @@
   bssl::UniquePtr<uint8_t> 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> 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) {
@@ -532,7 +575,6 @@
   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 @@
   bssl::UniquePtr<uint8_t> 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> 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 7f83405..a2a6b46 100644
--- a/crypto/pkcs7/pkcs7_x509.c
+++ b/crypto/pkcs7/pkcs7_x509.c
@@ -26,6 +26,7 @@
 #include <openssl/x509.h>
 
 #include "internal.h"
+#include "../internal.h"
 
 
 int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) {
@@ -227,3 +228,168 @@
 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, &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;
+}
diff --git a/include/openssl/pem.h b/include/openssl/pem.h
index 4868e12..a43ca0d 100644
--- a/include/openssl/pem.h
+++ b/include/openssl/pem.h
@@ -63,6 +63,7 @@
 #include <openssl/digest.h>
 #include <openssl/evp.h>
 #include <openssl/stack.h>
+#include <openssl/pkcs7.h>
 #include <openssl/x509.h>
 
 /* For compatibility with open-iscsi, which assumes that it can get
@@ -329,6 +330,7 @@
 
 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 71a74c8..52b649c 100644
--- a/include/openssl/pkcs7.h
+++ b/include/openssl/pkcs7.h
@@ -82,8 +82,129 @@
                                       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