#include "../curl_setup.h"
#if defined(USE_QUICHE) || defined(USE_OPENSSL)
#include <limits.h>
#ifdef USE_WIN32_CRYPTO
#include <wincrypt.h>
#undef X509_NAME
#undef X509_EXTENSIONS
#undef PKCS7_ISSUER_AND_SERIAL
#undef PKCS7_SIGNER_INFO
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#endif
#include "../urldata.h"
#include "../sendf.h"
#include "../formdata.h"
#include "../url.h"
#include "../curlx/inet_pton.h"
#include "openssl.h"
#include "../connect.h"
#include "../slist.h"
#include "../select.h"
#include "../curlx/wait.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "../vauth/vauth.h"
#include "keylog.h"
#include "hostcheck.h"
#include "../transfer.h"
#include "../multiif.h"
#include "../curlx/strerr.h"
#include "../curlx/strparse.h"
#include "../strdup.h"
#include "apple.h"
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/x509v3.h>
#ifndef OPENSSL_NO_DSA
#include <openssl/dsa.h>
#endif
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/md5.h>
#include <openssl/conf.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/pkcs12.h>
#include <openssl/tls1.h>
#include <openssl/evp.h>
#ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST
#define USE_ECH_OPENSSL
#endif
#if defined(USE_ECH_OPENSSL) && !defined(HAVE_BORINGSSL_LIKE)
#include <openssl/ech.h>
#endif
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
#endif
#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE)
#define USE_OPENSSL_ENGINE
#include <openssl/engine.h>
#endif
#ifdef LIBRESSL_VERSION_NUMBER
# if LIBRESSL_VERSION_NUMBER < 0x2090100fL
# error "LibreSSL 2.9.1 or later required"
# endif
#elif OPENSSL_VERSION_NUMBER < 0x1000201fL
# error "OpenSSL 1.0.2a or later required"
#endif
#if defined(HAVE_OPENSSL3) && !defined(OPENSSL_NO_UI_CONSOLE)
#include <openssl/provider.h>
#include <openssl/store.h>
#define OPENSSL_HAS_PROVIDERS
static void ossl_provider_cleanup(struct Curl_easy *data);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) && \
(!defined(OPENSSL_IS_AWSLC) || defined(X509_V_ERR_EC_KEY_EXPLICIT_PARAMS))
#define HAVE_SSL_CTX_SET_DEFAULT_READ_BUFFER_LEN 1
#endif
#include "../curlx/warnless.h"
#include "../curl_memory.h"
#include "../memdebug.h"
#if defined(USE_OPENSSL_ENGINE) || defined(OPENSSL_HAS_PROVIDERS)
#include <openssl/ui.h>
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define OSSL_UI_METHOD_CAST(x) (x)
#else
#define OSSL_UI_METHOD_CAST(x) CURL_UNCONST(x)
#endif
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define HAVE_X509_GET0_EXTENSIONS 1
#define HAVE_OPAQUE_EVP_PKEY 1
#define HAVE_OPAQUE_RSA_DSA_DH 1
#define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1
#else
#define ASN1_STRING_get0_data(x) ASN1_STRING_data(x)
#define X509_get0_notBefore(x) X509_get_notBefore(x)
#define X509_get0_notAfter(x) X509_get_notAfter(x)
#define OpenSSL_version_num() SSLeay()
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002003L && \
OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \
!defined(OPENSSL_NO_COMP)
#define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1
#endif
#ifdef HAVE_OPENSSL3
#define HAVE_EVP_PKEY_GET_PARAMS 1
#endif
#ifdef HAVE_EVP_PKEY_GET_PARAMS
#include <openssl/core_names.h>
#define DECLARE_PKEY_PARAM_BIGNUM(name) BIGNUM *name = NULL
#define FREE_PKEY_PARAM_BIGNUM(name) BN_clear_free(name)
#else
#define DECLARE_PKEY_PARAM_BIGNUM(name) const BIGNUM *name
#define FREE_PKEY_PARAM_BIGNUM(name)
#endif
#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L && \
!defined(LIBRESSL_VERSION_NUMBER)) || \
(defined(LIBRESSL_VERSION_NUMBER) && \
LIBRESSL_VERSION_NUMBER >= 0x3040100fL)) && \
!defined(OPENSSL_IS_BORINGSSL)
# define HAVE_SSL_CTX_SET_CIPHERSUITES
# ifndef OPENSSL_IS_AWSLC
# define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
# endif
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
#define HAVE_SSL_CTX_SET1_SIGALGS
#endif
#ifdef LIBRESSL_VERSION_NUMBER
#define OSSL_PACKAGE "LibreSSL"
#elif defined(OPENSSL_IS_BORINGSSL)
#define OSSL_PACKAGE "BoringSSL"
#elif defined(OPENSSL_IS_AWSLC)
#define OSSL_PACKAGE "AWS-LC"
#elif defined(USE_NGTCP2) && defined(USE_NGHTTP3) && \
!defined(OPENSSL_QUIC_API2)
#define OSSL_PACKAGE "quictls"
#else
#define OSSL_PACKAGE "OpenSSL"
#endif
#ifdef HAVE_BORINGSSL_LIKE
typedef size_t numcert_t;
typedef uint32_t sslerr_t;
#else
typedef int numcert_t;
typedef unsigned long sslerr_t;
#endif
#define ossl_valsize_t numcert_t
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define DEFAULT_CIPHER_SELECTION NULL
#else
#define DEFAULT_CIPHER_SELECTION \
"ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH"
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define HAVE_RANDOM_INIT_BY_DEFAULT 1
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#define HAVE_SSL_X509_STORE_SHARE
#endif
static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl);
static CURLcode push_certinfo(struct Curl_easy *data,
BIO *mem, const char *label, int num)
WARN_UNUSED_RESULT;
static CURLcode push_certinfo(struct Curl_easy *data,
BIO *mem, const char *label, int num)
{
char *ptr;
long len = BIO_get_mem_data(mem, &ptr);
CURLcode result = Curl_ssl_push_certinfo_len(data, num, label, ptr, len);
(void)BIO_reset(mem);
return result;
}
static CURLcode pubkey_show(struct Curl_easy *data,
BIO *mem,
int num,
const char *type,
const char *name,
const BIGNUM *bn)
{
char namebuf[32];
curl_msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
if(bn)
BN_print(mem, bn);
return push_certinfo(data, mem, namebuf, num);
}
#ifdef HAVE_OPAQUE_RSA_DSA_DH
#define print_pubkey_BN(_type, _name, _num) \
pubkey_show(data, mem, _num, #_type, #_name, _name)
#else
#define print_pubkey_BN(_type, _name, _num) \
do { \
if(_type->_name) { \
pubkey_show(data, mem, _num, #_type, #_name, _type->_name); \
} \
} while(0)
#endif
static int asn1_object_dump(const ASN1_OBJECT *a, char *buf, size_t len)
{
int i;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
i = i2t_ASN1_OBJECT(buf, (int)len, a);
#else
i = i2t_ASN1_OBJECT(buf, (int)len, CURL_UNCONST(a));
#endif
return (i >= (int)len);
}
static CURLcode X509V3_ext(struct Curl_easy *data,
int certnum,
const STACK_OF(X509_EXTENSION) *extsarg)
{
int i;
CURLcode result = CURLE_OK;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
const STACK_OF(X509_EXTENSION) *exts = extsarg;
#else
STACK_OF(X509_EXTENSION) *exts = CURL_UNCONST(extsarg);
#endif
if((int)sk_X509_EXTENSION_num(exts) <= 0)
return result;
for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) {
ASN1_OBJECT *obj;
X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, (ossl_valsize_t)i);
BUF_MEM *biomem;
char namebuf[128];
BIO *bio_out = BIO_new(BIO_s_mem());
if(!bio_out)
return result;
obj = X509_EXTENSION_get_object(ext);
if(asn1_object_dump(obj, namebuf, sizeof(namebuf)))
namebuf [ sizeof(namebuf) - 1] = 0;
if(!X509V3_EXT_print(bio_out, ext, 0, 0))
ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));
BIO_get_mem_ptr(bio_out, &biomem);
result = Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data,
biomem->length);
BIO_free(bio_out);
if(result)
break;
}
return result;
}
#define MAX_ALLOWED_CERT_AMOUNT 100
static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl)
{
CURLcode result;
STACK_OF(X509) *sk;
int i;
numcert_t numcerts;
BIO *mem;
DEBUGASSERT(ssl);
sk = SSL_get_peer_cert_chain(ssl);
if(!sk)
return CURLE_SSL_CONNECT_ERROR;
numcerts = sk_X509_num(sk);
if(numcerts > MAX_ALLOWED_CERT_AMOUNT) {
failf(data, "%d certificates is more than allowed (%u)", (int)numcerts,
MAX_ALLOWED_CERT_AMOUNT);
return CURLE_SSL_CONNECT_ERROR;
}
result = Curl_ssl_init_certinfo(data, (int)numcerts);
if(result)
return result;
mem = BIO_new(BIO_s_mem());
if(!mem)
result = CURLE_OUT_OF_MEMORY;
for(i = 0; !result && (i < (int)numcerts); i++) {
ASN1_INTEGER *num;
X509 *x = sk_X509_value(sk, (ossl_valsize_t)i);
EVP_PKEY *pubkey = NULL;
int j;
const ASN1_BIT_STRING *psig = NULL;
X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE);
result = push_certinfo(data, mem, "Subject", i);
if(result)
break;
X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE);
result = push_certinfo(data, mem, "Issuer", i);
if(result)
break;
BIO_printf(mem, "%lx", X509_get_version(x));
result = push_certinfo(data, mem, "Version", i);
if(result)
break;
num = X509_get_serialNumber(x);
if(num->type == V_ASN1_NEG_INTEGER)
BIO_puts(mem, "-");
for(j = 0; j < num->length; j++)
BIO_printf(mem, "%02x", num->data[j]);
result = push_certinfo(data, mem, "Serial Number", i);
if(result)
break;
#ifdef HAVE_X509_GET0_EXTENSIONS
{
const X509_ALGOR *sigalg = NULL;
X509_PUBKEY *xpubkey = NULL;
ASN1_OBJECT *pubkeyoid = NULL;
X509_get0_signature(&psig, &sigalg, x);
if(sigalg) {
const ASN1_OBJECT *sigalgoid = NULL;
X509_ALGOR_get0(&sigalgoid, NULL, NULL, sigalg);
i2a_ASN1_OBJECT(mem, sigalgoid);
result = push_certinfo(data, mem, "Signature Algorithm", i);
if(result)
break;
}
xpubkey = X509_get_X509_PUBKEY(x);
if(xpubkey) {
X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey);
if(pubkeyoid) {
i2a_ASN1_OBJECT(mem, pubkeyoid);
result = push_certinfo(data, mem, "Public Key Algorithm", i);
if(result)
break;
}
}
result = X509V3_ext(data, i, X509_get0_extensions(x));
if(result)
break;
}
#else
{
X509_CINF *cinf = x->cert_info;
i2a_ASN1_OBJECT(mem, cinf->signature->algorithm);
result = push_certinfo(data, mem, "Signature Algorithm", i);
if(!result) {
i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm);
result = push_certinfo(data, mem, "Public Key Algorithm", i);
}
if(!result)
result = X509V3_ext(data, i, cinf->extensions);
if(result)
break;
psig = x->signature;
}
#endif
ASN1_TIME_print(mem, X509_get0_notBefore(x));
result = push_certinfo(data, mem, "Start date", i);
if(result)
break;
ASN1_TIME_print(mem, X509_get0_notAfter(x));
result = push_certinfo(data, mem, "Expire date", i);
if(result)
break;
pubkey = X509_get_pubkey(x);
if(!pubkey)
infof(data, " Unable to load public key");
else {
int pktype;
#ifdef HAVE_OPAQUE_EVP_PKEY
pktype = EVP_PKEY_id(pubkey);
#else
pktype = pubkey->type;
#endif
switch(pktype) {
case EVP_PKEY_RSA: {
#ifndef HAVE_EVP_PKEY_GET_PARAMS
RSA *rsa;
#ifdef HAVE_OPAQUE_EVP_PKEY
rsa = EVP_PKEY_get0_RSA(pubkey);
#else
rsa = pubkey->pkey.rsa;
#endif
#endif
{
#ifdef HAVE_OPAQUE_RSA_DSA_DH
DECLARE_PKEY_PARAM_BIGNUM(n);
DECLARE_PKEY_PARAM_BIGNUM(e);
#ifdef HAVE_EVP_PKEY_GET_PARAMS
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &n);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &e);
#else
RSA_get0_key(rsa, &n, &e, NULL);
#endif
BIO_printf(mem, "%d", n ? BN_num_bits(n) : 0);
#else
BIO_printf(mem, "%d", rsa->n ? BN_num_bits(rsa->n) : 0);
#endif
result = push_certinfo(data, mem, "RSA Public Key", i);
if(result)
break;
print_pubkey_BN(rsa, n, i);
print_pubkey_BN(rsa, e, i);
FREE_PKEY_PARAM_BIGNUM(n);
FREE_PKEY_PARAM_BIGNUM(e);
}
break;
}
case EVP_PKEY_DSA:
{
#ifndef OPENSSL_NO_DSA
#ifndef HAVE_EVP_PKEY_GET_PARAMS
DSA *dsa;
#ifdef HAVE_OPAQUE_EVP_PKEY
dsa = EVP_PKEY_get0_DSA(pubkey);
#else
dsa = pubkey->pkey.dsa;
#endif
#endif
{
#ifdef HAVE_OPAQUE_RSA_DSA_DH
DECLARE_PKEY_PARAM_BIGNUM(p);
DECLARE_PKEY_PARAM_BIGNUM(q);
DECLARE_PKEY_PARAM_BIGNUM(g);
DECLARE_PKEY_PARAM_BIGNUM(pub_key);
#ifdef HAVE_EVP_PKEY_GET_PARAMS
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
#else
DSA_get0_pqg(dsa, &p, &q, &g);
DSA_get0_key(dsa, &pub_key, NULL);
#endif
#endif
print_pubkey_BN(dsa, p, i);
print_pubkey_BN(dsa, q, i);
print_pubkey_BN(dsa, g, i);
print_pubkey_BN(dsa, pub_key, i);
FREE_PKEY_PARAM_BIGNUM(p);
FREE_PKEY_PARAM_BIGNUM(q);
FREE_PKEY_PARAM_BIGNUM(g);
FREE_PKEY_PARAM_BIGNUM(pub_key);
}
#endif
break;
}
case EVP_PKEY_DH: {
#ifndef HAVE_EVP_PKEY_GET_PARAMS
DH *dh;
#ifdef HAVE_OPAQUE_EVP_PKEY
dh = EVP_PKEY_get0_DH(pubkey);
#else
dh = pubkey->pkey.dh;
#endif
#endif
{
#ifdef HAVE_OPAQUE_RSA_DSA_DH
DECLARE_PKEY_PARAM_BIGNUM(p);
DECLARE_PKEY_PARAM_BIGNUM(q);
DECLARE_PKEY_PARAM_BIGNUM(g);
DECLARE_PKEY_PARAM_BIGNUM(pub_key);
#ifdef HAVE_EVP_PKEY_GET_PARAMS
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g);
EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
#else
DH_get0_pqg(dh, &p, &q, &g);
DH_get0_key(dh, &pub_key, NULL);
#endif
print_pubkey_BN(dh, p, i);
print_pubkey_BN(dh, q, i);
print_pubkey_BN(dh, g, i);
#else
print_pubkey_BN(dh, p, i);
print_pubkey_BN(dh, g, i);
#endif
print_pubkey_BN(dh, pub_key, i);
FREE_PKEY_PARAM_BIGNUM(p);
FREE_PKEY_PARAM_BIGNUM(q);
FREE_PKEY_PARAM_BIGNUM(g);
FREE_PKEY_PARAM_BIGNUM(pub_key);
}
break;
}
}
EVP_PKEY_free(pubkey);
}
if(!result && psig) {
for(j = 0; j < psig->length; j++)
BIO_printf(mem, "%02x:", psig->data[j]);
result = push_certinfo(data, mem, "Signature", i);
}
if(!result) {
PEM_write_bio_X509(mem, x);
result = push_certinfo(data, mem, "Cert", i);
}
}
BIO_free(mem);
if(result)
Curl_ssl_free_certinfo(data);
return result;
}
#endif
#ifdef USE_OPENSSL
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define BIO_set_init(x,v) ((x)->init=(v))
#define BIO_get_data(x) ((x)->ptr)
#define BIO_set_data(x,v) ((x)->ptr=(v))
#define BIO_get_shutdown(x) ((x)->shutdown)
#define BIO_set_shutdown(x,v) ((x)->shutdown=(v))
#endif
static int ossl_bio_cf_create(BIO *bio)
{
BIO_set_shutdown(bio, 1);
BIO_set_init(bio, 1);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
bio->num = -1;
#endif
BIO_set_data(bio, NULL);
return 1;
}
static int ossl_bio_cf_destroy(BIO *bio)
{
if(!bio)
return 0;
return 1;
}
static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
struct Curl_cfilter *cf = BIO_get_data(bio);
long ret = 1;
(void)cf;
(void)ptr;
switch(cmd) {
case BIO_CTRL_GET_CLOSE:
ret = (long)BIO_get_shutdown(bio);
break;
case BIO_CTRL_SET_CLOSE:
BIO_set_shutdown(bio, (int)num);
break;
case BIO_CTRL_FLUSH:
ret = 1;
break;
case BIO_CTRL_DUP:
ret = 1;
break;
case BIO_CTRL_EOF: {
struct ssl_connect_data *connssl = cf->ctx;
return connssl->peer_closed;
}
default:
ret = 0;
break;
}
return ret;
}
static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen)
{
struct Curl_cfilter *cf = BIO_get_data(bio);
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
size_t nwritten;
CURLcode result;
DEBUGASSERT(data);
if(blen < 0)
return 0;
result = Curl_conn_cf_send(cf->next, data, buf, (size_t)blen, FALSE,
&nwritten);
CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, %zu",
blen, result, nwritten);
BIO_clear_retry_flags(bio);
octx->io_result = result;
if(result) {
if(CURLE_AGAIN == result)
BIO_set_retry_write(bio);
return -1;
}
return (int)nwritten;
}
static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen)
{
struct Curl_cfilter *cf = BIO_get_data(bio);
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
size_t nread;
CURLcode result, r2;
DEBUGASSERT(data);
if(!buf)
return 0;
if(blen < 0)
return 0;
result = Curl_conn_cf_recv(cf->next, data, buf, (size_t)blen, &nread);
CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, %zu",
blen, result, nread);
BIO_clear_retry_flags(bio);
octx->io_result = result;
if(result) {
if(CURLE_AGAIN == result)
BIO_set_retry_read(bio);
}
else {
connssl->input_pending = TRUE;
if(nread == 0)
connssl->peer_closed = TRUE;
}
if(!octx->x509_store_setup) {
r2 = Curl_ssl_setup_x509_store(cf, data, octx);
if(r2) {
BIO_clear_retry_flags(bio);
octx->io_result = r2;
return -1;
}
octx->x509_store_setup = TRUE;
}
return result ? -1 : (int)nread;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static BIO_METHOD ossl_bio_cf_meth_1_0 = {
BIO_TYPE_MEM,
"OpenSSL CF BIO",
ossl_bio_cf_out_write,
ossl_bio_cf_in_read,
NULL,
NULL,
ossl_bio_cf_ctrl,
ossl_bio_cf_create,
ossl_bio_cf_destroy,
NULL
};
static BIO_METHOD *ossl_bio_cf_method_create(void)
{
return &ossl_bio_cf_meth_1_0;
}
#define ossl_bio_cf_method_free(m) Curl_nop_stmt
#else
static BIO_METHOD *ossl_bio_cf_method_create(void)
{
BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
if(m) {
BIO_meth_set_write(m, &ossl_bio_cf_out_write);
BIO_meth_set_read(m, &ossl_bio_cf_in_read);
BIO_meth_set_ctrl(m, &ossl_bio_cf_ctrl);
BIO_meth_set_create(m, &ossl_bio_cf_create);
BIO_meth_set_destroy(m, &ossl_bio_cf_destroy);
}
return m;
}
static void ossl_bio_cf_method_free(BIO_METHOD *m)
{
if(m)
BIO_meth_free(m);
}
#endif
#ifdef HAVE_KEYLOG_CALLBACK
static void ossl_keylog_callback(const SSL *ssl, const char *line)
{
(void)ssl;
Curl_tls_keylog_write_line(line);
}
#else
static void
ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done)
{
const SSL_SESSION *session;
unsigned char client_random[SSL3_RANDOM_SIZE];
unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH];
int master_key_length = 0;
ERR_set_mark();
session = SSL_get_session(ssl);
if(!session || *keylog_done) {
ERR_pop_to_mark();
return;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE);
master_key_length = (int)
SSL_SESSION_get_master_key(session, master_key, SSL_MAX_MASTER_KEY_LENGTH);
#else
if(ssl->s3 && session->master_key_length > 0) {
master_key_length = session->master_key_length;
memcpy(master_key, session->master_key, session->master_key_length);
memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
}
#endif
ERR_pop_to_mark();
if(master_key_length <= 0)
return;
*keylog_done = TRUE;
Curl_tls_keylog_write("CLIENT_RANDOM", client_random,
master_key, master_key_length);
}
#endif
static const char *SSL_ERROR_to_str(int err)
{
switch(err) {
case SSL_ERROR_NONE:
return "SSL_ERROR_NONE";
case SSL_ERROR_SSL:
return "SSL_ERROR_SSL";
case SSL_ERROR_WANT_READ:
return "SSL_ERROR_WANT_READ";
case SSL_ERROR_WANT_WRITE:
return "SSL_ERROR_WANT_WRITE";
case SSL_ERROR_WANT_X509_LOOKUP:
return "SSL_ERROR_WANT_X509_LOOKUP";
case SSL_ERROR_SYSCALL:
return "SSL_ERROR_SYSCALL";
case SSL_ERROR_ZERO_RETURN:
return "SSL_ERROR_ZERO_RETURN";
case SSL_ERROR_WANT_CONNECT:
return "SSL_ERROR_WANT_CONNECT";
case SSL_ERROR_WANT_ACCEPT:
return "SSL_ERROR_WANT_ACCEPT";
#ifdef SSL_ERROR_WANT_ASYNC
case SSL_ERROR_WANT_ASYNC:
return "SSL_ERROR_WANT_ASYNC";
#endif
#ifdef SSL_ERROR_WANT_ASYNC_JOB
case SSL_ERROR_WANT_ASYNC_JOB:
return "SSL_ERROR_WANT_ASYNC_JOB";
#endif
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
return "SSL_ERROR_WANT_CLIENT_HELLO_CB";
#endif
default:
return "SSL_ERROR unknown";
}
}
static char *ossl_strerror(unsigned long error, char *buf, size_t size)
{
size_t len;
DEBUGASSERT(size);
*buf = '\0';
len = Curl_ossl_version(buf, size);
DEBUGASSERT(len < (size - 2));
if(len < (size - 2)) {
buf += len;
size -= (len + 2);
*buf++ = ':';
*buf++ = ' ';
*buf = '\0';
}
#ifdef HAVE_BORINGSSL_LIKE
ERR_error_string_n((uint32_t)error, buf, size);
#else
ERR_error_string_n(error, buf, size);
#endif
if(!*buf) {
const char *msg = error ? "Unknown error" : "No error";
if(strlen(msg) < size)
strcpy(buf, msg);
}
return buf;
}
static int passwd_callback(char *buf, int num, int encrypting,
void *password)
{
DEBUGASSERT(encrypting == 0);
if(!encrypting && num >= 0 && password) {
int klen = curlx_uztosi(strlen((char *)password));
if(num > klen) {
memcpy(buf, password, klen + 1);
return klen;
}
}
return 0;
}
static bool rand_enough(void)
{
return RAND_status() != 0;
}
static CURLcode ossl_seed(struct Curl_easy *data)
{
if(data->multi && data->multi->ssl_seeded)
return CURLE_OK;
if(rand_enough()) {
if(data->multi)
data->multi->ssl_seeded = TRUE;
return CURLE_OK;
}
#ifdef HAVE_RANDOM_INIT_BY_DEFAULT
failf(data, "Insufficient randomness");
return CURLE_SSL_CONNECT_ERROR;
#else
do {
unsigned char randb[64];
size_t len = sizeof(randb);
size_t i, i_max;
for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) {
struct curltime tv = curlx_now();
curlx_wait_ms(1);
tv.tv_sec *= (time_t)i + 1;
tv.tv_usec *= (int)i + 2;
tv.tv_sec ^= ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) *
(time_t)(i + 3)) << 8;
tv.tv_usec ^= (int) ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) *
(time_t)(i + 4)) << 16;
memcpy(&randb[i * sizeof(struct curltime)], &tv,
sizeof(struct curltime));
}
RAND_add(randb, (int)len, (double)len/2);
} while(!rand_enough());
# define RAND_LOAD_LENGTH 1024
{
char fname[256];
fname[0] = 0;
RAND_file_name(fname, sizeof(fname));
if(fname[0]) {
RAND_load_file(fname, RAND_LOAD_LENGTH);
if(rand_enough())
return CURLE_OK;
}
}
infof(data, "libcurl is now using a weak random seed");
return rand_enough() ? CURLE_OK :
CURLE_SSL_CONNECT_ERROR;
#endif
}
#ifndef SSL_FILETYPE_ENGINE
#define SSL_FILETYPE_ENGINE 42
#endif
#ifndef SSL_FILETYPE_PKCS12
#define SSL_FILETYPE_PKCS12 43
#endif
#ifndef SSL_FILETYPE_PROVIDER
#define SSL_FILETYPE_PROVIDER 44
#endif
static int ossl_do_file_type(const char *type)
{
if(!type || !type[0])
return SSL_FILETYPE_PEM;
if(curl_strequal(type, "PEM"))
return SSL_FILETYPE_PEM;
if(curl_strequal(type, "DER"))
return SSL_FILETYPE_ASN1;
if(curl_strequal(type, "PROV"))
return SSL_FILETYPE_PROVIDER;
if(curl_strequal(type, "ENG"))
return SSL_FILETYPE_ENGINE;
if(curl_strequal(type, "P12"))
return SSL_FILETYPE_PKCS12;
return -1;
}
#if defined(USE_OPENSSL_ENGINE) || defined(OPENSSL_HAS_PROVIDERS)
static int ssl_ui_reader(UI *ui, UI_STRING *uis)
{
const char *password;
switch(UI_get_string_type(uis)) {
case UIT_PROMPT:
case UIT_VERIFY:
password = (const char *)UI_get0_user_data(ui);
if(password && (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) {
UI_set_result(ui, uis, password);
return 1;
}
FALLTHROUGH();
default:
break;
}
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
}
static int ssl_ui_writer(UI *ui, UI_STRING *uis)
{
switch(UI_get_string_type(uis)) {
case UIT_PROMPT:
case UIT_VERIFY:
if(UI_get0_user_data(ui) &&
(UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) {
return 1;
}
FALLTHROUGH();
default:
break;
}
return (UI_method_get_writer(UI_OpenSSL()))(ui, uis);
}
static bool is_pkcs11_uri(const char *string)
{
return string && curl_strnequal(string, "pkcs11:", 7);
}
#endif
static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine);
#ifdef OPENSSL_HAS_PROVIDERS
static CURLcode ossl_set_provider(struct Curl_easy *data,
const char *provider);
#endif
static int use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob,
int type, const char *key_passwd)
{
int ret = 0;
X509 *x = NULL;
BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
if(!in)
return CURLE_OUT_OF_MEMORY;
if(type == SSL_FILETYPE_ASN1) {
x = d2i_X509_bio(in, NULL);
}
else if(type == SSL_FILETYPE_PEM) {
x = PEM_read_bio_X509(in, NULL,
passwd_callback, CURL_UNCONST(key_passwd));
}
else {
ret = 0;
goto end;
}
if(!x) {
ret = 0;
goto end;
}
ret = SSL_CTX_use_certificate(ctx, x);
end:
X509_free(x);
BIO_free(in);
return ret;
}
static int use_privatekey_blob(SSL_CTX *ctx, const struct curl_blob *blob,
int type, const char *key_passwd)
{
int ret = 0;
EVP_PKEY *pkey = NULL;
BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
if(!in)
return CURLE_OUT_OF_MEMORY;
if(type == SSL_FILETYPE_PEM)
pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback,
CURL_UNCONST(key_passwd));
else if(type == SSL_FILETYPE_ASN1)
pkey = d2i_PrivateKey_bio(in, NULL);
else
goto end;
if(!pkey)
goto end;
ret = SSL_CTX_use_PrivateKey(ctx, pkey);
EVP_PKEY_free(pkey);
end:
BIO_free(in);
return ret;
}
static int
use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob,
const char *key_passwd)
{
int ret = 0;
X509 *x = NULL;
BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
if(!in)
return CURLE_OUT_OF_MEMORY;
ERR_clear_error();
x = PEM_read_bio_X509_AUX(in, NULL,
passwd_callback, CURL_UNCONST(key_passwd));
if(!x)
goto end;
ret = SSL_CTX_use_certificate(ctx, x);
if(ERR_peek_error() != 0)
ret = 0;
if(ret) {
X509 *ca;
sslerr_t err;
if(!SSL_CTX_clear_chain_certs(ctx)) {
ret = 0;
goto end;
}
while((ca = PEM_read_bio_X509(in, NULL, passwd_callback,
CURL_UNCONST(key_passwd)))
!= 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);
BIO_free(in);
return ret;
}
static int enginecheck(struct Curl_easy *data,
SSL_CTX* ctx,
const char *key_file,
const char *key_passwd)
#ifdef USE_OPENSSL_ENGINE
{
EVP_PKEY *priv_key = NULL;
if(!data->state.engine) {
if(is_pkcs11_uri(key_file)) {
if(ossl_set_engine(data, "pkcs11") != CURLE_OK) {
return 0;
}
}
}
if(data->state.engine) {
UI_METHOD *ui_method =
UI_create_method(OSSL_UI_METHOD_CAST("curl user interface"));
if(!ui_method) {
failf(data, "unable to create " OSSL_PACKAGE " user-interface method");
return 0;
}
UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL()));
UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL()));
UI_method_set_reader(ui_method, ssl_ui_reader);
UI_method_set_writer(ui_method, ssl_ui_writer);
priv_key = ENGINE_load_private_key(data->state.engine, key_file,
ui_method,
CURL_UNCONST(key_passwd));
UI_destroy_method(ui_method);
if(!priv_key) {
failf(data, "failed to load private key from crypto engine");
return 0;
}
if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) {
failf(data, "unable to set private key");
EVP_PKEY_free(priv_key);
return 0;
}
EVP_PKEY_free(priv_key);
}
else {
failf(data, "crypto engine not set, cannot load private key");
return 0;
}
return 1;
}
#else
{
(void)ctx;
(void)key_file;
(void)key_passwd;
failf(data, "SSL_FILETYPE_ENGINE not supported for private key");
return 0;
}
#endif
static int providercheck(struct Curl_easy *data,
SSL_CTX* ctx,
const char *key_file)
#ifdef OPENSSL_HAS_PROVIDERS
{
char error_buffer[256];
if(!data->state.provider_loaded) {
if(is_pkcs11_uri(key_file)) {
if(ossl_set_provider(data, "pkcs11") != CURLE_OK) {
return 0;
}
}
}
if(data->state.provider_loaded) {
EVP_PKEY *priv_key = NULL;
OSSL_STORE_CTX *store = NULL;
OSSL_STORE_INFO *info = NULL;
UI_METHOD *ui_method =
UI_create_method(OSSL_UI_METHOD_CAST("curl user interface"));
if(!ui_method) {
failf(data, "unable to create " OSSL_PACKAGE " user-interface method");
return 0;
}
UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL()));
UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL()));
UI_method_set_reader(ui_method, ssl_ui_reader);
UI_method_set_writer(ui_method, ssl_ui_writer);
store = OSSL_STORE_open_ex(key_file, data->state.libctx,
data->state.propq, ui_method, NULL, NULL,
NULL, NULL);
if(!store) {
failf(data, "Failed to open OpenSSL store: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
UI_destroy_method(ui_method);
return 0;
}
if(OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) != 1) {
failf(data, "Failed to set store preference. Ignoring the error: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
}
info = OSSL_STORE_load(store);
if(info) {
int ossl_type = OSSL_STORE_INFO_get_type(info);
if(ossl_type == OSSL_STORE_INFO_PKEY)
priv_key = OSSL_STORE_INFO_get1_PKEY(info);
OSSL_STORE_INFO_free(info);
}
OSSL_STORE_close(store);
UI_destroy_method(ui_method);
if(!priv_key) {
failf(data, "No private key found in the openssl store: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) {
failf(data, "unable to set private key [%s]",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
EVP_PKEY_free(priv_key);
return 0;
}
EVP_PKEY_free(priv_key);
}
else {
failf(data, "crypto provider not set, cannot load private key");
return 0;
}
return 1;
}
#else
{
(void)ctx;
(void)key_file;
failf(data, "SSL_FILETYPE_PROVIDER not supported for private key");
return 0;
}
#endif
static int engineload(struct Curl_easy *data,
SSL_CTX* ctx,
const char *cert_file)
#if defined(USE_OPENSSL_ENGINE) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME)
{
char error_buffer[256];
if(!data->state.engine) {
if(is_pkcs11_uri(cert_file)) {
if(ossl_set_engine(data, "pkcs11") != CURLE_OK) {
return 0;
}
}
}
if(data->state.engine) {
const char *cmd_name = "LOAD_CERT_CTRL";
struct {
const char *cert_id;
X509 *cert;
} params;
params.cert_id = cert_file;
params.cert = NULL;
if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME,
0, CURL_UNCONST(cmd_name), NULL)) {
failf(data, "ssl engine does not support loading certificates");
return 0;
}
if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name,
0, ¶ms, NULL, 1)) {
failf(data, "ssl engine cannot load client cert with id"
" '%s' [%s]", cert_file,
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
if(!params.cert) {
failf(data, "ssl engine did not initialized the certificate "
"properly.");
return 0;
}
if(SSL_CTX_use_certificate(ctx, params.cert) != 1) {
failf(data, "unable to set client certificate [%s]",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
X509_free(params.cert);
}
else {
failf(data, "crypto engine not set, cannot load certificate");
return 0;
}
return 1;
}
#else
{
(void)ctx;
(void)cert_file;
failf(data, "SSL_FILETYPE_ENGINE not supported for certificate");
return 0;
}
#endif
static int providerload(struct Curl_easy *data,
SSL_CTX* ctx,
const char *cert_file)
#ifdef OPENSSL_HAS_PROVIDERS
{
char error_buffer[256];
if(!data->state.provider_loaded) {
if(is_pkcs11_uri(cert_file)) {
if(ossl_set_provider(data, "pkcs11") != CURLE_OK) {
return 0;
}
}
}
if(data->state.provider_loaded) {
OSSL_STORE_INFO *info = NULL;
X509 *cert = NULL;
OSSL_STORE_CTX *store =
OSSL_STORE_open_ex(cert_file, data->state.libctx,
NULL, NULL, NULL, NULL, NULL, NULL);
int rc;
if(!store) {
failf(data, "Failed to open OpenSSL store: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
if(OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) != 1) {
failf(data, "Failed to set store preference. Ignoring the error: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
}
info = OSSL_STORE_load(store);
if(info) {
int ossl_type = OSSL_STORE_INFO_get_type(info);
if(ossl_type == OSSL_STORE_INFO_CERT)
cert = OSSL_STORE_INFO_get1_CERT(info);
OSSL_STORE_INFO_free(info);
}
OSSL_STORE_close(store);
if(!cert) {
failf(data, "No cert found in the openssl store: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
rc = SSL_CTX_use_certificate(ctx, cert);
X509_free(cert);
if(rc != 1) {
failf(data, "unable to set client certificate [%s]",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
return 0;
}
}
else {
failf(data, "crypto provider not set, cannot load certificate");
return 0;
}
return 1;
}
#else
{
(void)ctx;
(void)cert_file;
failf(data, "SSL_FILETYPE_PROVIDER not supported for certificate");
return 0;
}
#endif
static int pkcs12load(struct Curl_easy *data,
SSL_CTX* ctx,
const struct curl_blob *cert_blob,
const char *cert_file,
const char *key_passwd)
{
char error_buffer[256];
BIO *cert_bio = NULL;
PKCS12 *p12 = NULL;
EVP_PKEY *pri;
X509 *x509;
int cert_done = 0;
STACK_OF(X509) *ca = NULL;
if(cert_blob) {
cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len));
if(!cert_bio) {
failf(data,
"BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)) );
return 0;
}
}
else {
cert_bio = BIO_new(BIO_s_file());
if(!cert_bio) {
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(cert_bio, CURL_UNCONST(cert_file)) <= 0) {
failf(data, "could not open PKCS12 file '%s'", cert_file);
BIO_free(cert_bio);
return 0;
}
}
p12 = d2i_PKCS12_bio(cert_bio, NULL);
BIO_free(cert_bio);
if(!p12) {
failf(data, "error reading PKCS12 file '%s'",
cert_blob ? "(memory blob)" : cert_file);
return 0;
}
if(!PKCS12_parse(p12, key_passwd, &pri, &x509, &ca)) {
failf(data,
"could not parse PKCS12 file, check password, " OSSL_PACKAGE
" error %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)) );
PKCS12_free(p12);
return 0;
}
PKCS12_free(p12);
if(SSL_CTX_use_certificate(ctx, x509) != 1) {
failf(data,
"could not load PKCS12 client certificate, " OSSL_PACKAGE
" error %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)) );
goto fail;
}
if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) {
failf(data, "unable to use private key from PKCS12 file '%s'",
cert_file);
goto fail;
}
if(!SSL_CTX_check_private_key(ctx)) {
failf(data, "private key from PKCS12 file '%s' "
"does not match certificate in same file", cert_file);
goto fail;
}
if(ca) {
while(sk_X509_num(ca)) {
X509 *x = sk_X509_pop(ca);
if(!SSL_CTX_add_client_CA(ctx, x)) {
X509_free(x);
failf(data, "cannot add certificate to client CA list");
goto fail;
}
if(!SSL_CTX_add_extra_chain_cert(ctx, x)) {
X509_free(x);
failf(data, "cannot add certificate to certificate chain");
goto fail;
}
}
}
cert_done = 1;
fail:
EVP_PKEY_free(pri);
X509_free(x509);
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
#endif
sk_X509_pop_free(ca, X509_free);
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic pop
#endif
if(!cert_done)
return 0;
return 1;
}
static CURLcode client_cert(struct Curl_easy *data,
SSL_CTX* ctx,
char *cert_file,
const struct curl_blob *cert_blob,
const char *cert_type,
char *key_file,
const struct curl_blob *key_blob,
const char *key_type,
char *key_passwd)
{
char error_buffer[256];
bool check_privkey = TRUE;
int file_type = ossl_do_file_type(cert_type);
if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE) ||
(file_type == SSL_FILETYPE_PROVIDER)) {
SSL *ssl;
X509 *x509;
bool pcks12_done = FALSE;
int cert_use_result;
if(key_passwd) {
SSL_CTX_set_default_passwd_cb_userdata(ctx, key_passwd);
SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
}
switch(file_type) {
case SSL_FILETYPE_PEM:
cert_use_result = cert_blob ?
use_certificate_chain_blob(ctx, cert_blob, key_passwd) :
SSL_CTX_use_certificate_chain_file(ctx, cert_file);
if(cert_use_result != 1) {
failf(data,
"could not load PEM client certificate from %s, " OSSL_PACKAGE
" error %s, "
"(no key found, wrong pass phrase, or wrong file format?)",
(cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file),
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)) );
return CURLE_SSL_CERTPROBLEM;
}
break;
case SSL_FILETYPE_ASN1:
cert_use_result = cert_blob ?
use_certificate_blob(ctx, cert_blob, 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 from %s, " OSSL_PACKAGE
" error %s, "
"(no key found, wrong pass phrase, or wrong file format?)",
(cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file),
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)) );
return CURLE_SSL_CERTPROBLEM;
}
break;
case SSL_FILETYPE_ENGINE:
if(!cert_file || !engineload(data, ctx, cert_file))
return CURLE_SSL_CERTPROBLEM;
break;
case SSL_FILETYPE_PROVIDER:
if(!cert_file || !providerload(data, ctx, cert_file))
return CURLE_SSL_CERTPROBLEM;
break;
case SSL_FILETYPE_PKCS12:
if(!pkcs12load(data, ctx, cert_blob, cert_file, key_passwd))
return CURLE_SSL_CERTPROBLEM;
pcks12_done = TRUE;
break;
default:
failf(data, "not supported file type '%s' for certificate", cert_type);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if((!key_file) && (!key_blob)) {
key_file = cert_file;
key_blob = cert_blob;
}
else
file_type = ossl_do_file_type(key_type);
switch(file_type) {
case SSL_FILETYPE_PEM:
case SSL_FILETYPE_ASN1:
cert_use_result = key_blob ?
use_privatekey_blob(ctx, key_blob, 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_file : "(memory blob)",
key_type ? key_type : "PEM");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
break;
case SSL_FILETYPE_ENGINE:
if(!enginecheck(data, ctx, key_file, key_passwd))
return CURLE_SSL_CERTPROBLEM;
break;
case SSL_FILETYPE_PROVIDER:
if(!providercheck(data, ctx, key_file))
return CURLE_SSL_CERTPROBLEM;
break;
case SSL_FILETYPE_PKCS12:
if(!pcks12_done) {
failf(data, "file type P12 for private key not supported");
return CURLE_SSL_CERTPROBLEM;
}
break;
default:
failf(data, "not supported file type for private key");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
ssl = SSL_new(ctx);
if(!ssl) {
failf(data, "unable to create an SSL structure");
return CURLE_OUT_OF_MEMORY;
}
x509 = SSL_get_certificate(ssl);
if(x509) {
EVP_PKEY *pktmp = X509_get_pubkey(x509);
EVP_PKEY_copy_parameters(pktmp, SSL_get_privatekey(ssl));
EVP_PKEY_free(pktmp);
}
#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_NO_DEPRECATED_3_0)
{
EVP_PKEY *priv_key = SSL_get_privatekey(ssl);
int pktype;
#ifdef HAVE_OPAQUE_EVP_PKEY
pktype = EVP_PKEY_id(priv_key);
#else
pktype = priv_key->type;
#endif
if(pktype == EVP_PKEY_RSA) {
RSA *rsa = EVP_PKEY_get1_RSA(priv_key);
if(RSA_flags(rsa) & RSA_METHOD_FLAG_NO_CHECK)
check_privkey = FALSE;
RSA_free(rsa);
}
}
#endif
SSL_free(ssl);
if(check_privkey == TRUE) {
if(!SSL_CTX_check_private_key(ctx)) {
failf(data, "Private key does not match the certificate public key");
return CURLE_SSL_CERTPROBLEM;
}
}
}
return CURLE_OK;
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d)
{
BIO *bio_out = BIO_new(BIO_s_mem());
BUF_MEM *biomem;
int rc;
CURLcode result = CURLE_OUT_OF_MEMORY;
if(bio_out) {
unsigned long flags = XN_FLAG_SEP_SPLUS_SPC |
(XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB & ~XN_FLAG_SPC_EQ);
curlx_dyn_reset(d);
rc = X509_NAME_print_ex(bio_out, a, 0, flags);
if(rc != -1) {
BIO_get_mem_ptr(bio_out, &biomem);
result = curlx_dyn_addn(d, biomem->data, biomem->length);
BIO_free(bio_out);
}
}
return result;
}
#endif
static int ossl_init(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
const uint64_t flags =
#ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN
OPENSSL_INIT_ENGINE_ALL_BUILTIN |
#endif
#ifdef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG
OPENSSL_INIT_NO_LOAD_CONFIG |
#else
OPENSSL_INIT_LOAD_CONFIG |
#endif
0;
OPENSSL_init_ssl(flags, NULL);
#else
OPENSSL_load_builtin_modules();
#ifdef USE_OPENSSL_ENGINE
ENGINE_load_builtin_engines();
#endif
#ifndef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG
CONF_modules_load_file(NULL, NULL,
CONF_MFLAGS_DEFAULT_SECTION|
CONF_MFLAGS_IGNORE_MISSING_FILE);
#endif
SSL_load_error_strings();
if(!SSLeay_add_ssl_algorithms())
return 0;
OpenSSL_add_all_algorithms();
#endif
Curl_tls_keylog_open();
return 1;
}
static void ossl_cleanup(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#else
EVP_cleanup();
#ifdef USE_OPENSSL_ENGINE
ENGINE_cleanup();
#endif
ERR_free_strings();
ERR_remove_thread_state(NULL);
CONF_modules_free();
#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS
SSL_COMP_free_compression_methods();
#endif
#endif
Curl_tls_keylog_close();
}
static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name)
{
#ifdef USE_OPENSSL_ENGINE
CURLcode result = CURLE_SSL_ENGINE_NOTFOUND;
ENGINE *e = ENGINE_by_id(name);
if(e) {
if(data->state.engine) {
ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine);
data->state.engine = NULL;
}
if(!ENGINE_init(e)) {
char buf[256];
ENGINE_free(e);
failf(data, "Failed to initialise SSL Engine '%s': %s",
name, ossl_strerror(ERR_get_error(), buf, sizeof(buf)));
result = CURLE_SSL_ENGINE_INITFAILED;
e = NULL;
}
else {
result = CURLE_OK;
}
data->state.engine = e;
return result;
}
#endif
#ifdef OPENSSL_HAS_PROVIDERS
return ossl_set_provider(data, name);
#else
(void)name;
failf(data, "OpenSSL engine not found");
return CURLE_SSL_ENGINE_NOTFOUND;
#endif
}
static CURLcode ossl_set_engine_default(struct Curl_easy *data)
{
#ifdef USE_OPENSSL_ENGINE
if(data->state.engine) {
if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) {
infof(data, "set default crypto engine '%s'",
ENGINE_get_id(data->state.engine));
}
else {
failf(data, "set default crypto engine '%s' failed",
ENGINE_get_id(data->state.engine));
return CURLE_SSL_ENGINE_SETFAILED;
}
}
#else
(void)data;
#endif
return CURLE_OK;
}
static struct curl_slist *ossl_engines_list(struct Curl_easy *data)
{
struct curl_slist *list = NULL;
#ifdef USE_OPENSSL_ENGINE
struct curl_slist *beg;
ENGINE *e;
for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) {
beg = curl_slist_append(list, ENGINE_get_id(e));
if(!beg) {
curl_slist_free_all(list);
return NULL;
}
list = beg;
}
#endif
(void)data;
return list;
}
#ifdef OPENSSL_HAS_PROVIDERS
static void ossl_provider_cleanup(struct Curl_easy *data)
{
if(data->state.baseprov) {
OSSL_PROVIDER_unload(data->state.baseprov);
data->state.baseprov = NULL;
}
if(data->state.provider) {
OSSL_PROVIDER_unload(data->state.provider);
data->state.provider = NULL;
}
OSSL_LIB_CTX_free(data->state.libctx);
data->state.libctx = NULL;
Curl_safefree(data->state.propq);
data->state.provider_loaded = FALSE;
}
#define MAX_PROVIDER_LEN 128
static CURLcode ossl_set_provider(struct Curl_easy *data, const char *iname)
{
char name[MAX_PROVIDER_LEN + 1];
struct Curl_str prov;
const char *propq = NULL;
if(!iname) {
ossl_provider_cleanup(data);
return CURLE_OK;
}
if(curlx_str_until(&iname, &prov, MAX_PROVIDER_LEN, ':'))
return CURLE_BAD_FUNCTION_ARGUMENT;
if(!curlx_str_single(&iname, ':'))
propq = iname;
memcpy(name, curlx_str(&prov), curlx_strlen(&prov));
name[curlx_strlen(&prov)] = 0;
if(!data->state.libctx) {
OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new();
if(!libctx)
return CURLE_OUT_OF_MEMORY;
if(propq) {
data->state.propq = strdup(propq);
if(!data->state.propq) {
OSSL_LIB_CTX_free(libctx);
return CURLE_OUT_OF_MEMORY;
}
}
data->state.libctx = libctx;
}
#ifndef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG
if(!OSSL_LIB_CTX_load_config(data->state.libctx, NULL)) {
infof(data, "Failed to load default openssl config. Proceeding.");
}
#endif
if(OSSL_PROVIDER_available(data->state.libctx, name)) {
data->state.provider_loaded = TRUE;
return CURLE_OK;
}
data->state.provider =
OSSL_PROVIDER_try_load(data->state.libctx, name, 1);
if(!data->state.provider) {
char error_buffer[256];
failf(data, "Failed to initialize provider: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
ossl_provider_cleanup(data);
return CURLE_SSL_ENGINE_NOTFOUND;
}
data->state.baseprov =
OSSL_PROVIDER_try_load(data->state.libctx, "base", 1);
if(!data->state.baseprov) {
ossl_provider_cleanup(data);
failf(data, "Failed to load base");
return CURLE_SSL_ENGINE_NOTFOUND;
}
else
data->state.provider_loaded = TRUE;
return CURLE_OK;
}
#endif
static CURLcode ossl_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool send_shutdown, bool *done)
{
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
CURLcode result = CURLE_OK;
char buf[1024];
int nread = -1, err;
unsigned long sslerr;
size_t i;
DEBUGASSERT(octx);
if(!octx->ssl || cf->shutdown) {
*done = TRUE;
goto out;
}
connssl->io_need = CURL_SSL_IO_NEED_NONE;
*done = FALSE;
if(!(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) {
ERR_clear_error();
for(i = 0; i < 10; ++i) {
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
CURL_TRC_CF(data, cf, "SSL shutdown not sent, read -> %d", nread);
if(nread <= 0)
break;
}
err = SSL_get_error(octx->ssl, nread);
if(!nread && err == SSL_ERROR_ZERO_RETURN) {
bool input_pending;
if(!send_shutdown) {
CURL_TRC_CF(data, cf, "SSL shutdown received, not sending");
*done = TRUE;
goto out;
}
else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) {
connssl->peer_closed = TRUE;
CURL_TRC_CF(data, cf, "peer closed connection");
*done = TRUE;
goto out;
}
}
}
if(send_shutdown && !(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) {
int rc;
ERR_clear_error();
CURL_TRC_CF(data, cf, "send SSL close notify");
rc = SSL_shutdown(octx->ssl);
if(rc == 1) {
CURL_TRC_CF(data, cf, "SSL shutdown finished");
*done = TRUE;
goto out;
}
if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, rc)) {
CURL_TRC_CF(data, cf, "SSL shutdown still wants to send");
connssl->io_need = CURL_SSL_IO_NEED_SEND;
goto out;
}
}
for(i = 0; i < 10; ++i) {
ERR_clear_error();
nread = SSL_read(octx->ssl, buf, (int)sizeof(buf));
CURL_TRC_CF(data, cf, "SSL shutdown read -> %d", nread);
if(nread <= 0)
break;
}
err = SSL_get_error(octx->ssl, nread);
switch(err) {
case SSL_ERROR_ZERO_RETURN:
if(SSL_shutdown(octx->ssl) == 1)
CURL_TRC_CF(data, cf, "SSL shutdown finished");
else
CURL_TRC_CF(data, cf, "SSL shutdown not received, but closed");
*done = TRUE;
break;
case SSL_ERROR_NONE:
case SSL_ERROR_WANT_READ:
CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive");
connssl->io_need = CURL_SSL_IO_NEED_RECV;
break;
case SSL_ERROR_WANT_WRITE:
CURL_TRC_CF(data, cf, "SSL shutdown send blocked");
connssl->io_need = CURL_SSL_IO_NEED_SEND;
break;
default:
sslerr = ERR_get_error();
CURL_TRC_CF(data, cf, "SSL shutdown, ignore recv error: '%s', errno %d",
(sslerr ?
ossl_strerror(sslerr, buf, sizeof(buf)) :
SSL_ERROR_to_str(err)),
SOCKERRNO);
*done = TRUE;
result = CURLE_OK;
break;
}
out:
cf->shutdown = (result || *done);
if(cf->shutdown || (connssl->io_need != CURL_SSL_IO_NEED_NONE))
connssl->input_pending = FALSE;
return result;
}
static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
(void)data;
DEBUGASSERT(octx);
connssl->input_pending = FALSE;
if(octx->ssl) {
SSL_free(octx->ssl);
octx->ssl = NULL;
}
if(octx->ssl_ctx) {
SSL_CTX_free(octx->ssl_ctx);
octx->ssl_ctx = NULL;
octx->x509_store_setup = FALSE;
}
if(octx->bio_method) {
ossl_bio_cf_method_free(octx->bio_method);
octx->bio_method = NULL;
}
}
static void ossl_close_all(struct Curl_easy *data)
{
#ifdef USE_OPENSSL_ENGINE
if(data->state.engine) {
ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine);
data->state.engine = NULL;
}
#else
(void)data;
#endif
#ifdef OPENSSL_HAS_PROVIDERS
ossl_provider_cleanup(data);
#endif
#ifndef HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED
ERR_remove_thread_state(NULL);
#endif
}
static CURLcode ossl_verifyhost(struct Curl_easy *data,
struct connectdata *conn,
struct ssl_peer *peer,
X509 *server_cert)
{
bool matched = FALSE;
int target;
size_t addrlen = 0;
STACK_OF(GENERAL_NAME) *altnames;
#ifdef USE_IPV6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
CURLcode result = CURLE_OK;
bool dNSName = FALSE;
bool iPAddress = FALSE;
size_t hostlen = strlen(peer->hostname);
(void)conn;
switch(peer->type) {
case CURL_SSL_PEER_IPV4:
if(!curlx_inet_pton(AF_INET, peer->hostname, &addr))
return CURLE_PEER_FAILED_VERIFICATION;
target = GEN_IPADD;
addrlen = sizeof(struct in_addr);
break;
#ifdef USE_IPV6
case CURL_SSL_PEER_IPV6:
if(!curlx_inet_pton(AF_INET6, peer->hostname, &addr))
return CURLE_PEER_FAILED_VERIFICATION;
target = GEN_IPADD;
addrlen = sizeof(struct in6_addr);
break;
#endif
case CURL_SSL_PEER_DNS:
target = GEN_DNS;
break;
default:
DEBUGASSERT(0);
failf(data, "unexpected ssl peer type: %d", peer->type);
return CURLE_PEER_FAILED_VERIFICATION;
}
altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
if(altnames) {
#ifdef HAVE_BORINGSSL_LIKE
size_t numalts;
size_t i;
#else
int numalts;
int i;
#endif
numalts = sk_GENERAL_NAME_num(altnames);
for(i = 0; (i < numalts) && !matched; i++) {
const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
if(check->type == GEN_DNS)
dNSName = TRUE;
else if(check->type == GEN_IPADD)
iPAddress = TRUE;
if(check->type == target) {
const char *altptr = (const char *)ASN1_STRING_get0_data(check->d.ia5);
size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5);
switch(target) {
case GEN_DNS:
if((altlen == strlen(altptr)) &&
Curl_cert_hostcheck(altptr, altlen, peer->hostname, hostlen)) {
matched = TRUE;
infof(data, " subjectAltName: \"%s\" matches cert's \"%.*s\"",
peer->dispname, (int)altlen, altptr);
}
break;
case GEN_IPADD:
if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) {
matched = TRUE;
infof(data,
" subjectAltName: \"%s\" matches cert's IP address!",
peer->dispname);
}
break;
}
}
}
GENERAL_NAMES_free(altnames);
}
if(matched)
;
else if(dNSName || iPAddress) {
const char *tname = (peer->type == CURL_SSL_PEER_DNS) ? "hostname" :
(peer->type == CURL_SSL_PEER_IPV4) ?
"ipv4 address" : "ipv6 address";
infof(data, " subjectAltName does not match %s %s", tname, peer->dispname);
failf(data, "SSL: no alternative certificate subject name matches "
"target %s '%s'", tname, peer->dispname);
result = CURLE_PEER_FAILED_VERIFICATION;
}
else {
int i = -1;
unsigned char *cn = NULL;
int cnlen = 0;
bool free_cn = FALSE;
X509_NAME *name = X509_get_subject_name(server_cert);
if(name) {
int j;
while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0)
i = j;
}
if(i >= 0) {
ASN1_STRING *tmp =
X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
if(tmp) {
if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
cnlen = ASN1_STRING_length(tmp);
cn = (unsigned char *)CURL_UNCONST(ASN1_STRING_get0_data(tmp));
}
else {
cnlen = ASN1_STRING_to_UTF8(&cn, tmp);
free_cn = TRUE;
}
if((cnlen <= 0) || !cn)
result = CURLE_OUT_OF_MEMORY;
else if((size_t)cnlen != strlen((char *)cn)) {
failf(data, "SSL: illegal cert name field");
result = CURLE_PEER_FAILED_VERIFICATION;
}
}
}
if(result)
;
else if(!cn) {
failf(data,
"SSL: unable to obtain common name from peer certificate");
result = CURLE_PEER_FAILED_VERIFICATION;
}
else if(!Curl_cert_hostcheck((const char *)cn, cnlen,
peer->hostname, hostlen)) {
failf(data, "SSL: certificate subject name '%s' does not match "
"target hostname '%s'", cn, peer->dispname);
result = CURLE_PEER_FAILED_VERIFICATION;
}
else {
infof(data, " common name: %s (matched)", cn);
}
if(free_cn)
OPENSSL_free(cn);
}
return result;
}
#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
static CURLcode verifystatus(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx)
{
int i, ocsp_status;
#ifdef HAVE_BORINGSSL_LIKE
const uint8_t *status;
#else
unsigned char *status;
#endif
const unsigned char *p;
CURLcode result = CURLE_OK;
OCSP_RESPONSE *rsp = NULL;
OCSP_BASICRESP *br = NULL;
X509_STORE *st = NULL;
STACK_OF(X509) *ch = NULL;
X509 *cert;
OCSP_CERTID *id = NULL;
int cert_status, crl_reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
int ret;
long len;
(void)cf;
DEBUGASSERT(octx);
len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &status);
if(!status) {
failf(data, "No OCSP response received");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
p = status;
rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
if(!rsp) {
failf(data, "Invalid OCSP response");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
ocsp_status = OCSP_response_status(rsp);
if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
failf(data, "Invalid OCSP response status: %s (%d)",
OCSP_response_status_str(ocsp_status), ocsp_status);
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
br = OCSP_response_get1_basic(rsp);
if(!br) {
failf(data, "Invalid OCSP response");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
ch = SSL_get_peer_cert_chain(octx->ssl);
if(!ch) {
failf(data, "Could not get peer certificate chain");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
st = SSL_CTX_get_cert_store(octx->ssl_ctx);
if(OCSP_basic_verify(br, ch, st, 0) <= 0) {
failf(data, "OCSP response verification failed");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
cert = SSL_get1_peer_certificate(octx->ssl);
if(!cert) {
failf(data, "Error getting peer certificate");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
for(i = 0; i < (int)sk_X509_num(ch); i++) {
X509 *issuer = sk_X509_value(ch, (ossl_valsize_t)i);
if(X509_check_issued(issuer, cert) == X509_V_OK) {
id = OCSP_cert_to_id(EVP_sha1(), cert, issuer);
break;
}
}
X509_free(cert);
if(!id) {
failf(data, "Error computing OCSP ID");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
ret = OCSP_resp_find_status(br, id, &cert_status, &crl_reason, &rev,
&thisupd, &nextupd);
OCSP_CERTID_free(id);
if(ret != 1) {
failf(data, "Could not find certificate ID in OCSP response");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) {
failf(data, "OCSP response has expired");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
infof(data, "SSL certificate status: %s (%d)",
OCSP_cert_status_str(cert_status), cert_status);
switch(cert_status) {
case V_OCSP_CERTSTATUS_GOOD:
break;
case V_OCSP_CERTSTATUS_REVOKED:
result = CURLE_SSL_INVALIDCERTSTATUS;
failf(data, "SSL certificate revocation reason: %s (%d)",
OCSP_crl_reason_str(crl_reason), crl_reason);
goto end;
case V_OCSP_CERTSTATUS_UNKNOWN:
default:
result = CURLE_SSL_INVALIDCERTSTATUS;
goto end;
}
end:
if(br)
OCSP_BASICRESP_free(br);
OCSP_RESPONSE_free(rsp);
return result;
}
#endif
static const char *ssl_msg_type(int ssl_ver, int msg)
{
#ifdef SSL2_VERSION_MAJOR
if(ssl_ver == SSL2_VERSION_MAJOR) {
switch(msg) {
case SSL2_MT_ERROR:
return "Error";
case SSL2_MT_CLIENT_HELLO:
return "Client hello";
case SSL2_MT_CLIENT_MASTER_KEY:
return "Client key";
case SSL2_MT_CLIENT_FINISHED:
return "Client finished";
case SSL2_MT_SERVER_HELLO:
return "Server hello";
case SSL2_MT_SERVER_VERIFY:
return "Server verify";
case SSL2_MT_SERVER_FINISHED:
return "Server finished";
case SSL2_MT_REQUEST_CERTIFICATE:
return "Request CERT";
case SSL2_MT_CLIENT_CERTIFICATE:
return "Client CERT";
}
}
else
#endif
if(ssl_ver == SSL3_VERSION_MAJOR) {
switch(msg) {
case SSL3_MT_HELLO_REQUEST:
return "Hello request";
case SSL3_MT_CLIENT_HELLO:
return "Client hello";
case SSL3_MT_SERVER_HELLO:
return "Server hello";
#ifdef SSL3_MT_NEWSESSION_TICKET
case SSL3_MT_NEWSESSION_TICKET:
return "Newsession Ticket";
#endif
case SSL3_MT_CERTIFICATE:
return "Certificate";
case SSL3_MT_SERVER_KEY_EXCHANGE:
return "Server key exchange";
case SSL3_MT_CLIENT_KEY_EXCHANGE:
return "Client key exchange";
case SSL3_MT_CERTIFICATE_REQUEST:
return "Request CERT";
case SSL3_MT_SERVER_DONE:
return "Server finished";
case SSL3_MT_CERTIFICATE_VERIFY:
return "CERT verify";
case SSL3_MT_FINISHED:
return "Finished";
#ifdef SSL3_MT_CERTIFICATE_STATUS
case SSL3_MT_CERTIFICATE_STATUS:
return "Certificate Status";
#endif
#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS
case SSL3_MT_ENCRYPTED_EXTENSIONS:
return "Encrypted Extensions";
#endif
#ifdef SSL3_MT_SUPPLEMENTAL_DATA
case SSL3_MT_SUPPLEMENTAL_DATA:
return "Supplemental data";
#endif
#ifdef SSL3_MT_END_OF_EARLY_DATA
case SSL3_MT_END_OF_EARLY_DATA:
return "End of early data";
#endif
#ifdef SSL3_MT_KEY_UPDATE
case SSL3_MT_KEY_UPDATE:
return "Key update";
#endif
#ifdef SSL3_MT_NEXT_PROTO
case SSL3_MT_NEXT_PROTO:
return "Next protocol";
#endif
#ifdef SSL3_MT_MESSAGE_HASH
case SSL3_MT_MESSAGE_HASH:
return "Message hash";
#endif
}
}
return "Unknown";
}
static const char *tls_rt_type(int type)
{
switch(type) {
#ifdef SSL3_RT_HEADER
case SSL3_RT_HEADER:
return "TLS header";
#endif
case SSL3_RT_CHANGE_CIPHER_SPEC:
return "TLS change cipher";
case SSL3_RT_ALERT:
return "TLS alert";
case SSL3_RT_HANDSHAKE:
return "TLS handshake";
case SSL3_RT_APPLICATION_DATA:
return "TLS app data";
default:
return "TLS Unknown";
}
}
static void ossl_trace(int direction, int ssl_ver, int content_type,
const void *buf, size_t len, SSL *ssl,
void *userp)
{
const char *verstr = "???";
struct Curl_cfilter *cf = userp;
struct Curl_easy *data = NULL;
char unknown[32];
if(!cf)
return;
data = CF_DATA_CURRENT(cf);
if(!data || !data->set.fdebug || (direction && direction != 1))
return;
switch(ssl_ver) {
#ifdef SSL2_VERSION
case SSL2_VERSION:
verstr = "SSLv2";
break;
#endif
#ifdef SSL3_VERSION
case SSL3_VERSION:
verstr = "SSLv3";
break;
#endif
case TLS1_VERSION:
verstr = "TLSv1.0";
break;
#ifdef TLS1_1_VERSION
case TLS1_1_VERSION:
verstr = "TLSv1.1";
break;
#endif
#ifdef TLS1_2_VERSION
case TLS1_2_VERSION:
verstr = "TLSv1.2";
break;
#endif
#ifdef TLS1_3_VERSION
case TLS1_3_VERSION:
verstr = "TLSv1.3";
break;
#endif
case 0:
break;
default:
curl_msnprintf(unknown, sizeof(unknown), "(%x)", ssl_ver);
verstr = unknown;
break;
}
if(ssl_ver
#ifdef SSL3_RT_HEADER
&& content_type != SSL3_RT_HEADER
#endif
#ifdef SSL3_RT_INNER_CONTENT_TYPE
&& content_type != SSL3_RT_INNER_CONTENT_TYPE
#endif
) {
const char *msg_name, *tls_rt_name;
char ssl_buf[1024];
int msg_type, txt_len;
ssl_ver >>= 8;
if(ssl_ver == SSL3_VERSION_MAJOR && content_type)
tls_rt_name = tls_rt_type(content_type);
else
tls_rt_name = "";
if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) {
msg_type = *(const char *)buf;
msg_name = "Change cipher spec";
}
else if(content_type == SSL3_RT_ALERT) {
msg_type = (((const char *)buf)[0] << 8) + ((const char *)buf)[1];
msg_name = SSL_alert_desc_string_long(msg_type);
}
else {
msg_type = *(const char *)buf;
msg_name = ssl_msg_type(ssl_ver, msg_type);
}
txt_len = curl_msnprintf(ssl_buf, sizeof(ssl_buf),
"%s (%s), %s, %s (%d):\n",
verstr, direction ? "OUT" : "IN",
tls_rt_name, msg_name, msg_type);
Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len);
}
Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
CURLINFO_SSL_DATA_IN, (const char *)buf, len);
(void)ssl;
}
#ifndef OPENSSL_NO_TLSEXT
# define HAS_ALPN_OPENSSL
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
static CURLcode
ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx,
unsigned int ssl_version_min)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
long curl_ssl_version_min = (long)ssl_version_min;
long curl_ssl_version_max;
#if defined(HAVE_BORINGSSL_LIKE) || defined(LIBRESSL_VERSION_NUMBER)
uint16_t ossl_ssl_version_min = 0;
uint16_t ossl_ssl_version_max = 0;
#else
long ossl_ssl_version_min = 0;
long ossl_ssl_version_max = 0;
#endif
switch(curl_ssl_version_min) {
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_0:
ossl_ssl_version_min = TLS1_VERSION;
break;
case CURL_SSLVERSION_TLSv1_1:
ossl_ssl_version_min = TLS1_1_VERSION;
break;
case CURL_SSLVERSION_TLSv1_2:
ossl_ssl_version_min = TLS1_2_VERSION;
break;
case CURL_SSLVERSION_TLSv1_3:
#ifdef TLS1_3_VERSION
ossl_ssl_version_min = TLS1_3_VERSION;
break;
#else
return CURLE_NOT_BUILT_IN;
#endif
}
if(curl_ssl_version_min != CURL_SSLVERSION_DEFAULT) {
if(!SSL_CTX_set_min_proto_version(ctx, ossl_ssl_version_min)) {
return CURLE_SSL_CONNECT_ERROR;
}
}
curl_ssl_version_max = (long)conn_config->version_max;
switch(curl_ssl_version_max) {
case CURL_SSLVERSION_MAX_TLSv1_0:
ossl_ssl_version_max = TLS1_VERSION;
break;
case CURL_SSLVERSION_MAX_TLSv1_1:
ossl_ssl_version_max = TLS1_1_VERSION;
break;
case CURL_SSLVERSION_MAX_TLSv1_2:
ossl_ssl_version_max = TLS1_2_VERSION;
break;
#ifdef TLS1_3_VERSION
case CURL_SSLVERSION_MAX_TLSv1_3:
ossl_ssl_version_max = TLS1_3_VERSION;
break;
#endif
case CURL_SSLVERSION_MAX_NONE:
case CURL_SSLVERSION_MAX_DEFAULT:
default:
ossl_ssl_version_max = 0;
break;
}
if(!SSL_CTX_set_max_proto_version(ctx, ossl_ssl_version_max)) {
return CURLE_SSL_CONNECT_ERROR;
}
return CURLE_OK;
}
#endif
#ifdef HAVE_BORINGSSL_LIKE
typedef uint32_t ctx_option_t;
#elif defined(HAVE_OPENSSL3)
typedef uint64_t ctx_option_t;
#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(LIBRESSL_VERSION_NUMBER)
typedef unsigned long ctx_option_t;
#else
typedef long ctx_option_t;
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static CURLcode
ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
long ssl_version = conn_config->version;
long ssl_version_max = conn_config->version_max;
(void)data;
switch(ssl_version) {
case CURL_SSLVERSION_TLSv1_3:
#ifdef TLS1_3_VERSION
{
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
DEBUGASSERT(octx);
SSL_CTX_set_max_proto_version(octx->ssl_ctx, TLS1_3_VERSION);
*ctx_options |= SSL_OP_NO_TLSv1_2;
}
#else
(void)ctx_options;
failf(data, OSSL_PACKAGE " was built without TLS 1.3 support");
return CURLE_NOT_BUILT_IN;
#endif
FALLTHROUGH();
case CURL_SSLVERSION_TLSv1_2:
*ctx_options |= SSL_OP_NO_TLSv1_1;
FALLTHROUGH();
case CURL_SSLVERSION_TLSv1_1:
*ctx_options |= SSL_OP_NO_TLSv1;
FALLTHROUGH();
case CURL_SSLVERSION_TLSv1_0:
case CURL_SSLVERSION_TLSv1:
break;
}
switch(ssl_version_max) {
case CURL_SSLVERSION_MAX_TLSv1_0:
*ctx_options |= SSL_OP_NO_TLSv1_1;
FALLTHROUGH();
case CURL_SSLVERSION_MAX_TLSv1_1:
*ctx_options |= SSL_OP_NO_TLSv1_2;
FALLTHROUGH();
case CURL_SSLVERSION_MAX_TLSv1_2:
#ifdef TLS1_3_VERSION
*ctx_options |= SSL_OP_NO_TLSv1_3;
#endif
break;
case CURL_SSLVERSION_MAX_TLSv1_3:
#ifdef TLS1_3_VERSION
break;
#else
failf(data, OSSL_PACKAGE " was built without TLS 1.3 support");
return CURLE_NOT_BUILT_IN;
#endif
}
return CURLE_OK;
}
#endif
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
SSL_SESSION *session,
int ietf_tls_id,
const char *alpn,
unsigned char *quic_tp,
size_t quic_tp_len)
{
unsigned char *der_session_buf = NULL;
unsigned char *qtp_clone = NULL;
CURLcode result = CURLE_OK;
if(!cf || !data)
goto out;
if(Curl_ssl_scache_use(cf, data)) {
struct Curl_ssl_session *sc_session = NULL;
size_t der_session_size;
unsigned char *der_session_ptr;
size_t earlydata_max = 0;
der_session_size = i2d_SSL_SESSION(session, NULL);
if(der_session_size == 0) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
der_session_buf = der_session_ptr = malloc(der_session_size);
if(!der_session_buf) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
der_session_size = i2d_SSL_SESSION(session, &der_session_ptr);
if(der_session_size == 0) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
#ifdef HAVE_OPENSSL_EARLYDATA
earlydata_max = SSL_SESSION_get_max_early_data(session);
#endif
if(quic_tp && quic_tp_len) {
qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len);
if(!qtp_clone) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
result = Curl_ssl_session_create2(der_session_buf, der_session_size,
ietf_tls_id, alpn,
(curl_off_t)time(NULL) +
SSL_SESSION_get_timeout(session),
earlydata_max, qtp_clone, quic_tp_len,
&sc_session);
der_session_buf = NULL;
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
}
}
out:
free(der_session_buf);
return result;
}
static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
{
struct Curl_cfilter *cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
if(cf) {
struct Curl_easy *data = CF_DATA_CURRENT(cf);
struct ssl_connect_data *connssl = cf->ctx;
Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid,
SSL_version(ssl), connssl->negotiated.alpn,
NULL, 0);
}
return 0;
}
static CURLcode load_cacert_from_memory(X509_STORE *store,
const struct curl_blob *ca_info_blob)
{
BIO *cbio = NULL;
STACK_OF(X509_INFO) *inf = NULL;
int i, count = 0;
X509_INFO *itmp = NULL;
if(ca_info_blob->len > (size_t)INT_MAX)
return CURLE_SSL_CACERT_BADFILE;
cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len);
if(!cbio)
return CURLE_OUT_OF_MEMORY;
inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if(!inf) {
BIO_free(cbio);
return CURLE_SSL_CACERT_BADFILE;
}
for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) {
itmp = sk_X509_INFO_value(inf, (ossl_valsize_t)i);
if(itmp->x509) {
if(X509_STORE_add_cert(store, itmp->x509)) {
++count;
}
else {
count = 0;
break;
}
}
if(itmp->crl) {
if(X509_STORE_add_crl(store, itmp->crl)) {
++count;
}
else {
count = 0;
break;
}
}
}
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
#endif
sk_X509_INFO_pop_free(inf, X509_INFO_free);
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic pop
#endif
BIO_free(cbio);
return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE;
}
#ifdef USE_WIN32_CRYPTO
static CURLcode ossl_win_load_store(struct Curl_easy *data,
const char *win_store,
X509_STORE *store,
bool *padded)
{
CURLcode result = CURLE_OK;
HCERTSTORE hStore;
*padded = FALSE;
hStore = CertOpenSystemStoreA(0, win_store);
if(hStore) {
PCCERT_CONTEXT pContext = NULL;
CERT_ENHKEY_USAGE *enhkey_usage = NULL;
DWORD enhkey_usage_size = 0;
result = CURLE_OK;
for(;;) {
X509 *x509;
FILETIME now;
BYTE key_usage[2];
DWORD req_size;
const unsigned char *encoded_cert;
pContext = CertEnumCertificatesInStore(hStore, pContext);
if(!pContext)
break;
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
else {
char cert_name[256];
if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
NULL, cert_name, sizeof(cert_name)))
infof(data, "SSL: unknown cert name");
else
infof(data, "SSL: Checking cert \"%s\"", cert_name);
}
#endif
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
if(!encoded_cert)
continue;
GetSystemTimeAsFileTime(&now);
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
continue;
if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
pContext->pCertInfo,
key_usage, sizeof(key_usage))) {
if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
continue;
}
else if(GetLastError())
continue;
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
if(req_size && req_size > enhkey_usage_size) {
void *tmp = realloc(enhkey_usage, req_size);
if(!tmp) {
failf(data, "SSL: Out of memory allocating for OID list");
result = CURLE_OUT_OF_MEMORY;
break;
}
enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
enhkey_usage_size = req_size;
}
if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
if(!enhkey_usage->cUsageIdentifier) {
if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
continue;
}
else {
DWORD i;
bool found = FALSE;
for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
if(!strcmp("1.3.6.1.5.5.7.3.1" ,
enhkey_usage->rgpszUsageIdentifier[i])) {
found = TRUE;
break;
}
}
if(!found)
continue;
}
}
else
continue;
}
else
continue;
x509 = d2i_X509(NULL, &encoded_cert, (long)pContext->cbCertEncoded);
if(!x509)
continue;
if(X509_STORE_add_cert(store, x509) == 1) {
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
infof(data, "SSL: Imported cert");
#endif
*padded = TRUE;
}
X509_free(x509);
}
free(enhkey_usage);
CertFreeCertificateContext(pContext);
CertCloseStore(hStore, 0);
if(result)
return result;
}
return result;
}
static CURLcode ossl_windows_load_anchors(struct Curl_cfilter *cf,
struct Curl_easy *data,
X509_STORE *store,
bool *padded)
{
const char *win_stores[] = {
"ROOT",
"CA"
};
size_t i;
CURLcode result = CURLE_OK;
*padded = FALSE;
for(i = 0; i < CURL_ARRAYSIZE(win_stores); ++i) {
bool store_added = FALSE;
result = ossl_win_load_store(data, win_stores[i], store, &store_added);
if(result)
return result;
if(store_added) {
CURL_TRC_CF(data, cf, "added trust anchors from Windows %s store",
win_stores[i]);
*padded = TRUE;
}
else
infof(data, "error importing Windows %s store, continuing anyway",
win_stores[i]);
}
return result;
}
#endif
static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
X509_STORE *store)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
CURLcode result = CURLE_OK;
const char * const ssl_cafile =
(conn_config->ca_info_blob ? NULL : conn_config->CAfile);
const char * const ssl_capath = conn_config->CApath;
bool have_native_check = FALSE;
octx->store_is_empty = TRUE;
if(ssl_config->native_ca_store) {
#ifdef USE_WIN32_CRYPTO
bool added = FALSE;
result = ossl_windows_load_anchors(cf, data, store, &added);
if(result)
return result;
if(added) {
infof(data, " Native: Windows System Stores ROOT+CA");
octx->store_is_empty = FALSE;
}
#elif defined(USE_APPLE_SECTRUST)
infof(data, " Native: Apple SecTrust");
have_native_check = TRUE;
#endif
}
if(conn_config->ca_info_blob) {
result = load_cacert_from_memory(store, conn_config->ca_info_blob);
if(result) {
failf(data, "error adding trust anchors from certificate blob: %d",
result);
return result;
}
infof(data, " CA Blob from configuration");
octx->store_is_empty = FALSE;
}
if(ssl_cafile || ssl_capath) {
#ifdef HAVE_OPENSSL3
if(ssl_cafile) {
if(!X509_STORE_load_file(store, ssl_cafile)) {
if(octx->store_is_empty && !have_native_check) {
failf(data, "error adding trust anchors from file: %s", ssl_cafile);
return CURLE_SSL_CACERT_BADFILE;
}
else
infof(data, "error setting certificate file, continuing anyway");
}
infof(data, " CAfile: %s", ssl_cafile);
octx->store_is_empty = FALSE;
}
if(ssl_capath) {
if(!X509_STORE_load_path(store, ssl_capath)) {
if(octx->store_is_empty && !have_native_check) {
failf(data, "error adding trust anchors from path: %s", ssl_capath);
return CURLE_SSL_CACERT_BADFILE;
}
else
infof(data, "error setting certificate path, continuing anyway");
}
infof(data, " CApath: %s", ssl_capath);
octx->store_is_empty = FALSE;
}
#else
if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
if(octx->store_is_empty && !have_native_check) {
failf(data, "error adding trust anchors from locations:"
" CAfile: %s CApath: %s",
ssl_cafile ? ssl_cafile : "none",
ssl_capath ? ssl_capath : "none");
return CURLE_SSL_CACERT_BADFILE;
}
else {
infof(data, "error setting certificate verify locations,"
" continuing anyway");
}
}
if(ssl_cafile)
infof(data, " CAfile: %s", ssl_cafile);
if(ssl_capath)
infof(data, " CApath: %s", ssl_capath);
octx->store_is_empty = FALSE;
#endif
}
#ifdef CURL_CA_FALLBACK
if(octx->store_is_empty) {
X509_STORE_set_default_paths(store);
infof(data, " OpenSSL default paths (fallback)");
octx->store_is_empty = FALSE;
}
#endif
if(octx->store_is_empty && !have_native_check)
infof(data, " no trust anchors configured");
return result;
}
static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
X509_STORE *store)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
CURLcode result = CURLE_OK;
X509_LOOKUP *lookup = NULL;
const char * const ssl_crlfile = ssl_config->primary.CRLfile;
unsigned long x509flags = 0;
CURL_TRC_CF(data, cf, "configuring OpenSSL's x509 trust store");
if(!store)
return CURLE_OUT_OF_MEMORY;
if(!conn_config->verifypeer) {
infof(data, "SSL Trust: peer verification disabled");
return CURLE_OK;
}
infof(data, "SSL Trust Anchors:");
result = ossl_load_trust_anchors(cf, data, octx, store);
if(result)
return result;
if(ssl_crlfile) {
lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if(!lookup ||
(!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
failf(data, "error loading CRL file: %s", ssl_crlfile);
return CURLE_SSL_CRL_BADFILE;
}
x509flags = X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL;
infof(data, " CRLfile: %s", ssl_crlfile);
}
x509flags |= X509_V_FLAG_TRUSTED_FIRST;
if(!ssl_config->no_partialchain && !ssl_crlfile) {
x509flags |= X509_V_FLAG_PARTIAL_CHAIN;
}
(void)X509_STORE_set_flags(store, x509flags);
return result;
}
#ifdef HAVE_SSL_X509_STORE_SHARE
#define MPROTO_OSSL_X509_KEY "tls:ossl:x509:share"
struct ossl_x509_share {
char *CAfile;
X509_STORE *store;
struct curltime time;
BIT(store_is_empty);
};
static void oss_x509_share_free(void *key, size_t key_len, void *p)
{
struct ossl_x509_share *share = p;
DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY)-1));
DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len));
(void)key;
(void)key_len;
if(share->store) {
X509_STORE_free(share->store);
}
free(share->CAfile);
free(share);
}
static bool
ossl_cached_x509_store_expired(const struct Curl_easy *data,
const struct ossl_x509_share *mb)
{
const struct ssl_general_config *cfg = &data->set.general_ssl;
if(cfg->ca_cache_timeout < 0)
return FALSE;
else {
struct curltime now = curlx_now();
timediff_t elapsed_ms = curlx_timediff(now, mb->time);
timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000;
return elapsed_ms >= timeout_ms;
}
}
static bool
ossl_cached_x509_store_different(struct Curl_cfilter *cf,
const struct ossl_x509_share *mb)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
if(!mb->CAfile || !conn_config->CAfile)
return mb->CAfile != conn_config->CAfile;
return strcmp(mb->CAfile, conn_config->CAfile);
}
static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf,
const struct Curl_easy *data,
bool *pempty)
{
struct Curl_multi *multi = data->multi;
struct ossl_x509_share *share;
X509_STORE *store = NULL;
DEBUGASSERT(multi);
*pempty = TRUE;
share = multi ? Curl_hash_pick(&multi->proto_hash,
CURL_UNCONST(MPROTO_OSSL_X509_KEY),
sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL;
if(share && share->store &&
!ossl_cached_x509_store_expired(data, share) &&
!ossl_cached_x509_store_different(cf, share)) {
store = share->store;
*pempty = share->store_is_empty;
}
return store;
}
static void ossl_set_cached_x509_store(struct Curl_cfilter *cf,
const struct Curl_easy *data,
X509_STORE *store,
bool is_empty)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct Curl_multi *multi = data->multi;
struct ossl_x509_share *share;
DEBUGASSERT(multi);
if(!multi)
return;
share = Curl_hash_pick(&multi->proto_hash,
CURL_UNCONST(MPROTO_OSSL_X509_KEY),
sizeof(MPROTO_OSSL_X509_KEY)-1);
if(!share) {
share = calloc(1, sizeof(*share));
if(!share)
return;
if(!Curl_hash_add2(&multi->proto_hash,
CURL_UNCONST(MPROTO_OSSL_X509_KEY),
sizeof(MPROTO_OSSL_X509_KEY)-1,
share, oss_x509_share_free)) {
free(share);
return;
}
}
if(X509_STORE_up_ref(store)) {
char *CAfile = NULL;
if(conn_config->CAfile) {
CAfile = strdup(conn_config->CAfile);
if(!CAfile) {
X509_STORE_free(store);
return;
}
}
if(share->store) {
X509_STORE_free(share->store);
free(share->CAfile);
}
share->time = curlx_now();
share->store = store;
share->store_is_empty = is_empty;
share->CAfile = CAfile;
}
}
CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
CURLcode result = CURLE_OK;
X509_STORE *cached_store;
bool cache_criteria_met, is_empty;
cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) &&
conn_config->verifypeer &&
!conn_config->CApath &&
!conn_config->ca_info_blob &&
!ssl_config->primary.CRLfile &&
!ssl_config->native_ca_store;
ERR_set_mark();
cached_store = ossl_get_cached_x509_store(cf, data, &is_empty);
if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) {
SSL_CTX_set_cert_store(octx->ssl_ctx, cached_store);
octx->store_is_empty = is_empty;
}
else {
X509_STORE *store = SSL_CTX_get_cert_store(octx->ssl_ctx);
result = ossl_populate_x509_store(cf, data, octx, store);
if(result == CURLE_OK && cache_criteria_met) {
ossl_set_cached_x509_store(cf, data, store, octx->store_is_empty);
}
}
ERR_pop_to_mark();
return result;
}
#else
CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx)
{
CURLcode result;
X509_STORE *store;
ERR_set_mark();
store = SSL_CTX_get_cert_store(octx->ssl_ctx);
result = ossl_populate_x509_store(cf, data, octx, store);
ERR_pop_to_mark();
return result;
}
#endif
static CURLcode
ossl_init_session_and_alpns(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
const struct alpn_spec *alpns_requested,
Curl_ossl_init_session_reuse_cb *sess_reuse_cb)
{
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf);
struct alpn_spec alpns;
char error_buffer[256];
CURLcode result;
Curl_alpn_copy(&alpns, alpns_requested);
octx->reused_session = FALSE;
if(Curl_ssl_scache_use(cf, data) && !conn_cfg->verifystatus) {
struct Curl_ssl_session *scs = NULL;
result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
if(!result && scs && scs->sdata && scs->sdata_len) {
const unsigned char *der_sessionid = scs->sdata;
size_t der_sessionid_size = scs->sdata_len;
SSL_SESSION *ssl_session = NULL;
ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid,
(long)der_sessionid_size);
if(ssl_session) {
if(!SSL_set_session(octx->ssl, ssl_session)) {
infof(data, "SSL: SSL_set_session not accepted, "
"continuing without: %s",
ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer)));
}
else {
infof(data, "SSL reusing session with ALPN '%s'",
scs->alpn ? scs->alpn : "-");
octx->reused_session = TRUE;
#ifdef HAVE_OPENSSL_EARLYDATA
if(ssl_config->earlydata && scs->alpn &&
SSL_SESSION_get_max_early_data(ssl_session) &&
!cf->conn->connect_only &&
(SSL_version(octx->ssl) == TLS1_3_VERSION)) {
bool do_early_data = FALSE;
if(sess_reuse_cb) {
result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data);
if(result)
return result;
}
if(do_early_data) {
Curl_alpn_restrict_to(&alpns, scs->alpn);
}
}
#else
(void)ssl_config;
(void)sess_reuse_cb;
#endif
}
SSL_SESSION_free(ssl_session);
}
else {
infof(data, "SSL session not accepted by OpenSSL, continuing without");
}
}
Curl_ssl_scache_return(cf, data, peer->scache_key, scs);
}
#ifdef HAS_ALPN_OPENSSL
if(alpns.count) {
struct alpn_proto_buf proto;
memset(&proto, 0, sizeof(proto));
result = Curl_alpn_to_proto_buf(&proto, &alpns);
if(result) {
failf(data, "Error determining ALPN");
return CURLE_SSL_CONNECT_ERROR;
}
if(SSL_set_alpn_protos(octx->ssl, proto.data, (int)proto.len)) {
failf(data, "Error setting ALPN");
return CURLE_SSL_CONNECT_ERROR;
}
}
#endif
return CURLE_OK;
}
#ifdef USE_ECH_OPENSSL
static CURLcode ossl_init_ech(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer)
{
unsigned char *ech_config = NULL;
size_t ech_config_len = 0;
char *outername = data->set.str[STRING_ECH_PUBLIC];
int trying_ech_now = 0;
CURLcode result;
if(!ECH_ENABLED(data))
return CURLE_OK;
if(data->set.tls_ech & CURLECH_GREASE) {
infof(data, "ECH: will GREASE ClientHello");
# ifdef HAVE_BORINGSSL_LIKE
SSL_set_enable_ech_grease(octx->ssl, 1);
# else
SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE);
# endif
}
else if(data->set.tls_ech & CURLECH_CLA_CFG) {
# ifdef HAVE_BORINGSSL_LIKE
const char *b64 = data->set.str[STRING_ECH_CONFIG];
if(!b64) {
infof(data, "ECH: ECHConfig from command line empty");
return CURLE_SSL_CONNECT_ERROR;
}
ech_config_len = 2 * strlen(b64);
result = curlx_base64_decode(b64, &ech_config, &ech_config_len);
if(result || !ech_config) {
infof(data, "ECH: cannot base64 decode ECHConfig from command line");
if(data->set.tls_ech & CURLECH_HARD)
return result;
}
if(SSL_set1_ech_config_list(octx->ssl, ech_config,
ech_config_len) != 1) {
infof(data, "ECH: SSL_ECH_set1_ech_config_list failed");
if(data->set.tls_ech & CURLECH_HARD) {
free(ech_config);
return CURLE_SSL_CONNECT_ERROR;
}
}
free(ech_config);
trying_ech_now = 1;
# else
ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG];
if(!ech_config) {
infof(data, "ECH: ECHConfig from command line empty");
return CURLE_SSL_CONNECT_ERROR;
}
ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]);
if(SSL_set1_ech_config_list(octx->ssl, ech_config,
ech_config_len) != 1) {
infof(data, "ECH: SSL_ECH_set1_ech_config_list failed");
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
}
else
trying_ech_now = 1;
# endif
infof(data, "ECH: ECHConfig from command line");
}
else {
struct Curl_dns_entry *dns = NULL;
if(peer->hostname)
dns = Curl_dnscache_get(data, peer->hostname, peer->port,
cf->conn->ip_version);
if(!dns) {
infof(data, "ECH: requested but no DNS info available");
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
}
else {
struct Curl_https_rrinfo *rinfo = NULL;
rinfo = dns->hinfo;
if(rinfo && rinfo->echconfiglist) {
unsigned char *ecl = rinfo->echconfiglist;
size_t elen = rinfo->echconfiglist_len;
infof(data, "ECH: ECHConfig from DoH HTTPS RR");
if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
infof(data, "ECH: SSL_set1_ech_config_list failed");
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
}
else {
trying_ech_now = 1;
infof(data, "ECH: imported ECHConfigList of length %zu", elen);
}
}
else {
infof(data, "ECH: requested but no ECHConfig available");
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
}
Curl_resolv_unlink(data, &dns);
}
}
# ifdef HAVE_BORINGSSL_LIKE
if(trying_ech_now && outername) {
infof(data, "ECH: setting public_name not supported with BoringSSL");
return CURLE_SSL_CONNECT_ERROR;
}
# else
if(trying_ech_now && outername) {
infof(data, "ECH: inner: '%s', outer: '%s'",
peer->hostname ? peer->hostname : "NULL", outername);
result = SSL_ech_set1_server_names(octx->ssl,
peer->hostname, outername,
0 );
if(result != 1) {
infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result);
return CURLE_SSL_CONNECT_ERROR;
}
}
# endif
if(trying_ech_now
&& SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) {
infof(data, "ECH: cannot force TLSv1.3 [ERROR]");
return CURLE_SSL_CONNECT_ERROR;
}
return CURLE_OK;
}
#endif
static CURLcode ossl_init_ssl(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
const struct alpn_spec *alpns_requested,
void *ssl_user_data,
Curl_ossl_init_session_reuse_cb *sess_reuse_cb)
{
if(octx->ssl)
SSL_free(octx->ssl);
octx->ssl = SSL_new(octx->ssl_ctx);
if(!octx->ssl) {
failf(data, "SSL: could not create a context (handle)");
return CURLE_OUT_OF_MEMORY;
}
SSL_set_app_data(octx->ssl, ssl_user_data);
#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
if(Curl_ssl_cf_get_primary_config(cf)->verifystatus)
SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp);
#endif
SSL_set_connect_state(octx->ssl);
if(peer->sni) {
if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) {
failf(data, "Failed set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
}
#ifdef USE_ECH_OPENSSL
{
CURLcode result = ossl_init_ech(octx, cf, data, peer);
if(result)
return result;
}
#endif
return ossl_init_session_and_alpns(octx, cf, data, peer,
alpns_requested, sess_reuse_cb);
}
static CURLcode ossl_init_method(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
const SSL_METHOD **pmethod,
unsigned int *pssl_version_min)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
*pmethod = NULL;
*pssl_version_min = conn_config->version;
switch(peer->transport) {
case TRNSPRT_TCP:
switch(*pssl_version_min) {
case CURL_SSLVERSION_DEFAULT:
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_0:
case CURL_SSLVERSION_TLSv1_1:
case CURL_SSLVERSION_TLSv1_2:
case CURL_SSLVERSION_TLSv1_3:
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
*pmethod = TLS_client_method();
#else
*pmethod = SSLv23_client_method();
#endif
break;
case CURL_SSLVERSION_SSLv2:
failf(data, "No SSLv2 support");
return CURLE_NOT_BUILT_IN;
case CURL_SSLVERSION_SSLv3:
failf(data, "No SSLv3 support");
return CURLE_NOT_BUILT_IN;
default:
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
return CURLE_SSL_CONNECT_ERROR;
}
break;
case TRNSPRT_QUIC:
*pssl_version_min = CURL_SSLVERSION_TLSv1_3;
if(conn_config->version_max &&
(conn_config->version_max != CURL_SSLVERSION_MAX_DEFAULT) &&
(conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) {
failf(data, "QUIC needs at least TLS version 1.3");
return CURLE_SSL_CONNECT_ERROR;
}
#ifdef USE_OPENSSL_QUIC
*pmethod = OSSL_QUIC_client_method();
#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
*pmethod = TLS_method();
#else
*pmethod = SSLv23_client_method();
#endif
break;
default:
failf(data, "unsupported transport %d in SSL init", peer->transport);
return CURLE_SSL_CONNECT_ERROR;
}
return *pmethod ? CURLE_OK : CURLE_SSL_CONNECT_ERROR;
}
CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
const struct alpn_spec *alpns_requested,
Curl_ossl_ctx_setup_cb *cb_setup,
void *cb_user_data,
Curl_ossl_new_session_cb *cb_new_session,
void *ssl_user_data,
Curl_ossl_init_session_reuse_cb *sess_reuse_cb)
{
CURLcode result = CURLE_OK;
const char *ciphers;
const SSL_METHOD *req_method = NULL;
ctx_option_t ctx_options = 0;
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
char * const ssl_cert = ssl_config->primary.clientcert;
const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
const char * const ssl_cert_type = ssl_config->cert_type;
unsigned int ssl_version_min;
char error_buffer[256];
result = ossl_seed(data);
if(result)
return result;
ssl_config->certverifyresult = !X509_V_OK;
result = ossl_init_method(cf, data, peer, &req_method, &ssl_version_min);
if(result)
return result;
DEBUGASSERT(req_method);
DEBUGASSERT(!octx->ssl_ctx);
octx->ssl_ctx =
#ifdef OPENSSL_HAS_PROVIDERS
data->state.libctx ?
SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method):
#endif
SSL_CTX_new(req_method);
if(!octx->ssl_ctx) {
failf(data, "SSL: could not create a context: %s",
ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
return CURLE_OUT_OF_MEMORY;
}
if(cb_setup) {
result = cb_setup(cf, data, cb_user_data);
if(result)
return result;
}
if(data->set.fdebug && data->set.verbose) {
SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace);
SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf);
}
ctx_options = SSL_OP_ALL | SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION;
ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
if(!ssl_config->enable_beast)
ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
switch(ssl_version_min) {
case CURL_SSLVERSION_SSLv2:
case CURL_SSLVERSION_SSLv3:
return CURLE_NOT_BUILT_IN;
case CURL_SSLVERSION_DEFAULT:
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_0:
case CURL_SSLVERSION_TLSv1_1:
case CURL_SSLVERSION_TLSv1_2:
case CURL_SSLVERSION_TLSv1_3:
ctx_options |= SSL_OP_NO_SSLv2;
ctx_options |= SSL_OP_NO_SSLv3;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx, ssl_version_min);
#else
result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data);
#endif
if(result)
return result;
break;
default:
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
return CURLE_SSL_CONNECT_ERROR;
}
SSL_CTX_set_options(octx->ssl_ctx, ctx_options);
SSL_CTX_set_read_ahead(octx->ssl_ctx, 1);
#ifdef HAVE_SSL_CTX_SET_DEFAULT_READ_BUFFER_LEN
SSL_CTX_set_default_read_buffer_len(octx->ssl_ctx, 0x401e * 4);
#endif
SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
ciphers = conn_config->cipher_list;
if(!ciphers && (peer->transport != TRNSPRT_QUIC))
ciphers = DEFAULT_CIPHER_SELECTION;
if(ciphers && (ssl_version_min < CURL_SSLVERSION_TLSv1_3)) {
if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) {
failf(data, "failed setting cipher list: %s", ciphers);
return CURLE_SSL_CIPHER;
}
infof(data, "Cipher selection: %s", ciphers);
}
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
{
const char *ciphers13 = conn_config->cipher_list13;
if(ciphers13 &&
(!conn_config->version_max ||
(conn_config->version_max == CURL_SSLVERSION_MAX_DEFAULT) ||
(conn_config->version_max >= CURL_SSLVERSION_MAX_TLSv1_3))) {
if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) {
failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
return CURLE_SSL_CIPHER;
}
infof(data, "TLS 1.3 cipher selection: %s", ciphers13);
}
}
#endif
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
result = client_cert(data, octx->ssl_ctx,
ssl_cert, ssl_cert_blob, ssl_cert_type,
ssl_config->key, ssl_config->key_blob,
ssl_config->key_type, ssl_config->key_passwd);
if(result)
return result;
}
#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
SSL_CTX_set_post_handshake_auth(octx->ssl_ctx, 1);
#endif
{
const char *curves = conn_config->curves;
if(curves) {
#ifdef HAVE_BORINGSSL_LIKE
#define OSSL_CURVE_CAST(x) (x)
#else
#define OSSL_CURVE_CAST(x) (char *)CURL_UNCONST(x)
#endif
if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, OSSL_CURVE_CAST(curves))) {
failf(data, "failed setting curves list: '%s'", curves);
return CURLE_SSL_CIPHER;
}
}
}
#ifdef HAVE_SSL_CTX_SET1_SIGALGS
#define OSSL_SIGALG_CAST(x) OSSL_CURVE_CAST(x)
{
const char *signature_algorithms = conn_config->signature_algorithms;
if(signature_algorithms) {
if(!SSL_CTX_set1_sigalgs_list(octx->ssl_ctx,
OSSL_SIGALG_CAST(signature_algorithms))) {
failf(data, "failed setting signature algorithms: '%s'",
signature_algorithms);
return CURLE_SSL_CIPHER;
}
}
}
#endif
#if defined(HAVE_OPENSSL_SRP) && defined(USE_TLS_SRP)
if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) {
char * const ssl_username = ssl_config->primary.username;
char * const ssl_password = ssl_config->primary.password;
infof(data, "Using TLS-SRP username: %s", ssl_username);
if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) {
failf(data, "Unable to set SRP username");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) {
failf(data, "failed setting SRP password");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!conn_config->cipher_list) {
infof(data, "Setting cipher list SRP");
if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) {
failf(data, "failed setting SRP cipher list");
return CURLE_SSL_CIPHER;
}
}
}
#endif
SSL_CTX_set_verify(octx->ssl_ctx, SSL_VERIFY_NONE, NULL);
#ifdef HAVE_KEYLOG_CALLBACK
if(Curl_tls_keylog_enabled()) {
SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback);
}
#endif
if(cb_new_session) {
SSL_CTX_set_session_cache_mode(octx->ssl_ctx,
SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session);
}
if(data->set.ssl.fsslctx) {
if(!octx->x509_store_setup) {
result = Curl_ssl_setup_x509_store(cf, data, octx);
if(result)
return result;
octx->x509_store_setup = TRUE;
}
Curl_set_in_callback(data, TRUE);
result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx,
data->set.ssl.fsslctxp);
Curl_set_in_callback(data, FALSE);
if(result) {
failf(data, "error signaled by ssl ctx callback");
return result;
}
}
return ossl_init_ssl(octx, cf, data, peer, alpns_requested,
ssl_user_data, sess_reuse_cb);
}
static CURLcode ossl_on_session_reuse(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct alpn_spec *alpns,
struct Curl_ssl_session *scs,
bool *do_early_data)
{
struct ssl_connect_data *connssl = cf->ctx;
CURLcode result = CURLE_OK;
*do_early_data = FALSE;
connssl->earlydata_max = scs->earlydata_max;
if(!connssl->earlydata_max) {
CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
}
else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) {
CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data");
}
else {
infof(data, "SSL session allows %zu bytes of early data, "
"reusing ALPN '%s'", connssl->earlydata_max, scs->alpn);
connssl->earlydata_state = ssl_earlydata_await;
connssl->state = ssl_connection_deferred;
result = Curl_alpn_set_negotiated(cf, data, connssl,
(const unsigned char *)scs->alpn,
scs->alpn ? strlen(scs->alpn) : 0);
*do_early_data = !result;
}
return result;
}
void Curl_ossl_report_handshake(struct Curl_easy *data,
struct ossl_ctx *octx)
{
#ifndef CURL_DISABLE_VERBOSE_STRINGS
if(Curl_trc_is_verbose(data)) {
int psigtype_nid = NID_undef;
const char *negotiated_group_name = NULL;
#ifdef HAVE_OPENSSL3
SSL_get_peer_signature_type_nid(octx->ssl, &psigtype_nid);
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
negotiated_group_name = SSL_get0_group_name(octx->ssl);
#else
negotiated_group_name =
OBJ_nid2sn(SSL_get_negotiated_group(octx->ssl) & 0x0000FFFF);
#endif
#endif
infof(data, "SSL connection using %s / %s / %s / %s",
SSL_get_version(octx->ssl),
SSL_get_cipher(octx->ssl),
negotiated_group_name ? negotiated_group_name : "[blank]",
OBJ_nid2sn(psigtype_nid));
}
#else
(void)data;
(void)octx;
#endif
}
static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
BIO *bio;
CURLcode result;
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
DEBUGASSERT(octx);
result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer,
connssl->alpn, NULL, NULL,
ossl_new_session_cb, cf,
ossl_on_session_reuse);
if(result)
return result;
octx->bio_method = ossl_bio_cf_method_create();
if(!octx->bio_method)
return CURLE_OUT_OF_MEMORY;
bio = BIO_new(octx->bio_method);
if(!bio)
return CURLE_OUT_OF_MEMORY;
BIO_set_data(bio, cf);
#ifdef HAVE_SSL_SET0_WBIO
BIO_up_ref(bio);
SSL_set0_rbio(octx->ssl, bio);
SSL_set0_wbio(octx->ssl, bio);
#else
SSL_set_bio(octx->ssl, bio, bio);
#endif
#ifdef HAS_ALPN_OPENSSL
if(connssl->alpn && (connssl->state != ssl_connection_deferred)) {
struct alpn_proto_buf proto;
memset(&proto, 0, sizeof(proto));
Curl_alpn_to_proto_str(&proto, connssl->alpn);
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
}
#endif
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
#ifdef USE_ECH_OPENSSL
static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl,
int reason)
{
CURLcode result = CURLE_OK;
size_t rcl = 0;
int rv = 1;
# ifndef HAVE_BORINGSSL_LIKE
char *inner = NULL;
unsigned char *rcs = NULL;
char *outer = NULL;
# else
const char *inner = NULL;
const uint8_t *rcs = NULL;
const char *outer = NULL;
size_t out_name_len = 0;
int servername_type = 0;
# endif
if(!ECH_ENABLED(data))
return;
# ifndef HAVE_BORINGSSL_LIKE
rv = SSL_ech_get1_retry_config(ssl, &rcs, &rcl);
# else
SSL_get0_ech_retry_configs(ssl, &rcs, &rcl);
rv = (int)rcl;
# endif
if(rv && rcs) {
char *b64str = NULL;
size_t blen = 0;
result = curlx_base64_encode((const char *)rcs, rcl, &b64str, &blen);
if(!result && b64str) {
infof(data, "ECH: retry_configs %s", b64str);
free(b64str);
#ifndef HAVE_BORINGSSL_LIKE
rv = SSL_ech_get1_status(ssl, &inner, &outer);
infof(data, "ECH: retry_configs for %s from %s, %d %d",
inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
#else
rv = SSL_ech_accepted(ssl);
servername_type = SSL_get_servername_type(ssl);
inner = SSL_get_servername(ssl, servername_type);
SSL_get0_ech_name_override(ssl, &outer, &out_name_len);
infof(data, "ECH: retry_configs for %s from %s, %d %d",
inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
#endif
}
}
else
infof(data, "ECH: no retry_configs (rv = %d)", rv);
# ifndef HAVE_BORINGSSL_LIKE
OPENSSL_free((void *)rcs);
# endif
return;
}
#endif
static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
int err;
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
DEBUGASSERT(ssl_connect_2 == connssl->connecting_state);
DEBUGASSERT(octx);
connssl->io_need = CURL_SSL_IO_NEED_NONE;
ERR_clear_error();
err = SSL_connect(octx->ssl);
if(!octx->x509_store_setup) {
CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx);
if(result)
return result;
octx->x509_store_setup = TRUE;
}
#ifndef HAVE_KEYLOG_CALLBACK
if(Curl_tls_keylog_enabled() && !octx->keylog_done)
ossl_log_tls12_secret(octx->ssl, &octx->keylog_done);
#endif
if(err != 1) {
int detail = SSL_get_error(octx->ssl, err);
CURL_TRC_CF(data, cf, "SSL_connect() -> err=%d, detail=%d", err, detail);
if(SSL_ERROR_WANT_READ == detail) {
CURL_TRC_CF(data, cf, "SSL_connect() -> want recv");
connssl->io_need = CURL_SSL_IO_NEED_RECV;
return CURLE_AGAIN;
}
if(SSL_ERROR_WANT_WRITE == detail) {
CURL_TRC_CF(data, cf, "SSL_connect() -> want send");
connssl->io_need = CURL_SSL_IO_NEED_SEND;
return CURLE_AGAIN;
}
#ifdef SSL_ERROR_WANT_ASYNC
if(SSL_ERROR_WANT_ASYNC == detail) {
CURL_TRC_CF(data, cf, "SSL_connect() -> want async");
connssl->io_need = CURL_SSL_IO_NEED_RECV;
return CURLE_AGAIN;
}
#endif
#ifdef SSL_ERROR_WANT_RETRY_VERIFY
if(SSL_ERROR_WANT_RETRY_VERIFY == detail) {
CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify");
Curl_xfer_pause_recv(data, TRUE);
return CURLE_AGAIN;
}
#endif
else {
sslerr_t errdetail;
char error_buffer[256]="";
CURLcode result;
long lerr;
int lib;
int reason;
connssl->connecting_state = ssl_connect_2;
errdetail = ERR_get_error();
lib = ERR_GET_LIB(errdetail);
reason = ERR_GET_REASON(errdetail);
if((lib == ERR_LIB_SSL) &&
((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) ||
(reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) {
result = CURLE_PEER_FAILED_VERIFICATION;
lerr = SSL_get_verify_result(octx->ssl);
if(lerr != X509_V_OK) {
ssl_config->certverifyresult = lerr;
failf(data, "SSL certificate problem: %s",
X509_verify_cert_error_string(lerr));
}
else
failf(data, "%s", "SSL certificate verification failed");
}
#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
else if((lib == ERR_LIB_SSL) &&
(reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) {
result = CURLE_SSL_CLIENTCERT;
failf(data, "TLS cert problem: %s",
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)));
}
#endif
#ifdef USE_ECH_OPENSSL
else if((lib == ERR_LIB_SSL) &&
# ifndef HAVE_BORINGSSL_LIKE
(reason == SSL_R_ECH_REQUIRED)) {
# else
(reason == SSL_R_ECH_REJECTED)) {
# endif
ossl_trace_ech_retry_configs(data, octx->ssl, reason);
result = CURLE_ECH_REQUIRED;
failf(data, "ECH required: %s",
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)));
}
#endif
else {
result = CURLE_SSL_CONNECT_ERROR;
failf(data, "TLS connect error: %s",
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)));
}
if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) {
char extramsg[80]="";
int sockerr = SOCKERRNO;
if(sockerr && detail == SSL_ERROR_SYSCALL)
curlx_strerror(sockerr, extramsg, sizeof(extramsg));
failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ",
extramsg[0] ? extramsg : SSL_ERROR_to_str(detail),
connssl->peer.hostname, connssl->peer.port);
}
return result;
}
}
else {
connssl->connecting_state = ssl_connect_3;
Curl_ossl_report_handshake(data, octx);
#if defined(USE_ECH_OPENSSL) && !defined(HAVE_BORINGSSL_LIKE)
if(ECH_ENABLED(data)) {
char *inner = NULL, *outer = NULL;
const char *status = NULL;
int rv;
rv = SSL_ech_get1_status(octx->ssl, &inner, &outer);
switch(rv) {
case SSL_ECH_STATUS_SUCCESS:
status = "succeeded";
break;
case SSL_ECH_STATUS_GREASE_ECH:
status = "sent GREASE, got retry-configs";
break;
case SSL_ECH_STATUS_GREASE:
status = "sent GREASE";
break;
case SSL_ECH_STATUS_NOT_TRIED:
status = "not attempted";
break;
case SSL_ECH_STATUS_NOT_CONFIGURED:
status = "not configured";
break;
case SSL_ECH_STATUS_BACKEND:
status = "backend (unexpected)";
break;
case SSL_ECH_STATUS_FAILED:
status = "failed";
break;
case SSL_ECH_STATUS_BAD_CALL:
status = "bad call (unexpected)";
break;
case SSL_ECH_STATUS_BAD_NAME:
status = "bad name (unexpected)";
break;
default:
status = "unexpected status";
infof(data, "ECH: unexpected status %d",rv);
}
infof(data, "ECH: result: status is %s, inner is %s, outer is %s",
(status ? status : "NULL"),
(inner ? inner : "NULL"),
(outer ? outer : "NULL"));
OPENSSL_free(inner);
OPENSSL_free(outer);
if(rv == SSL_ECH_STATUS_GREASE_ECH) {
ossl_trace_ech_retry_configs(data, octx->ssl, 0);
}
if(rv != SSL_ECH_STATUS_SUCCESS
&& data->set.tls_ech & CURLECH_HARD) {
infof(data, "ECH: ech-hard failed");
return CURLE_SSL_CONNECT_ERROR;
}
}
else {
infof(data, "ECH: result: status is not attempted");
}
#endif
#ifdef HAS_ALPN_OPENSSL
if(connssl->alpn) {
const unsigned char *neg_protocol;
unsigned int len;
SSL_get0_alpn_selected(octx->ssl, &neg_protocol, &len);
return Curl_alpn_set_negotiated(cf, data, connssl, neg_protocol, len);
}
#endif
return CURLE_OK;
}
}
static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert,
const char *pinnedpubkey)
{
int len1 = 0, len2 = 0;
unsigned char *buff1 = NULL, *temp = NULL;
CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
if(!pinnedpubkey)
return CURLE_OK;
if(!cert)
return result;
do {
len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
if(len1 < 1)
break;
buff1 = temp = malloc(len1);
if(!buff1)
break;
len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp);
if((len1 != len2) || !temp || ((temp - buff1) != len1))
break;
result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
} while(0);
if(buff1)
free(buff1);
return result;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!(defined(LIBRESSL_VERSION_NUMBER) && \
LIBRESSL_VERSION_NUMBER < 0x3060000fL) && \
!defined(HAVE_BORINGSSL_LIKE) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
static void infof_certstack(struct Curl_easy *data, const SSL *ssl)
{
STACK_OF(X509) *certstack;
long verify_result;
int num_cert_levels;
int cert_level;
if(!Curl_trc_is_verbose(data))
return;
verify_result = SSL_get_verify_result(ssl);
if(verify_result != X509_V_OK)
certstack = SSL_get_peer_cert_chain(ssl);
else
certstack = SSL_get0_verified_chain(ssl);
if(!certstack)
return;
num_cert_levels = sk_X509_num(certstack);
for(cert_level = 0; cert_level < num_cert_levels; cert_level++) {
char cert_algorithm[80] = "";
char group_name_final[80] = "";
const X509_ALGOR *palg_cert = NULL;
const ASN1_OBJECT *paobj_cert = NULL;
X509 *current_cert;
EVP_PKEY *current_pkey;
int key_bits;
int key_sec_bits;
int get_group_name;
const char *type_name;
current_cert = sk_X509_value(certstack, cert_level);
if(!current_cert)
continue;
current_pkey = X509_get0_pubkey(current_cert);
if(!current_pkey)
continue;
X509_get0_signature(NULL, &palg_cert, current_cert);
X509_ALGOR_get0(&paobj_cert, NULL, NULL, palg_cert);
OBJ_obj2txt(cert_algorithm, sizeof(cert_algorithm), paobj_cert, 0);
key_bits = EVP_PKEY_bits(current_pkey);
#ifndef HAVE_OPENSSL3
#define EVP_PKEY_get_security_bits EVP_PKEY_security_bits
#endif
key_sec_bits = EVP_PKEY_get_security_bits(current_pkey);
#ifdef HAVE_OPENSSL3
{
char group_name[80] = "";
get_group_name = EVP_PKEY_get_group_name(current_pkey, group_name,
sizeof(group_name), NULL);
curl_msnprintf(group_name_final, sizeof(group_name_final), "/%s",
group_name);
}
type_name = EVP_PKEY_get0_type_name(current_pkey);
#else
get_group_name = 0;
type_name = NULL;
#endif
infof(data,
" Certificate level %d: "
"Public key type %s%s (%d/%d Bits/secBits), signed using %s",
cert_level, type_name ? type_name : "?",
get_group_name == 0 ? "" : group_name_final,
key_bits, key_sec_bits, cert_algorithm);
}
}
#else
#define infof_certstack(data, ssl)
#endif
static CURLcode ossl_check_issuer(struct Curl_cfilter *cf,
struct Curl_easy *data,
X509 *server_cert)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
X509 *issuer = NULL;
BIO *fp = NULL;
char err_buf[256]="";
bool strict = (conn_config->verifypeer || conn_config->verifyhost);
CURLcode result = CURLE_OK;
if(conn_config->issuercert_blob) {
fp = BIO_new_mem_buf(conn_config->issuercert_blob->data,
(int)conn_config->issuercert_blob->len);
if(!fp) {
failf(data, "BIO_new_mem_buf NULL, " OSSL_PACKAGE " error %s",
ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf)));
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
else if(conn_config->issuercert) {
fp = BIO_new(BIO_s_file());
if(!fp) {
failf(data, "BIO_new return NULL, " OSSL_PACKAGE " error %s",
ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf)));
result = CURLE_OUT_OF_MEMORY;
goto out;
}
if(BIO_read_filename(fp, conn_config->issuercert) <= 0) {
if(strict)
failf(data, "SSL: Unable to open issuer cert (%s)",
conn_config->issuercert);
result = CURLE_SSL_ISSUER_ERROR;
goto out;
}
}
if(fp) {
issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL);
if(!issuer) {
if(strict)
failf(data, "SSL: Unable to read issuer cert (%s)",
conn_config->issuercert);
result = CURLE_SSL_ISSUER_ERROR;
goto out;
}
if(X509_check_issued(issuer, server_cert) != X509_V_OK) {
if(strict)
failf(data, "SSL: Certificate issuer check failed (%s)",
conn_config->issuercert);
result = CURLE_SSL_ISSUER_ERROR;
goto out;
}
infof(data, " SSL certificate issuer check ok (%s)",
conn_config->issuercert);
}
out:
if(fp)
BIO_free(fp);
if(issuer)
X509_free(issuer);
return result;
}
static CURLcode ossl_check_pinned_key(struct Curl_cfilter *cf,
struct Curl_easy *data,
X509 *server_cert)
{
const char *ptr;
CURLcode result = CURLE_OK;
(void)cf;
#ifndef CURL_DISABLE_PROXY
ptr = Curl_ssl_cf_is_proxy(cf) ?
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
#else
ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
#endif
if(ptr) {
result = ossl_pkp_pin_peer_pubkey(data, server_cert, ptr);
if(result)
failf(data, "SSL: public key does not match pinned public key");
}
return result;
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
#define MAX_CERT_NAME_LENGTH 2048
static CURLcode ossl_infof_cert(struct Curl_cfilter *cf,
struct Curl_easy *data,
X509 *server_cert)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
bool strict = (conn_config->verifypeer || conn_config->verifyhost);
BIO *mem = NULL;
struct dynbuf dname;
char err_buf[256] = "";
char *buf;
long len;
CURLcode result = CURLE_OK;
if(!Curl_trc_is_verbose(data))
return CURLE_OK;
curlx_dyn_init(&dname, MAX_CERT_NAME_LENGTH);
mem = BIO_new(BIO_s_mem());
if(!mem) {
failf(data, "BIO_new return NULL, " OSSL_PACKAGE " error %s",
ossl_strerror(ERR_get_error(), err_buf, sizeof(err_buf)));
result = CURLE_OUT_OF_MEMORY;
goto out;
}
infof(data, "%s certificate:", Curl_ssl_cf_is_proxy(cf) ?
"Proxy" : "Server");
result = x509_name_oneline(X509_get_subject_name(server_cert), &dname);
infof(data, " subject: %s", result ? "[NONE]" : curlx_dyn_ptr(&dname));
ASN1_TIME_print(mem, X509_get0_notBefore(server_cert));
len = BIO_get_mem_data(mem, (char **) &buf);
infof(data, " start date: %.*s", (int)len, buf);
(void)BIO_reset(mem);
ASN1_TIME_print(mem, X509_get0_notAfter(server_cert));
len = BIO_get_mem_data(mem, (char **) &buf);
infof(data, " expire date: %.*s", (int)len, buf);
(void)BIO_reset(mem);
result = x509_name_oneline(X509_get_issuer_name(server_cert), &dname);
if(result) {
if(strict)
failf(data, "SSL: could not get X509-issuer name");
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
}
infof(data, " issuer: %s", curlx_dyn_ptr(&dname));
out:
BIO_free(mem);
curlx_dyn_free(&dname);
return result;
}
#endif
#ifdef USE_APPLE_SECTRUST
struct ossl_certs_ctx {
STACK_OF(X509) *sk;
size_t num_certs;
};
static CURLcode ossl_chain_get_der(struct Curl_cfilter *cf,
struct Curl_easy *data,
void *user_data,
size_t i,
unsigned char **pder,
size_t *pder_len)
{
struct ossl_certs_ctx *chain = user_data;
X509 *cert;
int der_len;
(void)cf;
(void)data;
*pder_len = 0;
*pder = NULL;
if(i >= chain->num_certs)
return CURLE_TOO_LARGE;
cert = sk_X509_value(chain->sk, (int)i);
if(!cert)
return CURLE_FAILED_INIT;
der_len = i2d_X509(cert, pder);
if(der_len < 0)
return CURLE_FAILED_INIT;
*pder_len = (size_t)der_len;
return CURLE_OK;
}
static CURLcode ossl_apple_verify(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
struct ssl_peer *peer,
bool *pverified)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ossl_certs_ctx chain;
CURLcode result;
memset(&chain, 0, sizeof(chain));
chain.sk = SSL_get_peer_cert_chain(octx->ssl);
chain.num_certs = chain.sk ? sk_X509_num(chain.sk) : 0;
if(!chain.num_certs &&
(conn_config->verifypeer || conn_config->verifyhost)) {
failf(data, "SSL: could not get peer certificate");
result = CURLE_PEER_FAILED_VERIFICATION;
}
else {
#ifdef HAVE_BORINGSSL_LIKE
const uint8_t *ocsp_data = NULL;
#else
unsigned char *ocsp_data = NULL;
#endif
long ocsp_len = 0;
if(conn_config->verifystatus && !octx->reused_session)
ocsp_len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &ocsp_data);
if(ocsp_len < 0)
ocsp_len = 0;
result = Curl_vtls_apple_verify(cf, data, peer, chain.num_certs,
ossl_chain_get_der, &chain,
ocsp_data, ocsp_len);
}
*pverified = !result;
return result;
}
#endif
CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ossl_ctx *octx,
struct ssl_peer *peer)
{
struct connectdata *conn = cf->conn;
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
CURLcode result = CURLE_OK;
long ossl_verify;
bool strict = (conn_config->verifypeer || conn_config->verifyhost);
X509 *server_cert;
bool verified = FALSE;
#ifdef USE_APPLE_SECTRUST
bool sectrust_verified = FALSE;
#endif
if(data->set.ssl.certinfo && !octx->reused_session) {
result = ossl_certchain(data, octx->ssl);
if(result)
return result;
}
server_cert = SSL_get1_peer_certificate(octx->ssl);
if(!server_cert) {
if(!strict)
goto out;
failf(data, "SSL: could not get peer certificate");
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
result = ossl_infof_cert(cf, data, server_cert);
if(result)
goto out;
infof_certstack(data, octx->ssl);
#endif
if(conn_config->verifyhost) {
result = ossl_verifyhost(data, conn, peer, server_cert);
if(result)
goto out;
}
ossl_verify = SSL_get_verify_result(octx->ssl);
ssl_config->certverifyresult = ossl_verify;
verified = (ossl_verify == X509_V_OK);
if(verified)
infof(data, "SSL certificate verified via OpenSSL.");
#ifdef USE_APPLE_SECTRUST
if(!verified &&
conn_config->verifypeer && ssl_config->native_ca_store &&
(ossl_verify == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)) {
result = ossl_apple_verify(cf, data, octx, peer, &verified);
if(result && (result != CURLE_PEER_FAILED_VERIFICATION))
goto out;
if(verified) {
infof(data, "SSL certificate verified via Apple SecTrust.");
ssl_config->certverifyresult = X509_V_OK;
sectrust_verified = TRUE;
}
}
#endif
if(!verified) {
failf(data, "SSL certificate OpenSSL verify result: %s (%ld)",
X509_verify_cert_error_string(ossl_verify), ossl_verify);
result = CURLE_PEER_FAILED_VERIFICATION;
if(conn_config->verifypeer)
goto out;
infof(data, " SSL certificate verification failed, continuing anyway!");
}
#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
if(conn_config->verifystatus &&
#ifdef USE_APPLE_SECTRUST
!sectrust_verified &&
#endif
!octx->reused_session) {
result = verifystatus(cf, data, octx);
if(result)
goto out;
}
#endif
result = ossl_check_issuer(cf, data, server_cert);
if(result)
goto out;
result = ossl_check_pinned_key(cf, data, server_cert);
out:
X509_free(server_cert);
return result;
}
static CURLcode ossl_connect_step3(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
CURLcode result = CURLE_OK;
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
result = Curl_ossl_check_peer_cert(cf, data, octx, &connssl->peer);
if(result)
Curl_ssl_scache_remove_all(cf, data, connssl->peer.scache_key);
return result;
}
#ifdef HAVE_OPENSSL_EARLYDATA
static CURLcode ossl_send_earlydata(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
CURLcode result = CURLE_OK;
const unsigned char *buf;
size_t blen, nwritten;
int rc;
DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending);
octx->io_result = CURLE_OK;
while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) {
nwritten = 0;
rc = SSL_write_early_data(octx->ssl, buf, blen, &nwritten);
CURL_TRC_CF(data, cf, "SSL_write_early_data(len=%zu) -> %d, %zu",
blen, rc, nwritten);
if(rc <= 0) {
long sslerror;
char error_buffer[256];
int err = SSL_get_error(octx->ssl, rc);
switch(err) {
case SSL_ERROR_WANT_READ:
connssl->io_need = CURL_SSL_IO_NEED_RECV;
result = CURLE_AGAIN;
goto out;
case SSL_ERROR_WANT_WRITE:
connssl->io_need = CURL_SSL_IO_NEED_SEND;
result = CURLE_AGAIN;
goto out;
case SSL_ERROR_SYSCALL: {
int sockerr = SOCKERRNO;
if(octx->io_result == CURLE_AGAIN) {
result = CURLE_AGAIN;
goto out;
}
sslerror = ERR_get_error();
if(sslerror)
ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
else if(sockerr)
curlx_strerror(sockerr, error_buffer, sizeof(error_buffer));
else
curl_msnprintf(error_buffer, sizeof(error_buffer), "%s",
SSL_ERROR_to_str(err));
failf(data, OSSL_PACKAGE " SSL_write:early_data: %s, errno %d",
error_buffer, sockerr);
result = CURLE_SEND_ERROR;
goto out;
}
case SSL_ERROR_SSL: {
sslerror = ERR_get_error();
failf(data, "SSL_write_early_data() error: %s",
ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)));
result = CURLE_SEND_ERROR;
goto out;
}
default:
failf(data, OSSL_PACKAGE " SSL_write_early_data: %s, errno %d",
SSL_ERROR_to_str(err), SOCKERRNO);
result = CURLE_SEND_ERROR;
goto out;
}
}
Curl_bufq_skip(&connssl->earlydata, nwritten);
}
infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip);
out:
return result;
}
#endif
static CURLcode ossl_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
CURLcode result = CURLE_OK;
struct ssl_connect_data *connssl = cf->ctx;
if(ssl_connection_complete == connssl->state) {
*done = TRUE;
return CURLE_OK;
}
*done = FALSE;
connssl->io_need = CURL_SSL_IO_NEED_NONE;
if(ssl_connect_1 == connssl->connecting_state) {
CURL_TRC_CF(data, cf, "ossl_connect, step1");
result = ossl_connect_step1(cf, data);
if(result)
goto out;
}
if(ssl_connect_2 == connssl->connecting_state) {
CURL_TRC_CF(data, cf, "ossl_connect, step2");
#ifdef HAVE_OPENSSL_EARLYDATA
if(connssl->earlydata_state == ssl_earlydata_await) {
goto out;
}
else if(connssl->earlydata_state == ssl_earlydata_sending) {
result = ossl_send_earlydata(cf, data);
if(result)
goto out;
connssl->earlydata_state = ssl_earlydata_sent;
}
#endif
DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) ||
(connssl->earlydata_state == ssl_earlydata_sent));
result = ossl_connect_step2(cf, data);
if(result)
goto out;
}
if(ssl_connect_3 == connssl->connecting_state) {
CURL_TRC_CF(data, cf, "ossl_connect, step3");
result = ossl_connect_step3(cf, data);
if(result)
goto out;
connssl->connecting_state = ssl_connect_done;
#ifdef HAVE_OPENSSL_EARLYDATA
if(connssl->earlydata_state > ssl_earlydata_none) {
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent);
connssl->earlydata_state =
(SSL_get_early_data_status(octx->ssl) == SSL_EARLY_DATA_ACCEPTED) ?
ssl_earlydata_accepted : ssl_earlydata_rejected;
}
#endif
}
if(ssl_connect_done == connssl->connecting_state) {
CURL_TRC_CF(data, cf, "ossl_connect, done");
connssl->state = ssl_connection_complete;
}
out:
if(result == CURLE_AGAIN) {
*done = FALSE;
return CURLE_OK;
}
*done = ((connssl->state == ssl_connection_complete) ||
(connssl->state == ssl_connection_deferred));
return result;
}
static bool ossl_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
(void)data;
return connssl->input_pending;
}
static CURLcode ossl_send(struct Curl_cfilter *cf,
struct Curl_easy *data,
const void *mem,
size_t len,
size_t *pnwritten)
{
int err;
char error_buffer[256];
sslerr_t sslerror;
int memlen;
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
CURLcode result = CURLE_OK;
int nwritten;
(void)data;
DEBUGASSERT(octx);
*pnwritten = 0;
ERR_clear_error();
connssl->io_need = CURL_SSL_IO_NEED_NONE;
memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
if(octx->blocked_ssl_write_len && (octx->blocked_ssl_write_len != memlen)) {
if(octx->blocked_ssl_write_len > memlen) {
DEBUGASSERT(0);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
memlen = octx->blocked_ssl_write_len;
}
octx->blocked_ssl_write_len = 0;
nwritten = SSL_write(octx->ssl, mem, memlen);
if(nwritten > 0)
*pnwritten = (size_t)nwritten;
else {
err = SSL_get_error(octx->ssl, nwritten);
switch(err) {
case SSL_ERROR_WANT_READ:
connssl->io_need = CURL_SSL_IO_NEED_RECV;
octx->blocked_ssl_write_len = memlen;
result = CURLE_AGAIN;
goto out;
case SSL_ERROR_WANT_WRITE:
result = CURLE_AGAIN;
octx->blocked_ssl_write_len = memlen;
goto out;
case SSL_ERROR_SYSCALL:
{
int sockerr = SOCKERRNO;
if(octx->io_result == CURLE_AGAIN) {
octx->blocked_ssl_write_len = memlen;
result = CURLE_AGAIN;
goto out;
}
sslerror = ERR_get_error();
if(sslerror)
ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
else if(sockerr)
curlx_strerror(sockerr, error_buffer, sizeof(error_buffer));
else
curl_msnprintf(error_buffer, sizeof(error_buffer), "%s",
SSL_ERROR_to_str(err));
failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d",
error_buffer, sockerr);
result = CURLE_SEND_ERROR;
goto out;
}
case SSL_ERROR_SSL: {
sslerror = ERR_get_error();
failf(data, "SSL_write() error: %s",
ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)));
result = CURLE_SEND_ERROR;
goto out;
}
default:
failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d",
SSL_ERROR_to_str(err), SOCKERRNO);
result = CURLE_SEND_ERROR;
goto out;
}
}
out:
return result;
}
static CURLcode ossl_recv(struct Curl_cfilter *cf,
struct Curl_easy *data,
char *buf,
size_t buffersize,
size_t *pnread)
{
char error_buffer[256];
unsigned long sslerror;
int buffsize;
struct connectdata *conn = cf->conn;
struct ssl_connect_data *connssl = cf->ctx;
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
CURLcode result = CURLE_OK;
int nread;
(void)data;
DEBUGASSERT(octx);
*pnread = 0;
ERR_clear_error();
connssl->io_need = CURL_SSL_IO_NEED_NONE;
buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
nread = SSL_read(octx->ssl, buf, buffsize);
if(nread > 0)
*pnread = (size_t)nread;
else {
int err = SSL_get_error(octx->ssl, (int)nread);
switch(err) {
case SSL_ERROR_NONE:
break;
case SSL_ERROR_ZERO_RETURN:
if(cf->sockindex == FIRSTSOCKET)
connclose(conn, "TLS close_notify");
break;
case SSL_ERROR_WANT_READ:
connssl->io_need = CURL_SSL_IO_NEED_RECV;
result = CURLE_AGAIN;
goto out;
case SSL_ERROR_WANT_WRITE:
connssl->io_need = CURL_SSL_IO_NEED_SEND;
result = CURLE_AGAIN;
goto out;
default:
if(octx->io_result == CURLE_AGAIN) {
result = CURLE_AGAIN;
goto out;
}
sslerror = ERR_get_error();
if((nread < 0) || sslerror) {
int sockerr = SOCKERRNO;
if(sslerror)
ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
else if(sockerr && err == SSL_ERROR_SYSCALL)
curlx_strerror(sockerr, error_buffer, sizeof(error_buffer));
else
curl_msnprintf(error_buffer, sizeof(error_buffer), "%s",
SSL_ERROR_to_str(err));
failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d",
error_buffer, sockerr);
result = CURLE_RECV_ERROR;
goto out;
}
else if(err == SSL_ERROR_SYSCALL) {
if(octx->io_result) {
result = octx->io_result;
}
else if(connssl->peer_closed) {
failf(data, "Connection closed abruptly");
result = CURLE_RECV_ERROR;
}
else {
int sockerr = SOCKERRNO;
if(sockerr)
curlx_strerror(sockerr, error_buffer, sizeof(error_buffer));
else {
curl_msnprintf(error_buffer, sizeof(error_buffer),
"Connection closed abruptly");
}
failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d",
error_buffer, sockerr);
result = CURLE_RECV_ERROR;
}
goto out;
}
}
}
out:
if((!result && !*pnread) || (result == CURLE_AGAIN)) {
connssl->input_pending = FALSE;
}
CURL_TRC_CF(data, cf, "ossl_recv(len=%zu) -> %d, %zu (in_pending=%d)",
buffersize, result, *pnread, connssl->input_pending);
return result;
}
static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex,
struct dynbuf *binding)
{
#if OPENSSL_VERSION_NUMBER > 0x10100000L
X509 *cert;
int algo_nid;
const EVP_MD *algo_type;
const char *algo_name;
unsigned int length;
unsigned char buf[EVP_MAX_MD_SIZE];
const char prefix[] = "tls-server-end-point:";
struct connectdata *conn = data->conn;
struct Curl_cfilter *cf = conn->cfilter[sockindex];
struct ossl_ctx *octx = NULL;
CURLcode result = CURLE_OK;
do {
const struct Curl_cftype *cft = cf->cft;
struct ssl_connect_data *connssl = cf->ctx;
if(cft->name && !strcmp(cft->name, "SSL")) {
octx = (struct ossl_ctx *)connssl->backend;
break;
}
cf = cf->next;
} while(cf);
if(!octx) {
failf(data, "Failed to find the SSL filter");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
cert = SSL_get1_peer_certificate(octx->ssl);
if(!cert)
return CURLE_OK;
if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) {
failf(data,
"Unable to find digest NID for certificate signature algorithm");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto error;
}
if(algo_nid == NID_md5 || algo_nid == NID_sha1) {
algo_type = EVP_sha256();
}
else {
algo_type = EVP_get_digestbynid(algo_nid);
if(!algo_type) {
algo_name = OBJ_nid2sn(algo_nid);
failf(data, "Could not find digest algorithm %s (NID %d)",
algo_name ? algo_name : "(null)", algo_nid);
result = CURLE_SSL_INVALIDCERTSTATUS;
goto error;
}
}
if(!X509_digest(cert, algo_type, buf, &length)) {
failf(data, "X509_digest() failed");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto error;
}
result = curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1);
if(result)
goto error;
result = curlx_dyn_addn(binding, buf, length);
error:
X509_free(cert);
return result;
#else
(void)data;
(void)sockindex;
(void)binding;
return CURLE_OK;
#endif
}
size_t Curl_ossl_version(char *buffer, size_t size)
{
#ifdef LIBRESSL_VERSION_NUMBER
char *p;
size_t count;
const char *ver = OpenSSL_version(OPENSSL_VERSION);
const char expected[] = OSSL_PACKAGE " ";
if(curl_strnequal(ver, expected, sizeof(expected) - 1)) {
ver += sizeof(expected) - 1;
}
count = curl_msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver);
for(p = buffer; *p; ++p) {
if(ISBLANK(*p))
*p = '_';
}
return count;
#elif defined(OPENSSL_IS_BORINGSSL)
#ifdef CURL_BORINGSSL_VERSION
return curl_msnprintf(buffer, size, "%s/%s",
OSSL_PACKAGE, CURL_BORINGSSL_VERSION);
#else
return curl_msnprintf(buffer, size, OSSL_PACKAGE);
#endif
#elif defined(OPENSSL_IS_AWSLC)
return curl_msnprintf(buffer, size, "%s/%s",
OSSL_PACKAGE, AWSLC_VERSION_NUMBER_STRING);
#elif defined(OPENSSL_VERSION_STRING)
return curl_msnprintf(buffer, size, "%s/%s",
OSSL_PACKAGE, OpenSSL_version(OPENSSL_VERSION_STRING));
#else
char sub[3];
unsigned long ssleay_value;
sub[2]='\0';
sub[1]='\0';
ssleay_value = OpenSSL_version_num();
if(ssleay_value&0xff0) {
int minor_ver = (ssleay_value >> 4) & 0xff;
if(minor_ver > 26) {
sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1);
sub[0] = 'z';
}
else {
sub[0] = (char) (minor_ver + 'a' - 1);
}
}
else
sub[0]='\0';
return curl_msnprintf(buffer, size, "%s/%lx.%lx.%lx%s"
#ifdef OPENSSL_FIPS
"-fips"
#endif
,
OSSL_PACKAGE,
(ssleay_value >> 28) & 0xf,
(ssleay_value >> 20) & 0xff,
(ssleay_value >> 12) & 0xff,
sub);
#endif
}
static CURLcode ossl_random(struct Curl_easy *data,
unsigned char *entropy, size_t length)
{
int rc;
if(data) {
if(ossl_seed(data))
return CURLE_FAILED_INIT;
}
else {
if(!rand_enough())
return CURLE_FAILED_INIT;
}
rc = RAND_bytes(entropy, (ossl_valsize_t)curlx_uztosi(length));
return rc == 1 ? CURLE_OK : CURLE_FAILED_INIT;
}
#ifndef OPENSSL_NO_SHA256
static CURLcode ossl_sha256sum(const unsigned char *tmp,
size_t tmplen,
unsigned char *sha256sum ,
size_t unused)
{
EVP_MD_CTX *mdctx;
unsigned int len = 0;
(void)unused;
mdctx = EVP_MD_CTX_create();
if(!mdctx)
return CURLE_OUT_OF_MEMORY;
if(!EVP_DigestInit(mdctx, EVP_sha256())) {
EVP_MD_CTX_destroy(mdctx);
return CURLE_FAILED_INIT;
}
EVP_DigestUpdate(mdctx, tmp, tmplen);
EVP_DigestFinal_ex(mdctx, sha256sum, &len);
EVP_MD_CTX_destroy(mdctx);
return CURLE_OK;
}
#endif
static bool ossl_cert_status_request(void)
{
#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
return TRUE;
#else
return FALSE;
#endif
}
static void *ossl_get_internals(struct ssl_connect_data *connssl,
CURLINFO info)
{
struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend;
DEBUGASSERT(octx);
return info == CURLINFO_TLS_SESSION ?
(void *)octx->ssl_ctx : (void *)octx->ssl;
}
const struct Curl_ssl Curl_ssl_openssl = {
{ CURLSSLBACKEND_OPENSSL, "openssl" },
SSLSUPP_CA_PATH |
SSLSUPP_CAINFO_BLOB |
SSLSUPP_CERTINFO |
SSLSUPP_PINNEDPUBKEY |
SSLSUPP_SSL_CTX |
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
SSLSUPP_TLS13_CIPHERSUITES |
#endif
#ifdef HAVE_SSL_CTX_SET1_SIGALGS
SSLSUPP_SIGNATURE_ALGORITHMS |
#endif
#ifdef USE_ECH_OPENSSL
SSLSUPP_ECH |
#endif
SSLSUPP_CA_CACHE |
SSLSUPP_HTTPS_PROXY |
SSLSUPP_CIPHER_LIST,
sizeof(struct ossl_ctx),
ossl_init,
ossl_cleanup,
Curl_ossl_version,
ossl_shutdown,
ossl_data_pending,
ossl_random,
ossl_cert_status_request,
ossl_connect,
Curl_ssl_adjust_pollset,
ossl_get_internals,
ossl_close,
ossl_close_all,
ossl_set_engine,
ossl_set_engine_default,
ossl_engines_list,
#ifndef OPENSSL_NO_SHA256
ossl_sha256sum,
#else
NULL,
#endif
ossl_recv,
ossl_send,
ossl_get_channel_binding
};
#endif