diff options
Diffstat (limited to 'src/crypto/bio/bio.c')
-rw-r--r-- | src/crypto/bio/bio.c | 140 |
1 files changed, 140 insertions, 0 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; +} |