diff options
author | Adam Langley <agl@chromium.org> | 2014-08-21 10:54:06 -0700 |
---|---|---|
committer | Adam Langley <agl@google.com> | 2014-08-25 21:38:08 +0000 |
commit | 9c01e00c2e5bb8087c27203c0adccd9738beb64d (patch) | |
tree | 8fb4501a8b05d649506580db138a302713955aa2 | |
parent | cc8fcf45bbf37cbee85d4b8dac9d9814f6831961 (diff) | |
download | src-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.txt | 1 | ||||
-rw-r--r-- | crypto/bytestring/ber.c | 226 | ||||
-rw-r--r-- | crypto/bytestring/bytestring_test.c | 142 | ||||
-rw-r--r-- | crypto/bytestring/cbs.c | 110 | ||||
-rw-r--r-- | crypto/bytestring/internal.h | 55 | ||||
-rw-r--r-- | crypto/x509/pkcs7.c | 71 | ||||
-rw-r--r-- | include/openssl/bytestring.h | 16 |
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. * |