From 6ea10df6ec054049f4ad8e3ab77b3eab33290f09 Mon Sep 17 00:00:00 2001 From: Maokaman1 Date: Tue, 5 May 2026 06:27:03 +0300 Subject: [PATCH] net.http: support Schannel validation modes (#27092) --- thirdparty/vschannel/vschannel.c | 81 ++++++++++--------- thirdparty/vschannel/vschannel.h | 2 +- vlib/builtin/cfns.c.v | 2 +- vlib/net/http/backend.c.v | 6 +- vlib/net/http/backend_vschannel_windows.c.v | 3 +- .../http/vschannel_validation_windows_test.v | 60 ++++++++++++++ 6 files changed, 109 insertions(+), 45 deletions(-) create mode 100644 vlib/net/http/vschannel_validation_windows_test.v diff --git a/thirdparty/vschannel/vschannel.c b/thirdparty/vschannel/vschannel.c index 3fe3f366e..e2e13de2d 100644 --- a/thirdparty/vschannel/vschannel.c +++ b/thirdparty/vschannel/vschannel.c @@ -25,6 +25,7 @@ struct TlsContext { CtxtHandle h_context; PCCERT_CONTEXT p_pemote_cert_context; INT last_error_code; + BOOL validate_server_certificate; BOOL creds_initialized; BOOL context_initialized; }; @@ -34,6 +35,7 @@ TlsContext new_tls_context() { .cert_store = NULL, .last_error_code = 0, .socket = INVALID_SOCKET, + .validate_server_certificate = TRUE, .creds_initialized = FALSE, .context_initialized = FALSE, .p_pemote_cert_context = NULL @@ -84,8 +86,9 @@ void vschannel_cleanup(TlsContext *tls_ctx) { } } -void vschannel_init(TlsContext *tls_ctx) { +void vschannel_init(TlsContext *tls_ctx, BOOL validate_server_certificate) { tls_ctx->sspi = InitSecurityInterface(); + tls_ctx->validate_server_certificate = validate_server_certificate; if(tls_ctx->sspi == NULL) { wprintf(L"Error 0x%x reading security interface.\n", @@ -133,38 +136,40 @@ INT request(TlsContext *tls_ctx, INT iport, LPWSTR host, CHAR *req, DWORD req_le } tls_ctx->context_initialized = TRUE; - // Authenticate server's credentials. + if(tls_ctx->validate_server_certificate) { + // Authenticate server's credentials. - // Get server's certificate. - Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - (PVOID)&tls_ctx->p_pemote_cert_context); - if(Status != SEC_E_OK) { - vschannel_set_last_error(tls_ctx, Status); - wprintf(L"Error 0x%x querying remote certificate\n", Status); - vschannel_cleanup(tls_ctx); - return resp_length; - } + // Get server's certificate. + Status = tls_ctx->sspi->QueryContextAttributes(&tls_ctx->h_context, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + (PVOID)&tls_ctx->p_pemote_cert_context); + if(Status != SEC_E_OK) { + vschannel_set_last_error(tls_ctx, Status); + wprintf(L"Error 0x%x querying remote certificate\n", Status); + vschannel_cleanup(tls_ctx); + return resp_length; + } - // Attempt to validate server certificate. - Status = verify_server_certificate(tls_ctx->p_pemote_cert_context, host,0); - if(Status) { - vschannel_set_last_error(tls_ctx, Status); - // The server certificate did not validate correctly. At this - // point, we cannot tell if we are connecting to the correct - // server, or if we are connecting to a "man in the middle" - // attack server. + // Attempt to validate server certificate. + Status = verify_server_certificate(tls_ctx->p_pemote_cert_context, host,0); + if(Status) { + vschannel_set_last_error(tls_ctx, Status); + // The server certificate did not validate correctly. At this + // point, we cannot tell if we are connecting to the correct + // server, or if we are connecting to a "man in the middle" + // attack server. - // It is therefore best if we abort the connection. + // It is therefore best if we abort the connection. - wprintf(L"Error 0x%x authenticating server credentials!\n", Status); - vschannel_cleanup(tls_ctx); - return resp_length; - } + wprintf(L"Error 0x%x authenticating server credentials!\n", Status); + vschannel_cleanup(tls_ctx); + return resp_length; + } - // Free the server certificate context. - CertFreeCertificateContext(tls_ctx->p_pemote_cert_context); - tls_ctx->p_pemote_cert_context = NULL; + // Free the server certificate context. + CertFreeCertificateContext(tls_ctx->p_pemote_cert_context); + tls_ctx->p_pemote_cert_context = NULL; + } // Request from server Status = https_make_request(tls_ctx, req, req_len, out, &resp_length, afn); @@ -239,15 +244,10 @@ static SECURITY_STATUS create_credentials(TlsContext *tls_ctx) { } tls_ctx->schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; + tls_ctx->schannel_cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; - // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because - // this sample verifies the server certificate manually. - // Applications that expect to run on WinNT, Win9x, or WinME - // should specify this flag and also manually verify the server - // certificate. Applications running on newer versions of Windows can - // leave off this flag, in which case the InitializeSecurityContext - // function will validate the server certificate automatically. - // tls_ctx->schannel_cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + // Keep certificate validation under the caller's control. The validated + // path runs explicit hostname/chain validation after the handshake. // Create an SSPI credential. @@ -986,7 +986,13 @@ static DWORD verify_server_certificate( PCCERT_CONTEXT pServerCert, LPWSTR host ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; - if(!CertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext)) { + // Best-effort TLS revocation check: detect a positively revoked leaf + // certificate, but let policy evaluation ignore unknown/offline status. + if(!CertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, + CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_END_CERT | + CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT, + NULL, &pChainContext)) { Status = GetLastError(); wprintf(L"Error 0x%x returned by CertGetCertificateChain!\n", Status); goto cleanup; @@ -1001,6 +1007,7 @@ static DWORD verify_server_certificate( PCCERT_CONTEXT pServerCert, LPWSTR host memset(&PolicyPara, 0, sizeof(PolicyPara)); PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; PolicyPara.pvExtraPolicyPara = &polHttps; memset(&PolicyStatus, 0, sizeof(PolicyStatus)); diff --git a/thirdparty/vschannel/vschannel.h b/thirdparty/vschannel/vschannel.h index 9c43a0d8c..b3b229f40 100644 --- a/thirdparty/vschannel/vschannel.h +++ b/thirdparty/vschannel/vschannel.h @@ -26,7 +26,7 @@ typedef struct TlsContext TlsContext; TlsContext new_tls_context(); -static void vschannel_init(TlsContext *tls_ctx); +static void vschannel_init(TlsContext *tls_ctx, BOOL validate_server_certificate); static void vschannel_cleanup(TlsContext *tls_ctx); diff --git a/vlib/builtin/cfns.c.v b/vlib/builtin/cfns.c.v index 6ebfbbe5c..ad9531b3a 100644 --- a/vlib/builtin/cfns.c.v +++ b/vlib/builtin/cfns.c.v @@ -428,7 +428,7 @@ fn C.WSAGetLastError() i32 fn C.closesocket(i32) i32 -fn C.vschannel_init(&C.TlsContext) +fn C.vschannel_init(&C.TlsContext, C.BOOL) fn C.request(&C.TlsContext, i32, &u16, &u8, u32, &&u8, fn (voidptr, isize) voidptr) i32 diff --git a/vlib/net/http/backend.c.v b/vlib/net/http/backend.c.v index c9872dd22..ffc9ab848 100644 --- a/vlib/net/http/backend.c.v +++ b/vlib/net/http/backend.c.v @@ -8,11 +8,7 @@ import strings fn (req &Request) ssl_do(port int, method Method, host_name string, path string, data string, header Header) !Response { $if windows && !no_vschannel ? { - if req.validate { - return vschannel_ssl_do(req, port, method, host_name, path, data, header) - } - // vschannel enforces certificate validation during handshake. - // Use net.ssl when validation is explicitly disabled. + return vschannel_ssl_do(req, port, method, host_name, path, data, header) } return net_ssl_do(req, port, method, host_name, path, data, header) } diff --git a/vlib/net/http/backend_vschannel_windows.c.v b/vlib/net/http/backend_vschannel_windows.c.v index f9959e35e..cf4c35f38 100644 --- a/vlib/net/http/backend_vschannel_windows.c.v +++ b/vlib/net/http/backend_vschannel_windows.c.v @@ -15,12 +15,13 @@ const C.vsc_init_resp_buff_size int fn C.new_tls_context() C.TlsContext fn C.vschannel_use_tls12_client_protocol() +fn C.vschannel_init(tls_ctx &C.TlsContext, validate_server_certificate C.BOOL) fn C.vschannel_last_error(tls_ctx &C.TlsContext) int fn vschannel_ssl_do(req &Request, port int, method Method, host_name string, path string, data string, header Header) !Response { mut ctx := C.new_tls_context() C.vschannel_use_tls12_client_protocol() - C.vschannel_init(&ctx) + C.vschannel_init(&ctx, C.BOOL(if req.validate { 1 } else { 0 })) mut buff := unsafe { malloc_noscan(C.vsc_init_resp_buff_size) } addr := host_name sdata := req.build_request_headers_with(method, host_name, port, path, data, header) diff --git a/vlib/net/http/vschannel_validation_windows_test.v b/vlib/net/http/vschannel_validation_windows_test.v new file mode 100644 index 000000000..f0bf6c24d --- /dev/null +++ b/vlib/net/http/vschannel_validation_windows_test.v @@ -0,0 +1,60 @@ +module http + +import net +import net.mbedtls + +const vschannel_test_cert_path = @VEXEROOT + + '/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.crt' +const vschannel_test_key_path = @VEXEROOT + + '/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.key' + +fn start_vschannel_test_https_server() !(int, thread) { + mut port_listener := net.listen_tcp(.ip, '127.0.0.1:0')! + port := port_listener.addr()!.port()! + port_listener.close()! + mut listener := mbedtls.new_ssl_listener('127.0.0.1:${port}', mbedtls.SSLConnectConfig{ + cert: vschannel_test_cert_path + cert_key: vschannel_test_key_path + validate: false + })! + return port, spawn serve_vschannel_test_https_once(mut listener) +} + +fn serve_vschannel_test_https_once(mut listener mbedtls.SSLListener) { + defer { + listener.shutdown() or {} + } + mut conn := listener.accept() or { return } + defer { + conn.shutdown() or {} + } + mut request_buf := []u8{len: 2048} + _ = conn.read(mut request_buf) or { return } + conn.write_string('HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok') or { + return + } +} + +fn test_vschannel_accepts_self_signed_certificate_when_validation_is_disabled() { + port, server := start_vschannel_test_https_server()! + resp := fetch( + url: 'https://127.0.0.1:${port}/' + validate: false + )! + server.wait() + assert resp.status_code == 200 + assert resp.body == 'ok' +} + +fn test_vschannel_rejects_self_signed_certificate_when_validation_is_enabled() { + port, server := start_vschannel_test_https_server()! + fetch( + url: 'https://127.0.0.1:${port}/' + validate: true + ) or { + server.wait() + return + } + server.wait() + assert false, 'expected certificate validation to reject the self-signed certificate' +} -- 2.39.5