diff --git a/lib/ldap.c b/lib/ldap.c index 0f9e782171..ed74e9a19a 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -326,8 +326,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ - if(data->set.ssl.cert_type && - !curl_strequal(data->set.ssl.cert_type, "PEM")) { + if(data->set.ssl.primary.cert_type && + !curl_strequal(data->set.ssl.primary.cert_type, "PEM")) { failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type"); result = CURLE_SSL_CERTPROBLEM; goto quit; diff --git a/lib/urldata.h b/lib/urldata.h index 85630b05b7..883e3cec31 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -150,9 +150,14 @@ struct ssl_primary_config { char *signature_algorithms; /* list of signature algorithms to use */ char *pinned_key; char *CRLfile; /* CRL to check certificate revocation */ + char *cert_type; /* format for certificate (default: PEM) */ + char *key; /* private key filename */ + char *key_type; /* format for private key (default: PEM) */ + char *key_passwd; /* plain text private key password */ struct curl_blob *cert_blob; struct curl_blob *ca_info_blob; struct curl_blob *issuercert_blob; + struct curl_blob *key_blob; #ifdef USE_TLS_SRP char *username; /* TLS username (for, e.g., SRP) */ char *password; /* TLS password (for, e.g., SRP) */ @@ -172,11 +177,6 @@ struct ssl_config_data { long certverifyresult; /* result from the certificate verification */ curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ void *fsslctxp; /* parameter for call back */ - char *cert_type; /* format for certificate (default: PEM) */ - char *key; /* private key filename */ - struct curl_blob *key_blob; - char *key_type; /* format for private key (default: PEM) */ - char *key_passwd; /* plain text private key password */ BIT(certinfo); /* gather lots of certificate info */ BIT(earlydata); /* use TLS 1.3 early data */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 149b4cce0c..09084765bc 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -862,7 +862,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - if(sshc->pubkey && !data->set.ssl.key_passwd) { + if(sshc->pubkey && !data->set.ssl.primary.key_passwd) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -875,7 +875,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, rc = ssh_pki_import_privkey_file(data-> set.str[STRING_SSH_PRIVATE_KEY], - data->set.ssl.key_passwd, NULL, + data->set.ssl.primary.key_passwd, NULL, NULL, &sshc->privkey); if(rc != SSH_OK) { failf(data, "Could not load private key file %s", @@ -888,7 +888,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } else { rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.key_passwd); + data->set.ssl.primary.key_passwd); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 0226ebfd27..31c3024449 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -1147,7 +1147,7 @@ static CURLcode ssh_state_pkey_init(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - sshc->passphrase = data->set.ssl.key_passwd; + sshc->passphrase = data->set.ssl.primary.key_passwd; if(!sshc->passphrase) sshc->passphrase = ""; diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index fa4d6c42cc..a8ffc28e8c 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -996,10 +996,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; } - if(ssl_config->cert_type && curl_strequal(ssl_config->cert_type, "P12")) { + if(ssl_config->primary.cert_type && + curl_strequal(ssl_config->primary.cert_type, "P12")) { rc = gnutls_certificate_set_x509_simple_pkcs12_file( gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, - ssl_config->key_passwd ? ssl_config->key_passwd : ""); + ssl_config->primary.key_passwd ? ssl_config->primary.key_passwd : ""); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 potentially-encrypted key or certificate " @@ -1017,14 +1018,15 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, rc = gnutls_certificate_set_x509_key_file2( gtls->shared_creds->creds, config->clientcert, - ssl_config->key ? ssl_config->key : config->clientcert, - gnutls_do_file_type(ssl_config->cert_type), - ssl_config->key_passwd, + ssl_config->primary.key ? ssl_config->primary.key : + config->clientcert, + gnutls_do_file_type(ssl_config->primary.cert_type), + ssl_config->primary.key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 %skey file: %s", - ssl_config->key_passwd ? "potentially-encrypted " : "", + ssl_config->primary.key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 9cd890a1c0..390570bacd 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -486,7 +486,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, const char * const ssl_capath = conn_config->CApath; #ifdef MBEDTLS_PEM_PARSE_C struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_cert_type = ssl_config->cert_type; + const char * const ssl_cert_type = ssl_config->primary.cert_type; #endif int ret = -1; char errorbuf[128]; @@ -581,7 +581,7 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; #ifdef MBEDTLS_PEM_PARSE_C - const char * const ssl_cert_type = ssl_config->cert_type; + const char * const ssl_cert_type = ssl_config->primary.cert_type; #endif int ret = -1; char errorbuf[128]; @@ -662,12 +662,12 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, mbedtls_pk_init(&backend->pk); - if(ssl_config->key || ssl_config->key_blob) { - if(ssl_config->key) { + if(ssl_config->primary.key || ssl_config->primary.key_blob) { + if(ssl_config->primary.key) { #ifdef MBEDTLS_FS_IO #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, - ssl_config->key_passwd); + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, + ssl_config->primary.key_passwd); if(ret == 0 && !(mbedtls_pk_can_do_psa(&backend->pk, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH), @@ -677,8 +677,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, - ssl_config->key_passwd, + ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, + ssl_config->primary.key_passwd, mbedtls_ctr_drbg_random, &rng.drbg); if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || @@ -689,7 +689,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading private key %s: (-0x%04X) %s", - ssl_config->key, -ret, errorbuf); + ssl_config->primary.key, -ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } #else @@ -698,8 +698,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, #endif } else { - const struct curl_blob *ssl_key_blob = ssl_config->key_blob; - const char *passwd = ssl_config->key_passwd; + const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; + const char *passwd = ssl_config->primary.key_passwd; /* Unfortunately, mbedtls_pk_parse_key() requires the data to be null-terminated if the data is PEM encoded (even when provided the exact length). */ @@ -933,7 +933,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif ); - if(ssl_config->key || ssl_config->key_blob) { + if(ssl_config->primary.key || ssl_config->primary.key_blob) { mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 2eeb2f349d..2302ddacc6 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3677,7 +3677,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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; + const char * const ssl_cert_type = ssl_config->primary.cert_type; unsigned int ssl_version_min; char error_buffer[256]; @@ -3841,8 +3841,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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); + ssl_config->primary.key, ssl_config->primary.key_blob, + ssl_config->primary.key_type, + ssl_config->primary.key_passwd); if(result) /* failf() is already done in client_cert() */ return result; diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 5759194952..90a37cbeda 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -845,14 +845,14 @@ init_config_builder_client_auth(struct Curl_easy *data, const struct rustls_certified_key *certified_key = NULL; CURLcode result = CURLE_OK; - if(conn_config->clientcert && !ssl_config->key) { + if(conn_config->clientcert && !ssl_config->primary.key) { failf(data, "rustls: must provide key with certificate '%s'", conn_config->clientcert); return CURLE_SSL_CERTPROBLEM; } - else if(!conn_config->clientcert && ssl_config->key) { + else if(!conn_config->clientcert && ssl_config->primary.key) { failf(data, "rustls: must provide certificate with key '%s'", - ssl_config->key); + ssl_config->primary.key); return CURLE_SSL_CERTPROBLEM; } @@ -866,8 +866,9 @@ init_config_builder_client_auth(struct Curl_easy *data, goto cleanup; } - if(!read_file_into(ssl_config->key, &key_contents)) { - failf(data, "rustls: failed to read key file: '%s'", ssl_config->key); + if(!read_file_into(ssl_config->primary.key, &key_contents)) { + failf(data, "rustls: failed to read key file: '%s'", + ssl_config->primary.key); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } @@ -1066,7 +1067,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } } - if(conn_config->clientcert || ssl_config->key) { + if(conn_config->clientcert || ssl_config->primary.key) { result = init_config_builder_client_auth(data, conn_config, ssl_config, diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 9466de0e14..e3b2263e59 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -415,8 +415,8 @@ static CURLcode get_client_cert(struct Curl_easy *data, } } - if((fInCert || blob) && data->set.ssl.cert_type && - !curl_strequal(data->set.ssl.cert_type, "P12")) { + if((fInCert || blob) && data->set.ssl.primary.cert_type && + !curl_strequal(data->set.ssl.primary.cert_type, "P12")) { failf(data, "schannel: certificate format compatibility error " "for %s", blob ? "(memory blob)" : data->set.ssl.primary.clientcert); @@ -466,15 +466,15 @@ static CURLcode get_client_cert(struct Curl_easy *data, datablob.pbData = (BYTE *)certdata; datablob.cbData = (DWORD)certsize; - if(data->set.ssl.key_passwd) - pwd_len = strlen(data->set.ssl.key_passwd); + if(data->set.ssl.primary.key_passwd) + pwd_len = strlen(data->set.ssl.primary.key_passwd); pszPassword = (WCHAR *)curlx_malloc(sizeof(WCHAR) * (pwd_len + 1)); if(pszPassword) { int str_w_len = 0; if(pwd_len > 0) str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - data->set.ssl.key_passwd, + data->set.ssl.primary.key_passwd, (int)pwd_len, pszPassword, (int)(pwd_len + 1)); diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 4600557879..73dd3f56f1 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -205,6 +205,7 @@ static bool match_ssl_primary_config(struct Curl_easy *data, blobcmp(c1->cert_blob, c2->cert_blob) && blobcmp(c1->ca_info_blob, c2->ca_info_blob) && blobcmp(c1->issuercert_blob, c2->issuercert_blob) && + blobcmp(c1->key_blob, c2->key_blob) && Curl_safecmp(c1->CApath, c2->CApath) && Curl_safecmp(c1->CAfile, c2->CAfile) && Curl_safecmp(c1->issuercert, c2->issuercert) && @@ -218,7 +219,11 @@ static bool match_ssl_primary_config(struct Curl_easy *data, curl_strequal(c1->curves, c2->curves) && curl_strequal(c1->signature_algorithms, c2->signature_algorithms) && Curl_safecmp(c1->CRLfile, c2->CRLfile) && - Curl_safecmp(c1->pinned_key, c2->pinned_key)) + Curl_safecmp(c1->pinned_key, c2->pinned_key) && + curl_strequal(c1->cert_type, c2->cert_type) && + Curl_safecmp(c1->key, c2->key) && + curl_strequal(c1->key_type, c2->key_type) && + !Curl_timestrcmp(c1->key_passwd, c2->key_passwd)) return TRUE; return FALSE; @@ -253,6 +258,7 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); CLONE_BLOB(issuercert_blob); + CLONE_BLOB(key_blob); CLONE_STRING(CApath); CLONE_STRING(CAfile); CLONE_STRING(issuercert); @@ -263,6 +269,10 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, CLONE_STRING(curves); CLONE_STRING(signature_algorithms); CLONE_STRING(CRLfile); + CLONE_STRING(cert_type); + CLONE_STRING(key); + CLONE_STRING(key_type); + CLONE_STRING(key_passwd); #ifdef USE_TLS_SRP CLONE_STRING(username); CLONE_STRING(password); @@ -283,9 +293,14 @@ static void free_primary_ssl_config(struct ssl_primary_config *sslc) curlx_safefree(sslc->cert_blob); curlx_safefree(sslc->ca_info_blob); curlx_safefree(sslc->issuercert_blob); + curlx_safefree(sslc->key_blob); curlx_safefree(sslc->curves); curlx_safefree(sslc->signature_algorithms); curlx_safefree(sslc->CRLfile); + curlx_safefree(sslc->cert_type); + curlx_safefree(sslc->key); + curlx_safefree(sslc->key_type); + curlx_safefree(sslc->key_passwd); #ifdef USE_TLS_SRP curlx_safefree(sslc->username); curlx_safefree(sslc->password); @@ -337,12 +352,12 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; #endif - sslc->cert_type = data->set.str[STRING_CERT_TYPE]; - sslc->key = data->set.str[STRING_KEY]; - sslc->key_type = data->set.str[STRING_KEY_TYPE]; - sslc->key_passwd = data->set.str[STRING_KEY_PASSWD]; + sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE]; + sslc->primary.key = data->set.str[STRING_KEY]; + sslc->primary.key_type = data->set.str[STRING_KEY_TYPE]; + sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD]; sslc->primary.clientcert = data->set.str[STRING_CERT]; - sslc->key_blob = data->set.blobs[BLOB_KEY]; + sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; #ifndef CURL_DISABLE_PROXY sslc = &data->set.proxy_ssl; @@ -378,12 +393,12 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY]; sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY]; - sslc->cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - sslc->key = data->set.str[STRING_KEY_PROXY]; - sslc->key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - sslc->key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; + sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; + sslc->primary.key = data->set.str[STRING_KEY_PROXY]; + sslc->primary.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; + sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; sslc->primary.clientcert = data->set.str[STRING_CERT_PROXY]; - sslc->key_blob = data->set.blobs[BLOB_KEY_PROXY]; + sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; #ifdef USE_TLS_SRP sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index 88593b20ae..2fc563e800 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -50,6 +50,7 @@ struct Curl_ssl_scache_peer { char *ssl_peer_key; /* id for peer + relevant TLS configuration */ char *clientcert; + char *key_passwd; char *srp_username; char *srp_password; struct Curl_llist sessions; @@ -123,6 +124,48 @@ out: return result; } +static CURLcode cf_ssl_peer_key_add_mtls(struct dynbuf *buf, + struct ssl_primary_config *ssl, + bool *is_local) +{ + CURLcode result = CURLE_OK; + if(ssl->clientcert && ssl->clientcert[0]) { + result = cf_ssl_peer_key_add_path(buf, "CCERT", ssl->clientcert, is_local); + if(result) + goto out; + } + if(ssl->key && ssl->key[0]) { + result = cf_ssl_peer_key_add_path(buf, "KEY", ssl->key, is_local); + if(result) + goto out; + } + if(ssl->key_blob) { + result = cf_ssl_peer_key_add_hash(buf, "KEYBlob", ssl->key_blob); + if(result) + goto out; + } + if(ssl->cert_type && ssl->cert_type[0]) { + size_t i; + result = curlx_dyn_add(buf, ":CT-"); + for(i = 0; !result && ssl->cert_type[i]; i++) { + char c = Curl_raw_toupper(ssl->cert_type[i]); + result = curlx_dyn_addn(buf, &c, 1); + } + if(result) + goto out; + } + if(ssl->key_type && ssl->key_type[0]) { + size_t i; + result = curlx_dyn_add(buf, ":KT-"); + for(i = 0; !result && ssl->key_type[i]; i++) { + char c = Curl_raw_toupper(ssl->key_type[i]); + result = curlx_dyn_addn(buf, &c, 1); + } + } +out: + return result; +} + #define CURL_SSLS_LOCAL_SUFFIX ":L" #define CURL_SSLS_GLOBAL_SUFFIX ":G" @@ -134,12 +177,12 @@ static bool cf_ssl_peer_key_is_global(const char *peer_key) (peer_key[len - 2] == ':'); } -CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, - const struct ssl_peer *peer, - const char *tls_id, - char **ppeer_key) +CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl, + const struct ssl_peer *peer, + const struct Curl_peer *via_peer, + const char *tls_id, + char **ppeer_key) { - struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); struct dynbuf buf; size_t key_len; bool is_local = FALSE; @@ -188,10 +231,10 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, goto out; } if(!ssl->verifypeer || !ssl->verifyhost) { - if(cf->conn->via_peer) { + if(via_peer) { result = curlx_dyn_addf(&buf, ":CHOST-%s:CPORT-%u", - cf->conn->via_peer->hostname, - cf->conn->via_peer->port); + via_peer->hostname, + via_peer->port); if(result) goto out; } @@ -266,11 +309,9 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, goto out; } - if(ssl->clientcert && ssl->clientcert[0]) { - result = curlx_dyn_add(&buf, ":CCERT"); - if(result) - goto out; - } + result = cf_ssl_peer_key_add_mtls(&buf, ssl, &is_local); + if(result) + goto out; #ifdef USE_TLS_SRP if(ssl->username || ssl->password) { result = curlx_dyn_add(&buf, ":SRP-AUTH"); @@ -301,6 +342,16 @@ out: return result; } +CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, + const struct ssl_peer *peer, + const char *tls_id, + char **ppeer_key) +{ + struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); + return Curl_ssl_peer_key_build(ssl, peer, cf->conn->via_peer, tls_id, + ppeer_key); +} + struct Curl_ssl_scache { unsigned int magic; struct Curl_ssl_scache_peer *peers; @@ -409,6 +460,7 @@ static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) } peer->sobj_free = NULL; curlx_safefree(peer->clientcert); + curlx_safefree(peer->key_passwd); #ifdef USE_TLS_SRP curlx_safefree(peer->srp_username); curlx_safefree(peer->srp_password); @@ -437,8 +489,8 @@ static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer) * - its peer key is not yet known, because sessions were * imported using only the salt+hmac * - the peer key is global, e.g. carrying no relative paths */ - peer->exportable = (!peer->clientcert && !peer->srp_username && - !peer->srp_password && + peer->exportable = (!peer->clientcert && !peer->key_passwd && + !peer->srp_username && !peer->srp_password && (!peer->ssl_peer_key || cf_ssl_peer_key_is_global(peer->ssl_peer_key))); } @@ -447,6 +499,7 @@ static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, const char *ssl_peer_key, const char *clientcert, + const char *key_passwd, const char *srp_username, const char *srp_password, const unsigned char *salt, @@ -475,6 +528,11 @@ cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, if(!peer->clientcert) goto out; } + if(key_passwd) { + peer->key_passwd = curlx_strdup(key_passwd); + if(!peer->key_passwd) + goto out; + } if(srp_username) { peer->srp_username = curlx_strdup(srp_username); if(!peer->srp_username) @@ -616,7 +674,7 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, struct ssl_primary_config *conn_config) { if(!conn_config) { - if(peer->clientcert) + if(peer->clientcert || peer->key_passwd) return FALSE; #ifdef USE_TLS_SRP if(peer->srp_username || peer->srp_password) @@ -626,6 +684,8 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, } else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert)) return FALSE; + if(Curl_timestrcmp(peer->key_passwd, conn_config->key_passwd)) + return FALSE; #ifdef USE_TLS_SRP if(Curl_timestrcmp(peer->srp_username, conn_config->username) || Curl_timestrcmp(peer->srp_password, conn_config->password)) @@ -754,6 +814,7 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data, if(peer) { char buffer[64]; const char *ccert = conn_config ? conn_config->clientcert : NULL; + const char *kpasswd = conn_config ? conn_config->key_passwd : NULL; const char *username = NULL, *password = NULL; #ifdef USE_TLS_SRP username = conn_config ? conn_config->username : NULL; @@ -765,7 +826,7 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data, "cert-%p", conn_config->cert_blob->data); ccert = buffer; /* data is strduped by cf_ssl_scache_peer_init */ } - result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, + result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, kpasswd, username, password, NULL, NULL); if(result) goto out; @@ -1144,7 +1205,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, if(!peer) { peer = cf_ssl_get_free_peer(scache); if(peer) { - result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, + result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, NULL, NULL, NULL, salt, hmac); if(result) goto out; diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index a6a36f1630..cf270ba413 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -66,6 +66,22 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, const char *tls_id, char **ppeer_key); +/** + * Like Curl_ssl_peer_key_make() but takes the primary config and peer + * descriptors directly, without requiring a Curl_cfilter. Exposed for + * unit testing. + * @param ssl the primary SSL config to key on + * @param peer the peer the filter wants to talk to + * @param via_peer the connecting-through peer, or NULL + * @param tls_id identifier of TLS implementation for sessions + * @param ppeer_key on successful return, the key generated + */ +CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl, + const struct ssl_peer *peer, + const struct Curl_peer *via_peer, + const char *tls_id, + char **ppeer_key); + /* Return if there is a session cache shall be used. * An ssl session might not be configured or not available for * "connect-only" transfers. diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 59574c9b6a..90fc33173d 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -920,10 +920,10 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, #ifndef NO_FILESYSTEM if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { const char *cert_file = ssl_config->primary.clientcert; - const char *key_file = ssl_config->key; + const char *key_file = ssl_config->primary.key; const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); + const struct curl_blob *key_blob = ssl_config->primary.key_blob; + int file_type = wssl_do_file_type(ssl_config->primary.cert_type); int rc; switch(file_type) { @@ -954,7 +954,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, key_file = cert_file; } else - file_type = wssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->primary.key_type); rc = key_blob ? wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, @@ -968,8 +968,8 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, #else /* NO_FILESYSTEM */ if(ssl_config->primary.cert_blob) { const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->key_blob; - int file_type = wssl_do_file_type(ssl_config->cert_type); + const struct curl_blob *key_blob = ssl_config->primary.key_blob; + int file_type = wssl_do_file_type(ssl_config->primary.cert_type); int rc; switch(file_type) { @@ -994,7 +994,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, if(!key_blob) key_blob = cert_blob; else - file_type = wssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->primary.key_type); if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index a3778bdad1..cde5c28736 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -287,7 +287,7 @@ test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 \ test3208 test3209 test3210 test3211 test3212 test3213 test3214 test3215 \ test3216 test3217 test3218 test3219 test3220 \ \ -test3300 test3301 test3302 \ +test3300 test3301 test3302 test3303 test3304 \ \ test4000 test4001 diff --git a/tests/data/test3303 b/tests/data/test3303 new file mode 100644 index 0000000000..697049f013 --- /dev/null +++ b/tests/data/test3303 @@ -0,0 +1,20 @@ + + + + +unittest +TLS +mTLS + + + +# Client-side + + +unittest + + +conn-reuse match distinguishes mTLS key, cert_type, key_type and key_passwd fields + + + diff --git a/tests/data/test3304 b/tests/data/test3304 new file mode 100644 index 0000000000..4380c0819f --- /dev/null +++ b/tests/data/test3304 @@ -0,0 +1,20 @@ + + + + +unittest +TLS +mTLS + + + +# Client-side + + +unittest + + +TLS session cache peer key discriminates on mTLS key, key_type and cert_type fields + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index b474f3d7fc..c8eccd27ad 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -47,4 +47,4 @@ TESTS_C = \ unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \ unit3200.c unit3205.c \ unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c unit3219.c \ - unit3300.c unit3301.c unit3302.c + unit3300.c unit3301.c unit3302.c unit3303.c unit3304.c diff --git a/tests/unit/unit3303.c b/tests/unit/unit3303.c new file mode 100644 index 0000000000..41bced542d --- /dev/null +++ b/tests/unit/unit3303.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "urldata.h" + +#ifdef USE_SSL +#include "vtls/vtls.h" +#endif + +static CURLcode test_unit3303(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifdef USE_SSL + { + CURL *curl; + struct connectdata *conn; + struct ssl_primary_config *primary; + char *saved; + static char alt_passwd[] = "wrong"; + static char alt_key[] = "other.key"; + static char alt_ktype[] = "DER"; + static char alt_ctype[] = "P12"; + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + if(!curl) { + curl_global_cleanup(); + goto unit_test_abort; + } + + curl_easy_setopt(curl, CURLOPT_SSLCERT, "client.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key"); + curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "secret"); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); + curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); + + if(Curl_ssl_easy_config_complete((struct Curl_easy *)curl)) { + curl_easy_cleanup(curl); + curl_global_cleanup(); + goto unit_test_abort; + } + + conn = curlx_calloc(1, sizeof(*conn)); + if(!conn || Curl_ssl_conn_config_init((struct Curl_easy *)curl, conn)) { + if(conn) + Curl_ssl_conn_config_cleanup(conn); + curlx_free(conn); + curl_easy_cleanup(curl); + curl_global_cleanup(); + goto unit_test_abort; + } + + /* Baseline: identical config must match. */ + fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "identical mTLS config should match"); + + primary = &((struct Curl_easy *)curl)->set.ssl.primary; + + /* Different key_passwd must not match. */ + saved = primary->key_passwd; + primary->key_passwd = alt_passwd; + fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "different key_passwd must not reuse conn"); + primary->key_passwd = saved; + + /* Different key path must not match. */ + saved = primary->key; + primary->key = alt_key; + fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "different key must not reuse conn"); + primary->key = saved; + + /* Different key type must not match. */ + saved = primary->key_type; + primary->key_type = alt_ktype; + fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "different key_type must not reuse conn"); + primary->key_type = saved; + + /* Different cert type must not match. */ + saved = primary->cert_type; + primary->cert_type = alt_ctype; + fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "different cert_type must not reuse conn"); + primary->cert_type = saved; + + /* All fields restored: must match again. */ + fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, + FALSE), + "restored mTLS config should match"); + + Curl_ssl_conn_config_cleanup(conn); + curlx_free(conn); + curl_easy_cleanup(curl); + curl_global_cleanup(); + } +#endif /* USE_SSL */ + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit3304.c b/tests/unit/unit3304.c new file mode 100644 index 0000000000..7c39c60801 --- /dev/null +++ b/tests/unit/unit3304.c @@ -0,0 +1,168 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Unit tests for TLS session cache peer key discrimination on mTLS fields. + * Verifies that Curl_ssl_peer_key_build() produces distinct keys when two + * handles differ only on key, key_type or cert_type. key_passwd is NOT + * embedded in the peer key; it is compared separately at session lookup via + * cf_ssl_scache_match_auth(), following the same pattern as SRP + * credentials. */ + +#include "unitcheck.h" +#include "urldata.h" +#include "peer.h" + +#ifdef USE_SSL +#include "vtls/vtls.h" +#include "vtls/vtls_scache.h" +#endif + +static CURLcode test_unit3304(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifdef USE_SSL + { + struct Curl_peer dest; + struct ssl_peer peer; + struct ssl_primary_config ssl; + char *key1 = NULL; + char *key2 = NULL; + static char base_hostname[] = "example.com"; + static char base_cert[] = "client.pem"; + static char base_key[] = "client.key"; + static char base_passwd[] = "secret"; + static char base_ctype[] = "PEM"; + static char base_ktype[] = "PEM"; + static char alt_key[] = "other.key"; + static char alt_ktype[] = "DER"; + static char alt_ctype[] = "P12"; + static char lc_ctype[] = "pem"; + static char lc_ktype[] = "pem"; + + memset(&dest, 0, sizeof(dest)); + dest.hostname = base_hostname; + dest.port = 443; + + memset(&peer, 0, sizeof(peer)); + peer.dest = &dest; + peer.transport = TRNSPRT_TCP; + + memset(&ssl, 0, sizeof(ssl)); + ssl.verifypeer = TRUE; + ssl.verifyhost = TRUE; + ssl.clientcert = base_cert; + ssl.key = base_key; + ssl.key_passwd = base_passwd; + ssl.cert_type = base_ctype; + ssl.key_type = base_ktype; + + /* Baseline: same config produces same key. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && !strcmp(key1, key2), + "identical config should produce identical peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + + /* key_passwd is NOT in the peer key: lookup uses timing-safe comparison + * via cf_ssl_scache_match_auth(), same as SRP credentials. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.key_passwd = NULL; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && !strcmp(key1, key2), + "key_passwd must not affect the peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + ssl.key_passwd = base_passwd; + + /* Different key path must produce a different peer key. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.key = alt_key; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && strcmp(key1, key2), + "different key must produce different peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + ssl.key = base_key; + + /* Different key_type must produce a different peer key. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.key_type = alt_ktype; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && strcmp(key1, key2), + "different key_type must produce different peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + ssl.key_type = base_ktype; + + /* Different cert_type must produce a different peer key. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.cert_type = alt_ctype; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && strcmp(key1, key2), + "different cert_type must produce different peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + ssl.cert_type = base_ctype; + + /* cert_type is case-insensitive: "PEM" and "pem" must produce the + * same peer key, consistent with the conn-reuse comparison. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.cert_type = lc_ctype; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && !strcmp(key1, key2), + "cert_type case must not affect peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + ssl.cert_type = base_ctype; + + /* key_type is case-insensitive: "PEM" and "pem" must produce the + * same peer key. */ + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + "peer key build failed"); + ssl.key_type = lc_ktype; + fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + "peer key build failed"); + fail_unless(key1 && key2 && !strcmp(key1, key2), + "key_type case must not affect peer key"); + curlx_free(key1); key1 = NULL; + curlx_free(key2); key2 = NULL; + } +#endif /* USE_SSL */ + + UNITTEST_END_SIMPLE +}