secur32: Add support for client certificate authentication.

Signed-off-by: Hans Leidekker <hans@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Hans Leidekker 2019-02-07 10:52:11 +01:00 committed by Alexandre Julliard
parent 88b20b2dee
commit 16d9f62bdf
4 changed files with 344 additions and 55 deletions

View file

@ -338,26 +338,26 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryCredentialsAttributesW(
return ret;
}
static SECURITY_STATUS schan_CheckCreds(const SCHANNEL_CRED *schanCred)
static SECURITY_STATUS get_cert(const SCHANNEL_CRED *cred, CERT_CONTEXT const **cert)
{
SECURITY_STATUS st;
SECURITY_STATUS status;
DWORD i;
TRACE("dwVersion = %d\n", schanCred->dwVersion);
TRACE("cCreds = %d\n", schanCred->cCreds);
TRACE("hRootStore = %p\n", schanCred->hRootStore);
TRACE("cMappers = %d\n", schanCred->cMappers);
TRACE("cSupportedAlgs = %d:\n", schanCred->cSupportedAlgs);
for (i = 0; i < schanCred->cSupportedAlgs; i++)
TRACE("%08x\n", schanCred->palgSupportedAlgs[i]);
TRACE("grbitEnabledProtocols = %08x\n", schanCred->grbitEnabledProtocols);
TRACE("dwMinimumCipherStrength = %d\n", schanCred->dwMinimumCipherStrength);
TRACE("dwMaximumCipherStrength = %d\n", schanCred->dwMaximumCipherStrength);
TRACE("dwSessionLifespan = %d\n", schanCred->dwSessionLifespan);
TRACE("dwFlags = %08x\n", schanCred->dwFlags);
TRACE("dwCredFormat = %d\n", schanCred->dwCredFormat);
TRACE("dwVersion = %u\n", cred->dwVersion);
TRACE("cCreds = %u\n", cred->cCreds);
TRACE("paCred = %p\n", cred->paCred);
TRACE("hRootStore = %p\n", cred->hRootStore);
TRACE("cMappers = %u\n", cred->cMappers);
TRACE("cSupportedAlgs = %u:\n", cred->cSupportedAlgs);
for (i = 0; i < cred->cSupportedAlgs; i++) TRACE("%08x\n", cred->palgSupportedAlgs[i]);
TRACE("grbitEnabledProtocols = %08x\n", cred->grbitEnabledProtocols);
TRACE("dwMinimumCipherStrength = %u\n", cred->dwMinimumCipherStrength);
TRACE("dwMaximumCipherStrength = %u\n", cred->dwMaximumCipherStrength);
TRACE("dwSessionLifespan = %u\n", cred->dwSessionLifespan);
TRACE("dwFlags = %08x\n", cred->dwFlags);
TRACE("dwCredFormat = %u\n", cred->dwCredFormat);
switch (schanCred->dwVersion)
switch (cred->dwVersion)
{
case SCH_CRED_V3:
case SCHANNEL_CRED_VERSION:
@ -366,29 +366,24 @@ static SECURITY_STATUS schan_CheckCreds(const SCHANNEL_CRED *schanCred)
return SEC_E_INTERNAL_ERROR;
}
if (schanCred->cCreds == 0)
st = SEC_E_NO_CREDENTIALS;
else if (schanCred->cCreds > 1)
st = SEC_E_UNKNOWN_CREDENTIALS;
if (!cred->cCreds) status = SEC_E_NO_CREDENTIALS;
else if (cred->cCreds > 1) status = SEC_E_UNKNOWN_CREDENTIALS;
else
{
DWORD keySpec;
HCRYPTPROV csp;
BOOL ret, freeCSP;
DWORD spec;
HCRYPTPROV prov;
BOOL free;
ret = CryptAcquireCertificatePrivateKey(schanCred->paCred[0],
0, /* FIXME: what flags to use? */ NULL,
&csp, &keySpec, &freeCSP);
if (ret)
if (CryptAcquireCertificatePrivateKey(cred->paCred[0], CRYPT_ACQUIRE_CACHE_FLAG, NULL, &prov, &spec, &free))
{
st = SEC_E_OK;
if (freeCSP)
CryptReleaseContext(csp, 0);
if (free) CryptReleaseContext(prov, 0);
*cert = cred->paCred[0];
status = SEC_E_OK;
}
else
st = SEC_E_UNKNOWN_CREDENTIALS;
else status = SEC_E_UNKNOWN_CREDENTIALS;
}
return st;
return status;
}
static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schanCred,
@ -397,17 +392,18 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
struct schan_credentials *creds;
unsigned enabled_protocols;
ULONG_PTR handle;
SECURITY_STATUS st = SEC_E_OK;
SECURITY_STATUS status = SEC_E_OK;
const CERT_CONTEXT *cert = NULL;
TRACE("schanCred %p, phCredential %p, ptsExpiry %p\n", schanCred, phCredential, ptsExpiry);
if (schanCred)
{
st = schan_CheckCreds(schanCred);
if (st != SEC_E_OK && st != SEC_E_NO_CREDENTIALS)
return st;
status = get_cert(schanCred, &cert);
if (status != SEC_E_OK && status != SEC_E_NO_CREDENTIALS)
return status;
st = SEC_E_OK;
status = SEC_E_OK;
}
read_config();
@ -420,9 +416,6 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
return SEC_E_NO_AUTHENTICATING_AUTHORITY;
}
/* For now, the only thing I'm interested in is the direction of the
* connection, so just store it.
*/
creds = heap_alloc(sizeof(*creds));
if (!creds) return SEC_E_INSUFFICIENT_MEMORY;
@ -430,7 +423,7 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
if (handle == SCHAN_INVALID_HANDLE) goto fail;
creds->credential_use = SECPKG_CRED_OUTBOUND;
if (!schan_imp_allocate_certificate_credentials(creds))
if (!schan_imp_allocate_certificate_credentials(creds, cert))
{
schan_free_handle(handle, SCHAN_HANDLE_CRED);
goto fail;
@ -447,7 +440,7 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
ptsExpiry->HighPart = 0;
}
return st;
return status;
fail:
heap_free(creds);
@ -457,14 +450,15 @@ fail:
static SECURITY_STATUS schan_AcquireServerCredentials(const SCHANNEL_CRED *schanCred,
PCredHandle phCredential, PTimeStamp ptsExpiry)
{
SECURITY_STATUS st;
SECURITY_STATUS status;
const CERT_CONTEXT *cert = NULL;
TRACE("schanCred %p, phCredential %p, ptsExpiry %p\n", schanCred, phCredential, ptsExpiry);
if (!schanCred) return SEC_E_NO_CREDENTIALS;
st = schan_CheckCreds(schanCred);
if (st == SEC_E_OK)
status = get_cert(schanCred, &cert);
if (status == SEC_E_OK)
{
ULONG_PTR handle;
struct schan_credentials *creds;
@ -485,7 +479,7 @@ static SECURITY_STATUS schan_AcquireServerCredentials(const SCHANNEL_CRED *schan
/* FIXME: get expiry from cert */
}
return st;
return status;
}
static SECURITY_STATUS schan_AcquireCredentialsHandle(ULONG fCredentialUse,

View file

@ -24,18 +24,24 @@
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#ifdef SONAME_LIBGNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/abstract.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "sspi.h"
#include "schannel.h"
#include "lmcons.h"
#include "winreg.h"
#include "secur32_priv.h"
#include "wine/debug.h"
#include "wine/library.h"
#include "wine/unicode.h"
#if defined(SONAME_LIBGNUTLS) && !defined(HAVE_SECURITY_SECURITY_H)
@ -43,7 +49,10 @@ WINE_DEFAULT_DEBUG_CHANNEL(secur32);
WINE_DECLARE_DEBUG_CHANNEL(winediag);
/* Not present in gnutls version < 2.9.10. */
static int (*pgnutls_cipher_get_block_size)(gnutls_cipher_algorithm_t algorithm);
static int (*pgnutls_cipher_get_block_size)(gnutls_cipher_algorithm_t);
/* Not present in gnutls version < 3.4.0. */
static int (*pgnutls_privkey_export_x509)(gnutls_privkey_t, gnutls_x509_privkey_t *);
static void *libgnutls_handle;
#define MAKE_FUNCPTR(f) static typeof(f) * p##f
@ -52,6 +61,7 @@ MAKE_FUNCPTR(gnutls_alert_get_name);
MAKE_FUNCPTR(gnutls_certificate_allocate_credentials);
MAKE_FUNCPTR(gnutls_certificate_free_credentials);
MAKE_FUNCPTR(gnutls_certificate_get_peers);
MAKE_FUNCPTR(gnutls_certificate_set_x509_key);
MAKE_FUNCPTR(gnutls_cipher_get);
MAKE_FUNCPTR(gnutls_cipher_get_key_size);
MAKE_FUNCPTR(gnutls_credentials_set);
@ -68,6 +78,9 @@ MAKE_FUNCPTR(gnutls_mac_get_key_size);
MAKE_FUNCPTR(gnutls_perror);
MAKE_FUNCPTR(gnutls_protocol_get_version);
MAKE_FUNCPTR(gnutls_priority_set_direct);
MAKE_FUNCPTR(gnutls_privkey_deinit);
MAKE_FUNCPTR(gnutls_privkey_import_rsa_raw);
MAKE_FUNCPTR(gnutls_privkey_init);
MAKE_FUNCPTR(gnutls_record_get_max_size);
MAKE_FUNCPTR(gnutls_record_recv);
MAKE_FUNCPTR(gnutls_record_send);
@ -77,6 +90,10 @@ MAKE_FUNCPTR(gnutls_transport_set_errno);
MAKE_FUNCPTR(gnutls_transport_set_ptr);
MAKE_FUNCPTR(gnutls_transport_set_pull_function);
MAKE_FUNCPTR(gnutls_transport_set_push_function);
MAKE_FUNCPTR(gnutls_x509_crt_deinit);
MAKE_FUNCPTR(gnutls_x509_crt_import);
MAKE_FUNCPTR(gnutls_x509_crt_init);
MAKE_FUNCPTR(gnutls_x509_privkey_deinit);
#undef MAKE_FUNCPTR
#if GNUTLS_VERSION_MAJOR < 3
@ -115,6 +132,12 @@ static int compat_cipher_get_block_size(gnutls_cipher_algorithm_t cipher)
}
}
static int compat_gnutls_privkey_export_x509(gnutls_privkey_t privkey, gnutls_x509_privkey_t *key)
{
FIXME("\n");
return GNUTLS_E_UNKNOWN_PK_ALGORITHM;
}
static ssize_t schan_pull_adapter(gnutls_transport_ptr_t transport,
void *buff, size_t buff_len)
{
@ -556,12 +579,271 @@ again:
return SEC_E_OK;
}
BOOL schan_imp_allocate_certificate_credentials(schan_credentials *c)
static WCHAR *get_key_container_path(const CERT_CONTEXT *ctx)
{
int ret = pgnutls_certificate_allocate_credentials((gnutls_certificate_credentials_t*)&c->credentials);
if (ret != GNUTLS_E_SUCCESS)
static const WCHAR rsabaseW[] =
{'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','C','r','y','p','t','o','\\','R','S','A','\\',0};
DWORD size;
CERT_KEY_CONTEXT keyctx;
CRYPT_KEY_PROV_INFO *prov;
WCHAR username[UNLEN + 1], *ret = NULL;
DWORD len = ARRAY_SIZE(username);
size = sizeof(keyctx);
if (CertGetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, &keyctx, &size))
{
char *str;
if (!CryptGetProvParam(keyctx.hCryptProv, PP_CONTAINER, NULL, &size, 0)) return NULL;
if (!(str = heap_alloc(size))) return NULL;
if (!CryptGetProvParam(keyctx.hCryptProv, PP_CONTAINER, (BYTE *)str, &size, 0)) return NULL;
len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
if (!(ret = heap_alloc(sizeof(rsabaseW) + len * sizeof(WCHAR))))
{
heap_free(str);
return NULL;
}
strcpyW(ret, rsabaseW);
MultiByteToWideChar(CP_ACP, 0, str, -1, ret + strlenW(ret), len);
heap_free(str);
}
else
{
size = 0;
if (!CertGetCertificateContextProperty(ctx, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size)) return NULL;
if (!(prov = heap_alloc(size))) return NULL;
if (!CertGetCertificateContextProperty(ctx, CERT_KEY_PROV_INFO_PROP_ID, prov, &size))
{
heap_free(prov);
return NULL;
}
if (!(ret = heap_alloc(sizeof(rsabaseW) + strlenW(prov->pwszContainerName) * sizeof(WCHAR))))
{
heap_free(prov);
return NULL;
}
strcpyW(ret, rsabaseW);
strcatW(ret, prov->pwszContainerName);
heap_free(prov);
}
if (!ret && GetUserNameW(username, &len) && (ret = heap_alloc(sizeof(rsabaseW) + len * sizeof(WCHAR))))
{
strcpyW(ret, rsabaseW);
strcatW(ret, username);
}
return ret;
}
#define MAX_LEAD_BYTES 8
static BYTE *get_key_blob(const CERT_CONTEXT *ctx, ULONG *size)
{
static const WCHAR keyexchangeW[] =
{'K','e','y','E','x','c','h','a','n','g','e','K','e','y','P','a','i','r',0};
static const WCHAR signatureW[] =
{'S','i','g','n','a','t','u','r','e','K','e','y','P','a','i','r',0};
BYTE *buf, *ret = NULL;
DATA_BLOB blob_in, blob_out;
DWORD spec = 0, type, len;
WCHAR *path;
HKEY hkey;
if (!(path = get_key_container_path(ctx))) return NULL;
if (RegOpenKeyExW(HKEY_CURRENT_USER, path, 0, KEY_READ, &hkey))
{
heap_free(path);
return NULL;
}
if (!RegQueryValueExW(hkey, keyexchangeW, 0, &type, NULL, &len)) spec = AT_KEYEXCHANGE;
else if (!RegQueryValueExW(hkey, signatureW, 0, &type, NULL, &len)) spec = AT_SIGNATURE;
else
{
RegCloseKey(hkey);
return NULL;
}
if (!(buf = heap_alloc(len + MAX_LEAD_BYTES)))
{
RegCloseKey(hkey);
return NULL;
}
if (!RegQueryValueExW(hkey, (spec == AT_KEYEXCHANGE) ? keyexchangeW : signatureW, 0, &type, buf, &len))
{
blob_in.pbData = buf;
blob_in.cbData = len;
if (CryptUnprotectData(&blob_in, NULL, NULL, NULL, NULL, 0, &blob_out))
{
assert(blob_in.cbData >= blob_out.cbData);
memcpy(buf, blob_out.pbData, blob_out.cbData);
LocalFree(blob_out.pbData);
*size = blob_out.cbData + MAX_LEAD_BYTES;
ret = buf;
}
}
else heap_free(buf);
RegCloseKey(hkey);
heap_free(path);
return ret;
}
static inline void reverse_bytes(BYTE *buf, ULONG len)
{
BYTE tmp;
ULONG i;
for (i = 0; i < len / 2; i++)
{
tmp = buf[i];
buf[i] = buf[len - i - 1];
buf[len - i - 1] = tmp;
}
}
static ULONG set_component(gnutls_datum_t *comp, BYTE *data, ULONG len, ULONG *buflen)
{
comp->data = data;
comp->size = len;
reverse_bytes(comp->data, comp->size);
if (comp->data[0] & 0x80) /* add leading 0 byte if most significant bit is set */
{
memmove(comp->data + 1, comp->data, *buflen);
comp->data[0] = 0;
comp->size++;
}
*buflen -= comp->size;
return comp->size;
}
static gnutls_x509_privkey_t get_x509_key(const CERT_CONTEXT *ctx)
{
gnutls_privkey_t key = NULL;
gnutls_x509_privkey_t x509key = NULL;
gnutls_datum_t m, e, d, p, q, u, e1, e2;
BYTE *ptr, *buffer;
RSAPUBKEY *rsakey;
DWORD size;
int ret;
if (!(buffer = get_key_blob(ctx, &size))) return NULL;
if (size < sizeof(BLOBHEADER)) goto done;
rsakey = (RSAPUBKEY *)(buffer + sizeof(BLOBHEADER));
TRACE("RSA key bitlen %u pubexp %u\n", rsakey->bitlen, rsakey->pubexp);
size -= sizeof(BLOBHEADER) + FIELD_OFFSET(RSAPUBKEY, pubexp);
set_component(&e, (BYTE *)&rsakey->pubexp, sizeof(rsakey->pubexp), &size);
ptr = (BYTE *)(rsakey + 1);
ptr += set_component(&m, ptr, rsakey->bitlen / 8, &size);
ptr += set_component(&p, ptr, rsakey->bitlen / 16, &size);
ptr += set_component(&q, ptr, rsakey->bitlen / 16, &size);
ptr += set_component(&e1, ptr, rsakey->bitlen / 16, &size);
ptr += set_component(&e2, ptr, rsakey->bitlen / 16, &size);
ptr += set_component(&u, ptr, rsakey->bitlen / 16, &size);
ptr += set_component(&d, ptr, rsakey->bitlen / 8, &size);
if ((ret = pgnutls_privkey_init(&key)) < 0)
{
pgnutls_perror(ret);
return (ret == GNUTLS_E_SUCCESS);
goto done;
}
if ((ret = pgnutls_privkey_import_rsa_raw(key, &m, &e, &d, &p, &q, &u, &e1, &e2)) < 0)
{
pgnutls_perror(ret);
goto done;
}
if ((ret = pgnutls_privkey_export_x509(key, &x509key)) < 0)
{
pgnutls_perror(ret);
}
done:
heap_free(buffer);
pgnutls_privkey_deinit(key);
return x509key;
}
static gnutls_x509_crt_t get_x509_crt(const CERT_CONTEXT *ctx)
{
gnutls_datum_t data;
gnutls_x509_crt_t crt;
int ret;
if (!ctx) return FALSE;
if (ctx->dwCertEncodingType != X509_ASN_ENCODING)
{
FIXME("encoding type %u not supported\n", ctx->dwCertEncodingType);
return NULL;
}
if ((ret = pgnutls_x509_crt_init(&crt)) < 0)
{
pgnutls_perror(ret);
return NULL;
}
data.data = ctx->pbCertEncoded;
data.size = ctx->cbCertEncoded;
if ((ret = pgnutls_x509_crt_import(crt, &data, GNUTLS_X509_FMT_DER)) < 0)
{
pgnutls_perror(ret);
pgnutls_x509_crt_deinit(crt);
return NULL;
}
return crt;
}
BOOL schan_imp_allocate_certificate_credentials(schan_credentials *c, const CERT_CONTEXT *ctx)
{
gnutls_certificate_credentials_t creds;
gnutls_x509_crt_t crt;
gnutls_x509_privkey_t key;
int ret;
ret = pgnutls_certificate_allocate_credentials(&creds);
if (ret != GNUTLS_E_SUCCESS)
{
pgnutls_perror(ret);
return FALSE;
}
if (!ctx)
{
c->credentials = creds;
return TRUE;
}
if (!(crt = get_x509_crt(ctx)))
{
pgnutls_certificate_free_credentials(creds);
return FALSE;
}
if (!(key = get_x509_key(ctx)))
{
pgnutls_x509_crt_deinit(crt);
pgnutls_certificate_free_credentials(creds);
return FALSE;
}
ret = pgnutls_certificate_set_x509_key(creds, &crt, 1, key);
pgnutls_x509_privkey_deinit(key);
pgnutls_x509_crt_deinit(crt);
if (ret != GNUTLS_E_SUCCESS)
{
pgnutls_perror(ret);
pgnutls_certificate_free_credentials(creds);
return FALSE;
}
c->credentials = creds;
return TRUE;
}
void schan_imp_free_certificate_credentials(schan_credentials *c)
@ -597,6 +879,7 @@ BOOL schan_imp_init(void)
LOAD_FUNCPTR(gnutls_certificate_allocate_credentials)
LOAD_FUNCPTR(gnutls_certificate_free_credentials)
LOAD_FUNCPTR(gnutls_certificate_get_peers)
LOAD_FUNCPTR(gnutls_certificate_set_x509_key)
LOAD_FUNCPTR(gnutls_cipher_get)
LOAD_FUNCPTR(gnutls_cipher_get_key_size)
LOAD_FUNCPTR(gnutls_credentials_set)
@ -613,6 +896,9 @@ BOOL schan_imp_init(void)
LOAD_FUNCPTR(gnutls_perror)
LOAD_FUNCPTR(gnutls_protocol_get_version)
LOAD_FUNCPTR(gnutls_priority_set_direct)
LOAD_FUNCPTR(gnutls_privkey_deinit)
LOAD_FUNCPTR(gnutls_privkey_import_rsa_raw)
LOAD_FUNCPTR(gnutls_privkey_init)
LOAD_FUNCPTR(gnutls_record_get_max_size);
LOAD_FUNCPTR(gnutls_record_recv);
LOAD_FUNCPTR(gnutls_record_send);
@ -622,6 +908,10 @@ BOOL schan_imp_init(void)
LOAD_FUNCPTR(gnutls_transport_set_ptr)
LOAD_FUNCPTR(gnutls_transport_set_pull_function)
LOAD_FUNCPTR(gnutls_transport_set_push_function)
LOAD_FUNCPTR(gnutls_x509_crt_deinit)
LOAD_FUNCPTR(gnutls_x509_crt_import)
LOAD_FUNCPTR(gnutls_x509_crt_init)
LOAD_FUNCPTR(gnutls_x509_privkey_deinit)
#undef LOAD_FUNCPTR
if (!(pgnutls_cipher_get_block_size = wine_dlsym(libgnutls_handle, "gnutls_cipher_get_block_size", NULL, 0)))
@ -629,6 +919,11 @@ 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_privkey_export_x509 = wine_dlsym(libgnutls_handle, "gnutls_privkey_export_x509", NULL, 0)))
{
WARN("gnutls_privkey_export_x509 not found\n");
pgnutls_privkey_export_x509 = compat_gnutls_privkey_export_x509;
}
ret = pgnutls_global_init();
if (ret != GNUTLS_E_SUCCESS)

View file

@ -1185,9 +1185,9 @@ SECURITY_STATUS schan_imp_recv(schan_imp_session session, void *buffer,
return SEC_E_OK;
}
BOOL schan_imp_allocate_certificate_credentials(schan_credentials *c)
BOOL schan_imp_allocate_certificate_credentials(schan_credentials *c, const CERT_CONTEXT *cert)
{
/* The certificate is never really used for anything. */
if (cert) FIXME("no support for certificate credentials on this platform\n");
c->credentials = NULL;
return TRUE;
}

View file

@ -247,7 +247,7 @@ extern SECURITY_STATUS schan_imp_send(schan_imp_session session, const void *buf
SIZE_T *length) DECLSPEC_HIDDEN;
extern SECURITY_STATUS schan_imp_recv(schan_imp_session session, void *buffer,
SIZE_T *length) DECLSPEC_HIDDEN;
extern BOOL schan_imp_allocate_certificate_credentials(schan_credentials*) DECLSPEC_HIDDEN;
extern BOOL schan_imp_allocate_certificate_credentials(schan_credentials *, const CERT_CONTEXT *) DECLSPEC_HIDDEN;
extern void schan_imp_free_certificate_credentials(schan_credentials*) DECLSPEC_HIDDEN;
extern DWORD schan_imp_enabled_protocols(void) DECLSPEC_HIDDEN;
extern BOOL schan_imp_init(void) DECLSPEC_HIDDEN;