mirror of
https://github.com/MopriaAlliance/CUPS-for-Android.git
synced 2025-08-04 03:14:37 +08:00
1486 lines
42 KiB
C
1486 lines
42 KiB
C
/*
|
|
* "$Id: sspi.c 3247 2011-05-12 06:22:31Z msweet $"
|
|
*
|
|
* 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: sspi.c 3247 2011-05-12 06:22:31Z msweet $".
|
|
*/
|