summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Langley <agl@chromium.org>2014-08-21 10:54:06 -0700
committerAdam Langley <agl@google.com>2014-08-25 21:38:08 +0000
commit9c01e00c2e5bb8087c27203c0adccd9738beb64d (patch)
tree8fb4501a8b05d649506580db138a302713955aa2
parentcc8fcf45bbf37cbee85d4b8dac9d9814f6831961 (diff)
downloadsrc-9c01e00c2e5bb8087c27203c0adccd9738beb64d.tar.gz
Rework support for ASN.1 BER.
Previously, the ASN.1 functions in bytestring were capable of processing indefinite length elements when the _ber functions were used. That works well enough for PKCS#3, but NSS goes a bit crazy with BER encoding and PKCS#12. Rather than complicate the core bytestring functions further, the BER support is removed from them and moved to a separate function that converts from BER to DER (if needed). Change-Id: I2212b28e99bab9fab8c61f80d2012d3e5a3cc2f0 Reviewed-on: https://boringssl-review.googlesource.com/1591 Reviewed-by: Adam Langley <agl@google.com>
-rw-r--r--crypto/bytestring/CMakeLists.txt1
-rw-r--r--crypto/bytestring/ber.c226
-rw-r--r--crypto/bytestring/bytestring_test.c142
-rw-r--r--crypto/bytestring/cbs.c110
-rw-r--r--crypto/bytestring/internal.h55
-rw-r--r--crypto/x509/pkcs7.c71
-rw-r--r--include/openssl/bytestring.h16
7 files changed, 463 insertions, 158 deletions
diff --git a/crypto/bytestring/CMakeLists.txt b/crypto/bytestring/CMakeLists.txt
index 409a0ce..dc48583 100644
--- a/crypto/bytestring/CMakeLists.txt
+++ b/crypto/bytestring/CMakeLists.txt
@@ -5,6 +5,7 @@ add_library(
OBJECT
+ ber.c
cbs.c
cbb.c
)
diff --git a/crypto/bytestring/ber.c b/crypto/bytestring/ber.c
new file mode 100644
index 0000000..8be77e0
--- /dev/null
+++ b/crypto/bytestring/ber.c
@@ -0,0 +1,226 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/bytestring.h>
+
+#include "internal.h"
+
+
+/* kMaxDepth is a just a sanity limit. The code should be such that the length
+ * of the input being processes always decreases. None the less, a very large
+ * input could otherwise cause the stack to overflow. */
+static const unsigned kMaxDepth = 2048;
+
+/* cbs_find_ber walks an ASN.1 structure in |orig_in| and sets |*ber_found|
+ * depending on whether an indefinite length element was found. The value of
+ * |in| is not changed. It returns one on success (i.e. |*ber_found| was set)
+ * and zero on error. */
+static int cbs_find_ber(CBS *orig_in, char *ber_found, unsigned depth) {
+ CBS in;
+
+ if (depth > kMaxDepth) {
+ return 0;
+ }
+
+ CBS_init(&in, CBS_data(orig_in), CBS_len(orig_in));
+ *ber_found = 0;
+
+ while (CBS_len(&in) > 0) {
+ CBS contents;
+ unsigned tag;
+ size_t header_len;
+
+ if (!CBS_get_any_asn1_element(&in, &contents, &tag, &header_len)) {
+ return 0;
+ }
+ if (CBS_len(&contents) == header_len &&
+ header_len > 0 &&
+ CBS_data(&contents)[header_len-1] == 0x80) {
+ *ber_found = 1;
+ return 1;
+ }
+ if (tag & CBS_ASN1_CONSTRUCTED) {
+ if (!CBS_skip(&contents, header_len) ||
+ !cbs_find_ber(&contents, ber_found, depth + 1)) {
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+/* is_primitive_type returns true if |tag| likely a primitive type. Normally
+ * one can just test the "constructed" bit in the tag but, in BER, even
+ * primitive tags can have the constructed bit if they have indefinite
+ * length. */
+static char is_primitive_type(unsigned tag) {
+ return (tag & 0xc0) == 0 &&
+ (tag & 0x1f) != (CBS_ASN1_SEQUENCE & 0x1f) &&
+ (tag & 0x1f) != (CBS_ASN1_SET & 0x1f);
+}
+
+/* is_eoc returns true if |header_len| and |contents|, as returned by
+ * |CBS_get_any_asn1_element|, indicate an "end of contents" (EOC) value. */
+static char is_eoc(size_t header_len, CBS *contents) {
+ return header_len == 2 && CBS_len(contents) == 2 &&
+ memcmp(CBS_data(contents), "\x00\x00", 2) == 0;
+}
+
+/* cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If
+ * |squash_header| is set then the top-level of elements from |in| will not
+ * have their headers written. This is used when concatenating the fragments of
+ * an indefinite length, primitive value. If |looking_for_eoc| is set then any
+ * EOC elements found will cause the function to return after consuming it.
+ * It returns one on success and zero on error. */
+static int cbs_convert_ber(CBS *in, CBB *out, char squash_header,
+ char looking_for_eoc, unsigned depth) {
+ if (depth > kMaxDepth) {
+ return 0;
+ }
+
+ while (CBS_len(in) > 0) {
+ CBS contents;
+ unsigned tag;
+ size_t header_len;
+ CBB *out_contents, out_contents_storage;
+
+ if (!CBS_get_any_asn1_element(in, &contents, &tag, &header_len)) {
+ return 0;
+ }
+ out_contents = out;
+
+ if (CBS_len(&contents) == header_len) {
+ if (is_eoc(header_len, &contents)) {
+ return looking_for_eoc;
+ }
+
+ if (header_len > 0 && CBS_data(&contents)[header_len - 1] == 0x80) {
+ CBB out_context_specific;
+
+ /* This is an indefinite length element. If it's a SEQUENCE or SET then
+ * we just need to write the out the contents as normal, but with a
+ * concrete length prefix.
+ *
+ * If it's a something else then the contents will be a series of BER
+ * elements of the same type which need to be concatenated. */
+ const char context_specific = (tag & 0xc0) == 0x80;
+ const char simple_type = is_primitive_type(tag);
+
+ if (!squash_header) {
+ unsigned out_tag = tag;
+ if (simple_type) {
+ out_tag &= ~CBS_ASN1_CONSTRUCTED;
+ }
+ if (!CBB_add_asn1(out, &out_contents_storage, out_tag)) {
+ return 0;
+ }
+ out_contents = &out_contents_storage;
+ }
+
+ /* If context specific then we peek at the inner tag and replicate it.
+ * This is because NSS produces odd-seeming BER structures where an
+ * indefinite length explicit tag doesn't have the expected actual tag
+ * inside it. */
+ if (context_specific) {
+ CBS in_copy, contents;
+ unsigned tag;
+ size_t header_len;
+
+ CBS_init(&in_copy, CBS_data(in), CBS_len(in));
+ if (!CBS_get_any_asn1_element(&in_copy, &contents, &tag, &header_len)) {
+ return 0;
+ }
+ if (is_eoc(header_len, &contents)) {
+ /* The indefinite-length value is empty. Unread the EOC and
+ * continue. */
+ CBS_init(in, CBS_data(&in_copy), CBS_len(&in_copy));
+ continue;
+ }
+ if (is_primitive_type(tag)) {
+ tag &= 0x1f;
+ }
+ if (!CBB_add_asn1(out_contents, &out_context_specific, tag)) {
+ return 0;
+ }
+ out_contents = &out_context_specific;
+ }
+
+ if (!cbs_convert_ber(in, out_contents,
+ context_specific || simple_type,
+ 1 /* looking for eoc */, depth + 1)) {
+ return 0;
+ }
+ if (out_contents != out && !CBB_flush(out)) {
+ return 0;
+ }
+ continue;
+ }
+ }
+
+ if (!squash_header) {
+ if (!CBB_add_asn1(out, &out_contents_storage, tag)) {
+ return 0;
+ }
+ out_contents = &out_contents_storage;
+ }
+
+ if (!CBS_skip(&contents, header_len)) {
+ return 0;
+ }
+
+ if (tag & CBS_ASN1_CONSTRUCTED) {
+ if (!cbs_convert_ber(&contents, out_contents, 0 /* don't squash header */,
+ 0 /* not looking for eoc */, depth + 1)) {
+ return 0;
+ }
+ } else {
+ if (!CBB_add_bytes(out_contents, CBS_data(&contents),
+ CBS_len(&contents))) {
+ return 0;
+ }
+ }
+
+ if (out_contents != out && !CBB_flush(out)) {
+ return 0;
+ }
+ }
+
+ return looking_for_eoc == 0;
+}
+
+int CBS_asn1_ber_to_der(CBS *in, uint8_t **out, size_t *out_len) {
+ CBB cbb;
+
+ /* First, do a quick walk to find any indefinite-length elements. Most of the
+ * time we hope that there aren't any and thus we can quickly return. */
+ char conversion_needed;
+ if (!cbs_find_ber(in, &conversion_needed, 0)) {
+ return 0;
+ }
+
+ if (!conversion_needed) {
+ *out = NULL;
+ *out_len = 0;
+ return 1;
+ }
+
+ CBB_init(&cbb, CBS_len(in));
+ if (!cbs_convert_ber(in, &cbb, 0, 0, 0)) {
+ CBB_cleanup(&cbb);
+ return 0;
+ }
+
+ return CBB_finish(&cbb, out, out_len);
+}
diff --git a/crypto/bytestring/bytestring_test.c b/crypto/bytestring/bytestring_test.c
index 29d26e8..5ea9d48 100644
--- a/crypto/bytestring/bytestring_test.c
+++ b/crypto/bytestring/bytestring_test.c
@@ -17,6 +17,8 @@
#include <openssl/bytestring.h>
+#include "internal.h"
+
static int test_skip(void) {
static const uint8_t kData[] = {1, 2, 3};
@@ -145,61 +147,6 @@ static int test_get_asn1(void) {
return 1;
}
-static int test_get_indef(void) {
- static const uint8_t kData1[] = {0x30, 0x80, 0x00, 0x00};
- static const uint8_t kDataWithoutEOC[] = {0x30, 0x80, 0x01, 0x00};
- static const uint8_t kDataWithBadInternalLength[] = {0x30, 0x80, 0x01, 0x01};
- static const uint8_t kDataNested[] = {0x30, 0x80, 0x30, 0x80, 0x30, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
- static const uint8_t kDataPrimitive[] = {0x02, 0x80, 0x00, 0x00};
-
- CBS data, contents;
- CBS_init(&data, kData1, sizeof(kData1));
- if (CBS_get_asn1(&data, &contents, 0x30)) {
- /* Indefinite lengths should not be supported in DER mode. */
- fprintf(stderr, "Indefinite length parsed by CBS_get_asn1.\n");
- return 0;
- }
-
- if (!CBS_get_asn1_ber(&data, &contents, 0x30) ||
- CBS_len(&contents) != 0 ||
- CBS_len(&data) != 0) {
- fprintf(stderr, "Simple indefinite length failed.\n");
- return 0;
- }
-
- CBS_init(&data, kDataWithoutEOC, sizeof(kDataWithoutEOC));
- if (CBS_get_asn1_ber(&data, &contents, 0x30)) {
- fprintf(stderr, "Parsed without EOC.\n");
- return 0;
- }
-
- CBS_init(&data, kDataWithBadInternalLength,
- sizeof(kDataWithBadInternalLength));
- if (CBS_get_asn1_ber(&data, &contents, 0x30)) {
- fprintf(stderr, "Parsed with internal length.\n");
- return 0;
- }
-
- CBS_init(&data, kDataNested, sizeof(kDataNested));
- if (!CBS_get_asn1_ber(&data, &contents, 0x30) ||
- CBS_len(&contents) != 8 ||
- CBS_len(&data) != 0) {
- fprintf(stderr, "Nested indefinite lengths failed.\n");
- return 0;
- }
-
- CBS_init(&data, kDataPrimitive, sizeof(kDataPrimitive));
- if (CBS_get_asn1_ber(&data, &contents, 0x02)) {
- /* Indefinite lengths should not be supported for non-constructed
- * elements. */
- fprintf(stderr, "Parsed non-constructed element with indefinite length\n");
- return 0;
- }
-
- return 1;
-}
-
static int test_cbb_basic(void) {
static const uint8_t kExpected[] = {1, 2, 3, 4, 5, 6, 7, 8};
uint8_t *buf;
@@ -405,19 +352,100 @@ static int test_cbb_asn1(void) {
return 1;
}
+static int do_ber_convert(const char *name,
+ const uint8_t *der_expected, size_t der_len,
+ const uint8_t *ber, size_t ber_len) {
+ CBS in;
+ uint8_t *out;
+ size_t out_len;
+
+ CBS_init(&in, ber, ber_len);
+ if (!CBS_asn1_ber_to_der(&in, &out, &out_len)) {
+ fprintf(stderr, "%s: CBS_asn1_ber_to_der failed.\n", name);
+ return 0;
+ }
+
+ if (out == NULL) {
+ if (ber_len != der_len ||
+ memcmp(der_expected, ber, ber_len) != 0) {
+ fprintf(stderr, "%s: incorrect unconverted result.\n", name);
+ return 0;
+ }
+
+ return 1;
+ }
+
+ if (out_len != der_len ||
+ memcmp(out, der_expected, der_len) != 0) {
+ fprintf(stderr, "%s: incorrect converted result.\n", name);
+ return 0;
+ }
+
+ free(out);
+ return 1;
+}
+
+static int test_ber_convert(void) {
+ static const uint8_t kSimpleBER[] = {0x01, 0x01, 0x00};
+
+ /* kIndefBER contains a SEQUENCE with an indefinite length. */
+ static const uint8_t kIndefBER[] = {0x30, 0x80, 0x01, 0x01, 0x02, 0x00, 0x00};
+ static const uint8_t kIndefDER[] = {0x30, 0x03, 0x01, 0x01, 0x02};
+
+ /* kOctetStringBER contains an indefinite length OCTETSTRING with two parts.
+ * These parts need to be concatenated in DER form. */
+ static const uint8_t kOctetStringBER[] = {0x24, 0x80, 0x04, 0x02, 0, 1,
+ 0x04, 0x02, 2, 3, 0x00, 0x00};
+ static const uint8_t kOctetStringDER[] = {0x04, 0x04, 0, 1, 2, 3};
+
+ /* kNSSBER is part of a PKCS#12 message generated by NSS that uses indefinite
+ * length elements extensively. */
+ static const uint8_t kNSSBER[] = {
+ 0x30, 0x80, 0x02, 0x01, 0x03, 0x30, 0x80, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x80, 0x24, 0x80, 0x04, 0x04,
+ 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39,
+ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05,
+ 0x00, 0x04, 0x14, 0x84, 0x98, 0xfc, 0x66, 0x33, 0xee, 0xba, 0xe7, 0x90,
+ 0xc1, 0xb6, 0xe8, 0x8f, 0xfe, 0x1d, 0xc5, 0xa5, 0x97, 0x93, 0x3e, 0x04,
+ 0x10, 0x38, 0x62, 0xc6, 0x44, 0x12, 0xd5, 0x30, 0x00, 0xf8, 0xf2, 0x1b,
+ 0xf0, 0x6e, 0x10, 0x9b, 0xb8, 0x02, 0x02, 0x07, 0xd0, 0x00, 0x00,
+ };
+
+ static const uint8_t kNSSDER[] = {
+ 0x30, 0x53, 0x02, 0x01, 0x03, 0x30, 0x13, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x06, 0x04, 0x04,
+ 0x01, 0x02, 0x03, 0x04, 0x30, 0x39, 0x30, 0x21, 0x30, 0x09, 0x06,
+ 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, 0x84,
+ 0x98, 0xfc, 0x66, 0x33, 0xee, 0xba, 0xe7, 0x90, 0xc1, 0xb6, 0xe8,
+ 0x8f, 0xfe, 0x1d, 0xc5, 0xa5, 0x97, 0x93, 0x3e, 0x04, 0x10, 0x38,
+ 0x62, 0xc6, 0x44, 0x12, 0xd5, 0x30, 0x00, 0xf8, 0xf2, 0x1b, 0xf0,
+ 0x6e, 0x10, 0x9b, 0xb8, 0x02, 0x02, 0x07, 0xd0,
+ };
+
+ return do_ber_convert("kSimpleBER", kSimpleBER, sizeof(kSimpleBER),
+ kSimpleBER, sizeof(kSimpleBER)) &&
+ do_ber_convert("kIndefBER", kIndefDER, sizeof(kIndefDER), kIndefBER,
+ sizeof(kIndefBER)) &&
+ do_ber_convert("kOctetStringBER", kOctetStringDER,
+ sizeof(kOctetStringDER), kOctetStringBER,
+ sizeof(kOctetStringBER)) &&
+ do_ber_convert("kNSSBER", kNSSDER, sizeof(kNSSDER), kNSSBER,
+ sizeof(kNSSBER));
+}
+
int main(void) {
if (!test_skip() ||
!test_get_u() ||
!test_get_prefixed() ||
!test_get_prefixed_bad() ||
!test_get_asn1() ||
- !test_get_indef() ||
!test_cbb_basic() ||
!test_cbb_fixed() ||
!test_cbb_finish_child() ||
!test_cbb_misuse() ||
!test_cbb_prefixed() ||
- !test_cbb_asn1()) {
+ !test_cbb_asn1() ||
+ !test_ber_convert()) {
return 1;
}
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index 547b5a4..d6e9442 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -19,6 +19,8 @@
#include <assert.h>
#include <string.h>
+#include "internal.h"
+
void CBS_init(CBS *cbs, const uint8_t *data, size_t len) {
cbs->data = data;
@@ -156,50 +158,8 @@ int CBS_get_u24_length_prefixed(CBS *cbs, CBS *out) {
return cbs_get_length_prefixed(cbs, out, 3);
}
-static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
- size_t *out_header_len, unsigned depth,
- int *was_indefinite_len);
-
-/* cbs_get_asn1_indefinite_len sets |*out| to be a CBS that covers an
- * indefinite length element in |cbs| and advances |*in|. On entry, |cbs| will
- * not have had the tag and length byte removed. On exit, |*out| does not cover
- * the EOC element, but |*in| is skipped over it.
- *
- * The |depth| argument counts the number of times the code has recursed trying
- * to find an indefinite length. */
-static int cbs_get_asn1_indefinite_len(CBS *in, CBS *out, unsigned depth) {
- static const size_t kEOCLength = 2;
- size_t header_len;
- unsigned tag;
- int was_indefinite_len;
- CBS orig = *in, child;
-
- if (!CBS_skip(in, 2 /* tag plus 0x80 byte for indefinite len */)) {
- return 0;
- }
-
- for (;;) {
- if (!cbs_get_asn1_element(in, &child, &tag, &header_len, depth + 1,
- &was_indefinite_len)) {
- return 0;
- }
-
- if (!was_indefinite_len && CBS_len(&child) == kEOCLength &&
- header_len == kEOCLength && tag == 0) {
- break;
- }
- }
-
- return CBS_get_bytes(&orig, out, CBS_len(&orig) - CBS_len(in) - kEOCLength);
-}
-
-/* MAX_DEPTH the maximum number of levels of indefinite lengths that we'll
- * support. */
-#define MAX_DEPTH 64
-
-static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
- size_t *out_header_len, unsigned depth,
- int *was_indefinite_len) {
+int CBS_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
+ size_t *out_header_len) {
uint8_t tag, length_byte;
CBS header = *cbs;
if (!CBS_get_u8(&header, &tag) ||
@@ -213,9 +173,6 @@ static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
}
*out_tag = tag;
- if (was_indefinite_len) {
- *was_indefinite_len = 0;
- }
size_t len;
if ((length_byte & 0x80) == 0) {
@@ -227,14 +184,10 @@ static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
const size_t num_bytes = length_byte & 0x7f;
uint32_t len32;
- if ((tag & CBS_ASN1_CONSTRUCTED) != 0 && depth < MAX_DEPTH &&
- num_bytes == 0) {
+ if ((tag & CBS_ASN1_CONSTRUCTED) != 0 && num_bytes == 0) {
/* indefinite length */
*out_header_len = 2;
- if (was_indefinite_len) {
- *was_indefinite_len = 1;
- }
- return cbs_get_asn1_indefinite_len(cbs, out, depth);
+ return CBS_get_bytes(cbs, out, 2);
}
if (num_bytes == 0 || num_bytes > 4) {
@@ -263,7 +216,7 @@ static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
return CBS_get_bytes(cbs, out, len);
}
-static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value, int ber,
+static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value,
int skip_header) {
size_t header_len;
unsigned tag;
@@ -273,9 +226,13 @@ static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value, int ber,
out = &throwaway;
}
- if (!cbs_get_asn1_element(cbs, out, &tag, &header_len, ber ? 0 : MAX_DEPTH,
- NULL) ||
- tag != tag_value) {
+ if (!CBS_get_any_asn1_element(cbs, out, &tag, &header_len) ||
+ tag != tag_value ||
+ (header_len > 0 &&
+ /* This ensures that the tag is either zero length or
+ * indefinite-length. */
+ CBS_len(out) == header_len &&
+ CBS_data(out)[header_len - 1] == 0x80)) {
return 0;
}
@@ -288,16 +245,39 @@ static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value, int ber,
}
int CBS_get_asn1(CBS *cbs, CBS *out, unsigned tag_value) {
- return cbs_get_asn1(cbs, out, tag_value, 0 /* DER */,
- 1 /* skip header */);
+ return cbs_get_asn1(cbs, out, tag_value, 1 /* skip header */);
}
-int CBS_get_asn1_ber(CBS *cbs, CBS *out, unsigned tag_value) {
- return cbs_get_asn1(cbs, out, tag_value, 1 /* BER */,
- 1 /* skip header */);
+int CBS_get_asn1_element(CBS *cbs, CBS *out, unsigned tag_value) {
+ return cbs_get_asn1(cbs, out, tag_value, 0 /* include header */);
}
-int CBS_get_asn1_element(CBS *cbs, CBS *out, unsigned tag_value) {
- return cbs_get_asn1(cbs, out, tag_value, 0 /* DER */,
- 0 /* include header */);
+int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out) {
+ CBS bytes;
+ const uint8_t *data;
+ size_t i, len;
+
+ if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_INTEGER)) {
+ return 0;
+ }
+
+ *out = 0;
+ data = CBS_data(&bytes);
+ len = CBS_len(&bytes);
+
+ if (len > 0 && (data[0] & 0x80) != 0) {
+ /* negative number */
+ return 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ if ((*out >> 56) != 0) {
+ /* Too large to represent as a uint64_t. */
+ return 0;
+ }
+ *out <<= 8;
+ *out |= data[i];
+ }
+
+ return 1;
}
diff --git a/crypto/bytestring/internal.h b/crypto/bytestring/internal.h
new file mode 100644
index 0000000..42c1a39
--- /dev/null
+++ b/crypto/bytestring/internal.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef OPENSSL_HEADER_BYTESTRING_INTERNAL_H
+#define OPENSSL_HEADER_BYTESTRING_INTERNAL_H
+
+#include <openssl/base.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+/* CBS_get_any_asn1_element sets |*out| to contain the next ASN.1 element from
+ * |*cbs| (including header bytes) and advances |*cbs|. It sets |*out_tag| to
+ * the tag number and |*out_header_len| to the length of the ASN.1 header. If
+ * the element has indefinite length then |*out| will only contain the header.
+ *
+ * Tag numbers greater than 31 are not supported. */
+int CBS_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
+ size_t *out_header_len);
+
+/* CBS_asn1_ber_to_der reads an ASN.1 structure from |in|. If it finds
+ * indefinite-length elements then it attempts to convert the BER data to DER
+ * and sets |*out| and |*out_length| to describe a malloced buffer containing
+ * the DER data. Additionally, |*in| will be advanced over the ASN.1 data.
+ *
+ * If it doesn't find any indefinite-length elements then it sets |*out| to
+ * NULL and |*in| is unmodified.
+ *
+ * A sufficiently complex ASN.1 structure will break this function because it's
+ * not possible to generically convert BER to DER without knowledge of the
+ * structure itself. However, this sufficies to handle the PKCS#7 and #12 output
+ * from NSS.
+ *
+ * It returns one on success and zero otherwise. */
+int CBS_asn1_ber_to_der(CBS *in, uint8_t **out, size_t *out_len);
+
+
+#if defined(__cplusplus)
+} /* extern C */
+#endif
+
+#endif /* OPENSSL_HEADER_BYTESTRING_INTERNAL_H */
diff --git a/crypto/x509/pkcs7.c b/crypto/x509/pkcs7.c
index 7744fcc..75c101b 100644
--- a/crypto/x509/pkcs7.c
+++ b/crypto/x509/pkcs7.c
@@ -19,48 +19,61 @@
#include <openssl/obj.h>
#include <openssl/stack.h>
+#include "../bytestring/internal.h"
+
int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) {
- CBS content_info, content_type, wrapped_signed_data, signed_data,
- version_bytes, certificates;
- int nid;
+ uint8_t *der_bytes = NULL;
+ size_t der_len;
+ CBS in, content_info, content_type, wrapped_signed_data, signed_data,
+ certificates;
const size_t initial_certs_len = sk_X509_num(out_certs);
+ uint64_t version;
+ int ret = 0;
+
+ /* The input may be in BER format. */
+ if (!CBS_asn1_ber_to_der(cbs, &der_bytes, &der_len)) {
+ return 0;
+ }
+ if (der_bytes != NULL) {
+ CBS_init(&in, der_bytes, der_len);
+ } else {
+ CBS_init(&in, CBS_data(cbs), CBS_len(cbs));
+ }
/* See https://tools.ietf.org/html/rfc2315#section-7 */
- if (!CBS_get_asn1_ber(cbs, &content_info, CBS_ASN1_SEQUENCE) ||
+ if (!CBS_get_asn1(&in, &content_info, CBS_ASN1_SEQUENCE) ||
!CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT)) {
- return 0;
+ goto err;
}
- nid = OBJ_cbs2nid(&content_type);
- if (nid != NID_pkcs7_signed) {
+ if (OBJ_cbs2nid(&content_type) != NID_pkcs7_signed) {
OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
X509_R_NOT_PKCS7_SIGNED_DATA);
- return 0;
+ goto err;
}
/* See https://tools.ietf.org/html/rfc2315#section-9.1 */
- if (!CBS_get_asn1_ber(&content_info, &wrapped_signed_data,
- CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
- !CBS_get_asn1_ber(&wrapped_signed_data, &signed_data,
- CBS_ASN1_SEQUENCE) ||
- !CBS_get_asn1_ber(&signed_data, &version_bytes, CBS_ASN1_INTEGER) ||
- !CBS_get_asn1_ber(&signed_data, NULL /* digests */, CBS_ASN1_SET) ||
- !CBS_get_asn1_ber(&signed_data, NULL /* content */, CBS_ASN1_SEQUENCE)) {
- return 0;
+ if (!CBS_get_asn1(&content_info, &wrapped_signed_data,
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
+ !CBS_get_asn1(&wrapped_signed_data, &signed_data, CBS_ASN1_SEQUENCE) ||
+ !CBS_get_asn1_uint64(&signed_data, &version) ||
+ !CBS_get_asn1(&signed_data, NULL /* digests */, CBS_ASN1_SET) ||
+ !CBS_get_asn1(&signed_data, NULL /* content */, CBS_ASN1_SEQUENCE)) {
+ goto err;
}
- if (CBS_len(&version_bytes) < 1 || CBS_data(&version_bytes)[0] == 0) {
+ if (version < 1) {
OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
X509_R_BAD_PKCS7_VERSION);
- return 0;
+ goto err;
}
- if (!CBS_get_asn1_ber(&signed_data, &certificates,
- CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
+ if (!CBS_get_asn1(&signed_data, &certificates,
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
X509_R_NO_CERTIFICATES_INCLUDED);
- return 0;
+ goto err;
}
while (CBS_len(&certificates) > 0) {
@@ -86,15 +99,21 @@ int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) {
sk_X509_push(out_certs, x509);
}
- return 1;
+ ret = 1;
err:
- while (sk_X509_num(out_certs) != initial_certs_len) {
- X509 *x509 = sk_X509_pop(out_certs);
- X509_free(x509);
+ if (der_bytes) {
+ OPENSSL_free(der_bytes);
+ }
+
+ if (!ret) {
+ while (sk_X509_num(out_certs) != initial_certs_len) {
+ X509 *x509 = sk_X509_pop(out_certs);
+ X509_free(x509);
+ }
}
- return 0;
+ return ret;
}
int PKCS7_bundle_certificates(CBB *out, const STACK_OF(X509) *certs) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 6c0e799..a7507d8 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -138,20 +138,16 @@ OPENSSL_EXPORT int CBS_get_u24_length_prefixed(CBS *cbs, CBS *out);
* Tag numbers greater than 31 are not supported. */
OPENSSL_EXPORT int CBS_get_asn1(CBS *cbs, CBS *out, unsigned tag_value);
-/* CBS_get_asn1_ber sets |*out| to the contents of BER-encoded, ASN.1 element
- * (not including tag and length bytes) and advances |cbs| over it. The ASN.1
- * element must match |tag_value|. It returns one on success and zero on error.
- *
- * The major difference between this function and |CBS_get_asn1| is that
- * indefinite-length elements may be processed by this function.
- *
- * Tag numbers greater than 31 are not supported. */
-OPENSSL_EXPORT int CBS_get_asn1_ber(CBS *cbs, CBS *out, unsigned tag_value);
-
/* CBS_get_asn1_element acts like |CBS_get_asn1| but |out| will include the
* ASN.1 header bytes too. */
OPENSSL_EXPORT int CBS_get_asn1_element(CBS *cbs, CBS *out, unsigned tag_value);
+/* CBS_get_asn1_uint64 gets an ASN.1 INTEGER from |cbs| using |CBS_get_asn1|
+ * and sets |*out| to its value. It returns one on success and zero on error,
+ * where error includes the integer being negative, or too large to represent
+ * in 64 bits. */
+OPENSSL_EXPORT int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out);
+
/* CRYPTO ByteBuilder.
*