aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGilles Vollant <info@winimage.com>2020-05-15 10:47:46 +0200
committerDaniel Stenberg <daniel@haxx.se>2020-05-15 13:03:59 +0200
commitcac5374298b3e79405bbdabe38941227c73a4c96 (patch)
treeb3548eba0d3ea4538765897a7ef01154805788e5 /lib
parent8df455479f8801bbebad8839fc96abbffa711603 (diff)
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
Diffstat (limited to 'lib')
-rw-r--r--lib/easy.c11
-rw-r--r--lib/setopt.c66
-rw-r--r--lib/setopt.h4
-rw-r--r--lib/url.c12
-rw-r--r--lib/urldata.h13
-rw-r--r--lib/vtls/openssl.c264
-rw-r--r--lib/vtls/schannel.c143
-rw-r--r--lib/vtls/sectransp.c63
8 files changed, 452 insertions, 124 deletions
diff --git a/lib/easy.c b/lib/easy.c
index 988ff613c..1deedb265 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -764,6 +764,7 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src)
{
CURLcode result = CURLE_OK;
enum dupstring i;
+ enum dupblob j;
/* Copy src->set into dst->set first, then deal with the strings
afterwards */
@@ -780,6 +781,16 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src)
return result;
}
+ /* clear all blob pointers first */
+ memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *));
+ /* duplicate all blobs */
+ for(j = (enum dupblob)0; j < BLOB_LAST; j++) {
+ result = Curl_setblobopt(&dst->set.blobs[j], src->set.blobs[j]);
+ /* Curl_setstropt return CURLE_BAD_FUNCTION_ARGUMENT with blob */
+ if(result)
+ return result;
+ }
+
/* duplicate memory areas pointed to */
i = STRING_COPYPOSTFIELDS;
if(src->set.postfieldsize && src->set.str[i]) {
diff --git a/lib/setopt.c b/lib/setopt.c
index 93ba0975a..4570cc06a 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -77,6 +77,37 @@ CURLcode Curl_setstropt(char **charp, const char *s)
return CURLE_OK;
}
+CURLcode Curl_setblobopt(struct curl_blob **blobp,
+ const struct curl_blob *blob)
+{
+ /* free the previous storage at `blobp' and replace by a dynamic storage
+ copy of blob. If CURL_BLOB_COPY is set, the data is copied. */
+
+ Curl_safefree(*blobp);
+
+ if(blob) {
+ struct curl_blob *nblob;
+ if(blob->len > CURL_MAX_INPUT_LENGTH)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ nblob = (struct curl_blob *)
+ malloc(sizeof(struct curl_blob) +
+ ((blob->flags & CURL_BLOB_COPY) ? blob->len : 0));
+ if(!nblob)
+ return CURLE_OUT_OF_MEMORY;
+ *nblob = *blob;
+ if(blob->flags & CURL_BLOB_COPY) {
+ /* put the data after the blob struct in memory */
+ nblob->data = (char *)nblob + sizeof(struct curl_blob);
+ memcpy(nblob->data, blob->data, blob->len);
+ }
+
+ *blobp = nblob;
+ return CURLE_OK;
+ }
+
+ return CURLE_OK;
+}
+
static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
{
CURLcode result = CURLE_OK;
@@ -1606,6 +1637,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_CERT_ORIG],
va_arg(param, char *));
break;
+ case CURLOPT_SSLCERT_BLOB:
+ /*
+ * Blob that holds file name of the SSL certificate to use
+ */
+ result = Curl_setblobopt(&data->set.blobs[BLOB_CERT_ORIG],
+ va_arg(param, struct curl_blob *));
+ break;
#ifndef CURL_DISABLE_PROXY
case CURLOPT_PROXY_SSLCERT:
/*
@@ -1614,6 +1652,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_CERT_PROXY],
va_arg(param, char *));
break;
+ case CURLOPT_PROXY_SSLCERT_BLOB:
+ /*
+ * Blob that holds file name of the SSL certificate to use for proxy
+ */
+ result = Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY],
+ va_arg(param, struct curl_blob *));
+ break;
#endif
case CURLOPT_SSLCERTTYPE:
/*
@@ -1638,6 +1683,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_KEY_ORIG],
va_arg(param, char *));
break;
+ case CURLOPT_SSLKEY_BLOB:
+ /*
+ * Blob that holds file name of the SSL key to use
+ */
+ result = Curl_setblobopt(&data->set.blobs[BLOB_KEY_ORIG],
+ va_arg(param, struct curl_blob *));
+ break;
#ifndef CURL_DISABLE_PROXY
case CURLOPT_PROXY_SSLKEY:
/*
@@ -1646,6 +1698,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_KEY_PROXY],
va_arg(param, char *));
break;
+ case CURLOPT_PROXY_SSLKEY_BLOB:
+ /*
+ * Blob that holds file name of the SSL key to use for proxy
+ */
+ result = Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY],
+ va_arg(param, struct curl_blob *));
+ break;
#endif
case CURLOPT_SSLKEYTYPE:
/*
@@ -1970,6 +2029,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_ORIG],
va_arg(param, char *));
break;
+ case CURLOPT_ISSUERCERT_BLOB:
+ /*
+ * Blob that holds Issuer certificate to check certificates issuer
+ */
+ result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_ORIG],
+ va_arg(param, struct curl_blob *));
+ break;
#ifndef CURL_DISABLE_TELNET
case CURLOPT_TELNETOPTIONS:
/*
diff --git a/lib/setopt.h b/lib/setopt.h
index 5e347dd66..5fc4368dc 100644
--- a/lib/setopt.h
+++ b/lib/setopt.h
@@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -23,6 +23,8 @@
***************************************************************************/
CURLcode Curl_setstropt(char **charp, const char *s);
+CURLcode Curl_setblobopt(struct curl_blob **blobp,
+ const struct curl_blob *blob);
CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg);
#endif /* HEADER_CURL_SETOPT_H */
diff --git a/lib/url.c b/lib/url.c
index 9b8b2bdde..0173dc88a 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -281,10 +281,16 @@ void Curl_freeset(struct Curl_easy *data)
{
/* Free all dynamic strings stored in the data->set substructure. */
enum dupstring i;
+ enum dupblob j;
+
for(i = (enum dupstring)0; i < STRING_LAST; i++) {
Curl_safefree(data->set.str[i]);
}
+ for(j = (enum dupblob)0; j < BLOB_LAST; j++) {
+ Curl_safefree(data->set.blobs[j]);
+ }
+
if(data->change.referer_alloc) {
Curl_safefree(data->change.referer);
data->change.referer_alloc = FALSE;
@@ -3617,6 +3623,12 @@ static CURLcode create_conn(struct Curl_easy *data,
data->set.proxy_ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY];
#endif
+ data->set.ssl.cert_blob = data->set.blobs[BLOB_CERT_ORIG];
+ data->set.proxy_ssl.cert_blob = data->set.blobs[BLOB_CERT_PROXY];
+ data->set.ssl.key_blob = data->set.blobs[BLOB_KEY_ORIG];
+ data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY];
+ data->set.ssl.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_ORIG];
+
if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary,
&conn->ssl_config)) {
result = CURLE_OUT_OF_MEMORY;
diff --git a/lib/urldata.h b/lib/urldata.h
index ddee7da31..9b4ce5f5b 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -240,11 +240,14 @@ struct ssl_config_data {
long certverifyresult; /* result from the certificate verification */
char *CRLfile; /* CRL to check certificate revocation */
char *issuercert;/* optional issuer certificate filename */
+ struct curl_blob *issuercert_blob;
curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */
void *fsslctxp; /* parameter for call back */
char *cert; /* client certificate file name */
+ struct curl_blob *cert_blob;
char *cert_type; /* format for certificate (default: PEM)*/
char *key; /* private key file name */
+ struct curl_blob *key_blob;
char *key_type; /* format for private key (default: PEM) */
char *key_passwd; /* plain text private key password */
#ifdef USE_TLS_SRP
@@ -1580,6 +1583,15 @@ enum dupstring {
STRING_LAST /* not used, just an end-of-list marker */
};
+enum dupblob {
+ BLOB_CERT_ORIG,
+ BLOB_CERT_PROXY,
+ BLOB_KEY_ORIG,
+ BLOB_KEY_PROXY,
+ BLOB_SSL_ISSUERCERT_ORIG,
+ BLOB_LAST
+};
+
/* callback that gets called when this easy handle is completed within a multi
handle. Only used for internally created transfers, like for example
DoH. */
@@ -1711,6 +1723,7 @@ struct UserDefined {
long new_directory_perms; /* Permissions to use when creating remote dirs */
long ssh_auth_types; /* allowed SSH auth types */
char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */
+ struct curl_blob *blobs[BLOB_LAST];
unsigned int scope_id; /* Scope id for IPv6 */
long allowed_protocols;
long redir_protocols;
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);
diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c
index 1305205dc..f1499786f 100644
--- a/lib/vtls/schannel.c
+++ b/lib/vtls/schannel.c
@@ -39,6 +39,7 @@
#include "schannel.h"
#include "vtls.h"
+#include "strcase.h"
#include "sendf.h"
#include "connect.h" /* for the connect timeout */
#include "strerror.h"
@@ -583,94 +584,122 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
#ifdef HAS_CLIENT_CERT_PATH
/* client certificate */
- if(data->set.ssl.cert) {
- DWORD cert_store_name;
+ if(data->set.ssl.cert || data->set.ssl.cert_blob) {
+ DWORD cert_store_name = 0;
TCHAR *cert_store_path = NULL;
- TCHAR *cert_thumbprint_str;
+ TCHAR *cert_thumbprint_str = NULL;
CRYPT_HASH_BLOB cert_thumbprint;
BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN];
- HCERTSTORE cert_store;
+ HCERTSTORE cert_store = NULL;
FILE *fInCert = NULL;
+ void *certdata = NULL;
+ size_t certsize = 0;
+ bool blob = data->set.ssl.cert_blob != NULL;
+ TCHAR *cert_path = NULL;
+ if(blob) {
+ certdata = data->set.ssl.cert_blob->data;
+ certsize = data->set.ssl.cert_blob->len;
+ }
+ else {
+ cert_path = curlx_convert_UTF8_to_tchar(data->set.ssl.cert);
+ if(!cert_path)
+ return CURLE_OUT_OF_MEMORY;
- TCHAR *cert_path = curlx_convert_UTF8_to_tchar(data->set.ssl.cert);
- if(!cert_path)
- return CURLE_OUT_OF_MEMORY;
+ result = get_cert_location(cert_path, &cert_store_name,
+ &cert_store_path, &cert_thumbprint_str);
+
+ if(result && (data->set.ssl.cert[0]!='\0'))
+ fInCert = fopen(data->set.ssl.cert, "rb");
- result = get_cert_location(cert_path, &cert_store_name,
- &cert_store_path, &cert_thumbprint_str);
- if((result != CURLE_OK) && (data->set.ssl.cert[0]!='\0'))
- fInCert = fopen(data->set.ssl.cert, "rb");
+ if(result && !fInCert) {
+ failf(data, "schannel: Failed to get certificate location"
+ " or file for %s",
+ data->set.ssl.cert);
+ curlx_unicodefree(cert_path);
+ return result;
+ }
+ }
- if((result != CURLE_OK) && (fInCert == NULL)) {
- failf(data, "schannel: Failed to get certificate location"
- " or file for %s",
- data->set.ssl.cert);
+ if((fInCert || blob) && (data->set.ssl.cert_type) &&
+ (!strcasecompare(data->set.ssl.cert_type, "P12"))) {
+ failf(data, "schannel: certificate format compatibility error "
+ " for %s",
+ blob ? "(memory blob)" : data->set.ssl.cert);
curlx_unicodefree(cert_path);
- return result;
+ return CURLE_SSL_CERTPROBLEM;
}
- if(fInCert) {
+ if(fInCert || blob) {
/* Reading a .P12 or .pfx file, like the example at bottom of
- https://social.msdn.microsoft.com/Forums/windowsdesktop/
- en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5
+ https://social.msdn.microsoft.com/Forums/windowsdesktop/
+ en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5
*/
- void *certdata = NULL;
- long filesize = 0;
CRYPT_DATA_BLOB datablob;
WCHAR* pszPassword;
size_t pwd_len = 0;
int str_w_len = 0;
- int continue_reading = fseek(fInCert, 0, SEEK_END) == 0;
- if(continue_reading)
- filesize = ftell(fInCert);
- if(filesize < 0)
- continue_reading = 0;
- if(continue_reading)
- continue_reading = fseek(fInCert, 0, SEEK_SET) == 0;
- if(continue_reading)
- certdata = malloc(((size_t)filesize) + 1);
- if((certdata == NULL) ||
- ((int) fread(certdata, (size_t)filesize, 1, fInCert) != 1))
- continue_reading = 0;
- fclose(fInCert);
+ const char *cert_showfilename_error = blob ?
+ "(memory blob)" : data->set.ssl.cert;
curlx_unicodefree(cert_path);
-
- if(!continue_reading) {
- failf(data, "schannel: Failed to read cert file %s",
+ if(fInCert) {
+ long cert_tell = 0;
+ bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0;
+ if(continue_reading)
+ cert_tell = ftell(fInCert);
+ if(cert_tell < 0)
+ continue_reading = FALSE;
+ else
+ certsize = (size_t)cert_tell;
+ if(continue_reading)
+ continue_reading = fseek(fInCert, 0, SEEK_SET) == 0;
+ if(continue_reading)
+ certdata = malloc(certsize + 1);
+ if((!certdata) ||
+ ((int) fread(certdata, certsize, 1, fInCert) != 1))
+ continue_reading = FALSE;
+ fclose(fInCert);
+ if(!continue_reading) {
+ failf(data, "schannel: Failed to read cert file %s",
data->set.ssl.cert);
- free(certdata);
- return CURLE_SSL_CERTPROBLEM;
+ free(certdata);
+ return CURLE_SSL_CERTPROBLEM;
+ }
}
/* Convert key-pair data to the in-memory certificate store */
datablob.pbData = (BYTE*)certdata;
- datablob.cbData = (DWORD)filesize;
+ datablob.cbData = (DWORD)certsize;
if(data->set.ssl.key_passwd != NULL)
pwd_len = strlen(data->set.ssl.key_passwd);
pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1));
- if(pwd_len > 0)
- str_w_len =
- MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- data->set.ssl.key_passwd, (int)pwd_len,
- pszPassword, (int)(pwd_len + 1));
-
- if((str_w_len >= 0) && (str_w_len <= (int)pwd_len))
- pszPassword[str_w_len] = 0;
- else
- pszPassword[0] = 0;
-
- cert_store = PFXImportCertStore(&datablob, pszPassword, 0);
- free(pszPassword);
- free(certdata);
+ if(pszPassword) {
+ if(pwd_len > 0)
+ str_w_len = MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ data->set.ssl.key_passwd, (int)pwd_len,
+ pszPassword, (int)(pwd_len + 1));
+
+ if((str_w_len >= 0) && (str_w_len <= (int)pwd_len))
+ pszPassword[str_w_len] = 0;
+ else
+ pszPassword[0] = 0;
+
+ cert_store = PFXImportCertStore(&datablob, pszPassword, 0);
+ free(pszPassword);
+ }
+ if(!blob)
+ free(certdata);
if(cert_store == NULL) {
DWORD errorcode = GetLastError();
if(errorcode == ERROR_INVALID_PASSWORD)
failf(data, "schannel: Failed to import cert file %s, "
- "password is bad", data->set.ssl.cert);
+ "password is bad",
+ cert_showfilename_error);
else
failf(data, "schannel: Failed to import cert file %s, "
- "last error is 0x%x", data->set.ssl.cert, errorcode);
+ "last error is 0x%x",
+ cert_showfilename_error, errorcode);
return CURLE_SSL_CERTPROBLEM;
}
@@ -681,7 +710,7 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
if(client_certs[0] == NULL) {
failf(data, "schannel: Failed to get certificate from file %s"
", last error is 0x%x",
- data->set.ssl.cert, GetLastError());
+ cert_showfilename_error, GetLastError());
CertCloseStore(cert_store, 0);
return CURLE_SSL_CERTPROBLEM;
}
diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c
index 6b2d436fc..135d34cf0 100644
--- a/lib/vtls/sectransp.c
+++ b/lib/vtls/sectransp.c
@@ -1126,12 +1126,12 @@ static OSStatus CopyIdentityWithLabel(char *label,
}
static OSStatus CopyIdentityFromPKCS12File(const char *cPath,
+ const struct curl_blob *blob,
const char *cPassword,
SecIdentityRef *out_cert_and_key)
{
OSStatus status = errSecItemNotFound;
- CFURLRef pkcs_url = CFURLCreateFromFileSystemRepresentation(NULL,
- (const UInt8 *)cPath, strlen(cPath), false);
+ CFURLRef pkcs_url = NULL;
CFStringRef password = cPassword ? CFStringCreateWithCString(NULL,
cPassword, kCFStringEncodingUTF8) : NULL;
CFDataRef pkcs_data = NULL;
@@ -1140,8 +1140,26 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath,
/* These constants are documented as having first appeared in 10.6 but they
raise linker errors when used on that cat for some reason. */
#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS
- if(CFURLCreateDataAndPropertiesFromResource(NULL, pkcs_url, &pkcs_data,
- NULL, NULL, &status)) {
+ bool resource_imported;
+
+ if(blob) {
+ pkcs_data = CFDataCreate(kCFAllocatorDefault,
+ (const unsigned char *)blob->data, blob->len);
+ status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate;
+ resource_imported = (pkcs_data != NULL);
+ }
+ else {
+ pkcs_url =
+ CFURLCreateFromFileSystemRepresentation(NULL,
+ (const UInt8 *)cPath,
+ strlen(cPath), false);
+ resource_imported =
+ CFURLCreateDataAndPropertiesFromResource(NULL,
+ pkcs_url, &pkcs_data,
+ NULL, NULL, &status);
+ }
+
+ if(resource_imported) {
CFArrayRef items = NULL;
/* On iOS SecPKCS12Import will never add the client certificate to the
@@ -1219,7 +1237,8 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath,
#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */
if(password)
CFRelease(password);
- CFRelease(pkcs_url);
+ if(pkcs_url)
+ CFRelease(pkcs_url);
return status;
}
@@ -1376,8 +1395,10 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct ssl_backend_data *backend = connssl->backend;
const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
+ const struct curl_blob *ssl_cablob = NULL;
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
char * const ssl_cert = SSL_SET_OPTION(cert);
+ const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(cert_blob);
const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port;
@@ -1612,15 +1633,16 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
"Transport. The private key must be in the Keychain.\n");
}
- if(ssl_cert) {
+ if(ssl_cert || ssl_cert_blob) {
+ bool is_cert_data = ssl_cert_blob != NULL;
+ bool is_cert_file = (!is_cert_data) && is_file(ssl_cert);
SecIdentityRef cert_and_key = NULL;
- bool is_cert_file = is_file(ssl_cert);
/* User wants to authenticate with a client cert. Look for it:
If we detect that this is a file on disk, then let's load it.
Otherwise, assume that the user wants to use an identity loaded
from the Keychain. */
- if(is_cert_file) {
+ if(is_cert_file || is_cert_data) {
if(!SSL_SET_OPTION(cert_type))
infof(data, "WARNING: SSL: Certificate type not set, assuming "
"PKCS#12 format.\n");
@@ -1629,7 +1651,7 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
infof(data, "WARNING: SSL: The Security framework only supports "
"loading identities that are in PKCS#12 format.\n");
- err = CopyIdentityFromPKCS12File(ssl_cert,
+ err = CopyIdentityFromPKCS12File(ssl_cert, ssl_cert_blob,
SSL_SET_OPTION(key_passwd), &cert_and_key);
}
else
@@ -1669,27 +1691,30 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
CFRelease(cert_and_key);
}
else {
+ const char *cert_showfilename_error =
+ is_cert_data ? "(memory blob)" : ssl_cert;
+
switch(err) {
case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */
failf(data, "SSL: Incorrect password for the certificate \"%s\" "
- "and its private key.", ssl_cert);
+ "and its private key.", cert_showfilename_error);
break;
case -26275: /* errSecDecode */ case -25257: /* errSecUnknownFormat */
failf(data, "SSL: Couldn't make sense of the data in the "
"certificate \"%s\" and its private key.",
- ssl_cert);
+ cert_showfilename_error);
break;
case -25260: /* errSecPassphraseRequired */
failf(data, "SSL The certificate \"%s\" requires a password.",
- ssl_cert);
+ cert_showfilename_error);
break;
case errSecItemNotFound:
failf(data, "SSL: Can't find the certificate \"%s\" and its private "
- "key in the Keychain.", ssl_cert);
+ "key in the Keychain.", cert_showfilename_error);
break;
default:
failf(data, "SSL: Can't load the certificate \"%s\" and its private "
- "key: OSStatus %d", ssl_cert, err);
+ "key: OSStatus %d", cert_showfilename_error, err);
break;
}
return CURLE_SSL_CERTPROBLEM;
@@ -1721,7 +1746,8 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
#else
if(SSLSetSessionOption != NULL) {
#endif /* CURL_BUILD_MAC */
- bool break_on_auth = !conn->ssl_config.verifypeer || ssl_cafile;
+ bool break_on_auth = !conn->ssl_config.verifypeer ||
+ ssl_cafile || ssl_cablob;
err = SSLSetSessionOption(backend->ssl_ctx,
kSSLSessionOptionBreakOnServerAuth,
break_on_auth);
@@ -1749,10 +1775,11 @@ static CURLcode sectransp_connect_step1(struct connectdata *conn,
}
#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */
- if(ssl_cafile && verifypeer) {
- bool is_cert_file = is_file(ssl_cafile);
+ if((ssl_cafile || ssl_cablob) && verifypeer) {
+ bool is_cert_data = ssl_cablob != NULL;
+ bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile);
- if(!is_cert_file) {
+ if(!(is_cert_file || is_cert_data)) {
failf(data, "SSL: can't load CA certificate file %s", ssl_cafile);
return CURLE_SSL_CACERT_BADFILE;
}