/* * "$Id$" * * Windows SSPI SSL implementation for CUPS. * * Copyright 2010-2011 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 * file is missing or damaged, see the license at "http://www.cups.org/". * * Contents: * * sspi_alloc() - Allocate SSPI ssl object * _sspiGetCredentials() - Retrieve an SSL/TLS certificate from the * system store If one cannot be found, one is * created. * _sspiConnect() - Make an SSL connection. This function * assumes a TCP/IP connection has already been * successfully made * _sspiAccept() - Accept an SSL/TLS connection * _sspiSetAllowsAnyRoot() - Set the client cert policy for untrusted * root certs * _sspiSetAllowsExpiredCerts() - Set the client cert policy for expired root * certs * _sspiWrite() - Write a buffer to an ssl socket * _sspiRead() - Read a buffer from an ssl socket * _sspiPending() - Returns the number of available bytes * _sspiFree() - Close a connection and free resources * sspi_verify_certificate() - Verify a server certificate */ /* * Include necessary headers... */ #include "sspi-private.h" #include "debug-private.h" /* required to link this library for certificate functions */ #pragma comment(lib, "Crypt32.lib") #pragma comment(lib, "Secur32.lib") #pragma comment(lib, "Ws2_32.lib") #if !defined(SECURITY_FLAG_IGNORE_UNKNOWN_CA) # define SECURITY_FLAG_IGNORE_UNKNOWN_CA 0x00000100 /* Untrusted root */ #endif #if !defined(SECURITY_FLAG_IGNORE_CERT_DATE_INVALID) # define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID 0x00002000 /* Expired X509 Cert. */ #endif static DWORD sspi_verify_certificate(PCCERT_CONTEXT serverCert, const CHAR *serverName, DWORD dwCertFlags); /* * 'sspi_alloc()' - Allocate SSPI ssl object */ _sspi_struct_t* /* O - New SSPI/SSL object */ _sspiAlloc(void) { _sspi_struct_t *conn = calloc(sizeof(_sspi_struct_t), 1); if (conn) conn->sock = INVALID_SOCKET; return (conn); } /* * '_sspiGetCredentials()' - Retrieve an SSL/TLS certificate from the system store * If one cannot be found, one is created. */ BOOL /* O - 1 on success, 0 on failure */ _sspiGetCredentials(_sspi_struct_t *conn, /* I - Client connection */ const LPWSTR container, /* I - Cert container name */ const TCHAR *cn, /* I - Common name of certificate */ BOOL isServer) /* I - Is caller a server? */ { 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 */ if (!conn) return (FALSE); if (!cn) return (FALSE); 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(("_sspiGetCredentials: CryptAcquireContext failed: %x\n", 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(("_sspiGetCredentials: CertOpenSystemStore failed: %x\n", GetLastError())); ok = FALSE; goto cleanup; } dwSize = 0; if (!CertStrToName(X509_ASN_ENCODING, cn, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL)) { DEBUG_printf(("_sspiGetCredentials: CertStrToName failed: %x\n", GetLastError())); ok = FALSE; goto cleanup; } p = (PBYTE) malloc(dwSize); if (!p) { DEBUG_printf(("_sspiGetCredentials: malloc failed for %d bytes", dwSize)); ok = FALSE; goto cleanup; } if (!CertStrToName(X509_ASN_ENCODING, cn, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL)) { DEBUG_printf(("_sspiGetCredentials: CertStrToName failed: %x", 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) { /* * If we couldn't find the context, then we'll * create a new one */ if (!CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hKey)) { DEBUG_printf(("_sspiGetCredentials: CryptGenKey failed: %x", 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 += 10; ZeroMemory(&exts, sizeof(exts)); createdContext = CertCreateSelfSignCertificate(hProv, &sib, 0, &kpi, NULL, NULL, &et, &exts); if (!createdContext) { DEBUG_printf(("_sspiGetCredentials: CertCreateSelfSignCertificate failed: %x", GetLastError())); ok = FALSE; goto cleanup; } if (!CertAddCertificateContextToStore(store, createdContext, CERT_STORE_ADD_REPLACE_EXISTING, &storedContext)) { DEBUG_printf(("_sspiGetCredentials: CertAddCertificateContextToStore failed: %x", 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(("_sspiGetCredentials: CertSetCertificateContextProperty failed: %x", GetLastError())); ok = FALSE; goto cleanup; } } 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 (isServer) SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3TLS1; /* * Create an SSPI credential. */ Status = AcquireCredentialsHandle(NULL, UNISP_NAME, isServer ? SECPKG_CRED_INBOUND:SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &conn->creds, &tsExpiry); if (Status != SEC_E_OK) { DEBUG_printf(("_sspiGetCredentials: AcquireCredentialsHandle failed: %x", 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); } /* * '_sspiConnect()' - Make an SSL connection. This function * assumes a TCP/IP connection has already * been successfully made */ BOOL /* O - 1 on success, 0 on failure */ _sspiConnect(_sspi_struct_t *conn, /* I - Client connection */ const CHAR *hostname) /* I - Server hostname */ { PCCERT_CONTEXT serverCert; /* Server certificate */ DWORD dwSSPIFlags; /* SSL connection attributes we want */ DWORD dwSSPIOutFlags; /* SSL connection attributes we got */ TimeStamp tsExpiry; /* Time stamp */ SECURITY_STATUS scRet; /* Status */ DWORD 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 */ BOOL ok = TRUE; /* Return value */ serverCert = NULL; dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; /* * 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(&conn->creds, NULL, TEXT(""), dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, &conn->context, &outBuffer, &dwSSPIOutFlags, &tsExpiry); if (scRet != SEC_I_CONTINUE_NEEDED) { DEBUG_printf(("_sspiConnect: InitializeSecurityContext(1) failed: %x", scRet)); ok = FALSE; goto cleanup; } /* * Send response to server if there is one. */ if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer) { cbData = send(conn->sock, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0); if ((cbData == SOCKET_ERROR) || !cbData) { DEBUG_printf(("_sspiConnect: send failed: %d", WSAGetLastError())); FreeContextBuffer(outBuffers[0].pvBuffer); DeleteSecurityContext(&conn->context); ok = FALSE; goto cleanup; } DEBUG_printf(("_sspiConnect: %d bytes of handshake data sent", cbData)); /* * Free output buffer. */ 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; conn->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 ((conn->decryptBufferUsed == 0) || (scRet == SEC_E_INCOMPLETE_MESSAGE)) { if (conn->decryptBufferLength <= conn->decryptBufferUsed) { conn->decryptBufferLength += 4096; conn->decryptBuffer = (BYTE*) realloc(conn->decryptBuffer, conn->decryptBufferLength); if (!conn->decryptBuffer) { DEBUG_printf(("_sspiConnect: unable to allocate %d byte decrypt buffer", conn->decryptBufferLength)); SetLastError(E_OUTOFMEMORY); ok = FALSE; goto cleanup; } } cbData = recv(conn->sock, conn->decryptBuffer + conn->decryptBufferUsed, (int) (conn->decryptBufferLength - conn->decryptBufferUsed), 0); if (cbData == SOCKET_ERROR) { DEBUG_printf(("_sspiConnect: recv failed: %d", WSAGetLastError())); ok = FALSE; goto cleanup; } else if (cbData == 0) { DEBUG_printf(("_sspiConnect: server unexpectedly disconnected")); ok = FALSE; goto cleanup; } DEBUG_printf(("_sspiConnect: %d bytes of handshake data received", cbData)); conn->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 = conn->decryptBuffer; inBuffers[0].cbBuffer = (unsigned long) conn->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(&conn->creds, &conn->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 contends 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(conn->sock, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0); if ((cbData == SOCKET_ERROR) || !cbData) { DEBUG_printf(("_sspiConnect: send failed: %d", WSAGetLastError())); FreeContextBuffer(outBuffers[0].pvBuffer); DeleteSecurityContext(&conn->context); ok = FALSE; goto cleanup; } DEBUG_printf(("_sspiConnect: %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_printf(("_sspiConnect: Handshake was successful")); if (inBuffers[1].BufferType == SECBUFFER_EXTRA) { if (conn->decryptBufferLength < inBuffers[1].cbBuffer) { conn->decryptBuffer = realloc(conn->decryptBuffer, inBuffers[1].cbBuffer); if (!conn->decryptBuffer) { DEBUG_printf(("_sspiConnect: unable to allocate %d bytes for decrypt buffer", inBuffers[1].cbBuffer)); SetLastError(E_OUTOFMEMORY); ok = FALSE; goto cleanup; } } memmove(conn->decryptBuffer, conn->decryptBuffer + (conn->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer); conn->decryptBufferUsed = inBuffers[1].cbBuffer; DEBUG_printf(("_sspiConnect: %d bytes of app data was bundled with handshake data", conn->decryptBufferUsed)); } else conn->decryptBufferUsed = 0; /* * Bail out to quit */ break; } /* * Check for fatal error. */ if (FAILED(scRet)) { DEBUG_printf(("_sspiConnect: InitializeSecurityContext(2) failed: %x", scRet)); ok = FALSE; break; } /* * If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS, * then the server just requested client authentication. */ if (scRet == SEC_I_INCOMPLETE_CREDENTIALS) { /* * Unimplemented */ DEBUG_printf(("_sspiConnect: server requested client credentials")); ok = FALSE; break; } /* * Copy any leftover data from the "extra" buffer, and go around * again. */ if (inBuffers[1].BufferType == SECBUFFER_EXTRA) { memmove(conn->decryptBuffer, conn->decryptBuffer + (conn->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer); conn->decryptBufferUsed = inBuffers[1].cbBuffer; } else { conn->decryptBufferUsed = 0; } } if (ok) { conn->contextInitialized = TRUE; /* * Get the server cert */ scRet = QueryContextAttributes(&conn->context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (VOID*) &serverCert ); if (scRet != SEC_E_OK) { DEBUG_printf(("_sspiConnect: QueryContextAttributes failed(SECPKG_ATTR_REMOTE_CERT_CONTEXT): %x", scRet)); ok = FALSE; goto cleanup; } scRet = sspi_verify_certificate(serverCert, hostname, conn->certFlags); if (scRet != SEC_E_OK) { DEBUG_printf(("_sspiConnect: sspi_verify_certificate failed: %x", scRet)); ok = FALSE; goto cleanup; } /* * Find out how big the header/trailer will be: */ scRet = QueryContextAttributes(&conn->context, SECPKG_ATTR_STREAM_SIZES, &conn->streamSizes); if (scRet != SEC_E_OK) { DEBUG_printf(("_sspiConnect: QueryContextAttributes failed(SECPKG_ATTR_STREAM_SIZES): %x", scRet)); ok = FALSE; } } cleanup: if (serverCert) CertFreeCertificateContext(serverCert); return (ok); } /* * '_sspiAccept()' - Accept an SSL/TLS connection */ BOOL /* O - 1 on success, 0 on failure */ _sspiAccept(_sspi_struct_t *conn) /* I - Client connection */ { 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 */ DWORD num = 0; /* 32 bit status value */ BOOL fInitContext = TRUE; /* Has the context been init'd? */ BOOL ok = TRUE; /* Return value */ if (!conn) return (FALSE); dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | ASC_REQ_EXTENDED_ERROR | ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM; conn->decryptBufferUsed = 0; /* * 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 ((conn->decryptBufferUsed == 0) || (scRet == SEC_E_INCOMPLETE_MESSAGE)) { if (conn->decryptBufferLength <= conn->decryptBufferUsed) { conn->decryptBufferLength += 4096; conn->decryptBuffer = (BYTE*) realloc(conn->decryptBuffer, conn->decryptBufferLength); if (!conn->decryptBuffer) { DEBUG_printf(("_sspiAccept: unable to allocate %d byte decrypt buffer", conn->decryptBufferLength)); ok = FALSE; goto cleanup; } } for (;;) { num = recv(conn->sock, conn->decryptBuffer + conn->decryptBufferUsed, (int)(conn->decryptBufferLength - conn->decryptBufferUsed), 0); if ((num == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK)) Sleep(1); else break; } if (num == SOCKET_ERROR) { DEBUG_printf(("_sspiAccept: recv failed: %d", WSAGetLastError())); ok = FALSE; goto cleanup; } else if (num == 0) { DEBUG_printf(("_sspiAccept: client disconnected")); ok = FALSE; goto cleanup; } DEBUG_printf(("_sspiAccept: received %d (handshake) bytes from client", num)); conn->decryptBufferUsed += num; } /* * InBuffers[1] is for getting extra data that * SSPI/SCHANNEL doesn't proccess on this * run around the loop. */ inBuffers[0].pvBuffer = conn->decryptBuffer; inBuffers[0].cbBuffer = (unsigned long) conn->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(&conn->creds, (fInitContext?NULL:&conn->context), &inBuffer, dwSSPIFlags, SECURITY_NATIVE_DREP, (fInitContext?&conn->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(conn->sock, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0); if ((num == SOCKET_ERROR) || (num == 0)) { DEBUG_printf(("_sspiAccept: handshake send failed: %d", WSAGetLastError())); ok = FALSE; goto cleanup; } DEBUG_printf(("_sspiAccept: send %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(conn->decryptBuffer, (LPBYTE) (conn->decryptBuffer + (conn->decryptBufferUsed - inBuffers[1].cbBuffer)), inBuffers[1].cbBuffer); conn->decryptBufferUsed = inBuffers[1].cbBuffer; } else { conn->decryptBufferUsed = 0; } ok = TRUE; break; } else if (FAILED(scRet) && (scRet != SEC_E_INCOMPLETE_MESSAGE)) { DEBUG_printf(("_sspiAccept: AcceptSecurityContext failed: %x", scRet)); ok = FALSE; break; } if (scRet != SEC_E_INCOMPLETE_MESSAGE && scRet != SEC_I_INCOMPLETE_CREDENTIALS) { if (inBuffers[1].BufferType == SECBUFFER_EXTRA) { memcpy(conn->decryptBuffer, (LPBYTE) (conn->decryptBuffer + (conn->decryptBufferUsed - inBuffers[1].cbBuffer)), inBuffers[1].cbBuffer); conn->decryptBufferUsed = inBuffers[1].cbBuffer; } else { conn->decryptBufferUsed = 0; } } } if (ok) { conn->contextInitialized = TRUE; /* * Find out how big the header will be: */ scRet = QueryContextAttributes(&conn->context, SECPKG_ATTR_STREAM_SIZES, &conn->streamSizes); if (scRet != SEC_E_OK) { DEBUG_printf(("_sspiAccept: QueryContextAttributes failed: %x", scRet)); ok = FALSE; } } cleanup: return (ok); } /* * '_sspiSetAllowsAnyRoot()' - Set the client cert policy for untrusted root certs */ void _sspiSetAllowsAnyRoot(_sspi_struct_t *conn, /* I - Client connection */ BOOL allow) /* I - Allow any root */ { conn->certFlags = (allow) ? conn->certFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA : conn->certFlags & ~SECURITY_FLAG_IGNORE_UNKNOWN_CA; } /* * '_sspiSetAllowsExpiredCerts()' - Set the client cert policy for expired root certs */ void _sspiSetAllowsExpiredCerts(_sspi_struct_t *conn, /* I - Client connection */ BOOL allow) /* I - Allow expired certs */ { conn->certFlags = (allow) ? conn->certFlags | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID : conn->certFlags & ~SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; } /* * '_sspiWrite()' - Write a buffer to an ssl socket */ int /* O - Bytes written or SOCKET_ERROR */ _sspiWrite(_sspi_struct_t *conn, /* I - Client connection */ void *buf, /* I - Buffer */ size_t len) /* I - Buffer length */ { SecBufferDesc message; /* Array of SecBuffer struct */ SecBuffer buffers[4] = { 0 }; /* Security package buffer */ BYTE *buffer = NULL; /* Scratch buffer */ int bufferLen; /* Buffer length */ size_t bytesLeft; /* Bytes left to write */ int index = 0; /* Index into buffer */ int num = 0; /* Return value */ if (!conn || !buf || !len) { WSASetLastError(WSAEINVAL); num = SOCKET_ERROR; goto cleanup; } bufferLen = conn->streamSizes.cbMaximumMessage + conn->streamSizes.cbHeader + conn->streamSizes.cbTrailer; buffer = (BYTE*) malloc(bufferLen); if (!buffer) { DEBUG_printf(("_sspiWrite: buffer alloc of %d bytes failed", bufferLen)); WSASetLastError(E_OUTOFMEMORY); num = SOCKET_ERROR; goto cleanup; } bytesLeft = len; while (bytesLeft) { size_t chunk = min(conn->streamSizes.cbMaximumMessage, /* Size of data to write */ bytesLeft); SECURITY_STATUS scRet; /* SSPI status */ /* * Copy user data into the buffer, starting * just past the header */ memcpy(buffer + conn->streamSizes.cbHeader, ((BYTE*) buf) + index, chunk); /* * Setup the SSPI buffers */ message.ulVersion = SECBUFFER_VERSION; message.cBuffers = 4; message.pBuffers = buffers; buffers[0].pvBuffer = buffer; buffers[0].cbBuffer = conn->streamSizes.cbHeader; buffers[0].BufferType = SECBUFFER_STREAM_HEADER; buffers[1].pvBuffer = buffer + conn->streamSizes.cbHeader; buffers[1].cbBuffer = (unsigned long) chunk; buffers[1].BufferType = SECBUFFER_DATA; buffers[2].pvBuffer = buffer + conn->streamSizes.cbHeader + chunk; buffers[2].cbBuffer = conn->streamSizes.cbTrailer; buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; buffers[3].BufferType = SECBUFFER_EMPTY; /* * Encrypt the data */ scRet = EncryptMessage(&conn->context, 0, &message, 0); if (FAILED(scRet)) { DEBUG_printf(("_sspiWrite: EncryptMessage failed: %x", scRet)); WSASetLastError(WSASYSCALLFAILURE); num = SOCKET_ERROR; goto cleanup; } /* * 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(conn->sock, buffer, buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer, 0); if ((num == SOCKET_ERROR) || (num == 0)) { DEBUG_printf(("_sspiWrite: send failed: %ld", WSAGetLastError())); goto cleanup; } bytesLeft -= (int) chunk; index += (int) chunk; } num = (int) len; cleanup: if (buffer) free(buffer); return (num); } /* * '_sspiRead()' - Read a buffer from an ssl socket */ int /* O - Bytes read or SOCKET_ERROR */ _sspiRead(_sspi_struct_t *conn, /* I - Client connection */ void *buf, /* I - Buffer */ size_t len) /* I - Buffer length */ { SecBufferDesc message; /* Array of SecBuffer struct */ SecBuffer buffers[4] = { 0 }; /* Security package buffer */ int num = 0; /* Return value */ if (!conn) { WSASetLastError(WSAEINVAL); num = SOCKET_ERROR; goto cleanup; } /* * If there are bytes that have already been * decrypted and have not yet been read, return * those */ if (buf && (conn->readBufferUsed > 0)) { int bytesToCopy = (int) min(conn->readBufferUsed, len); /* Amount of bytes to copy */ /* from read buffer */ memcpy(buf, conn->readBuffer, bytesToCopy); conn->readBufferUsed -= bytesToCopy; if (conn->readBufferUsed > 0) /* * If the caller didn't request all the bytes * we have in the buffer, then move the unread * bytes down */ memmove(conn->readBuffer, conn->readBuffer + bytesToCopy, conn->readBufferUsed); num = bytesToCopy; } else { PSecBuffer pDataBuffer; /* Data buffer */ PSecBuffer pExtraBuffer; /* Excess data buffer */ SECURITY_STATUS scRet; /* SSPI status */ int i; /* Loop control variable */ /* * 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 it's size */ if (conn->decryptBufferLength <= conn->decryptBufferUsed) { conn->decryptBufferLength += 4096; conn->decryptBuffer = (BYTE*) realloc(conn->decryptBuffer, conn->decryptBufferLength); if (!conn->decryptBuffer) { DEBUG_printf(("_sspiRead: unable to allocate %d byte buffer", conn->decryptBufferLength)); WSASetLastError(E_OUTOFMEMORY); num = SOCKET_ERROR; goto cleanup; } } buffers[0].pvBuffer = conn->decryptBuffer; buffers[0].cbBuffer = (unsigned long) conn->decryptBufferUsed; buffers[0].BufferType = SECBUFFER_DATA; buffers[1].BufferType = SECBUFFER_EMPTY; buffers[2].BufferType = SECBUFFER_EMPTY; buffers[3].BufferType = SECBUFFER_EMPTY; scRet = DecryptMessage(&conn->context, &message, 0, NULL); if (scRet == SEC_E_INCOMPLETE_MESSAGE) { if (buf) { num = recv(conn->sock, conn->decryptBuffer + conn->decryptBufferUsed, (int)(conn->decryptBufferLength - conn->decryptBufferUsed), 0); if (num == SOCKET_ERROR) { DEBUG_printf(("_sspiRead: recv failed: %d", WSAGetLastError())); goto cleanup; } else if (num == 0) { DEBUG_printf(("_sspiRead: server disconnected")); goto cleanup; } conn->decryptBufferUsed += num; } else { num = (int) conn->readBufferUsed; goto cleanup; } } } while (scRet == SEC_E_INCOMPLETE_MESSAGE); if (scRet == SEC_I_CONTEXT_EXPIRED) { DEBUG_printf(("_sspiRead: context expired")); WSASetLastError(WSAECONNRESET); num = SOCKET_ERROR; goto cleanup; } else if (scRet != SEC_E_OK) { DEBUG_printf(("_sspiRead: DecryptMessage failed: %lx", scRet)); WSASetLastError(WSASYSCALLFAILURE); num = SOCKET_ERROR; goto cleanup; } /* * 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(pDataBuffer->cbBuffer, (int) 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 ((int)(conn->readBufferLength - conn->readBufferUsed) < bytesToSave) { conn->readBufferLength = conn->readBufferUsed + bytesToSave; conn->readBuffer = realloc(conn->readBuffer, conn->readBufferLength); if (!conn->readBuffer) { DEBUG_printf(("_sspiRead: unable to allocate %d bytes", conn->readBufferLength)); WSASetLastError(E_OUTOFMEMORY); num = SOCKET_ERROR; goto cleanup; } } memcpy(((BYTE*) conn->readBuffer) + conn->readBufferUsed, ((BYTE*) pDataBuffer->pvBuffer) + bytesToCopy, bytesToSave); conn->readBufferUsed += bytesToSave; } num = (buf) ? bytesToCopy : (int) conn->readBufferUsed; } else { DEBUG_printf(("_sspiRead: unable to find data buffer")); WSASetLastError(WSASYSCALLFAILURE); num = SOCKET_ERROR; goto cleanup; } /* * 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(conn->decryptBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer); conn->decryptBufferUsed = pExtraBuffer->cbBuffer; } else { conn->decryptBufferUsed = 0; } } cleanup: return (num); } /* * '_sspiPending()' - Returns the number of available bytes */ int /* O - Number of available bytes */ _sspiPending(_sspi_struct_t *conn) /* I - Client connection */ { return (_sspiRead(conn, NULL, 0)); } /* * '_sspiFree()' - Close a connection and free resources */ void _sspiFree(_sspi_struct_t *conn) /* I - Client connection */ { if (!conn) return; if (conn->contextInitialized) { 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(&conn->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(&conn->creds, &conn->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(conn->sock, pbMessage, cbMessage, 0); if ((cbData == SOCKET_ERROR) || (cbData == 0)) { status = WSAGetLastError(); DEBUG_printf(("_sspiFree: sending close notify failed: %d", status)); } else { FreeContextBuffer(pbMessage); } } } else { DEBUG_printf(("_sspiFree: AcceptSecurityContext failed: %x", status)); } } else { DEBUG_printf(("_sspiFree: ApplyControlToken failed: %x", status)); } DeleteSecurityContext(&conn->context); conn->contextInitialized = FALSE; } if (conn->decryptBuffer) { free(conn->decryptBuffer); conn->decryptBuffer = NULL; } if (conn->readBuffer) { free(conn->readBuffer); conn->readBuffer = NULL; } if (conn->sock != INVALID_SOCKET) { closesocket(conn->sock); conn->sock = INVALID_SOCKET; } free(conn); } /* * 'sspi_verify_certificate()' - Verify a server certificate */ static DWORD /* 0 - Error code (0 == No error) */ sspi_verify_certificate(PCCERT_CONTEXT serverCert, /* I - Server certificate */ const CHAR *serverName, /* I - Server 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 serverNameUnicode = NULL; /* Unicode server 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 */ if (!serverCert) { status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } /* * Convert server name to unicode. */ if (!serverName || (strlen(serverName) == 0)) { status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } count = MultiByteToWideChar(CP_ACP, 0, serverName, -1, NULL, 0); serverNameUnicode = LocalAlloc(LMEM_FIXED, count * sizeof(WCHAR)); if (!serverNameUnicode) { status = SEC_E_INSUFFICIENT_MEMORY; goto cleanup; } count = MultiByteToWideChar(CP_ACP, 0, serverName, -1, serverNameUnicode, count); if (count == 0) { status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } /* * 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, serverCert, NULL, serverCert->hCertStore, &chainPara, 0, NULL, &chainContext)) { status = GetLastError(); DEBUG_printf(("CertGetCertificateChain returned 0x%x\n", status)); goto cleanup; } /* * Validate certificate chain. */ ZeroMemory(&httpsPolicy, sizeof(HTTPSPolicyCallbackData)); httpsPolicy.cbStruct = sizeof(HTTPSPolicyCallbackData); httpsPolicy.dwAuthType = AUTHTYPE_SERVER; httpsPolicy.fdwChecks = dwCertFlags; httpsPolicy.pwszServerName = serverNameUnicode; 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 %d", status)); goto cleanup; } if (policyStatus.dwError) { status = policyStatus.dwError; goto cleanup; } status = SEC_E_OK; cleanup: if (chainContext) CertFreeCertificateChain(chainContext); if (serverNameUnicode) LocalFree(serverNameUnicode); return (status); } /* * End of "$Id$". */