windows-nt/Source/XPSP1/NT/ds/security/protocols/schannel/spbase/cred.c
2020-09-26 16:20:57 +08:00

1600 lines
42 KiB
C

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995.
//
// File: cred.c
//
// Contents: Schannel credential management routines.
//
// Classes:
//
// Functions:
//
// History: 09-23-97 jbanes LSA integration stuff.
// 03-15-99 jbanes Remove dead code, fix legacy SGC.
//
//----------------------------------------------------------------------------
#include <spbase.h>
#include <wincrypt.h>
#include <oidenc.h>
#include <mapper.h>
#include <userenv.h>
RTL_CRITICAL_SECTION g_SslCredLock;
LIST_ENTRY g_SslCredList;
HANDLE g_GPEvent;
SP_STATUS
GetPrivateFromCert(
PSPCredential pCred,
DWORD dwProtocol,
PLSA_SCHANNEL_SUB_CRED pSubCred);
BOOL
SslInitCredentialManager(VOID)
{
BOOL fImpersonating = FALSE;
NTSTATUS Status;
Status = RtlInitializeCriticalSection( &g_SslCredLock );
if (!NT_SUCCESS(Status))
{
return FALSE;
}
InitializeListHead( &g_SslCredList );
fImpersonating = SslImpersonateClient();
g_GPEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(g_GPEvent)
{
if(!RegisterGPNotification(g_GPEvent, FALSE))
{
DebugLog((DEB_ERROR, "Error 0x%x registering for user GP notification\n", GetLastError()));
}
if(!RegisterGPNotification(g_GPEvent, TRUE))
{
DebugLog((DEB_ERROR, "Error 0x%x registering for machine GP notification\n", GetLastError()));
}
}
if(fImpersonating)
{
RevertToSelf();
}
return( TRUE );
}
BOOL
SslFreeCredentialManager(VOID)
{
BOOL fImpersonating = FALSE;
fImpersonating = SslImpersonateClient();
if(g_GPEvent)
{
if(!UnregisterGPNotification(g_GPEvent))
{
DebugLog((DEB_ERROR, "Error 0x%x unregistering for user GP notification\n", GetLastError()));
}
if(!UnregisterGPNotification(g_GPEvent))
{
DebugLog((DEB_ERROR, "Error 0x%x unregistering for machine GP notification\n", GetLastError()));
}
CloseHandle(g_GPEvent);
g_GPEvent = NULL;
}
if(fImpersonating)
{
RevertToSelf();
}
RtlDeleteCriticalSection( &g_SslCredLock );
return TRUE;
}
BOOL
SslCheckForGPEvent(void)
{
PLIST_ENTRY pList;
PSPCredentialGroup pCredGroup;
DWORD Status;
if(g_GPEvent)
{
Status = WaitForSingleObjectEx(g_GPEvent, 0, FALSE);
if(Status == WAIT_OBJECT_0)
{
DebugLog((DEB_WARN, "GP event detected, so download new trusted issuer list\n"));
RtlEnterCriticalSection( &g_SslCredLock );
pList = g_SslCredList.Flink ;
while ( pList != &g_SslCredList )
{
pCredGroup = CONTAINING_RECORD( pList, SPCredentialGroup, ListEntry.Flink );
pCredGroup->dwFlags |= CRED_FLAG_UPDATE_ISSUER_LIST;
pList = pList->Flink ;
}
RtlLeaveCriticalSection( &g_SslCredLock );
return TRUE;
}
}
return FALSE;
}
SP_STATUS
IsCredentialInGroup(
PSPCredentialGroup pCredGroup,
PCCERT_CONTEXT pCertContext,
PBOOL pfInGroup)
{
PSPCredential pCred;
BYTE rgbThumbprint[20];
DWORD cbThumbprint;
BYTE rgbHash[20];
DWORD cbHash;
DWORD i;
*pfInGroup = FALSE;
// Get thumbprint of certificate.
cbThumbprint = sizeof(rgbThumbprint);
if(!CertGetCertificateContextProperty(pCertContext,
CERT_MD5_HASH_PROP_ID,
rgbThumbprint,
&cbThumbprint))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_UNKNOWN_CREDENTIAL;
}
for(i = 0; i < pCredGroup->cCredList; i++)
{
pCred = pCredGroup->pCredList + i;
// Get thumbprint of certificate.
cbHash = sizeof(rgbHash);
if(!CertGetCertificateContextProperty(pCred->pCert,
CERT_MD5_HASH_PROP_ID,
rgbHash,
&cbHash))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_UNKNOWN_CREDENTIAL;
}
if(memcmp(rgbThumbprint, rgbHash, cbThumbprint) == 0)
{
*pfInGroup = TRUE;
break;
}
}
return PCT_ERR_OK;
}
BOOL
IsValidThumbprint(
PCRED_THUMBPRINT Thumbprint)
{
if(Thumbprint->LowPart == 0 && Thumbprint->HighPart == 0)
{
return FALSE;
}
return TRUE;
}
BOOL
IsSameThumbprint(
PCRED_THUMBPRINT Thumbprint1,
PCRED_THUMBPRINT Thumbprint2)
{
if(Thumbprint1->LowPart == Thumbprint2->LowPart &&
Thumbprint1->HighPart == Thumbprint2->HighPart)
{
return TRUE;
}
return FALSE;
}
void
GenerateCertThumbprint(
PCCERT_CONTEXT pCertContext,
PCRED_THUMBPRINT Thumbprint)
{
MD5_CTX Md5Hash;
MD5Init(&Md5Hash);
MD5Update(&Md5Hash,
pCertContext->pbCertEncoded,
pCertContext->cbCertEncoded);
MD5Final(&Md5Hash);
CopyMemory((PBYTE)Thumbprint,
Md5Hash.digest,
sizeof(CRED_THUMBPRINT));
}
void
GenerateRandomThumbprint(
PCRED_THUMBPRINT Thumbprint)
{
GenerateRandomBits((PBYTE)Thumbprint, sizeof(CRED_THUMBPRINT));
}
BOOL
DoesCredThumbprintMatch(
PSPCredentialGroup pCredGroup,
PCRED_THUMBPRINT pThumbprint)
{
PSPCredential pCurrentCred;
BOOL fFound = FALSE;
DWORD i;
for(i = 0; i < pCredGroup->cCredList; i++)
{
pCurrentCred = pCredGroup->pCredList + i;
if(IsSameThumbprint(pThumbprint, &pCurrentCred->CertThumbprint))
{
fFound = TRUE;
break;
}
}
return fFound;
}
SP_STATUS
SPCreateCred(
DWORD dwProtocol,
PLSA_SCHANNEL_SUB_CRED pSubCred,
PSPCredential pCurrentCred,
BOOL * pfEventLogged)
{
SP_STATUS pctRet;
pCurrentCred->pCert = CertDuplicateCertificateContext(pSubCred->pCert);
if(pCurrentCred->pCert == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_CERT_UNKNOWN);
goto error;
}
pctRet = SPPublicKeyFromCert(pCurrentCred->pCert,
&pCurrentCred->pPublicKey,
&pCurrentCred->dwExchSpec);
if(pctRet != PCT_ERR_OK)
{
goto error;
}
pctRet = GetPrivateFromCert(pCurrentCred, dwProtocol, pSubCred);
if(pctRet != PCT_ERR_OK)
{
*pfEventLogged = TRUE;
goto error;
}
pCurrentCred->dwCF = CF_EXPORT;
if(SslGlobalStrongEncryptionPermitted)
{
pCurrentCred->dwCF |= CF_DOMESTIC;
}
// Generate the credential thumbprint. This is computed by
// taking the hash of the certificate.
GenerateCertThumbprint(pCurrentCred->pCert,
&pCurrentCred->CertThumbprint);
DebugLog((DEB_TRACE, "Credential thumbprint: %x %x\n",
pCurrentCred->CertThumbprint.LowPart,
pCurrentCred->CertThumbprint.HighPart));
// Read list of supported algorithms.
if((dwProtocol & SP_PROT_SERVERS) && pCurrentCred->hProv)
{
GetSupportedCapiAlgs(pCurrentCred->hProv,
pCurrentCred->dwCapiFlags,
&pCurrentCred->pCapiAlgs,
&pCurrentCred->cCapiAlgs);
}
// Build SSL3 serialized certificate chain. This is an optimization
// so that we won't have to build it for each connection.
pctRet = SPSerializeCertificate(
SP_PROT_SSL3,
TRUE,
&pCurrentCred->pbSsl3SerializedChain,
&pCurrentCred->cbSsl3SerializedChain,
pCurrentCred->pCert,
CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL);
if(pctRet != PCT_ERR_OK)
{
goto error;
}
error:
return pctRet;
}
SP_STATUS
SPCreateCredential(
PSPCredentialGroup *ppCred,
DWORD grbitProtocol,
PLSA_SCHANNEL_CRED pSchannelCred)
{
PSPCredentialGroup pCred = NULL;
PSPCredential pCurrentCred = NULL;
SECPKG_CALL_INFO CallInfo;
SP_STATUS pctRet = PCT_ERR_OK;
DWORD i;
DWORD dwKeySpec;
HMAPPER * pMapper;
BOOL fSelectiveCrypto;
NTSTATUS Status;
BOOL fImpersonating = FALSE;
BOOL fEventLogged = FALSE;
SP_BEGIN("SPCreateCredential");
DebugLog((DEB_TRACE, " dwVersion: %d\n", pSchannelCred->dwVersion));
DebugLog((DEB_TRACE, " cCreds: %d\n", pSchannelCred->cSubCreds));
DebugLog((DEB_TRACE, " paCred: 0x%p\n", pSchannelCred->paSubCred));
DebugLog((DEB_TRACE, " hRootStore: 0x%p\n", pSchannelCred->hRootStore));
DebugLog((DEB_TRACE, " cMappers: %d\n", pSchannelCred->cMappers));
DebugLog((DEB_TRACE, " aphMappers: 0x%p\n", pSchannelCred->aphMappers));
DebugLog((DEB_TRACE, " cSupportedAlgs: %d\n", pSchannelCred->cSupportedAlgs));
DebugLog((DEB_TRACE, " palgSupportedAlgs: 0x%p\n", pSchannelCred->palgSupportedAlgs));
DebugLog((DEB_TRACE, " grbitEnabledProtocols: 0x%x\n", pSchannelCred->grbitEnabledProtocols));
DebugLog((DEB_TRACE, " dwMinimumCipherStrength:%d\n", pSchannelCred->dwMinimumCipherStrength));
DebugLog((DEB_TRACE, " dwMaximumCipherStrength:%d\n", pSchannelCred->dwMaximumCipherStrength));
DebugLog((DEB_TRACE, " dwSessionLifespan: %d\n", pSchannelCred->dwSessionLifespan));
DebugLog((DEB_TRACE, " dwFlags: 0x%x\n", pSchannelCred->dwFlags));
DebugLog((DEB_TRACE, " reserved: 0x%x\n", pSchannelCred->reserved));
LogCreateCredEvent(grbitProtocol, pSchannelCred);
//
// Allocate the internal credential structure and perform
// basic initialization.
//
pCred = SPExternalAlloc(sizeof(SPCredentialGroup));
if(pCred == NULL)
{
SP_RETURN(SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY));
}
DebugLog((DEB_TRACE, "New cred:%p, Protocol:%x\n", pCred, grbitProtocol));
pCred->Magic = PCT_CRED_MAGIC;
pCred->grbitProtocol = grbitProtocol;
Status = RtlInitializeCriticalSection(&pCred->csLock);
if (!NT_SUCCESS(Status))
{
pctRet = SEC_E_NO_CREDENTIALS;
goto error;
}
pCred->RefCount = 0;
pCred->cMappers = 0;
pCred->pahMappers = NULL;
pCred->dwFlags = 0;
pCred->pCredList = NULL;
GenerateRandomThumbprint(&pCred->CredThumbprint);
if((grbitProtocol & SP_PROT_SERVERS) && (pSchannelCred->cSubCreds == 0))
{
pctRet = SP_LOG_RESULT(SEC_E_NO_CREDENTIALS);
goto error;
}
if(LsaTable->GetCallInfo(&CallInfo))
{
pCred->ProcessId = CallInfo.ProcessId;
}
//
// Walk through and initialize all certs and keys.
//
if(pSchannelCred->cSubCreds)
{
pCred->pCredList = SPExternalAlloc(pSchannelCred->cSubCreds * sizeof(SPCredential));
if(pCred->pCredList == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
pCred->cCredList = pSchannelCred->cSubCreds;
for(i = 0; i < pSchannelCred->cSubCreds; i++)
{
pCurrentCred = pCred->pCredList + i;
pctRet = SPCreateCred(grbitProtocol,
pSchannelCred->paSubCred + i,
pCurrentCred,
&fEventLogged);
if(pctRet != PCT_ERR_OK)
{
goto error;
}
}
}
//
// Determine which protocols are to be supported.
//
if(pSchannelCred->grbitEnabledProtocols == 0)
{
pCred->grbitEnabledProtocols = g_ProtEnabled;
if(g_PctClientDisabledByDefault)
{
pCred->grbitEnabledProtocols &= ~SP_PROT_PCT1_CLIENT;
}
if(g_Ssl2ClientDisabledByDefault)
{
pCred->grbitEnabledProtocols &= ~SP_PROT_SSL2_CLIENT;
}
}
else
{
pCred->grbitEnabledProtocols = pSchannelCred->grbitEnabledProtocols & g_ProtEnabled;
}
// Force credential to client-only or server only.
if(grbitProtocol & SP_PROT_SERVERS)
{
pCred->grbitEnabledProtocols &= SP_PROT_SERVERS;
}
else
{
pCred->grbitEnabledProtocols &= SP_PROT_CLIENTS;
}
//
// Propagate flags from SCHANNEL_CRED structure.
//
if(pSchannelCred->dwFlags & SCH_CRED_NO_SYSTEM_MAPPER)
{
pCred->dwFlags |= CRED_FLAG_NO_SYSTEM_MAPPER;
}
if(pSchannelCred->dwFlags & SCH_CRED_NO_SERVERNAME_CHECK)
{
pCred->dwFlags |= CRED_FLAG_NO_SERVERNAME_CHECK;
}
if(pSchannelCred->dwFlags & SCH_CRED_MANUAL_CRED_VALIDATION)
{
pCred->dwFlags |= CRED_FLAG_MANUAL_CRED_VALIDATION;
}
if(pSchannelCred->dwFlags & SCH_CRED_NO_DEFAULT_CREDS)
{
pCred->dwFlags |= CRED_FLAG_NO_DEFAULT_CREDS;
}
if(pSchannelCred->dwFlags & SCH_CRED_AUTO_CRED_VALIDATION)
{
// Automatically validate server credentials.
pCred->dwFlags &= ~CRED_FLAG_MANUAL_CRED_VALIDATION;
}
if(pSchannelCred->dwFlags & SCH_CRED_USE_DEFAULT_CREDS)
{
// Use default client credentials.
pCred->dwFlags &= ~CRED_FLAG_NO_DEFAULT_CREDS;
}
if(pSchannelCred->dwFlags & SCH_CRED_DISABLE_RECONNECTS)
{
// Disable reconnects.
pCred->dwFlags |= CRED_FLAG_DISABLE_RECONNECTS;
}
// set revocation flags
if(pSchannelCred->dwFlags & SCH_CRED_REVOCATION_CHECK_END_CERT)
pCred->dwFlags |= CRED_FLAG_REVCHECK_END_CERT;
if(pSchannelCred->dwFlags & SCH_CRED_REVOCATION_CHECK_CHAIN)
pCred->dwFlags |= CRED_FLAG_REVCHECK_CHAIN;
if(pSchannelCred->dwFlags & SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT)
pCred->dwFlags |= CRED_FLAG_REVCHECK_CHAIN_EXCLUDE_ROOT;
if(pSchannelCred->dwFlags & SCH_CRED_IGNORE_NO_REVOCATION_CHECK)
pCred->dwFlags |= CRED_FLAG_IGNORE_NO_REVOCATION_CHECK;
if(pSchannelCred->dwFlags & SCH_CRED_IGNORE_REVOCATION_OFFLINE)
pCred->dwFlags |= CRED_FLAG_IGNORE_REVOCATION_OFFLINE;
// set up the min and max strength
GetBaseCipherSizes(&pCred->dwMinStrength, &pCred->dwMaxStrength);
if(pSchannelCred->dwMinimumCipherStrength == 0)
{
pCred->dwMinStrength = max(40, pCred->dwMinStrength);
}
else if(pSchannelCred->dwMinimumCipherStrength == (DWORD)(-1))
{
// Turn on NULL cipher.
pCred->dwMinStrength = 0;
}
else
{
pCred->dwMinStrength = pSchannelCred->dwMinimumCipherStrength;
}
if(pSchannelCred->dwMaximumCipherStrength == (DWORD)(-1))
{
// NULL cipher only.
pCred->dwMaxStrength = 0;
}
else if(pSchannelCred->dwMaximumCipherStrength != 0)
{
pCred->dwMaxStrength = pSchannelCred->dwMaximumCipherStrength;
}
// set up the allowed ciphers
BuildAlgList(pCred, pSchannelCred->palgSupportedAlgs, pSchannelCred->cSupportedAlgs);
//
// Set up all of the applications mappers and add in the default mapper.
//
pCred->cMappers = pSchannelCred->cMappers + 1;
pCred->pahMappers = SPExternalAlloc(pCred->cMappers * sizeof(HMAPPER *));
if(pCred->pahMappers == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
// Make system certificate mapper the first mapper in the list.
pCred->pahMappers[0] = SslGetMapper(TRUE);
if(pCred->dwFlags & CRED_FLAG_REVCHECK_END_CERT)
pCred->pahMappers[0]->m_dwFlags |= SCH_FLAG_REVCHECK_END_CERT;
if(pCred->dwFlags & CRED_FLAG_REVCHECK_CHAIN)
pCred->pahMappers[0]->m_dwFlags |= SCH_FLAG_REVCHECK_CHAIN;
if(pCred->dwFlags & CRED_FLAG_REVCHECK_CHAIN_EXCLUDE_ROOT)
pCred->pahMappers[0]->m_dwFlags |= SCH_FLAG_REVCHECK_CHAIN_EXCLUDE_ROOT;
if(pCred->dwFlags & CRED_FLAG_IGNORE_NO_REVOCATION_CHECK)
pCred->pahMappers[0]->m_dwFlags |= SCH_FLAG_IGNORE_NO_REVOCATION_CHECK;
if(pCred->dwFlags & CRED_FLAG_IGNORE_REVOCATION_OFFLINE)
pCred->pahMappers[0]->m_dwFlags |= SCH_FLAG_IGNORE_REVOCATION_OFFLINE;
SslReferenceMapper(pCred->pahMappers[0]);
for(i = 0; i < pSchannelCred->cMappers; i++)
{
pCred->pahMappers[i + 1] = pSchannelCred->aphMappers[i];
pSchannelCred->aphMappers[i] = NULL;
SslReferenceMapper(pCred->pahMappers[i + 1]);
}
// set up timeouts.
if(pSchannelCred->dwSessionLifespan == 0)
{
if(grbitProtocol & SP_PROT_CLIENTS)
{
pCred->dwSessionLifespan = SchannelCache.dwClientLifespan;
}
else
{
pCred->dwSessionLifespan = SchannelCache.dwServerLifespan;
}
}
else if(pSchannelCred->dwSessionLifespan == (DWORD)(-1))
{
pCred->dwSessionLifespan = 0;
}
else
{
pCred->dwSessionLifespan = pSchannelCred->dwSessionLifespan;
}
//
// Add credential to global list of credentials.
//
RtlEnterCriticalSection( &g_SslCredLock );
InsertTailList( &g_SslCredList, &pCred->ListEntry );
RtlLeaveCriticalSection( &g_SslCredLock );
//
// Get list of trusted issuers.
//
if(grbitProtocol & SP_PROT_SERVERS)
{
if(pSchannelCred->hRootStore)
{
pCred->hApplicationRoots = CertDuplicateStore(pSchannelCred->hRootStore);
if(!pCred->hApplicationRoots)
{
DebugLog((DEB_ERROR, "Error 0x%x duplicating app root store\n", GetLastError()));
}
}
fImpersonating = SslImpersonateClient();
pCred->hUserRoots = CertOpenSystemStore(0, "ROOT");
if(!pCred->hUserRoots)
{
DebugLog((DEB_ERROR, "Error 0x%x opening user root store\n", GetLastError()));
}
else
{
if(!CertControlStore(pCred->hUserRoots,
0,
CERT_STORE_CTRL_NOTIFY_CHANGE,
&g_GPEvent))
{
DebugLog((DEB_ERROR, "Error 0x%x registering user root change notification\n", GetLastError()));
}
}
if(fImpersonating)
{
RevertToSelf();
fImpersonating = FALSE;
}
}
SPReferenceCredential(pCred);
*ppCred = pCred;
SP_RETURN(PCT_ERR_OK);
error:
if(fEventLogged == FALSE)
{
LogCreateCredFailedEvent(grbitProtocol);
}
// Error case, free the credential
if(pCred)
{
SPDeleteCredential(pCred);
}
SP_RETURN(pctRet);
}
BOOL
SPDeleteCredential(
PSPCredentialGroup pCred)
{
DWORD i;
SP_BEGIN("SPDeleteCredential");
if(pCred == NULL)
{
SP_RETURN(TRUE);
}
if(pCred->Magic != PCT_CRED_MAGIC)
{
DebugLog((SP_LOG_ERROR, "Attempting to delete invalid credential!\n"));
SP_RETURN (FALSE);
}
LockCredential(pCred);
if(pCred->pCredList)
{
for(i = 0; i < pCred->cCredList; i++)
{
SPDeleteCred(pCred->pCredList + i);
}
SPExternalFree(pCred->pCredList);
pCred->pCredList = NULL;
pCred->cCredList = 0;
}
if(pCred->cMappers && pCred->pahMappers)
{
for(i=0; i < (DWORD)pCred->cMappers; i++)
{
SslDereferenceMapper(pCred->pahMappers[i]);
}
SPExternalFree(pCred->pahMappers);
}
if(pCred->palgSupportedAlgs)
{
SPExternalFree(pCred->palgSupportedAlgs);
}
pCred->Magic = PCT_INVALID_MAGIC;
if(pCred->ListEntry.Flink)
{
RtlEnterCriticalSection( &g_SslCredLock );
RemoveEntryList( &pCred->ListEntry );
RtlLeaveCriticalSection( &g_SslCredLock );
}
if(pCred->pbTrustedIssuers)
{
// LocalFree is used for the issuer list because realloc
// is used when building the list and the LSA doesn't
// provide a realloc helper function.
LocalFree(pCred->pbTrustedIssuers);
}
if(pCred->hApplicationRoots)
{
CertCloseStore(pCred->hApplicationRoots, 0);
}
if(pCred->hUserRoots)
{
BOOL fImpersonating = SslImpersonateClient();
CertCloseStore(pCred->hUserRoots, 0);
if(fImpersonating) RevertToSelf();
}
UnlockCredential(pCred);
RtlDeleteCriticalSection(&pCred->csLock);
ZeroMemory(pCred, sizeof(SPCredentialGroup));
SPExternalFree(pCred);
SP_RETURN(TRUE);
}
void
SPDeleteCred(PSPCredential pCred)
{
BOOL fImpersonating = FALSE;
if(pCred == NULL)
{
return;
}
if(pCred->pPublicKey)
{
SPExternalFree(pCred->pPublicKey);
pCred->pPublicKey = NULL;
}
if(pCred->pCert)
{
CertFreeCertificateContext(pCred->pCert);
pCred->pCert = NULL;
}
if(pCred->hTek)
{
if(!CryptDestroyKey(pCred->hTek))
{
SP_LOG_RESULT(GetLastError());
}
pCred->hTek = 0;
}
if(pCred->hProv)
{
fImpersonating = SslImpersonateClient();
if(!SchCryptReleaseContext(
pCred->hProv,
0,
pCred->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
}
pCred->hProv = 0;
if(fImpersonating)
{
RevertToSelf();
fImpersonating = FALSE;
}
}
if(pCred->pCapiAlgs)
{
SPExternalFree(pCred->pCapiAlgs);
pCred->pCapiAlgs = NULL;
}
if(pCred->hRemoteProv && !pCred->fAppRemoteProv)
{
if(!RemoteCryptReleaseContext(
pCred->hRemoteProv,
0,
pCred->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
}
pCred->hRemoteProv = 0;
}
if(pCred->hEphem512Prov)
{
if(!SchCryptReleaseContext(pCred->hEphem512Prov,
0,
pCred->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
}
pCred->hEphem512Prov = 0;
}
if(pCred->pbSsl3SerializedChain)
{
SPExternalFree(pCred->pbSsl3SerializedChain);
}
}
// Reference a credential.
// Note: This should only be called by someone who already
// has a reference to the credential, or by the CreateCredential
// call.
BOOL
SPReferenceCredential(
PSPCredentialGroup pCred)
{
BOOL fRet = FALSE;
fRet = (InterlockedIncrement(&pCred->RefCount) > 0);
DebugLog((SP_LOG_TRACE, "Reference Cred %lx: %d\n", pCred, pCred->RefCount));
return fRet;
}
BOOL
SPDereferenceCredential(
PSPCredentialGroup pCred)
{
LONG Ref;
BOOL fRet = FALSE;
if(pCred == NULL)
{
return FALSE;
}
if(pCred->Magic != PCT_CRED_MAGIC)
{
DebugLog((SP_LOG_ERROR, "Attempting to dereference invalid credential!\n"));
return FALSE;
}
fRet = TRUE;
DebugLog((SP_LOG_TRACE, "Dereference Cred %lx: %d\n", pCred, pCred->RefCount-1));
if(0 == InterlockedDecrement(&pCred->RefCount))
{
fRet = SPDeleteCredential(pCred);
}
return fRet;
}
SECURITY_STATUS
UpdateCredentialFormat(
PSCH_CRED pSchCred, // in
PLSA_SCHANNEL_CRED pSchannelCred) // out
{
DWORD dwVersion;
DWORD dwType;
SP_STATUS pctRet;
DWORD i;
PBYTE pbChain;
DWORD cbChain;
PSCH_CRED_PUBLIC_CERTCHAIN pCertChain;
CRYPT_DATA_BLOB DataBlob;
SP_BEGIN("UpdateCredentialFormat");
//
// Initialize the output structure to null credential.
//
if(pSchannelCred == NULL)
{
SP_RETURN(SP_LOG_RESULT(SEC_E_INTERNAL_ERROR));
}
memset(pSchannelCred, 0, sizeof(LSA_SCHANNEL_CRED));
pSchannelCred->dwVersion = SCHANNEL_CRED_VERSION;
//
// If input buffer is empty then we're done.
//
if(pSchCred == NULL)
{
SP_RETURN(SEC_E_OK);
}
//
// Copy over the mapper fields.
//
pSchannelCred->cMappers = pSchCred->cMappers;
pSchannelCred->aphMappers = pSchCred->aphMappers;
//
// Convert the certificates and private keys.
//
if(pSchCred->cCreds == 0)
{
SP_RETURN(SEC_E_OK);
}
pSchannelCred->cSubCreds = pSchCred->cCreds;
pSchannelCred->paSubCred = SPExternalAlloc(sizeof(LSA_SCHANNEL_SUB_CRED) * pSchannelCred->cSubCreds);
if(pSchannelCred->paSubCred == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
// Loop through each of the creds, and convert them into something we know
for(i = 0; i < pSchannelCred->cSubCreds; i++)
{
PLSA_SCHANNEL_SUB_CRED pSubCred = pSchannelCred->paSubCred + i;
//
// Decode the certificate.
//
dwType = *(PDWORD)pSchCred->paPublic[i];
if(dwType != SCH_CRED_X509_CERTCHAIN)
{
pctRet = SP_LOG_RESULT(SEC_E_UNKNOWN_CREDENTIALS);
goto error;
}
pCertChain = (PSCH_CRED_PUBLIC_CERTCHAIN)pSchCred->paPublic[i];
pbChain = pCertChain->pCertChain;
cbChain = pCertChain->cbCertChain;
// Decode the credential
pctRet = SPLoadCertificate(0,
X509_ASN_ENCODING,
pbChain,
cbChain,
&pSubCred->pCert);
if(pctRet != PCT_ERR_OK)
{
pctRet = SP_LOG_RESULT(SEC_E_UNKNOWN_CREDENTIALS);
goto error;
}
//
// Now deal with the private key.
//
dwType = *(DWORD *)pSchCred->paSecret[i];
if(dwType == SCHANNEL_SECRET_PRIVKEY)
{
PUCHAR pPrivateKeySave;
PSCH_CRED_SECRET_PRIVKEY pPrivKey;
DWORD Size;
pPrivKey = (PSCH_CRED_SECRET_PRIVKEY)pSchCred->paSecret[i];
pSubCred->pPrivateKey = SPExternalAlloc(pPrivKey->cbPrivateKey);
if(pSubCred->pPrivateKey == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
memcpy(pSubCred->pPrivateKey, pPrivKey->pPrivateKey, pPrivKey->cbPrivateKey);
pSubCred->cbPrivateKey = pPrivKey->cbPrivateKey;
Size = strlen(pPrivKey->pszPassword) + sizeof(CHAR);
pSubCred->pszPassword = SPExternalAlloc(Size);
if(pSubCred->pszPassword == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
memcpy(pSubCred->pszPassword, pPrivKey->pszPassword, Size);
break;
}
else if(dwType == SCHANNEL_SECRET_TYPE_CAPI)
{
PSCH_CRED_SECRET_CAPI pCapiKey;
pCapiKey = (PSCH_CRED_SECRET_CAPI)pSchCred->paSecret[i];
pSubCred->hRemoteProv = pCapiKey->hProv;
break;
}
else
{
pctRet = SP_LOG_RESULT(SEC_E_UNKNOWN_CREDENTIALS);
goto error;
}
}
SP_RETURN(SEC_E_OK);
error:
if(pSchannelCred->paSubCred)
{
SPExternalFree((PVOID)pSchannelCred->paSubCred);
pSchannelCred->paSubCred = NULL;
}
SP_RETURN(pctRet);
}
SP_STATUS
GetIisPrivateFromCert(
PSPCredential pCred,
PLSA_SCHANNEL_SUB_CRED pSubCred)
{
PBYTE pbPrivate = NULL;
DWORD cbPrivate;
PBYTE pbPassword = NULL;
DWORD cbPassword;
PPRIVATE_KEY_FILE_ENCODE pPrivateFile = NULL;
DWORD cbPrivateFile;
BLOBHEADER *pPrivateBlob = NULL;
DWORD cbPrivateBlob;
HCRYPTKEY hPrivateKey;
PKeyExchangeInfo pExchInfo;
HCRYPTPROV hProv = 0;
SP_STATUS pctRet;
MD5_CTX md5Ctx;
struct RC4_KEYSTRUCT rc4Key;
DWORD i;
if(pSubCred->cbPrivateKey == 0 ||
pSubCred->pPrivateKey == NULL ||
pSubCred->pszPassword == NULL)
{
return SP_LOG_RESULT(SEC_E_NO_CREDENTIALS);
}
pbPrivate = pSubCred->pPrivateKey;
cbPrivate = pSubCred->cbPrivateKey;
pbPassword = pSubCred->pszPassword;
cbPassword = strlen(pbPassword);
// We have to do a little fixup here. Old versions of
// schannel wrote the wrong header data into the ASN
// for private key files, so we must fix the size data.
pbPrivate[2] = MSBOF(cbPrivate - 4);
pbPrivate[3] = LSBOF(cbPrivate - 4);
// ASN.1 decode the private key.
if(!CryptDecodeObject(X509_ASN_ENCODING,
szPrivateKeyFileEncode,
pbPrivate,
cbPrivate,
0,
NULL,
&cbPrivateFile))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x decoding the private key\n",
GetLastError()));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
pPrivateFile = SPExternalAlloc(cbPrivateFile);
if(pPrivateFile == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
if(!CryptDecodeObject(X509_ASN_ENCODING,
szPrivateKeyFileEncode,
pbPrivate,
cbPrivate,
0,
pPrivateFile,
&cbPrivateFile))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x decoding the private key\n",
GetLastError()));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
// Decrypt the decoded private key using the password.
MD5Init(&md5Ctx);
MD5Update(&md5Ctx, pbPassword, cbPassword);
MD5Final(&md5Ctx);
rc4_key(&rc4Key, 16, md5Ctx.digest);
rc4(&rc4Key,
pPrivateFile->EncryptedBlob.cbData,
pPrivateFile->EncryptedBlob.pbData);
// Build a PRIVATEKEYBLOB from the decrypted private key.
if(!CryptDecodeObject(X509_ASN_ENCODING,
szPrivateKeyInfoEncode,
pPrivateFile->EncryptedBlob.pbData,
pPrivateFile->EncryptedBlob.cbData,
0,
NULL,
&cbPrivateBlob))
{
// Maybe this was a SGC style key.
// Re-encrypt it, and build the SGC decrypting
// key, and re-decrypt it.
BYTE md5Digest[MD5DIGESTLEN];
rc4_key(&rc4Key, 16, md5Ctx.digest);
rc4(&rc4Key,
pPrivateFile->EncryptedBlob.cbData,
pPrivateFile->EncryptedBlob.pbData);
CopyMemory(md5Digest, md5Ctx.digest, MD5DIGESTLEN);
MD5Init(&md5Ctx);
MD5Update(&md5Ctx, md5Digest, MD5DIGESTLEN);
MD5Update(&md5Ctx, SGC_KEY_SALT, lstrlen(SGC_KEY_SALT));
MD5Final(&md5Ctx);
rc4_key(&rc4Key, 16, md5Ctx.digest);
rc4(&rc4Key,
pPrivateFile->EncryptedBlob.cbData,
pPrivateFile->EncryptedBlob.pbData);
// Try again...
if(!CryptDecodeObject(X509_ASN_ENCODING,
szPrivateKeyInfoEncode,
pPrivateFile->EncryptedBlob.pbData,
pPrivateFile->EncryptedBlob.cbData,
0,
NULL,
&cbPrivateBlob))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x building PRIVATEKEYBLOB\n",
GetLastError()));
ZeroMemory(&md5Ctx, sizeof(md5Ctx));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
}
ZeroMemory(&md5Ctx, sizeof(md5Ctx));
pPrivateBlob = SPExternalAlloc(cbPrivateBlob);
if(pPrivateBlob == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto error;
}
if(!CryptDecodeObject(X509_ASN_ENCODING,
szPrivateKeyInfoEncode,
pPrivateFile->EncryptedBlob.pbData,
pPrivateFile->EncryptedBlob.cbData,
0,
pPrivateBlob,
&cbPrivateBlob))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x building PRIVATEKEYBLOB\n",
GetLastError()));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
// HACKHACK - Make sure that the key contained within the private
// key blob is marked for "key exchange".
pPrivateBlob->aiKeyAlg = CALG_RSA_KEYX;
// Create an in-memory key container.
if(!CryptAcquireContext(&hProv,
NULL,
NULL,
PROV_RSA_SCHANNEL,
CRYPT_VERIFYCONTEXT))
{
DebugLog((SP_LOG_ERROR, "Couldn't Acquire RSA Provider %lx\n", GetLastError()));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
// Import the private key blob into the key container.
if(!CryptImportKey(hProv,
(PBYTE)pPrivateBlob,
cbPrivateBlob,
0, 0,
&hPrivateKey))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x importing PRIVATEKEYBLOB\n",
GetLastError()));
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto error;
}
CryptDestroyKey(hPrivateKey);
// Obtain a matching CSP handle in the application process.
pctRet = RemoteCryptAcquireContextW(
&pCred->hRemoteProv,
NULL,
NULL,
PROV_RSA_SCHANNEL,
CRYPT_VERIFYCONTEXT,
SCH_CAPI_USE_CSP);
if(!NT_SUCCESS(pctRet))
{
pCred->hRemoteProv = 0;
SP_LOG_RESULT(pctRet);
goto error;
}
pCred->hProv = hProv;
pCred->dwKeySpec = AT_KEYEXCHANGE;
pCred->dwCapiFlags = SCH_CAPI_USE_CSP;
pctRet = PCT_ERR_OK;
error:
if(pPrivateFile) SPExternalFree(pPrivateFile);
if(pPrivateBlob) SPExternalFree(pPrivateBlob);
return pctRet;
}
SP_STATUS
LocalCryptAcquireContext(
HCRYPTPROV * phProv,
PCRYPT_KEY_PROV_INFO pProvInfo,
DWORD dwProtocol,
BOOL * pfEventLogged)
{
BOOL fImpersonating = FALSE;
BOOL fSuccess;
SP_STATUS Status;
HCRYPTPROV hProv;
// If the private key belongs to one of the Microsoft PROV_RSA_FULL
// CSPs, then manually divert it to the Microsoft PROV_RSA_SCHANNEL
// CSP. This works because both CSP types use the same private key
// storage scheme.
if(pProvInfo->dwProvType == PROV_RSA_FULL)
{
if(lstrcmpW(pProvInfo->pwszProvName, MS_DEF_PROV_W) == 0 ||
lstrcmpW(pProvInfo->pwszProvName, MS_STRONG_PROV_W) == 0 ||
lstrcmpW(pProvInfo->pwszProvName, MS_ENHANCED_PROV_W) == 0)
{
DebugLog((DEB_WARN, "Force CSP type to PROV_RSA_SCHANNEL.\n"));
pProvInfo->pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W;
pProvInfo->dwProvType = PROV_RSA_SCHANNEL;
}
}
if(pProvInfo->dwProvType != PROV_RSA_SCHANNEL &&
pProvInfo->dwProvType != PROV_DH_SCHANNEL)
{
DebugLog((SP_LOG_ERROR, "Bad server CSP type:%d\n", pProvInfo->dwProvType));
return SP_LOG_RESULT(PCT_ERR_UNKNOWN_CREDENTIAL);
}
fImpersonating = SslImpersonateClient();
fSuccess = CryptAcquireContextW(&hProv,
pProvInfo->pwszContainerName,
pProvInfo->pwszProvName,
pProvInfo->dwProvType,
pProvInfo->dwFlags | CRYPT_SILENT);
if(fImpersonating)
{
RevertToSelf();
fImpersonating = FALSE;
}
if(!fSuccess)
{
Status = GetLastError();
DebugLog((SP_LOG_ERROR, "Error 0x%x calling CryptAcquireContextW\n", Status));
LogCredAcquireContextFailedEvent(dwProtocol, Status);
*pfEventLogged = TRUE;
return SP_LOG_RESULT(PCT_ERR_UNKNOWN_CREDENTIAL);
}
DebugLog((SP_LOG_TRACE, "Local CSP handle acquired (0x%p)\n", hProv));
*phProv = hProv;
return PCT_ERR_OK;
}
//+---------------------------------------------------------------------------
//
// Function: GetPrivateFromCert
//
// Synopsis: Given a certificate context, somehow obtain a handle to the
// corresponding key container. Determine the key spec of the
// private key.
//
// Arguments: [pCred] -- Pointer to the credential.
//
// History: 09-24-96 jbanes Hacked for LSA integration.
//
// Notes: The private key often lives in a CSP. In this case, a handle
// to the CSP context is obtained by either reading the
// CERT_KEY_REMOTE_PROV_HANDLE_PROP_ID property, or by reading
// the CERT_KEY_PROV_INFO_PROP_ID property and then calling
// CryptAcquireContext.
//
// If this fails, then check and see if the private key is
// stored by IIS. If this is the case, then the encrypted
// private key is obtained by reading the
//
//----------------------------------------------------------------------------
SP_STATUS
GetPrivateFromCert(
PSPCredential pCred,
DWORD dwProtocol,
PLSA_SCHANNEL_SUB_CRED pSubCred)
{
PCRYPT_KEY_PROV_INFO pProvInfo = NULL;
HCRYPTPROV hProv;
DWORD cbSize;
BOOL fRemoteProvider = FALSE;
NTSTATUS Status;
BOOL fEventLogged = FALSE;
//
// Set the output fields to default values.
//
pCred->hProv = 0;
pCred->hRemoteProv = 0;
pCred->dwCapiFlags = SCH_CAPI_USE_CSP;
pCred->dwKeySpec = AT_KEYEXCHANGE;
if(dwProtocol & SP_PROT_CLIENTS)
{
// Access the CSP from the application process.
fRemoteProvider = TRUE;
}
//
// Check to see if the application called CryptAcquireContext. If so then
// we don't have to. This will typically not be the case.
//
if(fRemoteProvider && pSubCred->hRemoteProv)
{
DebugLog((SP_LOG_TRACE, "Application provided CSP handle (0x%p)\n", pSubCred->hRemoteProv));
pCred->hRemoteProv = pSubCred->hRemoteProv;
pCred->fAppRemoteProv = TRUE;
}
//
// Read the certificate context's "key info" property.
//
if(CertGetCertificateContextProperty(pCred->pCert,
CERT_KEY_PROV_INFO_PROP_ID,
NULL,
&cbSize))
{
pProvInfo = SPExternalAlloc(cbSize);
if(pProvInfo == NULL)
{
Status = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto cleanup;
}
if(!CertGetCertificateContextProperty(pCred->pCert,
CERT_KEY_PROV_INFO_PROP_ID,
pProvInfo,
&cbSize))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x reading CERT_KEY_PROV_INFO_PROP_ID\n",GetLastError()));
SPExternalFree(pProvInfo);
pProvInfo = NULL;
}
else
{
// Success.
pCred->dwKeySpec = pProvInfo->dwKeySpec;
DebugLog((SP_LOG_TRACE, "Container:%ls\n", pProvInfo->pwszContainerName));
DebugLog((SP_LOG_TRACE, "Provider: %ls\n", pProvInfo->pwszProvName));
DebugLog((SP_LOG_TRACE, "Type: 0x%8.8x\n", pProvInfo->dwProvType));
DebugLog((SP_LOG_TRACE, "Flags: 0x%8.8x\n", pProvInfo->dwFlags));
DebugLog((SP_LOG_TRACE, "Key spec: %d\n", pProvInfo->dwKeySpec));
LogCredPropertiesEvent(dwProtocol, pProvInfo, pCred->pCert);
}
}
if(pCred->hRemoteProv)
{
// The application supplied an hProv for us to use.
Status = PCT_ERR_OK;
goto cleanup;
}
if(pProvInfo)
{
//
// We read the "key info" property successfully, so call
// CryptAcquireContext in order to get a handle to the appropriate
// key container.
//
if(!fRemoteProvider)
{
// Call CryptAcquireContext from the LSA process.
Status = LocalCryptAcquireContext(&hProv, pProvInfo, dwProtocol, &fEventLogged);
if(Status != PCT_ERR_OK)
{
goto cleanup;
}
pCred->hProv = hProv;
}
// Obtain a matching CSP handle in the application process.
Status = RemoteCryptAcquireContextW(
&pCred->hRemoteProv,
pProvInfo->pwszContainerName,
pProvInfo->pwszProvName,
pProvInfo->dwProvType,
pProvInfo->dwFlags,
pCred->dwCapiFlags);
if(!NT_SUCCESS(Status))
{
LogCredAcquireContextFailedEvent(dwProtocol, Status);
fEventLogged = TRUE;
Status = SP_LOG_RESULT(PCT_ERR_UNKNOWN_CREDENTIAL);
goto cleanup;
}
}
else
{
//
// We weren't able to read the "key info" property, so attempt to
// read the "iis private key" property, and build the private key
// up from that.
//
DebugLog((SP_LOG_TRACE, "Attempt IIS 4.0 compatibility hack.\n"));
Status = GetIisPrivateFromCert(pCred, pSubCred);
if(Status != PCT_ERR_OK)
{
SP_LOG_RESULT(Status);
goto cleanup;
}
}
Status = PCT_ERR_OK;
cleanup:
if(Status != PCT_ERR_OK && fEventLogged == FALSE)
{
if(pProvInfo == NULL)
{
LogNoPrivateKeyEvent(dwProtocol);
}
else
{
LogCreateCredFailedEvent(dwProtocol);
}
}
if(pProvInfo)
{
SPExternalFree(pProvInfo);
}
return Status;
}
DWORD
GetCredentialKeySize(
PSPCredential pCred)
{
BLOBHEADER *pPublic;
RSAPUBKEY * pRsaPublic;
if(pCred->pPublicKey == NULL)
{
return 0;
}
pPublic = pCred->pPublicKey->pPublic;
if(pPublic == NULL || pPublic->bType != PUBLICKEYBLOB)
{
return 0;
}
pRsaPublic = (RSAPUBKEY *)(pPublic + 1);
return pRsaPublic->bitlen;
}