/****************************** 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 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;icUsageIdentifier;i++) { for (j=0;jcUsageIdentifier;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;icbData;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;ipInternalInfo->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; }