openssl: enable builds for *both* engines and providers

OpenSSL3 can in fact have both enabled at once. Load the provider and
key/cert appropriately. When loading a provider, the user can now also
set an associated "property string".

Work on this was sponsored by Valantic.

Closes #17165
This commit is contained in:
Daniel Stenberg 2025-04-08 11:45:17 +02:00
parent e0ebc3ff13
commit f2ce6c46b9
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 151 additions and 83 deletions

View file

@ -17,7 +17,7 @@ Added-in: 7.9.3
# NAME # NAME
CURLOPT_SSLENGINE - SSL engine identifier CURLOPT_SSLENGINE - Set SSL engine or provider
# SYNOPSIS # SYNOPSIS
@ -30,11 +30,16 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLENGINE, char *id);
# DESCRIPTION # DESCRIPTION
Pass a pointer to a null-terminated string as parameter. It is used as the Pass a pointer to a null-terminated string as parameter. It is used as the
identifier for the crypto engine you want to use for your private key. identifier for the *engine* or *provider* you want to use for your private
key. OpenSSL 1 had engines, OpenSSL 3 has providers.
The application does not have to keep the string around after setting this The application does not have to keep the string around after setting this
option. option.
When asking libcurl to use a provider, the application can also optionally
provide a *property*, a set of name value pairs. Such a property can be
specified separated from the name with a colon (`:`).
Using this option multiple times makes the last set string override the Using this option multiple times makes the last set string override the
previous ones. Set it to NULL to disable its use again. previous ones. Set it to NULL to disable its use again.

View file

@ -1196,10 +1196,13 @@ struct UrlState {
#if defined(USE_OPENSSL) #if defined(USE_OPENSSL)
/* void instead of ENGINE to avoid bleeding OpenSSL into this header */ /* void instead of ENGINE to avoid bleeding OpenSSL into this header */
void *engine; void *engine;
/* this is just a flag -- we do not need to reference the provider in any /* void instead of OSSL_PROVIDER */
* way as OpenSSL takes care of that */ void *provider;
BIT(provider); void *baseprov;
BIT(provider_failed); void *libctx;
char *propq; /* for a provider */
BIT(provider_loaded);
#endif /* USE_OPENSSL */ #endif /* USE_OPENSSL */
struct curltime expiretime; /* set this with Curl_expire() only */ struct curltime expiretime; /* set this with Curl_expire() only */
struct Curl_tree timenode; /* for the splay stuff */ struct Curl_tree timenode; /* for the splay stuff */

View file

@ -62,6 +62,7 @@
#include "../strcase.h" #include "../strcase.h"
#include "hostcheck.h" #include "hostcheck.h"
#include "../multiif.h" #include "../multiif.h"
#include "../strparse.h"
#include "../strdup.h" #include "../strdup.h"
#include "../strerror.h" #include "../strerror.h"
#include "../curl_printf.h" #include "../curl_printf.h"
@ -117,6 +118,8 @@
#include <openssl/store.h> #include <openssl/store.h>
/* this is used in the following conditions to make them easier to read */ /* this is used in the following conditions to make them easier to read */
#define OPENSSL_HAS_PROVIDERS #define OPENSSL_HAS_PROVIDERS
static void ossl_provider_cleanup(struct Curl_easy *data);
#endif #endif
#include "../warnless.h" #include "../warnless.h"
@ -1100,7 +1103,7 @@ static bool is_pkcs11_uri(const char *string)
#endif #endif
static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine); static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine);
#if !defined(USE_OPENSSL_ENGINE) && defined(OPENSSL_HAS_PROVIDERS) #if defined(OPENSSL_HAS_PROVIDERS)
static CURLcode ossl_set_provider(struct Curl_easy *data, static CURLcode ossl_set_provider(struct Curl_easy *data,
const char *provider); const char *provider);
#endif #endif
@ -1353,7 +1356,8 @@ int cert_stuff(struct Curl_easy *data,
} }
} }
break; break;
#elif defined(OPENSSL_HAS_PROVIDERS) #endif
#if defined(OPENSSL_HAS_PROVIDERS)
/* fall through to compatible provider */ /* fall through to compatible provider */
case SSL_FILETYPE_PROVIDER: case SSL_FILETYPE_PROVIDER:
{ {
@ -1369,10 +1373,11 @@ int cert_stuff(struct Curl_easy *data,
if(data->state.provider) { if(data->state.provider) {
/* Load the certificate from the provider */ /* Load the certificate from the provider */
OSSL_STORE_CTX *store = NULL;
OSSL_STORE_INFO *info = NULL; OSSL_STORE_INFO *info = NULL;
X509 *cert = NULL; X509 *cert = NULL;
store = OSSL_STORE_open(cert_file, NULL, NULL, NULL, NULL); OSSL_STORE_CTX *store =
OSSL_STORE_open_ex(cert_file, data->state.libctx,
NULL, NULL, NULL, NULL, NULL, NULL);
if(!store) { if(!store) {
failf(data, "Failed to open OpenSSL store: %s", failf(data, "Failed to open OpenSSL store: %s",
ossl_strerror(ERR_get_error(), error_buffer, ossl_strerror(ERR_get_error(), error_buffer,
@ -1385,22 +1390,13 @@ int cert_stuff(struct Curl_easy *data,
sizeof(error_buffer))); sizeof(error_buffer)));
} }
for(info = OSSL_STORE_load(store); info = OSSL_STORE_load(store);
info != NULL; if(info) {
info = OSSL_STORE_load(store)) {
int ossl_type = OSSL_STORE_INFO_get_type(info); int ossl_type = OSSL_STORE_INFO_get_type(info);
if(ossl_type == OSSL_STORE_INFO_CERT) { if(ossl_type == OSSL_STORE_INFO_CERT)
cert = OSSL_STORE_INFO_get1_CERT(info); cert = OSSL_STORE_INFO_get1_CERT(info);
}
else {
failf(data, "Ignoring object not matching our type: %d",
ossl_type);
OSSL_STORE_INFO_free(info);
continue;
}
OSSL_STORE_INFO_free(info); OSSL_STORE_INFO_free(info);
break;
} }
OSSL_STORE_close(store); OSSL_STORE_close(store);
if(!cert) { if(!cert) {
@ -1424,9 +1420,6 @@ int cert_stuff(struct Curl_easy *data,
} }
} }
break; break;
#else
failf(data, "file type ENG nor PROV for certificate not implemented");
return 0;
#endif #endif
case SSL_FILETYPE_PKCS12: case SSL_FILETYPE_PKCS12:
@ -1616,7 +1609,8 @@ fail:
} }
} }
break; break;
#elif defined(OPENSSL_HAS_PROVIDERS) #endif
#if defined(OPENSSL_HAS_PROVIDERS)
/* fall through to compatible provider */ /* fall through to compatible provider */
case SSL_FILETYPE_PROVIDER: case SSL_FILETYPE_PROVIDER:
{ {
@ -1647,7 +1641,9 @@ fail:
UI_method_set_reader(ui_method, ssl_ui_reader); UI_method_set_reader(ui_method, ssl_ui_reader);
UI_method_set_writer(ui_method, ssl_ui_writer); UI_method_set_writer(ui_method, ssl_ui_writer);
store = OSSL_STORE_open(key_file, ui_method, NULL, NULL, NULL); store = OSSL_STORE_open_ex(key_file, data->state.libctx,
data->state.propq, ui_method, NULL, NULL,
NULL, NULL);
if(!store) { if(!store) {
failf(data, "Failed to open OpenSSL store: %s", failf(data, "Failed to open OpenSSL store: %s",
ossl_strerror(ERR_get_error(), error_buffer, ossl_strerror(ERR_get_error(), error_buffer,
@ -1660,22 +1656,13 @@ fail:
sizeof(error_buffer))); sizeof(error_buffer)));
} }
for(info = OSSL_STORE_load(store); info = OSSL_STORE_load(store);
info != NULL; if(info) {
info = OSSL_STORE_load(store)) {
int ossl_type = OSSL_STORE_INFO_get_type(info); int ossl_type = OSSL_STORE_INFO_get_type(info);
if(ossl_type == OSSL_STORE_INFO_PKEY) { if(ossl_type == OSSL_STORE_INFO_PKEY)
priv_key = OSSL_STORE_INFO_get1_PKEY(info); priv_key = OSSL_STORE_INFO_get1_PKEY(info);
}
else {
failf(data, "Ignoring object not matching our type: %d",
ossl_type);
OSSL_STORE_INFO_free(info);
continue;
}
OSSL_STORE_INFO_free(info); OSSL_STORE_INFO_free(info);
break;
} }
OSSL_STORE_close(store); OSSL_STORE_close(store);
UI_destroy_method(ui_method); UI_destroy_method(ui_method);
@ -1701,9 +1688,6 @@ fail:
} }
} }
break; break;
#else
failf(data, "file type ENG nor PROV for private key not implemented");
return 0;
#endif #endif
case SSL_FILETYPE_PKCS12: case SSL_FILETYPE_PKCS12:
@ -1878,36 +1862,39 @@ static void ossl_cleanup(void)
Curl_tls_keylog_close(); Curl_tls_keylog_close();
} }
/* Selects an OpenSSL crypto engine /* Selects an OpenSSL crypto engine or provider.
*/ */
static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine) static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name)
{ {
#ifdef USE_OPENSSL_ENGINE #ifdef USE_OPENSSL_ENGINE
ENGINE *e = ENGINE_by_id(engine); CURLcode result = CURLE_SSL_ENGINE_NOTFOUND;
ENGINE *e = ENGINE_by_id(name);
if(!e) { if(e) {
failf(data, "SSL Engine '%s' not found", engine);
return CURLE_SSL_ENGINE_NOTFOUND;
}
if(data->state.engine) { if(data->state.engine) {
ENGINE_finish(data->state.engine); ENGINE_finish(data->state.engine);
ENGINE_free(data->state.engine); ENGINE_free(data->state.engine);
data->state.engine = NULL; data->state.engine = NULL;
} }
if(!ENGINE_init(e)) { if(!ENGINE_init(e)) {
char buf[256]; char buf[256];
ENGINE_free(e); ENGINE_free(e);
failf(data, "Failed to initialise SSL Engine '%s': %s", failf(data, "Failed to initialise SSL Engine '%s': %s",
engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); name, ossl_strerror(ERR_get_error(), buf, sizeof(buf)));
return CURLE_SSL_ENGINE_INITFAILED; result = CURLE_SSL_ENGINE_INITFAILED;
e = NULL;
}
data->state.engine = e;
return result;
} }
data->state.engine = e; #endif
return CURLE_OK; #ifdef OPENSSL_HAS_PROVIDERS
return ossl_set_provider(data, name);
#else #else
(void)engine; (void)name;
failf(data, "SSL Engine not supported"); failf(data, "OpenSSL engine not found");
return CURLE_SSL_ENGINE_NOTFOUND; return CURLE_SSL_ENGINE_NOTFOUND;
#endif #endif
} }
@ -1956,33 +1943,97 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data)
return list; return list;
} }
#if !defined(USE_OPENSSL_ENGINE) && defined(OPENSSL_HAS_PROVIDERS) #if defined(OPENSSL_HAS_PROVIDERS)
/* Selects an OpenSSL crypto provider
*/
static CURLcode ossl_set_provider(struct Curl_easy *data, const char *provider)
{
OSSL_PROVIDER *pkcs11_provider = NULL;
char error_buffer[256];
if(OSSL_PROVIDER_available(NULL, provider)) { static void ossl_provider_cleanup(struct Curl_easy *data)
/* already loaded through the configuration - no action needed */ {
data->state.provider = TRUE; OSSL_LIB_CTX_free(data->state.libctx);
data->state.libctx = NULL;
Curl_safefree(data->state.propq);
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;
}
data->state.provider_loaded = FALSE;
}
#define MAX_PROVIDER_LEN 128 /* reasonable */
/* Selects an OpenSSL crypto provider.
*
* A provider might need an associated property, a string passed on to
* OpenSSL. Specify this as [PROVIDER][:PROPERTY]: separate the name and the
* property with a colon. No colon means no property is set.
*
* An example provider + property looks like "tpm2:?provider=tpm2".
*/
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) {
/* clear and cleanup provider use */
ossl_provider_cleanup(data);
return CURLE_OK; return CURLE_OK;
} }
if(data->state.provider_failed) { if(Curl_str_until(&iname, &prov, MAX_PROVIDER_LEN, ':'))
return CURLE_SSL_ENGINE_NOTFOUND; return CURLE_BAD_FUNCTION_ARGUMENT;
if(!Curl_str_single(&iname, ':'))
/* there was a colon, get the propq until the end of string */
propq = iname;
/* we need the name in a buffer, null-terminated */
memcpy(name, Curl_str(&prov), Curl_strlen(&prov));
name[Curl_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;
} }
pkcs11_provider = OSSL_PROVIDER_try_load(NULL, provider, 1); if(OSSL_PROVIDER_available(data->state.libctx, name)) {
if(!pkcs11_provider) { /* already loaded through the configuration - no action needed */
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", failf(data, "Failed to initialize provider: %s",
ossl_strerror(ERR_get_error(), error_buffer, ossl_strerror(ERR_get_error(), error_buffer,
sizeof(error_buffer))); sizeof(error_buffer)));
/* Do not attempt to load it again */ ossl_provider_cleanup(data);
data->state.provider_failed = TRUE;
return CURLE_SSL_ENGINE_NOTFOUND; return CURLE_SSL_ENGINE_NOTFOUND;
} }
data->state.provider = TRUE;
/* load the base provider as well */
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; return CURLE_OK;
} }
#endif #endif
@ -2142,6 +2193,9 @@ static void ossl_close_all(struct Curl_easy *data)
#else #else
(void)data; (void)data;
#endif #endif
#ifdef OPENSSL_HAS_PROVIDERS
ossl_provider_cleanup(data);
#endif
#ifndef HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED #ifndef HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED
/* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread
so we need to clean it here in case the thread will be killed. All OpenSSL so we need to clean it here in case the thread will be killed. All OpenSSL
@ -3634,7 +3688,13 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
DEBUGASSERT(!octx->ssl_ctx); DEBUGASSERT(!octx->ssl_ctx);
octx->ssl_ctx = SSL_CTX_new(req_method); 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) { if(!octx->ssl_ctx) {
failf(data, "SSL: could not create a context: %s", failf(data, "SSL: could not create a context: %s",
@ -5485,7 +5545,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
ossl_get_internals, /* get_internals */ ossl_get_internals, /* get_internals */
ossl_close, /* close_one */ ossl_close, /* close_one */
ossl_close_all, /* close_all */ ossl_close_all, /* close_all */
ossl_set_engine, /* set_engine */ ossl_set_engine, /* set_engine or provider */
ossl_set_engine_default, /* set_engine_default */ ossl_set_engine_default, /* set_engine_default */
ossl_engines_list, /* engines_list */ ossl_engines_list, /* engines_list */
NULL, /* false_start */ NULL, /* false_start */