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 --- docs/libcurl/curl_easy_setopt.3 | 10 + docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.3 | 79 ++++++++ docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.3 | 72 +++++++ docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.3 | 73 +++++++ docs/libcurl/opts/CURLOPT_SSLCERT.3 | 10 +- docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.3 | 69 +++++++ docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.3 | 75 +++++++ docs/libcurl/opts/Makefile.inc | 5 + docs/libcurl/symbols-in-versions | 6 + include/curl/curl.h | 8 + include/curl/easy.h | 13 +- lib/easy.c | 11 ++ lib/setopt.c | 66 +++++++ lib/setopt.h | 4 +- lib/url.c | 12 ++ lib/urldata.h | 13 ++ lib/vtls/openssl.c | 264 ++++++++++++++++++++----- lib/vtls/schannel.c | 143 ++++++++------ lib/vtls/sectransp.c | 63 ++++-- src/tool_operate.c | 79 ++++++++ src/tool_setopt.c | 16 +- 21 files changed, 961 insertions(+), 130 deletions(-) create mode 100644 docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.3 create mode 100644 docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.3 create mode 100644 docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.3 create mode 100644 docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.3 create mode 100644 docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.3 diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 099ca658c..9692a10b7 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -506,16 +506,24 @@ Sets the interval at which connection upkeep are performed. See .SH SSL and SECURITY OPTIONS .IP CURLOPT_SSLCERT Client cert. See \fICURLOPT_SSLCERT(3)\fP +.IP CURLOPT_SSLCERT_BLOB +Client cert memory buffer. See \fICURLOPT_SSLCERT_BLOB(3)\fP .IP CURLOPT_PROXY_SSLCERT Proxy client cert. See \fICURLOPT_PROXY_SSLCERT(3)\fP +.IP CURLOPT_PROXY_SSLCERT_BLOB +Proxy client cert memory buffer. See \fICURLOPT_PROXY_SSLCERT_BLOB(3)\fP .IP CURLOPT_SSLCERTTYPE Client cert type. See \fICURLOPT_SSLCERTTYPE(3)\fP .IP CURLOPT_PROXY_SSLCERTTYPE Proxy client cert type. See \fICURLOPT_PROXY_SSLCERTTYPE(3)\fP .IP CURLOPT_SSLKEY Client key. See \fICURLOPT_SSLKEY(3)\fP +.IP CURLOPT_SSLKEY_BLOB +Client key memory buffer. See \fICURLOPT_SSLKEY_BLOB(3)\fP .IP CURLOPT_PROXY_SSLKEY Proxy client key. See \fICURLOPT_PROXY_SSLKEY(3)\fP +.IP CURLOPT_PROXY_SSLKEY_BLOB +Proxy client key. See \fICURLOPT_PROXY_SSLKEY_BLOB(3)\fP .IP CURLOPT_SSLKEYTYPE Client key type. See \fICURLOPT_SSLKEYTYPE(3)\fP .IP CURLOPT_PROXY_SSLKEYTYPE @@ -554,6 +562,8 @@ CA cert bundle. See \fICURLOPT_CAINFO(3)\fP Proxy CA cert bundle. See \fICURLOPT_PROXY_CAINFO(3)\fP .IP CURLOPT_ISSUERCERT Issuer certificate. See \fICURLOPT_ISSUERCERT(3)\fP +.IP CURLOPT_ISSUERCERT_BLOB +Issuer certificate memory buffer. See \fICURLOPT_ISSUERCERT_BLOB(3)\fP .IP CURLOPT_CAPATH Path to CA cert bundle. See \fICURLOPT_CAPATH(3)\fP .IP CURLOPT_PROXY_CAPATH diff --git a/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.3 b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.3 new file mode 100644 index 000000000..5fbf86df4 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.3 @@ -0,0 +1,79 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_ISSUERCERT_BLOB 3 "24 Jun 2020" "libcurl 7.71.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_ISSUERCERT_BLOB \- issuer SSL certificate from memory blob +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ISSUERCERT_BLOB, struct curl_blob *stblob); +.SH DESCRIPTION +Pass a pointer to a curl_blob structure, which contains information (pointer +and size) about a memory block with binary data of a CA certificate in PEM +format. If the option is set, an additional check against the peer certificate +is performed to verify the issuer is indeed the one associated with the +certificate provided by the option. This additional check is useful in +multi-level PKI where one needs to enforce that the peer certificate is from a +specific branch of the tree. + +This option should be used in combination with the +\fICURLOPT_SSL_VERIFYPEER(3)\fP option. Otherwise, the result of the check is +not considered as failure. + +A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, +which is returned if the setup of the SSL/TLS session has failed due to a +mismatch with the issuer of peer certificate (\fICURLOPT_SSL_VERIFYPEER(3)\fP +has to be set too for the check to fail). + +If the blob is initialized with the flags member of struct curl_blob set to +CURL_BLOB_COPY, the application does not have to keep the buffer around after +setting this. + +This option is an alternative to \fICURLOPT_ISSUERCERT(3)\fP which instead +expects a file name as input. +.SH DEFAULT +NULL +.SH PROTOCOLS +All TLS-based protocols +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + struct curl_blob blob; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + blob.data = certificateData; + blob.len = filesize; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_ISSUERCERT_BLOB, &blob); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in libcurl 7.71.0. This option is supported by the OpenSSL backends. +.SH RETURN VALUE +Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_ISSUERCERT "(3)," +.BR CURLOPT_CRLFILE "(3), " CURLOPT_SSL_VERIFYPEER "(3), " diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.3 b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.3 new file mode 100644 index 000000000..8b5a1336d --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.3 @@ -0,0 +1,72 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_PROXY_SSLCERT_BLOB 3 "24 Jun 2020" "libcurl 7.71.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_PROXY_SSLCERT_BLOB \- SSL proxy client certificate from memory blob +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLCERT_BLOB, struct curl_blob *blob); +.SH DESCRIPTION +Pass a pointer to a curl_blob structure, which contains information (pointer +and size) about a memory block with binary data of the certificate used to +connect to the HTTPS proxy. The format must be "P12" on Secure Transport or +Schannel. The format must be "P12" or "PEM" on OpenSSL. The string "P12" or +"PEM" must be specified with \fICURLOPT_PROXY_SSLCERTTYPE(3)\fP. + +If the blob is initialized with the flags member of struct curl_blob set to +CURL_BLOB_COPY, the application does not have to keep the buffer around after +setting this. + +This option is an alternative to \fICURLOPT_PROXY_SSLCERT(3)\fP which instead +expects a file name as input. +.SH DEFAULT +NULL +.SH PROTOCOLS +All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc. +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + struct curl_blob blob; + blob.data = certificateData; + blob.len = filesize; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + curl_easy_setopt(curl, CURLOPT_PROXY, "https://proxy"); + curl_easy_setopt(curl, CURLOPT_PROXY_SSLKEY, "key.pem"); + curl_easy_setopt(curl, CURLOPT_PROXY_KEYPASSWD, "s3cret"); + curl_easy_setopt(curl, CURLOPT_PROXY_SSLCERT_BLOB, &blob); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in libcurl 7.71.0. This option is supported by the OpenSSL, Secure +Transport and Schannel backends. +.SH RETURN VALUE +Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_PROXY_SSLCERTTYPE "(3), " CURLOPT_PROXY_SSLKEY "(3), " +.BR CURLOPT_PROXY_SSLCERT "(3), " diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.3 b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.3 new file mode 100644 index 000000000..daaff0df0 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.3 @@ -0,0 +1,73 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_PROXY_SSLKEY_BLOB 3 "24 Jun 2020" "libcurl 7.71.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_PROXY_SSLKEY_BLOB \- private key for proxy cert from memory blob +.SH SYNOPSIS +.nf +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLKEY_BLOB, + struct curl_blob *blob); +.fi +.SH DESCRIPTION +Pass a pointer to a curl_blob structure that contains information (pointer and +size) about the private key for connecting to the HTTPS proxy. Compatible with +OpenSSL. The format (like "PEM") must be specified with +\fICURLOPT_PROXY_SSLKEYTYPE(3)\fP. + +If the blob is initialized with the flags member of struct curl_blob set to +CURL_BLOB_COPY, the application does not have to keep the buffer around after +setting this. +.SH DEFAULT +NULL +.SH PROTOCOLS +All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc. +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + struct curl_blob blob; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + curl_easy_setopt(curl, CURLOPT_PROXY, "https://proxy"); + blob.data = certificateData; + blob.len = filesize; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_PROXY_SSLCERT_BLOB, &blob); + curl_easy_setopt(curl, CURLOPT_PROXY_SSLCERTTYPE, "PEM"); + + blob.data = privateKeyData; + blob.len = privateKeySize; + curl_easy_setopt(curl, CURLOPT_PROXY_SSLKEY_BLOB, &blob); + curl_easy_setopt(curl, CURLOPT_PROXY_KEYPASSWD, "s3cret"); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in libcurl 7.71.0. This option is supported by the OpenSSL backends. +.SH RETURN VALUE +Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_SSLKEYTYPE "(3), " CURLOPT_SSLKEY "(3), " diff --git a/docs/libcurl/opts/CURLOPT_SSLCERT.3 b/docs/libcurl/opts/CURLOPT_SSLCERT.3 index 4321e473f..4a43584c3 100644 --- a/docs/libcurl/opts/CURLOPT_SSLCERT.3 +++ b/docs/libcurl/opts/CURLOPT_SSLCERT.3 @@ -38,15 +38,17 @@ you wish to authenticate with as it is named in the security database. If you want to use a file from the current directory, please precede it with "./" prefix, in order to avoid confusion with a nickname. -(Schannel only) Client certificates must be specified by a path expression to -a certificate store. (Loading PFX is not supported; you can import it to a -store first). You can use "\\\\" to -refer to a certificate in the system certificates store, for example, +(Schannel only) Client certificates can be specified by a path expression to +a certificate store. (You can import PFX to a store first). You can use +"\\\\" to refer to a certificate +in the system certificates store, for example, "CurrentUser\\MY\\934a7ac6f8a5d579285a74fa61e19f23ddfe8d7a". Thumbprint is usually a SHA-1 hex string which you can see in certificate details. Following store locations are supported: CurrentUser, LocalMachine, CurrentService, Services, CurrentUserGroupPolicy, LocalMachineGroupPolicy, LocalMachineEnterprise. +Schannel also support P12 certificate file, with the string "P12" specified +with \fICURLOPT_SSLCERTTYPE(3)\fP. When using a client certificate, you most likely also need to provide a private key with \fICURLOPT_SSLKEY(3)\fP. diff --git a/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.3 b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.3 new file mode 100644 index 000000000..ed13d6cfc --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.3 @@ -0,0 +1,69 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_SSLCERT_BLOB 3 "24 Jun 2020" "libcurl 7.71.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_SSLCERT_BLOB \- SSL client certificate from memory blob +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLCERT_BLOB, struct curl_blob *stblob); +.SH DESCRIPTION +Pass a pointer to a curl_blob structure, which contains (pointer and size) a +client certificate. The format must be "P12" on Secure Transport or +Schannel. The format must be "P12" or "PEM" on OpenSSL. The string "P12" or +"PEM" must be specified with \fICURLOPT_SSLCERTTYPE(3)\fP. + +If the blob is initialized with the flags member of struct curl_blob set to +CURL_BLOB_COPY, the application does not have to keep the buffer around after +setting this. + +This option is an alternative to \fICURLOPT_SSLCERT(3)\fP which instead +expects a file name as input. +.SH DEFAULT +NULL +.SH PROTOCOLS +All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc. +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + struct curl_blob stblob; + stblob.data = certificateData; + stblob.len = filesize; + stblob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &stblob); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "P12"); + curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "s3cret"); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in libcurl 7.71.0. This option is supported by the OpenSSL, Secure +Transport and Schannel backends. +.SH RETURN VALUE +Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_SSLCERTTYPE "(3), " CURLOPT_SSLKEY "(3), " diff --git a/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.3 b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.3 new file mode 100644 index 000000000..1f657c8ec --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.3 @@ -0,0 +1,75 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_SSLKEY_BLOB 3 "24 Jun 2020" "libcurl 7.71.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_SSLKEY_BLOB \- private key for client cert from memory blob +.SH SYNOPSIS +.nf +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLKEY_BLOB, + struct curl_blob *blob); +.fi +.SH DESCRIPTION +Pass a pointer to a curl_blob structure, which contains information (pointer +and size) for a private key. Compatible with OpenSSL. The format (like "PEM") +must be specified with \fICURLOPT_SSLKEYTYPE(3)\fP. + +If the blob is initialized with the flags member of struct curl_blob set to +CURL_BLOB_COPY, the application does not have to keep the buffer around after +setting this. + +This option is an alternative to \fICURLOPT_SSLKEY(3)\fP which instead expects +a file name as input. +.SH DEFAULT +NULL +.SH PROTOCOLS +All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc. +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + struct curl_blob blob; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + blob.data = certificateData; + blob.len = filesize; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); + + blob.data = privateKeyData; + blob.len = privateKeySize; + curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob); + curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "s3cret"); + curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in libcurl 7.71.0. This option is supported by the OpenSSL backends. +.SH RETURN VALUE +Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_SSLKEYTYPE "(3), " CURLOPT_SSLKEY "(3), " diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index f7a3b435d..21e14fc95 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -198,6 +198,7 @@ man_MANS = \ CURLOPT_IOCTLFUNCTION.3 \ CURLOPT_IPRESOLVE.3 \ CURLOPT_ISSUERCERT.3 \ + CURLOPT_ISSUERCERT_BLOB.3 \ CURLOPT_KEEP_SENDING_ON_ERROR.3 \ CURLOPT_KEYPASSWD.3 \ CURLOPT_KRBLEVEL.3 \ @@ -260,8 +261,10 @@ man_MANS = \ CURLOPT_PROXY_PINNEDPUBLICKEY.3 \ CURLOPT_PROXY_SERVICE_NAME.3 \ CURLOPT_PROXY_SSLCERT.3 \ + CURLOPT_PROXY_SSLCERT_BLOB.3 \ CURLOPT_PROXY_SSLCERTTYPE.3 \ CURLOPT_PROXY_SSLKEY.3 \ + CURLOPT_PROXY_SSLKEY_BLOB.3 \ CURLOPT_PROXY_SSLKEYTYPE.3 \ CURLOPT_PROXY_SSLVERSION.3 \ CURLOPT_PROXY_SSL_CIPHER_LIST.3 \ @@ -313,10 +316,12 @@ man_MANS = \ CURLOPT_SSH_PRIVATE_KEYFILE.3 \ CURLOPT_SSH_PUBLIC_KEYFILE.3 \ CURLOPT_SSLCERT.3 \ + CURLOPT_SSLCERT_BLOB.3 \ CURLOPT_SSLCERTTYPE.3 \ CURLOPT_SSLENGINE.3 \ CURLOPT_SSLENGINE_DEFAULT.3 \ CURLOPT_SSLKEY.3 \ + CURLOPT_SSLKEY_BLOB.3 \ CURLOPT_SSLKEYTYPE.3 \ CURLOPT_SSLVERSION.3 \ CURLOPT_SSL_CIPHER_LIST.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 4264547bc..92159d02a 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -347,6 +347,7 @@ CURLM_RECURSIVE_API_CALL 7.59.0 CURLM_UNKNOWN_OPTION 7.15.4 CURLM_WAKEUP_FAILURE 7.68.0 CURLOPT 7.69.0 +CURLOPTTYPE_BLOB 7.71.0 CURLOPTTYPE_FUNCTIONPOINT 7.1 CURLOPTTYPE_LONG 7.1 CURLOPTTYPE_OBJECTPOINT 7.1 @@ -459,6 +460,7 @@ CURLOPT_IOCTLDATA 7.12.3 CURLOPT_IOCTLFUNCTION 7.12.3 CURLOPT_IPRESOLVE 7.10.8 CURLOPT_ISSUERCERT 7.19.0 +CURLOPT_ISSUERCERT_BLOB 7.71.0 CURLOPT_KEEP_SENDING_ON_ERROR 7.51.0 CURLOPT_KEYPASSWD 7.17.0 CURLOPT_KRB4LEVEL 7.3 7.17.0 @@ -528,8 +530,10 @@ CURLOPT_PROXY_KEYPASSWD 7.52.0 CURLOPT_PROXY_PINNEDPUBLICKEY 7.52.0 CURLOPT_PROXY_SERVICE_NAME 7.43.0 CURLOPT_PROXY_SSLCERT 7.52.0 +CURLOPT_PROXY_SSLCERT_BLOB 7.71.0 CURLOPT_PROXY_SSLCERTTYPE 7.52.0 CURLOPT_PROXY_SSLKEY 7.52.0 +CURLOPT_PROXY_SSLKEY_BLOB 7.71.0 CURLOPT_PROXY_SSLKEYTYPE 7.52.0 CURLOPT_PROXY_SSLVERSION 7.52.0 CURLOPT_PROXY_SSL_CIPHER_LIST 7.52.0 @@ -591,11 +595,13 @@ CURLOPT_SSH_KNOWNHOSTS 7.19.6 CURLOPT_SSH_PRIVATE_KEYFILE 7.16.1 CURLOPT_SSH_PUBLIC_KEYFILE 7.16.1 CURLOPT_SSLCERT 7.1 +CURLOPT_SSLCERT_BLOB 7.71.0 CURLOPT_SSLCERTPASSWD 7.1.1 7.17.0 CURLOPT_SSLCERTTYPE 7.9.3 CURLOPT_SSLENGINE 7.9.3 CURLOPT_SSLENGINE_DEFAULT 7.9.3 CURLOPT_SSLKEY 7.9.3 +CURLOPT_SSLKEY_BLOB 7.71.0 CURLOPT_SSLKEYPASSWD 7.9.3 7.17.0 CURLOPT_SSLKEYTYPE 7.9.3 CURLOPT_SSLVERSION 7.1 diff --git a/include/curl/curl.h b/include/curl/curl.h index 7b92975c7..17d0384f0 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -950,6 +950,7 @@ typedef enum { #define CURLOPTTYPE_OBJECTPOINT 10000 #define CURLOPTTYPE_FUNCTIONPOINT 20000 #define CURLOPTTYPE_OFF_T 30000 +#define CURLOPTTYPE_BLOB 40000 /* *STRINGPOINT is an alias for OBJECTPOINT to allow tools to extract the string options from the header file */ @@ -1959,6 +1960,13 @@ typedef enum { /* allow RCPT TO command to fail for some recipients */ CURLOPT(CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOPTTYPE_LONG, 290), + /* the private SSL-certificate as a "blob" */ + CURLOPT(CURLOPT_SSLCERT_BLOB, CURLOPTTYPE_BLOB, 291), + CURLOPT(CURLOPT_SSLKEY_BLOB, CURLOPTTYPE_BLOB, 292), + CURLOPT(CURLOPT_PROXY_SSLCERT_BLOB, CURLOPTTYPE_BLOB, 293), + CURLOPT(CURLOPT_PROXY_SSLKEY_BLOB, CURLOPTTYPE_BLOB, 294), + CURLOPT(CURLOPT_ISSUERCERT_BLOB, CURLOPTTYPE_BLOB, 295), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/include/curl/easy.h b/include/curl/easy.h index 592f5d3c1..9aef13396 100644 --- a/include/curl/easy.h +++ b/include/curl/easy.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2019, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -25,6 +25,17 @@ extern "C" { #endif +/* Flag bits in the curl_blob struct: */ +#define CURL_BLOB_COPY 1 /* tell libcurl to copy the data */ +#define CURL_BLOB_NOCOPY 0 /* tell libcurl to NOT copy the data */ + +struct curl_blob { + void *data; + size_t len; + unsigned int flags; /* bit 0 is defined, the rest are reserved and should be + left zeroes */ +}; + CURL_EXTERN CURL *curl_easy_init(void); CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); 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, , et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, , 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; } diff --git a/src/tool_operate.c b/src/tool_operate.c index 2a12d030f..e6d346067 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1556,11 +1556,90 @@ static CURLcode single_transfer(struct GlobalConfig *global, } } + /* In debug build of curl tool, using + * --cert loadmem=: --cert-type p12 + * must do the same thing than classic: + * --cert : --cert-type p12 + * but is designed to test blob */ +#if defined(CURLDEBUG) || defined(DEBUGBUILD) + if(config->cert && (strlen(config->cert) > 8) && + (memcmp(config->cert, "loadmem=",8) == 0)) { + FILE *fInCert = fopen(config->cert + 8, "rb"); + void *certdata = NULL; + long filesize = 0; + bool continue_reading = fInCert != NULL; + if(continue_reading) + continue_reading = fseek(fInCert, 0, SEEK_END) == 0; + if(continue_reading) + filesize = ftell(fInCert); + if(filesize < 0) + continue_reading = FALSE; + if(continue_reading) + continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; + if(continue_reading) + certdata = malloc(((size_t)filesize) + 1); + if((!certdata) || + ((int)fread(certdata, (size_t)filesize, 1, fInCert) != 1)) + continue_reading = FALSE; + if(fInCert) + fclose(fInCert); + if((filesize > 0) && continue_reading) { + struct curl_blob structblob; + structblob.data = certdata; + structblob.len = (size_t)filesize; + structblob.flags = CURL_BLOB_COPY; + my_setopt_str(curl, CURLOPT_SSLCERT_BLOB, &structblob); + /* if test run well, we are sure we don't reuse + * original mem pointer */ + memset(certdata, 0, (size_t)filesize); + } + free(certdata); + } + else +#endif my_setopt_str(curl, CURLOPT_SSLCERT, config->cert); my_setopt_str(curl, CURLOPT_PROXY_SSLCERT, config->proxy_cert); my_setopt_str(curl, CURLOPT_SSLCERTTYPE, config->cert_type); my_setopt_str(curl, CURLOPT_PROXY_SSLCERTTYPE, config->proxy_cert_type); + + +#if defined(CURLDEBUG) || defined(DEBUGBUILD) + if(config->key && (strlen(config->key) > 8) && + (memcmp(config->key, "loadmem=",8) == 0)) { + FILE *fInCert = fopen(config->key + 8, "rb"); + void *certdata = NULL; + long filesize = 0; + bool continue_reading = fInCert != NULL; + if(continue_reading) + continue_reading = fseek(fInCert, 0, SEEK_END) == 0; + if(continue_reading) + filesize = ftell(fInCert); + if(filesize < 0) + continue_reading = FALSE; + if(continue_reading) + continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; + if(continue_reading) + certdata = malloc(((size_t)filesize) + 1); + if((!certdata) || + ((int)fread(certdata, (size_t)filesize, 1, fInCert) != 1)) + continue_reading = FALSE; + if(fInCert) + fclose(fInCert); + if((filesize > 0) && continue_reading) { + struct curl_blob structblob; + structblob.data = certdata; + structblob.len = (size_t)filesize; + structblob.flags = CURL_BLOB_COPY; + my_setopt_str(curl, CURLOPT_SSLKEY_BLOB, &structblob); + /* if test run well, we are sure we don't reuse + * original mem pointer */ + memset(certdata, 0, (size_t)filesize); + } + free(certdata); + } + else +#endif my_setopt_str(curl, CURLOPT_SSLKEY, config->key); my_setopt_str(curl, CURLOPT_PROXY_SSLKEY, config->proxy_key); my_setopt_str(curl, CURLOPT_SSLKEYTYPE, config->key_type); diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 494518b6b..9858d49c9 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -683,7 +683,7 @@ CURLcode tool_setopt(CURL *curl, bool str, struct GlobalConfig *config, ret = curl_easy_setopt(curl, tag, pval); } - else { + else if(tag < CURLOPTTYPE_BLOB) { /* Value is expected to be curl_off_t */ curl_off_t oval = va_arg(arg, curl_off_t); msnprintf(buf, sizeof(buf), @@ -694,6 +694,20 @@ CURLcode tool_setopt(CURL *curl, bool str, struct GlobalConfig *config, if(!oval) skip = TRUE; } + else { + /* Value is a blob */ + void *pblob = va_arg(arg, void *); + + /* blobs are never printable */ + if(pblob) { + value = "blobpointer"; + remark = TRUE; + } + else + skip = TRUE; + + ret = curl_easy_setopt(curl, tag, pblob); + } va_end(arg); -- cgit v1.2.3