[openssl-commits] [openssl] master update

Richard Levitte levitte at openssl.org
Mon May 8 19:23:54 UTC 2017


The branch master has been updated
       via  fa3ed5b2c2b508a6444124fdf12ecbb4898007ed (commit)
       via  44612e0a817d1cf25df776b00993820f612f3cd3 (commit)
       via  7671342e550ed2de676b23c79d0e7f45a381c76e (commit)
       via  204afd81b12c71d625e89599c0eef33588afc1f0 (commit)
      from  d396da33130aba2e77478d00fd369eb8d34bd8bf (commit)


- Log -----------------------------------------------------------------
commit fa3ed5b2c2b508a6444124fdf12ecbb4898007ed
Author: Benjamin Kaduk <bkaduk at akamai.com>
Date:   Wed Apr 12 16:24:43 2017 -0500

    Add unit test for PEM_FLAG_ONLY_B64
    
    Get some trivial test coverage that this flag does what it claims to.
    
    [extended tests]
    
    Reviewed-by: Rich Salz <rsalz at openssl.org>
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/1700)

commit 44612e0a817d1cf25df776b00993820f612f3cd3
Author: Benjamin Kaduk <bkaduk at akamai.com>
Date:   Fri Feb 19 17:36:52 2016 -0600

    Make PEM_read_{,bio_}PrivateKey use secmem
    
    We now have a version of PEM_read_bytes that can use temporary
    buffers allocated from the secure heap; use them to handle this
    sensitive information.
    
    Note that for PEM_read_PrivateKey, the i/o still goes through
    stdio since the input is a FILE pointer.  Standard I/O performs
    additional buffering, which cannot be changed to use the OpenSSL
    secure heap for temporary storage.  As such, it is recommended
    to use BIO_new_file() and PEM_read_bio_PrivateKey() instead.
    
    Reviewed-by: Rich Salz <rsalz at openssl.org>
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/1700)

commit 7671342e550ed2de676b23c79d0e7f45a381c76e
Author: Benjamin Kaduk <bkaduk at akamai.com>
Date:   Mon Feb 29 15:47:12 2016 -0600

    Add PEM_bytes_read_bio_secmem()
    
    Split the PEM_bytes_read_bio() implementation out into a
    pem_bytes_read_bio_flags() helper, to allow it to pass PEM_FLAG_SECURE
    as needed.  Adjust the cleanup to properly use OPENSSL_secure_free()
    when needed, and reimplement PEM_bytes_read() as a wrapper around
    the _flags helper.
    
    Add documentation for PEM_bytes_read_bio() and the new secmem variant.
    
    Reviewed-by: Rich Salz <rsalz at openssl.org>
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/1700)

commit 204afd81b12c71d625e89599c0eef33588afc1f0
Author: Benjamin Kaduk <bkaduk at akamai.com>
Date:   Thu Feb 18 21:24:27 2016 -0600

    Add PEM_read_bio_ex
    
    The extended function includes a 'flags' argument to allow callers
    to specify different requested behaviors.  In particular, callers can
    request that temporary storage buffers are allocated from the secure heap,
    which could be relevant when loading private key material.
    
    Refactor PEM_read_bio to use BIO_mems instead of BUFs directly,
    use some helper routines to reduce the overall function length, and make
    some of the checks more reasonable.
    
    Reviewed-by: Rich Salz <rsalz at openssl.org>
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/1700)

-----------------------------------------------------------------------

Summary of changes:
 crypto/pem/pem_err.c            |   3 +
 crypto/pem/pem_lib.c            | 456 ++++++++++++++++++++++++++--------------
 crypto/pem/pem_pkey.c           |   7 +-
 doc/man3/PEM_bytes_read_bio.pod |  81 +++++++
 doc/man3/PEM_read_bio_ex.pod    |  70 ++++++
 include/openssl/pem.h           |  11 +
 test/build.info                 |   6 +-
 test/pemtest.c                  |  76 +++++++
 test/recipes/04-test_pem.t      |   4 +-
 util/libcrypto.num              |   2 +
 10 files changed, 554 insertions(+), 162 deletions(-)
 create mode 100644 doc/man3/PEM_bytes_read_bio.pod
 create mode 100644 doc/man3/PEM_read_bio_ex.pod
 create mode 100644 test/pemtest.c

diff --git a/crypto/pem/pem_err.c b/crypto/pem/pem_err.c
index f36d893..6c25a52 100644
--- a/crypto/pem/pem_err.c
+++ b/crypto/pem/pem_err.c
@@ -33,6 +33,8 @@ static ERR_STRING_DATA PEM_str_functs[] = {
     {ERR_FUNC(PEM_F_DO_PK8PKEY_FP), "do_pk8pkey_fp"},
     {ERR_FUNC(PEM_F_DO_PVK_BODY), "do_PVK_body"},
     {ERR_FUNC(PEM_F_DO_PVK_HEADER), "do_PVK_header"},
+    {ERR_FUNC(PEM_F_GET_HEADER_AND_DATA), "get_header_and_data"},
+    {ERR_FUNC(PEM_F_GET_NAME), "get_name"},
     {ERR_FUNC(PEM_F_I2B_PVK), "i2b_PVK"},
     {ERR_FUNC(PEM_F_I2B_PVK_BIO), "i2b_PVK_bio"},
     {ERR_FUNC(PEM_F_LOAD_IV), "load_iv"},
@@ -46,6 +48,7 @@ static ERR_STRING_DATA PEM_str_functs[] = {
     {ERR_FUNC(PEM_F_PEM_READ), "PEM_read"},
     {ERR_FUNC(PEM_F_PEM_READ_BIO), "PEM_read_bio"},
     {ERR_FUNC(PEM_F_PEM_READ_BIO_DHPARAMS), "PEM_read_bio_DHparams"},
+    {ERR_FUNC(PEM_F_PEM_READ_BIO_EX), "PEM_read_bio_ex"},
     {ERR_FUNC(PEM_F_PEM_READ_BIO_PARAMETERS), "PEM_read_bio_Parameters"},
     {ERR_FUNC(PEM_F_PEM_READ_BIO_PRIVATEKEY), "PEM_read_bio_PrivateKey"},
     {ERR_FUNC(PEM_F_PEM_READ_DHPARAMS), "PEM_read_DHparams"},
diff --git a/crypto/pem/pem_lib.c b/crypto/pem/pem_lib.c
index 3f53fd8..75b022e 100644
--- a/crypto/pem/pem_lib.c
+++ b/crypto/pem/pem_lib.c
@@ -228,9 +228,24 @@ static int check_pem(const char *nm, const char *name)
     return 0;
 }
 
-int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
-                       const char *name, BIO *bp, pem_password_cb *cb,
-                       void *u)
+static void pem_free(void *p, unsigned int flags)
+{
+    if (flags & PEM_FLAG_SECURE)
+        OPENSSL_secure_free(p);
+    else
+        OPENSSL_free(p);
+}
+
+static void *pem_malloc(int num, unsigned int flags)
+{
+    return (flags & PEM_FLAG_SECURE) ? OPENSSL_secure_malloc(num)
+                                     : OPENSSL_malloc(num);
+}
+
+static int pem_bytes_read_bio_flags(unsigned char **pdata, long *plen,
+                                    char **pnm, const char *name, BIO *bp,
+                                    pem_password_cb *cb, void *u,
+                                    unsigned int flags)
 {
     EVP_CIPHER_INFO cipher;
     char *nm = NULL, *header = NULL;
@@ -238,18 +253,16 @@ int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
     long len;
     int ret = 0;
 
-    for (;;) {
-        if (!PEM_read_bio(bp, &nm, &header, &data, &len)) {
+    do {
+        pem_free(nm, flags);
+        pem_free(header, flags);
+        pem_free(data, flags);
+        if (!PEM_read_bio_ex(bp, &nm, &header, &data, &len, flags)) {
             if (ERR_GET_REASON(ERR_peek_error()) == PEM_R_NO_START_LINE)
                 ERR_add_error_data(2, "Expecting: ", name);
             return 0;
         }
-        if (check_pem(nm, name))
-            break;
-        OPENSSL_free(nm);
-        OPENSSL_free(header);
-        OPENSSL_free(data);
-    }
+    } while (!check_pem(nm, name));
     if (!PEM_get_EVP_CIPHER_INFO(header, &cipher))
         goto err;
     if (!PEM_do_header(&cipher, data, &len, cb, u))
@@ -258,20 +271,34 @@ int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
     *pdata = data;
     *plen = len;
 
-    if (pnm)
+    if (pnm != NULL)
         *pnm = nm;
 
     ret = 1;
 
  err:
-    if (!ret || !pnm)
-        OPENSSL_free(nm);
-    OPENSSL_free(header);
+    if (!ret || pnm == NULL)
+        pem_free(nm, flags);
+    pem_free(header, flags);
     if (!ret)
-        OPENSSL_free(data);
+        pem_free(data, flags);
     return ret;
 }
 
+int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
+                       const char *name, BIO *bp, pem_password_cb *cb,
+                       void *u) {
+    return pem_bytes_read_bio_flags(pdata, plen, pnm, name, bp, cb, u,
+                                    PEM_FLAG_EAY_COMPATIBLE);
+}
+
+int PEM_bytes_read_bio_secmem(unsigned char **pdata, long *plen, char **pnm,
+                              const char *name, BIO *bp, pem_password_cb *cb,
+                              void *u) {
+    return pem_bytes_read_bio_flags(pdata, plen, pnm, name, bp, cb, u,
+                                    PEM_FLAG_SECURE | PEM_FLAG_EAY_COMPATIBLE);
+}
+
 #ifndef OPENSSL_NO_STDIO
 int PEM_ASN1_write(i2d_of_void *i2d, const char *name, FILE *fp,
                    void *x, const EVP_CIPHER *enc, unsigned char *kstr,
@@ -661,177 +688,292 @@ int PEM_read(FILE *fp, char **name, char **header, unsigned char **data,
 }
 #endif
 
-int PEM_read_bio(BIO *bp, char **name, char **header, unsigned char **data,
-                 long *len)
+/* Some helpers for PEM_read_bio_ex(). */
+
+#define isb64(c) (isalnum(c) || (c) == '+' || (c) == '/' || (c) == '=')
+
+static int sanitize_line(char *linebuf, int len, unsigned int flags)
 {
-    EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
-    int end = 0, i, k, bl = 0, hl = 0, nohead = 0;
-    char buf[256];
-    BUF_MEM *nameB;
-    BUF_MEM *headerB;
-    BUF_MEM *dataB, *tmpB;
+    int i;
 
-    if (ctx == NULL) {
-        PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
-        return (0);
+    if (flags & PEM_FLAG_EAY_COMPATIBLE) {
+        /* Strip trailing whitespace */
+        while ((len >= 0) && (linebuf[len] <= ' '))
+            len--;
+        /* Go back to whitespace before applying uniform line ending. */
+        len++;
+    } else if (flags & PEM_FLAG_ONLY_B64) {
+        for (i = 0; i < len; ++i) {
+            if (!isb64(linebuf[i]) || linebuf[i] == '\n' || linebuf[i] == '\r')
+                break;
+        }
+        len = i;
+    } else {
+        /* EVP_DecodeBlock strips leading and trailing whitespace, so just strip
+         * control characters in-place and let everything through. */
+        for (i = 0; i < len; ++i) {
+            if (linebuf[i] == '\n' || linebuf[i] == '\r')
+                break;
+            if (iscntrl(linebuf[i]))
+                linebuf[i] = ' ';
+        }
+        len = i;
     }
+    /* The caller allocated LINESIZE+1, so this is safe. */
+    linebuf[len++] = '\n';
+    linebuf[len] = '\0';
+    return len;
+}
 
-    nameB = BUF_MEM_new();
-    headerB = BUF_MEM_new();
-    dataB = BUF_MEM_new();
-    if ((nameB == NULL) || (headerB == NULL) || (dataB == NULL)) {
-        goto err;
+#define LINESIZE 255
+/* Note trailing spaces for begin and end. */
+static const char beginstr[] = "-----BEGIN ";
+static const char endstr[] = "-----END ";
+static const char tailstr[] = "-----\n";
+#define BEGINLEN (sizeof(beginstr) - 1)
+#define ENDLEN (sizeof(endstr) - 1)
+#define TAILLEN (sizeof(tailstr) - 1)
+static int get_name(BIO *bp, char **name, unsigned int flags)
+{
+    char *linebuf;
+    int ret = 0;
+    size_t len;
+
+    /*
+     * Need to hold trailing NUL (accounted for by BIO_gets() and the newline
+     * that will be added by sanitize_line() (the extra '1').
+     */
+    linebuf = pem_malloc(LINESIZE + 1, flags);
+    if (linebuf == NULL) {
+        PEMerr(PEM_F_GET_NAME, ERR_R_MALLOC_FAILURE);
+        return 0;
     }
 
-    buf[254] = '\0';
-    for (;;) {
-        i = BIO_gets(bp, buf, 254);
+    do {
+        len = BIO_gets(bp, linebuf, LINESIZE);
 
-        if (i <= 0) {
-            PEMerr(PEM_F_PEM_READ_BIO, PEM_R_NO_START_LINE);
+        if (len <= 0) {
+            PEMerr(PEM_F_GET_NAME, PEM_R_NO_START_LINE);
             goto err;
         }
 
-        while ((i >= 0) && (buf[i] <= ' '))
-            i--;
-        buf[++i] = '\n';
-        buf[++i] = '\0';
+        /* Strip trailing garbage and standardize ending. */
+        len = sanitize_line(linebuf, len, flags & ~PEM_FLAG_ONLY_B64);
+
+        /* Allow leading empty or non-matching lines. */
+    } while (strncmp(linebuf, beginstr, BEGINLEN) != 0
+             || len < TAILLEN
+             || strncmp(linebuf + len - TAILLEN, tailstr, TAILLEN) != 0);
+    linebuf[len - TAILLEN] = '\0';
+    len = len - BEGINLEN - TAILLEN + 1;
+    *name = pem_malloc(len, flags);
+    if (*name == NULL) {
+        PEMerr(PEM_F_GET_NAME, ERR_R_MALLOC_FAILURE);
+        goto err;
+    }
+    memcpy(*name, linebuf + BEGINLEN, len);
+    ret = 1;
 
-        if (strncmp(buf, "-----BEGIN ", 11) == 0) {
-            i = strlen(&(buf[11]));
+err:
+    pem_free(linebuf, flags);
+    return ret;
+}
+
+/* Keep track of how much of a header we've seen. */
+enum header_status {
+    MAYBE_HEADER,
+    IN_HEADER,
+    POST_HEADER
+};
+
+/**
+ * Extract the optional PEM header, with details on the type of content and
+ * any encryption used on the contents, and the bulk of the data from the bio.
+ * The end of the header is marked by a blank line; if the end-of-input marker
+ * is reached prior to a blank line, there is no header.
+ *
+ * The header and data arguments are BIO** since we may have to swap them
+ * if there is no header, for efficiency.
+ *
+ * We need the name of the PEM-encoded type to verify the end string.
+ */
+static int get_header_and_data(BIO *bp, BIO **header, BIO **data, char *name,
+                               unsigned int flags)
+{
+    BIO *tmp = *header;
+    char *linebuf, *p;
+    int len, line, ret = 0, end = 0;
+    /* 0 if not seen (yet), 1 if reading header, 2 if finished header */
+    enum header_status got_header = MAYBE_HEADER;
+    unsigned int flags_mask;
+    size_t namelen;
+
+    /* Need to hold trailing NUL (accounted for by BIO_gets() and the newline
+     * that will be added by sanitize_line() (the extra '1'). */
+    linebuf = pem_malloc(LINESIZE + 1, flags);
+    if (linebuf == NULL) {
+        PEMerr(PEM_F_GET_HEADER_AND_DATA, ERR_R_MALLOC_FAILURE);
+        return 0;
+    }
+
+    for (line = 0; ; line++) {
+        flags_mask = ~0u;
+        len = BIO_gets(bp, linebuf, LINESIZE);
+        if (len <= 0) {
+            PEMerr(PEM_F_GET_HEADER_AND_DATA, PEM_R_SHORT_HEADER);
+            goto err;
+        }
 
-            if (strncmp(&(buf[11 + i - 6]), "-----\n", 6) != 0)
-                continue;
-            if (!BUF_MEM_grow(nameB, i + 9)) {
-                PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
+        if (got_header == MAYBE_HEADER) {
+            if (memchr(linebuf, ':', len) != NULL)
+                got_header = IN_HEADER;
+        }
+        if (!strncmp(linebuf, endstr, ENDLEN) || got_header == IN_HEADER)
+            flags_mask &= ~PEM_FLAG_ONLY_B64;
+        len = sanitize_line(linebuf, len, flags & flags_mask);
+
+        /* Check for end of header. */
+        if (linebuf[0] == '\n') {
+            if (got_header == POST_HEADER) {
+                /* Another blank line is an error. */
+                PEMerr(PEM_F_GET_HEADER_AND_DATA, PEM_R_BAD_END_LINE);
                 goto err;
             }
-            memcpy(nameB->data, &(buf[11]), i - 6);
-            nameB->data[i - 6] = '\0';
-            break;
+            got_header = POST_HEADER;
+            tmp = *data;
+            continue;
         }
-    }
-    hl = 0;
-    if (!BUF_MEM_grow(headerB, 256)) {
-        PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
-        goto err;
-    }
-    headerB->data[0] = '\0';
-    for (;;) {
-        i = BIO_gets(bp, buf, 254);
-        if (i <= 0)
-            break;
 
-        while ((i >= 0) && (buf[i] <= ' '))
-            i--;
-        buf[++i] = '\n';
-        buf[++i] = '\0';
-
-        if (buf[0] == '\n')
+        /* Check for end of stream (which means there is no header). */
+        if (strncmp(linebuf, endstr, ENDLEN) == 0) {
+            p = linebuf + ENDLEN;
+            namelen = strlen(name);
+            if (strncmp(p, name, namelen) != 0 ||
+                strncmp(p + namelen, tailstr, TAILLEN) != 0) {
+                PEMerr(PEM_F_GET_HEADER_AND_DATA, PEM_R_BAD_END_LINE);
+                goto err;
+            }
+            if (got_header == MAYBE_HEADER) {
+                *header = *data;
+                *data = tmp;
+            }
             break;
-        if (!BUF_MEM_grow(headerB, hl + i + 9)) {
-            PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
+        } else if (end) {
+            /* Malformed input; short line not at end of data. */
+            PEMerr(PEM_F_GET_HEADER_AND_DATA, PEM_R_BAD_END_LINE);
             goto err;
         }
-        if (strncmp(buf, "-----END ", 9) == 0) {
-            nohead = 1;
-            break;
+        /*
+         * Else, a line of text -- could be header or data; we don't
+         * know yet.  Just pass it through.
+         */
+        BIO_puts(tmp, linebuf);
+        /*
+         * Only encrypted files need the line length check applied.
+         */
+        if (got_header == POST_HEADER) {
+            /* 65 includes the trailing newline */
+            if (len > 65)
+                goto err;
+            if (len < 65)
+                end = 1;
         }
-        memcpy(&(headerB->data[hl]), buf, i);
-        headerB->data[hl + i] = '\0';
-        hl += i;
     }
 
-    bl = 0;
-    if (!BUF_MEM_grow(dataB, 1024)) {
-        PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
-        goto err;
-    }
-    dataB->data[0] = '\0';
-    if (!nohead) {
-        for (;;) {
-            i = BIO_gets(bp, buf, 254);
-            if (i <= 0)
-                break;
-
-            while ((i >= 0) && (buf[i] <= ' '))
-                i--;
-            buf[++i] = '\n';
-            buf[++i] = '\0';
+    ret = 1;
+err:
+    pem_free(linebuf, flags);
+    return ret;
+}
 
-            if (i != 65)
-                end = 1;
-            if (strncmp(buf, "-----END ", 9) == 0)
-                break;
-            if (i > 65)
-                break;
-            if (!BUF_MEM_grow_clean(dataB, i + bl + 9)) {
-                PEMerr(PEM_F_PEM_READ_BIO, ERR_R_MALLOC_FAILURE);
-                goto err;
-            }
-            memcpy(&(dataB->data[bl]), buf, i);
-            dataB->data[bl + i] = '\0';
-            bl += i;
-            if (end) {
-                buf[0] = '\0';
-                i = BIO_gets(bp, buf, 254);
-                if (i <= 0)
-                    break;
-
-                while ((i >= 0) && (buf[i] <= ' '))
-                    i--;
-                buf[++i] = '\n';
-                buf[++i] = '\0';
+/**
+ * Read in PEM-formatted data from the given BIO.
+ *
+ * By nature of the PEM format, all content must be printable ASCII (except
+ * for line endings).  Other characters, or lines that are longer than 80
+ * characters, are malformed input and will be rejected.
+ */
+int PEM_read_bio_ex(BIO *bp, char **name_out, char **header,
+                    unsigned char **data, long *len_out, unsigned int flags)
+{
+    EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
+    const BIO_METHOD *bmeth;
+    BIO *headerB = NULL, *dataB = NULL;
+    char *name = NULL;
+    int len, taillen, headerlen, ret = 0;
+    BUF_MEM * buf_mem;
 
-                break;
-            }
-        }
-    } else {
-        tmpB = headerB;
-        headerB = dataB;
-        dataB = tmpB;
-        bl = hl;
-    }
-    i = strlen(nameB->data);
-    if ((strncmp(buf, "-----END ", 9) != 0) ||
-        (strncmp(nameB->data, &(buf[9]), i) != 0) ||
-        (strncmp(&(buf[9 + i]), "-----\n", 6) != 0)) {
-        PEMerr(PEM_F_PEM_READ_BIO, PEM_R_BAD_END_LINE);
-        goto err;
+    if (ctx == NULL) {
+        PEMerr(PEM_F_PEM_READ_BIO_EX, ERR_R_MALLOC_FAILURE);
+        return 0;
     }
 
-    EVP_DecodeInit(ctx);
-    i = EVP_DecodeUpdate(ctx,
-                         (unsigned char *)dataB->data, &bl,
-                         (unsigned char *)dataB->data, bl);
-    if (i < 0) {
-        PEMerr(PEM_F_PEM_READ_BIO, PEM_R_BAD_BASE64_DECODE);
-        goto err;
+    *len_out = 0;
+    *name_out = *header = NULL;
+    *data = NULL;
+    if ((flags & PEM_FLAG_EAY_COMPATIBLE) && (flags & PEM_FLAG_ONLY_B64)) {
+        /* These two are mutually incompatible; bail out. */
+        PEMerr(PEM_F_PEM_READ_BIO_EX, ERR_R_PASSED_INVALID_ARGUMENT);
+        goto end;
     }
-    i = EVP_DecodeFinal(ctx, (unsigned char *)&(dataB->data[bl]), &k);
-    if (i < 0) {
-        PEMerr(PEM_F_PEM_READ_BIO, PEM_R_BAD_BASE64_DECODE);
-        goto err;
+    bmeth = (flags & PEM_FLAG_SECURE) ? BIO_s_secmem() : BIO_s_mem();
+
+    headerB = BIO_new(bmeth);
+    dataB = BIO_new(bmeth);
+    if (headerB == NULL || dataB == NULL) {
+        PEMerr(PEM_F_PEM_READ_BIO_EX, ERR_R_MALLOC_FAILURE);
+        goto end;
     }
-    bl += k;
 
-    if (bl == 0)
-        goto err;
-    *name = nameB->data;
-    *header = headerB->data;
-    *data = (unsigned char *)dataB->data;
-    *len = bl;
-    OPENSSL_free(nameB);
-    OPENSSL_free(headerB);
-    OPENSSL_free(dataB);
-    EVP_ENCODE_CTX_free(ctx);
-    return (1);
- err:
-    BUF_MEM_free(nameB);
-    BUF_MEM_free(headerB);
-    BUF_MEM_free(dataB);
+    if (!get_name(bp, &name, flags))
+        goto end;
+    if (!get_header_and_data(bp, &headerB, &dataB, name, flags))
+        goto end;
+
+    EVP_DecodeInit(ctx);
+    BIO_get_mem_ptr(dataB, &buf_mem);
+    len = buf_mem->length;
+    if (EVP_DecodeUpdate(ctx, (unsigned char*)buf_mem->data, &len,
+                         (unsigned char*)buf_mem->data, len) < 0
+            || EVP_DecodeFinal(ctx, (unsigned char*)&(buf_mem->data[len]),
+                               &taillen) < 0) {
+        PEMerr(PEM_F_PEM_READ_BIO_EX, PEM_R_BAD_BASE64_DECODE);
+        goto end;
+    }
+    len += taillen;
+    buf_mem->length = len;
+
+    /* There was no data in the PEM file; avoid malloc(0). */
+    if (len == 0)
+        goto end;
+    headerlen = BIO_get_mem_data(headerB, NULL);
+    *header = pem_malloc(headerlen + 1, flags);
+    *data = pem_malloc(len, flags);
+    if (*header == NULL || *data == NULL) {
+        pem_free(*header, flags);
+        pem_free(*data, flags);
+        goto end;
+    }
+    BIO_read(headerB, *header, headerlen);
+    (*header)[headerlen] = '\0';
+    BIO_read(dataB, *data, len);
+    *len_out = len;
+    *name_out = name;
+    name = NULL;
+    ret = 1;
+
+end:
     EVP_ENCODE_CTX_free(ctx);
-    return (0);
+    pem_free(name, flags);
+    BIO_free(headerB);
+    BIO_free(dataB);
+    return ret;
+}
+
+int PEM_read_bio(BIO *bp, char **name, char **header, unsigned char **data,
+                 long *len)
+{
+    return PEM_read_bio_ex(bp, name, header, data, len, PEM_FLAG_EAY_COMPATIBLE);
 }
 
 /*
diff --git a/crypto/pem/pem_pkey.c b/crypto/pem/pem_pkey.c
index 6308622..9356501 100644
--- a/crypto/pem/pem_pkey.c
+++ b/crypto/pem/pem_pkey.c
@@ -32,7 +32,8 @@ EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb,
     int slen;
     EVP_PKEY *ret = NULL;
 
-    if (!PEM_bytes_read_bio(&data, &len, &nm, PEM_STRING_EVP_PKEY, bp, cb, u))
+    if (!PEM_bytes_read_bio_secmem(&data, &len, &nm, PEM_STRING_EVP_PKEY, bp,
+				   cb, u))
         return NULL;
     p = data;
 
@@ -85,8 +86,8 @@ EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb,
     if (ret == NULL)
         PEMerr(PEM_F_PEM_READ_BIO_PRIVATEKEY, ERR_R_ASN1_LIB);
  err:
-    OPENSSL_free(nm);
-    OPENSSL_clear_free(data, len);
+    OPENSSL_secure_free(nm);
+    OPENSSL_secure_free(data);
     return (ret);
 }
 
diff --git a/doc/man3/PEM_bytes_read_bio.pod b/doc/man3/PEM_bytes_read_bio.pod
new file mode 100644
index 0000000..d16ccd8
--- /dev/null
+++ b/doc/man3/PEM_bytes_read_bio.pod
@@ -0,0 +1,81 @@
+=pod
+
+=head1 NAME
+
+PEM_bytes_read_bio, PEM_bytes_read_bio_secmem - read a PEM-encoded data structure from a BIO
+
+=head1 SYNOPSIS
+
+ #include <openssl/pem.h>
+
+ int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
+                        const char *name, BIO *bp, pem_password_cb *cb,
+                        void *u);
+ int PEM_bytes_read_bio_secmem(unsigned char **pdata, long *plen, char **pnm,
+                               const char *name, BIO *bp, pem_password_cb *cb,
+                               void *u);
+
+=head1 DESCRIPTION
+
+PEM_bytes_read_bio() reads PEM-formatted (RFC 1421) data from the BIO
+I<bp> for the data type given in I<name> (RSA PRIVATE KEY, CERTIFICATE,
+etc.).  If multiple PEM-encoded data structures are present in the same
+stream, PEM_bytes_read_bio() will skip non-matching data types and
+continue reading.  Non-PEM data present in the stream may cause an
+error.
+
+The PEM header may indicate that the following data is encrypted; if so,
+the data will be decrypted, waiting on user input to supply a passphrase
+if needed.  The password callback I<cb> and rock I<u> are used to obtain
+the decryption passphrase, if applicable.
+
+Some data types have compatibility aliases, such as a file containing
+X509 CERTIFICATE matching a request for the deprecated type CERTIFICATE.
+The actual type indicated by the file is returned in I<*pnm> if I<pnm> is
+non-NULL.  The caller must free the storage pointed to by I<*pnm>.
+
+The returned data is the DER-encoded form of the requested type, in
+I<*pdata> with length I<*plen>.  The caller must free the storage pointed
+to by I<*pdata>.
+
+PEM_bytes_read_bio_secmem() is similar to PEM_bytes_read_bio(), but uses
+memory from the secure heap for its temporary buffers and the storage
+returned in I<*pdata> and I<*pnm>.  Accordingly, the caller must use
+OPENSSL_secure_free() to free that storage.
+
+=head1 NOTES
+
+PEM_bytes_read_bio_secmem() only enforces that the secure heap is used for
+storage allocated within the PEM processing stack.  The BIO stack from
+which input is read may also use temporary buffers, which are not necessarily
+allocated from the secure heap.  In cases where it is desirable to ensure
+that the contents of the PEM file only appears in memory from the secure heap,
+care is needed in generating the BIO passed as I<bp>.  In particular, the
+use of BIO_s_file() indicates the use of the operating system stdio
+functionality, which includes buffering as a feature; BIO_s_fd() is likely
+to be more appropriate in such cases.
+
+=head1 RETURN VALUES
+
+PEM_bytes_read_bio() and PEM_bytes_read_bio_secmem() return 1 for success or
+0 for failure.
+
+=head1 SEE ALSO
+
+L<PEM(3)>,
+L<PEM_read_bio_ex(3)>
+
+=head1 HISTORY
+
+PEM_bytes_read_bio_secmem() was introduced in OpenSSL 1.1.1
+
+=head1 COPYRIGHT
+
+Copyright 2017 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the OpenSSL license (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/doc/man3/PEM_read_bio_ex.pod b/doc/man3/PEM_read_bio_ex.pod
new file mode 100644
index 0000000..e171bff
--- /dev/null
+++ b/doc/man3/PEM_read_bio_ex.pod
@@ -0,0 +1,70 @@
+=pod
+
+=head1 NAME
+
+PEM_read_bio_ex, PEM_FLAG_SECURE, PEM_FLAG_EAY_COMPATIBLE,
+PEM_FLAG_ONLY_B64 - read PEM format files with custom processing
+
+=head1 SYNOPSIS
+
+ #include <openssl/pem.h>
+
+ #define PEM_FLAG_SECURE             0x1
+ #define PEM_FLAG_EAY_COMPATIBLE     0x2
+ #define PEM_FLAG_ONLY_B64           0x4
+ int PEM_read_bio_ex(BIO *in, char **name, char **header,
+                     unsigned char **data, long *len, unsigned int flags);
+
+=head1 DESCRIPTION
+
+PEM_read_bio_ex() reads in PEM formatted data from an input BIO, outputting
+the name of the type of contained data, the header information regarding
+the possibly encrypted data, and the binary data payload (after base64 decoding).
+It should generally only be used to implement PEM_read_bio_-family functions
+for specific data types or other usage, but is exposed to allow greater flexibility
+over how processing is performed, if needed.
+
+If PEM_FLAG_SECURE is set, the intermediate buffers used to read in lines of
+input are allocated from the secure heap.
+
+If PEM_FLAG_EAY_COMPATIBLE is set, a simple algorithm is used to remove whitespace
+and control characters from the end of each line, so as to be compatible with
+the historical behavior of PEM_read_bio().
+
+If PEM_FLAG_ONLY_B64 is set, all characters are required to be valid base64
+characters (or newlines); non-base64 characters are treated as end of input.
+
+If neither PEM_FLAG_EAY_COMPATIBLE or PEM_FLAG_ONLY_B64 is set, control characters
+are ignored.
+
+If both PEM_FLAG_EAY_COMPATIBLE and PEM_FLAG_ONLY_B64 are set, an error is returned;
+these options are not compatible with each other.
+
+=head1 NOTES
+
+The caller must release the storage allocated for *name, *header, and *data.
+If PEM_FLAG_SECURE was set, use OPENSSL_secure_free(); otherwise,
+OPENSSL_free() is used.
+
+=head1 RETURN VALUES
+
+PEM_read_bio_ex() returns 1 for success or 0 for failure.
+
+=head1 SEE ALSO
+
+L<PEM(3)>
+
+=head1 HISTORY
+
+PEM_read_bio_ex() was added in OpenSSL 1.1.1.
+
+=head1 COPYRIGHT
+
+Copyright 2017 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the OpenSSL license (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/include/openssl/pem.h b/include/openssl/pem.h
index 431ee3e..105c810 100644
--- a/include/openssl/pem.h
+++ b/include/openssl/pem.h
@@ -236,6 +236,14 @@ int PEM_do_header(EVP_CIPHER_INFO *cipher, unsigned char *data, long *len,
 
 int PEM_read_bio(BIO *bp, char **name, char **header,
                  unsigned char **data, long *len);
+#   define PEM_FLAG_SECURE             0x1
+#   define PEM_FLAG_EAY_COMPATIBLE     0x2
+#   define PEM_FLAG_ONLY_B64           0x4
+int PEM_read_bio_ex(BIO *bp, char **name, char **header,
+                    unsigned char **data, long *len, unsigned int flags);
+int PEM_bytes_read_bio_secmem(unsigned char **pdata, long *plen, char **pnm,
+                              const char *name, BIO *bp, pem_password_cb *cb,
+                              void *u);
 int PEM_write_bio(BIO *bp, const char *name, const char *hdr,
                   const unsigned char *data, long len);
 int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
@@ -388,6 +396,8 @@ int ERR_load_PEM_strings(void);
 # define PEM_F_DO_PK8PKEY_FP                              125
 # define PEM_F_DO_PVK_BODY                                135
 # define PEM_F_DO_PVK_HEADER                              136
+# define PEM_F_GET_HEADER_AND_DATA                        143
+# define PEM_F_GET_NAME                                   144
 # define PEM_F_I2B_PVK                                    137
 # define PEM_F_I2B_PVK_BIO                                138
 # define PEM_F_LOAD_IV                                    101
@@ -401,6 +411,7 @@ int ERR_load_PEM_strings(void);
 # define PEM_F_PEM_READ                                   108
 # define PEM_F_PEM_READ_BIO                               109
 # define PEM_F_PEM_READ_BIO_DHPARAMS                      141
+# define PEM_F_PEM_READ_BIO_EX                            145
 # define PEM_F_PEM_READ_BIO_PARAMETERS                    140
 # define PEM_F_PEM_READ_BIO_PRIVATEKEY                    123
 # define PEM_F_PEM_READ_DHPARAMS                          142
diff --git a/test/build.info b/test/build.info
index 94a7402..bd80b18 100644
--- a/test/build.info
+++ b/test/build.info
@@ -43,7 +43,7 @@ INCLUDE_MAIN___test_libtestutil_OLB = /INCLUDE=MAIN
           bioprinttest sslapitest dtlstest sslcorrupttest bio_enc_test \
           pkey_meth_test uitest cipherbytes_test asn1_encode_test \
           x509_time_test x509_dup_cert_test recordlentest \
-          time_offset_test
+          time_offset_test pemtest
 
   SOURCE[aborttest]=aborttest.c
   INCLUDE[aborttest]=../include
@@ -314,6 +314,10 @@ INCLUDE_MAIN___test_libtestutil_OLB = /INCLUDE=MAIN
   INCLUDE[x509_dup_cert_test]=../include
   DEPEND[x509_dup_cert_test]=../libcrypto libtestutil.a
 
+  SOURCE[pemtest]=pemtest.c
+  INCLUDE[pemtest]=../include .
+  DEPEND[pemtest]=../libcrypto libtestutil.a
+
   IF[{- !$disabled{psk} -}]
     PROGRAMS_NO_INST=dtls_mtu_test
     SOURCE[dtls_mtu_test]=dtls_mtu_test.c ssltestlib.c
diff --git a/test/pemtest.c b/test/pemtest.c
new file mode 100644
index 0000000..c573278
--- /dev/null
+++ b/test/pemtest.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#include "testutil.h"
+
+static const char raw[] = "hello world";
+static const char encoded[] = "aGVsbG8gd29ybGQ=";
+static const char pemtype[] = "PEMTESTDATA";
+
+static int test_b64(void)
+{
+    BIO *b = BIO_new(BIO_s_mem());
+    char *name = NULL, *header = NULL;
+    unsigned char *data = NULL;
+    long len;
+    int ret = 0;
+
+    if (!TEST_ptr(b)
+        || !TEST_true(BIO_printf(b, "-----BEGIN %s-----\n", pemtype))
+        || !TEST_true(BIO_printf(b, "%s\n", encoded))
+        || !TEST_true(BIO_printf(b, "-----END %s-----\n", pemtype))
+        || !TEST_true(PEM_read_bio_ex(b, &name, &header, &data, &len,
+                                      PEM_FLAG_ONLY_B64)))
+        goto err;
+    if (!TEST_int_eq(memcmp(pemtype, name, sizeof(pemtype) - 1), 0)
+        || !TEST_int_eq(len,sizeof(raw) - 1)
+        || !TEST_int_eq(memcmp(data, raw, sizeof(raw) - 1), 0))
+        goto err;
+    ret = 1;
+ err:
+    BIO_free(b);
+    OPENSSL_free(name);
+    OPENSSL_free(header);
+    OPENSSL_free(data);
+    return ret;
+}
+
+static int test_invalid(void)
+{
+    BIO *b = BIO_new(BIO_s_mem());
+    char *name = NULL, *header = NULL;
+    unsigned char *data = NULL;
+    long len;
+
+    if (!TEST_ptr(b)
+        || !TEST_true(BIO_printf(b, "-----BEGIN %s-----\n", pemtype))
+        || !TEST_true(BIO_printf(b, "%c%s\n", '\t', encoded))
+        || !TEST_true(BIO_printf(b, "-----END %s-----\n", pemtype))
+        /* Expected to fail due to non-base64 character */
+        || TEST_true(PEM_read_bio_ex(b, &name, &header, &data, &len,
+                                     PEM_FLAG_ONLY_B64))) {
+        BIO_free(b);
+        return 0;
+    }
+    BIO_free(b);
+    OPENSSL_free(name);
+    OPENSSL_free(header);
+    OPENSSL_free(data);
+    return 1;
+}
+
+void register_tests(void)
+{
+    ADD_TEST(test_b64);
+    ADD_TEST(test_invalid);
+}
diff --git a/test/recipes/04-test_pem.t b/test/recipes/04-test_pem.t
index 23d1ef8..c37c987 100644
--- a/test/recipes/04-test_pem.t
+++ b/test/recipes/04-test_pem.t
@@ -76,7 +76,7 @@ my %dsa_expected = (
     "dsa.pem" => 1
 );
 
-plan tests =>  scalar keys(%cert_expected) + scalar keys(%dsa_expected) + 1;
+plan tests =>  scalar keys(%cert_expected) + scalar keys(%dsa_expected) + 2;
 
 foreach my $input (keys %cert_expected) {
     my @common = ($cmd, "x509", "-text", "-noout", "-inform", "PEM", "-in");
@@ -100,3 +100,5 @@ SKIP: {
     my @match = grep /00:a0:3a:21:14:5d:cd:b6:d5:a0:3e:49:23:c1:3a:/, @data;
     ok(scalar @match > 0 ? 1 : 0);
 }
+
+ok(run(test(["pemtest"])), "running pemtest");
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 2e82042..d6c122d 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -4288,3 +4288,5 @@ TS_CONF_set_ess_cert_id_digest          4230	1_1_1	EXIST::FUNCTION:TS
 ESS_SIGNING_CERT_V2_free                4231	1_1_1	EXIST::FUNCTION:TS
 ESS_SIGNING_CERT_V2_dup                 4232	1_1_1	EXIST::FUNCTION:TS
 ESS_CERT_ID_V2_new                      4233	1_1_1	EXIST::FUNCTION:TS
+PEM_read_bio_ex                         4234	1_1_1	EXIST::FUNCTION:
+PEM_bytes_read_bio_secmem               4235	1_1_1	EXIST::FUNCTION:


More information about the openssl-commits mailing list