Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5138 lines
155 KiB

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