You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2431 lines
68 KiB
2431 lines
68 KiB
/*
|
|
* TLS support for CUPS on Windows using the Security Support Provider
|
|
* Interface (SSPI).
|
|
*
|
|
* Copyright 2010-2018 by Apple Inc.
|
|
*
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more information.
|
|
*/
|
|
|
|
/**** This file is included from tls.c ****/
|
|
|
|
/*
|
|
* Include necessary headers...
|
|
*/
|
|
|
|
#include "debug-private.h"
|
|
|
|
|
|
/*
|
|
* Include necessary libraries...
|
|
*/
|
|
|
|
#pragma comment(lib, "Crypt32.lib")
|
|
#pragma comment(lib, "Secur32.lib")
|
|
#pragma comment(lib, "Ws2_32.lib")
|
|
|
|
|
|
/*
|
|
* Constants...
|
|
*/
|
|
|
|
#ifndef SECURITY_FLAG_IGNORE_UNKNOWN_CA
|
|
# define SECURITY_FLAG_IGNORE_UNKNOWN_CA 0x00000100 /* Untrusted root */
|
|
#endif /* SECURITY_FLAG_IGNORE_UNKNOWN_CA */
|
|
|
|
#ifndef SECURITY_FLAG_IGNORE_CERT_CN_INVALID
|
|
# define SECURITY_FLAG_IGNORE_CERT_CN_INVALID 0x00001000 /* Common name does not match */
|
|
#endif /* !SECURITY_FLAG_IGNORE_CERT_CN_INVALID */
|
|
|
|
#ifndef SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
|
|
# define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID 0x00002000 /* Expired X509 Cert. */
|
|
#endif /* !SECURITY_FLAG_IGNORE_CERT_DATE_INVALID */
|
|
|
|
|
|
/*
|
|
* Local globals...
|
|
*/
|
|
|
|
static int tls_options = -1,/* Options for TLS connections */
|
|
tls_min_version = _HTTP_TLS_1_0,
|
|
tls_max_version = _HTTP_TLS_MAX;
|
|
|
|
|
|
/*
|
|
* Local functions...
|
|
*/
|
|
|
|
static _http_sspi_t *http_sspi_alloc(void);
|
|
static int http_sspi_client(http_t *http, const char *hostname);
|
|
static PCCERT_CONTEXT http_sspi_create_credential(http_credential_t *cred);
|
|
static BOOL http_sspi_find_credentials(http_t *http, const LPWSTR containerName, const char *common_name);
|
|
static void http_sspi_free(_http_sspi_t *sspi);
|
|
static BOOL http_sspi_make_credentials(_http_sspi_t *sspi, const LPWSTR containerName, const char *common_name, _http_mode_t mode, int years);
|
|
static int http_sspi_server(http_t *http, const char *hostname);
|
|
static void http_sspi_set_allows_any_root(_http_sspi_t *sspi, BOOL allow);
|
|
static void http_sspi_set_allows_expired_certs(_http_sspi_t *sspi, BOOL allow);
|
|
static const char *http_sspi_strerror(char *buffer, size_t bufsize, DWORD code);
|
|
static DWORD http_sspi_verify(PCCERT_CONTEXT cert, const char *common_name, DWORD dwCertFlags);
|
|
|
|
|
|
/*
|
|
* 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
int /* O - 1 on success, 0 on failure */
|
|
cupsMakeServerCredentials(
|
|
const char *path, /* I - Keychain path or @code NULL@ for default */
|
|
const char *common_name, /* I - Common name */
|
|
int num_alt_names, /* I - Number of subject alternate names */
|
|
const char **alt_names, /* I - Subject Alternate Names */
|
|
time_t expiration_date) /* I - Expiration date */
|
|
{
|
|
_http_sspi_t *sspi; /* SSPI data */
|
|
int ret; /* Return value */
|
|
|
|
|
|
DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date));
|
|
|
|
(void)path;
|
|
(void)num_alt_names;
|
|
(void)alt_names;
|
|
|
|
sspi = http_sspi_alloc();
|
|
ret = http_sspi_make_credentials(sspi, L"ServerContainer", common_name, _HTTP_MODE_SERVER, (int)((expiration_date - time(NULL) + 86399) / 86400 / 365));
|
|
|
|
http_sspi_free(sspi);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'cupsSetServerCredentials()' - Set the default server credentials.
|
|
*
|
|
* Note: The server credentials are used by all threads in the running process.
|
|
* This function is threadsafe.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
int /* O - 1 on success, 0 on failure */
|
|
cupsSetServerCredentials(
|
|
const char *path, /* I - Keychain path or @code NULL@ for default */
|
|
const char *common_name, /* I - Default common name for server */
|
|
int auto_create) /* I - 1 = automatically create self-signed certificates */
|
|
{
|
|
DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create));
|
|
|
|
(void)path;
|
|
(void)common_name;
|
|
(void)auto_create;
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpCopyCredentials()' - Copy the credentials associated with the peer in
|
|
* an encrypted connection.
|
|
*
|
|
* @since CUPS 1.5/macOS 10.7@
|
|
*/
|
|
|
|
int /* O - Status of call (0 = success) */
|
|
httpCopyCredentials(
|
|
http_t *http, /* I - Connection to server */
|
|
cups_array_t **credentials) /* O - Array of credentials */
|
|
{
|
|
DEBUG_printf(("httpCopyCredentials(http=%p, credentials=%p)", http, credentials));
|
|
|
|
if (!http || !http->tls || !http->tls->remoteCert || !credentials)
|
|
{
|
|
if (credentials)
|
|
*credentials = NULL;
|
|
|
|
return (-1);
|
|
}
|
|
|
|
*credentials = cupsArrayNew(NULL, NULL);
|
|
httpAddCredential(*credentials, http->tls->remoteCert->pbCertEncoded, http->tls->remoteCert->cbCertEncoded);
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpCreateCredentials()' - Create credentials in the internal format.
|
|
*/
|
|
|
|
http_tls_credentials_t /* O - Internal credentials */
|
|
_httpCreateCredentials(
|
|
cups_array_t *credentials) /* I - Array of credentials */
|
|
{
|
|
return (http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials)));
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
int /* O - 1 if valid, 0 otherwise */
|
|
httpCredentialsAreValidForName(
|
|
cups_array_t *credentials, /* I - Credentials */
|
|
const char *common_name) /* I - Name to check */
|
|
{
|
|
int valid = 1; /* Valid name? */
|
|
PCCERT_CONTEXT cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
|
|
/* Certificate */
|
|
char cert_name[1024]; /* Name from certificate */
|
|
|
|
|
|
if (cert)
|
|
{
|
|
if (CertNameToStrA(X509_ASN_ENCODING, &(cert->pCertInfo->Subject), CERT_SIMPLE_NAME_STR, cert_name, sizeof(cert_name)))
|
|
{
|
|
/*
|
|
* Extract common name at end...
|
|
*/
|
|
|
|
char *ptr = strrchr(cert_name, ',');
|
|
if (ptr && ptr[1])
|
|
_cups_strcpy(cert_name, ptr + 2);
|
|
}
|
|
else
|
|
strlcpy(cert_name, "unknown", sizeof(cert_name));
|
|
|
|
CertFreeCertificateContext(cert);
|
|
}
|
|
else
|
|
strlcpy(cert_name, "unknown", sizeof(cert_name));
|
|
|
|
/*
|
|
* Compare the common names...
|
|
*/
|
|
|
|
if (_cups_strcasecmp(common_name, cert_name))
|
|
{
|
|
/*
|
|
* Not an exact match for the common name, check for wildcard certs...
|
|
*/
|
|
|
|
const char *domain = strchr(common_name, '.');
|
|
/* Domain in common name */
|
|
|
|
if (strncmp(cert_name, "*.", 2) || !domain || _cups_strcasecmp(domain, cert_name + 1))
|
|
{
|
|
/*
|
|
* Not a wildcard match.
|
|
*/
|
|
|
|
/* TODO: Check subject alternate names */
|
|
valid = 0;
|
|
}
|
|
}
|
|
|
|
return (valid);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpCredentialsGetTrust()' - Return the trust of credentials.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
http_trust_t /* O - Level of trust */
|
|
httpCredentialsGetTrust(
|
|
cups_array_t *credentials, /* I - Credentials */
|
|
const char *common_name) /* I - Common name for trust lookup */
|
|
{
|
|
http_trust_t trust = HTTP_TRUST_OK; /* Level of trust */
|
|
PCCERT_CONTEXT cert = NULL; /* Certificate to validate */
|
|
DWORD certFlags = 0; /* Cert verification flags */
|
|
_cups_globals_t *cg = _cupsGlobals(); /* Per-thread global data */
|
|
|
|
|
|
if (!common_name)
|
|
return (HTTP_TRUST_UNKNOWN);
|
|
|
|
cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
|
|
if (!cert)
|
|
return (HTTP_TRUST_UNKNOWN);
|
|
|
|
if (cg->any_root < 0)
|
|
_cupsSetDefaults();
|
|
|
|
if (cg->any_root)
|
|
certFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
|
|
|
|
if (cg->expired_certs)
|
|
certFlags |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
|
|
|
|
if (!cg->validate_certs)
|
|
certFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
|
|
|
|
if (http_sspi_verify(cert, common_name, certFlags) != SEC_E_OK)
|
|
trust = HTTP_TRUST_INVALID;
|
|
|
|
CertFreeCertificateContext(cert);
|
|
|
|
return (trust);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
time_t /* O - Expiration date of credentials */
|
|
httpCredentialsGetExpiration(
|
|
cups_array_t *credentials) /* I - Credentials */
|
|
{
|
|
time_t expiration_date = 0; /* Expiration data of credentials */
|
|
PCCERT_CONTEXT cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
|
|
/* Certificate */
|
|
|
|
if (cert)
|
|
{
|
|
SYSTEMTIME systime; /* System time */
|
|
struct tm tm; /* UNIX date/time */
|
|
|
|
FileTimeToSystemTime(&(cert->pCertInfo->NotAfter), &systime);
|
|
|
|
tm.tm_year = systime.wYear - 1900;
|
|
tm.tm_mon = systime.wMonth - 1;
|
|
tm.tm_mday = systime.wDay;
|
|
tm.tm_hour = systime.wHour;
|
|
tm.tm_min = systime.wMinute;
|
|
tm.tm_sec = systime.wSecond;
|
|
|
|
expiration_date = mktime(&tm);
|
|
|
|
CertFreeCertificateContext(cert);
|
|
}
|
|
|
|
return (expiration_date);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpCredentialsString()' - Return a string representing the credentials.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
size_t /* O - Total size of credentials string */
|
|
httpCredentialsString(
|
|
cups_array_t *credentials, /* I - Credentials */
|
|
char *buffer, /* I - Buffer or @code NULL@ */
|
|
size_t bufsize) /* I - Size of buffer */
|
|
{
|
|
http_credential_t *first = (http_credential_t *)cupsArrayFirst(credentials);
|
|
/* First certificate */
|
|
PCCERT_CONTEXT cert; /* Certificate */
|
|
|
|
|
|
DEBUG_printf(("httpCredentialsString(credentials=%p, buffer=%p, bufsize=" CUPS_LLFMT ")", credentials, buffer, CUPS_LLCAST bufsize));
|
|
|
|
if (!buffer)
|
|
return (0);
|
|
|
|
if (buffer && bufsize > 0)
|
|
*buffer = '\0';
|
|
|
|
cert = http_sspi_create_credential(first);
|
|
|
|
if (cert)
|
|
{
|
|
char cert_name[256]; /* Common name */
|
|
SYSTEMTIME systime; /* System time */
|
|
struct tm tm; /* UNIX date/time */
|
|
time_t expiration; /* Expiration date of cert */
|
|
unsigned char md5_digest[16]; /* MD5 result */
|
|
|
|
FileTimeToSystemTime(&(cert->pCertInfo->NotAfter), &systime);
|
|
|
|
tm.tm_year = systime.wYear - 1900;
|
|
tm.tm_mon = systime.wMonth - 1;
|
|
tm.tm_mday = systime.wDay;
|
|
tm.tm_hour = systime.wHour;
|
|
tm.tm_min = systime.wMinute;
|
|
tm.tm_sec = systime.wSecond;
|
|
|
|
expiration = mktime(&tm);
|
|
|
|
if (CertNameToStrA(X509_ASN_ENCODING, &(cert->pCertInfo->Subject), CERT_SIMPLE_NAME_STR, cert_name, sizeof(cert_name)))
|
|
{
|
|
/*
|
|
* Extract common name at end...
|
|
*/
|
|
|
|
char *ptr = strrchr(cert_name, ',');
|
|
if (ptr && ptr[1])
|
|
_cups_strcpy(cert_name, ptr + 2);
|
|
}
|
|
else
|
|
strlcpy(cert_name, "unknown", sizeof(cert_name));
|
|
|
|
cupsHashData("md5", first->data, first->datalen, md5_digest, sizeof(md5_digest));
|
|
|
|
snprintf(buffer, bufsize, "%s / %s / %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", cert_name, httpGetDateString(expiration), md5_digest[0], md5_digest[1], md5_digest[2], md5_digest[3], md5_digest[4], md5_digest[5], md5_digest[6], md5_digest[7], md5_digest[8], md5_digest[9], md5_digest[10], md5_digest[11], md5_digest[12], md5_digest[13], md5_digest[14], md5_digest[15]);
|
|
|
|
CertFreeCertificateContext(cert);
|
|
}
|
|
|
|
DEBUG_printf(("1httpCredentialsString: Returning \"%s\".", buffer));
|
|
|
|
return (strlen(buffer));
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpFreeCredentials()' - Free internal credentials.
|
|
*/
|
|
|
|
void
|
|
_httpFreeCredentials(
|
|
http_tls_credentials_t credentials) /* I - Internal credentials */
|
|
{
|
|
if (!credentials)
|
|
return;
|
|
|
|
CertFreeCertificateContext(credentials);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpLoadCredentials()' - Load X.509 credentials from a keychain file.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
int /* O - 0 on success, -1 on error */
|
|
httpLoadCredentials(
|
|
const char *path, /* I - Keychain path or @code NULL@ for default */
|
|
cups_array_t **credentials, /* IO - Credentials */
|
|
const char *common_name) /* I - Common name for credentials */
|
|
{
|
|
HCERTSTORE store = NULL; /* Certificate store */
|
|
PCCERT_CONTEXT storedContext = NULL; /* Context created from the store */
|
|
DWORD dwSize = 0; /* 32 bit size */
|
|
PBYTE p = NULL; /* Temporary storage */
|
|
HCRYPTPROV hProv = (HCRYPTPROV)NULL;
|
|
/* Handle to a CSP */
|
|
CERT_NAME_BLOB sib; /* Arbitrary array of bytes */
|
|
#ifdef DEBUG
|
|
char error[1024]; /* Error message buffer */
|
|
#endif /* DEBUG */
|
|
|
|
|
|
DEBUG_printf(("httpLoadCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, credentials, common_name));
|
|
|
|
(void)path;
|
|
|
|
if (credentials)
|
|
{
|
|
*credentials = NULL;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_puts("1httpLoadCredentials: NULL credentials pointer, returning -1.");
|
|
return (-1);
|
|
}
|
|
|
|
if (!common_name)
|
|
{
|
|
DEBUG_puts("1httpLoadCredentials: Bad common name, returning -1.");
|
|
return (-1);
|
|
}
|
|
|
|
if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
|
|
{
|
|
if (GetLastError() == NTE_EXISTS)
|
|
{
|
|
if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: CryptAcquireContext failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");
|
|
|
|
if (!store)
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: CertOpenSystemStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
dwSize = 0;
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
p = (PBYTE)malloc(dwSize);
|
|
|
|
if (!p)
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: malloc failed for %d bytes.", dwSize));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
sib.cbData = dwSize;
|
|
sib.pbData = p;
|
|
|
|
storedContext = CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &sib, NULL);
|
|
|
|
if (!storedContext)
|
|
{
|
|
DEBUG_printf(("1httpLoadCredentials: Unable to find credentials for \"%s\".", common_name));
|
|
goto cleanup;
|
|
}
|
|
|
|
*credentials = cupsArrayNew(NULL, NULL);
|
|
httpAddCredential(*credentials, storedContext->pbCertEncoded, storedContext->cbCertEncoded);
|
|
|
|
cleanup:
|
|
|
|
/*
|
|
* Cleanup
|
|
*/
|
|
|
|
if (storedContext)
|
|
CertFreeCertificateContext(storedContext);
|
|
|
|
if (p)
|
|
free(p);
|
|
|
|
if (store)
|
|
CertCloseStore(store, 0);
|
|
|
|
if (hProv)
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
DEBUG_printf(("1httpLoadCredentials: Returning %d.", *credentials ? 0 : -1));
|
|
|
|
return (*credentials ? 0 : -1);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'httpSaveCredentials()' - Save X.509 credentials to a keychain file.
|
|
*
|
|
* @since CUPS 2.0/OS 10.10@
|
|
*/
|
|
|
|
int /* O - -1 on error, 0 on success */
|
|
httpSaveCredentials(
|
|
const char *path, /* I - Keychain path or @code NULL@ for default */
|
|
cups_array_t *credentials, /* I - Credentials */
|
|
const char *common_name) /* I - Common name for credentials */
|
|
{
|
|
HCERTSTORE store = NULL; /* Certificate store */
|
|
PCCERT_CONTEXT storedContext = NULL; /* Context created from the store */
|
|
PCCERT_CONTEXT createdContext = NULL; /* Context created by us */
|
|
DWORD dwSize = 0; /* 32 bit size */
|
|
PBYTE p = NULL; /* Temporary storage */
|
|
HCRYPTPROV hProv = (HCRYPTPROV)NULL;
|
|
/* Handle to a CSP */
|
|
CRYPT_KEY_PROV_INFO ckp; /* Handle to crypto key */
|
|
int ret = -1; /* Return value */
|
|
#ifdef DEBUG
|
|
char error[1024]; /* Error message buffer */
|
|
#endif /* DEBUG */
|
|
|
|
|
|
DEBUG_printf(("httpSaveCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, credentials, common_name));
|
|
|
|
(void)path;
|
|
|
|
if (!common_name)
|
|
{
|
|
DEBUG_puts("1httpSaveCredentials: Bad common name, returning -1.");
|
|
return (-1);
|
|
}
|
|
|
|
createdContext = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
|
|
if (!createdContext)
|
|
{
|
|
DEBUG_puts("1httpSaveCredentials: Bad credentials, returning -1.");
|
|
return (-1);
|
|
}
|
|
|
|
if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
|
|
{
|
|
if (GetLastError() == NTE_EXISTS)
|
|
{
|
|
if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CryptAcquireContext failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");
|
|
|
|
if (!store)
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CertOpenSystemStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
dwSize = 0;
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
p = (PBYTE)malloc(dwSize);
|
|
|
|
if (!p)
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: malloc failed for %d bytes.", dwSize));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Add the created context to the named store, and associate it with the named
|
|
* container...
|
|
*/
|
|
|
|
if (!CertAddCertificateContextToStore(store, createdContext, CERT_STORE_ADD_REPLACE_EXISTING, &storedContext))
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CertAddCertificateContextToStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(&ckp, sizeof(ckp));
|
|
ckp.pwszContainerName = L"RememberedContainer";
|
|
ckp.pwszProvName = MS_DEF_PROV_W;
|
|
ckp.dwProvType = PROV_RSA_FULL;
|
|
ckp.dwFlags = CRYPT_MACHINE_KEYSET;
|
|
ckp.dwKeySpec = AT_KEYEXCHANGE;
|
|
|
|
if (!CertSetCertificateContextProperty(storedContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &ckp))
|
|
{
|
|
DEBUG_printf(("1httpSaveCredentials: CertSetCertificateContextProperty failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
|
|
/*
|
|
* Cleanup
|
|
*/
|
|
|
|
if (createdContext)
|
|
CertFreeCertificateContext(createdContext);
|
|
|
|
if (storedContext)
|
|
CertFreeCertificateContext(storedContext);
|
|
|
|
if (p)
|
|
free(p);
|
|
|
|
if (store)
|
|
CertCloseStore(store, 0);
|
|
|
|
if (hProv)
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
DEBUG_printf(("1httpSaveCredentials: Returning %d.", ret));
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSInitialize()' - Initialize the TLS stack.
|
|
*/
|
|
|
|
void
|
|
_httpTLSInitialize(void)
|
|
{
|
|
/*
|
|
* Nothing to do...
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes.
|
|
*/
|
|
|
|
size_t /* O - Bytes available */
|
|
_httpTLSPending(http_t *http) /* I - HTTP connection */
|
|
{
|
|
if (http->tls)
|
|
return (http->tls->readBufferUsed);
|
|
else
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSRead()' - Read from a SSL/TLS connection.
|
|
*/
|
|
|
|
int /* O - Bytes read */
|
|
_httpTLSRead(http_t *http, /* I - HTTP connection */
|
|
char *buf, /* I - Buffer to store data */
|
|
int len) /* I - Length of buffer */
|
|
{
|
|
int i; /* Looping var */
|
|
_http_sspi_t *sspi = http->tls; /* SSPI data */
|
|
SecBufferDesc message; /* Array of SecBuffer struct */
|
|
SecBuffer buffers[4] = { 0 }; /* Security package buffer */
|
|
int num = 0; /* Return value */
|
|
PSecBuffer pDataBuffer; /* Data buffer */
|
|
PSecBuffer pExtraBuffer; /* Excess data buffer */
|
|
SECURITY_STATUS scRet; /* SSPI status */
|
|
|
|
|
|
DEBUG_printf(("4_httpTLSRead(http=%p, buf=%p, len=%d)", http, buf, len));
|
|
|
|
/*
|
|
* If there are bytes that have already been decrypted and have not yet been
|
|
* read, return those...
|
|
*/
|
|
|
|
if (sspi->readBufferUsed > 0)
|
|
{
|
|
int bytesToCopy = min(sspi->readBufferUsed, len);
|
|
/* Number of bytes to copy */
|
|
|
|
memcpy(buf, sspi->readBuffer, bytesToCopy);
|
|
sspi->readBufferUsed -= bytesToCopy;
|
|
|
|
if (sspi->readBufferUsed > 0)
|
|
memmove(sspi->readBuffer, sspi->readBuffer + bytesToCopy, sspi->readBufferUsed);
|
|
|
|
DEBUG_printf(("5_httpTLSRead: Returning %d bytes previously decrypted.", bytesToCopy));
|
|
|
|
return (bytesToCopy);
|
|
}
|
|
|
|
/*
|
|
* Initialize security buffer structs
|
|
*/
|
|
|
|
message.ulVersion = SECBUFFER_VERSION;
|
|
message.cBuffers = 4;
|
|
message.pBuffers = buffers;
|
|
|
|
do
|
|
{
|
|
/*
|
|
* If there is not enough space in the buffer, then increase its size...
|
|
*/
|
|
|
|
if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
|
|
{
|
|
BYTE *temp; /* New buffer */
|
|
|
|
if (sspi->decryptBufferLength >= 262144)
|
|
{
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
DEBUG_puts("_httpTLSRead: Decryption buffer too large (>256k)");
|
|
return (-1);
|
|
}
|
|
|
|
if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
|
|
{
|
|
DEBUG_printf(("_httpTLSRead: Unable to allocate %d byte decryption buffer.", sspi->decryptBufferLength + 4096));
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
return (-1);
|
|
}
|
|
|
|
sspi->decryptBufferLength += 4096;
|
|
sspi->decryptBuffer = temp;
|
|
|
|
DEBUG_printf(("_httpTLSRead: Resized decryption buffer to %d bytes.", sspi->decryptBufferLength));
|
|
}
|
|
|
|
buffers[0].pvBuffer = sspi->decryptBuffer;
|
|
buffers[0].cbBuffer = (unsigned long)sspi->decryptBufferUsed;
|
|
buffers[0].BufferType = SECBUFFER_DATA;
|
|
buffers[1].BufferType = SECBUFFER_EMPTY;
|
|
buffers[2].BufferType = SECBUFFER_EMPTY;
|
|
buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
DEBUG_printf(("5_httpTLSRead: decryptBufferUsed=%d", sspi->decryptBufferUsed));
|
|
|
|
scRet = DecryptMessage(&sspi->context, &message, 0, NULL);
|
|
|
|
if (scRet == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
num = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);
|
|
if (num < 0)
|
|
{
|
|
DEBUG_printf(("5_httpTLSRead: recv failed: %d", WSAGetLastError()));
|
|
return (-1);
|
|
}
|
|
else if (num == 0)
|
|
{
|
|
DEBUG_puts("5_httpTLSRead: Server disconnected.");
|
|
return (0);
|
|
}
|
|
|
|
DEBUG_printf(("5_httpTLSRead: Read %d bytes into decryption buffer.", num));
|
|
|
|
sspi->decryptBufferUsed += num;
|
|
}
|
|
}
|
|
while (scRet == SEC_E_INCOMPLETE_MESSAGE);
|
|
|
|
if (scRet == SEC_I_CONTEXT_EXPIRED)
|
|
{
|
|
DEBUG_puts("5_httpTLSRead: Context expired.");
|
|
WSASetLastError(WSAECONNRESET);
|
|
return (-1);
|
|
}
|
|
else if (scRet != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5_httpTLSRead: DecryptMessage failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
WSASetLastError(WSASYSCALLFAILURE);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* The decryption worked. Now, locate data buffer.
|
|
*/
|
|
|
|
pDataBuffer = NULL;
|
|
pExtraBuffer = NULL;
|
|
|
|
for (i = 1; i < 4; i++)
|
|
{
|
|
if (buffers[i].BufferType == SECBUFFER_DATA)
|
|
pDataBuffer = &buffers[i];
|
|
else if (!pExtraBuffer && (buffers[i].BufferType == SECBUFFER_EXTRA))
|
|
pExtraBuffer = &buffers[i];
|
|
}
|
|
|
|
/*
|
|
* If a data buffer is found, then copy the decrypted bytes to the passed-in
|
|
* buffer...
|
|
*/
|
|
|
|
if (pDataBuffer)
|
|
{
|
|
int bytesToCopy = min((int)pDataBuffer->cbBuffer, len);
|
|
/* Number of bytes to copy into buf */
|
|
int bytesToSave = pDataBuffer->cbBuffer - bytesToCopy;
|
|
/* Number of bytes to save in our read buffer */
|
|
|
|
if (bytesToCopy)
|
|
memcpy(buf, pDataBuffer->pvBuffer, bytesToCopy);
|
|
|
|
/*
|
|
* If there are more decrypted bytes than can be copied to the passed in
|
|
* buffer, then save them...
|
|
*/
|
|
|
|
if (bytesToSave)
|
|
{
|
|
if ((sspi->readBufferLength - sspi->readBufferUsed) < bytesToSave)
|
|
{
|
|
BYTE *temp; /* New buffer pointer */
|
|
|
|
if ((temp = realloc(sspi->readBuffer, sspi->readBufferUsed + bytesToSave)) == NULL)
|
|
{
|
|
DEBUG_printf(("_httpTLSRead: Unable to allocate %d bytes.", sspi->readBufferUsed + bytesToSave));
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
return (-1);
|
|
}
|
|
|
|
sspi->readBufferLength = sspi->readBufferUsed + bytesToSave;
|
|
sspi->readBuffer = temp;
|
|
}
|
|
|
|
memcpy(((BYTE *)sspi->readBuffer) + sspi->readBufferUsed, ((BYTE *)pDataBuffer->pvBuffer) + bytesToCopy, bytesToSave);
|
|
|
|
sspi->readBufferUsed += bytesToSave;
|
|
}
|
|
|
|
num = bytesToCopy;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_puts("_httpTLSRead: Unable to find data buffer.");
|
|
WSASetLastError(WSASYSCALLFAILURE);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* If the decryption process left extra bytes, then save those back in
|
|
* decryptBuffer. They will be processed the next time through the loop.
|
|
*/
|
|
|
|
if (pExtraBuffer)
|
|
{
|
|
memmove(sspi->decryptBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
|
|
sspi->decryptBufferUsed = pExtraBuffer->cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
sspi->decryptBufferUsed = 0;
|
|
}
|
|
|
|
return (num);
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options.
|
|
*/
|
|
|
|
void
|
|
_httpTLSSetOptions(int options, /* I - Options */
|
|
int min_version, /* I - Minimum TLS version */
|
|
int max_version) /* I - Maximum TLS version */
|
|
{
|
|
if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0)
|
|
{
|
|
tls_options = options;
|
|
tls_min_version = min_version;
|
|
tls_max_version = max_version;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSStart()' - Set up SSL/TLS support on a connection.
|
|
*/
|
|
|
|
int /* O - 0 on success, -1 on failure */
|
|
_httpTLSStart(http_t *http) /* I - HTTP connection */
|
|
{
|
|
char hostname[256], /* Hostname */
|
|
*hostptr; /* Pointer into hostname */
|
|
|
|
|
|
DEBUG_printf(("3_httpTLSStart(http=%p)", http));
|
|
|
|
if (tls_options < 0)
|
|
{
|
|
DEBUG_puts("4_httpTLSStart: Setting defaults.");
|
|
_cupsSetDefaults();
|
|
DEBUG_printf(("4_httpTLSStart: tls_options=%x", tls_options));
|
|
}
|
|
|
|
if ((http->tls = http_sspi_alloc()) == NULL)
|
|
return (-1);
|
|
|
|
if (http->mode == _HTTP_MODE_CLIENT)
|
|
{
|
|
/*
|
|
* Client: determine hostname...
|
|
*/
|
|
|
|
if (httpAddrLocalhost(http->hostaddr))
|
|
{
|
|
strlcpy(hostname, "localhost", sizeof(hostname));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise make sure the hostname we have does not end in a trailing dot.
|
|
*/
|
|
|
|
strlcpy(hostname, http->hostname, sizeof(hostname));
|
|
if ((hostptr = hostname + strlen(hostname) - 1) >= hostname &&
|
|
*hostptr == '.')
|
|
*hostptr = '\0';
|
|
}
|
|
|
|
return (http_sspi_client(http, hostname));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Server: determine hostname to use...
|
|
*/
|
|
|
|
if (http->fields[HTTP_FIELD_HOST])
|
|
{
|
|
/*
|
|
* Use hostname for TLS upgrade...
|
|
*/
|
|
|
|
strlcpy(hostname, http->fields[HTTP_FIELD_HOST], sizeof(hostname));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Resolve hostname from connection address...
|
|
*/
|
|
|
|
http_addr_t addr; /* Connection address */
|
|
socklen_t addrlen; /* Length of address */
|
|
|
|
addrlen = sizeof(addr);
|
|
if (getsockname(http->fd, (struct sockaddr *)&addr, &addrlen))
|
|
{
|
|
DEBUG_printf(("4_httpTLSStart: Unable to get socket address: %s", strerror(errno)));
|
|
hostname[0] = '\0';
|
|
}
|
|
else if (httpAddrLocalhost(&addr))
|
|
hostname[0] = '\0';
|
|
else
|
|
{
|
|
httpAddrLookup(&addr, hostname, sizeof(hostname));
|
|
DEBUG_printf(("4_httpTLSStart: Resolved socket address to \"%s\".", hostname));
|
|
}
|
|
}
|
|
|
|
return (http_sspi_server(http, hostname));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSStop()' - Shut down SSL/TLS on a connection.
|
|
*/
|
|
|
|
void
|
|
_httpTLSStop(http_t *http) /* I - HTTP connection */
|
|
{
|
|
_http_sspi_t *sspi = http->tls; /* SSPI data */
|
|
|
|
|
|
if (sspi->contextInitialized && http->fd >= 0)
|
|
{
|
|
SecBufferDesc message; /* Array of SecBuffer struct */
|
|
SecBuffer buffers[1] = { 0 };
|
|
/* Security package buffer */
|
|
DWORD dwType; /* Type */
|
|
DWORD status; /* Status */
|
|
|
|
/*
|
|
* Notify schannel that we are about to close the connection.
|
|
*/
|
|
|
|
dwType = SCHANNEL_SHUTDOWN;
|
|
|
|
buffers[0].pvBuffer = &dwType;
|
|
buffers[0].BufferType = SECBUFFER_TOKEN;
|
|
buffers[0].cbBuffer = sizeof(dwType);
|
|
|
|
message.cBuffers = 1;
|
|
message.pBuffers = buffers;
|
|
message.ulVersion = SECBUFFER_VERSION;
|
|
|
|
status = ApplyControlToken(&sspi->context, &message);
|
|
|
|
if (SUCCEEDED(status))
|
|
{
|
|
PBYTE pbMessage; /* Message buffer */
|
|
DWORD cbMessage; /* Message buffer count */
|
|
DWORD cbData; /* Data count */
|
|
DWORD dwSSPIFlags; /* SSL attributes we requested */
|
|
DWORD dwSSPIOutFlags; /* SSL attributes we received */
|
|
TimeStamp tsExpiry; /* Time stamp */
|
|
|
|
dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT |
|
|
ASC_REQ_REPLAY_DETECT |
|
|
ASC_REQ_CONFIDENTIALITY |
|
|
ASC_REQ_EXTENDED_ERROR |
|
|
ASC_REQ_ALLOCATE_MEMORY |
|
|
ASC_REQ_STREAM;
|
|
|
|
buffers[0].pvBuffer = NULL;
|
|
buffers[0].BufferType = SECBUFFER_TOKEN;
|
|
buffers[0].cbBuffer = 0;
|
|
|
|
message.cBuffers = 1;
|
|
message.pBuffers = buffers;
|
|
message.ulVersion = SECBUFFER_VERSION;
|
|
|
|
status = AcceptSecurityContext(&sspi->creds, &sspi->context, NULL,
|
|
dwSSPIFlags, SECURITY_NATIVE_DREP, NULL,
|
|
&message, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
if (SUCCEEDED(status))
|
|
{
|
|
pbMessage = buffers[0].pvBuffer;
|
|
cbMessage = buffers[0].cbBuffer;
|
|
|
|
/*
|
|
* Send the close notify message to the client.
|
|
*/
|
|
|
|
if (pbMessage && cbMessage)
|
|
{
|
|
cbData = send(http->fd, pbMessage, cbMessage, 0);
|
|
if ((cbData == SOCKET_ERROR) || (cbData == 0))
|
|
{
|
|
status = WSAGetLastError();
|
|
DEBUG_printf(("_httpTLSStop: sending close notify failed: %d", status));
|
|
}
|
|
else
|
|
{
|
|
FreeContextBuffer(pbMessage);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_printf(("_httpTLSStop: AcceptSecurityContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), status)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_printf(("_httpTLSStop: ApplyControlToken failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), status)));
|
|
}
|
|
}
|
|
|
|
http_sspi_free(sspi);
|
|
|
|
http->tls = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* '_httpTLSWrite()' - Write to a SSL/TLS connection.
|
|
*/
|
|
|
|
int /* O - Bytes written */
|
|
_httpTLSWrite(http_t *http, /* I - HTTP connection */
|
|
const char *buf, /* I - Buffer holding data */
|
|
int len) /* I - Length of buffer */
|
|
{
|
|
_http_sspi_t *sspi = http->tls; /* SSPI data */
|
|
SecBufferDesc message; /* Array of SecBuffer struct */
|
|
SecBuffer buffers[4] = { 0 }; /* Security package buffer */
|
|
int bufferLen; /* Buffer length */
|
|
int bytesLeft; /* Bytes left to write */
|
|
const char *bufptr; /* Pointer into buffer */
|
|
int num = 0; /* Return value */
|
|
|
|
|
|
bufferLen = sspi->streamSizes.cbMaximumMessage + sspi->streamSizes.cbHeader + sspi->streamSizes.cbTrailer;
|
|
|
|
if (bufferLen > sspi->writeBufferLength)
|
|
{
|
|
BYTE *temp; /* New buffer pointer */
|
|
|
|
if ((temp = (BYTE *)realloc(sspi->writeBuffer, bufferLen)) == NULL)
|
|
{
|
|
DEBUG_printf(("_httpTLSWrite: Unable to allocate buffer of %d bytes.", bufferLen));
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
return (-1);
|
|
}
|
|
|
|
sspi->writeBuffer = temp;
|
|
sspi->writeBufferLength = bufferLen;
|
|
}
|
|
|
|
bytesLeft = len;
|
|
bufptr = buf;
|
|
|
|
while (bytesLeft)
|
|
{
|
|
int chunk = min((int)sspi->streamSizes.cbMaximumMessage, bytesLeft);
|
|
/* Size of data to write */
|
|
SECURITY_STATUS scRet; /* SSPI status */
|
|
|
|
/*
|
|
* Copy user data into the buffer, starting just past the header...
|
|
*/
|
|
|
|
memcpy(sspi->writeBuffer + sspi->streamSizes.cbHeader, bufptr, chunk);
|
|
|
|
/*
|
|
* Setup the SSPI buffers
|
|
*/
|
|
|
|
message.ulVersion = SECBUFFER_VERSION;
|
|
message.cBuffers = 4;
|
|
message.pBuffers = buffers;
|
|
|
|
buffers[0].pvBuffer = sspi->writeBuffer;
|
|
buffers[0].cbBuffer = sspi->streamSizes.cbHeader;
|
|
buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
buffers[1].pvBuffer = sspi->writeBuffer + sspi->streamSizes.cbHeader;
|
|
buffers[1].cbBuffer = (unsigned long) chunk;
|
|
buffers[1].BufferType = SECBUFFER_DATA;
|
|
buffers[2].pvBuffer = sspi->writeBuffer + sspi->streamSizes.cbHeader + chunk;
|
|
buffers[2].cbBuffer = sspi->streamSizes.cbTrailer;
|
|
buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
/*
|
|
* Encrypt the data
|
|
*/
|
|
|
|
scRet = EncryptMessage(&sspi->context, 0, &message, 0);
|
|
|
|
if (FAILED(scRet))
|
|
{
|
|
DEBUG_printf(("_httpTLSWrite: EncryptMessage failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
WSASetLastError(WSASYSCALLFAILURE);
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Send the data. Remember the size of the total data to send is the size
|
|
* of the header, the size of the data the caller passed in and the size
|
|
* of the trailer...
|
|
*/
|
|
|
|
num = send(http->fd, sspi->writeBuffer, buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer, 0);
|
|
|
|
if (num <= 0)
|
|
{
|
|
DEBUG_printf(("_httpTLSWrite: send failed: %ld", WSAGetLastError()));
|
|
return (num);
|
|
}
|
|
|
|
bytesLeft -= chunk;
|
|
bufptr += chunk;
|
|
}
|
|
|
|
return (len);
|
|
}
|
|
|
|
|
|
#if 0
|
|
/*
|
|
* 'http_setup_ssl()' - Set up SSL/TLS support on a connection.
|
|
*/
|
|
|
|
static int /* O - 0 on success, -1 on failure */
|
|
http_setup_ssl(http_t *http) /* I - Connection to server */
|
|
{
|
|
char hostname[256], /* Hostname */
|
|
*hostptr; /* Pointer into hostname */
|
|
|
|
TCHAR username[256]; /* Username returned from GetUserName() */
|
|
TCHAR commonName[256];/* Common name for certificate */
|
|
DWORD dwSize; /* 32 bit size */
|
|
|
|
|
|
DEBUG_printf(("7http_setup_ssl(http=%p)", http));
|
|
|
|
/*
|
|
* Get the hostname to use for SSL...
|
|
*/
|
|
|
|
if (httpAddrLocalhost(http->hostaddr))
|
|
{
|
|
strlcpy(hostname, "localhost", sizeof(hostname));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise make sure the hostname we have does not end in a trailing dot.
|
|
*/
|
|
|
|
strlcpy(hostname, http->hostname, sizeof(hostname));
|
|
if ((hostptr = hostname + strlen(hostname) - 1) >= hostname &&
|
|
*hostptr == '.')
|
|
*hostptr = '\0';
|
|
}
|
|
|
|
http->tls = http_sspi_alloc();
|
|
|
|
if (!http->tls)
|
|
{
|
|
_cupsSetHTTPError(HTTP_STATUS_ERROR);
|
|
return (-1);
|
|
}
|
|
|
|
dwSize = sizeof(username) / sizeof(TCHAR);
|
|
GetUserName(username, &dwSize);
|
|
_sntprintf_s(commonName, sizeof(commonName) / sizeof(TCHAR),
|
|
sizeof(commonName) / sizeof(TCHAR), TEXT("CN=%s"), username);
|
|
|
|
if (!_sspiGetCredentials(http->tls, L"ClientContainer",
|
|
commonName, FALSE))
|
|
{
|
|
_sspiFree(http->tls);
|
|
http->tls = NULL;
|
|
|
|
http->error = EIO;
|
|
http->status = HTTP_STATUS_ERROR;
|
|
|
|
_cupsSetError(IPP_STATUS_ERROR_CUPS_PKI,
|
|
_("Unable to establish a secure connection to host."), 1);
|
|
|
|
return (-1);
|
|
}
|
|
|
|
_sspiSetAllowsAnyRoot(http->tls, TRUE);
|
|
_sspiSetAllowsExpiredCerts(http->tls, TRUE);
|
|
|
|
if (!_sspiConnect(http->tls, hostname))
|
|
{
|
|
_sspiFree(http->tls);
|
|
http->tls = NULL;
|
|
|
|
http->error = EIO;
|
|
http->status = HTTP_STATUS_ERROR;
|
|
|
|
_cupsSetError(IPP_STATUS_ERROR_CUPS_PKI,
|
|
_("Unable to establish a secure connection to host."), 1);
|
|
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
#endif // 0
|
|
|
|
|
|
/*
|
|
* 'http_sspi_alloc()' - Allocate SSPI object.
|
|
*/
|
|
|
|
static _http_sspi_t * /* O - New SSPI/SSL object */
|
|
http_sspi_alloc(void)
|
|
{
|
|
return ((_http_sspi_t *)calloc(sizeof(_http_sspi_t), 1));
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_client()' - Negotiate a TLS connection as a client.
|
|
*/
|
|
|
|
static int /* O - 0 on success, -1 on failure */
|
|
http_sspi_client(http_t *http, /* I - Client connection */
|
|
const char *hostname) /* I - Server hostname */
|
|
{
|
|
_http_sspi_t *sspi = http->tls; /* SSPI data */
|
|
DWORD dwSize; /* Size for buffer */
|
|
DWORD dwSSPIFlags; /* SSL connection attributes we want */
|
|
DWORD dwSSPIOutFlags; /* SSL connection attributes we got */
|
|
TimeStamp tsExpiry; /* Time stamp */
|
|
SECURITY_STATUS scRet; /* Status */
|
|
int cbData; /* Data count */
|
|
SecBufferDesc inBuffer; /* Array of SecBuffer structs */
|
|
SecBuffer inBuffers[2]; /* Security package buffer */
|
|
SecBufferDesc outBuffer; /* Array of SecBuffer structs */
|
|
SecBuffer outBuffers[1]; /* Security package buffer */
|
|
int ret = 0; /* Return value */
|
|
char username[1024], /* Current username */
|
|
common_name[1024]; /* CN=username */
|
|
|
|
|
|
DEBUG_printf(("4http_sspi_client(http=%p, hostname=\"%s\")", http, hostname));
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_RET_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
/*
|
|
* Lookup the client certificate...
|
|
*/
|
|
|
|
dwSize = sizeof(username);
|
|
GetUserNameA(username, &dwSize);
|
|
snprintf(common_name, sizeof(common_name), "CN=%s", username);
|
|
|
|
if (!http_sspi_find_credentials(http, L"ClientContainer", common_name))
|
|
if (!http_sspi_make_credentials(http->tls, L"ClientContainer", common_name, _HTTP_MODE_CLIENT, 10))
|
|
{
|
|
DEBUG_puts("5http_sspi_client: Unable to get client credentials.");
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Initiate a ClientHello message and generate a token.
|
|
*/
|
|
|
|
outBuffers[0].pvBuffer = NULL;
|
|
outBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
outBuffers[0].cbBuffer = 0;
|
|
|
|
outBuffer.cBuffers = 1;
|
|
outBuffer.pBuffers = outBuffers;
|
|
outBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
scRet = InitializeSecurityContext(&sspi->creds, NULL, TEXT(""), dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, &sspi->context, &outBuffer, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
if (scRet != SEC_I_CONTINUE_NEEDED)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: InitializeSecurityContext(1) failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Send response to server if there is one.
|
|
*/
|
|
|
|
if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
|
|
{
|
|
if ((cbData = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0)) <= 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: send failed: %d", WSAGetLastError()));
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
DeleteSecurityContext(&sspi->context);
|
|
return (-1);
|
|
}
|
|
|
|
DEBUG_printf(("5http_sspi_client: %d bytes of handshake data sent.", cbData));
|
|
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
outBuffers[0].pvBuffer = NULL;
|
|
}
|
|
|
|
dwSSPIFlags = ISC_REQ_MANUAL_CRED_VALIDATION |
|
|
ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_RET_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
sspi->decryptBufferUsed = 0;
|
|
|
|
/*
|
|
* Loop until the handshake is finished or an error occurs.
|
|
*/
|
|
|
|
scRet = SEC_I_CONTINUE_NEEDED;
|
|
|
|
while(scRet == SEC_I_CONTINUE_NEEDED ||
|
|
scRet == SEC_E_INCOMPLETE_MESSAGE ||
|
|
scRet == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
if (sspi->decryptBufferUsed == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
|
|
{
|
|
BYTE *temp; /* New buffer */
|
|
|
|
if (sspi->decryptBufferLength >= 262144)
|
|
{
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
DEBUG_puts("5http_sspi_client: Decryption buffer too large (>256k)");
|
|
return (-1);
|
|
}
|
|
|
|
if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: Unable to allocate %d byte buffer.", sspi->decryptBufferLength + 4096));
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
return (-1);
|
|
}
|
|
|
|
sspi->decryptBufferLength += 4096;
|
|
sspi->decryptBuffer = temp;
|
|
}
|
|
|
|
cbData = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);
|
|
|
|
if (cbData < 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: recv failed: %d", WSAGetLastError()));
|
|
return (-1);
|
|
}
|
|
else if (cbData == 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: Server unexpectedly disconnected."));
|
|
return (-1);
|
|
}
|
|
|
|
DEBUG_printf(("5http_sspi_client: %d bytes of handshake data received", cbData));
|
|
|
|
sspi->decryptBufferUsed += cbData;
|
|
}
|
|
|
|
/*
|
|
* Set up the input buffers. Buffer 0 is used to pass in data received from
|
|
* the server. Schannel will consume some or all of this. Leftover data
|
|
* (if any) will be placed in buffer 1 and given a buffer type of
|
|
* SECBUFFER_EXTRA.
|
|
*/
|
|
|
|
inBuffers[0].pvBuffer = sspi->decryptBuffer;
|
|
inBuffers[0].cbBuffer = (unsigned long)sspi->decryptBufferUsed;
|
|
inBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
inBuffers[1].pvBuffer = NULL;
|
|
inBuffers[1].cbBuffer = 0;
|
|
inBuffers[1].BufferType = SECBUFFER_EMPTY;
|
|
|
|
inBuffer.cBuffers = 2;
|
|
inBuffer.pBuffers = inBuffers;
|
|
inBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
/*
|
|
* Set up the output buffers. These are initialized to NULL so as to make it
|
|
* less likely we'll attempt to free random garbage later.
|
|
*/
|
|
|
|
outBuffers[0].pvBuffer = NULL;
|
|
outBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
outBuffers[0].cbBuffer = 0;
|
|
|
|
outBuffer.cBuffers = 1;
|
|
outBuffer.pBuffers = outBuffers;
|
|
outBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
/*
|
|
* Call InitializeSecurityContext.
|
|
*/
|
|
|
|
scRet = InitializeSecurityContext(&sspi->creds, &sspi->context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &inBuffer, 0, NULL, &outBuffer, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
/*
|
|
* If InitializeSecurityContext was successful (or if the error was one of
|
|
* the special extended ones), send the contents of the output buffer to the
|
|
* server.
|
|
*/
|
|
|
|
if (scRet == SEC_E_OK ||
|
|
scRet == SEC_I_CONTINUE_NEEDED ||
|
|
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
|
|
{
|
|
if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
|
|
{
|
|
cbData = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0);
|
|
|
|
if (cbData <= 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: send failed: %d", WSAGetLastError()));
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
DeleteSecurityContext(&sspi->context);
|
|
return (-1);
|
|
}
|
|
|
|
DEBUG_printf(("5http_sspi_client: %d bytes of handshake data sent.", cbData));
|
|
|
|
/*
|
|
* Free output buffer.
|
|
*/
|
|
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
outBuffers[0].pvBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE, then we
|
|
* need to read more data from the server and try again.
|
|
*/
|
|
|
|
if (scRet == SEC_E_INCOMPLETE_MESSAGE)
|
|
continue;
|
|
|
|
/*
|
|
* If InitializeSecurityContext returned SEC_E_OK, then the handshake
|
|
* completed successfully.
|
|
*/
|
|
|
|
if (scRet == SEC_E_OK)
|
|
{
|
|
/*
|
|
* If the "extra" buffer contains data, this is encrypted application
|
|
* protocol layer stuff. It needs to be saved. The application layer will
|
|
* later decrypt it with DecryptMessage.
|
|
*/
|
|
|
|
DEBUG_puts("5http_sspi_client: Handshake was successful.");
|
|
|
|
if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
memmove(sspi->decryptBuffer, sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer, inBuffers[1].cbBuffer);
|
|
|
|
sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
|
|
|
|
DEBUG_printf(("5http_sspi_client: %d bytes of app data was bundled with handshake data", sspi->decryptBufferUsed));
|
|
}
|
|
else
|
|
sspi->decryptBufferUsed = 0;
|
|
|
|
/*
|
|
* Bail out to quit
|
|
*/
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check for fatal error.
|
|
*/
|
|
|
|
if (FAILED(scRet))
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: InitializeSecurityContext(2) failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
|
|
* then the server just requested client authentication.
|
|
*/
|
|
|
|
if (scRet == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
/*
|
|
* Unimplemented
|
|
*/
|
|
|
|
DEBUG_printf(("5http_sspi_client: server requested client credentials."));
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Copy any leftover data from the "extra" buffer, and go around again.
|
|
*/
|
|
|
|
if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
memmove(sspi->decryptBuffer, sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer, inBuffers[1].cbBuffer);
|
|
|
|
sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
sspi->decryptBufferUsed = 0;
|
|
}
|
|
}
|
|
|
|
if (!ret)
|
|
{
|
|
/*
|
|
* Success! Get the server cert
|
|
*/
|
|
|
|
sspi->contextInitialized = TRUE;
|
|
|
|
scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (VOID *)&(sspi->remoteCert));
|
|
|
|
if (scRet != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: QueryContextAttributes failed(SECPKG_ATTR_REMOTE_CERT_CONTEXT): %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Find out how big the header/trailer will be:
|
|
*/
|
|
|
|
scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_STREAM_SIZES, &sspi->streamSizes);
|
|
|
|
if (scRet != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5http_sspi_client: QueryContextAttributes failed(SECPKG_ATTR_STREAM_SIZES): %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_create_credential()' - Create an SSPI certificate context.
|
|
*/
|
|
|
|
static PCCERT_CONTEXT /* O - Certificate context */
|
|
http_sspi_create_credential(
|
|
http_credential_t *cred) /* I - Credential */
|
|
{
|
|
if (cred)
|
|
return (CertCreateCertificateContext(X509_ASN_ENCODING, cred->data, cred->datalen));
|
|
else
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_find_credentials()' - Retrieve a TLS certificate from the system store.
|
|
*/
|
|
|
|
static BOOL /* O - 1 on success, 0 on failure */
|
|
http_sspi_find_credentials(
|
|
http_t *http, /* I - HTTP connection */
|
|
const LPWSTR container, /* I - Cert container name */
|
|
const char *common_name) /* I - Common name of certificate */
|
|
{
|
|
_http_sspi_t *sspi = http->tls; /* SSPI data */
|
|
HCERTSTORE store = NULL; /* Certificate store */
|
|
PCCERT_CONTEXT storedContext = NULL; /* Context created from the store */
|
|
DWORD dwSize = 0; /* 32 bit size */
|
|
PBYTE p = NULL; /* Temporary storage */
|
|
HCRYPTPROV hProv = (HCRYPTPROV)NULL;
|
|
/* Handle to a CSP */
|
|
CERT_NAME_BLOB sib; /* Arbitrary array of bytes */
|
|
SCHANNEL_CRED SchannelCred; /* Schannel credential data */
|
|
TimeStamp tsExpiry; /* Time stamp */
|
|
SECURITY_STATUS Status; /* Status */
|
|
BOOL ok = TRUE; /* Return value */
|
|
|
|
|
|
if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
|
|
{
|
|
if (GetLastError() == NTE_EXISTS)
|
|
{
|
|
if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: CryptAcquireContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");
|
|
|
|
if (!store)
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: CertOpenSystemStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
dwSize = 0;
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
p = (PBYTE)malloc(dwSize);
|
|
|
|
if (!p)
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: malloc failed for %d bytes.", dwSize));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
sib.cbData = dwSize;
|
|
sib.pbData = p;
|
|
|
|
storedContext = CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &sib, NULL);
|
|
|
|
if (!storedContext)
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: Unable to find credentials for \"%s\".", common_name));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(&SchannelCred, sizeof(SchannelCred));
|
|
|
|
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
SchannelCred.cCreds = 1;
|
|
SchannelCred.paCred = &storedContext;
|
|
|
|
/*
|
|
* Set supported protocols (can also be overriden in the registry...)
|
|
*/
|
|
|
|
#ifdef SP_PROT_TLS1_2_SERVER
|
|
if (http->mode == _HTTP_MODE_SERVER)
|
|
{
|
|
if (tls_min_version > _HTTP_TLS_1_1)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER;
|
|
else if (tls_min_version > _HTTP_TLS_1_0)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER;
|
|
else if (tls_min_version == _HTTP_TLS_SSL3)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_0_SERVER | SP_PROT_SSL3_SERVER;
|
|
else
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_0_SERVER;
|
|
}
|
|
else
|
|
{
|
|
if (tls_min_version > _HTTP_TLS_1_1)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT;
|
|
else if (tls_min_version > _HTTP_TLS_1_0)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT;
|
|
else if (tls_min_version == _HTTP_TLS_SSL3)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_0_CLIENT | SP_PROT_SSL3_CLIENT;
|
|
else
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_0_CLIENT;
|
|
}
|
|
|
|
#else
|
|
if (http->mode == _HTTP_MODE_SERVER)
|
|
{
|
|
if (tls_min_version == _HTTP_TLS_SSL3)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER | SP_PROT_SSL3_SERVER;
|
|
else
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER;
|
|
}
|
|
else
|
|
{
|
|
if (tls_min_version == _HTTP_TLS_SSL3)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT | SP_PROT_SSL3_CLIENT;
|
|
else
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT;
|
|
}
|
|
#endif /* SP_PROT_TLS1_2_SERVER */
|
|
|
|
/* TODO: Support _HTTP_TLS_ALLOW_RC4, _HTTP_TLS_ALLOW_DH, and _HTTP_TLS_DENY_CBC options; right now we'll rely on Windows registry to enable/disable RC4/DH/CBC... */
|
|
|
|
/*
|
|
* Create an SSPI credential.
|
|
*/
|
|
|
|
Status = AcquireCredentialsHandle(NULL, UNISP_NAME, http->mode == _HTTP_MODE_SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &sspi->creds, &tsExpiry);
|
|
if (Status != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5http_sspi_find_credentials: AcquireCredentialsHandle failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), Status)));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
/*
|
|
* Cleanup
|
|
*/
|
|
|
|
if (storedContext)
|
|
CertFreeCertificateContext(storedContext);
|
|
|
|
if (p)
|
|
free(p);
|
|
|
|
if (store)
|
|
CertCloseStore(store, 0);
|
|
|
|
if (hProv)
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
return (ok);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_free()' - Close a connection and free resources.
|
|
*/
|
|
|
|
static void
|
|
http_sspi_free(_http_sspi_t *sspi) /* I - SSPI data */
|
|
{
|
|
if (!sspi)
|
|
return;
|
|
|
|
if (sspi->contextInitialized)
|
|
DeleteSecurityContext(&sspi->context);
|
|
|
|
if (sspi->decryptBuffer)
|
|
free(sspi->decryptBuffer);
|
|
|
|
if (sspi->readBuffer)
|
|
free(sspi->readBuffer);
|
|
|
|
if (sspi->writeBuffer)
|
|
free(sspi->writeBuffer);
|
|
|
|
if (sspi->localCert)
|
|
CertFreeCertificateContext(sspi->localCert);
|
|
|
|
if (sspi->remoteCert)
|
|
CertFreeCertificateContext(sspi->remoteCert);
|
|
|
|
free(sspi);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_make_credentials()' - Create a TLS certificate in the system store.
|
|
*/
|
|
|
|
static BOOL /* O - 1 on success, 0 on failure */
|
|
http_sspi_make_credentials(
|
|
_http_sspi_t *sspi, /* I - SSPI data */
|
|
const LPWSTR container, /* I - Cert container name */
|
|
const char *common_name, /* I - Common name of certificate */
|
|
_http_mode_t mode, /* I - Client or server? */
|
|
int years) /* I - Years until expiration */
|
|
{
|
|
HCERTSTORE store = NULL; /* Certificate store */
|
|
PCCERT_CONTEXT storedContext = NULL; /* Context created from the store */
|
|
PCCERT_CONTEXT createdContext = NULL; /* Context created by us */
|
|
DWORD dwSize = 0; /* 32 bit size */
|
|
PBYTE p = NULL; /* Temporary storage */
|
|
HCRYPTPROV hProv = (HCRYPTPROV)NULL;
|
|
/* Handle to a CSP */
|
|
CERT_NAME_BLOB sib; /* Arbitrary array of bytes */
|
|
SCHANNEL_CRED SchannelCred; /* Schannel credential data */
|
|
TimeStamp tsExpiry; /* Time stamp */
|
|
SECURITY_STATUS Status; /* Status */
|
|
HCRYPTKEY hKey = (HCRYPTKEY)NULL; /* Handle to crypto key */
|
|
CRYPT_KEY_PROV_INFO kpi; /* Key container info */
|
|
SYSTEMTIME et; /* System time */
|
|
CERT_EXTENSIONS exts; /* Array of cert extensions */
|
|
CRYPT_KEY_PROV_INFO ckp; /* Handle to crypto key */
|
|
BOOL ok = TRUE; /* Return value */
|
|
|
|
|
|
DEBUG_printf(("4http_sspi_make_credentials(sspi=%p, container=%p, common_name=\"%s\", mode=%d, years=%d)", sspi, container, common_name, mode, years));
|
|
|
|
if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
|
|
{
|
|
if (GetLastError() == NTE_EXISTS)
|
|
{
|
|
if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CryptAcquireContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");
|
|
|
|
if (!store)
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertOpenSystemStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
dwSize = 0;
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
p = (PBYTE)malloc(dwSize);
|
|
|
|
if (!p)
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: malloc failed for %d bytes", dwSize));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Create a private key and self-signed certificate...
|
|
*/
|
|
|
|
if (!CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hKey))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CryptGenKey failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(&kpi, sizeof(kpi));
|
|
kpi.pwszContainerName = (LPWSTR)container;
|
|
kpi.pwszProvName = MS_DEF_PROV_W;
|
|
kpi.dwProvType = PROV_RSA_FULL;
|
|
kpi.dwFlags = CERT_SET_KEY_CONTEXT_PROP_ID;
|
|
kpi.dwKeySpec = AT_KEYEXCHANGE;
|
|
|
|
GetSystemTime(&et);
|
|
et.wYear += years;
|
|
if (et.wMonth == 2 && et.wDay == 29)
|
|
et.wDay = 28; /* Avoid Feb 29th due to leap years */
|
|
|
|
ZeroMemory(&exts, sizeof(exts));
|
|
|
|
createdContext = CertCreateSelfSignCertificate(hProv, &sib, 0, &kpi, NULL, NULL, &et, &exts);
|
|
|
|
if (!createdContext)
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertCreateSelfSignCertificate failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Add the created context to the named store, and associate it with the named
|
|
* container...
|
|
*/
|
|
|
|
if (!CertAddCertificateContextToStore(store, createdContext, CERT_STORE_ADD_REPLACE_EXISTING, &storedContext))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertAddCertificateContextToStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(&ckp, sizeof(ckp));
|
|
ckp.pwszContainerName = (LPWSTR) container;
|
|
ckp.pwszProvName = MS_DEF_PROV_W;
|
|
ckp.dwProvType = PROV_RSA_FULL;
|
|
ckp.dwFlags = CRYPT_MACHINE_KEYSET;
|
|
ckp.dwKeySpec = AT_KEYEXCHANGE;
|
|
|
|
if (!CertSetCertificateContextProperty(storedContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &ckp))
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: CertSetCertificateContextProperty failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Get a handle to use the certificate...
|
|
*/
|
|
|
|
ZeroMemory(&SchannelCred, sizeof(SchannelCred));
|
|
|
|
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
SchannelCred.cCreds = 1;
|
|
SchannelCred.paCred = &storedContext;
|
|
|
|
/*
|
|
* SSPI doesn't seem to like it if grbitEnabledProtocols is set for a client.
|
|
*/
|
|
|
|
if (mode == _HTTP_MODE_SERVER)
|
|
SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3TLS1;
|
|
|
|
/*
|
|
* Create an SSPI credential.
|
|
*/
|
|
|
|
Status = AcquireCredentialsHandle(NULL, UNISP_NAME, mode == _HTTP_MODE_SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &sspi->creds, &tsExpiry);
|
|
if (Status != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5http_sspi_make_credentials: AcquireCredentialsHandle failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), Status)));
|
|
ok = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
/*
|
|
* Cleanup
|
|
*/
|
|
|
|
if (hKey)
|
|
CryptDestroyKey(hKey);
|
|
|
|
if (createdContext)
|
|
CertFreeCertificateContext(createdContext);
|
|
|
|
if (storedContext)
|
|
CertFreeCertificateContext(storedContext);
|
|
|
|
if (p)
|
|
free(p);
|
|
|
|
if (store)
|
|
CertCloseStore(store, 0);
|
|
|
|
if (hProv)
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
return (ok);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_server()' - Negotiate a TLS connection as a server.
|
|
*/
|
|
|
|
static int /* O - 0 on success, -1 on failure */
|
|
http_sspi_server(http_t *http, /* I - HTTP connection */
|
|
const char *hostname) /* I - Hostname of server */
|
|
{
|
|
_http_sspi_t *sspi = http->tls; /* I - SSPI data */
|
|
char common_name[512]; /* Common name for cert */
|
|
DWORD dwSSPIFlags; /* SSL connection attributes we want */
|
|
DWORD dwSSPIOutFlags; /* SSL connection attributes we got */
|
|
TimeStamp tsExpiry; /* Time stamp */
|
|
SECURITY_STATUS scRet; /* SSPI Status */
|
|
SecBufferDesc inBuffer; /* Array of SecBuffer structs */
|
|
SecBuffer inBuffers[2]; /* Security package buffer */
|
|
SecBufferDesc outBuffer; /* Array of SecBuffer structs */
|
|
SecBuffer outBuffers[1]; /* Security package buffer */
|
|
int num = 0; /* 32 bit status value */
|
|
BOOL fInitContext = TRUE; /* Has the context been init'd? */
|
|
int ret = 0; /* Return value */
|
|
|
|
|
|
DEBUG_printf(("4http_sspi_server(http=%p, hostname=\"%s\")", http, hostname));
|
|
|
|
dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT |
|
|
ASC_REQ_REPLAY_DETECT |
|
|
ASC_REQ_CONFIDENTIALITY |
|
|
ASC_REQ_EXTENDED_ERROR |
|
|
ASC_REQ_ALLOCATE_MEMORY |
|
|
ASC_REQ_STREAM;
|
|
|
|
sspi->decryptBufferUsed = 0;
|
|
|
|
/*
|
|
* Lookup the server certificate...
|
|
*/
|
|
|
|
snprintf(common_name, sizeof(common_name), "CN=%s", hostname);
|
|
|
|
if (!http_sspi_find_credentials(http, L"ServerContainer", common_name))
|
|
if (!http_sspi_make_credentials(http->tls, L"ServerContainer", common_name, _HTTP_MODE_SERVER, 10))
|
|
{
|
|
DEBUG_puts("5http_sspi_server: Unable to get server credentials.");
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Set OutBuffer for AcceptSecurityContext call
|
|
*/
|
|
|
|
outBuffer.cBuffers = 1;
|
|
outBuffer.pBuffers = outBuffers;
|
|
outBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
scRet = SEC_I_CONTINUE_NEEDED;
|
|
|
|
while (scRet == SEC_I_CONTINUE_NEEDED ||
|
|
scRet == SEC_E_INCOMPLETE_MESSAGE ||
|
|
scRet == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
if (sspi->decryptBufferUsed == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
|
|
{
|
|
BYTE *temp; /* New buffer */
|
|
|
|
if (sspi->decryptBufferLength >= 262144)
|
|
{
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
DEBUG_puts("5http_sspi_server: Decryption buffer too large (>256k)");
|
|
return (-1);
|
|
}
|
|
|
|
if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
|
|
{
|
|
DEBUG_printf(("5http_sspi_server: Unable to allocate %d byte buffer.", sspi->decryptBufferLength + 4096));
|
|
WSASetLastError(E_OUTOFMEMORY);
|
|
return (-1);
|
|
}
|
|
|
|
sspi->decryptBufferLength += 4096;
|
|
sspi->decryptBuffer = temp;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
num = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);
|
|
|
|
if (num == -1 && WSAGetLastError() == WSAEWOULDBLOCK)
|
|
Sleep(1);
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (num < 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_server: recv failed: %d", WSAGetLastError()));
|
|
return (-1);
|
|
}
|
|
else if (num == 0)
|
|
{
|
|
DEBUG_puts("5http_sspi_server: client disconnected");
|
|
return (-1);
|
|
}
|
|
|
|
DEBUG_printf(("5http_sspi_server: received %d (handshake) bytes from client.", num));
|
|
sspi->decryptBufferUsed += num;
|
|
}
|
|
|
|
/*
|
|
* InBuffers[1] is for getting extra data that SSPI/SCHANNEL doesn't process
|
|
* on this run around the loop.
|
|
*/
|
|
|
|
inBuffers[0].pvBuffer = sspi->decryptBuffer;
|
|
inBuffers[0].cbBuffer = (unsigned long)sspi->decryptBufferUsed;
|
|
inBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
inBuffers[1].pvBuffer = NULL;
|
|
inBuffers[1].cbBuffer = 0;
|
|
inBuffers[1].BufferType = SECBUFFER_EMPTY;
|
|
|
|
inBuffer.cBuffers = 2;
|
|
inBuffer.pBuffers = inBuffers;
|
|
inBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
/*
|
|
* Initialize these so if we fail, pvBuffer contains NULL, so we don't try to
|
|
* free random garbage at the quit.
|
|
*/
|
|
|
|
outBuffers[0].pvBuffer = NULL;
|
|
outBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
outBuffers[0].cbBuffer = 0;
|
|
|
|
scRet = AcceptSecurityContext(&sspi->creds, (fInitContext?NULL:&sspi->context), &inBuffer, dwSSPIFlags, SECURITY_NATIVE_DREP, (fInitContext?&sspi->context:NULL), &outBuffer, &dwSSPIOutFlags, &tsExpiry);
|
|
|
|
fInitContext = FALSE;
|
|
|
|
if (scRet == SEC_E_OK ||
|
|
scRet == SEC_I_CONTINUE_NEEDED ||
|
|
(FAILED(scRet) && ((dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR) != 0)))
|
|
{
|
|
if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
|
|
{
|
|
/*
|
|
* Send response to server if there is one.
|
|
*/
|
|
|
|
num = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0);
|
|
|
|
if (num <= 0)
|
|
{
|
|
DEBUG_printf(("5http_sspi_server: handshake send failed: %d", WSAGetLastError()));
|
|
return (-1);
|
|
}
|
|
|
|
DEBUG_printf(("5http_sspi_server: sent %d handshake bytes to client.", outBuffers[0].cbBuffer));
|
|
|
|
FreeContextBuffer(outBuffers[0].pvBuffer);
|
|
outBuffers[0].pvBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
if (scRet == SEC_E_OK)
|
|
{
|
|
/*
|
|
* If there's extra data then save it for next time we go to decrypt.
|
|
*/
|
|
|
|
if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
memcpy(sspi->decryptBuffer, (LPBYTE)(sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer);
|
|
sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
sspi->decryptBufferUsed = 0;
|
|
}
|
|
break;
|
|
}
|
|
else if (FAILED(scRet) && scRet != SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
DEBUG_printf(("5http_sspi_server: AcceptSecurityContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (scRet != SEC_E_INCOMPLETE_MESSAGE &&
|
|
scRet != SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
memcpy(sspi->decryptBuffer, (LPBYTE)(sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer);
|
|
sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
sspi->decryptBufferUsed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ret)
|
|
{
|
|
sspi->contextInitialized = TRUE;
|
|
|
|
/*
|
|
* Find out how big the header will be:
|
|
*/
|
|
|
|
scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_STREAM_SIZES, &sspi->streamSizes);
|
|
|
|
if (scRet != SEC_E_OK)
|
|
{
|
|
DEBUG_printf(("5http_sspi_server: QueryContextAttributes failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_strerror()' - Return a string for the specified error code.
|
|
*/
|
|
|
|
static const char * /* O - String for error */
|
|
http_sspi_strerror(char *buffer, /* I - Error message buffer */
|
|
size_t bufsize, /* I - Size of buffer */
|
|
DWORD code) /* I - Error code */
|
|
{
|
|
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, 0, buffer, bufsize, NULL))
|
|
{
|
|
/*
|
|
* Strip trailing CR + LF...
|
|
*/
|
|
|
|
char *ptr; /* Pointer into error message */
|
|
|
|
for (ptr = buffer + strlen(buffer) - 1; ptr >= buffer; ptr --)
|
|
if (*ptr == '\n' || *ptr == '\r')
|
|
*ptr = '\0';
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
snprintf(buffer, bufsize, "Unknown error %x", code);
|
|
|
|
return (buffer);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'http_sspi_verify()' - Verify a certificate.
|
|
*/
|
|
|
|
static DWORD /* O - Error code (0 == No error) */
|
|
http_sspi_verify(
|
|
PCCERT_CONTEXT cert, /* I - Server certificate */
|
|
const char *common_name, /* I - Common name */
|
|
DWORD dwCertFlags) /* I - Verification flags */
|
|
{
|
|
HTTPSPolicyCallbackData httpsPolicy; /* HTTPS Policy Struct */
|
|
CERT_CHAIN_POLICY_PARA policyPara; /* Cert chain policy parameters */
|
|
CERT_CHAIN_POLICY_STATUS policyStatus;/* Cert chain policy status */
|
|
CERT_CHAIN_PARA chainPara; /* Used for searching and matching criteria */
|
|
PCCERT_CHAIN_CONTEXT chainContext = NULL;
|
|
/* Certificate chain */
|
|
PWSTR commonNameUnicode = NULL;
|
|
/* Unicode common name */
|
|
LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
|
|
szOID_SERVER_GATED_CRYPTO,
|
|
szOID_SGC_NETSCAPE };
|
|
/* How are we using this certificate? */
|
|
DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
|
|
/* Number of ites in rgszUsages */
|
|
DWORD count; /* 32 bit count variable */
|
|
DWORD status; /* Return value */
|
|
#ifdef DEBUG
|
|
char error[1024]; /* Error message string */
|
|
#endif /* DEBUG */
|
|
|
|
|
|
if (!cert)
|
|
return (SEC_E_WRONG_PRINCIPAL);
|
|
|
|
/*
|
|
* Convert common name to Unicode.
|
|
*/
|
|
|
|
if (!common_name || !*common_name)
|
|
return (SEC_E_WRONG_PRINCIPAL);
|
|
|
|
count = MultiByteToWideChar(CP_ACP, 0, common_name, -1, NULL, 0);
|
|
commonNameUnicode = LocalAlloc(LMEM_FIXED, count * sizeof(WCHAR));
|
|
if (!commonNameUnicode)
|
|
return (SEC_E_INSUFFICIENT_MEMORY);
|
|
|
|
if (!MultiByteToWideChar(CP_ACP, 0, common_name, -1, commonNameUnicode, count))
|
|
{
|
|
LocalFree(commonNameUnicode);
|
|
return (SEC_E_WRONG_PRINCIPAL);
|
|
}
|
|
|
|
/*
|
|
* Build certificate chain.
|
|
*/
|
|
|
|
ZeroMemory(&chainPara, sizeof(chainPara));
|
|
|
|
chainPara.cbSize = sizeof(chainPara);
|
|
chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
|
|
chainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages;
|
|
chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
|
|
|
|
if (!CertGetCertificateChain(NULL, cert, NULL, cert->hCertStore, &chainPara, 0, NULL, &chainContext))
|
|
{
|
|
status = GetLastError();
|
|
|
|
DEBUG_printf(("CertGetCertificateChain returned: %s", http_sspi_strerror(error, sizeof(error), status)));
|
|
|
|
LocalFree(commonNameUnicode);
|
|
return (status);
|
|
}
|
|
|
|
/*
|
|
* Validate certificate chain.
|
|
*/
|
|
|
|
ZeroMemory(&httpsPolicy, sizeof(HTTPSPolicyCallbackData));
|
|
httpsPolicy.cbStruct = sizeof(HTTPSPolicyCallbackData);
|
|
httpsPolicy.dwAuthType = AUTHTYPE_SERVER;
|
|
httpsPolicy.fdwChecks = dwCertFlags;
|
|
httpsPolicy.pwszServerName = commonNameUnicode;
|
|
|
|
memset(&policyPara, 0, sizeof(policyPara));
|
|
policyPara.cbSize = sizeof(policyPara);
|
|
policyPara.pvExtraPolicyPara = &httpsPolicy;
|
|
|
|
memset(&policyStatus, 0, sizeof(policyStatus));
|
|
policyStatus.cbSize = sizeof(policyStatus);
|
|
|
|
if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext, &policyPara, &policyStatus))
|
|
{
|
|
status = GetLastError();
|
|
|
|
DEBUG_printf(("CertVerifyCertificateChainPolicy returned %s", http_sspi_strerror(error, sizeof(error), status)));
|
|
}
|
|
else if (policyStatus.dwError)
|
|
status = policyStatus.dwError;
|
|
else
|
|
status = SEC_E_OK;
|
|
|
|
if (chainContext)
|
|
CertFreeCertificateChain(chainContext);
|
|
|
|
if (commonNameUnicode)
|
|
LocalFree(commonNameUnicode);
|
|
|
|
return (status);
|
|
}
|