secur32: Add TLS application protocol negotiation support.

Signed-off-by: Hans Leidekker <hans@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Hans Leidekker 2020-04-09 17:25:32 +02:00 committed by Alexandre Julliard
parent 86d20a47f4
commit 0527cf89fb
5 changed files with 323 additions and 42 deletions

View File

@ -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);

View File

@ -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");

View File

@ -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__ */

View File

@ -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();
}

View File

@ -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)