summaryrefslogtreecommitdiffstats
path: root/src/crypto/bio/bio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/crypto/bio/bio.c')
-rw-r--r--src/crypto/bio/bio.c140
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;
+}