|
|
/****************************** Module Header ******************************\
* Module Name: autoenrl.c * * Copyright (c) 1997, Microsoft Corporation * * Module for Public Key certificate auto enrollment * * History: * 11-21-97 jeffspel Created. * 01-30-98 jeffspel changed to include machine auto enrollment \***************************************************************************/ #include "precomp.h"
#pragma hdrstop
#include "tchar.h"
#define AE_DEFAULT_REFRESH_RATE 8 // 8 hour default autoenrollment rate
#define SYSTEM_POLICIES_KEY TEXT("Software\\Policies\\Microsoft\\Windows\\System")
#define MAX_TEMPLATE_NAME_VALUE_SIZE 64 // sizeof (CERT_NAME_VALUE) + wcslen(wszCERTTYPE_DC)
#define MAX_DN_SIZE 256
#define MACHINE_AUTOENROLL_INITIAL_DELAY 10 // seconds
#define USER_AUTOENROLL_INITIAL_DELAY 120 // seconds
LPWSTR g_wszEmptyExtension=L"Empty Extension";
#if DBG
DWORD g_AutoenrollDebugLevel = AE_ERROR ; //| AE_WARNING | AE_INFO | AE_TRACE;
#endif
PISECURITY_DESCRIPTOR AEMakeGenericSecurityDesc();
//
// Structure to hold information passed to Auto Enrollment threads
//
HANDLE g_hUserMutex = 0; HANDLE g_hMachineMutex = 0;
#define DS_ATTR_COMMON_NAME TEXT("cn")
#define DS_ATTR_DNS_NAME TEXT("dNSHostName")
#define DS_ATTR_EMAIL_ADDR TEXT("mail")
#define DS_ATTR_OBJECT_GUID TEXT("objectGUID")
#define DS_ATTR_UPN TEXT("userPrincipalName")
static HINSTANCE g_hInstSecur32 = NULL; static HINSTANCE g_hInstWldap32 = NULL; static PFNLDAP_INIT g_pfnldap_init = NULL; static PFNLDAP_BIND_S g_pfnldap_bind_s = NULL; static PFNLDAP_SET_OPTION g_pfnldap_set_option = NULL; static PFNLDAP_SEARCH_EXT_S g_pfnldap_search_ext_s = NULL; static PFNLDAP_FIRST_ENTRY g_pfnldap_first_entry = NULL; static PFNLDAP_EXPLODE_DN g_pfnldap_explode_dn = NULL; static PFNLDAP_GET_VALUES g_pfnldap_get_values = NULL; static PFNLDAP_VALUE_FREE g_pfnldap_value_free = NULL; static PFNLDAP_MSGFREE g_pfnldap_msgfree = NULL; static PFNLDAP_UNBIND g_pfnldap_unbind = NULL; static PFNLDAPGETLASTERROR g_pfnLdapGetLastError = NULL; static PFNLDAPMAPERRORTOWIN32 g_pfnLdapMapErrorToWin32 = NULL; static PFNGETUSERNAMEEX g_pfnGetUserNameEx = NULL;
#ifndef CERT_ENTERPRISE_SYSTEM_STORE_REGPATH
#define CERT_ENTERPRISE_SYSTEM_STORE_REGPATH L"Software\\Microsoft\\EnterpriseCertificates"
#endif
typedef struct _AUTO_ENROLL_THREAD_INFO_ { BOOL fMachineEnrollment; HANDLE hNotifyEvent; HANDLE hTimer; HANDLE hToken; HANDLE hNotifyWait; HANDLE hTimerWait; } AUTO_ENROLL_THREAD_INFO, *PAUTO_ENROLL_THREAD_INFO;
//
// Structure to hold internal information needed perform Auto Enrollment
//
typedef struct _INTERNAL_CA_LIST_ { HCAINFO hCAInfo; LPWSTR wszName; BYTE CACertHash[20]; LPWSTR wszDNSName; LPWSTR *awszCertificateTemplates; } INTERNAL_CA_LIST, *PINTERNAL_CA_LIST;
typedef struct _INTERNAL_INFO_ { BOOL fMachineEnrollment; HANDLE hToken; HCERTSTORE hRootStore; HCERTSTORE hCAStore; HCERTSTORE hMYStore; LPTSTR * awszldap_UPN; LPTSTR wszConstructedUPN; LPTSTR * awszEmail; LPTSTR wszDN; CERT_NAME_BLOB blobDN;
// List of all ca's
DWORD ccaList; PINTERNAL_CA_LIST acaList;
} INTERNAL_INFO, *PINTERNAL_INFO;
typedef struct _AE_INSTANCE_INFO_ { PCCTL_CONTEXT pCTLContext; PINTERNAL_INFO pInternalInfo; PCCERT_CONTEXT pOldCert; BOOL fRenewalOK; DWORD dwRandomIndex; LPWSTR pwszCertType; LPWSTR pwszAEIdentifier; CERT_EXTENSIONS *pCertTypeExtensions; DWORD dwCertTypeFlags; LARGE_INTEGER ftExpirationOffset;
} AE_INSTANCE_INFO, *PAE_INSTANCE_INFO;
// Key usage masks
typedef struct _KUMASK { DWORD dwMask; LPSTR pszAlg; } KUMASK;
KUMASK g_aKUMasks[] = { {~CERT_KEY_AGREEMENT_KEY_USAGE, szOID_RSA_RSA }, {~CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_OIWSEC_dsa }, {~CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_X957_DSA }, {~CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_ANSI_X942_DH }, {~CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_RSA_DH }, {~CERT_KEY_AGREEMENT_KEY_USAGE, szOID_OIWSEC_rsaXchg }, {~CERT_KEY_ENCIPHERMENT_KEY_USAGE, szOID_INFOSEC_mosaicKMandUpdSig } };
DWORD g_cKUMasks = sizeof(g_aKUMasks)/sizeof(g_aKUMasks[0]);
#define DEFAULT_AUTO_ENROLL_PROV "pautoenr.dll"
#define AUTOENROLL_EVENT_LOG_SUBKEY L"System\\CurrentControlSet\\Services\\EventLog\\System\\AutoEnroll"
#define SZ_AUTO_ENROLL L"AutoEnroll"
HRESULT myGetConfigDN( IN LDAP *pld, OUT LPWSTR *pwszConfigDn );
LPWSTR HelperExtensionToString(PCERT_EXTENSION Extension);
HRESULT aeRobustLdapBind( OUT LDAP ** ppldap, IN BOOL fGC);
//
// Time skew margin for fast CA's
//
#define FILETIME_TICKS_PER_SECOND 10000000
#define DEFAULT_AUTOENROLL_SKEW 60*60*1 // 1 hour
//
// ERROR values to be logged as events when auto enrollment fails
//
//
// memory allocation and free routines
void *AEAlloc( IN DWORD cb ) { return LocalAlloc(LMEM_ZEROINIT, cb); }
void AEFree( void *p ) { LocalFree(p); }
HRESULT GetExceptionError(EXCEPTION_POINTERS const *pep) { if((pep == NULL) || (pep->ExceptionRecord == NULL)) { return E_UNEXPECTED; }
return pep->ExceptionRecord->ExceptionCode; }
//
// Name: AELogTestResult
//
// Description: This function logs the result of a certificate
// test into the AE_CERT_TEST_ARRAY
//
void AELogTestResult(PAE_CERT_TEST_ARRAY *ppAEData, DWORD idTest, ...) { va_list ArgList; va_start(ArgList, idTest);
if((*ppAEData == NULL) || ((*ppAEData)->cTests == (*ppAEData)->cMaxTests)) { PAE_CERT_TEST_ARRAY pAENew = NULL; DWORD cAENew = ((*ppAEData)?(*ppAEData)->cMaxTests:0) + AE_CERT_TEST_SIZE_INCREMENT; // We need to grow the array
pAENew = LocalAlloc(LMEM_FIXED, sizeof(AE_CERT_TEST_ARRAY) + (cAENew - ANYSIZE_ARRAY)*sizeof(AE_CERT_TEST)); if(pAENew == NULL) { return; } pAENew->dwVersion = AE_CERT_TEST_ARRAY_VERSION; pAENew->fRenewalOK = ((*ppAEData)?(*ppAEData)->fRenewalOK:FALSE); pAENew->cTests = ((*ppAEData)?(*ppAEData)->cTests:0); pAENew->cMaxTests = cAENew; if((*ppAEData) && (pAENew->cTests != 0)) { CopyMemory(pAENew->Test, (*ppAEData)->Test, sizeof(AE_CERT_TEST)*pAENew->cTests); }
if(*ppAEData) { AEFree(*ppAEData); }
(*ppAEData) = pAENew; }
(*ppAEData)->Test[(*ppAEData)->cTests].idTest = idTest;
if(FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, g_hInstance, idTest, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (PVOID)&(*ppAEData)->Test[(*ppAEData)->cTests].pwszReason, 0, &ArgList)) {
(*ppAEData)->cTests++; }
}
void AEFreeTestResult(PAE_CERT_TEST_ARRAY *ppAEData) { DWORD iTest = 0; if((ppAEData == NULL) || (*ppAEData == NULL)) { return; }
for(iTest = 0; iTest < (*ppAEData)->cTests; iTest++) { if((*ppAEData)->Test[iTest].pwszReason) { LocalFree((*ppAEData)->Test[iTest].pwszReason); } }
AEFree(*ppAEData); *ppAEData = NULL; }
//
// Name: LogAutoEnrollmentEvent
//
// Description: This function registers an event in the event log of the
// local machine.
//
void LogAutoEnrollmentEvent( IN DWORD dwEventId, IN HANDLE hToken, ... ) { HANDLE hEventSource = 0; BYTE FastBuffer[256]; PTOKEN_USER ptgUser; DWORD cbUser; BOOL fAlloced = FALSE; PSID pSID = NULL;
WORD dwEventType = 0;
LPWSTR awszStrings[20]; WORD cStrings = 0; LPWSTR wszString = NULL;
va_list ArgList; va_start(ArgList, hToken);
for(wszString = va_arg(ArgList, LPWSTR); wszString != NULL; wszString = va_arg(ArgList, LPWSTR)) { awszStrings[cStrings++] = wszString; if(cStrings >= ARRAYSIZE(awszStrings)) { break; } }
va_end(ArgList);
// event logging code
hEventSource = RegisterEventSourceW(NULL, EVENTLOG_SOURCE);
if(NULL == hEventSource) goto Ret;
// check if the token is non zero is so then impersonating so get the SID
if (hToken) { ptgUser = (PTOKEN_USER)FastBuffer; // try fast buffer first
cbUser = 256;
if (!GetTokenInformation( hToken, // identifies access token
TokenUser, // TokenUser info type
ptgUser, // retrieved info buffer
cbUser, // size of buffer passed-in
&cbUser // required buffer size
)) { if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { //
// try again with the specified buffer size
//
if (NULL != (ptgUser = (PTOKEN_USER)AEAlloc(cbUser))) { fAlloced = TRUE;
// get the user info and assign the sid if able to
if (GetTokenInformation( hToken, // identifies access token
TokenUser, // TokenUser info type
ptgUser, // retrieved info buffer
cbUser, // size of buffer passed-in
&cbUser // required buffer size
)) { pSID = ptgUser->User.Sid; } } }
} else { // assign the sid when fast buffer worked
pSID = ptgUser->User.Sid; } } switch(dwEventId >> 30) { case 0: dwEventType = EVENTLOG_SUCCESS; break; case 1: dwEventType = EVENTLOG_INFORMATION_TYPE; break; case 2: dwEventType = EVENTLOG_WARNING_TYPE; break; case 3: dwEventType = EVENTLOG_ERROR_TYPE; break; }
// UNDONE - probably want a string to go with the error code
if (!ReportEventW(hEventSource, // handle of event source
dwEventType, // event type
0, // event category
dwEventId, // event ID
pSID, // current user's SID
cStrings, // strings in lpszStrings
0, // no bytes of raw data
(LPCWSTR*)awszStrings,// array of error strings
NULL // no raw data
)) { goto Ret; }
Ret:
if (hEventSource) (VOID) DeregisterEventSource(hEventSource); return; }
//
// Name: LogAutoEnrollmentError
//
// Description: This function registers an event in the event log of the
// local machine.
//
void LogAutoEnrollmentError( IN HRESULT hr, IN DWORD dwEventId, IN BOOL fMachineEnrollment, IN HANDLE hToken, IN LPWSTR wszCertType, IN LPWSTR wszCA ) { HKEY hRegKey = 0; WCHAR szMsg[512]; HANDLE hEventSource = 0; LPWSTR lpszStrings[4]; WORD cStrings = 0;
BYTE FastBuffer[256]; PTOKEN_USER ptgUser; DWORD cbUser; BOOL fAlloced = FALSE; PSID pSID = NULL;
WORD dwEventType = 0;
// event logging code
hEventSource = RegisterEventSourceW(NULL, EVENTLOG_SOURCE);
if(NULL == hEventSource) goto Ret;
wsprintfW(szMsg, L"0x%lx", hr); lpszStrings[cStrings++] = szMsg;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (WCHAR *) &lpszStrings[cStrings++], 0, NULL); if(wszCertType) { lpszStrings[cStrings++] = wszCertType; }
if(wszCA) { lpszStrings[cStrings++] = wszCA; }
// check if the token is non zero is so then impersonating so get the SID
if (hToken) { ptgUser = (PTOKEN_USER)FastBuffer; // try fast buffer first
cbUser = 256;
if (!GetTokenInformation( hToken, // identifies access token
TokenUser, // TokenUser info type
ptgUser, // retrieved info buffer
cbUser, // size of buffer passed-in
&cbUser // required buffer size
)) { if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { //
// try again with the specified buffer size
//
if (NULL != (ptgUser = (PTOKEN_USER)AEAlloc(cbUser))) { fAlloced = TRUE;
// get the user info and assign the sid if able to
if (GetTokenInformation( hToken, // identifies access token
TokenUser, // TokenUser info type
ptgUser, // retrieved info buffer
cbUser, // size of buffer passed-in
&cbUser // required buffer size
)) { pSID = ptgUser->User.Sid; } } }
} else { // assign the sid when fast buffer worked
pSID = ptgUser->User.Sid; } } switch(dwEventId >> 30) { case 0: dwEventType = EVENTLOG_SUCCESS; break; case 1: dwEventType = EVENTLOG_INFORMATION_TYPE; break; case 2: dwEventType = EVENTLOG_WARNING_TYPE; break; case 3: dwEventType = EVENTLOG_ERROR_TYPE; break; }
// UNDONE - probably want a string to go with the error code
if (!ReportEventW(hEventSource, // handle of event source
dwEventType, // event type
0, // event category
dwEventId, // event ID
pSID, // current user's SID
cStrings, // strings in lpszStrings
0, // no bytes of raw data
(LPCWSTR*)lpszStrings,// array of error strings
NULL // no raw data
)) { goto Ret; }
Ret: if (hRegKey) RegCloseKey(hRegKey); if (hEventSource) (VOID) DeregisterEventSource(hEventSource);
if((cStrings == 2) && lpszStrings[1]) { AEFree(lpszStrings[1]); } return; }
//+-------------------------------------------------------------------------
// Microsoft Auto Enrollment Object Identifiers
//+-------------------------------------------------------------------------
//
// Name: LoadAndCallEnrollmentProvider
//
// Description: This function loads the specified Auto Enrollment provider,
// and calls the entry point to that provider. It then
// unloads the provider.
//
BOOL LoadAndCallEnrollmentProvider( IN BOOL fMachineEnrollment, IN PAUTO_ENROLL_INFO pEnrollmentInfo ) { HANDLE hAutoEnrollProv = 0; FARPROC pEntryPoint = NULL; BOOL fRet = FALSE;
AE_BEGIN(L"LoadAndCallEnrollmentProvider");
// load the auto enrollment provider and get the entry point
if (NULL == (hAutoEnrollProv = LoadLibraryA(pEnrollmentInfo->pszAutoEnrollProvider))) { AE_DEBUG((AE_ERROR, L"Could not load auto-enrollment provider %ls\n\r", pEnrollmentInfo->pszAutoEnrollProvider)); goto Ret; }
if (NULL == (pEntryPoint = GetProcAddress(hAutoEnrollProv, "ProvAutoEnrollment"))) { AE_DEBUG((AE_ERROR, L"Entry point ProvAutoEnrollment not found in %ls\n\r", pEnrollmentInfo->pszAutoEnrollProvider)); goto Ret; }
if (FALSE == pEntryPoint(fMachineEnrollment, pEnrollmentInfo)) { AE_DEBUG((AE_ERROR, L"Enrollment Failed, wizard returned %lx error\n", GetLastError())); LogAutoEnrollmentError(HRESULT_FROM_WIN32(GetLastError()), EVENT_AE_ENROLLMENT_FAILED, fMachineEnrollment, NULL, pEnrollmentInfo->pwszCertType, pEnrollmentInfo->pwszCAAuthority); goto Ret; }
AE_DEBUG((AE_WARNING, L"Enrolled for a %ls certificate\n", pEnrollmentInfo->pwszCertType));
fRet = TRUE; Ret: if (hAutoEnrollProv) FreeLibrary(hAutoEnrollProv); AE_END(); return fRet; }
//
// Name: InitInternalInfo
//
// Description: This function initializes information needed to proceed with
// auto enrollment.
//
HRESULT InitInternalInfo( LDAP *pld, IN BOOL fMachineEnrollment, IN HANDLE hToken, OUT PINTERNAL_INFO pInternalInfo ) { HRESULT hrLocal = S_OK; HRESULT hrNetwork = S_OK; DWORD dwOpenStoreFlags = CERT_SYSTEM_STORE_CURRENT_USER; DWORD dwErr = 0; BOOL fRet = FALSE; DWORD ldaperr; DWORD cNameBuffer; LDAPMessage * SearchResult = NULL; LDAPMessage * PrincipalAttributes = NULL; HCAINFO hCACurrent = NULL; DWORD iCAIndex, cCA; DWORD cbHash; struct l_timeval timeout;
// Initialize LDAP session
LPTSTR wszSearchUser = TEXT("(objectCategory=user)"); LPTSTR wszSearchComputer = TEXT("(objectCategory=computer)");
// We want the following attributes
LPTSTR AttrsUser[] = { DS_ATTR_COMMON_NAME, DS_ATTR_EMAIL_ADDR, DS_ATTR_OBJECT_GUID, DS_ATTR_UPN, NULL, }; LPTSTR AttrsComputer[] = { DS_ATTR_COMMON_NAME, DS_ATTR_DNS_NAME, DS_ATTR_EMAIL_ADDR, DS_ATTR_OBJECT_GUID, DS_ATTR_UPN, NULL, };
AE_BEGIN(L"InitInternalInfo"); pInternalInfo->fMachineEnrollment = fMachineEnrollment;
pInternalInfo->hToken = hToken;
if (fMachineEnrollment) { dwOpenStoreFlags = CERT_SYSTEM_STORE_LOCAL_MACHINE; }
// open the appropriate ROOT store
if (NULL == (pInternalInfo->hRootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, 0, 0, dwOpenStoreFlags | CERT_STORE_READONLY_FLAG, L"ROOT"))) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable to open ROOT store (%lx)\n\r", hrLocal)); goto Ret; }
// open the appropriate CA store
if (NULL == (pInternalInfo->hCAStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, 0, 0, dwOpenStoreFlags | CERT_STORE_READONLY_FLAG, L"CA"))) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable to open CA store (%lx)\n\r", hrLocal)); goto Ret; }
// open the appropriate MY store
if (NULL == (pInternalInfo->hMYStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, 0, 0, dwOpenStoreFlags, L"MY"))) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable to open MY store (%lx)\n\r", hrLocal)); goto Ret; } if(!CertControlStore(pInternalInfo->hMYStore, 0, CERT_STORE_CTRL_AUTO_RESYNC, NULL)) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable configure MY store for auto-resync(%lx)\n\r", hrLocal)); goto Ret; }
cNameBuffer = MAX_DN_SIZE; pInternalInfo->wszDN = AEAlloc(cNameBuffer*sizeof(TCHAR)); if(pInternalInfo->wszDN == NULL) { hrLocal = E_OUTOFMEMORY; goto Ret; } if(!g_pfnGetUserNameEx(NameFullyQualifiedDN, pInternalInfo->wszDN, &cNameBuffer)) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"GetUserNameEx Failed (%lx)\n\r", hrLocal));
goto Ret; } // Normalize the directory DN into a
// real BER encoded name
pInternalInfo->blobDN.cbData = 0; CertStrToName(X509_ASN_ENCODING, pInternalInfo->wszDN, CERT_X500_NAME_STR, NULL, NULL, &pInternalInfo->blobDN.cbData, NULL); if(pInternalInfo->blobDN.cbData == 0) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } pInternalInfo->blobDN.pbData = AEAlloc(pInternalInfo->blobDN.cbData); if(pInternalInfo->blobDN.pbData == NULL) { hrLocal = E_OUTOFMEMORY; goto Ret; } if(!CertStrToName(X509_ASN_ENCODING, pInternalInfo->wszDN, CERT_X500_NAME_STR, NULL, pInternalInfo->blobDN.pbData, &pInternalInfo->blobDN.cbData, NULL)) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Could not encode DN (%lx)\n\r", hrLocal)); goto Ret; }
timeout.tv_sec = csecLDAPTIMEOUT; timeout.tv_usec = 0;
ldaperr = g_pfnldap_search_ext_s(pld, pInternalInfo->wszDN, LDAP_SCOPE_BASE, (fMachineEnrollment?wszSearchComputer:wszSearchUser), (fMachineEnrollment?AttrsComputer:AttrsUser), 0, NULL, NULL, &timeout, 10000, &SearchResult);
if(ldaperr != LDAP_SUCCESS) { hrNetwork = HRESULT_FROM_WIN32(g_pfnLdapMapErrorToWin32(ldaperr)); AE_DEBUG((AE_ERROR, L"ldap_search_ext_s failed (%lx)\n\r", hrLocal)); goto Ret; }
PrincipalAttributes = g_pfnldap_first_entry(pld, SearchResult);
if(NULL == PrincipalAttributes) { AE_DEBUG((AE_ERROR, L"no user entity found\n\r")); hrNetwork = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER); goto Ret; } if(fMachineEnrollment) { pInternalInfo->awszldap_UPN = g_pfnldap_get_values(pld, PrincipalAttributes, DS_ATTR_DNS_NAME);
if((pInternalInfo->awszldap_UPN) && (*pInternalInfo->awszldap_UPN)) { AE_DEBUG((AE_INFO, L"ldap DNS Name %ls\n\r", *pInternalInfo->awszldap_UPN)); }
} else { pInternalInfo->awszldap_UPN = g_pfnldap_get_values(pld, PrincipalAttributes, DS_ATTR_UPN); if((pInternalInfo->awszldap_UPN == NULL) || (*pInternalInfo->awszldap_UPN == NULL)) { LPTSTR wszUPNBuffer = NULL; DWORD cbUPNBuffer = 0; LPTSTR *awszExplodedDN, * pwszCurrent; // Build a UPN. The UPN is built from
// The username (without the SAM domain),
// Get a buffer that will be big enough
GetUserName(NULL, &cbUPNBuffer); if(cbUPNBuffer == 0) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
cbUPNBuffer += _tcslen(pInternalInfo->wszDN)*sizeof(TCHAR);
wszUPNBuffer = AEAlloc(cbUPNBuffer); if(wszUPNBuffer == NULL) { hrLocal = E_OUTOFMEMORY; goto Ret; } if(!GetUserName(wszUPNBuffer, &cbUPNBuffer)) { hrLocal = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
awszExplodedDN = g_pfnldap_explode_dn(pInternalInfo->wszDN, 0); if(awszExplodedDN != NULL) { _tcscat(wszUPNBuffer, TEXT("@")); pwszCurrent = awszExplodedDN; while(*pwszCurrent) { if(0 == _tcsncmp(*pwszCurrent, TEXT("DC="), 3)) { _tcscat(wszUPNBuffer, (*pwszCurrent)+3); _tcscat(wszUPNBuffer, TEXT(".")); } pwszCurrent++; } // remove the trailing '.' or "@" if there were no DC=
wszUPNBuffer[_tcslen(wszUPNBuffer)-1] = 0; } pInternalInfo->wszConstructedUPN = wszUPNBuffer; AE_DEBUG((AE_INFO, L"Constructed UPN %ls\n\r", pInternalInfo->wszConstructedUPN)); } else { AE_DEBUG((AE_INFO, L"ldap UPN %ls\n\r", *pInternalInfo->awszldap_UPN));
} } pInternalInfo->awszEmail = g_pfnldap_get_values(pld, PrincipalAttributes, DS_ATTR_EMAIL_ADDR);
if((pInternalInfo->awszEmail) && (*pInternalInfo->awszEmail)) { AE_DEBUG((AE_INFO, L"E-mail name %ls\n\r", *pInternalInfo->awszEmail)); } // Build up the list of CA's
// Build up the list of CA's
hrNetwork = CAEnumFirstCA((LPCWSTR)pld, CA_FLAG_SCOPE_IS_LDAP_HANDLE | (fMachineEnrollment?CA_FIND_LOCAL_SYSTEM:0), &hCACurrent); if(hrNetwork != S_OK) { goto Ret; } if((hCACurrent == NULL) || (0 == (cCA = CACountCAs(hCACurrent)))) { pInternalInfo->ccaList = 0; AE_DEBUG((AE_WARNING, L"No CA's available for auto-enrollment\n\r")); goto Ret; }
pInternalInfo->acaList = (PINTERNAL_CA_LIST)AEAlloc(sizeof(INTERNAL_CA_LIST) * cCA); if(pInternalInfo->acaList == NULL) { hrLocal = E_OUTOFMEMORY; goto Ret; } ZeroMemory(pInternalInfo->acaList, sizeof(INTERNAL_CA_LIST) * cCA); AE_DEBUG((AE_INFO, L" %d CA's in enterprise\n\r", cCA));
pInternalInfo->ccaList = 0; hrLocal = S_OK; hrNetwork = S_OK;
for(iCAIndex = 0; iCAIndex < cCA; iCAIndex++ ) { PCCERT_CONTEXT pCert = NULL; LPWSTR *awszName = NULL; HCAINFO hCANew = NULL;
if(iCAIndex > 0) { hrNetwork = CAEnumNextCA(hCACurrent, &hCANew); } // Clean up from previous
if(pInternalInfo->acaList[pInternalInfo->ccaList].wszName) { AEFree(pInternalInfo->acaList[pInternalInfo->ccaList].wszName); } if(pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName) { AEFree(pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName); } if(pInternalInfo->acaList[iCAIndex].awszCertificateTemplates) { CAFreeCAProperty(pInternalInfo->acaList[pInternalInfo->ccaList].hCAInfo, pInternalInfo->acaList[iCAIndex].awszCertificateTemplates); }
if(pInternalInfo->acaList[pInternalInfo->ccaList].hCAInfo) { CACloseCA(pInternalInfo->acaList[pInternalInfo->ccaList].hCAInfo); pInternalInfo->acaList[pInternalInfo->ccaList].hCAInfo = NULL; } if((hrNetwork != S_OK) || (hrLocal != S_OK)) { break; }
if(iCAIndex > 0) { hCACurrent = hCANew; }
if(hCACurrent == NULL) { break; }
pInternalInfo->acaList[pInternalInfo->ccaList].hCAInfo = hCACurrent;
hrNetwork = CAGetCAProperty(hCACurrent, CA_PROP_NAME, & awszName); if(hrNetwork != S_OK) { AE_DEBUG((AE_INFO, L"No name property for ca\n\r")); // skip to the next one.
hrNetwork = S_OK; continue; } if((awszName != NULL) && (*awszName != NULL)) { pInternalInfo->acaList[pInternalInfo->ccaList].wszName = AEAlloc(sizeof(WCHAR)*(wcslen(*awszName)+1)); if(pInternalInfo->acaList[pInternalInfo->ccaList].wszName == NULL) { CAFreeCAProperty(hCACurrent, awszName); hrLocal = E_OUTOFMEMORY; continue; } wcscpy(pInternalInfo->acaList[pInternalInfo->ccaList].wszName, *awszName); } else { AE_DEBUG((AE_INFO, L"No name property for ca\n\r")); if(awszName != NULL) { CAFreeCAProperty(hCACurrent, awszName); } // skip to the next one
continue; }
CAFreeCAProperty(hCACurrent, awszName); hrNetwork = CAGetCAProperty(hCACurrent, CA_PROP_DNSNAME, & awszName); if(hrNetwork != S_OK) { AE_DEBUG((AE_INFO, L"No DNS property for CA %ls\n\r", pInternalInfo->acaList[pInternalInfo->ccaList].wszName)); hrNetwork = S_OK; continue; } if((awszName != NULL) && (*awszName != NULL)) { pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName = AEAlloc(sizeof(WCHAR)*(wcslen(*awszName)+1)); if(pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName == NULL) { CAFreeCAProperty(hCACurrent, awszName); hrLocal = E_OUTOFMEMORY; continue; } wcscpy(pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName, *awszName); } else { AE_DEBUG((AE_INFO, L"No DNS property for CA %ls\n\r", pInternalInfo->acaList[pInternalInfo->ccaList].wszName)); if(awszName != NULL) { CAFreeCAProperty(hCACurrent, awszName); } continue; }
CAFreeCAProperty(hCACurrent, awszName); hrNetwork = CAGetCAProperty(hCACurrent, CA_PROP_CERT_TYPES, & pInternalInfo->acaList[pInternalInfo->ccaList].awszCertificateTemplates); if(hrNetwork != S_OK) { AE_DEBUG((AE_INFO, L"No cert type property for CA %ls\n\r", pInternalInfo->acaList[pInternalInfo->ccaList].wszName)); continue; }
hrNetwork = CAGetCACertificate(hCACurrent, &pCert); if(hrNetwork != S_OK) { AE_DEBUG((AE_INFO, L"No certificate property for CA %ls\n\r", pInternalInfo->acaList[pInternalInfo->ccaList].wszName)); continue; } cbHash = sizeof(pInternalInfo->acaList[pInternalInfo->ccaList].CACertHash);
if(!CertGetCertificateContextProperty(pCert, CERT_SHA1_HASH_PROP_ID, pInternalInfo->acaList[pInternalInfo->ccaList].CACertHash, &cbHash)) { continue; } CertFreeCertificateContext(pCert); AE_DEBUG((AE_INFO, L"CA %ls\\%ls available\n\r", pInternalInfo->acaList[pInternalInfo->ccaList].wszName, pInternalInfo->acaList[pInternalInfo->ccaList].wszDNSName)); pInternalInfo->ccaList++; } if(pInternalInfo->ccaList == 0) { AE_DEBUG((AE_WARNING, L"No CA's available for auto-enrollment\n\r")); }
fRet = TRUE; Ret: if (hrLocal != S_OK) { LogAutoEnrollmentError(hrLocal, EVENT_AE_LOCAL_CYCLE_INIT_FAILED, pInternalInfo->fMachineEnrollment, pInternalInfo->hToken, NULL, NULL); } if (hrNetwork != S_OK) { LogAutoEnrollmentError(hrNetwork, EVENT_AE_NETWORK_CYCLE_INIT_FAILED, pInternalInfo->fMachineEnrollment, pInternalInfo->hToken, NULL, NULL); }
if(SearchResult) { g_pfnldap_msgfree(SearchResult); }
AE_END(); if(hrLocal != S_OK) { return hrLocal; } return hrNetwork; }
//
// Name: FreeInternalInfo
//
// Description: This function frees and resources which were needed for
// auto enrollment.
//
void FreeInternalInfo( IN PINTERNAL_INFO pInternalInfo ) { DWORD i; if (pInternalInfo->hRootStore) CertCloseStore(pInternalInfo->hRootStore, 0); if (pInternalInfo->hCAStore) CertCloseStore(pInternalInfo->hCAStore, 0); if (pInternalInfo->hMYStore) CertCloseStore(pInternalInfo->hMYStore, 0);
if(pInternalInfo->awszldap_UPN) { g_pfnldap_value_free(pInternalInfo->awszldap_UPN); }
if(pInternalInfo->awszEmail) { g_pfnldap_value_free(pInternalInfo->awszEmail); }
if(pInternalInfo->wszDN) { AEFree(pInternalInfo->wszDN); } if(pInternalInfo->blobDN.pbData) { AEFree(pInternalInfo->blobDN.pbData); }
if(pInternalInfo->wszConstructedUPN) { AEFree(pInternalInfo->wszConstructedUPN); }
if( pInternalInfo->acaList ) {
for(i=0; i <pInternalInfo->ccaList; i++) { if(pInternalInfo->acaList[i].wszName) { AEFree(pInternalInfo->acaList[i].wszName); } if(pInternalInfo->acaList[i].wszDNSName) { AEFree(pInternalInfo->acaList[i].wszDNSName); } if(pInternalInfo->acaList[i].awszCertificateTemplates) {
CAFreeCAProperty(pInternalInfo->acaList[i].hCAInfo, pInternalInfo->acaList[i].awszCertificateTemplates); } if(pInternalInfo->acaList[i].hCAInfo) { CACloseCA(pInternalInfo->acaList[i].hCAInfo); } } AEFree(pInternalInfo->acaList); } }
//
// Name: InitInstance
//
// Description: This function initializes information needed to proceed with
// auto enrollment.
//
BOOL InitInstance( IN PCCTL_CONTEXT pCTLContext, IN PINTERNAL_INFO pInternalInfo, OUT PAE_INSTANCE_INFO pInstance ) { FILETIME ft;
pInstance->pCTLContext = CertDuplicateCTLContext(pCTLContext); pInstance->pInternalInfo = pInternalInfo;
// choose a random CA order
// Get the current time
GetSystemTimeAsFileTime(&ft);
// use mod to get something marginally random
// It's an index into the main list of ca's
if(pInternalInfo->ccaList) { pInstance->dwRandomIndex = ft.dwLowDateTime % pInternalInfo->ccaList; } else { pInstance->dwRandomIndex = 0; }
return TRUE; }
//
// Name: FreeInstance
//
// Description: This function frees and resources which were needed for
// auto enrollment.
//
void FreeInstance( IN PAE_INSTANCE_INFO pInstance ) { if (pInstance->pOldCert) CertFreeCertificateContext(pInstance->pOldCert);
if (pInstance->pwszCertType) AEFree(pInstance->pwszCertType);
if (pInstance->pwszAEIdentifier) AEFree(pInstance->pwszAEIdentifier);
if (pInstance->pCertTypeExtensions) // use LocalFree b/c certcli.dll
LocalFree(pInstance->pCertTypeExtensions); // uses LocalAlloc to alloc
if(pInstance->pCTLContext) { CertFreeCTLContext(pInstance->pCTLContext); } }
// Name: SetEnrollmentType
//
// Description: This function retrieves additional enrollment information
// needed to enroll for a cert.
//
BOOL SetEnrollmentCertType( IN PAE_INSTANCE_INFO pInstance, OUT PAUTO_ENROLL_INFO pEnrollmentInfo ) { BOOL fRet = FALSE;
// copy over the cert extensions, this is freed by the FreeInternalInfo
// function, but after being used in the EnrollmentInfo struct
pEnrollmentInfo->CertExtensions.cExtension = pInstance->pCertTypeExtensions->cExtension; pEnrollmentInfo->CertExtensions.rgExtension = pInstance->pCertTypeExtensions->rgExtension;
// copy over the cert type name, this is freed by the FreeInternalInfo
// function, but after being used in the EnrollmentInfo struct
pEnrollmentInfo->pwszCertType = pInstance->pwszCertType;
// The auto enrollment ID is used to uniquely tie an autoenrolled cert to
// it's autoenrollment object.
pEnrollmentInfo->pwszAutoEnrollmentID = pInstance->pwszAEIdentifier;
// copy over the handle to the MY store, this is freed by the
// FreeInternalInfo function, but after being used in the
// EnrollmentInfo struct
pEnrollmentInfo->hMYStore = pInstance->pInternalInfo->hMYStore;
// copy over the pointer to the cert context to be renewed, this
// is freed by the FreeInternalInfo function, but after being used in the
// EnrollmentInfo struct
if ((pInstance->pOldCert) && (pInstance->fRenewalOK)) { pEnrollmentInfo->pOldCert = pInstance->pOldCert; }
// Enrollment controll chooses provider type based on cert type
pEnrollmentInfo->dwProvType = 0; // Enrollment controll chooses key spec based on cert type
pEnrollmentInfo->dwKeySpec = 0;
// UNDONE - currently the gen key flags is hard coded to 0x0
pEnrollmentInfo->dwGenKeyFlags = 0;
fRet = TRUE; //Ret:
return fRet; }
// Name: GetCertTypeInfo
//
// Description: This function retrieves information (the extensions) for the
// cert type specified in the ListIdentifier field of the auto enrollment
// object (CTL) in the internal info structure. In addition the
// function makes a call to check if the current entity has permission
// to enroll for this cert type.
//
BOOL GetCertTypeInfo( IN OUT PAE_INSTANCE_INFO pInstance, IN LDAP * pld, OUT BOOL *pfPermissionToEnroll ) { HRESULT hr = S_OK; HCERTTYPE hCertType = 0; DWORD dwErr = 0; BOOL fRet = FALSE; LPWSTR *awszName = NULL;
LPWSTR wszCertTypeName = NULL;
CERT_EXTENSIONS CertTypeExtensions; AE_BEGIN(L"GetCertTypeInfo");
*pfPermissionToEnroll = FALSE;
AE_DEBUG((AE_INFO, L"Found auto-enrollment object with cert type: %ls\n\r", pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData));
wszCertTypeName = wcschr((LPWSTR)pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData, L'|'); if(wszCertTypeName) { wszCertTypeName++; } else { wszCertTypeName = (LPWSTR)pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData; }
// get a handle to the cert type
if (S_OK != (hr = CAFindCertTypeByName(wszCertTypeName, (HCAINFO)pld, // special optimization, ldap handle passed as scope
CT_FLAG_SCOPE_IS_LDAP_HANDLE | (pInstance->pInternalInfo->fMachineEnrollment? CT_ENUM_MACHINE_TYPES | CT_FIND_LOCAL_SYSTEM : CT_ENUM_USER_TYPES), &hCertType))) { AE_DEBUG((AE_WARNING, L"Unknown cert type: %ls\n\r", pInstance->pwszCertType)); goto Ret; } // get the extensions for the cert type
if (S_OK != (hr = CAGetCertTypeProperty(hCertType, CERTTYPE_PROP_DN, &awszName))) { AE_DEBUG((AE_WARNING, L"Could not get cert type full name: %ls\n\r", pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData)); goto Ret; } if((awszName == NULL) || (*awszName == NULL)) { AE_DEBUG((AE_WARNING, L"Could not get cert type full name: %ls\n\r", pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData)); hr = CERTSRV_E_PROPERTY_EMPTY; goto Ret; }
if (NULL == (pInstance->pwszCertType = (LPWSTR)AEAlloc( (wcslen(*awszName) + 1)*sizeof(WCHAR)))) { hr = E_OUTOFMEMORY; goto Ret; } wcscpy(pInstance->pwszCertType, *awszName);
if (NULL == (pInstance->pwszAEIdentifier = (LPWSTR)AEAlloc( (wcslen((LPWSTR)pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData) + 1)*sizeof(WCHAR)))) { hr = E_OUTOFMEMORY; goto Ret; } wcscpy(pInstance->pwszAEIdentifier, (LPWSTR)pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData);
// get the extensions for the cert type
if (S_OK != (hr = CAGetCertTypeExtensions(hCertType, &pInstance->pCertTypeExtensions))) { AE_DEBUG((AE_WARNING, L"Could not get cert type extensions: %ls\n\r", pInstance->pwszCertType)); goto Ret; }
// get the extensions for the cert type
if (S_OK != (hr = CAGetCertTypeFlags(hCertType, &pInstance->dwCertTypeFlags))) { AE_DEBUG((AE_WARNING, L"Could not get cert type flags: %ls\n\r", pInstance->pwszCertType)); goto Ret; }
// get the expiration offset
if (S_OK != (hr = CAGetCertTypeExpiration(hCertType, NULL, (LPFILETIME)&pInstance->ftExpirationOffset))) { AE_DEBUG((AE_WARNING, L"Could not get cert type expirations: %ls\n\r", pInstance->pwszCertType)); goto Ret; }
*pfPermissionToEnroll = (S_OK == CACertTypeAccessCheck(hCertType, pInstance->pInternalInfo->hToken));
fRet = TRUE; Ret: if (hr != S_OK) { LogAutoEnrollmentError(hr, EVENT_UAE_UNKNOWN_CERT_TYPE, pInstance->pInternalInfo->fMachineEnrollment, pInstance->pInternalInfo->hToken, (LPWSTR)pInstance->pCTLContext->pCtlInfo->ListIdentifier.pbData, NULL); } // close the handle to the cert type
if (hCertType) { if(awszName) { CAFreeCertTypeProperty(hCertType, awszName); } CACloseCertType(hCertType); }
AE_END(); return fRet; }
//
// Name: CompareEnhancedKeyUsageExtensions
//
// Description: This function checks if a the enhanced key usage extensions
// in a certificate contain the enhanced key usage extensions
// from the auto enrollment object (CTL),
//
HRESULT CompareEnhancedKeyUsageExtensions( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCertContext, IN OUT PAE_CERT_TEST_ARRAY *ppAEData ) { HRESULT hr = S_OK; PCERT_ENHKEY_USAGE pCertUsage = NULL; DWORD cbCertUsage; PCERT_ENHKEY_USAGE pAEObjUsage = NULL; DWORD cbAEObjUsage; PCERT_EXTENSION pAEObjUsageExt; PCERT_EXTENSION pCertUsageExt; DWORD i; DWORD j;
LPWSTR wszCertEKU = NULL; LPWSTR wszTemplateEKU = NULL;
// get the enhanced key usages from the auto enrollment obj extensions
pCertUsageExt = CertFindExtension(szOID_ENHANCED_KEY_USAGE, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension);
if(pCertUsageExt) { if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, pCertUsageExt->Value.pbData, pCertUsageExt->Value.cbData, 0, NULL, &cbCertUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
if (NULL == (pCertUsage = (PCERT_ENHKEY_USAGE)AEAlloc(cbCertUsage))) { hr = E_OUTOFMEMORY; goto Ret; }
if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, pCertUsageExt->Value.pbData, pCertUsageExt->Value.cbData, 0, pCertUsage, &cbCertUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } } else { // No usage, so this cert is good for everything
goto Ret;
}
// get the enhanced key usages from the auto enrollment obj extensions
pAEObjUsageExt = CertFindExtension(szOID_ENHANCED_KEY_USAGE, pInstance->pCertTypeExtensions->cExtension, pInstance->pCertTypeExtensions->rgExtension);
if(pAEObjUsageExt) { if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, pAEObjUsageExt->Value.pbData, pAEObjUsageExt->Value.cbData, 0, NULL, &cbAEObjUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
if (NULL == (pAEObjUsage = (PCERT_ENHKEY_USAGE)AEAlloc(cbAEObjUsage))) { hr = E_OUTOFMEMORY; goto Ret; }
if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_ENHANCED_KEY_USAGE, pAEObjUsageExt->Value.pbData, pAEObjUsageExt->Value.cbData, 0, pAEObjUsage, &cbAEObjUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } } else { // The template requires no usage extension, so
// because the cert has a usage extension, we fail
// the test.
goto Failed;
}
// check if the number of usages is smaller in the cert then in the
// auto enrollment object
if (pCertUsage->cUsageIdentifier < pAEObjUsage->cUsageIdentifier) { goto Failed; }
// check if all the usages found in the auto enrollment object are in
// the cert
for (i=0;i<pAEObjUsage->cUsageIdentifier;i++) { for (j=0;j<pCertUsage->cUsageIdentifier;j++) { if (0 == strcmp(pCertUsage->rgpszUsageIdentifier[j], pAEObjUsage->rgpszUsageIdentifier[i])) { break; } } if (j == pCertUsage->cUsageIdentifier) { goto Failed; } }
Ret:
if(wszCertEKU) { AEFree(wszCertEKU); }
if(wszTemplateEKU) { AEFree(wszTemplateEKU); }
if (pCertUsage) AEFree(pCertUsage); if (pAEObjUsage) AEFree(pAEObjUsage); return hr;
Failed:
// Log a failure of this test
// Build extension strings
wszCertEKU = HelperExtensionToString(pCertUsageExt);
/* if(wszCertEKU == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
wszTemplateEKU = HelperExtensionToString(pAEObjUsageExt);
/* if(wszTemplateEKU == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
AELogTestResult(ppAEData, AE_TEST_EXTENSION_EKU, wszCertEKU ? wszCertEKU : g_wszEmptyExtension, wszTemplateEKU ? wszTemplateEKU : g_wszEmptyExtension);
goto Ret; }
#define MAX_KEY_USAGE_SIZE 20 // sizeof(CRYPT_BIT_BLOB) for 64bit + sizeof(DWORD)
//
// Name: CompareKeyUsageExtensions
//
// Description: This function checks if the key usages
// in a certificate are a superset of the key usages
// from the auto enrollment object (CTL),
//
HRESULT CompareKeyUsageExtensions( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCertContext, IN OUT PAE_CERT_TEST_ARRAY *ppAEData ) { HRESULT hr = S_OK; PCERT_EXTENSION pCertUsageExt; PCERT_EXTENSION pAEObjUsageExt; DWORD i; DWORD dwMask = (DWORD)-1; BYTE bCertUsageBuffer[MAX_KEY_USAGE_SIZE]; BYTE bAEObjUsageBuffer[MAX_KEY_USAGE_SIZE]; PCRYPT_BIT_BLOB pCertUsage = (PCRYPT_BIT_BLOB)bCertUsageBuffer; PCRYPT_BIT_BLOB pAEObjUsage = (PCRYPT_BIT_BLOB)bAEObjUsageBuffer; DWORD dwKeyUsage;
LPWSTR wszCertKU = NULL; LPWSTR wszTemplateKU = NULL;
// get the key usages from the cert
pCertUsageExt = CertFindExtension(szOID_KEY_USAGE, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension);
// get the key usages from the auto enrollment obj extensions
pAEObjUsageExt = CertFindExtension(szOID_KEY_USAGE, pInstance->pCertTypeExtensions->cExtension, pInstance->pCertTypeExtensions->rgExtension);
// If the cert has no key usage extension, then it's good in general
if (NULL == pCertUsageExt) { goto Ret; }
// If the type requires no extension, and the cert has one,
// then the cert is too limited.
if(pAEObjUsageExt == NULL) { goto Failed; }
// Decode the key usage into their basic bit's
dwKeyUsage = MAX_KEY_USAGE_SIZE; if(!CryptDecodeObject(X509_ASN_ENCODING, X509_KEY_USAGE, pCertUsageExt->Value.pbData, pCertUsageExt->Value.cbData, 0, (PVOID *)pCertUsage, &dwKeyUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
// Decode the key usage into their basic bit's
dwKeyUsage = MAX_KEY_USAGE_SIZE; if(!CryptDecodeObject(X509_ASN_ENCODING, X509_KEY_USAGE, pAEObjUsageExt->Value.pbData, pAEObjUsageExt->Value.cbData, 0, (PVOID *)pAEObjUsage, &dwKeyUsage)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; }
// Get the mask based on algs
for(i=0; i < g_cKUMasks; i++) { if(strcmp(pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, g_aKUMasks[i].pszAlg) == 0) { dwMask = g_aKUMasks[i].dwMask; break; } }
// see if auto enroll obj keys usages are a sub set of cert's
if (pAEObjUsage->cbData > pCertUsage->cbData) { goto Failed; }
for (i=0;i<pAEObjUsage->cbData;i++) { BYTE bMask = 0xff; if(i < sizeof(DWORD)) { bMask = ((PBYTE)&dwMask)[i]; } if ((pAEObjUsage->pbData[i] & bMask ) != ((pAEObjUsage->pbData[i] & bMask ) & pCertUsage->pbData[i])) { goto Failed; } }
Ret:
if(wszCertKU) { AEFree(wszCertKU); }
if(wszTemplateKU) { AEFree(wszTemplateKU); }
return hr; Failed:
// Log a failure of this test
// Build extension strings
wszCertKU = HelperExtensionToString(pCertUsageExt);
/* if(wszCertKU == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
wszTemplateKU = HelperExtensionToString(pAEObjUsageExt); /* if(wszTemplateKU == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
AELogTestResult(ppAEData, AE_TEST_EXTENSION_KU, wszCertKU ? wszCertKU : g_wszEmptyExtension, wszTemplateKU ? wszTemplateKU : g_wszEmptyExtension); goto Ret;
} //
// Name: CompareBasicConstraints
//
// Description: This function checks if the basic constraints
// in a certificate are a superset of the basic
// constraints from the auto enrollment object (CTL),
//
HRESULT CompareBasicConstraints( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCertContext, IN OUT PAE_CERT_TEST_ARRAY *ppAEData ) { HRESULT hr = S_OK; PCERT_EXTENSION pCertConstraints; CERT_BASIC_CONSTRAINTS2_INFO CertConstraintInfo = {FALSE, FALSE, 0}; PCERT_EXTENSION pAEObjConstraints; CERT_BASIC_CONSTRAINTS2_INFO AEObjConstraintInfo = {FALSE, FALSE, 0}; DWORD cb; DWORD i;
LPWSTR wszCertBC = NULL;
LPWSTR wszTemplateBC = NULL;
// get the basic constraints from the cert
pCertConstraints = CertFindExtension(szOID_BASIC_CONSTRAINTS2, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension);
// get the basic constraints from the auto enrollment obj extensions
pAEObjConstraints = CertFindExtension(szOID_BASIC_CONSTRAINTS2, pInstance->pCertTypeExtensions->cExtension, pInstance->pCertTypeExtensions->rgExtension);
// decode the objects
if(pCertConstraints) { cb = sizeof(CertConstraintInfo); if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, pCertConstraints->Value.pbData, pCertConstraints->Value.cbData, 0, &CertConstraintInfo, &cb)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } }
if(pAEObjConstraints) { cb = sizeof(AEObjConstraintInfo); if (!CryptDecodeObject(CRYPT_ASN_ENCODING, szOID_BASIC_CONSTRAINTS2, pAEObjConstraints->Value.pbData, pAEObjConstraints->Value.cbData, 0, &AEObjConstraintInfo, &cb)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } } // see if auto enroll obj constraints are the same as the cert's
if (AEObjConstraintInfo.fCA != CertConstraintInfo.fCA) { goto Failed; } if (CertConstraintInfo.fCA) { if (CertConstraintInfo.fPathLenConstraint != AEObjConstraintInfo.fPathLenConstraint) { goto Failed; } if (CertConstraintInfo.fPathLenConstraint) { if (CertConstraintInfo.dwPathLenConstraint > AEObjConstraintInfo.dwPathLenConstraint) { goto Failed; } } }
Ret:
// Build extension strings
if(wszCertBC) { AEFree(wszCertBC); }
if(wszTemplateBC) { AEFree(wszTemplateBC); }
return hr; Failed:
// Log a failure of this test
// Build extension strings
wszCertBC = HelperExtensionToString(pCertConstraints);
/* if(wszCertBC == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
wszTemplateBC = HelperExtensionToString(pAEObjConstraints); /* if(wszTemplateBC == NULL)
{ hr = E_OUTOFMEMORY; goto Ret; }*/
AELogTestResult(ppAEData, AE_TEST_EXTENSION_BC, wszCertBC ? wszCertBC : g_wszEmptyExtension, wszTemplateBC ? wszTemplateBC : g_wszEmptyExtension); goto Ret;
}
//
// Name: VerifyTemplateName
//
// Description:
//
HRESULT VerifyTemplateName( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCertContext, IN OUT PAE_CERT_TEST_ARRAY *ppAEData ) { HRESULT hr = S_OK; BOOL fMatch = FALSE; PCERT_NAME_VALUE pTemplateName = NULL;
if(pInstance->dwCertTypeFlags & CT_FLAG_ADD_TEMPLATE_NAME) { DWORD cbTemplateName = MAX_TEMPLATE_NAME_VALUE_SIZE; BYTE pbName[MAX_TEMPLATE_NAME_VALUE_SIZE]; // Only needs to be as big as wszDomainController
PCERT_EXTENSION pCertType = CertFindExtension(szOID_ENROLL_CERTTYPE_EXTENSION, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension);
if(pCertType == NULL) { goto Failed; } if(!CryptDecodeObject(X509_ASN_ENCODING, X509_UNICODE_ANY_STRING, pCertType->Value.pbData, pCertType->Value.cbData, 0, pbName, &cbTemplateName)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } pTemplateName = (PCERT_NAME_VALUE)pbName; if(pTemplateName->Value.pbData == NULL) { goto Failed; }
if(wcscmp((LPWSTR) pTemplateName->Value.pbData, pInstance->pwszCertType) != 0) { goto Failed; } }
Ret: return hr;
Failed:
{ WCHAR wszTemplateName[MAX_PATH]; if(pTemplateName) { wcscpy(wszTemplateName, (LPWSTR) pTemplateName->Value.pbData); } else { if(!LoadString(g_hInstance, IDS_AUTOENROLL_TEMPLATE_EXT, wszTemplateName, MAX_PATH)) { wcscpy(wszTemplateName, L"No template name"); }
} AELogTestResult(ppAEData, AE_TEST_EXTENSION_TEMPLATE, wszTemplateName, pInstance->pwszCertType);
}
goto Ret; }
//
// Name: VerifyCommonExtensions
//
// Description: This function checks if a the extensions in a certificate
// contain the appropriate extensions from the certificate template
//
HRESULT VerifyCommonExtensions( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCertContext, IN OUT PAE_CERT_TEST_ARRAY *ppAEData ) { HRESULT hr = S_OK;
AE_BEGIN(L"VerifyCommonExtensions");
if (S_OK != (hr = CompareEnhancedKeyUsageExtensions(pInstance, pCertContext, ppAEData))) { goto Ret; }
// check key usages
if (S_OK != (hr = CompareKeyUsageExtensions(pInstance, pCertContext, ppAEData))) { goto Ret; }
// check basic constraints
if (S_OK != (hr = CompareBasicConstraints(pInstance, pCertContext, ppAEData))) { goto Ret; }
// Check to see if the cert extension should be there, and if it matches.
// check basic constraints
if (S_OK != (hr = VerifyTemplateName(pInstance, pCertContext, ppAEData))) { goto Ret; }
Ret: AE_END(); return hr; }
//
// Name: VerifyCertificateNaming
//
// Description: Determine whether the acutal naming information
// for the user matches that in the certificate.
//
HRESULT VerifyCertificateNaming( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCert, IN OUT PAE_CERT_TEST_ARRAY * ppAEData ) {
HRESULT hr = S_OK; PCERT_NAME_INFO pInfo = NULL;
DWORD cbInfo = 0; DWORD iRDN, iATTR; DWORD iExtension;
BOOL fSubjectUPNMatch = FALSE; BOOL fSubjectEmailMatch = FALSE;
BOOL fAltSubjectEmailMatch = FALSE; BOOL fDNMatch = FALSE; BOOL fDNSMatch = FALSE; BOOL fObjIDMatch = FALSE; BOOL fAltSubjectUPNMatch = FALSE;
BOOL fDisplaySubjectName = FALSE; BOOL fDisplayAltSubjectName = FALSE;
AE_BEGIN(L"VerifyCertificateNaming");
// First, check if the cert type specifies enrollee supplied subject name
if(0 != (pInstance->dwCertTypeFlags & CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT)) { // We don't care what's in the cert, so return.
goto Ret; }
fSubjectEmailMatch = fAltSubjectEmailMatch = ((pInstance->pInternalInfo->awszEmail == NULL) || (*pInstance->pInternalInfo->awszEmail == NULL));
fObjIDMatch = (0 == (pInstance->dwCertTypeFlags & CT_FLAG_ADD_OBJ_GUID));
// Verify the names in the Subject Name
if(!CryptDecodeObjectEx(pCert->dwCertEncodingType, X509_NAME, pCert->pCertInfo->Subject.pbData, pCert->pCertInfo->Subject.cbData, CRYPT_ENCODE_ALLOC_FLAG, NULL, &pInfo, &cbInfo)) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Could not decode certificate name (%lx)\n\r", hr)); goto Ret; }
AE_DEBUG((AE_TRACE, L"Comparing Subject Name\n\r"));
for(iRDN = 0; iRDN < pInfo->cRDN; iRDN++) { for(iATTR = 0; iATTR < pInfo->rgRDN[iRDN].cRDNAttr; iATTR++) { LPTSTR wszRDNAttr = NULL; DWORD cszRDNAttr = 0;
// Get this name string
cszRDNAttr = CertRDNValueToStr(pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].dwValueType, &pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].Value, NULL, 0); if(cszRDNAttr == 0) { continue; }
wszRDNAttr = AEAlloc(cszRDNAttr * sizeof(TCHAR)); if(wszRDNAttr == NULL) { hr = E_OUTOFMEMORY; goto Ret; } cszRDNAttr = CertRDNValueToStr(pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].dwValueType, &pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].Value, wszRDNAttr, cszRDNAttr); if(cszRDNAttr == 0) { // We couldn't convert the name for some reason
AEFree(wszRDNAttr); continue; }
if(strcmp(szOID_COMMON_NAME, pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].pszObjId) == 0) { // If there are published UPN's, then
// we should check against those for a match,
// otherwise, we check against the generated UPN
if((pInstance->pInternalInfo->awszldap_UPN != NULL) && (*pInstance->pInternalInfo->awszldap_UPN != NULL)) { LPTSTR *pwszCurrentName = pInstance->pInternalInfo->awszldap_UPN; while(*pwszCurrentName) { if(_tcscmp(*pwszCurrentName, wszRDNAttr) == 0) { fSubjectUPNMatch = TRUE; break; } pwszCurrentName++; } } else if(pInstance->pInternalInfo->wszConstructedUPN != NULL) { if(_tcscmp(pInstance->pInternalInfo->wszConstructedUPN, wszRDNAttr) == 0) { fSubjectUPNMatch = TRUE; } }
}
if(strcmp(szOID_RSA_emailAddr, pInfo->rgRDN[iRDN].rgRDNAttr[iATTR].pszObjId) == 0) { // If there are published e-mails, then
// we should check against those for a match
if((pInstance->pInternalInfo->awszEmail != NULL) && (*pInstance->pInternalInfo->awszEmail != NULL)) { LPTSTR *pwszCurrentEmail = pInstance->pInternalInfo->awszEmail; while(*pwszCurrentEmail) { if(_tcscmp(*pwszCurrentEmail, wszRDNAttr) == 0) { fSubjectEmailMatch = TRUE; break; } pwszCurrentEmail++; } } else { // We have no e-mail name for this subject, yet there
// was one in the cert.
fSubjectEmailMatch = FALSE; } } AEFree(wszRDNAttr); } }
// Now check the extensions
for(iExtension = 0; iExtension < pCert->pCertInfo->cExtension; iExtension++) { if((strcmp(pCert->pCertInfo->rgExtension[iExtension].pszObjId, szOID_SUBJECT_ALT_NAME) == 0) || (strcmp(pCert->pCertInfo->rgExtension[iExtension].pszObjId, szOID_SUBJECT_ALT_NAME2) == 0)) { PCERT_ALT_NAME_INFO pAltName = NULL; DWORD cbAltName = 0; DWORD iAltName; // Now, check the AltSubjectName fields.
if(!CryptDecodeObjectEx(pCert->dwCertEncodingType, X509_ALTERNATE_NAME, pCert->pCertInfo->rgExtension[iExtension].Value.pbData, pCert->pCertInfo->rgExtension[iExtension].Value.cbData, CRYPT_ENCODE_ALLOC_FLAG, NULL, &pAltName, &cbAltName)) { continue; }
for(iAltName = 0; iAltName < pAltName->cAltEntry; iAltName++) { switch(pAltName->rgAltEntry[iAltName].dwAltNameChoice) { case CERT_ALT_NAME_RFC822_NAME: { // If there are published e-mails, then
// we should check against those for a match
if((pInstance->pInternalInfo->awszEmail != NULL) && (*pInstance->pInternalInfo->awszEmail != NULL)) { LPTSTR *pwszCurrentEmail = pInstance->pInternalInfo->awszEmail; while(*pwszCurrentEmail) { if(_tcscmp(*pwszCurrentEmail, pAltName->rgAltEntry[iAltName].pwszRfc822Name) == 0) { fAltSubjectEmailMatch = TRUE; break; } pwszCurrentEmail++; } } else { fAltSubjectEmailMatch = FALSE; } } break; case CERT_ALT_NAME_DIRECTORY_NAME: { if(CertCompareCertificateName(pCert->dwCertEncodingType, &pInstance->pInternalInfo->blobDN, &pAltName->rgAltEntry[iAltName].DirectoryName)) { fDNMatch = TRUE; } } break; case CERT_ALT_NAME_DNS_NAME: { if(pInstance->pInternalInfo->fMachineEnrollment) { if((pInstance->pInternalInfo->awszldap_UPN != NULL) && (*pInstance->pInternalInfo->awszldap_UPN != NULL)) { LPTSTR *pwszCurrentName = pInstance->pInternalInfo->awszldap_UPN; while(*pwszCurrentName) { if(_tcscmp(*pwszCurrentName, pAltName->rgAltEntry[iAltName].pwszDNSName) == 0) { fDNSMatch = TRUE; break; } pwszCurrentName++; } } } else { fDNSMatch = FALSE; } } break; case CERT_ALT_NAME_OTHER_NAME:
if(strcmp(pAltName->rgAltEntry[iAltName].pOtherName->pszObjId, szOID_NTDS_REPLICATION) == 0) { if(pInstance->dwCertTypeFlags & CT_FLAG_ADD_OBJ_GUID) { // Object ID's should always be the same, so don't compare them
// for now.
fObjIDMatch = TRUE; } else { // We had an obj-id, but we shouldn't
fObjIDMatch = FALSE; } } else if (strcmp(pAltName->rgAltEntry[iAltName].pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME) == 0) { PCERT_NAME_VALUE PrincipalNameBlob = NULL; DWORD PrincipalNameBlobSize = 0; if(CryptDecodeObjectEx(pCert->dwCertEncodingType, X509_UNICODE_ANY_STRING, pAltName->rgAltEntry[iAltName].pOtherName->Value.pbData, pAltName->rgAltEntry[iAltName].pOtherName->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, (PVOID)&PrincipalNameBlob, &PrincipalNameBlobSize)) {
// If there are published UPN's, then
// we should check against those for a match,
// otherwise, we check against the generated UPN
if((pInstance->pInternalInfo->awszldap_UPN != NULL) && (*pInstance->pInternalInfo->awszldap_UPN != NULL)) { LPTSTR *pwszCurrentName = pInstance->pInternalInfo->awszldap_UPN; while(*pwszCurrentName) { if(_tcscmp(*pwszCurrentName, (LPWSTR)PrincipalNameBlob->Value.pbData) == 0) { fAltSubjectUPNMatch = TRUE; break; } pwszCurrentName++; } } else if(pInstance->pInternalInfo->wszConstructedUPN != NULL) { if(_tcscmp(pInstance->pInternalInfo->wszConstructedUPN, (LPWSTR)PrincipalNameBlob->Value.pbData) == 0) { fAltSubjectUPNMatch = TRUE; } } LocalFree(PrincipalNameBlob); }
} break; default: break;
} } LocalFree(pAltName); } }
if(((pInstance->pInternalInfo->fMachineEnrollment)? ((!fSubjectUPNMatch)||(!fDNSMatch)): ((!fSubjectUPNMatch) && (!fAltSubjectUPNMatch)))) { // We didn't find an appropriate UPN in either the subject or alt subject
DWORD cUPNChars = 0; LPWSTR wszUPN = NULL; LPTSTR *pwszCurrentName = pInstance->pInternalInfo->awszldap_UPN; if(pInstance->pInternalInfo->wszConstructedUPN) { cUPNChars += wcslen(pInstance->pInternalInfo->wszConstructedUPN)+1; }
while((NULL != pwszCurrentName) && (NULL != *pwszCurrentName)) { cUPNChars += wcslen(*pwszCurrentName++)+1; } wszUPN = AEAlloc((cUPNChars+1)*sizeof(WCHAR)); if(wszUPN == NULL) { hr = E_OUTOFMEMORY; goto Ret; } wszUPN[0] = 0; if(pInstance->pInternalInfo->wszConstructedUPN) { wcscat(wszUPN, pInstance->pInternalInfo->wszConstructedUPN); wcscat(wszUPN, L","); } pwszCurrentName = pInstance->pInternalInfo->awszldap_UPN; while((NULL != pwszCurrentName) && (NULL != *pwszCurrentName)) { wcscat(wszUPN, *pwszCurrentName++); wcscat(wszUPN, L","); }
// Kill the last ','
wszUPN[cUPNChars-1] = 0;
AELogTestResult(ppAEData, pInstance->pInternalInfo->fMachineEnrollment?AE_TEST_NAME_SUBJECT_DNS:AE_TEST_NAME_UPN, wszUPN); AEFree(wszUPN); fDisplaySubjectName = TRUE; fDisplayAltSubjectName = TRUE; }
if((pInstance->dwCertTypeFlags & CT_FLAG_ADD_EMAIL) && ((!fSubjectEmailMatch) || (!fAltSubjectEmailMatch))) { // We didn't find an appropriate UPN in either the subject or alt subject
DWORD cEmailChars = 0; LPWSTR wszEmail = NULL; LPTSTR *pwszCurrentEmail = pInstance->pInternalInfo->awszEmail; while((NULL != pwszCurrentEmail) && (NULL != *pwszCurrentEmail)) { cEmailChars += wcslen(*pwszCurrentEmail++)+1; } wszEmail = AEAlloc((cEmailChars+1)*sizeof(WCHAR)); if(wszEmail == NULL) { hr = E_OUTOFMEMORY; goto Ret; } wszEmail[0] = 0;
pwszCurrentEmail = pInstance->pInternalInfo->awszEmail; while((NULL != pwszCurrentEmail) && (NULL != *pwszCurrentEmail)) { wcscat(wszEmail, *pwszCurrentEmail++); wcscat(wszEmail, L","); } // Kill the last ','
wszEmail[cEmailChars-1] = 0;
if(!fSubjectEmailMatch && fAltSubjectEmailMatch) { AELogTestResult(ppAEData, AE_TEST_NAME_SUBJECT_EMAIL, wszEmail); } else if(!fAltSubjectEmailMatch && fSubjectEmailMatch) { AELogTestResult(ppAEData, AE_TEST_NAME_ALT_SUBJECT_EMAIL, wszEmail); } else if((!fAltSubjectEmailMatch) && (!fSubjectEmailMatch)) { AELogTestResult(ppAEData, AE_TEST_NAME_BOTH_SUBJECT_EMAIL, wszEmail); } AEFree(wszEmail); if(!fSubjectEmailMatch) { fDisplaySubjectName = TRUE; } if(!fAltSubjectEmailMatch) { fDisplayAltSubjectName = TRUE; }
}
if((pInstance->dwCertTypeFlags & CT_FLAG_ADD_DIRECTORY_PATH) && (!fDNMatch)) { AELogTestResult(ppAEData, AE_TEST_NAME_DIRECTORY_NAME, pInstance->pInternalInfo->wszDN); fDisplayAltSubjectName = TRUE;
}
if(!fObjIDMatch) {
AELogTestResult(ppAEData, (pInstance->dwCertTypeFlags & CT_FLAG_ADD_OBJ_GUID)?AE_TEST_NAME_NO_OBJID:AE_TEST_NAME_OBJID); fDisplayAltSubjectName = TRUE; }
if(fDisplaySubjectName) {
DWORD cNameStr = 0; LPWSTR wszNameStr = NULL; cNameStr = CertNameToStr(X509_ASN_ENCODING, &pCert->pCertInfo->Subject, CERT_X500_NAME_STR, NULL, 0);
if(cNameStr) { wszNameStr = (LPWSTR)AEAlloc(cNameStr*sizeof(WCHAR)); if(wszNameStr == NULL) { hr = E_OUTOFMEMORY; goto Ret; }
cNameStr = CertNameToStr(X509_ASN_ENCODING, &pCert->pCertInfo->Subject, CERT_X500_NAME_STR, wszNameStr, cNameStr);
AELogTestResult(ppAEData, AT_TEST_SUBJECT_NAME, wszNameStr); AEFree(wszNameStr); }
}
if(fDisplayAltSubjectName) { DWORD cbFormat = 0; LPWSTR wszFormat = NULL; for(iExtension = 0; iExtension < pCert->pCertInfo->cExtension; iExtension++) { if((strcmp(pCert->pCertInfo->rgExtension[iExtension].pszObjId, szOID_SUBJECT_ALT_NAME) == 0) || (strcmp(pCert->pCertInfo->rgExtension[iExtension].pszObjId, szOID_SUBJECT_ALT_NAME2) == 0)) {
wszFormat = HelperExtensionToString(&pCert->pCertInfo->rgExtension[iExtension]);
if(wszFormat == NULL) { hr = E_OUTOFMEMORY; goto Ret; }
AELogTestResult(ppAEData, AT_TEST_ALT_SUBJECT_NAME, wszFormat); LocalFree(wszFormat); } }
} Ret: if(pInfo) { LocalFree(pInfo); }
return hr; }
//
// Name: VerifyCertificateChaining
//
// Description: This function checks if if the certificate has expired, or has been revoked
//
HRESULT VerifyCertificateChaining( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCert, IN OUT PAE_CERT_TEST_ARRAY * ppAEData ) {
HRESULT hr = S_OK; HRESULT hrChainStatus = S_OK;
CERT_CHAIN_PARA ChainParams; CERT_CHAIN_POLICY_PARA ChainPolicy; CERT_CHAIN_POLICY_STATUS PolicyStatus; PCCERT_CHAIN_CONTEXT pChainContext = NULL; PCTL_INFO pCTLInfo = NULL; LARGE_INTEGER ftTime;
AE_BEGIN(L"VerifyCertificateChaining");
if(*ppAEData) { (*ppAEData)->fRenewalOK = FALSE; }
pCTLInfo = pInstance->pCTLContext->pCtlInfo; // Build the certificate chain for trust
// operations
ChainParams.cbSize = sizeof(ChainParams); ChainParams.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
ChainParams.RequestedUsage.Usage.cUsageIdentifier = 0; ChainParams.RequestedUsage.Usage.rgpszUsageIdentifier = NULL;
ChainPolicy.cbSize = sizeof(ChainPolicy); ChainPolicy.dwFlags = 0; // ignore nothing
ChainPolicy.pvExtraPolicyPara = NULL;
PolicyStatus.cbSize = sizeof(PolicyStatus); PolicyStatus.dwError = 0; PolicyStatus.lChainIndex = -1; PolicyStatus.lElementIndex = -1; PolicyStatus.pvExtraPolicyStatus = NULL;
// Build a small time skew into the chain building in order to deal
// with servers that may skew slightly fast.
GetSystemTimeAsFileTime((LPFILETIME)&ftTime); ftTime.QuadPart += Int32x32To64(FILETIME_TICKS_PER_SECOND, DEFAULT_AUTOENROLL_SKEW);
// Build a cert chain for the current status of the cert..
if(!CertGetCertificateChain(pInstance->pInternalInfo->fMachineEnrollment?HCCE_LOCAL_MACHINE:HCCE_CURRENT_USER, pCert, (LPFILETIME)&ftTime, NULL, &ChainParams, CERT_CHAIN_REVOCATION_CHECK_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN, NULL, &pChainContext)) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_WARNING, L"Could not build certificate chain (%lx)\n\r", hr));
goto Ret; } if(!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, pChainContext, &ChainPolicy, &PolicyStatus)) { hrChainStatus = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_WARNING, L"Base Chain Policy failed (%lx) - must get new cert\n\r", PolicyStatus.dwError)); } else { hrChainStatus = PolicyStatus.dwError; } if((S_OK == hrChainStatus) || (CRYPT_E_NO_REVOCATION_CHECK == hrChainStatus) || (CRYPT_E_REVOCATION_OFFLINE == hrChainStatus)) { // The cert is still currently acceptable by trust standards,
// so we can renew it.
if(NULL == (*ppAEData)) { (*ppAEData) = (PAE_CERT_TEST_ARRAY)LocalAlloc(LMEM_FIXED, sizeof(AE_CERT_TEST_ARRAY) + (AE_CERT_TEST_SIZE_INCREMENT - ANYSIZE_ARRAY)*sizeof(AE_CERT_TEST)); if((*ppAEData) == NULL) { hr = E_OUTOFMEMORY; goto Ret;
} (*ppAEData)->dwVersion = AE_CERT_TEST_ARRAY_VERSION; (*ppAEData)->cTests = 0; (*ppAEData)->cMaxTests = AE_CERT_TEST_SIZE_INCREMENT; } (*ppAEData)->fRenewalOK = TRUE; hrChainStatus = S_OK; } else { LPWSTR wszChainStatus = NULL; if(0 == FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hrChainStatus, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (WCHAR *) &wszChainStatus, 0, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Ret; } // The cert has expired or has been revoked or something,
// we must re-enroll
AELogTestResult(ppAEData, AE_TEST_CHAIN_FAIL, hrChainStatus, wszChainStatus); AEFree(wszChainStatus); } // Verify that the immediate CA of the cert exists in the
// Autoenrollment Object. Empty ctl's imply any CA is
// ok.
if(pCTLInfo->cCTLEntry) { DWORD i; BYTE pHash[20]; DWORD cbHash; cbHash = sizeof(pHash); AE_DEBUG((AE_TRACE, L"Verifying Issuer presence in auto-enrollment object\n\r"));
if((pChainContext == NULL) || (pChainContext->rgpChain == NULL) || (pChainContext->cChain < 1) || (pChainContext->rgpChain[0]->rgpElement == NULL) || (pChainContext->rgpChain[0]->cElement < 2)) { hr = E_POINTER; goto Ret; }
if(!CertGetCertificateContextProperty(pChainContext->rgpChain[0]->rgpElement[1]->pCertContext, CERT_SHA1_HASH_PROP_ID, pHash, &cbHash)) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Could not get certificate Hash (%lx)\n\r",hr)); goto Ret; }
for(i=0; i < pCTLInfo->cCTLEntry; i++) { if(pCTLInfo->rgCTLEntry[i].SubjectIdentifier.pbData == NULL) continue;
if(pCTLInfo->rgCTLEntry[i].SubjectIdentifier.cbData != cbHash) continue;
if(memcmp(pCTLInfo->rgCTLEntry[i].SubjectIdentifier.pbData, pHash, cbHash) == 0) { break; } } if(i == pCTLInfo->cCTLEntry) { AE_DEBUG((AE_WARNING, L"Issuer not in auto-enrollment list - must renew\n\r"));
AELogTestResult(ppAEData, AE_TEST_ISSUER_FAIL); } }
if(pChainContext) { CertFreeCertificateChain(pChainContext); pChainContext = NULL; }
// only check expiration status if the cert is otherwise ok
if(hrChainStatus == S_OK) {
// Nudge the evaluation of the cert chain by the expiration
// offset so we know if is expired by that time in the future.
GetSystemTimeAsFileTime((LPFILETIME)&ftTime); // Build the certificate chain for trust
// operations
ChainParams.cbSize = sizeof(ChainParams); ChainParams.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
ChainParams.RequestedUsage.Usage.cUsageIdentifier = 0; ChainParams.RequestedUsage.Usage.rgpszUsageIdentifier = NULL;
if(pInstance->ftExpirationOffset.QuadPart < 0) { LARGE_INTEGER ftHalfLife; ftHalfLife.QuadPart = (((LARGE_INTEGER *)&pCert->pCertInfo->NotAfter)->QuadPart - ((LARGE_INTEGER *)&pCert->pCertInfo->NotBefore)->QuadPart)/2;
if(ftHalfLife.QuadPart > (- pInstance->ftExpirationOffset.QuadPart)) { // Assume that the old cert is not time nesting invalid
ftTime.QuadPart -= pInstance->ftExpirationOffset.QuadPart; } else { ftTime.QuadPart += ftHalfLife.QuadPart; } } else { ftTime = pInstance->ftExpirationOffset; }
// Is this a renewal, or an enroll on behalf of...
if(!CertGetCertificateChain(pInstance->pInternalInfo->fMachineEnrollment?HCCE_LOCAL_MACHINE:HCCE_CURRENT_USER, pCert, (LPFILETIME)&ftTime, NULL, &ChainParams, 0, NULL, &pChainContext)) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_WARNING, L"Could not build certificate chain (%lx)\n\r", hr));
goto Ret; }
// Verify revocation and expiration of the certificate
ChainPolicy.cbSize = sizeof(ChainPolicy); ChainPolicy.dwFlags = 0; // ignore nothing
ChainPolicy.pvExtraPolicyPara = NULL;
PolicyStatus.cbSize = sizeof(PolicyStatus); PolicyStatus.dwError = 0; PolicyStatus.lChainIndex = -1; PolicyStatus.lElementIndex = -1; PolicyStatus.pvExtraPolicyStatus = NULL;
if(!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, pChainContext, &ChainPolicy, &PolicyStatus)) { hrChainStatus = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_WARNING, L"Base Chain Policy failed (%lx) - must get new cert\n\r", hr)); } else { hrChainStatus = PolicyStatus.dwError; }
if((S_OK != hrChainStatus) && (CRYPT_E_NO_REVOCATION_CHECK != hrChainStatus) && (CRYPT_E_REVOCATION_OFFLINE != hrChainStatus)) { // The cert has expired or has been revoked or something,
// we must re-enroll
AELogTestResult(ppAEData, AT_TEST_PENDING_EXPIRATION); } }
Ret: if(pChainContext) { CertFreeCertificateChain(pChainContext); }
AE_END(); return hr; }
HRESULT VerifyAutoenrolledCertificate( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCert, IN OUT PAE_CERT_TEST_ARRAY * ppAEData ) { HRESULT hr = S_OK;
hr = VerifyCommonExtensions(pInstance, pCert, ppAEData); if(FAILED(hr)) { return hr; }
hr = VerifyCertificateNaming(pInstance, pCert, ppAEData); if(FAILED(hr)) { return hr; }
hr = VerifyCertificateChaining(pInstance, pCert, ppAEData);
return hr; }
//
// Name: IsOldCertificateValid
//
// Description: This function checks if the certificate in the pOldCert
// member is valid to satisfy this auto-enrollment request,
// or if it should be renewed.
//
HRESULT IsOldCertificateValid( IN PAE_INSTANCE_INFO pInstance, OUT BOOL *pfNeedNewCert ) {
HRESULT hr = S_OK;
PAE_CERT_TEST_ARRAY pAEData = NULL;
AE_BEGIN(L"IsOldCertificateValid");
*pfNeedNewCert = TRUE;
if((pInstance == NULL) || (pInstance->pCTLContext == NULL) || (pInstance->pCTLContext->pCtlInfo == NULL)) { goto Ret; } pInstance->fRenewalOK = FALSE;
hr = VerifyAutoenrolledCertificate(pInstance, pInstance->pOldCert, &pAEData);
if(FAILED(hr)) { goto Ret; }
if(pAEData) { pInstance->fRenewalOK = pAEData->fRenewalOK; }
// Scan the verification results
if((pAEData) && (pAEData->cTests > 0)) { BOOL fFailed = FALSE; DWORD cFailureMessage= 0; DWORD iFailure; LPWSTR wszFailureMessage = NULL; LPWSTR wszCurrent; DWORD cTestArray = 0; DWORD cbTestArray = 0; DWORD * aidTestArray = NULL; DWORD iTest;
if(CertGetCertificateContextProperty(pInstance->pOldCert, AE_CERT_TEST_ARRAY_PROPID, NULL, &cbTestArray)) { aidTestArray = (DWORD *)AEAlloc(cbTestArray); CertGetCertificateContextProperty(pInstance->pOldCert, AE_CERT_TEST_ARRAY_PROPID, aidTestArray, &cbTestArray); cTestArray = cbTestArray/sizeof(aidTestArray[0]); }
// Check to see of these are ignored failures.
for(iFailure=0; iFailure < pAEData->cTests; iFailure++) {
if(FAILED(pAEData->Test[iFailure].idTest)) { // Are we ignoring this test?
for(iTest = 0; iTest < cTestArray; iTest++) { if(aidTestArray[iTest] == pAEData->Test[iFailure].idTest) break; } if(iTest != cTestArray) { if(pAEData->Test[iFailure].pwszReason) { LocalFree(pAEData->Test[iFailure].pwszReason); } pAEData->Test[iFailure].pwszReason = NULL; pAEData->Test[iFailure].idTest = S_OK;
continue; } fFailed = TRUE; } if(pAEData->Test[iFailure].pwszReason) { cFailureMessage += wcslen(pAEData->Test[iFailure].pwszReason); } } cFailureMessage += 5; if(aidTestArray) { AEFree(aidTestArray); }
if(fFailed) { wszFailureMessage = (LPWSTR)AEAlloc(cFailureMessage*sizeof(WCHAR)); if(wszFailureMessage == NULL) { hr = E_OUTOFMEMORY; goto Ret; }
wcscpy(wszFailureMessage, L"\n\r\n\r"); wszCurrent = wszFailureMessage+2;
for(iFailure=0; iFailure < pAEData->cTests; iFailure++) { if(pAEData->Test[iFailure].pwszReason) { wcscpy(wszCurrent, pAEData->Test[iFailure].pwszReason); wszCurrent += wcslen(pAEData->Test[iFailure].pwszReason); } }
LogAutoEnrollmentEvent(pInstance->fRenewalOK? EVENT_OLD_CERT_VERIFY_RENEW_WARNING: EVENT_OLD_CERT_VERIFY_REENROLL_WARNING, pInstance->pInternalInfo->hToken, pInstance->pwszCertType, wszFailureMessage, NULL); AEFree(wszFailureMessage); // Report the event here
goto Ret; } }
*pfNeedNewCert = FALSE;
Ret:
if(pAEData) {
AEFreeTestResult(&pAEData); }
if(hr != S_OK) { LogAutoEnrollmentError(hr, EVENT_UAE_VERIFICATION_FAILURE, pInstance->pInternalInfo->fMachineEnrollment, pInstance->pInternalInfo->hToken, pInstance->pwszCertType, NULL); }
AE_END();
return hr; }
//
// Name: IsOldCertificateValid
//
// Description: This function checks if the certificate in the pOldCert
// member is valid to satisfy this auto-enrollment request,
// or if it should be renewed.
//
HRESULT VerifyEnrolledCertificate( IN PAE_INSTANCE_INFO pInstance, IN PCCERT_CONTEXT pCert ) {
HRESULT hr = S_OK;
PAE_CERT_TEST_ARRAY pAEData = NULL; CRYPT_DATA_BLOB AETestArray = {0, NULL};
AE_BEGIN(L"IsOldCertificateValid"); AETestArray.cbData = 0; AETestArray.pbData = NULL;
if((pInstance == NULL) || (pInstance->pCTLContext == NULL) || (pInstance->pCTLContext->pCtlInfo == NULL)) { goto Ret; } pInstance->fRenewalOK = FALSE;
hr = VerifyAutoenrolledCertificate(pInstance, pCert, &pAEData);
if(FAILED(hr)) { goto Ret; }
// Scan the verification results
if((pAEData) && (pAEData->cTests > 0)) { BOOL fFailed = FALSE; DWORD cFailureMessage= 0; DWORD iFailure; LPWSTR wszFailureMessage = NULL; LPWSTR wszCurrent; DWORD cTestArray = 0; DWORD iTestArray = 0;
cTestArray = pAEData->cTests; AETestArray.cbData = cTestArray*sizeof(DWORD);
if(AETestArray.cbData) { AETestArray.pbData = AEAlloc(AETestArray.cbData); } else { AETestArray.pbData = NULL; }
// Check to see of these are ignored failures.
for(iFailure=0; iFailure < pAEData->cTests; iFailure++) {
if(FAILED(pAEData->Test[iFailure].idTest)) { fFailed = TRUE; if(AETestArray.pbData) { ((DWORD *)AETestArray.pbData)[iTestArray++] = pAEData->Test[iFailure].idTest; } } if(pAEData->Test[iFailure].pwszReason) { cFailureMessage += wcslen(pAEData->Test[iFailure].pwszReason); } } cFailureMessage += 5;
wszFailureMessage = (LPWSTR)AEAlloc(cFailureMessage*sizeof(WCHAR)); if(wszFailureMessage == NULL) { hr = E_OUTOFMEMORY; goto Ret; }
wcscpy(wszFailureMessage, L"\n\r\n\r"); wszCurrent = wszFailureMessage+2;
for(iFailure=0; iFailure < pAEData->cTests; iFailure++) { if(pAEData->Test[iFailure].pwszReason) { wcscpy(wszCurrent, pAEData->Test[iFailure].pwszReason); wszCurrent += wcslen(pAEData->Test[iFailure].pwszReason); } }
LogAutoEnrollmentEvent(EVENT_ENROLLED_CERT_VERIFY_WARNING, pInstance->pInternalInfo->hToken, pInstance->pwszCertType, wszFailureMessage, NULL); AEFree(wszFailureMessage);
// Report the event here
}
CertSetCertificateContextProperty(pCert, AE_CERT_TEST_ARRAY_PROPID, 0, AETestArray.pbData?&AETestArray:NULL);
Ret:
if(pAEData) {
AEFreeTestResult(&pAEData); }
if(hr != S_OK) { LogAutoEnrollmentError(hr, EVENT_UAE_VERIFICATION_FAILURE, pInstance->pInternalInfo->fMachineEnrollment, pInstance->pInternalInfo->hToken, pInstance->pwszCertType, NULL); }
AE_END(); return hr; }
//
// Name: FindExistingEnrolledCertificate
//
// Description: This function searches for an existing certificate
// enrolled with this auto-enrollment object.
//
BOOL FindExistingEnrolledCertificate(IN PAE_INSTANCE_INFO pInstance, OUT PCCERT_CONTEXT *ppCert) { PCCERT_CONTEXT pCertContext = NULL; PCCERT_CONTEXT pPrevContext = NULL; DWORD i; BOOL fRet = FALSE;
DWORD dwEnrollPropId = CERT_AUTO_ENROLL_PROP_ID;
LPWSTR wszEnrollmentId = NULL; DWORD cbEnrollmentId = 0; DWORD cbCurrentId=0;
AE_BEGIN(L"FindExistingEnrolledCertificate");
if(pInstance->pwszCertType == NULL) { return FALSE; }
cbEnrollmentId = sizeof(WCHAR) * (wcslen(pInstance->pwszAEIdentifier) + 1); wszEnrollmentId = (WCHAR *)AEAlloc(cbEnrollmentId); if(wszEnrollmentId == NULL) { return FALSE; }
if(*ppCert) { CertFreeCertificateContext(*ppCert); } *ppCert = NULL;
// check if cert from CA is in the MY store
while(pCertContext = CertFindCertificateInStore( pInstance->pInternalInfo->hMYStore, X509_ASN_ENCODING, 0, CERT_FIND_PROPERTY, &dwEnrollPropId, pPrevContext)) {
pPrevContext = pCertContext;
// check if this is an auto enroll cert and
// if the cert type is correct
cbCurrentId = cbEnrollmentId; if(!CertGetCertificateContextProperty(pCertContext, CERT_AUTO_ENROLL_PROP_ID, wszEnrollmentId, &cbCurrentId)) { continue; }
if(wcscmp(wszEnrollmentId, pInstance->pwszAEIdentifier) != 0) { continue; } AE_DEBUG((AE_INFO, L"Found auto-enrolled certificate for %ls cert\n\r", pInstance->pwszCertType));
*ppCert = pCertContext; pCertContext = NULL; break;
} fRet = TRUE;
if (pCertContext) CertFreeCertificateContext(pCertContext); if(wszEnrollmentId) { AEFree(wszEnrollmentId); } AE_END(); return fRet; }
//
// Name:
//
// Description: This function calls a function to determine if an auto
// enrollment is to occur and if so it trys to enroll with
// different CAs until either an enrollment is successful or
// the list of CAs is exhausted.
//
void EnrollmentWithCTL( IN PAE_INSTANCE_INFO pInstance, IN LDAP * pld ) { INTERNAL_INFO pInternalInfo; BOOL fNeedToEnroll = FALSE; AUTO_ENROLL_INFO EnrollmentInfo; DWORD *pdwCAEntries = NULL; DWORD i; BOOL fPermitted; BOOL fAnyAcceptableCAs = FALSE; DWORD dwFailureCode = E_FAIL;
AE_BEGIN(L"EnrollmentWithCTL");
if(!GetCertTypeInfo(pInstance, pld, &fPermitted)) { goto Ret; } if(!fPermitted) { AE_DEBUG((AE_INFO, L"Not permitted to enroll for %ls cert type\n", pInstance->pwszCertType));
goto Ret; }
if(!FindExistingEnrolledCertificate(pInstance, &pInstance->pOldCert)) { goto Ret; }
if(pInstance->pOldCert) { if(FAILED(IsOldCertificateValid(pInstance, &fNeedToEnroll))) { goto Ret; } if(!fNeedToEnroll) { goto Ret; } if(!pInstance->fRenewalOK) { CRYPT_DATA_BLOB Archived; Archived.cbData = 0; Archived.pbData = NULL;
// We force an archive on the old cert and close it.
CertSetCertificateContextProperty(pInstance->pOldCert, CERT_ARCHIVED_PROP_ID, 0, &Archived); } }
// It looks like we need to enroll
// for a cert.
do { // Loop through all avaialble ca's to find one
// that supports this cert type, and is in our CTL
for (i=0;i<pInstance->pInternalInfo->ccaList;i++) { DWORD dwIndex; LPWSTR *pwszCertType; DWORD iCTL; dwIndex = (i + pInstance->dwRandomIndex) % pInstance->pInternalInfo->ccaList; AE_DEBUG((AE_TRACE, L"Trying CA %ws\\%ws\n\r", pInstance->pInternalInfo->acaList[dwIndex].wszDNSName, pInstance->pInternalInfo->acaList[dwIndex].wszName));
// Does this CA support our cert type.
pwszCertType = pInstance->pInternalInfo->acaList[dwIndex].awszCertificateTemplates; if(pwszCertType == NULL) { AE_DEBUG((AE_TRACE, L"There are no cert types supported on this CA\n\r")); continue; } while(*pwszCertType) { if(wcscmp(*pwszCertType, pInstance->pwszCertType) == 0) { break; } pwszCertType++; } if(*pwszCertType == NULL) { AE_DEBUG((AE_TRACE, L"The cert type %ws is not supported on this CA\n\r", pInstance->pwszCertType)); continue; }
// Is this CA in our CTL List
if(pInstance->pCTLContext->pCtlInfo->cCTLEntry > 0) { for(iCTL = 0; iCTL < pInstance->pCTLContext->pCtlInfo->cCTLEntry; iCTL++) { PCTL_ENTRY pEntry= &pInstance->pCTLContext->pCtlInfo->rgCTLEntry[iCTL];
if(pEntry->SubjectIdentifier.cbData != sizeof(pInstance->pInternalInfo->acaList[dwIndex].CACertHash)) { continue; }
if(memcmp(pEntry->SubjectIdentifier.pbData, pInstance->pInternalInfo->acaList[dwIndex].CACertHash, pEntry->SubjectIdentifier.cbData) == 0) { break; } } if(iCTL == pInstance->pCTLContext->pCtlInfo->cCTLEntry) { AE_DEBUG((AE_TRACE, L"The CA is not supported by the auto-enrollment object\n\r")); continue; } }
ZeroMemory(&EnrollmentInfo, sizeof(EnrollmentInfo));
// Yes, we may enroll at this CA!
fAnyAcceptableCAs = TRUE;
EnrollmentInfo.pwszCAMachine = pInstance->pInternalInfo->acaList[dwIndex].wszDNSName; EnrollmentInfo.pwszCAAuthority = pInstance->pInternalInfo->acaList[dwIndex].wszName; EnrollmentInfo.pszAutoEnrollProvider = DEFAULT_AUTO_ENROLL_PROV; EnrollmentInfo.fRenewal = pInstance->fRenewalOK && (pInstance->pOldCert != NULL);
if(!SetEnrollmentCertType(pInstance, &EnrollmentInfo)) { AE_DEBUG((AE_TRACE, L"SetEnrollmentCertType failed\n\r")); continue; }
// load the provider and call the entry point
if (LoadAndCallEnrollmentProvider(pInstance->pInternalInfo->fMachineEnrollment, &EnrollmentInfo)) { PCCERT_CONTEXT pNewCert = NULL; // Succeeded,
// verify the cert that was retrieved
if(!FindExistingEnrolledCertificate(pInstance, &pNewCert)) { continue; } VerifyEnrolledCertificate(pInstance, pNewCert);
CertFreeCertificateContext(pNewCert);
break; } }
if(i == pInstance->pInternalInfo->ccaList) { //
// If we have a preexisting cert, then we may need to try twice, first
// to renew, and then to re-enroll
if(pInstance->pOldCert) {
// Try again, but re-enrolling this time
CRYPT_DATA_BLOB Archived; Archived.cbData = 0; Archived.pbData = NULL;
// We force an archive on the old cert and close it.
CertSetCertificateContextProperty(pInstance->pOldCert, CERT_ARCHIVED_PROP_ID, 0, &Archived); CertFreeCertificateContext(pInstance->pOldCert); pInstance->pOldCert = NULL; pInstance->fRenewalOK = FALSE; continue; }
AE_DEBUG((AE_WARNING, L"Auto-enrollment not performed\n\r")); break; // failed
} else { break; }
} while (TRUE); Ret: AE_END(); return; }
#define SHA1_HASH_LENGTH 20
PCCERT_CONTEXT FindCertificateInOtherStore( IN HCERTSTORE hOtherStore, IN PCCERT_CONTEXT pCert ) { BYTE rgbHash[SHA1_HASH_LENGTH]; CRYPT_DATA_BLOB HashBlob;
HashBlob.pbData = rgbHash; HashBlob.cbData = SHA1_HASH_LENGTH; if (!CertGetCertificateContextProperty( pCert, CERT_SHA1_HASH_PROP_ID, rgbHash, &HashBlob.cbData ) || SHA1_HASH_LENGTH != HashBlob.cbData) return NULL;
return CertFindCertificateInStore( hOtherStore, 0, // dwCertEncodingType
0, // dwFindFlags
CERT_FIND_SHA1_HASH, (const void *) &HashBlob, NULL //pPrevCertContext
); }
//
// Name: UpdateEnterpriseRoots
//
// Description: This function enumerates all of the roots in the DS based
// enterprise root store, and moves them into the local machine root store.
//
HRESULT WINAPI UpdateEnterpriseRoots(LDAP *pld) {
HRESULT hr = S_OK; LPWSTR wszLdapRootStore = NULL; LPWSTR wszConfig = NULL; HCERTSTORE hEnterpriseRoots = NULL, hRootStore = NULL; PCCERT_CONTEXT pContext = NULL, pOtherCert = NULL;
static LPWSTR s_wszEnterpriseRoots = L"ldap:///CN=Certification Authorities,CN=Public Key Services,CN=Services,%ws?cACertificate?one?objectCategory=certificationAuthority";
hr = myGetConfigDN(pld, &wszConfig); if(hr != S_OK) { goto error; }
wszLdapRootStore = (LPWSTR)LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*(wcslen(wszConfig)+wcslen(s_wszEnterpriseRoots))); if(wszLdapRootStore == NULL) { hr = E_OUTOFMEMORY; goto error; }
wsprintf(wszLdapRootStore, s_wszEnterpriseRoots, wszConfig);
hRootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"ROOT"); if(hRootStore == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable to open ROOT store (%lx)\n\r", hr)); goto error; }
hEnterpriseRoots = CertOpenStore(CERT_STORE_PROV_LDAP, 0, 0, CERT_STORE_READONLY_FLAG, wszLdapRootStore); if(hEnterpriseRoots == NULL) { DWORD err = GetLastError();
// Checking against NO_SUCH_OBJECT
// is a workaround for the fact that
// the ldap provider returns LDAP errors
if((err == LDAP_NO_SUCH_OBJECT) || (err == ERROR_FILE_NOT_FOUND)) { // There was no store, so there are no certs
hr = S_OK; goto error; }
hr = HRESULT_FROM_WIN32(err);
AE_DEBUG((AE_ERROR, L"Unable to open ROOT store (%lx)\n\r", hr)); goto error; }
while(pContext = CertEnumCertificatesInStore(hEnterpriseRoots, pContext)) { if (pOtherCert = FindCertificateInOtherStore(hRootStore, pContext)) { CertFreeCertificateContext(pOtherCert); } else { CertAddCertificateContextToStore(hRootStore, pContext, CERT_STORE_ADD_ALWAYS, NULL); } }
while(pContext = CertEnumCertificatesInStore(hRootStore, pContext)) { if (pOtherCert = FindCertificateInOtherStore(hEnterpriseRoots, pContext)) { CertFreeCertificateContext(pOtherCert); } else { CertDeleteCertificateFromStore(CertDuplicateCertificateContext(pContext)); } }
error:
if(hr != S_OK) { LogAutoEnrollmentError(hr, EVENT_UPDATE_ENTERPRISE_ROOT_FAILURE, TRUE, NULL, NULL, NULL);
}
if(wszLdapRootStore) { LocalFree(wszLdapRootStore); }
if(wszConfig) { LocalFree(wszConfig); } if(hEnterpriseRoots) { CertCloseStore(hEnterpriseRoots,0); } if(hRootStore) { CertCloseStore(hRootStore,0); }
return hr; }
//
// Name: UpdateNTAuthTrust
//
// Description: This function enumerates all of the roots in the DS based
// NTAuth store, and moves them into the local machine NTAuth.
//
HRESULT WINAPI UpdateNTAuthTrust(LDAP *pld) {
HRESULT hr = S_OK; LPWSTR wszNTAuth = NULL; LPWSTR wszConfig = NULL; HCERTSTORE hDSAuthRoots = NULL, hAuthStore = NULL; PCCERT_CONTEXT pContext = NULL, pOtherCert = NULL;
static LPWSTR s_wszNTAuthRoots = L"ldap:///CN=Public Key Services,CN=Services,%ws?cACertificate?one?cn=NTAuthCertificates";
hr = myGetConfigDN(pld, &wszConfig); if(hr != S_OK) { goto error; }
wszNTAuth = (LPWSTR)LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*(wcslen(wszConfig)+wcslen(s_wszNTAuthRoots))); if(wszNTAuth == NULL) { hr = E_OUTOFMEMORY; goto error; }
wsprintf(wszNTAuth, s_wszNTAuthRoots, wszConfig);
hAuthStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"NTAuth"); if(hAuthStore == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); AE_DEBUG((AE_ERROR, L"Unable to open NTAuth store (%lx)\n\r", hr)); goto error; }
hDSAuthRoots = CertOpenStore(CERT_STORE_PROV_LDAP, 0, 0, CERT_STORE_READONLY_FLAG, wszNTAuth); if(hDSAuthRoots == NULL) { DWORD err = GetLastError(); // Checking against NO_SUCH_OBJECT
// is a workaround for the fact that
// the ldap provider returns LDAP errors
if((err == LDAP_NO_SUCH_OBJECT) || (err == ERROR_FILE_NOT_FOUND)) { // There was no store, so there are no certs
hr = S_OK; goto error; } hr = HRESULT_FROM_WIN32(err); AE_DEBUG((AE_ERROR, L"Unable to open ROOT store (%lx)\n\r", hr)); goto error; }
while(pContext = CertEnumCertificatesInStore(hDSAuthRoots, pContext)) { if (pOtherCert = FindCertificateInOtherStore(hAuthStore, pContext)) { CertFreeCertificateContext(pOtherCert); } else { CertAddCertificateContextToStore(hAuthStore, pContext, CERT_STORE_ADD_ALWAYS, NULL); } }
while(pContext = CertEnumCertificatesInStore(hAuthStore, pContext)) { if (pOtherCert = FindCertificateInOtherStore(hDSAuthRoots, pContext)) { CertFreeCertificateContext(pOtherCert); } else { CertDeleteCertificateFromStore(CertDuplicateCertificateContext(pContext)); } }
error: if(hr != S_OK) { LogAutoEnrollmentError(hr, EVENT_UPDATE_NTAUTH_FAILURE, TRUE, NULL, NULL, NULL);
} if(wszNTAuth) { LocalFree(wszNTAuth); }
if(wszConfig) { LocalFree(wszConfig); } if(hDSAuthRoots) { CertCloseStore(hDSAuthRoots,0); } if(hAuthStore) { CertCloseStore(hAuthStore,0); }
return hr; }
//
// Name: ProcessAutoEnrollment
//
// Description: This function retrieves the appropriate auto enrollment
// objects (CTLs) and then calls a function to proceed with
// auto enrollment for each of the objects.
//
DWORD WINAPI ProcessAutoEnrollment( BOOL fMachineEnrollment, HANDLE hToken ) { DWORD i; DWORD dwOpenStoreFlags = CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG; HCERTSTORE hACRSStore = 0; PCCTL_CONTEXT pCTLContext = NULL; PCCTL_CONTEXT pPrevCTLContext = NULL; CTL_FIND_USAGE_PARA CTLFindUsage; LPSTR pszCTLUsageOID; CERT_PHYSICAL_STORE_INFO PhysicalStoreInfo; DWORD dwRet = 0; INTERNAL_INFO InternalInfo; BOOL fInitialized = FALSE; LDAP *pld = NULL;
__try { AE_DEBUG((AE_TRACE, L"ProcessAutoEnrollment:%ls\n\r", fMachineEnrollment?L"Machine":L"User")); memset(&InternalInfo, 0, sizeof(InternalInfo)); memset(&PhysicalStoreInfo, 0, sizeof(PhysicalStoreInfo)); memset(&CTLFindUsage, 0, sizeof(CTLFindUsage)); CTLFindUsage.cbSize = sizeof(CTLFindUsage);
// since this is a user we need to impersonate the user
if (!fMachineEnrollment) { if (hToken) { if (!ImpersonateLoggedOnUser(hToken)) { dwRet = GetLastError(); AE_DEBUG((AE_ERROR, L"Could not impersonate user: (%lx)\n\r", dwRet)); LogAutoEnrollmentError(HRESULT_FROM_WIN32(dwRet), EVENT_AE_SECURITY_INIT_FAILED, fMachineEnrollment, hToken, NULL, NULL);
goto Ret; } } }
if(fMachineEnrollment) { dwRet = aeRobustLdapBind(&pld, FALSE); if(dwRet != S_OK) { goto Ret; }
UpdateEnterpriseRoots(pld); UpdateNTAuthTrust(pld); }
// if the auto enrollment is for a user then we need to shut off inheritance
// from the local machine store so that we don't try and enroll for certs
// which are meant to be for the machine
if (!fMachineEnrollment) { dwOpenStoreFlags = CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_READONLY_FLAG;
PhysicalStoreInfo.cbSize = sizeof(PhysicalStoreInfo); PhysicalStoreInfo.dwFlags = CERT_PHYSICAL_STORE_OPEN_DISABLE_FLAG;
if (!CertRegisterPhysicalStore(L"ACRS", CERT_SYSTEM_STORE_CURRENT_USER, CERT_PHYSICAL_STORE_LOCAL_MACHINE_NAME, &PhysicalStoreInfo, NULL)) { dwRet = GetLastError(); AE_DEBUG((AE_ERROR, L"Could not register ACRS store: (%lx)\n\r", dwRet )); LogAutoEnrollmentError(HRESULT_FROM_WIN32(dwRet), EVENT_AE_LOCAL_CYCLE_INIT_FAILED, fMachineEnrollment, hToken, NULL, NULL); goto Ret; } }
// open the ACRS store and fine the CTL based on the auto enrollment usage
if (0 == (hACRSStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, dwOpenStoreFlags, L"ACRS"))) { dwRet = GetLastError(); AE_DEBUG((AE_ERROR, L"Could not open ACRS store: (%lx)\n\r", dwRet )); LogAutoEnrollmentError(HRESULT_FROM_WIN32(dwRet), EVENT_AE_LOCAL_CYCLE_INIT_FAILED, fMachineEnrollment, hToken, NULL, NULL); goto Ret; }
// look for the Auto Enrollment usage
CTLFindUsage.SubjectUsage.cUsageIdentifier = 1; pszCTLUsageOID = szOID_AUTO_ENROLL_CTL_USAGE; CTLFindUsage.SubjectUsage.rgpszUsageIdentifier = &pszCTLUsageOID; for (i=0;;i++) { AE_INSTANCE_INFO Instance;
memset(&Instance, 0, sizeof(Instance));
if (NULL == (pCTLContext = CertFindCTLInStore(hACRSStore, X509_ASN_ENCODING, CTL_FIND_SAME_USAGE_FLAG, CTL_FIND_USAGE, &CTLFindUsage, pPrevCTLContext))) { // Freed by CertFindCTLInStore.
pPrevCTLContext = NULL; break; } pPrevCTLContext = pCTLContext;
if(!fInitialized) { if(pld == NULL) { dwRet = aeRobustLdapBind(&pld, FALSE); if(dwRet != S_OK) { goto Ret; } } // Initialize the internal information needed for an auto enrollment
if (S_OK != (dwRet = InitInternalInfo(pld, fMachineEnrollment, hToken, &InternalInfo))) { break; } if(InternalInfo.ccaList == 0) { // No CA's
break; } fInitialized = TRUE; }
if(!InitInstance(pPrevCTLContext, &InternalInfo, &Instance)) { dwRet = E_FAIL; break; } // UNDONE - perform a WinVerifyTrust check on this auto
// enrollment object (CTL) to make sure it is trusted
// have a CTL so do the Enrollment
EnrollmentWithCTL(&Instance, pld);
FreeInstance(&Instance); } } __except ( dwRet = GetExceptionError(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER ) { AE_DEBUG((AE_ERROR, L"Exception Caught (%lx)\n\r", dwRet )); goto Ret; } Ret: __try {
FreeInternalInfo(&InternalInfo);
if(pPrevCTLContext) { CertFreeCTLContext(pPrevCTLContext); } if (hACRSStore) CertCloseStore(hACRSStore, 0); if (hToken) { if (!fMachineEnrollment) { RevertToSelf(); } } if(pld != NULL) { g_pfnldap_unbind(pld); } AE_END(); } __except ( dwRet = GetExceptionError(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER ) {
return dwRet; } return dwRet; }
//*************************************************************
//
// AutoEnrollmentThread()
//
// Purpose: Background thread for AutoEnrollment.
//
// Parameters: pInfo - AutoEnrollment info
//
// Return: 0
//
//*************************************************************
VOID AutoEnrollmentThread (PVOID pVoid, BOOLEAN fTimeout) { HINSTANCE hInst; HKEY hKey; HKEY hCurrent ; DWORD dwType, dwSize, dwResult; LONG lTimeout; LARGE_INTEGER DueTime; PAUTO_ENROLL_THREAD_INFO pInfo = pVoid; DWORD dwWaitResult; // This is executed in a worker thread, so we need to be safe.
AE_BEGIN(L"AutoEnrollmentThread");
if(fTimeout) { AE_END(); return ; }
dwWaitResult = WaitForSingleObject(pInfo->fMachineEnrollment?g_hMachineMutex:g_hUserMutex, 0);
if((dwWaitResult == WAIT_FAILED) || (dwWaitResult == WAIT_TIMEOUT)) { AE_DEBUG((AE_ERROR, L"Mutex Contention\n\r" )); AE_END(); return; }
__try {
// Process the auto-enrollment.
ProcessAutoEnrollment( pInfo->fMachineEnrollment, pInfo->hToken );
//
// Build a timer event to ping us
// in about 8 hours if we don't get
// notified.
lTimeout = AE_DEFAULT_REFRESH_RATE;
//
// Query for the refresh timer value
//
hCurrent = HKEY_LOCAL_MACHINE ;
if (pInfo->fMachineEnrollment || NT_SUCCESS( RtlOpenCurrentUser( KEY_READ, &hCurrent ) ) ) {
if (RegOpenKeyEx (hCurrent, SYSTEM_POLICIES_KEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
dwSize = sizeof(lTimeout); RegQueryValueEx (hKey, TEXT("AutoEnrollmentRefreshTime"), NULL, &dwType, (LPBYTE) &lTimeout, &dwSize);
RegCloseKey (hKey); }
if (!pInfo->fMachineEnrollment) { RegCloseKey( hCurrent ); } }
//
// Limit the timeout to once every 1080 hours (45 days)
//
if (lTimeout >= 1080) { lTimeout = 1080; }
if (lTimeout < 0) { lTimeout = 0; }
//
// Convert hours to milliseconds
//
lTimeout = lTimeout * 60 * 60 * 1000;
//
// Special case 0 milliseconds to be 7 seconds
//
if (lTimeout == 0) { lTimeout = 7000; }
DueTime.QuadPart = Int32x32To64(-10000, lTimeout);
if(!SetWaitableTimer (pInfo->hTimer, &DueTime, 0, NULL, 0, FALSE)) { AE_DEBUG((AE_WARNING, L"Could not reset timer (%lx)\n\r", GetLastError())); } } __except ( EXCEPTION_EXECUTE_HANDLER ) { } ReleaseMutex(pInfo->fMachineEnrollment?g_hMachineMutex:g_hUserMutex); AE_END(); return ; }
//*************************************************************
//
// AutoEnrollmentWorker()
//
// Purpose: Called by the worker thread mechanism to launch an auto-enrollment thread.
//
// Parameters: pInfo - AutoEnrollment info
//
// Return: 0
//
//*************************************************************
/*VOID AutoEnrollmentWorker (PVOID pVoid, BOOLEAN fTimeout)
{ if(fTimeout) { return ; }
}*/
//+---------------------------------------------------------------------------
//
// Function: StartAutoEnrollThread
//
// Synopsis: Starts a thread which causes Autoenrollment
//
// Arguments:
// fMachineEnrollment - indicates if enrolling for a machine
//
// History: 01-11-98 jeffspel Created
//
// Notes:
//
//----------------------------------------------------------------------------
HANDLE RegisterAutoEnrollmentProcessing( IN BOOL fMachineEnrollment, IN HANDLE hToken ) { DWORD dwThreadId; HANDLE hWait = 0; PAUTO_ENROLL_THREAD_INFO pThreadInfo = NULL; TCHAR szEventName[60]; LARGE_INTEGER DueTime; HKEY hKeySafeBoot = NULL; DWORD dwStatus = ERROR_SUCCESS; SECURITY_ATTRIBUTES sa = {0,NULL, FALSE}; AE_DEBUG((AE_TRACE, L"RegisterAutoEnrollmentProcessing:%ls\n\r",fMachineEnrollment?L"Machine":L"User")); __try {
//
// We don't do autoenrollment in safe boot
//
// copied from the service controller code
dwStatus = RegOpenKey(HKEY_LOCAL_MACHINE, L"system\\currentcontrolset\\control\\safeboot\\option", &hKeySafeBoot);
if (dwStatus == ERROR_SUCCESS) {
DWORD dwSafeBoot = 0; DWORD cbSafeBoot = sizeof(dwSafeBoot); //
// we did in fact boot under safeboot control
//
dwStatus = RegQueryValueEx(hKeySafeBoot, L"OptionValue", NULL, NULL, (LPBYTE)&dwSafeBoot, &cbSafeBoot);
if (dwStatus != ERROR_SUCCESS) { dwSafeBoot = 0; }
RegCloseKey(hKeySafeBoot);
if(dwSafeBoot) { goto error; } }
if((g_hInstSecur32 == NULL) || (g_hInstWldap32 == NULL)) { goto error; }
if (NULL == (pThreadInfo = AEAlloc(sizeof(AUTO_ENROLL_THREAD_INFO)))) { goto error; }
ZeroMemory(pThreadInfo, sizeof(AUTO_ENROLL_THREAD_INFO));
pThreadInfo->fMachineEnrollment = fMachineEnrollment;
// if this is a user auto enrollment then duplicate the thread token
if (!pThreadInfo->fMachineEnrollment) { if (!DuplicateToken(hToken, SecurityImpersonation, &pThreadInfo->hToken)) { AE_DEBUG((AE_ERROR, L"Could not acquire user token: (%lx)\n\r", GetLastError())); goto error; }
}
sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; sa.lpSecurityDescriptor = AEMakeGenericSecurityDesc();
pThreadInfo->hNotifyEvent = CreateEvent(&sa, FALSE, FALSE, fMachineEnrollment? MACHINE_AUTOENROLLMENT_TRIGGER_EVENT: USER_AUTOENROLLMENT_TRIGGER_EVENT);
if(sa.lpSecurityDescriptor) { LocalFree(sa.lpSecurityDescriptor); }
if(pThreadInfo->hNotifyEvent == NULL) { AE_DEBUG((AE_ERROR, L"Could not create GPO Notification Event: (%lx)\n\r", GetLastError())); goto error; } if(!RegisterGPNotification(pThreadInfo->hNotifyEvent, pThreadInfo->fMachineEnrollment)) { AE_DEBUG((AE_ERROR, L"Could not register for GPO Notification: (%lx)\n\r", GetLastError())); goto error;
}
if(pThreadInfo->fMachineEnrollment) { wsprintf (szEventName, TEXT("AUTOENRL: machine refresh timer for %d:%d"), GetCurrentProcessId(), GetCurrentThreadId()); } else { wsprintf (szEventName, TEXT("AUTOENRL: user refresh timer for %d:%d"), GetCurrentProcessId(), GetCurrentThreadId()); } pThreadInfo->hTimer = CreateWaitableTimer (NULL, FALSE, szEventName);
if(pThreadInfo->hTimer == NULL) { goto error; }
if (! RegisterWaitForSingleObject(&pThreadInfo->hNotifyWait, pThreadInfo->hNotifyEvent, AutoEnrollmentThread, (PVOID)pThreadInfo, INFINITE, 0)) { AE_DEBUG((AE_ERROR, L"RegisterWait failed: (%lx)\n\r", GetLastError() )); goto error; }
if (! RegisterWaitForSingleObject(&pThreadInfo->hTimerWait, pThreadInfo->hTimer, AutoEnrollmentThread, (void*)pThreadInfo, INFINITE, 0)) { AE_DEBUG((AE_ERROR, L"RegisterWait failed: (%lx)\n\r", GetLastError())); goto error; }
// Seed the timer with about 5 minutes, so we can come back
// and run an auto-enroll later without blocking this thread.
DueTime.QuadPart = Int32x32To64(-10000, (fMachineEnrollment?MACHINE_AUTOENROLL_INITIAL_DELAY: USER_AUTOENROLL_INITIAL_DELAY) * 1000); if(!SetWaitableTimer (pThreadInfo->hTimer, &DueTime, 0, NULL, 0, FALSE)) { AE_DEBUG((AE_WARNING, L"Could not reset timer (%lx)\n\r", GetLastError())); } } __except ( EXCEPTION_EXECUTE_HANDLER ) { goto error; } AE_RETURN(pThreadInfo);
error:
if(pThreadInfo) { if(pThreadInfo->hTimerWait) { UnregisterWaitEx(pThreadInfo->hTimerWait, INVALID_HANDLE_VALUE ); } if(pThreadInfo->hTimer) { CloseHandle(pThreadInfo->hTimer); } if(pThreadInfo->hNotifyWait) { UnregisterWaitEx(pThreadInfo->hNotifyWait, INVALID_HANDLE_VALUE); } if(pThreadInfo->hNotifyEvent) { CloseHandle(pThreadInfo->hNotifyEvent); } if(pThreadInfo->hToken) { CloseHandle(pThreadInfo->hToken); }
AEFree(pThreadInfo); } AE_RETURN(NULL); }
BOOL DeRegisterAutoEnrollment(HANDLE hAuto) { PAUTO_ENROLL_THREAD_INFO pThreadInfo = (PAUTO_ENROLL_THREAD_INFO)hAuto;
if(pThreadInfo == NULL) { return FALSE; } if(pThreadInfo->hTimerWait) { UnregisterWaitEx(pThreadInfo->hTimerWait, INVALID_HANDLE_VALUE); } if(pThreadInfo->hTimer) { CloseHandle(pThreadInfo->hTimer); }
if(pThreadInfo->hNotifyWait) { UnregisterWaitEx(pThreadInfo->hNotifyWait, INVALID_HANDLE_VALUE ); }
if(pThreadInfo->hNotifyEvent) { UnregisterGPNotification(pThreadInfo->hNotifyEvent); CloseHandle(pThreadInfo->hNotifyEvent); } if(pThreadInfo->hToken) { CloseHandle(pThreadInfo->hToken); }
AEFree(pThreadInfo); return TRUE;
}
VOID InitializeAutoEnrollmentSupport (VOID) {
// NOTE, auto-enrollment is registered by
// the GPO download startup code.
AE_BEGIN(L"InitializeAutoEnrollmentSupport");
g_hUserMutex = CreateMutex(NULL, FALSE, NULL); if(g_hUserMutex) { g_hMachineMutex = CreateMutex(NULL, FALSE, NULL); } if((g_hUserMutex == NULL) || (g_hMachineMutex == NULL)) { AE_DEBUG((AE_ERROR, L"Could not create enrollment mutex (%lx)\n\r", GetLastError())); AE_END();
return; }
//
// Load some functions we need
//
g_hInstWldap32 = LoadLibrary (TEXT("wldap32.dll"));
if (!g_hInstWldap32) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_pfnldap_init = (PFNLDAP_INIT) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_initW"); #else
"ldap_initA"); #endif
if (!g_pfnldap_init) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL);
goto Exit; }
g_pfnldap_bind_s = (PFNLDAP_BIND_S) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_bind_sW"); #else
"ldap_bind_sA"); #endif
if (!g_pfnldap_bind_s) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL);
goto Exit; }
g_pfnldap_set_option= (PFNLDAP_SET_OPTION) GetProcAddress (g_hInstWldap32, "ldap_set_option");
if (!g_pfnldap_set_option) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL);
goto Exit; }
g_pfnldap_search_ext_s = (PFNLDAP_SEARCH_EXT_S) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_search_ext_sW"); #else
"ldap_search_ext_sA"); #endif
if (!g_pfnldap_search_ext_s) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; } g_pfnldap_explode_dn = (PFNLDAP_EXPLODE_DN) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_explode_dnW"); #else
"ldap_explode_dnA"); #endif
if (!g_pfnldap_explode_dn) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; } g_pfnldap_first_entry = (PFNLDAP_FIRST_ENTRY) GetProcAddress (g_hInstWldap32, "ldap_first_entry");
if (!g_pfnldap_first_entry) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; } g_pfnldap_get_values = (PFNLDAP_GET_VALUES) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_get_valuesW"); #else
"ldap_get_valuesA"); #endif
if (!g_pfnldap_get_values) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL);
goto Exit; }
g_pfnldap_value_free = (PFNLDAP_VALUE_FREE) GetProcAddress (g_hInstWldap32, #ifdef UNICODE
"ldap_value_freeW"); #else
"ldap_value_freeA"); #endif
if (!g_pfnldap_value_free) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_pfnldap_msgfree = (PFNLDAP_MSGFREE) GetProcAddress (g_hInstWldap32, "ldap_msgfree");
if (!g_pfnldap_msgfree) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_pfnldap_unbind = (PFNLDAP_UNBIND) GetProcAddress (g_hInstWldap32, "ldap_unbind");
if (!g_pfnldap_unbind) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_pfnLdapGetLastError = (PFNLDAPGETLASTERROR) GetProcAddress (g_hInstWldap32, "LdapGetLastError");
if (!g_pfnLdapGetLastError) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_pfnLdapMapErrorToWin32 = (PFNLDAPMAPERRORTOWIN32) GetProcAddress (g_hInstWldap32, "LdapMapErrorToWin32");
if (!g_pfnLdapMapErrorToWin32) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
g_hInstSecur32 = LoadLibrary (TEXT("secur32.dll"));
if (!g_hInstSecur32) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL);
goto Exit; }
g_pfnGetUserNameEx = (PFNGETUSERNAMEEX)GetProcAddress (g_hInstSecur32, #ifdef UNICODE
"GetUserNameExW"); #else
"GetUserNameExA"); #endif
if (!g_pfnGetUserNameEx) { LogAutoEnrollmentError(GetLastError(), EVENT_AE_INITIALIZATION_FAILED, TRUE, NULL, NULL, NULL); goto Exit; }
AE_END(); return; Exit:
if(g_hInstSecur32) { FreeLibrary(g_hInstSecur32); g_hInstSecur32 = NULL;
} if(g_hInstWldap32) { FreeLibrary(g_hInstWldap32); g_hInstWldap32 = NULL; }
AE_END();
return;
}
#if DBG
void AEDebugLog(long Mask, LPCWSTR Format, ...) { va_list ArgList; int Level = 0; int PrefixSize = 0; int iOut; WCHAR wszOutString[MAX_DEBUG_BUFFER]; long OriginalMask = Mask;
if (Mask & g_AutoenrollDebugLevel) {
// Make the prefix first: "Process.Thread> GINA-XXX"
iOut = wsprintfW( wszOutString, L"%3d.%3d> AUTOENRL: ", GetCurrentProcessId(), GetCurrentThreadId());
va_start(ArgList, Format);
if (wvsprintf(&wszOutString[iOut], Format, ArgList) < 0) { static WCHAR wszOverFlow[] = L"\n<256 byte OVERFLOW!>\n";
// Less than zero indicates that the string would not fit into the
// buffer. Output a special message indicating overflow.
wcscpy( &wszOutString[(sizeof(wszOutString) - sizeof(wszOverFlow))/sizeof(WCHAR)], wszOverFlow); } va_end(ArgList); OutputDebugStringW(wszOutString); } } #endif
HRESULT myGetConfigDN( IN LDAP *pld, OUT LPWSTR *pwszConfigDn )
{
HRESULT hr; ULONG LdapError;
LDAPMessage *SearchResult = NULL; LDAPMessage *Entry = NULL; WCHAR *Attr = NULL; BerElement *BerElement; WCHAR **Values = NULL;
WCHAR *AttrArray[3]; struct l_timeval timeout;
WCHAR *ConfigurationNamingContext = L"configurationNamingContext"; WCHAR *ObjectClassFilter = L"objectCategory=*";
//
// Set the out parameters to null
//
if(pwszConfigDn) { *pwszConfigDn = NULL; }
timeout.tv_sec = csecLDAPTIMEOUT; timeout.tv_usec = 0; //
// Query for the ldap server oerational attributes to obtain the default
// naming context.
//
AttrArray[0] = ConfigurationNamingContext; AttrArray[1] = NULL; // this is the sentinel
LdapError = g_pfnldap_search_ext_s(pld, NULL, LDAP_SCOPE_BASE, ObjectClassFilter, AttrArray, FALSE, NULL, NULL, &timeout, 10000, &SearchResult);
hr = HRESULT_FROM_WIN32(g_pfnLdapMapErrorToWin32(LdapError));
if (S_OK == hr) {
Entry = g_pfnldap_first_entry(pld, SearchResult);
if (Entry) {
Values = g_pfnldap_get_values(pld, Entry, ConfigurationNamingContext);
if (Values && Values[0]) { (*pwszConfigDn) = (LPWSTR)LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*(wcslen(Values[0])+1)); if(NULL==(*pwszConfigDn)) hr=E_OUTOFMEMORY; else wcscpy((*pwszConfigDn), Values[0]); }
g_pfnldap_value_free(Values);
}
if (pwszConfigDn && (!(*pwszConfigDn))) { //
// We could not get the default domain or out of memory - bail out
//
if(E_OUTOFMEMORY != hr) hr = HRESULT_FROM_WIN32(ERROR_CANT_ACCESS_DOMAIN_INFO);
} if(SearchResult) { g_pfnldap_msgfree(SearchResult); }
}
return hr; }
LPWSTR HelperExtensionToString(PCERT_EXTENSION Extension) { LPWSTR wszFormat = NULL; DWORD cbFormat = 0;
if(NULL==Extension) return NULL;
CryptFormatObject(X509_ASN_ENCODING, 0, 0, NULL, Extension->pszObjId, Extension->Value.pbData, Extension->Value.cbData, NULL, &cbFormat); if(cbFormat) { wszFormat = (LPWSTR)AEAlloc(cbFormat); if(wszFormat == NULL) { return NULL; }
CryptFormatObject(X509_ASN_ENCODING, 0, 0, NULL, Extension->pszObjId, Extension->Value.pbData, Extension->Value.cbData, wszFormat, &cbFormat);
}
return wszFormat; }
//*************************************************************
//
// MakeGenericSecurityDesc()
//
// Purpose: manufacture a security descriptor with generic
// access
//
// Parameters:
//
// Return: pointer to SECURITY_DESCRIPTOR or NULL on error
//
// Comments:
//
// History: Date Author Comment
// 4/12/99 NishadM Created
//
//*************************************************************
PISECURITY_DESCRIPTOR AEMakeGenericSecurityDesc() { PISECURITY_DESCRIPTOR psd = 0; SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY; SID_IDENTIFIER_AUTHORITY authWORLD = SECURITY_WORLD_SID_AUTHORITY;
PACL pAcl = 0; PSID psidSystem = 0, psidAdmin = 0, psidEveryOne = 0; DWORD cbMemSize; DWORD cbAcl; DWORD aceIndex; BOOL bSuccess = FALSE;
//
// Get the system sid
//
if (!AllocateAndInitializeSid(&authNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem)) { goto Exit; }
//
// Get the Admin sid
//
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmin)) { goto Exit; }
//
// Get the EveryOne sid
//
if (!AllocateAndInitializeSid(&authWORLD, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidEveryOne)) {
goto Exit; }
cbAcl = (2 * GetLengthSid (psidSystem)) + (2 * GetLengthSid (psidAdmin)) + (2 * GetLengthSid (psidEveryOne)) + sizeof(ACL) + (6 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
//
// Allocate space for the SECURITY_DESCRIPTOR + ACL
//
cbMemSize = sizeof( SECURITY_DESCRIPTOR ) + cbAcl;
psd = (PISECURITY_DESCRIPTOR) GlobalAlloc(GMEM_FIXED, cbMemSize);
if (!psd) { goto Exit; }
//
// increment psd by sizeof SECURITY_DESCRIPTOR
//
pAcl = (PACL) ( ( (unsigned char*)(psd) ) + sizeof(SECURITY_DESCRIPTOR) );
if (!InitializeAcl(pAcl, cbAcl, ACL_REVISION)) { goto Exit; }
//
// GENERIC_ALL for local system
//
aceIndex = 0; if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidSystem)) { goto Exit; }
//
// GENERIC_ALL for Administrators
//
aceIndex++; if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidAdmin)) { goto Exit; }
//
// GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | SYNCHRONIZE for world
//
aceIndex++; if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | SYNCHRONIZE, psidEveryOne)) { goto Exit; }
//
// Put together the security descriptor
//
if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION)) { goto Exit; }
if (!SetSecurityDescriptorDacl(psd, TRUE, pAcl, FALSE)) { goto Exit; }
bSuccess = TRUE; Exit: if (psidSystem) { FreeSid(psidSystem); }
if (psidAdmin) { FreeSid(psidAdmin); }
if (psidEveryOne) { FreeSid(psidEveryOne); }
if (!bSuccess && psd) { GlobalFree(psd); psd = 0; }
return psd; }
HRESULT aeRobustLdapBind( OUT LDAP ** ppldap, IN BOOL fGC) { DWORD dwErr = ERROR_SUCCESS; HRESULT hr = S_OK; BOOL fForceRediscovery = FALSE; DWORD dwGetDCFlags = DS_RETURN_DNS_NAME | DS_BACKGROUND_ONLY; PDOMAIN_CONTROLLER_INFO pDomainInfo = NULL; LDAP *pld = NULL; LPWSTR wszDomainControllerName = NULL;
ULONG ldaperr;
if(fGC) { dwGetDCFlags |= DS_GC_SERVER_REQUIRED; }
do { // netapi32!DsGetDcName is delay loaded, so wrap
if(fForceRediscovery) { dwGetDCFlags |= DS_FORCE_REDISCOVERY; } ldaperr = LDAP_SERVER_DOWN;
__try { // Get the GC location
dwErr = DsGetDcName(NULL, // Delayload wrapped
NULL, NULL, NULL, dwGetDCFlags, &pDomainInfo); } __except(dwErr = GetExceptionError(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) { } if(dwErr != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(dwErr); goto error; }
if((pDomainInfo == NULL) || ((pDomainInfo->Flags & DS_GC_FLAG) == 0) || ((pDomainInfo->Flags & DS_DNS_CONTROLLER_FLAG) == 0) || (pDomainInfo->DomainControllerName == NULL)) { if(!fForceRediscovery) { fForceRediscovery = TRUE; continue; } hr = HRESULT_FROM_WIN32(ERROR_CANT_ACCESS_DOMAIN_INFO); goto error; }
wszDomainControllerName = pDomainInfo->DomainControllerName;
// skip past forward slashes (why are they there?)
while(*wszDomainControllerName == L'\\') { wszDomainControllerName++; }
// bind to ds
if((pld = g_pfnldap_init(wszDomainControllerName, fGC?LDAP_GC_PORT:LDAP_PORT)) == NULL) { ldaperr = g_pfnLdapGetLastError(); } else { ldaperr = g_pfnldap_bind_s(pld, NULL, NULL, LDAP_AUTH_NEGOTIATE); } hr = HRESULT_FROM_WIN32(g_pfnLdapMapErrorToWin32(ldaperr));
if(fForceRediscovery) { break; } fForceRediscovery = TRUE;
} while(ldaperr == LDAP_SERVER_DOWN);
if(hr == S_OK) { *ppldap = pld; pld = NULL; }
error:
if(pld) { g_pfnldap_unbind(pld); } // we know netapi32 was already loaded safely (that's where we got pDomainInfo), so no need to wrap
if(pDomainInfo) { NetApiBufferFree(pDomainInfo); // Delayload wrapped
} return hr; }
|