diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/crypto/bio/bio.c | 140 | ||||
-rw-r--r-- | src/crypto/bio/bio_test.cc | 81 | ||||
-rw-r--r-- | src/include/openssl/bio.h | 15 |
3 files changed, 235 insertions, 1 deletions
diff --git a/src/crypto/bio/bio.c b/src/crypto/bio/bio.c index 48c1466..694a11c 100644 --- a/src/crypto/bio/bio.c +++ b/src/crypto/bio/bio.c @@ -56,6 +56,7 @@ #include <openssl/bio.h> +#include <assert.h> #include <errno.h> #include <limits.h> #include <string.h> @@ -459,3 +460,142 @@ static int print_bio(const char *str, size_t len, void *bio) { void BIO_print_errors(BIO *bio) { ERR_print_errors_cb(print_bio, bio); } + +/* bio_read_all reads everything from |bio| and prepends |prefix| to it. On + * success, |*out| is set to an allocated buffer (which should be freed with + * |OPENSSL_free|), |*out_len| is set to its length and one is returned. The + * buffer will contain |prefix| followed by the contents of |bio|. On failure, + * zero is returned. + * + * The function will fail if the size of the output would equal or exceed + * |max_len|. */ +static int bio_read_all(BIO *bio, uint8_t **out, size_t *out_len, + const uint8_t *prefix, size_t prefix_len, + size_t max_len) { + static const size_t kChunkSize = 4096; + + size_t len = prefix_len + kChunkSize; + if (len > max_len) { + len = max_len; + } + if (len < prefix_len) { + return 0; + } + *out = OPENSSL_malloc(len); + if (*out == NULL) { + return 0; + } + memcpy(*out, prefix, prefix_len); + size_t done = prefix_len; + + for (;;) { + if (done == len) { + OPENSSL_free(*out); + return 0; + } + const size_t todo = len - done; + assert(todo < INT_MAX); + const int n = BIO_read(bio, *out + done, todo); + if (n == 0) { + *out_len = done; + return 1; + } else if (n == -1) { + OPENSSL_free(*out); + return 0; + } + + done += n; + if (len < max_len && len - done < kChunkSize / 2) { + len += kChunkSize; + if (len > max_len) { + len = max_len; + } + uint8_t *new_buf = OPENSSL_realloc(*out, len); + if (new_buf == NULL) { + OPENSSL_free(*out); + return 0; + } + *out = new_buf; + } + } +} + +int BIO_read_asn1(BIO *bio, uint8_t **out, size_t *out_len, size_t max_len) { + uint8_t header[6]; + + static const size_t kInitialHeaderLen = 2; + if (BIO_read(bio, header, kInitialHeaderLen) != kInitialHeaderLen) { + return 0; + } + + const uint8_t tag = header[0]; + const uint8_t length_byte = header[1]; + + if ((tag & 0x1f) == 0x1f) { + /* Long form tags are not supported. */ + return 0; + } + + size_t len, header_len; + if ((length_byte & 0x80) == 0) { + /* Short form length. */ + len = length_byte; + header_len = kInitialHeaderLen; + } else { + const size_t num_bytes = length_byte & 0x7f; + + if ((tag & 0x20 /* constructed */) != 0 && num_bytes == 0) { + /* indefinite length. */ + return bio_read_all(bio, out, out_len, header, kInitialHeaderLen, + max_len); + } + + if (num_bytes == 0 || num_bytes > 4) { + return 0; + } + + if (BIO_read(bio, header + kInitialHeaderLen, num_bytes) != num_bytes) { + return 0; + } + header_len = kInitialHeaderLen + num_bytes; + + uint32_t len32 = 0; + unsigned i; + for (i = 0; i < num_bytes; i++) { + len32 <<= 8; + len32 |= header[kInitialHeaderLen + i]; + } + + if (len32 < 128) { + /* Length should have used short-form encoding. */ + return 0; + } + + if ((len32 >> ((num_bytes-1)*8)) == 0) { + /* Length should have been at least one byte shorter. */ + return 0; + } + + len = len32; + } + + if (len + header_len < len || + len + header_len > max_len) { + return 0; + } + len += header_len; + *out_len = len; + + *out = OPENSSL_malloc(len); + if (*out == NULL) { + return 0; + } + memcpy(*out, header, header_len); + if (BIO_read(bio, (*out) + header_len, len - header_len) != + len - header_len) { + OPENSSL_free(*out); + return 0; + } + + return 1; +} diff --git a/src/crypto/bio/bio_test.cc b/src/crypto/bio/bio_test.cc index 4c88df5..e0193f8 100644 --- a/src/crypto/bio/bio_test.cc +++ b/src/crypto/bio/bio_test.cc @@ -329,6 +329,84 @@ static bool TestPrintf() { return true; } +static bool ReadASN1(bool should_succeed, const uint8_t *data, size_t data_len, + size_t expected_len, size_t max_len) { + ScopedBIO bio(BIO_new_mem_buf(const_cast<uint8_t*>(data), data_len)); + + uint8_t *out; + size_t out_len; + int ok = BIO_read_asn1(bio.get(), &out, &out_len, max_len); + if (!ok) { + out = nullptr; + } + ScopedOpenSSLBytes out_storage(out); + + if (should_succeed != (ok == 1)) { + return false; + } + + if (should_succeed && + (out_len != expected_len || memcmp(data, out, expected_len) != 0)) { + return false; + } + + return true; +} + +static bool TestASN1() { + static const uint8_t kData1[] = {0x30, 2, 1, 2, 0, 0}; + static const uint8_t kData2[] = {0x30, 3, 1, 2}; /* truncated */ + static const uint8_t kData3[] = {0x30, 0x81, 1, 1}; /* should be short len */ + static const uint8_t kData4[] = {0x30, 0x82, 0, 1, 1}; /* zero padded. */ + + if (!ReadASN1(true, kData1, sizeof(kData1), 4, 100) || + !ReadASN1(false, kData2, sizeof(kData2), 0, 100) || + !ReadASN1(false, kData3, sizeof(kData3), 0, 100) || + !ReadASN1(false, kData4, sizeof(kData4), 0, 100)) { + return false; + } + + static const size_t kLargePayloadLen = 8000; + static const uint8_t kLargePrefix[] = {0x30, 0x82, kLargePayloadLen >> 8, + kLargePayloadLen & 0xff}; + ScopedOpenSSLBytes large(reinterpret_cast<uint8_t *>( + OPENSSL_malloc(sizeof(kLargePrefix) + kLargePayloadLen))); + memset(large.get() + sizeof(kLargePrefix), 0, kLargePayloadLen); + memcpy(large.get(), kLargePrefix, sizeof(kLargePrefix)); + + if (!ReadASN1(true, large.get(), sizeof(kLargePrefix) + kLargePayloadLen, + sizeof(kLargePrefix) + kLargePayloadLen, + kLargePayloadLen * 2)) { + fprintf(stderr, "Large payload test failed.\n"); + return false; + } + + if (!ReadASN1(false, large.get(), sizeof(kLargePrefix) + kLargePayloadLen, + sizeof(kLargePrefix) + kLargePayloadLen, + kLargePayloadLen - 1)) { + fprintf(stderr, "max_len test failed.\n"); + return false; + } + + static const uint8_t kIndefPrefix[] = {0x30, 0x80}; + memcpy(large.get(), kIndefPrefix, sizeof(kIndefPrefix)); + if (!ReadASN1(true, large.get(), sizeof(kLargePrefix) + kLargePayloadLen, + sizeof(kLargePrefix) + kLargePayloadLen, + kLargePayloadLen*2)) { + fprintf(stderr, "indefinite length test failed.\n"); + return false; + } + + if (!ReadASN1(false, large.get(), sizeof(kLargePrefix) + kLargePayloadLen, + sizeof(kLargePrefix) + kLargePayloadLen, + kLargePayloadLen-1)) { + fprintf(stderr, "indefinite length, max_len test failed.\n"); + return false; + } + + return true; +} + int main(void) { CRYPTO_library_init(); ERR_load_crypto_strings(); @@ -350,7 +428,8 @@ int main(void) { if (!TestSocketConnect() || !TestPrintf() || - !TestZeroCopyBioPairs()) { + !TestZeroCopyBioPairs() || + !TestASN1()) { return 1; } diff --git a/src/include/openssl/bio.h b/src/include/openssl/bio.h index b70b42f..a37077c 100644 --- a/src/include/openssl/bio.h +++ b/src/include/openssl/bio.h @@ -338,6 +338,21 @@ OPENSSL_EXPORT int BIO_hexdump(BIO *bio, const uint8_t *data, size_t len, * using human readable strings where possible. */ OPENSSL_EXPORT void BIO_print_errors(BIO *bio); +/* BIO_read_asn1 reads a single ASN.1 object from |bio|. If successful it sets + * |*out| to be an allocated buffer (that should be freed with |OPENSSL_free|), + * |*out_size| to the length, in bytes, of that buffer and returns one. + * Otherwise it returns zero. + * + * If the length of the object is greater than |max_len| or 2^32 then the + * function will fail. Long-form tags are not supported. If the length of the + * object is indefinite the full contents of |bio| are read, unless it would be + * greater than |max_len|, in which case the function fails. + * + * If the function fails then some unknown amount of data may have been read + * from |bio|. */ +OPENSSL_EXPORT int BIO_read_asn1(BIO *bio, uint8_t **out, size_t *out_len, + size_t max_len); + /* Memory BIOs. * |