CVE-2026-8932: incomplete mTLS config matching in conn reuse
What the report claims
A second curl handle with wrong mTLS credentials silently reuses an existing TLS connection authenticated with a different client identity, allowing impersonation within the same process.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-8932 incomplete mTLS config matching in conn reuse
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- The struct split (ssl_primary_config vs ssl_config_data) is real and grounded in lib/urldata.h. Credential fields (key_passwd, key, key_blob, key_type, cert_type) exist only in ssl_config_data and are never compared during connection reuse or session cache lookup.
- match_ssl_primary_config (lib/vtls/vtls.c:194-225) and cf_ssl_scache_match_auth (lib/vtls/vtls_scache.c:607-627) both omit these fields. connectdata (lib/urldata.h:389) stores only primary config, discarding the credential half when a connection is parked.
- The PoC demonstrates a concrete attack: two handles sharing a CURLSH pool with CURL_LOCK_DATA_CONNECT + CURL_LOCK_DATA_SSL_SESSION; handle A authenticates with correct KEYPASSWD, handle B (wrong KEYPASSWD) reuses A's connection and inherits A's client identity. Server sees CN=alice for both requests on the same connection ID.
- This is the same class of flaw as CVE-2022-27782 (incomplete TLS config comparison for connection reuse). It enables silent credential confusion/impersonation in mTLS scenarios where client cert identity maps to authorization — a clear security impact.
- Master (db4a6f286bbc) still has the gap; the fix proposed by the reporter (promote fields into primary, extend match functions and scache key) is the correct shape.
How this analysis ran 5 claims · 19 citations verified
Checked against curl master on 2026-05-13 (db4a6f286bbc). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thanks for the detailed report and PoC. The claims are grounded: ssl_primary_config omits key_passwd, key, key_blob, key_type, cert_type; match_ssl_primary_config and cf_ssl_scache_match_auth don't compare them; connectdata stores only primary. The PoC shows a real mTLS credential-confusion bug — handle B with a wrong passphrase reuses handle A's authenticated connection. This is a valid security issue (same class as CVE-2022-27782). We'll promote the missing fields into primary and extend the match/scache logic as you outlined.
Inspect all claims and source code 5 claims · 13 code excerpts
Report claims
match_ssl_primary_config only compares struct ssl_primary_config, ignoring fields stored in ssl_config_data such as key_passwd, key, key_type, and cert_type
Read the reasoning
The struct split is real: ssl_primary_config (compared) omits the credential fields that live in ssl_config_data (not compared). The connection-reuse gate literally cannot see them.
match_ssl_primary_config at lib/vtls/vtls.c:194-225
Read the reasoning
The call chain is grounded: url_match_ssl_config → Curl_ssl_conn_config_match → match_ssl_primary_config, all operating on ssl_primary_config only.
Two easy handles sharing a CURLSH pool with CURL_LOCK_DATA_CONNECT and CURL_LOCK_DATA_SSL_SESSION reuse a connection when they differ only on SSLKEY, SSLKEY_BLOB, KEYPASSWD, SSLKEYTYPE, or SSLCERTTYPE
Read the reasoning
All five credential fields (SSLKEY, SSLKEY_BLOB, KEYPASSWD, SSLKEYTYPE, SSLCERTTYPE) reside in ssl_config_data and are absent from ssl_primary_config and from the comparison function. When a connection is parked, only primary config survives in connectdata, so the credential differences are lost.
The second handle silently inherits the first's authenticated TLS identity, causing the server to see the first handle's certificate for all subsequent requests on that connection
Read the reasoning
The session cache key and its auth-match function omit the private-key material. A resumed session can therefore be reused with a different key/passphrase without detection.
F12_poc.c demonstrates that handle B with a wrong KEYPASSWD reuses handle A's connection and succeeds with HTTP 200, while standalone it would fail with CURLE_SSL_CERTPROBLEM
Read the reasoning
The PoC's observed behavior matches the code path: standalone fails at key load; shared pool reuses connection and skips key load entirely.
Deterministic checks that failed
- line none of curlx_dyn_add, ssl_primary_config, ssl_config_data, CURLSH appear near line 891 at db4a6f286bbc; it reads: '#ifdef MBEDTLS_SSL_RENEGOTIATION'
Read the original report (excerpt)
Source code
| 169 | return &ALPN_SPEC_H2; |
| 170 | } |
| 171 | #else |
| 172 | (void)wanted; |
| 173 | (void)preferred; |
| 174 | #endif |
| 175 | return &ALPN_SPEC_H11; |
| 176 | } |
| 177 | #endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_PROXY */ |
| 178 | #endif /* USE_SSL */ |
| 179 | |
| 180 | void Curl_ssl_easy_config_init(struct Curl_easy *data) |
| 181 | { |
| 182 | /* |
| 183 | * libcurl 7.10 introduced SSL verification *by default*! This needs to be |
| 184 | * switched off unless wanted. |
| 185 | */ |
| 186 | data->set.ssl.primary.verifypeer = TRUE; |
| 187 | data->set.ssl.primary.verifyhost = TRUE; |
| 188 | data->set.ssl.primary.cache_session = TRUE; /* caching by default */ |
| 189 | #ifndef CURL_DISABLE_PROXY |
| 190 | data->set.proxy_ssl = data->set.ssl; |
| 191 | #endif |
| 192 | } |
| 193 | |
| 194 | static bool match_ssl_primary_config(struct Curl_easy *data, |
| C1: match_ssl_primary_config (lines 194-225) compares only ssl_primary_config fields; no comparison of key_passwd, key, key_type, cert_type, key_blob | |
| C2: match_ssl_primary_config at lines 194-225 is the conn-reuse gate; its signature takes struct ssl_primary_config * | |
| C3: match_ssl_primary_config does not compare any of these fields | |
| C5: But with connection reuse, match_ssl_primary_config returns true (same primary), so B reuses A's connection and never calls client_cert | |
| 195 | struct ssl_primary_config *c1, |
| 196 | struct ssl_primary_config *c2) |
| 197 | { |
| 198 | (void)data; |
| 199 | if((c1->version == c2->version) && |
| 200 | (c1->version_max == c2->version_max) && |
| 201 | (c1->ssl_options == c2->ssl_options) && |
| 202 | (c1->verifypeer == c2->verifypeer) && |
| 203 | (c1->verifyhost == c2->verifyhost) && |
| 204 | (c1->verifystatus == c2->verifystatus) && |
| 205 | blobcmp(c1->cert_blob, c2->cert_blob) && |
| 206 | blobcmp(c1->ca_info_blob, c2->ca_info_blob) && |
| 207 | blobcmp(c1->issuercert_blob, c2->issuercert_blob) && |
| 208 | Curl_safecmp(c1->CApath, c2->CApath) && |
| 209 | Curl_safecmp(c1->CAfile, c2->CAfile) && |
| 210 | Curl_safecmp(c1->issuercert, c2->issuercert) && |
| 211 | Curl_safecmp(c1->clientcert, c2->clientcert) && |
| 212 | #ifdef USE_TLS_SRP |
| 213 | !Curl_timestrcmp(c1->username, c2->username) && |
| 214 | !Curl_timestrcmp(c1->password, c2->password) && |
| 215 | #endif |
| 216 | curl_strequal(c1->cipher_list, c2->cipher_list) && |
| 217 | curl_strequal(c1->cipher_list13, c2->cipher_list13) && |
| 218 | curl_strequal(c1->curves, c2->curves) && |
| 219 | curl_strequal(c1->signature_algorithms, c2->signature_algorithms) && |
| 220 | curl_strequal(c1->CRLfile, c2->CRLfile) && |
| 221 | curl_strequal(c1->pinned_key, c2->pinned_key)) |
| 222 | return TRUE; |
| 223 | |
| 224 | return FALSE; |
| 225 | } |
| 226 | |
| 227 | bool Curl_ssl_conn_config_match(struct Curl_easy *data, |
| C1: Curl_ssl_conn_config_match (lines 227-240) passes only primary configs to match_ssl_primary_config | |
| C2: Curl_ssl_conn_config_match (lines 227-240) calls it with primary configs only | |
| 228 | struct connectdata *candidate, |
| 229 | bool proxy) |
| 230 | { |
| 231 | #ifndef CURL_DISABLE_PROXY |
| 232 | if(proxy) |
| 233 | return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, |
| 234 | &candidate->proxy_ssl_config); |
| 235 | #else |
| 236 | (void)proxy; |
| 237 | #endif |
| 238 | return match_ssl_primary_config(data, &data->set.ssl.primary, |
| 239 | &candidate->ssl_config); |
| 240 | } |
| 241 | |
| 242 | static bool clone_ssl_primary_config(struct ssl_primary_config *source, |
| 243 | struct ssl_primary_config *dest) |
| 244 | { |
| 245 | dest->version = source->version; |
| 246 | dest->version_max = source->version_max; |
| 247 | dest->verifypeer = source->verifypeer; |
| 248 | dest->verifyhost = source->verifyhost; |
| 249 | dest->verifystatus = source->verifystatus; |
| 250 | dest->cache_session = source->cache_session; |
| 251 | dest->ssl_options = source->ssl_options; |
| 252 | |
| 253 | CLONE_BLOB(cert_blob); |
| 254 | CLONE_BLOB(ca_info_blob); |
| 255 | CLONE_BLOB(issuercert_blob); |
| 256 | CLONE_STRING(CApath); |
| 257 | CLONE_STRING(CAfile); |
| 258 | CLONE_STRING(issuercert); |
| 259 | CLONE_STRING(clientcert); |
| 260 | CLONE_STRING(cipher_list); |
| 261 | CLONE_STRING(cipher_list13); |
| 262 | CLONE_STRING(pinned_key); |
| 263 | CLONE_STRING(curves); |
| 264 | CLONE_STRING(signature_algorithms); |
| 265 | CLONE_STRING(CRLfile); |
| 266 | #ifdef USE_TLS_SRP |
| 267 | CLONE_STRING(username); |
| 268 | CLONE_STRING(password); |
| 269 | #endif |
| 270 | |
| 271 | return TRUE; |
| 272 | } |
| 966 | if(m->needle->scheme != conn->scheme) { |
| 967 | /* `needle` and `conn` do not have the same scheme... */ |
| 968 | if(get_protocol_family(conn->scheme) != m->needle->scheme->protocol) { |
| 969 | /* and `conn`s protocol family is not the protocol `needle` wants. |
| 970 | * IMAPS would work for IMAP, but no vice versa. */ |
| 971 | return FALSE; |
| 972 | } |
| 973 | /* We are in an IMAPS vs IMAP like case. We expect `conn` to have SSL */ |
| 974 | if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { |
| 975 | DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T |
| 976 | " has compatible protocol family, but no SSL, no match", |
| 977 | conn->connection_id)); |
| 978 | return FALSE; |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | /* `needle` must have the same hostname and port in origin and |
| 983 | * via_peer (if present, NULL peers are equal) */ |
| 984 | if(!Curl_peer_same_destination(m->needle->origin, conn->origin) || |
| 985 | !Curl_peer_same_destination(m->needle->via_peer, conn->via_peer)) |
| 986 | return FALSE; |
| 987 | } |
| 988 | return TRUE; |
| 989 | } |
| 990 | |
| 991 | static bool url_match_ssl_config(struct connectdata *conn, |
| C2: url_match_ssl_config (lines 991-1003) calls Curl_ssl_conn_config_match for connection reuse decisions | |
| 992 | struct url_conn_match *m) |
| 993 | { |
| 994 | /* If talking/upgrading to TLS, conn needs to use the same SSL options. */ |
| 995 | if(((m->needle->scheme->flags & PROTOPT_SSL) || m->may_tls) && |
| 996 | !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { |
| 997 | DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T |
| 998 | " has different SSL parameters, cannot reuse", |
| 999 | conn->connection_id)); |
| 1000 | return FALSE; |
| 1001 | } |
| 1002 | return TRUE; |
| 1003 | } |
| 1004 | |
| 1005 | #ifdef USE_NTLM |
| 1006 | static bool url_match_auth_ntlm(struct connectdata *conn, |
| 1007 | struct url_conn_match *m) |
| 1008 | { |
| 1009 | /* If we are looking for an HTTP+NTLM connection, check if this is |
| 1010 | already authenticating with the right credentials. If not, keep |
| 1011 | looking so that we can reuse NTLM connections if |
| 1012 | possible. (Especially we must not reuse the same connection if |
| 1013 | partway through a handshake!) */ |
| 1014 | if(m->want_ntlm_http) { |
| 1015 | if(!Curl_creds_same(m->data->state.creds, conn->creds)) { |
| 1016 | /* we prefer a credential match, but this is at least a connection |
| 1017 | that can be reused and "upgraded" to NTLM if it does |
| 1018 | not have any auth ongoing. */ |
| 1019 | #ifdef USE_SPNEGO |
| 1020 | if((conn->http_ntlm_state == NTLMSTATE_NONE) |
| 1021 | && (conn->http_negotiate_state == GSS_AUTHNONE)) { |
| 1022 | #else |
| 1023 | if(conn->http_ntlm_state == NTLMSTATE_NONE) { |
| 1024 | #endif |
| 1025 | m->found = conn; |
| 1026 | } |
| 1027 | return FALSE; |
| 1028 | } |
| 118 | /* The default upload buffer size, should not be smaller than |
| 119 | CURL_MAX_WRITE_SIZE, as it needs to hold a full buffer as could be sent in |
| 120 | a write callback. |
| 121 | |
| 122 | The size was 16KB for many years but was bumped to 64KB because it makes |
| 123 | libcurl able to do significantly faster uploads in some circumstances. Even |
| 124 | larger buffers can help further, but this is deemed a fair memory/speed |
| 125 | compromise. */ |
| 126 | #define UPLOADBUFFER_DEFAULT 65536 |
| 127 | #define UPLOADBUFFER_MAX (2 * 1024 * 1024) |
| 128 | #define UPLOADBUFFER_MIN CURL_MAX_WRITE_SIZE |
| 129 | |
| 130 | #define CURLEASY_MAGIC_NUMBER 0xc0dedbadU |
| 131 | #ifdef DEBUGBUILD |
| 132 | /* On a debug build, we want to fail hard on easy handles that |
| 133 | * are not NULL, but no longer have the MAGIC touch. This gives |
| 134 | * us early warning on things only discovered by valgrind otherwise. */ |
| 135 | #define GOOD_EASY_HANDLE(x) \ |
| 136 | (((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) ? TRUE : \ |
| 137 | (DEBUGASSERT(!(x)), FALSE)) |
| 138 | #else |
| 139 | #define GOOD_EASY_HANDLE(x) \ |
| 140 | ((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER)) |
| 141 | #endif |
| 142 | |
| 143 | struct ssl_primary_config { |
| C1: ssl_primary_config definition (lines 143-168) lacks key_passwd, key, key_type, cert_type, key_blob | |
| 144 | char *CApath; /* certificate directory (does not work on Windows) */ |
| 145 | char *CAfile; /* certificate to verify peer against */ |
| 146 | char *issuercert; /* optional issuer certificate filename */ |
| 147 | char *clientcert; |
| C4: ssl_primary_config.clientcert is the cert path; the key and its passphrase are not in primary | |
| 148 | char *cipher_list; /* list of ciphers to use */ |
| 149 | char *cipher_list13; /* list of TLS 1.3 cipher suites to use */ |
| 150 | char *signature_algorithms; /* list of signature algorithms to use */ |
| 151 | char *pinned_key; |
| 152 | char *CRLfile; /* CRL to check certificate revocation */ |
| 153 | struct curl_blob *cert_blob; |
| 154 | struct curl_blob *ca_info_blob; |
| 155 | struct curl_blob *issuercert_blob; |
| 156 | #ifdef USE_TLS_SRP |
| 157 | char *username; /* TLS username (for, e.g., SRP) */ |
| 158 | char *password; /* TLS password (for, e.g., SRP) */ |
| 159 | #endif |
| 160 | char *curves; /* list of curves to use */ |
| 161 | uint32_t version_max; /* max supported version the client wants to use */ |
| 162 | uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ |
| 163 | uint8_t version; /* what version the client wants to use */ |
| 164 | BIT(verifypeer); /* set TRUE if this is desired */ |
| 165 | BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ |
| 166 | BIT(verifystatus); /* set TRUE if certificate status must be checked */ |
| 167 | BIT(cache_session); /* cache session or not */ |
| 168 | }; |
| 169 | |
| 170 | struct ssl_config_data { |
| C1: ssl_config_data definition (lines 170-193) contains key_passwd (line 179), key (line 176), key_blob (line 177), key_type (line 178), cert_type (line 175) — not in primary | |
| 171 | struct ssl_primary_config primary; |
| 172 | long certverifyresult; /* result from the certificate verification */ |
| 173 | curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ |
| 174 | void *fsslctxp; /* parameter for call back */ |
| 175 | char *cert_type; /* format for certificate (default: PEM) */ |
| C3: ssl_config_data.cert_type at line 175 | |
| 176 | char *key; /* private key filename */ |
| C3: ssl_config_data.key at line 176 | |
| 177 | struct curl_blob *key_blob; |
| C3: ssl_config_data.key_blob at line 177 | |
| 178 | char *key_type; /* format for private key (default: PEM) */ |
| C3: ssl_config_data.key_type at line 178 | |
| 179 | char *key_passwd; /* plain text private key password */ |
| C3: ssl_config_data.key_passwd at line 179 | |
| 180 | BIT(certinfo); /* gather lots of certificate info */ |
| 181 | BIT(earlydata); /* use TLS 1.3 early data */ |
| 182 | BIT(enable_beast); /* allow this flaw for interoperability's sake */ |
| 183 | BIT(no_revoke); /* disable SSL certificate revocation checks */ |
| 184 | BIT(no_partialchain); /* do not accept partial certificate chains */ |
| 185 | BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation |
| 186 | list errors */ |
| 187 | BIT(native_ca_store); /* use the native CA store of operating system */ |
| 188 | BIT(auto_client_cert); /* automatically locate and use a client |
| 189 | certificate for authentication (Schannel) */ |
| 190 | BIT(custom_cafile); /* application has set custom CA file */ |
| 191 | BIT(custom_capath); /* application has set custom CA path */ |
| 192 | BIT(custom_cablob); /* application has set custom CA blob */ |
| 193 | }; |
| 194 | |
| 195 | struct ssl_general_config { |
| 196 | int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ |
| 197 | }; |
| 198 | |
| 199 | #ifdef USE_WINDOWS_SSPI |
| 200 | #include "curl_sspi.h" |
| 201 | #endif |
| 202 | |
| 203 | #ifndef CURL_DISABLE_DIGEST_AUTH |
| 204 | /* Struct used for Digest challenge-response authentication */ |
| 205 | struct digestdata { |
| 206 | #ifdef USE_WINDOWS_SSPI |
| 207 | BYTE *input_token; |
| 208 | size_t input_token_len; |
| 209 | CtxtHandle *http_context; |
| 210 | /* linked credentials used to make the identity for http_context. |
| 211 | may be NULL. */ |
| 212 | struct Curl_creds *creds; |
| 213 | #else |
| 214 | char *nonce; |
| 215 | char *cnonce; |
| 216 | char *realm; |
| 217 | char *opaque; |
| 218 | char *qop; |
| 364 | #ifndef CURL_DISABLE_PROXY |
| 365 | struct proxy_info socks_proxy; |
| 366 | struct proxy_info http_proxy; |
| 367 | #endif |
| 368 | struct Curl_creds *creds; /* When connection itself is tied to credentials */ |
| 369 | char *options; /* options string, allocated */ |
| 370 | struct curltime created; /* creation time */ |
| 371 | struct curltime lastused; /* when returned to the connection pool as idle */ |
| 372 | |
| 373 | /* A connection can have one or two sockets and connection filters. |
| 374 | * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ |
| 375 | curl_socket_t sock[2]; |
| 376 | struct Curl_cfilter *cfilter[2]; /* connection filters */ |
| 377 | Curl_recv *recv[2]; |
| 378 | Curl_send *send[2]; |
| 379 | int recv_idx; /* on which socket index to receive, default 0 */ |
| 380 | int send_idx; /* on which socket index to send, default 0 */ |
| 381 | |
| 382 | #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) |
| 383 | |
| 384 | struct { |
| 385 | struct curltime start[2]; /* when filter shutdown started */ |
| 386 | timediff_t timeout_ms; /* 0 means no timeout */ |
| 387 | } shutdown; |
| 388 | |
| 389 | struct ssl_primary_config ssl_config; |
| C3: connectdata carries only ssl_primary_config (line 389) into the pool; the full ssl_config_data is discarded | |
| 390 | #ifndef CURL_DISABLE_PROXY |
| 391 | struct ssl_primary_config proxy_ssl_config; |
| 392 | #endif |
| 393 | struct ConnectBits bits; /* various state-flags for this connection */ |
| 394 | |
| 395 | const struct Curl_scheme *scheme; /* Connection's protocol handler */ |
| 396 | const struct Curl_scheme *given; /* The protocol first given */ |
| 397 | |
| 398 | /* Protocols can use a custom keepalive mechanism to keep connections alive. |
| 399 | This allows those protocols to track the last time the keepalive mechanism |
| 400 | was used on this connection. */ |
| 401 | struct curltime keepalive; |
| 402 | |
| 403 | /* A connection cache from a SHARE might be used in several multi handles. |
| 404 | * We MUST not reuse connections that are running in another multi, |
| 405 | * for concurrency reasons. That multi might run in another thread. |
| 406 | * `attached_multi` is set by the first transfer attached and cleared |
| 407 | * when the last one is detached. |
| 408 | * NEVER call anything on this multi, check for equality. */ |
| 409 | struct Curl_multi *attached_multi; |
| 410 | |
| 411 | /*************** Request - specific items ************/ |
| 412 | #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) |
| 413 | CtxtHandle *sslContext; /* mingw-w64 v9+. MS SDK 7.0A+. */ |
| 414 | #endif |
| 415 | |
| 416 | #ifdef USE_NTLM |
| 417 | curlntlm http_ntlm_state; |
| 112 | goto out; |
| 113 | r = Curl_sha256it(hash, blob->data, blob->len); |
| 114 | if(r) |
| 115 | goto out; |
| 116 | for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) { |
| 117 | r = curlx_dyn_addf(buf, "%02x", hash[i]); |
| 118 | if(r) |
| 119 | goto out; |
| 120 | } |
| 121 | } |
| 122 | out: |
| 123 | return r; |
| 124 | } |
| 125 | |
| 126 | #define CURL_SSLS_LOCAL_SUFFIX ":L" |
| 127 | #define CURL_SSLS_GLOBAL_SUFFIX ":G" |
| 128 | |
| 129 | static bool cf_ssl_peer_key_is_global(const char *peer_key) |
| 130 | { |
| 131 | size_t len = peer_key ? strlen(peer_key) : 0; |
| 132 | return (len > 2) && |
| 133 | (peer_key[len - 1] == 'G') && |
| 134 | (peer_key[len - 2] == ':'); |
| 135 | } |
| 136 | |
| 137 | CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, |
| 138 | const struct ssl_peer *peer, |
| 139 | const char *tls_id, |
| 140 | char **ppeer_key) |
| 141 | { |
| 142 | struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); |
| 143 | struct dynbuf buf; |
| 144 | size_t key_len; |
| 145 | bool is_local = FALSE; |
| 146 | CURLcode r; |
| 147 | |
| 148 | *ppeer_key = NULL; |
| 149 | curlx_dyn_init(&buf, 10 * 1024); |
| 150 | |
| 151 | r = curlx_dyn_addf(&buf, "%s:%d", |
| 152 | peer->dest->hostname, peer->dest->port); |
| 153 | if(r) |
| 154 | goto out; |
| 155 | |
| 156 | switch(peer->transport) { |
| 157 | case TRNSPRT_TCP: |
| 158 | break; |
| 159 | case TRNSPRT_UDP: |
| 160 | r = curlx_dyn_add(&buf, ":UDP"); |
| 161 | break; |
| 162 | case TRNSPRT_QUIC: |
| 163 | r = curlx_dyn_add(&buf, ":QUIC"); |
| 164 | break; |
| 165 | case TRNSPRT_UNIX: |
| 166 | r = curlx_dyn_add(&buf, ":UNIX"); |
| 167 | break; |
| 168 | default: |
| 169 | r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); |
| 170 | break; |
| 171 | } |
| 172 | if(r) |
| 173 | goto out; |
| 174 | |
| 175 | if(!ssl->verifypeer) { |
| 176 | r = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); |
| 177 | if(r) |
| 178 | goto out; |
| 179 | } |
| 180 | if(!ssl->verifyhost) { |
| 181 | r = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); |
| 182 | if(r) |
| 183 | goto out; |
| 184 | } |
| 185 | if(ssl->verifystatus) { |
| 186 | r = curlx_dyn_add(&buf, ":VRFY-STATUS"); |
| 187 | if(r) |
| 188 | goto out; |
| 189 | } |
| 190 | if(!ssl->verifypeer || !ssl->verifyhost) { |
| 191 | if(cf->conn->via_peer) { |
| 192 | r = curlx_dyn_addf(&buf, ":CHOST-%s:CPORT-%u", |
| 193 | cf->conn->via_peer->hostname, |
| 194 | cf->conn->via_peer->port); |
| 195 | if(r) |
| 196 | goto out; |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | if(ssl->version || ssl->version_max) { |
| 201 | r = curlx_dyn_addf(&buf, ":TLSVER-%d-%u", ssl->version, |
| 202 | (ssl->version_max >> 16)); |
| 203 | if(r) |
| 204 | goto out; |
| 205 | } |
| 206 | if(ssl->ssl_options) { |
| 207 | r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); |
| 208 | if(r) |
| 209 | goto out; |
| 210 | } |
| 211 | if(ssl->cipher_list) { |
| 212 | r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); |
| 213 | if(r) |
| 214 | goto out; |
| 215 | } |
| 216 | if(ssl->cipher_list13) { |
| 217 | r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); |
| 218 | if(r) |
| 219 | goto out; |
| 220 | } |
| 221 | if(ssl->curves) { |
| 222 | r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); |
| 223 | if(r) |
| 224 | goto out; |
| 225 | } |
| 226 | if(ssl->verifypeer) { |
| 227 | r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); |
| 228 | if(r) |
| 229 | goto out; |
| 230 | r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); |
| 231 | if(r) |
| 232 | goto out; |
| 233 | r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); |
| 234 | if(r) |
| 235 | goto out; |
| 236 | r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local); |
| 237 | if(r) |
| 238 | goto out; |
| 239 | if(ssl->ca_info_blob) { |
| 240 | r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); |
| 241 | if(r) |
| 242 | goto out; |
| 243 | } |
| 244 | if(ssl->issuercert_blob) { |
| 245 | r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob); |
| 246 | if(r) |
| 247 | goto out; |
| 248 | } |
| 249 | } |
| 250 | if(ssl->cert_blob) { |
| 251 | r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); |
| 252 | if(r) |
| 253 | goto out; |
| 254 | } |
| 255 | if(ssl->pinned_key && ssl->pinned_key[0]) { |
| 256 | r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); |
| 257 | if(r) |
| 258 | goto out; |
| 259 | } |
| 260 | |
| 261 | if(ssl->clientcert && ssl->clientcert[0]) { |
| 262 | r = curlx_dyn_add(&buf, ":CCERT"); |
| 263 | if(r) |
| C4: Curl_ssl_peer_key_make (lines 137-294) adds only ':CCERT' literal for clientcert (line 263-266), not the cert path or key material | |
| 264 | goto out; |
| 265 | } |
| 266 | #ifdef USE_TLS_SRP |
| 267 | if(ssl->username || ssl->password) { |
| 268 | r = curlx_dyn_add(&buf, ":SRP-AUTH"); |
| 269 | if(r) |
| 270 | goto out; |
| 271 | } |
| 272 | #endif |
| 273 | |
| 274 | if(!tls_id || !tls_id[0]) { |
| 275 | r = CURLE_FAILED_INIT; |
| 276 | goto out; |
| 277 | } |
| 278 | r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); |
| 279 | if(r) |
| 280 | goto out; |
| 281 | |
| 282 | r = curlx_dyn_addf(&buf, is_local ? |
| 283 | CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); |
| 284 | if(r) |
| 285 | goto out; |
| 286 | |
| 287 | *ppeer_key = curlx_dyn_take(&buf, &key_len); |
| 288 | /* we added printable char, and dynbuf always null-terminates, no need |
| 289 | * to track length */ |
| 290 | |
| 291 | out: |
| 292 | curlx_dyn_free(&buf); |
| 293 | return r; |
| 294 | } |
| 295 | |
| 296 | struct Curl_ssl_scache { |
| 297 | unsigned int magic; |
| 298 | struct Curl_ssl_scache_peer *peers; |
| 299 | size_t peer_count; |
| 300 | int default_lifetime_secs; |
| 301 | long age; |
| 302 | BIT(is_locked); |
| 303 | }; |
| 304 | |
| 305 | static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data) |
| 306 | { |
| 307 | struct Curl_ssl_scache *scache = NULL; |
| 308 | /* If a share is present, its ssl_scache has preference over the multi */ |
| 309 | if(data->share && data->share->ssl_scache) |
| 310 | scache = data->share->ssl_scache; |
| 311 | else if(data->multi && data->multi->ssl_scache) |
| 312 | scache = data->multi->ssl_scache; |
| 313 | if(scache && !GOOD_SCACHE(scache)) { |
| 314 | failf(data, "transfer would use an invalid scache at %p, denied", |
| 315 | (void *)scache); |
| 316 | DEBUGASSERT(0); |
| 317 | return NULL; |
| 318 | } |
| 319 | return scache; |
| 582 | /* Lock shared SSL session data */ |
| 583 | void Curl_ssl_scache_lock(struct Curl_easy *data) |
| 584 | { |
| 585 | struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); |
| 586 | if(scache) { |
| 587 | if(CURL_SHARE_ssl_scache(data)) |
| 588 | Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, |
| 589 | CURL_LOCK_ACCESS_SINGLE); |
| 590 | DEBUGASSERT(!scache->is_locked); |
| 591 | scache->is_locked = TRUE; |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | /* Unlock shared SSL session data */ |
| 596 | void Curl_ssl_scache_unlock(struct Curl_easy *data) |
| 597 | { |
| 598 | struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); |
| 599 | if(scache) { |
| 600 | DEBUGASSERT(scache->is_locked); |
| 601 | scache->is_locked = FALSE; |
| 602 | if(CURL_SHARE_ssl_scache(data)) |
| 603 | Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); |
| 604 | } |
| 605 | } |
| 606 | |
| 607 | static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, |
| C4: cf_ssl_scache_match_auth (lines 607-627) compares only clientcert and SRP creds; never key, key_blob, key_passwd, key_type, cert_type | |
| 608 | struct ssl_primary_config *conn_config) |
| 609 | { |
| 610 | if(!conn_config) { |
| 611 | if(peer->clientcert) |
| 612 | return FALSE; |
| 613 | #ifdef USE_TLS_SRP |
| 614 | if(peer->srp_username || peer->srp_password) |
| 615 | return FALSE; |
| 616 | #endif |
| 617 | return TRUE; |
| 618 | } |
| 619 | else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert)) |
| 620 | return FALSE; |
| 621 | #ifdef USE_TLS_SRP |
| 622 | if(Curl_timestrcmp(peer->srp_username, conn_config->username) || |
| 623 | Curl_timestrcmp(peer->srp_password, conn_config->password)) |
| 624 | return FALSE; |
| 625 | #endif |
| 626 | return TRUE; |
| 627 | } |
| 628 | |
| 629 | static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, |
| 630 | struct Curl_ssl_scache *scache, |
| 631 | const char *ssl_peer_key, |
| 632 | struct ssl_primary_config *conn_config, |
| 633 | struct Curl_ssl_scache_peer **ppeer) |
| 634 | { |
| 635 | size_t i, peer_key_len = 0; |
| 636 | CURLcode result = CURLE_OK; |
| 637 | |
| 638 | *ppeer = NULL; |
| 639 | if(!GOOD_SCACHE(scache)) { |
| 640 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 641 | } |
| 642 | |
| 643 | CURL_TRC_SSLS(data, "find peer slot for %s among %zu slots", |
| 644 | ssl_peer_key, scache->peer_count); |
| 645 | |
| 646 | /* check for entries with known peer_key */ |
| 647 | for(i = 0; scache && i < scache->peer_count; i++) { |
| 648 | if(scache->peers[i].ssl_peer_key && |
| 649 | curl_strequal(ssl_peer_key, scache->peers[i].ssl_peer_key) && |
| 650 | cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { |
| 651 | /* yes, we have a cached session for this! */ |
| 652 | *ppeer = &scache->peers[i]; |
| 1422 | static CURLcode client_cert(struct Curl_easy *data, |
| C5: client_cert (lines 1422-1582) eagerly fails with CURLE_SSL_CERTPROBLEM when key_passwd is wrong (lines 1443-1448, 1522-1527) — confirming standalone B would fail | |
| 1423 | SSL_CTX* ctx, |
| 1424 | char *cert_file, |
| 1425 | const struct curl_blob *cert_blob, |
| 1426 | const char *cert_type, |
| 1427 | char *key_file, |
| 1428 | const struct curl_blob *key_blob, |
| 1429 | const char *key_type, |
| 1430 | char *key_passwd) |
| 1431 | { |
| 1432 | char error_buffer[256]; |
| 1433 | bool check_privkey = TRUE; |
| 1434 | int file_type = ossl_do_file_type(cert_type); |
| 1435 | |
| 1436 | if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE) || |
| 1437 | (file_type == SSL_FILETYPE_PROVIDER)) { |
| 1438 | SSL *ssl; |
| 1439 | X509 *x509; |
| 1440 | bool pcks12_done = FALSE; |
| 1441 | int cert_use_result; |
| 1442 | |
| 1443 | if(key_passwd) { |
| 1444 | /* set the password in the callback userdata */ |
| 1445 | SSL_CTX_set_default_passwd_cb_userdata(ctx, key_passwd); |
| 1446 | /* Set passwd callback: */ |
| 1447 | SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); |
| 1448 | } |
| 1449 | |
| 1450 | switch(file_type) { |
| 1451 | case SSL_FILETYPE_PEM: |
| 1452 | /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ |
| 1453 | cert_use_result = cert_blob ? |
| 1454 | use_certificate_chain_blob(ctx, cert_blob, key_passwd) : |
| 1455 | SSL_CTX_use_certificate_chain_file(ctx, cert_file); |
| 1456 | if(cert_use_result != 1) { |
| 1457 | failf(data, |
| 1458 | "could not load PEM client certificate from %s, " OSSL_PACKAGE |
| 1459 | " error %s, " |
| 1460 | "(no key found, wrong passphrase, or wrong file format?)", |
| 1461 | (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), |
| 1462 | ossl_strerror(ERR_get_error(), error_buffer, |
| 1463 | sizeof(error_buffer))); |
| 1464 | return CURLE_SSL_CERTPROBLEM; |
| 1465 | } |
| 1466 | break; |
| 1467 | |
| 1468 | case SSL_FILETYPE_ASN1: |
| 1469 | /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but |
| 1470 | we use the case above for PEM so this can only be performed with |
| 1471 | ASN1 files. */ |
| 1472 | |
| 1473 | cert_use_result = cert_blob ? |
| 1474 | use_certificate_blob(ctx, cert_blob, file_type, key_passwd) : |
| 1475 | SSL_CTX_use_certificate_file(ctx, cert_file, file_type); |
| 1476 | if(cert_use_result != 1) { |
| 1477 | failf(data, |
| 1478 | "could not load ASN1 client certificate from %s, " OSSL_PACKAGE |
| 1479 | " error %s, " |
| 1480 | "(no key found, wrong passphrase, or wrong file format?)", |
| 1481 | (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), |
| 1482 | ossl_strerror(ERR_get_error(), error_buffer, |
| 1483 | sizeof(error_buffer))); |
| 1484 | return CURLE_SSL_CERTPROBLEM; |
| 1485 | } |
| 1486 | break; |
| 1487 | |
| 1488 | case SSL_FILETYPE_ENGINE: |
| 1489 | if(!cert_file || !engineload(data, ctx, cert_file)) |
| 1490 | return CURLE_SSL_CERTPROBLEM; |
| 1491 | break; |
| 1492 | |
| 1493 | case SSL_FILETYPE_PROVIDER: |
| 1494 | if(!cert_file || !providerload(data, ctx, cert_file)) |
| 1495 | return CURLE_SSL_CERTPROBLEM; |
| 1496 | break; |
| 1497 | |
| 1498 | case SSL_FILETYPE_PKCS12: |
| 1499 | if(!pkcs12load(data, ctx, cert_blob, cert_file, key_passwd)) |
| 1500 | return CURLE_SSL_CERTPROBLEM; |
| 1501 | pcks12_done = TRUE; |
| 1502 | break; |
| 1503 | |
| 1504 | default: |
| 1505 | failf(data, "not supported file type '%s' for certificate", cert_type); |
| 1506 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1507 | } |
| 1508 | |
| 1509 | if(!key_file && !key_blob) { |
| 1510 | key_file = cert_file; |
| 1511 | key_blob = cert_blob; |
| 1512 | } |
| 1513 | else |
| 1514 | file_type = ossl_do_file_type(key_type); |
| 1515 | |
| 1516 | switch(file_type) { |
| 1517 | case SSL_FILETYPE_PEM: |
| 1518 | case SSL_FILETYPE_ASN1: |
| 1519 | cert_use_result = key_blob ? |
| 1520 | use_privatekey_blob(ctx, key_blob, file_type, key_passwd) : |
| 1521 | SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); |
| 1522 | if(cert_use_result != 1) { |
| 1523 | failf(data, "unable to set private key file: '%s' type %s", |
| 1524 | key_file ? key_file : "(memory blob)", |
| 1525 | key_type ? key_type : "PEM"); |
| 1526 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1527 | } |
| 1528 | break; |
| 1529 | case SSL_FILETYPE_ENGINE: |
| 1530 | if(!enginecheck(data, ctx, key_file, key_passwd)) |
| 1531 | return CURLE_SSL_CERTPROBLEM; |
| 1532 | break; |
| 1533 | |
| 1534 | case SSL_FILETYPE_PROVIDER: |
| 1535 | if(!providercheck(data, ctx, key_file)) |
| 1536 | return CURLE_SSL_CERTPROBLEM; |
| 1537 | break; |
| 1538 | |
| 1539 | case SSL_FILETYPE_PKCS12: |
| 1540 | if(!pcks12_done) { |
| 1541 | failf(data, "file type P12 for private key not supported"); |
| 1542 | return CURLE_SSL_CERTPROBLEM; |
| 1543 | } |
| 1544 | break; |
| 1545 | default: |
| 1546 | failf(data, "not supported file type for private key"); |
| 1547 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1548 | } |
| 1549 | |
| 1550 | ssl = SSL_new(ctx); |
| 1551 | if(!ssl) { |
| 1552 | failf(data, "unable to create an SSL structure"); |
| 1553 | return CURLE_OUT_OF_MEMORY; |
| 1554 | } |
| 1555 | |
| 1556 | x509 = SSL_get_certificate(ssl); |
| 1557 | |
| 1558 | if(x509) { |
| 1559 | EVP_PKEY *pktmp = X509_get_pubkey(x509); |
| 1560 | EVP_PKEY_copy_parameters(pktmp, SSL_get_privatekey(ssl)); |
| 1561 | EVP_PKEY_free(pktmp); |
| 1562 | } |
| 1563 | |
| 1564 | #if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_NO_DEPRECATED_3_0) |
| 1565 | { |
| 1566 | /* If RSA is used, do not check the private key if its flags indicate |
| 1567 | * it does not support it. */ |
| 1568 | EVP_PKEY *priv_key = SSL_get_privatekey(ssl); |
| 1569 | if(EVP_PKEY_id(priv_key) == EVP_PKEY_RSA) { |
| 1570 | RSA *rsa = EVP_PKEY_get1_RSA(priv_key); |
| 1571 | if(RSA_flags(rsa) & RSA_METHOD_FLAG_NO_CHECK) |
| 1572 | check_privkey = FALSE; |
| 1573 | RSA_free(rsa); /* Decrement reference count */ |
| 1574 | } |
| 1575 | } |
| 1576 | #endif |
| 1577 | |
| 1578 | SSL_free(ssl); |
| 1579 | |
| 1580 | /* If we are using DSA, we can copy the parameters from |
| 1581 | * the private key */ |
| 1582 |
Show 106 lines (not cited by the judge)
| 1046 | } |
| 1047 | else if(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { |
| 1048 | failf(data, "peer certificate could not be verified"); |
| 1049 | return CURLE_PEER_FAILED_VERIFICATION; |
| 1050 | } |
| 1051 | else if(ret) { |
| 1052 | char errorbuf[128]; |
| 1053 | CURL_TRC_CF(data, cf, "TLS version %04X", |
| 1054 | mbedtls_ssl_get_version_number(&backend->ssl)); |
| 1055 | mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); |
| 1056 | failf(data, "ssl_handshake returned: (-0x%04X) %s", |
| 1057 | -ret, errorbuf); |
| 1058 | return CURLE_SSL_CONNECT_ERROR; |
| 1059 | } |
| 1060 | |
| 1061 | { |
| 1062 | char cipher_str[64]; |
| 1063 | uint16_t cipher_id; |
| 1064 | cipher_id = |
| 1065 | (uint16_t)mbedtls_ssl_get_ciphersuite_id_from_ssl(&backend->ssl); |
| 1066 | mbed_cipher_suite_get_str(cipher_id, cipher_str, sizeof(cipher_str), TRUE); |
| 1067 | infof(data, "mbedTLS: %s Handshake complete, cipher is %s", |
| 1068 | mbedtls_ssl_get_version(&backend->ssl), cipher_str); |
| 1069 | } |
| 1070 | |
| 1071 | #ifdef HAVE_PINNED_PUBKEY |
| 1072 | if(pinnedpubkey) { |
| 1073 | int size; |
| 1074 | const mbedtls_x509_crt *peercert; |
| 1075 | mbedtls_x509_crt *p = NULL; |
| 1076 | unsigned char *pubkey = NULL; |
| 1077 | |
| 1078 | peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); |
| 1079 | if(!peercert || !peercert->raw.p || !peercert->raw.len) { |
| 1080 | failf(data, "Failed due to missing peer certificate"); |
| 1081 | return CURLE_SSL_PINNEDPUBKEYNOTMATCH; |
| 1082 | } |
| 1083 | |
| 1084 | p = curlx_calloc(1, sizeof(*p)); |
| 1085 | |
| 1086 | if(!p) |
| 1087 | return CURLE_OUT_OF_MEMORY; |
| 1088 | |
| 1089 | pubkey = curlx_malloc(PUB_DER_MAX_BYTES); |
| 1090 | |
| 1091 | if(!pubkey) { |
| 1092 | result = CURLE_OUT_OF_MEMORY; |
| 1093 | goto pinnedpubkey_error; |
| 1094 | } |
| 1095 | |
| 1096 | mbedtls_x509_crt_init(p); |
| 1097 | |
| 1098 | /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der |
| 1099 | needs a non-const key, for now. |
| 1100 | https://github.com/Mbed-TLS/mbedtls/issues/396 */ |
| 1101 | if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) { |
| 1102 | failf(data, "Failed copying peer certificate"); |
| 1103 | result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; |
| 1104 | goto pinnedpubkey_error; |
| 1105 | } |
| 1106 | |
| 1107 | size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES); |
| 1108 | |
| 1109 | if(size <= 0) { |
| 1110 | failf(data, "Failed copying public key from peer certificate"); |
| 1111 | result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; |
| 1112 | goto pinnedpubkey_error; |
| 1113 | } |
| 1114 | |
| 1115 | /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */ |
| 1116 | result = Curl_pin_peer_pubkey(data, |
| 1117 | pinnedpubkey, |
| 1118 | &pubkey[PUB_DER_MAX_BYTES - size], size); |
| 1119 | pinnedpubkey_error: |
| 1120 | mbedtls_x509_crt_free(p); |
| 1121 | curlx_free(p); |
| 1122 | curlx_free(pubkey); |
| 1123 | if(result) |
| 1124 | return result; |
| 1125 | } |
| 1126 | #endif |
| 1127 | |
| 1128 | #ifdef HAS_ALPN_MBEDTLS |
| 1129 | if(connssl->alpn) { |
| 1130 | const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); |
| 1131 | |
| 1132 | result = Curl_alpn_set_negotiated(cf, data, connssl, |
| 1133 | (const unsigned char *)proto, |
| 1134 | proto ? strlen(proto) : 0); |
| 1135 | if(result) |
| 1136 | return result; |
| 1137 | } |
| 1138 | #endif |
| 1139 | |
| 1140 | connssl->connecting_state = ssl_connect_3; |
| 1141 | infof(data, "SSL connected"); |
| 1142 | |
| 1143 | return CURLE_OK; |
| 1144 | } |
| 1145 | |
| 1146 | static CURLcode mbed_new_session(struct Curl_cfilter *cf, |
| 1147 | struct Curl_easy *data) |
| 1148 | { |
| 1149 | struct ssl_connect_data *connssl = cf->ctx; |
| 1150 | struct mbed_ssl_backend_data *backend = |
| 1151 | (struct mbed_ssl_backend_data *)connssl->backend; |
Show 140 lines (not cited by the judge)
| 755 | CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, |
| 756 | const char *pinnedpubkey, |
| 757 | const unsigned char *pubkey, size_t pubkeylen) |
| 758 | { |
| 759 | CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; |
| 760 | |
| 761 | /* if a path was not specified, do not pin */ |
| 762 | if(!pinnedpubkey) |
| 763 | return CURLE_OK; |
| 764 | if(!pubkey || !pubkeylen) |
| 765 | return result; |
| 766 | |
| 767 | /* only do this if pinnedpubkey starts with "sha256//", length 8 */ |
| 768 | if(!strncmp(pinnedpubkey, "sha256//", 8)) { |
| 769 | CURLcode encode; |
| 770 | char *cert_hash = NULL; |
| 771 | const char *pinned_hash, *end_pos; |
| 772 | size_t cert_hash_len = 0, pinned_hash_len; |
| 773 | unsigned char *sha256sumdigest; |
| 774 | |
| 775 | if(!Curl_ssl->sha256sum) { |
| 776 | /* without sha256 support, this cannot match */ |
| 777 | return result; |
| 778 | } |
| 779 | |
| 780 | /* compute sha256sum of public key */ |
| 781 | sha256sumdigest = curlx_malloc(CURL_SHA256_DIGEST_LENGTH); |
| 782 | if(!sha256sumdigest) |
| 783 | return CURLE_OUT_OF_MEMORY; |
| 784 | encode = Curl_ssl->sha256sum(pubkey, pubkeylen, |
| 785 | sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); |
| 786 | |
| 787 | if(!encode) |
| 788 | encode = curlx_base64_encode(sha256sumdigest, |
| 789 | CURL_SHA256_DIGEST_LENGTH, |
| 790 | &cert_hash, &cert_hash_len); |
| 791 | curlx_safefree(sha256sumdigest); |
| 792 | |
| 793 | if(encode) |
| 794 | return encode; |
| 795 | |
| 796 | infof(data, " public key hash: sha256//%s", cert_hash); |
| 797 | |
| 798 | pinned_hash = pinnedpubkey; |
| 799 | while(pinned_hash && |
| 800 | !strncmp(pinned_hash, "sha256//", (sizeof("sha256//") - 1))) { |
| 801 | pinned_hash = pinned_hash + (sizeof("sha256//") - 1); |
| 802 | end_pos = strchr(pinned_hash, ';'); |
| 803 | pinned_hash_len = end_pos ? |
| 804 | (size_t)(end_pos - pinned_hash) : strlen(pinned_hash); |
| 805 | |
| 806 | /* compare base64 sha256 digests */ |
| 807 | if(cert_hash_len == pinned_hash_len && |
| 808 | !memcmp(cert_hash, pinned_hash, cert_hash_len)) { |
| 809 | DEBUGF(infof(data, "public key hash matches pinned value")); |
| 810 | result = CURLE_OK; |
| 811 | break; |
| 812 | } |
| 813 | |
| 814 | DEBUGF(infof(data, "public key hash does not match 'sha256//%.*s'", |
| 815 | (int)pinned_hash_len, pinned_hash)); |
| 816 | /* next one or we are at the end */ |
| 817 | pinned_hash = end_pos ? (end_pos + 1) : NULL; |
| 818 | } |
| 819 | curlx_safefree(cert_hash); |
| 820 | } |
| 821 | else { |
| 822 | long filesize; |
| 823 | size_t size, pem_len; |
| 824 | CURLcode pem_read; |
| 825 | struct dynbuf buf; |
| 826 | unsigned char *pem_ptr = NULL; |
| 827 | size_t left; |
| 828 | FILE *fp = curlx_fopen(pinnedpubkey, "rb"); |
| 829 | if(!fp) |
| 830 | return result; |
| 831 | |
| 832 | curlx_dyn_init(&buf, MAX_PINNED_PUBKEY_SIZE); |
| 833 | |
| 834 | /* Determine the file's size */ |
| 835 | if(fseek(fp, 0, SEEK_END)) |
| 836 | goto end; |
| 837 | filesize = ftell(fp); |
| 838 | if(fseek(fp, 0, SEEK_SET)) |
| 839 | goto end; |
| 840 | if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE) |
| 841 | goto end; |
| 842 | |
| 843 | /* |
| 844 | * if the size of our certificate is bigger than the file |
| 845 | * size then it cannot match |
| 846 | */ |
| 847 | size = curlx_sotouz((curl_off_t)filesize); |
| 848 | if(pubkeylen > size) |
| 849 | goto end; |
| 850 | |
| 851 | /* |
| 852 | * Read the file into the dynbuf |
| 853 | */ |
| 854 | left = size; |
| 855 | do { |
| 856 | char buffer[1024]; |
| 857 | size_t want = left > sizeof(buffer) ? sizeof(buffer) : left; |
| 858 | if(want != fread(buffer, 1, want, fp)) |
| 859 | goto end; |
| 860 | if(curlx_dyn_addn(&buf, buffer, want)) |
| 861 | goto end; |
| 862 | left -= want; |
| 863 | } while(left); |
| 864 | |
| 865 | /* If the sizes are the same, it cannot be base64 encoded, must be der */ |
| 866 | if(pubkeylen == size) { |
| 867 | if(!memcmp(pubkey, curlx_dyn_ptr(&buf), pubkeylen)) |
| 868 | result = CURLE_OK; |
| 869 | goto end; |
| 870 | } |
| 871 | |
| 872 | /* |
| 873 | * Otherwise we assume it is PEM and try to decode it after placing |
| 874 | * null-terminator |
| 875 | */ |
| 876 | pem_read = pubkey_pem_to_der(curlx_dyn_ptr(&buf), &pem_ptr, &pem_len); |
| 877 | /* if it was not read successfully, exit */ |
| 878 | if(pem_read) |
| 879 | goto end; |
| 880 | |
| 881 | /* |
| 882 | * if the size of our certificate does not match the size of |
| 883 | * the decoded file, they cannot be the same, otherwise compare |
| 884 | */ |
| 885 | if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) |
| 886 | result = CURLE_OK; |
| 887 | end: |
| 888 | curlx_dyn_free(&buf); |
| 889 | curlx_safefree(pem_ptr); |
| 890 | curlx_fclose(fp); |
| 891 | } |
| 892 | |
| 893 | return result; |
| 894 | } |
Show 160 lines (not cited by the judge)
| 137 | CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, |
| 138 | const struct ssl_peer *peer, |
| 139 | const char *tls_id, |
| 140 | char **ppeer_key) |
| 141 | { |
| 142 | struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); |
| 143 | struct dynbuf buf; |
| 144 | size_t key_len; |
| 145 | bool is_local = FALSE; |
| 146 | CURLcode r; |
| 147 | |
| 148 | *ppeer_key = NULL; |
| 149 | curlx_dyn_init(&buf, 10 * 1024); |
| 150 | |
| 151 | r = curlx_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port); |
| 152 | if(r) |
| 153 | goto out; |
| 154 | |
| 155 | switch(peer->transport) { |
| 156 | case TRNSPRT_TCP: |
| 157 | break; |
| 158 | case TRNSPRT_UDP: |
| 159 | r = curlx_dyn_add(&buf, ":UDP"); |
| 160 | break; |
| 161 | case TRNSPRT_QUIC: |
| 162 | r = curlx_dyn_add(&buf, ":QUIC"); |
| 163 | break; |
| 164 | case TRNSPRT_UNIX: |
| 165 | r = curlx_dyn_add(&buf, ":UNIX"); |
| 166 | break; |
| 167 | default: |
| 168 | r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); |
| 169 | break; |
| 170 | } |
| 171 | if(r) |
| 172 | goto out; |
| 173 | |
| 174 | if(!ssl->verifypeer) { |
| 175 | r = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); |
| 176 | if(r) |
| 177 | goto out; |
| 178 | } |
| 179 | if(!ssl->verifyhost) { |
| 180 | r = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); |
| 181 | if(r) |
| 182 | goto out; |
| 183 | } |
| 184 | if(ssl->verifystatus) { |
| 185 | r = curlx_dyn_add(&buf, ":VRFY-STATUS"); |
| 186 | if(r) |
| 187 | goto out; |
| 188 | } |
| 189 | if(!ssl->verifypeer || !ssl->verifyhost) { |
| 190 | if(cf->conn->bits.conn_to_host) { |
| 191 | r = curlx_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name); |
| 192 | if(r) |
| 193 | goto out; |
| 194 | } |
| 195 | if(cf->conn->bits.conn_to_port) { |
| 196 | r = curlx_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port); |
| 197 | if(r) |
| 198 | goto out; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | if(ssl->version || ssl->version_max) { |
| 203 | r = curlx_dyn_addf(&buf, ":TLSVER-%d-%u", ssl->version, |
| 204 | (ssl->version_max >> 16)); |
| 205 | if(r) |
| 206 | goto out; |
| 207 | } |
| 208 | if(ssl->ssl_options) { |
| 209 | r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); |
| 210 | if(r) |
| 211 | goto out; |
| 212 | } |
| 213 | if(ssl->cipher_list) { |
| 214 | r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); |
| 215 | if(r) |
| 216 | goto out; |
| 217 | } |
| 218 | if(ssl->cipher_list13) { |
| 219 | r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); |
| 220 | if(r) |
| 221 | goto out; |
| 222 | } |
| 223 | if(ssl->curves) { |
| 224 | r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); |
| 225 | if(r) |
| 226 | goto out; |
| 227 | } |
| 228 | if(ssl->verifypeer) { |
| 229 | r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); |
| 230 | if(r) |
| 231 | goto out; |
| 232 | r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); |
| 233 | if(r) |
| 234 | goto out; |
| 235 | r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); |
| 236 | if(r) |
| 237 | goto out; |
| 238 | r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local); |
| 239 | if(r) |
| 240 | goto out; |
| 241 | if(ssl->ca_info_blob) { |
| 242 | r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); |
| 243 | if(r) |
| 244 | goto out; |
| 245 | } |
| 246 | if(ssl->issuercert_blob) { |
| 247 | r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob); |
| 248 | if(r) |
| 249 | goto out; |
| 250 | } |
| 251 | } |
| 252 | if(ssl->cert_blob) { |
| 253 | r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); |
| 254 | if(r) |
| 255 | goto out; |
| 256 | } |
| 257 | if(ssl->pinned_key && ssl->pinned_key[0]) { |
| 258 | r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); |
| 259 | if(r) |
| 260 | goto out; |
| 261 | } |
| 262 | |
| 263 | if(ssl->clientcert && ssl->clientcert[0]) { |
| 264 | r = curlx_dyn_add(&buf, ":CCERT"); |
| 265 | if(r) |
| 266 | goto out; |
| 267 | } |
| 268 | #ifdef USE_TLS_SRP |
| 269 | if(ssl->username || ssl->password) { |
| 270 | r = curlx_dyn_add(&buf, ":SRP-AUTH"); |
| 271 | if(r) |
| 272 | goto out; |
| 273 | } |
| 274 | #endif |
| 275 | |
| 276 | if(!tls_id || !tls_id[0]) { |
| 277 | r = CURLE_FAILED_INIT; |
| 278 | goto out; |
| 279 | } |
| 280 | r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); |
| 281 | if(r) |
| 282 | goto out; |
| 283 | |
| 284 | r = curlx_dyn_addf(&buf, is_local ? |
| 285 | CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); |
| 286 | if(r) |
| 287 | goto out; |
| 288 | |
| 289 | *ppeer_key = curlx_dyn_take(&buf, &key_len); |
| 290 | /* we added printable char, and dynbuf always null-terminates, no need |
| 291 | * to track length */ |
| 292 | |
| 293 | out: |
| 294 | curlx_dyn_free(&buf); |
| 295 | return r; |
| 296 | } |
Show 10 lines (not cited by the judge)
| 173 | CURLcode curlx_dyn_add(struct dynbuf *s, const char *str) |
| 174 | { |
| 175 | size_t n; |
| 176 | DEBUGASSERT(str); |
| 177 | DEBUGASSERT(s); |
| 178 | DEBUGASSERT(s->init == DYNINIT); |
| 179 | DEBUGASSERT(!s->leng || s->bufr); |
| 180 | n = strlen(str); |
| 181 | return dyn_nappend(s, (const unsigned char *)str, n); |
| 182 | } |
Show 20 lines (not cited by the judge)
| 274 | static void free_primary_ssl_config(struct ssl_primary_config *sslc) |
| 275 | { |
| 276 | curlx_safefree(sslc->CApath); |
| 277 | curlx_safefree(sslc->CAfile); |
| 278 | curlx_safefree(sslc->issuercert); |
| 279 | curlx_safefree(sslc->clientcert); |
| 280 | curlx_safefree(sslc->cipher_list); |
| 281 | curlx_safefree(sslc->cipher_list13); |
| 282 | curlx_safefree(sslc->pinned_key); |
| 283 | curlx_safefree(sslc->cert_blob); |
| 284 | curlx_safefree(sslc->ca_info_blob); |
| 285 | curlx_safefree(sslc->issuercert_blob); |
| 286 | curlx_safefree(sslc->curves); |
| 287 | curlx_safefree(sslc->signature_algorithms); |
| 288 | curlx_safefree(sslc->CRLfile); |
| 289 | #ifdef USE_TLS_SRP |
| 290 | curlx_safefree(sslc->username); |
| 291 | curlx_safefree(sslc->password); |
| 292 | #endif |
| 293 | } |
Show 13 lines (not cited by the judge)
| 1059 | static bool url_match_ssl_config(struct connectdata *conn, |
| 1060 | struct url_conn_match *m) |
| 1061 | { |
| 1062 | /* If talking TLS, conn needs to use the same SSL options. */ |
| 1063 | if((m->needle->scheme->flags & PROTOPT_SSL) && |
| 1064 | !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { |
| 1065 | DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T |
| 1066 | " has different SSL parameters, cannot reuse", |
| 1067 | conn->connection_id)); |
| 1068 | return FALSE; |
| 1069 | } |
| 1070 | return TRUE; |
| 1071 | } |