| /* |
| * TLS support for CUPS on Windows using the Security Support Provider |
| * Interface (SSPI). |
| * |
| * Copyright 2010-2017 by Apple Inc. |
| * |
| * These coded instructions, statements, and computer programs are the |
| * property of Apple Inc. and are protected by Federal copyright |
| * law. Distribution and use rights are outlined in the file "LICENSE.txt" |
| * which should have been included with this file. If this file is |
| * missing or damaged, see the license at "http://www.cups.org/". |
| * |
| * This file is subject to the Apple OS-Developed Software exception. |
| */ |
| |
| /**** 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 */ |
| |
| |
| /* |
| * 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 (CertNameToStr(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 */ |
| _cups_md5_state_t md5_state; /* MD5 state */ |
| 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 (CertNameToStr(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)); |
| |
| _cupsMD5Init(&md5_state); |
| _cupsMD5Append(&md5_state, first->data, (int)first->datalen); |
| _cupsMD5Finish(&md5_state, 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 (!CertStrToName(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 (!CertStrToName(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 (!CertStrToName(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 (!CertStrToName(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 */ |
| { |
| if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0) |
| tls_options = options; |
| } |
| |
| |
| /* |
| * '_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][0]) |
| { |
| /* |
| * 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); |
| GetUserName(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 (!CertStrToName(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 (!CertStrToName(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_options & _HTTP_TLS_DENY_TLS10) |
| SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER; |
| else if (tls_options & _HTTP_TLS_ALLOW_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_options & _HTTP_TLS_DENY_TLS10) |
| SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT; |
| else if (tls_options & _HTTP_TLS_ALLOW_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_options & _HTTP_TLS_ALLOW_SSL3) |
| SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER | SP_PROT_SSL3_SERVER; |
| else |
| SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER; |
| } |
| else |
| { |
| if (tls_options & _HTTP_TLS_ALLOW_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 (!CertStrToName(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 (!CertStrToName(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; |
| |
| 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 (FormatMessage(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); |
| } |