@@ -11330,6 +11330,17 @@ inline bool setup_client_tls_session(
1133011330 return false;
1133111331 };
1133211332
11333+ // A TLS failure, whose verification codes name the Error too
11334+ auto fail_tls = [&](ErrorCode code, uint64_t backend_error) {
11335+ auto error = Error::SSLConnection;
11336+ if (code == ErrorCode::CertVerifyFailed) {
11337+ error = Error::SSLServerVerification;
11338+ } else if (code == ErrorCode::HostnameMismatch) {
11339+ error = Error::SSLServerHostnameVerification;
11340+ }
11341+ return fail(error, static_cast<int>(code), backend_error);
11342+ };
11343+
1133311344 if (!ctx) {
1133411345 session = nullptr;
1133511346 return fail(Error::SSLConnection, 0, 0);
@@ -11345,45 +11356,42 @@ inline bool setup_client_tls_session(
1134511356 !options.server_certificate_verifier_set;
1134611357#endif
1134711358
11359+ // The chain, and a host name, are verified during the handshake, so that a
11360+ // server failing them never gets to see a client certificate. Unless
11361+ // Schannel verifies the chain instead, this holds for IP hosts too, whose
11362+ // identity is checked after the handshake by verify_hostname().
11363+ auto verify_in_handshake =
11364+ server_certificate_verification && !windows_verifies_chain;
11365+ #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
11366+ // A session verifier may accept a certificate the built-in check rejects,
11367+ // and runs only after the handshake
11368+ verify_in_handshake = verify_in_handshake && !options.session_verifier;
11369+ #endif
11370+
1134811371 {
1134911372 std::unique_lock<std::mutex> guard;
1135011373 if (options.ctx_mutex) {
1135111374 guard = std::unique_lock<std::mutex>(*options.ctx_mutex);
1135211375 }
11353- #if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
11354- // Mbed TLS and wolfSSL need the verification mode set explicitly, on the
11355- // context, so under the same lock; OpenSSL uses SSL_VERIFY_NONE and does
11356- // all verification post-handshake. Unless Schannel verifies the chain
11357- // instead, chain verification happens during the handshake even for IP
11358- // hosts; the certificate identity is verified post-handshake via
11359- // verify_hostname().
11360- set_verify_client(ctx, server_certificate_verification &&
11361- !windows_verifies_chain);
11362- #endif
11376+ // The mode is set on the context, so under the same lock
11377+ set_verify_client(ctx, verify_in_handshake);
1136311378 session = create_session(ctx, sock);
1136411379 }
1136511380 if (!session) { return fail(Error::SSLConnection, 0, get_error()); }
1136611381
1136711382 // RFC 6066: SNI must not be set for IP addresses; skip it for IP hosts, so
11368- // their identity is checked post-handshake below instead. On Mbed TLS and
11369- // wolfSSL, set_sni also drives handshake-time hostname verification, so
11370- // options.server_hostname_verification is threaded through here.
11383+ // their identity is checked post-handshake below instead.
1137111384 if (!is_ip_address(host)) {
11372- if (!set_sni(session, host.c_str(), options.server_hostname_verification)) {
11385+ if (!set_sni(session, host.c_str(),
11386+ verify_in_handshake && options.server_hostname_verification)) {
1137311387 return fail(Error::SSLConnection, 0, get_error());
1137411388 }
1137511389 }
1137611390
1137711391 TlsError tls_err;
1137811392 if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec,
1137911393 &tls_err)) {
11380- auto error = Error::SSLConnection;
11381- if (tls_err.code == ErrorCode::CertVerifyFailed) {
11382- error = Error::SSLServerVerification;
11383- } else if (tls_err.code == ErrorCode::HostnameMismatch) {
11384- error = Error::SSLServerHostnameVerification;
11385- }
11386- return fail(error, static_cast<int>(tls_err.code), tls_err.backend_code);
11394+ return fail_tls(tls_err.code, tls_err.backend_code);
1138711395 }
1138811396
1138911397 auto verification_status = SSLVerifierResponse::NoDecisionMade;
@@ -11400,14 +11408,14 @@ inline bool setup_client_tls_session(
1140011408 if (!windows_verifies_chain) {
1140111409 auto verify_result = get_verify_result(session);
1140211410 if (verify_result != 0) {
11403- return fail(Error::SSLServerVerification, 0 ,
11404- static_cast<uint64_t>(verify_result));
11411+ return fail_tls(ErrorCode::CertVerifyFailed ,
11412+ static_cast<uint64_t>(verify_result));
1140511413 }
1140611414 }
1140711415
1140811416 auto server_cert = get_peer_cert(session);
1140911417 if (!server_cert) {
11410- return fail(Error::SSLServerVerification, 0 , get_error());
11418+ return fail_tls(ErrorCode::CertVerifyFailed , get_error());
1141111419 }
1141211420 auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
1141311421
@@ -11416,8 +11424,7 @@ inline bool setup_client_tls_session(
1141611424 // hostname is bound during the handshake.
1141711425 if (options.server_hostname_verification) {
1141811426 if (!verify_hostname(server_cert, host.c_str())) {
11419- return fail(Error::SSLServerHostnameVerification, 0,
11420- hostname_mismatch_code());
11427+ return fail_tls(ErrorCode::HostnameMismatch, hostname_mismatch_code());
1142111428 }
1142211429 }
1142311430
@@ -11432,7 +11439,7 @@ inline bool setup_client_tls_session(
1143211439 !verify_cert_with_windows_schannel(
1143311440 der, host, options.server_hostname_verification, wincrypt_error,
1143411441 session)) {
11435- return fail(Error::SSLServerVerification, 0 , wincrypt_error);
11442+ return fail_tls(ErrorCode::CertVerifyFailed , wincrypt_error);
1143611443 }
1143711444 }
1143811445#endif
@@ -19715,6 +19722,30 @@ inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
1971519722 }
1971619723}
1971719724
19725+ // Fills in a failed SSL_connect(). One that the server certificate's
19726+ // verification stopped reports the verify result, as the other backends and
19727+ // a check after the handshake do.
19728+ inline void fill_connect_error(SSL *ssl, int ret, TlsError &err) {
19729+ auto ssl_err = SSL_get_error(ssl, ret);
19730+ auto error = ERR_get_error();
19731+ err.code = map_ssl_error(ssl_err, err.sys_errno);
19732+ err.backend_code = error;
19733+ if (ssl_err != SSL_ERROR_SSL || ERR_GET_LIB(error) != ERR_LIB_SSL ||
19734+ ERR_GET_REASON(error) != SSL_R_CERTIFICATE_VERIFY_FAILED) {
19735+ return;
19736+ }
19737+ // A certificate only a verify callback turned down carries no verdict of
19738+ // its own, and fails as any other handshake does
19739+ auto verify_result = SSL_get_verify_result(ssl);
19740+ if (verify_result == X509_V_OK || verify_result == X509_V_ERR_UNSPECIFIED) {
19741+ return;
19742+ }
19743+ err.code = verify_result == X509_V_ERR_HOSTNAME_MISMATCH
19744+ ? ErrorCode::HostnameMismatch
19745+ : ErrorCode::CertVerifyFailed;
19746+ err.backend_code = static_cast<uint64_t>(verify_result);
19747+ }
19748+
1971819749// Helper: Create client CA list from PEM string
1971919750// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
1972019751// Caller takes ownership of returned list
@@ -19790,7 +19821,14 @@ inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
1979019821 verify_ctx.error_string =
1979119822 (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
1979219823
19793- return run_verify_callback(*callback, verify_ctx) ? 1 : 0;
19824+ auto accepted = run_verify_callback(*callback, verify_ctx);
19825+ // A client verifying the server during the handshake, which is what
19826+ // set_verify_client(ctx, true) asks for there, keeps the built-in verdict
19827+ if (!SSL_is_server(ssl) &&
19828+ (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)) {
19829+ accepted = accepted && preverify_ok;
19830+ }
19831+ return accepted ? 1 : 0;
1979419832}
1979519833
1979619834// Sets the verify mode from whether the peer's certificate is required and
@@ -20066,14 +20104,18 @@ inline void free_session(session_t session) {
2006620104}
2006720105
2006820106inline bool set_sni(session_t session, const char *hostname,
20069- bool /* verify_hostname*/ ) {
20107+ bool verify_hostname) {
2007020108 if (!session || !hostname) return false;
2007120109
2007220110 auto ssl = static_cast<SSL *>(session);
2007320111
20074- // Set SNI (Server Name Indication) only - does not enable verification.
20075- // OpenSSL never binds identity checking to SNI (that happens post-
20076- // handshake in setup_client_tls_session()), so verify_hostname is unused.
20112+ // The handshake's chain verification then checks the name as well
20113+ if (verify_hostname &&
20114+ X509_VERIFY_PARAM_set1_host(SSL_get0_param(ssl), hostname,
20115+ strlen(hostname)) != 1) {
20116+ return false;
20117+ }
20118+
2007720119#if defined(OPENSSL_IS_BORINGSSL)
2007820120 return SSL_set_tlsext_host_name(ssl, hostname) == 1;
2007920121#else
@@ -20093,9 +20135,7 @@ inline TlsError connect(session_t session) {
2009320135 if (ret == 1) {
2009420136 err.code = ErrorCode::Success;
2009520137 } else {
20096- auto ssl_err = SSL_get_error(ssl, ret);
20097- err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
20098- err.backend_code = ERR_get_error();
20138+ impl::fill_connect_error(ssl, ret, err);
2009920139 }
2010020140 return err;
2010120141}
@@ -20151,10 +20191,7 @@ inline bool connect_nonblocking(session_t session, socket_t sock,
2015120191 break;
2015220192 default: break;
2015320193 }
20154- if (err) {
20155- err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
20156- err->backend_code = ERR_get_error();
20157- }
20194+ if (err) { impl::fill_connect_error(ssl, res, *err); }
2015820195 return false;
2015920196 }
2016020197 if (err) { err->code = ErrorCode::Success; }
@@ -22296,7 +22333,13 @@ inline int wolfssl_verify_callback(int preverify_ok,
2229622333 verify_ctx.error_string = nullptr;
2229722334 }
2229822335
22299- bool accepted = run_verify_callback(*callback, verify_ctx);
22336+ auto accepted = run_verify_callback(*callback, verify_ctx);
22337+ // A client verifying the server during the handshake, which is what
22338+ // set_verify_client(ctx, true) asks for there, keeps the built-in verdict
22339+ if (!wolfSSL_is_server(ssl) &&
22340+ (wolfSSL_get_verify_mode(ssl) & WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT)) {
22341+ accepted = accepted && preverify_ok;
22342+ }
2230022343 return accepted ? 1 : 0;
2230122344}
2230222345
0 commit comments