From cac5374298b3e79405bbdabe38941227c73a4c96 Mon Sep 17 00:00:00 2001 From: Gilles Vollant Date: Fri, 15 May 2020 10:47:46 +0200 Subject: setopt: support certificate options in memory with struct curl_blob This change introduces a generic way to provide binary data in setopt options, called BLOBs. This change introduces these new setopts: CURLOPT_ISSUERCERT_BLOB, CURLOPT_PROXY_SSLCERT_BLOB, CURLOPT_PROXY_SSLKEY_BLOB, CURLOPT_SSLCERT_BLOB and CURLOPT_SSLKEY_BLOB. Reviewed-by: Daniel Stenberg Closes #5357 --- lib/vtls/openssl.c | 264 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 216 insertions(+), 48 deletions(-) (limited to 'lib/vtls/openssl.c') diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 5876d196f..2988a0c16 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -621,12 +621,136 @@ static bool is_pkcs11_uri(const char *string) static CURLcode Curl_ossl_set_engine(struct Curl_easy *data, const char *engine); +static int +SSL_CTX_use_certificate_bio(SSL_CTX *ctx, BIO *in, int type, + const char *key_passwd) +{ + int ret = 0; + X509 *x = NULL; + + if(type == SSL_FILETYPE_ASN1) { + /* j = ERR_R_ASN1_LIB; */ + x = d2i_X509_bio(in, NULL); + } + else if(type == SSL_FILETYPE_PEM) { + /* ERR_R_PEM_LIB; */ + x = PEM_read_bio_X509(in, NULL, + passwd_callback, (void *)key_passwd); + } + else { + ret = 0; + goto end; + } + + if(x == NULL) { + ret = 0; + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + end: + X509_free(x); + return ret; +} + +static int +SSL_CTX_use_PrivateKey_bio(SSL_CTX *ctx, BIO* in, int type, + const char *key_passwd) +{ + int ret = 0; + EVP_PKEY *pkey = NULL; + + if(type == SSL_FILETYPE_PEM) + pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, + (void *)key_passwd); + else if(type == SSL_FILETYPE_ASN1) + pkey = d2i_PrivateKey_bio(in, NULL); + else { + ret = 0; + goto end; + } + if(pkey == NULL) { + ret = 0; + goto end; + } + ret = SSL_CTX_use_PrivateKey(ctx, pkey); + EVP_PKEY_free(pkey); + end: + return ret; +} + +static int +SSL_CTX_use_certificate_chain_bio(SSL_CTX *ctx, BIO* in, + const char *key_passwd) +{ +/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */ +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) /* 1.0.2 or later */ + int ret = 0; + X509 *x = NULL; + void *passwd_callback_userdata = (void *)key_passwd; + + ERR_clear_error(); + + x = PEM_read_bio_X509_AUX(in, NULL, + passwd_callback, (void *)key_passwd); + + if(x == NULL) { + ret = 0; + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if(ERR_peek_error() != 0) + ret = 0; + + if(ret) { + X509 *ca; + unsigned long err; + + if(!SSL_CTX_clear_chain_certs(ctx)) { + ret = 0; + goto end; + } + + while((ca = PEM_read_bio_X509(in, NULL, passwd_callback, + passwd_callback_userdata)) + != NULL) { + + if(!SSL_CTX_add0_chain_cert(ctx, ca)) { + X509_free(ca); + ret = 0; + goto end; + } + } + + err = ERR_peek_last_error(); + if((ERR_GET_LIB(err) == ERR_LIB_PEM) && + (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) + ERR_clear_error(); + else + ret = 0; + } + + end: + X509_free(x); + return ret; +#else + (void)ctx; /* unused */ + (void)in; /* unused */ + (void)key_passwd; /* unused */ + return 0; +#endif +} + static int cert_stuff(struct connectdata *conn, SSL_CTX* ctx, char *cert_file, + BIO *cert_bio, const char *cert_type, char *key_file, + BIO* key_bio, const char *key_type, char *key_passwd) { @@ -636,10 +760,11 @@ int cert_stuff(struct connectdata *conn, int file_type = do_file_type(cert_type); - if(cert_file || (file_type == SSL_FILETYPE_ENGINE)) { + if(cert_file || cert_bio || (file_type == SSL_FILETYPE_ENGINE)) { SSL *ssl; X509 *x509; int cert_done = 0; + int cert_use_result; if(key_passwd) { /* set the password in the callback userdata */ @@ -652,8 +777,10 @@ int cert_stuff(struct connectdata *conn, switch(file_type) { case SSL_FILETYPE_PEM: /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ - if(SSL_CTX_use_certificate_chain_file(ctx, - cert_file) != 1) { + cert_use_result = cert_bio ? + SSL_CTX_use_certificate_chain_bio(ctx, cert_bio, key_passwd) : + SSL_CTX_use_certificate_chain_file(ctx, cert_file); + if(cert_use_result != 1) { failf(data, "could not load PEM client certificate, " OSSL_PACKAGE " error %s, " @@ -668,9 +795,12 @@ int cert_stuff(struct connectdata *conn, /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but we use the case above for PEM so this can only be performed with ASN1 files. */ - if(SSL_CTX_use_certificate_file(ctx, - cert_file, - file_type) != 1) { + + cert_use_result = cert_bio ? + SSL_CTX_use_certificate_bio(ctx, cert_bio, + file_type, key_passwd) : + SSL_CTX_use_certificate_file(ctx, cert_file, file_type); + if(cert_use_result != 1) { failf(data, "could not load ASN1 client certificate, " OSSL_PACKAGE " error %s, " @@ -750,27 +880,31 @@ int cert_stuff(struct connectdata *conn, PKCS12 *p12 = NULL; EVP_PKEY *pri; STACK_OF(X509) *ca = NULL; + if(!cert_bio) { + fp = BIO_new(BIO_s_file()); + if(fp == NULL) { + failf(data, + "BIO_new return NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + return 0; + } - fp = BIO_new(BIO_s_file()); - if(fp == NULL) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return 0; + if(BIO_read_filename(fp, cert_file) <= 0) { + failf(data, "could not open PKCS12 file '%s'", cert_file); + BIO_free(fp); + return 0; + } } - if(BIO_read_filename(fp, cert_file) <= 0) { - failf(data, "could not open PKCS12 file '%s'", cert_file); + p12 = d2i_PKCS12_bio(cert_bio ? cert_bio : fp, NULL); + if(fp) BIO_free(fp); - return 0; - } - p12 = d2i_PKCS12_bio(fp, NULL); - BIO_free(fp); if(!p12) { - failf(data, "error reading PKCS12 file '%s'", cert_file); + failf(data, "error reading PKCS12 file '%s'", + cert_bio ? "(memory blob)" : cert_file); return 0; } @@ -851,8 +985,10 @@ int cert_stuff(struct connectdata *conn, return 0; } - if(!key_file) + if((!key_file) && (!key_bio)) { key_file = cert_file; + key_bio = cert_bio; + } else file_type = do_file_type(key_type); @@ -862,9 +998,12 @@ int cert_stuff(struct connectdata *conn, break; /* FALLTHROUGH */ case SSL_FILETYPE_ASN1: - if(SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type) != 1) { + cert_use_result = key_bio ? + SSL_CTX_use_PrivateKey_bio(ctx, key_bio, file_type, key_passwd) : + SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); + if(cert_use_result != 1) { failf(data, "unable to set private key file: '%s' type %s", - key_file, key_type?key_type:"PEM"); + key_file?key_file:"(memory blob)", key_type?key_type:"PEM"); return 0; } break; @@ -2418,6 +2557,7 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(authtype); #endif char * const ssl_cert = SSL_SET_OPTION(cert); + const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(cert_blob); const char * const ssl_cert_type = SSL_SET_OPTION(cert_type); const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); const char * const ssl_capath = SSL_CONN_CONFIG(CApath); @@ -2662,10 +2802,33 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) } #endif - if(ssl_cert || ssl_cert_type) { - if(!cert_stuff(conn, backend->ctx, ssl_cert, ssl_cert_type, - SSL_SET_OPTION(key), SSL_SET_OPTION(key_type), - SSL_SET_OPTION(key_passwd))) { + if(ssl_cert || ssl_cert_blob || ssl_cert_type) { + BIO *ssl_cert_bio = NULL; + BIO *ssl_key_bio = NULL; + int result_cert_stuff; + if(ssl_cert_blob) { + /* the typecast of blob->len is fine since it is guaranteed to never be + larger than CURL_MAX_INPUT_LENGTH */ + ssl_cert_bio = BIO_new_mem_buf(ssl_cert_blob->data, + (int)ssl_cert_blob->len); + if(!ssl_cert_bio) + return CURLE_SSL_CERTPROBLEM; + } + if(SSL_SET_OPTION(key_blob)) { + ssl_key_bio = BIO_new_mem_buf(SSL_SET_OPTION(key_blob)->data, + (int)SSL_SET_OPTION(key_blob)->len); + if(!ssl_key_bio) + return CURLE_SSL_CERTPROBLEM; + } + result_cert_stuff = cert_stuff(conn, backend->ctx, + ssl_cert, ssl_cert_bio, ssl_cert_type, + SSL_SET_OPTION(key), ssl_key_bio, + SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd)); + if(ssl_cert_bio) + BIO_free(ssl_cert_bio); + if(ssl_key_bio) + BIO_free(ssl_key_bio); + if(!result_cert_stuff) { /* failf() is already done in cert_stuff() */ return CURLE_SSL_CERTPROBLEM; } @@ -3714,27 +3877,32 @@ static CURLcode servercert(struct connectdata *conn, deallocating the certificate. */ /* e.g. match issuer name with provided issuer certificate */ - if(SSL_SET_OPTION(issuercert)) { - fp = BIO_new(BIO_s_file()); - if(fp == NULL) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_OUT_OF_MEMORY; - } + if(SSL_SET_OPTION(issuercert) || SSL_SET_OPTION(issuercert_blob)) { + if(SSL_SET_OPTION(issuercert_blob)) + fp = BIO_new_mem_buf(SSL_SET_OPTION(issuercert_blob)->data, + (int)SSL_SET_OPTION(issuercert_blob)->len); + else { + fp = BIO_new(BIO_s_file()); + if(fp == NULL) { + failf(data, + "BIO_new return NULL, " OSSL_PACKAGE + " error %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer)) ); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_OUT_OF_MEMORY; + } - if(BIO_read_filename(fp, SSL_SET_OPTION(issuercert)) <= 0) { - if(strict) - failf(data, "SSL: Unable to open issuer cert (%s)", - SSL_SET_OPTION(issuercert)); - BIO_free(fp); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; + if(BIO_read_filename(fp, SSL_SET_OPTION(issuercert)) <= 0) { + if(strict) + failf(data, "SSL: Unable to open issuer cert (%s)", + SSL_SET_OPTION(issuercert)); + BIO_free(fp); + X509_free(backend->server_cert); + backend->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } } issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); -- cgit v1.2.1