From 0527cf89fb907c330bc4fad3b135a1c85208fa9e Mon Sep 17 00:00:00 2001 From: Hans Leidekker Date: Thu, 9 Apr 2020 17:25:32 +0200 Subject: [PATCH] secur32: Add TLS application protocol negotiation support. Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- dlls/secur32/schannel.c | 25 ++++- dlls/secur32/schannel_gnutls.c | 112 +++++++++++++++++++ dlls/secur32/secur32_priv.h | 4 +- dlls/secur32/tests/schannel.c | 198 +++++++++++++++++++++++++++------ include/sspi.h | 26 +++++ 5 files changed, 323 insertions(+), 42 deletions(-) diff --git a/dlls/secur32/schannel.c b/dlls/secur32/schannel.c index 8063fd4d6b9..e76a3e46c34 100644 --- a/dlls/secur32/schannel.c +++ b/dlls/secur32/schannel.c @@ -786,6 +786,8 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW( struct schan_credentials *cred; SIZE_T expected_size = ~0UL; SECURITY_STATUS ret; + SecBuffer *buffer; + int idx; TRACE("%p %p %s 0x%08x %d %d %p %d %p %p %p %p\n", phCredential, phContext, debugstr_w(pszTargetName), fContextReq, Reserved1, TargetDataRep, pInput, @@ -842,6 +844,13 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW( heap_free( target ); } } + + if (pInput && (idx = schan_find_sec_buffer_idx(pInput, 0, SECBUFFER_APPLICATION_PROTOCOLS)) != -1) + { + buffer = &pInput->pBuffers[idx]; + schan_imp_set_application_protocols(ctx->session, buffer->pvBuffer, buffer->cbBuffer); + } + phNewContext->dwLower = handle; phNewContext->dwUpper = 0; } @@ -849,8 +858,6 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW( { SIZE_T record_size = 0; unsigned char *ptr; - SecBuffer *buffer; - int idx; if (!pInput) return SEC_E_INCOMPLETE_MESSAGE; @@ -1003,6 +1010,7 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( PCtxtHandle context_handle, ULONG attribute, PVOID buffer) { struct schan_context *ctx; + SECURITY_STATUS status; TRACE("context_handle %p, attribute %#x, buffer %p\n", context_handle, attribute, buffer); @@ -1015,7 +1023,7 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( case SECPKG_ATTR_STREAM_SIZES: { SecPkgContext_ConnectionInfo info; - SECURITY_STATUS status = schan_imp_get_connection_info(ctx->session, &info); + status = schan_imp_get_connection_info(ctx->session, &info); if (status == SEC_E_OK) { SecPkgContext_StreamSizes *stream_sizes = buffer; @@ -1039,7 +1047,7 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( case SECPKG_ATTR_KEY_INFO: { SecPkgContext_ConnectionInfo conn_info; - SECURITY_STATUS status = schan_imp_get_connection_info(ctx->session, &conn_info); + status = schan_imp_get_connection_info(ctx->session, &conn_info); if (status == SEC_E_OK) { SecPkgContext_KeyInfoW *info = buffer; @@ -1054,7 +1062,6 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( case SECPKG_ATTR_REMOTE_CERT_CONTEXT: { PCCERT_CONTEXT *cert = buffer; - SECURITY_STATUS status; status = ensure_remote_cert(ctx); if(status != SEC_E_OK) @@ -1075,7 +1082,6 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( ALG_ID hash_alg = CALG_SHA_256; BYTE hash[1024]; DWORD hash_size; - SECURITY_STATUS status; char *p; BOOL r; @@ -1109,6 +1115,11 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW( memcpy(p, hash, hash_size); return SEC_E_OK; } + case SECPKG_ATTR_APPLICATION_PROTOCOL: + { + SecPkgContext_ApplicationProtocol *protocol = buffer; + return schan_imp_get_application_protocol(ctx->session, protocol); + } default: FIXME("Unhandled attribute %#x\n", attribute); @@ -1143,6 +1154,8 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesA( return schan_QueryContextAttributesW(context_handle, attribute, buffer); case SECPKG_ATTR_ENDPOINT_BINDINGS: return schan_QueryContextAttributesW(context_handle, attribute, buffer); + case SECPKG_ATTR_APPLICATION_PROTOCOL: + return schan_QueryContextAttributesW(context_handle, attribute, buffer); default: FIXME("Unhandled attribute %#x\n", attribute); diff --git a/dlls/secur32/schannel_gnutls.c b/dlls/secur32/schannel_gnutls.c index ecc9f6bcfec..f177d90dfdb 100644 --- a/dlls/secur32/schannel_gnutls.c +++ b/dlls/secur32/schannel_gnutls.c @@ -50,6 +50,11 @@ WINE_DECLARE_DEBUG_CHANNEL(winediag); /* Not present in gnutls version < 2.9.10. */ static int (*pgnutls_cipher_get_block_size)(gnutls_cipher_algorithm_t); +/* Not present in gnutls version < 3.2.0. */ +static int (*pgnutls_alpn_get_selected_protocol)(gnutls_session_t, gnutls_datum_t *); +static int (*pgnutls_alpn_set_protocols)(gnutls_session_t, const gnutls_datum_t *, + unsigned, unsigned int); + /* Not present in gnutls version < 3.3.0. */ static int (*pgnutls_privkey_import_rsa_raw)(gnutls_privkey_t, const gnutls_datum_t *, const gnutls_datum_t *, const gnutls_datum_t *, @@ -114,6 +119,10 @@ MAKE_FUNCPTR(gnutls_x509_privkey_deinit); #define GNUTLS_KX_ECDHE_PSK 14 #endif +#if GNUTLS_VERSION_MAJOR < 3 || (GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR < 2) +#define GNUTLS_ALPN_SERVER_PRECEDENCE (1<<1) +#endif + static int compat_cipher_get_block_size(gnutls_cipher_algorithm_t cipher) { switch(cipher) { @@ -153,6 +162,19 @@ static int compat_gnutls_privkey_import_rsa_raw(gnutls_privkey_t key, const gnut return GNUTLS_E_UNKNOWN_PK_ALGORITHM; } +static int compat_gnutls_alpn_get_selected_protocol(gnutls_session_t session, gnutls_datum_t *protocol) +{ + FIXME("\n"); + return GNUTLS_E_INVALID_REQUEST; +} + +static int compat_gnutls_alpn_set_protocols(gnutls_session_t session, const gnutls_datum_t *protocols, + unsigned size, unsigned int flags) +{ + FIXME("\n"); + return GNUTLS_E_INVALID_REQUEST; +} + static ssize_t schan_pull_adapter(gnutls_transport_ptr_t transport, void *buff, size_t buff_len) { @@ -599,6 +621,86 @@ again: return SEC_E_OK; } +static unsigned int parse_alpn_protocol_list(unsigned char *buffer, unsigned int buflen, gnutls_datum_t *list) +{ + unsigned int len, offset = 0, count = 0; + + while (buflen) + { + len = buffer[offset++]; + buflen--; + if (!len || len > buflen) return 0; + if (list) + { + list[count].data = &buffer[offset]; + list[count].size = len; + } + buflen -= len; + offset += len; + count++; + } + + return count; +} + +void schan_imp_set_application_protocols(schan_imp_session session, unsigned char *buffer, unsigned int buflen) +{ + gnutls_session_t s = (gnutls_session_t)session; + unsigned int extension_len, extension, count = 0, offset = 0; + unsigned short list_len; + gnutls_datum_t *protocols; + int ret; + + if (sizeof(extension_len) > buflen) return; + extension_len = *(unsigned int *)&buffer[offset]; + offset += sizeof(extension_len); + + if (offset + sizeof(extension) > buflen) return; + extension = *(unsigned int *)&buffer[offset]; + if (extension != SecApplicationProtocolNegotiationExt_ALPN) + { + FIXME("extension %u not supported\n", extension); + return; + } + offset += sizeof(extension); + + if (offset + sizeof(list_len) > buflen) return; + list_len = *(unsigned short *)&buffer[offset]; + offset += sizeof(list_len); + + if (offset + list_len > buflen) return; + count = parse_alpn_protocol_list(&buffer[offset], list_len, NULL); + if (!count || !(protocols = heap_alloc(count * sizeof(*protocols)))) return; + + parse_alpn_protocol_list(&buffer[offset], list_len, protocols); + if ((ret = pgnutls_alpn_set_protocols(s, protocols, count, GNUTLS_ALPN_SERVER_PRECEDENCE) < 0)) + { + pgnutls_perror(ret); + } + + heap_free(protocols); +} + +SECURITY_STATUS schan_imp_get_application_protocol(schan_imp_session session, + SecPkgContext_ApplicationProtocol *protocol) +{ + gnutls_session_t s = (gnutls_session_t)session; + gnutls_datum_t selected; + + memset(protocol, 0, sizeof(*protocol)); + if (pgnutls_alpn_get_selected_protocol(s, &selected) < 0) return SEC_E_OK; + + if (selected.size <= sizeof(protocol->ProtocolId)) + { + protocol->ProtoNegoStatus = SecApplicationProtocolNegotiationStatus_Success; + protocol->ProtoNegoExt = SecApplicationProtocolNegotiationExt_ALPN; + protocol->ProtocolIdSize = selected.size; + memcpy(protocol->ProtocolId, selected.data, selected.size); + TRACE("returning %s\n", debugstr_an((const char *)selected.data, selected.size)); + } + return SEC_E_OK; +} + static WCHAR *get_key_container_path(const CERT_CONTEXT *ctx) { static const WCHAR rsabaseW[] = @@ -935,6 +1037,16 @@ BOOL schan_imp_init(void) WARN("gnutls_cipher_get_block_size not found\n"); pgnutls_cipher_get_block_size = compat_cipher_get_block_size; } + if (!(pgnutls_alpn_set_protocols = dlsym(libgnutls_handle, "gnutls_alpn_set_protocols"))) + { + WARN("gnutls_alpn_set_protocols not found\n"); + pgnutls_alpn_set_protocols = compat_gnutls_alpn_set_protocols; + } + if (!(pgnutls_alpn_get_selected_protocol = dlsym(libgnutls_handle, "gnutls_alpn_get_selected_protocol"))) + { + WARN("gnutls_alpn_get_selected_protocol not found\n"); + pgnutls_alpn_get_selected_protocol = compat_gnutls_alpn_get_selected_protocol; + } if (!(pgnutls_privkey_export_x509 = dlsym(libgnutls_handle, "gnutls_privkey_export_x509"))) { WARN("gnutls_privkey_export_x509 not found\n"); diff --git a/dlls/secur32/secur32_priv.h b/dlls/secur32/secur32_priv.h index 1bff1372b08..c34d1d32566 100644 --- a/dlls/secur32/secur32_priv.h +++ b/dlls/secur32/secur32_priv.h @@ -248,6 +248,8 @@ extern void schan_imp_free_certificate_credentials(schan_credentials*) DECLSPEC_ extern DWORD schan_imp_enabled_protocols(void) DECLSPEC_HIDDEN; extern BOOL schan_imp_init(void) DECLSPEC_HIDDEN; extern void schan_imp_deinit(void) DECLSPEC_HIDDEN; - +extern void schan_imp_set_application_protocols(schan_imp_session, unsigned char *, unsigned int) DECLSPEC_HIDDEN; +extern SECURITY_STATUS schan_imp_get_application_protocol(schan_imp_session, + SecPkgContext_ApplicationProtocol *) DECLSPEC_HIDDEN; #endif /* ndef __SECUR32_PRIV_H__ */ diff --git a/dlls/secur32/tests/schannel.c b/dlls/secur32/tests/schannel.c index 217dbe8dfaa..d51583d665f 100644 --- a/dlls/secur32/tests/schannel.c +++ b/dlls/secur32/tests/schannel.c @@ -670,14 +670,41 @@ static void test_InitializeSecurityContext(void) FreeCredentialsHandle(&cred_handle); } +static SOCKET create_ssl_socket( const char *hostname ) +{ + struct hostent *host; + struct sockaddr_in addr; + SOCKET sock; + + if (!(host = gethostbyname(hostname))) + { + skip("Can't resolve \"%s\"\n", hostname); + return -1; + } + + addr.sin_family = host->h_addrtype; + addr.sin_addr = *(struct in_addr *)host->h_addr_list[0]; + addr.sin_port = htons(443); + if ((sock = socket(host->h_addrtype, SOCK_STREAM, 0)) == -1) + { + skip("Can't create socket\n"); + return 1; + } + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + skip("Can't connect to \"%s\"\n", hostname); + closesocket(sock); + return -1; + } + + return sock; +} + static void test_communication(void) { int ret; - - WSADATA wsa_data; SOCKET sock; - struct hostent *host; - struct sockaddr_in addr; SECURITY_STATUS status; ULONG attrs; @@ -705,36 +732,7 @@ static void test_communication(void) } /* Create a socket and connect to test.winehq.org */ - ret = WSAStartup(0x0202, &wsa_data); - if (ret) - { - skip("Can't init winsock 2.2\n"); - return; - } - - host = gethostbyname("test.winehq.org"); - if (!host) - { - skip("Can't resolve test.winehq.org\n"); - return; - } - - addr.sin_family = host->h_addrtype; - addr.sin_addr = *(struct in_addr *)host->h_addr_list[0]; - addr.sin_port = htons(443); - sock = socket(host->h_addrtype, SOCK_STREAM, 0); - if (sock == SOCKET_ERROR) - { - skip("Can't create socket\n"); - return; - } - - ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); - if (ret == SOCKET_ERROR) - { - skip("Can't connect to test.winehq.org\n"); - return; - } + if ((sock = create_ssl_socket( "test.winehq.org" )) == -1) return; /* Create client credentials */ init_cred(&cred); @@ -952,7 +950,7 @@ todo_wine status = pQueryContextAttributesA(&context, SECPKG_ATTR_STREAM_SIZES, &sizes); ok(status == SEC_E_OK, "QueryContextAttributesW(SECPKG_ATTR_STREAM_SIZES) failed: %08x\n", status); - status = QueryContextAttributesA(&context, SECPKG_ATTR_NEGOTIATION_INFO, &info); + status = pQueryContextAttributesA(&context, SECPKG_ATTR_NEGOTIATION_INFO, &info); ok(status == SEC_E_UNSUPPORTED_FUNCTION, "QueryContextAttributesA returned %08x\n", status); reset_buffers(&buffers[0]); @@ -1037,12 +1035,142 @@ todo_wine closesocket(sock); } +static void test_application_protocol_negotiation(void) +{ + int ret; + SOCKET sock; + SECURITY_STATUS status; + ULONG attrs; + SCHANNEL_CRED cred; + CredHandle cred_handle; + CtxtHandle context; + SecPkgContext_ApplicationProtocol protocol; + SecBufferDesc buffers[3]; + SecBuffer *buf; + unsigned buf_size = 8192; + unsigned char *alpn_buffer; + unsigned int *extension_len; + unsigned short *list_len; + int list_start_index, offset = 0; + + if (!pQueryContextAttributesA) + { + win_skip("Required secur32 functions not available\n"); + return; + } + + if ((sock = create_ssl_socket( "test.winehq.org" )) == -1) return; + + init_cred(&cred); + cred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT; + cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS|SCH_CRED_MANUAL_CRED_VALIDATION; + + status = AcquireCredentialsHandleA(NULL, (SEC_CHAR *)UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, + &cred, NULL, NULL, &cred_handle, NULL); + ok(status == SEC_E_OK, "got %08x\n", status); + if (status != SEC_E_OK) return; + + init_buffers(&buffers[0], 4, buf_size); + init_buffers(&buffers[1], 4, buf_size); + init_buffers(&buffers[2], 1, 128); + + alpn_buffer = buffers[2].pBuffers[0].pvBuffer; + extension_len = (unsigned int *)&alpn_buffer[offset]; + offset += sizeof(*extension_len); + *(unsigned int *)&alpn_buffer[offset] = SecApplicationProtocolNegotiationExt_ALPN; + offset += sizeof(unsigned int); + list_len = (unsigned short *)&alpn_buffer[offset]; + offset += sizeof(*list_len); + list_start_index = offset; + + alpn_buffer[offset++] = sizeof("http/1.1") - 1; + memcpy(&alpn_buffer[offset], "http/1.1", sizeof("http/1.1") - 1); + offset += sizeof("http/1.1") - 1; + alpn_buffer[offset++] = sizeof("h2") - 1; + memcpy(&alpn_buffer[offset], "h2", sizeof("h2") - 1); + offset += sizeof("h2") - 1; + + *list_len = offset - list_start_index; + *extension_len = *list_len + sizeof(*extension_len) + sizeof(*list_len); + + buffers[2].pBuffers[0].BufferType = SECBUFFER_APPLICATION_PROTOCOLS; + buffers[2].pBuffers[0].cbBuffer = offset; + + buffers[0].pBuffers[0].BufferType = SECBUFFER_TOKEN; + status = InitializeSecurityContextA(&cred_handle, NULL, (SEC_CHAR *)"localhost", + ISC_REQ_CONFIDENTIALITY|ISC_REQ_STREAM, 0, 0, &buffers[2], 0, &context, &buffers[0], &attrs, NULL); + ok(status == SEC_I_CONTINUE_NEEDED, "got %08x\n", status); + + buf = &buffers[0].pBuffers[0]; + send(sock, buf->pvBuffer, buf->cbBuffer, 0); + buf->cbBuffer = buf_size; + + buf = &buffers[1].pBuffers[0]; + buf->cbBuffer = buf_size; + ret = receive_data(sock, buf); + if (ret == -1) + return; + + buffers[1].pBuffers[0].BufferType = SECBUFFER_TOKEN; + status = InitializeSecurityContextA(&cred_handle, &context, (SEC_CHAR *)"localhost", + ISC_REQ_CONFIDENTIALITY|ISC_REQ_STREAM|ISC_REQ_USE_SUPPLIED_CREDS, 0, 0, &buffers[1], 0, NULL, + &buffers[0], &attrs, NULL); + buffers[1].pBuffers[0].cbBuffer = buf_size; + while (status == SEC_I_CONTINUE_NEEDED) + { + buf = &buffers[0].pBuffers[0]; + send(sock, buf->pvBuffer, buf->cbBuffer, 0); + buf->cbBuffer = buf_size; + + buf = &buffers[1].pBuffers[0]; + ret = receive_data(sock, buf); + if (ret == -1) + return; + + buf->BufferType = SECBUFFER_TOKEN; + status = InitializeSecurityContextA(&cred_handle, &context, (SEC_CHAR *)"localhost", + ISC_REQ_USE_SUPPLIED_CREDS, 0, 0, &buffers[1], 0, NULL, &buffers[0], &attrs, NULL); + buffers[1].pBuffers[0].cbBuffer = buf_size; + } + + ok (status == SEC_E_OK || broken(status == SEC_E_ILLEGAL_MESSAGE) /* winxp */, "got %08x\n", status); + if (status != SEC_E_OK) + { + skip("Handshake failed\n"); + return; + } + + memset(&protocol, 0, sizeof(protocol)); + status = pQueryContextAttributesA(&context, SECPKG_ATTR_APPLICATION_PROTOCOL, &protocol); + ok(status == SEC_E_OK || broken(status == SEC_E_UNSUPPORTED_FUNCTION) /* win2k8 */, "got %08x\n", status); + if (status == SEC_E_OK) + { + ok(protocol.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success, "got %u\n", protocol.ProtoNegoStatus); + ok(protocol.ProtoNegoExt == SecApplicationProtocolNegotiationExt_ALPN, "got %u\n", protocol.ProtoNegoExt); + ok(protocol.ProtocolIdSize == 8, "got %u\n", protocol.ProtocolIdSize); + ok(!memcmp(protocol.ProtocolId, "http/1.1", 8), "wrong protocol id\n"); + } + + DeleteSecurityContext(&context); + FreeCredentialsHandle(&cred_handle); + + free_buffers(&buffers[0]); + free_buffers(&buffers[1]); + free_buffers(&buffers[2]); + + closesocket(sock); +} + START_TEST(schannel) { + WSADATA wsa_data; pQueryContextAttributesA = (void*)GetProcAddress(GetModuleHandleA("secur32.dll"), "QueryContextAttributesA"); + WSAStartup(0x0202, &wsa_data); + test_cread_attrs(); testAcquireSecurityContext(); test_InitializeSecurityContext(); test_communication(); + test_application_protocol_negotiation(); } diff --git a/include/sspi.h b/include/sspi.h index 036d2cf2f82..e7371f129de 100644 --- a/include/sspi.h +++ b/include/sspi.h @@ -201,6 +201,7 @@ typedef struct _SecBuffer #define SECBUFFER_MECHLIST_SIGNATURE 12 #define SECBUFFER_TARGET 13 #define SECBUFFER_CHANNEL_BINDINGS 14 +#define SECBUFFER_APPLICATION_PROTOCOLS 18 #define SECBUFFER_ATTRMASK 0xf0000000 #define SECBUFFER_READONLY 0x80000000 @@ -497,6 +498,7 @@ typedef SECURITY_STATUS (SEC_ENTRY *QUERY_CONTEXT_ATTRIBUTES_FN_W)(PCtxtHandle, #define SECPKG_ATTR_NEGO_PKG_INFO 31 #define SECPKG_ATTR_NEGO_STATUS 32 #define SECPKG_ATTR_CONTEXT_DELETED 33 +#define SECPKG_ATTR_APPLICATION_PROTOCOL 35 #define SECPKG_ATTR_SUBJECT_SECURITY_ATTRIBUTES 128 #define SECPKG_ATTR_NEGO_INFO_FLAG_NO_KERBEROS 0x1 @@ -712,6 +714,30 @@ typedef struct _SecPkgContext_Bindings SEC_CHANNEL_BINDINGS *Bindings; } SecPkgContext_Bindings, *PSecPkgContext_Bindings; +typedef enum _SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS +{ + SecApplicationProtocolNegotiationStatus_None, + SecApplicationProtocolNegotiationStatus_Success, + SecApplicationProtocolNegotiationStatus_SelectedClientOnly +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS, *PSEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS; + +typedef enum _SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT +{ + SecApplicationProtocolNegotiationExt_None, + SecApplicationProtocolNegotiationExt_NPN, + SecApplicationProtocolNegotiationExt_ALPN +} SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT, *PSEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT; + +#define MAX_PROTOCOL_ID_SIZE 0xff + +typedef struct _SecPkgContext_ApplicationProtocol +{ + SEC_APPLICATION_PROTOCOL_NEGOTIATION_STATUS ProtoNegoStatus; + SEC_APPLICATION_PROTOCOL_NEGOTIATION_EXT ProtoNegoExt; + unsigned char ProtocolIdSize; + unsigned char ProtocolId[MAX_PROTOCOL_ID_SIZE]; +} SecPkgContext_ApplicationProtocol, *PSecPkgContext_ApplicationProtocol; + SECURITY_STATUS SEC_ENTRY ImpersonateSecurityContext(PCtxtHandle phContext); typedef SECURITY_STATUS (SEC_ENTRY *IMPERSONATE_SECURITY_CONTEXT_FN)