From: Viktor Dukhovni Date: Sun, 24 Apr 2016 23:48:50 +0000 (-0400) Subject: Improve and document low-level PEM read routines X-Git-Tag: OpenSSL_1_1_0-pre6~773 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=67787844f11fd7614bb26452fda1a1de3ed005ef;p=oweals%2Fopenssl.git Improve and document low-level PEM read routines PEM_read(), PEM_read_bio(), PEM_get_EVP_CIPHER_INFO() and PEM_do_header(). Reviewed-by: Dr. Stephen Henson --- diff --git a/crypto/pem/pem_lib.c b/crypto/pem/pem_lib.c index 56865541aa..42b46dc4d5 100644 --- a/crypto/pem/pem_lib.c +++ b/crypto/pem/pem_lib.c @@ -9,6 +9,7 @@ #include #include +#include #include "internal/cryptlib.h" #include #include @@ -389,115 +390,153 @@ int PEM_ASN1_write_bio(i2d_of_void *i2d, const char *name, BIO *bp, int PEM_do_header(EVP_CIPHER_INFO *cipher, unsigned char *data, long *plen, pem_password_cb *callback, void *u) { - int i = 0, j, o, klen; - long len; + int ok; + int keylen; + long len = *plen; + int ilen = (int) len; /* EVP_DecryptUpdate etc. take int lengths */ EVP_CIPHER_CTX *ctx; unsigned char key[EVP_MAX_KEY_LENGTH]; char buf[PEM_BUFSIZE]; - len = *plen; +#if LONG_MAX > INT_MAX + /* Check that we did not truncate the length */ + if (len > INT_MAX) { + PEMerr(PEM_F_PEM_DO_HEADER, PEM_R_HEADER_TOO_LONG); + return 0; + } +#endif if (cipher->cipher == NULL) - return (1); + return 1; if (callback == NULL) - klen = PEM_def_callback(buf, PEM_BUFSIZE, 0, u); + keylen = PEM_def_callback(buf, PEM_BUFSIZE, 0, u); else - klen = callback(buf, PEM_BUFSIZE, 0, u); - if (klen <= 0) { + keylen = callback(buf, PEM_BUFSIZE, 0, u); + if (keylen <= 0) { PEMerr(PEM_F_PEM_DO_HEADER, PEM_R_BAD_PASSWORD_READ); - return (0); + return 0; } #ifdef CHARSET_EBCDIC /* Convert the pass phrase from EBCDIC */ - ebcdic2ascii(buf, buf, klen); + ebcdic2ascii(buf, buf, keylen); #endif if (!EVP_BytesToKey(cipher->cipher, EVP_md5(), &(cipher->iv[0]), - (unsigned char *)buf, klen, 1, key, NULL)) + (unsigned char *)buf, keylen, 1, key, NULL)) return 0; - j = (int)len; ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return 0; - o = EVP_DecryptInit_ex(ctx, cipher->cipher, NULL, key, &(cipher->iv[0])); - if (o) - o = EVP_DecryptUpdate(ctx, data, &i, data, j); - if (o) - o = EVP_DecryptFinal_ex(ctx, &(data[i]), &j); + + ok = EVP_DecryptInit_ex(ctx, cipher->cipher, NULL, key, &(cipher->iv[0])); + if (ok) + ok = EVP_DecryptUpdate(ctx, data, &ilen, data, ilen); + if (ok) { + /* Squirrel away the length of data decrypted so far. */ + *plen = ilen; + ok = EVP_DecryptFinal_ex(ctx, &(data[ilen]), &ilen); + } + if (ok) + *plen += ilen; + else + PEMerr(PEM_F_PEM_DO_HEADER, PEM_R_BAD_DECRYPT); + EVP_CIPHER_CTX_free(ctx); OPENSSL_cleanse((char *)buf, sizeof(buf)); OPENSSL_cleanse((char *)key, sizeof(key)); - if (o) - j += i; - else { - PEMerr(PEM_F_PEM_DO_HEADER, PEM_R_BAD_DECRYPT); - return (0); - } - *plen = j; - return (1); + return ok; } +/* + * This implements a very limited PEM header parser that does not support the + * full grammar of rfc1421. In particular, folded headers are not supported, + * nor is additional whitespace. + * + * A robust implementation would make use of a library that turns the headers + * into a BIO from which one folded line is read at a time, and is then split + * into a header label and content. We would then parse the content of the + * headers we care about. This is overkill for just this limited use-case, but + * presumably we also parse rfc822-style headers for S/MIME, so a common + * abstraction might well be more generally useful. + */ int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher) { + static const char ProcType[] = "Proc-Type:"; + static const char ENCRYPTED[] = "ENCRYPTED"; + static const char DEKInfo[] = "DEK-Info:"; const EVP_CIPHER *enc = NULL; + int ivlen; char *dekinfostart, c; cipher->cipher = NULL; if ((header == NULL) || (*header == '\0') || (*header == '\n')) - return (1); - if (strncmp(header, "Proc-Type: ", 11) != 0) { + return 1; + + if (strncmp(header, ProcType, sizeof(ProcType)-1) != 0) { PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_NOT_PROC_TYPE); - return (0); + return 0; } - header += 11; - if (*header != '4') - return (0); - header++; - if (*header != ',') - return (0); - header++; - if (strncmp(header, "ENCRYPTED", 9) != 0) { + header += sizeof(ProcType)-1; + header += strspn(header, " \t"); + + if (*header++ != '4' || *header++ != ',') + return 0; + header += strspn(header, " \t"); + + /* We expect "ENCRYPTED" followed by optional white-space + line break */ + if (strncmp(header, ENCRYPTED, sizeof(ENCRYPTED)-1) != 0 || + strspn(header+sizeof(ENCRYPTED)-1, " \t\r\n") == 0) { PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_NOT_ENCRYPTED); - return (0); + return 0; } - for (; (*header != '\n') && (*header != '\0'); header++) ; - if (*header == '\0') { + header += sizeof(ENCRYPTED)-1; + header += strspn(header, " \t\r"); + if (*header++ != '\n') { PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_SHORT_HEADER); - return (0); + return 0; } - header++; - if (strncmp(header, "DEK-Info: ", 10) != 0) { + + /*- + * https://tools.ietf.org/html/rfc1421#section-4.6.1.3 + * We expect "DEK-Info: algo[,hex-parameters]" + */ + if (strncmp(header, DEKInfo, sizeof(DEKInfo)-1) != 0) { PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_NOT_DEK_INFO); - return (0); + return 0; } - header += 10; + header += sizeof(DEKInfo)-1; + header += strspn(header, " \t"); + /* + * DEK-INFO is a comma-separated combination of algorithm name and optional + * parameters. + */ dekinfostart = header; - for (;;) { - c = *header; -#ifndef CHARSET_EBCDIC - if (!(((c >= 'A') && (c <= 'Z')) || (c == '-') || - ((c >= '0') && (c <= '9')))) - break; -#else - if (!(isupper(c) || (c == '-') || isdigit(c))) - break; -#endif - header++; - } + header += strcspn(header, " \t,"); + c = *header; *header = '\0'; cipher->cipher = enc = EVP_get_cipherbyname(dekinfostart); - *header++ = c; + *header = c; + header += strspn(header, " \t"); if (enc == NULL) { PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_UNSUPPORTED_ENCRYPTION); - return (0); + return 0; } + ivlen = EVP_CIPHER_iv_length(enc); + if (ivlen > 0 && *header++ != ',') { + PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_MISSING_DEK_IV); + return 0; + } else if (ivlen == 0 && *header == ',') { + PEMerr(PEM_F_PEM_GET_EVP_CIPHER_INFO, PEM_R_UNEXPECTED_DEK_IV); + return 0; + } + if (!load_iv(&header, cipher->iv, EVP_CIPHER_iv_length(enc))) - return (0); + return 0; - return (1); + return 1; } static int load_iv(char **fromp, unsigned char *to, int num) diff --git a/doc/crypto/pem_read.pod b/doc/crypto/pem_read.pod new file mode 100644 index 0000000000..b0ec0d8eca --- /dev/null +++ b/doc/crypto/pem_read.pod @@ -0,0 +1,90 @@ +=pod + +=head1 NAME + +PEM_read, PEM_read_bio, PEM_do_header - low-level PEM routines + +=head1 SYNOPSIS + + #include + + int PEM_read(FILE *fp, char **name, char **header, + unsigned char **data, long *len); + int PEM_read_bio(BIO *bp, char **name, char **header, + unsigned char **data, long *len); + int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cinfo); + int PEM_do_header(EVP_CIPHER_INFO *cinfo, unsigned char *data, long *len, + pem_password_cb *cb, void *u); + +=head1 DESCRIPTION + +These functions read and decode PEM-encoded objects, returning the +PEM type B, any encapsulation B
and the decoded +B of length B. + +PEM_read() reads from the stdio file handle B, while PEM_read_bio() reads +from the BIO B. +Both skip any non-PEM data that precedes the start of the next PEM object. +When an object is successfuly retrieved, the type name from the "----BEGIN +-----" is returned via the B argument, any encapsulation headers +are returned in B
and the base64-decoded content and its length are +returned via B and B respectively. +The B, B
and B pointers are allocated via OPENSSL_malloc() +and should be freed by the caller via OPENSSL_free() when no longer needed. + +PEM_get_EVP_CIPHER_INFO() can be used to determine the B returned by +PEM_read() or PEM_read_bio() is encrypted and to retrieve the associated cipher +and IV. +The caller passes a pointer to structure of type B via the +B argument and the B
returned via PEM_read() or PEM_read_bio(). +If the call is succesful 1 is retured and the cipher and IV are stored at the +address pointed to by B. +When the header is malformed, or not supported or when the cipher is unknown +or some internal error happens 0 is returned. +This function is deprecated, see B below. + +PEM_do_header() can then be used to decrypt the data if the header +indicates encryption. +The B argument is a pointer to the structure initialized by the previous +call to PEM_get_EVP_CIPHER_INFO(). +The B and B arguments are those returned by the previous call to +PEM_read() or PEM_read_bio(). +The B and B arguments make it possible to override the default password +prompt function as described in L. +On successful completion the B is decrypted in place, and B is +updated to indicate the plaintext length. +This function is deprecated, see B below. + +If the data is a priori known to not be encrypted, then neither PEM_do_header() +nor PEM_get_EVP_CIPHER_INFO() need be called. + +The final B buffer is typically an ASN.1 object which can be decoded with +the B function appropriate to the type B. + +=head1 RETURN VALUES + +PEM_read() and PEM_read_bio() return 1 on success and 0 on failure, the latter +includes the case when no more PEM objects remain in the input file. +To distinguish end of file from more serious errors the caller must peek at the +error stack and check for B, which indicates that no more +PEM objects were found. See L, L. + +PEM_get_EVP_CIPHER_INFO() and PEM_do_header() return 1 on success, and 0 on +failure. +The B is likely meaningless if these functions fail. + +=head1 NOTES + +The PEM_get_EVP_CIPHER_INFO() and PEM_do_header() functions are deprecated. +This is because the underlying PEM encryption format is obsolete, and should +be avoided. +It uses an encryption format with an OpenSSL-specific key-derivation function, +which employs MD5 with an iteration count of 1! +Instead, private keys should be stored in PKCS#8 form, with a strong PKCS#5 +v2.0 PBE. +See L and L and L. + +=head1 SEE ALSO + +L, L, L, L, +L.