//+--------------------------------------------------------------------------- // // Microsoft Windows NT Security // Copyright (C) Microsoft Corporation, 1997 - 1999 // // File: crobu.cpp // // Contents: CryptRetrieveObjectByUrl // // History: 23-Jul-97 kirtd Created // 01-Jan-02 philh Changed to internally use UNICODE Urls // //---------------------------------------------------------------------------- #include #include #ifndef INTERNET_MAX_PATH_LENGTH #define INTERNET_MAX_PATH_LENGTH 2048 #endif // Initial commit size of the stack, in bytes. Estimate of 20K needed for // wininet. #define URL_WITH_TIMEOUT_THREAD_STACK_SIZE 0x5000 // // CryptRetrieveObjectByUrl Entry // // Passed to the thread that does the real URL retrieval. The creator // thread waits for either the URL retrieval to complete or a timeout. // typedef struct _CROBU_ENTRY CROBU_ENTRY, *PCROBU_ENTRY; struct _CROBU_ENTRY { LPWSTR pwszUrl; LPCSTR pszObjectOid; DWORD dwRetrievalFlags; DWORD dwTimeout; LPVOID pvObject; CRYPT_RETRIEVE_AUX_INFO AuxInfo; FILETIME LastSyncTime; BOOL fResult; DWORD dwErr; HMODULE hModule; HANDLE hWaitEvent; DWORD dwState; PCROBU_ENTRY pNext; PCROBU_ENTRY pPrev; }; #define CROBU_RUN_STATE 1 #define CROBU_DONE_STATE 2 #define CROBU_PENDING_STATE 3 CRITICAL_SECTION CrobuCriticalSection; HMODULE hCrobuModule; // Linked list of pending URL retrievals PCROBU_ENTRY pCrobuPendingHead; VOID WINAPI InitializeCryptRetrieveObjectByUrl( HMODULE hModule ) { Pki_InitializeCriticalSection(&CrobuCriticalSection); hCrobuModule = hModule; } VOID WINAPI DeleteCryptRetrieveObjectByUrl() { DeleteCriticalSection(&CrobuCriticalSection); } // // Local Functions (Forward Reference) // BOOL WINAPI IsPendingCryptRetrieveObjectByUrl ( IN LPCWSTR pwszUrl ); BOOL WINAPI CryptRetrieveObjectByUrlWithTimeout ( IN LPCWSTR pwszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, OUT LPVOID* ppvObject, IN PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ); void DebugPrintUrlRetrievalError( IN LPCWSTR pwszUrl, IN DWORD dwTimeout, IN DWORD dwErr ); //+--------------------------------------------------------------------------- // // Function: CryptRetrieveObjectByUrlA // // Synopsis: retrieve PKI object given an URL // //---------------------------------------------------------------------------- BOOL WINAPI CryptRetrieveObjectByUrlA ( IN LPCSTR pszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, OUT LPVOID* ppvObject, IN HCRYPTASYNC hAsyncRetrieve, IN PCRYPT_CREDENTIALS pCredentials, IN LPVOID pvVerify, IN PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ) { WCHAR pwszUrl[INTERNET_MAX_PATH_LENGTH+1]; if ( !MultiByteToWideChar( CP_ACP, 0, pszUrl, -1, pwszUrl, INTERNET_MAX_PATH_LENGTH+1 )) { return( FALSE ); } return( CryptRetrieveObjectByUrlW( pwszUrl, pszObjectOid, dwRetrievalFlags, dwTimeout, ppvObject, hAsyncRetrieve, pCredentials, pvVerify, pAuxInfo ) ); } //+--------------------------------------------------------------------------- // // Function: CryptRetrieveObjectByUrlW // // Synopsis: retrieve PKI object given an URL // //---------------------------------------------------------------------------- BOOL WINAPI CryptRetrieveObjectByUrlW ( IN LPCWSTR pwszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, OUT LPVOID* ppvObject, IN HCRYPTASYNC hAsyncRetrieve, IN PCRYPT_CREDENTIALS pCredentials, IN LPVOID pvVerify, IN PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ) { BOOL fResult; CObjectRetrievalManager* porm = NULL; // Remove any leading spaces while (L' ' == *pwszUrl) pwszUrl++; I_CryptNetDebugTracePrintfA( "CRYPTNET.DLL --> %s URL to retrieve: %S\n", 0 != (dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL) ? "Cached" : "Wire", pwszUrl); // For a nonCache retrieval with timeout, do the retrieval in another // thread. wininet and winldap don't always honor the timeout value. // // Check for parameters not supported by doing in another thread. // // Also, check that a cancel callback hasn't been registered via // CryptInstallCancelRetrieval() if (0 != dwTimeout && !(dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL) && 0xFFFF >= (DWORD_PTR) pszObjectOid && NULL == hAsyncRetrieve && NULL == pCredentials && NULL == pvVerify && NULL == I_CryptGetTls(hCryptNetCancelTls) ) { if (IsPendingCryptRetrieveObjectByUrl( pwszUrl )) { I_CryptNetDebugErrorPrintfA( "CRYPTNET.DLL --> CryptRetrieveObjectByUrl, already pending for : %S\n", pwszUrl); SetLastError( (DWORD) ERROR_BAD_NET_RESP ); return( FALSE ); } else { return CryptRetrieveObjectByUrlWithTimeout ( pwszUrl, pszObjectOid, dwRetrievalFlags, dwTimeout, ppvObject, pAuxInfo ); } } porm = new CObjectRetrievalManager; if ( porm == NULL ) { SetLastError( (DWORD) E_OUTOFMEMORY ); return( FALSE ); } fResult = porm->RetrieveObjectByUrl( pwszUrl, pszObjectOid, dwRetrievalFlags, dwTimeout, ppvObject, NULL, NULL, hAsyncRetrieve, pCredentials, pvVerify, pAuxInfo ); porm->Release(); if (!fResult) { DWORD dwLastErr = GetLastError(); I_CryptNetDebugErrorPrintfA( "CRYPTNET.DLL --> %s URL to retrieve: %S, failed: %d (0x%x)\n", 0 != (dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL) ? "Cached" : "Wire", pwszUrl, dwLastErr, dwLastErr); SetLastError(dwLastErr); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: CryptCancelAsyncRetrieval // // Synopsis: cancel asynchronous object retrieval // //---------------------------------------------------------------------------- BOOL WINAPI CryptCancelAsyncRetrieval (HCRYPTASYNC hAsyncRetrieval) { SetLastError( (DWORD) E_NOTIMPL ); return( FALSE ); } //+=========================================================================== // // Functions supporting URL retrieval with timeout. The actual retrieval // is done in another, created thread. // //============================================================================ //+--------------------------------------------------------------------------- // Returns TRUE if the previously initiated URL retrieval hasn't completed. //---------------------------------------------------------------------------- BOOL WINAPI IsPendingCryptRetrieveObjectByUrl ( IN LPCWSTR pwszUrl ) { BOOL fPending = FALSE; PCROBU_ENTRY pEntry; EnterCriticalSection(&CrobuCriticalSection); for (pEntry = pCrobuPendingHead; NULL != pEntry; pEntry = pEntry->pNext) { assert(CROBU_PENDING_STATE == pEntry->dwState); if (0 == wcscmp(pwszUrl, pEntry->pwszUrl)) { fPending = TRUE; break; } } LeaveCriticalSection(&CrobuCriticalSection); return fPending; } //+------------------------------------------------------------------------- // Duplicate the Dll library's handle //-------------------------------------------------------------------------- static HMODULE DuplicateLibrary( IN HMODULE hDll ) { if (hDll) { WCHAR wszModule[_MAX_PATH + 1]; if (0 == GetModuleFileNameU(hDll, wszModule, _MAX_PATH)) goto GetModuleFileNameError; wszModule[_MAX_PATH] = L'\0'; if (NULL == (hDll = LoadLibraryExU(wszModule, NULL, 0))) goto LoadLibraryError; } CommonReturn: return hDll; ErrorReturn: hDll = NULL; goto CommonReturn; TRACE_ERROR(GetModuleFileNameError) TRACE_ERROR(LoadLibraryError) } //+--------------------------------------------------------------------------- // Thread procedure that does the actual URL retrieval. // // Note, even if the creator thread times out, this thread will continue to // execute until the underlying URL retrieval returns. //---------------------------------------------------------------------------- DWORD WINAPI CryptRetrieveObjectByUrlWithTimeoutThreadProc ( LPVOID lpThreadParameter ) { PCROBU_ENTRY pEntry = (PCROBU_ENTRY) lpThreadParameter; CObjectRetrievalManager* porm = NULL; HMODULE hModule; // Do the actual URL retrieval using the parameters passed to this // thread by the creator thread. porm = new CObjectRetrievalManager; if (NULL == porm ) { pEntry->dwErr = (DWORD) E_OUTOFMEMORY; pEntry->fResult = FALSE; } else { pEntry->fResult = porm->RetrieveObjectByUrl( pEntry->pwszUrl, pEntry->pszObjectOid, pEntry->dwRetrievalFlags, pEntry->dwTimeout, &pEntry->pvObject, NULL, // ppfnFreeObject NULL, // ppvFreeContext NULL, // hAsyncRetrieve NULL, // pCredentials NULL, // pvVerify &pEntry->AuxInfo ); pEntry->dwErr = GetLastError(); porm->Release(); } EnterCriticalSection(&CrobuCriticalSection); // The creator thread incremented cryptnet's ref count to prevent us // from being unloaded until this thread exits. hModule = pEntry->hModule; pEntry->hModule = NULL; if (CROBU_RUN_STATE == pEntry->dwState) { // The creator thread didn't timeout. Wake it up and set the // state to indicate we completed. assert(pEntry->hWaitEvent); SetEvent(pEntry->hWaitEvent); pEntry->dwState = CROBU_DONE_STATE; LeaveCriticalSection(&CrobuCriticalSection); } else { // The creator thread timed out. We were added to the pending // list when it timed out. LPVOID pv = pEntry->pvObject; LPCSTR pOID = pEntry->pszObjectOid; assert(CROBU_PENDING_STATE == pEntry->dwState); assert(NULL == pEntry->hWaitEvent); // Remove from pending list if (pEntry->pNext) pEntry->pNext->pPrev = pEntry->pPrev; if (pEntry->pPrev) pEntry->pPrev->pNext = pEntry->pNext; else { assert(pCrobuPendingHead == pEntry); pCrobuPendingHead = pEntry->pNext; } LeaveCriticalSection(&CrobuCriticalSection); I_CryptNetDebugErrorPrintfA( "CRYPTNET.DLL --> CryptRetrieveObjectByUrl, pending completed for : %S\n", pEntry->pwszUrl); if (pv) { // Free the returned object if (NULL == pOID) CryptMemFree( pv ); else if (pEntry->dwRetrievalFlags & CRYPT_RETRIEVE_MULTIPLE_OBJECTS) CertCloseStore((HCERTSTORE) pv, 0); else if (CONTEXT_OID_CERTIFICATE == pOID) CertFreeCertificateContext((PCCERT_CONTEXT) pv); else if (CONTEXT_OID_CTL == pOID) CertFreeCTLContext((PCCTL_CONTEXT) pv); else if (CONTEXT_OID_CRL == pOID) CertFreeCRLContext((PCCRL_CONTEXT) pv); else { assert(CONTEXT_OID_CAPI2_ANY == pOID || CONTEXT_OID_PKCS7 == pOID); if (CONTEXT_OID_CAPI2_ANY == pOID || CONTEXT_OID_PKCS7 == pOID) CertCloseStore((HCERTSTORE) pv, 0); } } // Finally free the entry PkiFree(pEntry); } if (hModule) FreeLibraryAndExitThread(hModule, 0); else ExitThread(0); } //+--------------------------------------------------------------------------- // Creates another thread to do the URL retrieval. Waits for either the // URL retrieval to complete or the timeout. For a timeout, the URL retrieval // entry is added to a pending list and the URL retrieval is allowed to // complete. However, for a timeout, this procedure returns. // // This function guarantees the timeout value is honored. //---------------------------------------------------------------------------- BOOL WINAPI CryptRetrieveObjectByUrlWithTimeout ( IN LPCWSTR pwszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, OUT LPVOID* ppvObject, IN PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ) { BOOL fResult; DWORD dwErr = 0; PCROBU_ENTRY pEntry = NULL; HANDLE hThread = NULL; HANDLE hToken = NULL; DWORD dwThreadId; DWORD cchUrl; // Allocate and initialize the entry to be passed to the created // thread for doing the URL retrieval. cchUrl = wcslen(pwszUrl) + 1; pEntry = (PCROBU_ENTRY) PkiZeroAlloc(sizeof(CROBU_ENTRY) + cchUrl * sizeof(WCHAR)); if (NULL == pEntry) goto OutOfMemory; pEntry->pwszUrl = (LPWSTR) &pEntry[1]; memcpy(pEntry->pwszUrl, pwszUrl, cchUrl * sizeof(WCHAR)); assert(0xFFFF >= (DWORD_PTR) pszObjectOid); pEntry->pszObjectOid = pszObjectOid; pEntry->dwRetrievalFlags = dwRetrievalFlags; pEntry->dwTimeout = dwTimeout; // pEntry->pvObject pEntry->AuxInfo.cbSize = sizeof(pEntry->AuxInfo); pEntry->AuxInfo.pLastSyncTime = &pEntry->LastSyncTime; if ( pAuxInfo && offsetof(CRYPT_RETRIEVE_AUX_INFO, dwMaxUrlRetrievalByteCount) < pAuxInfo->cbSize ) { pEntry->AuxInfo.dwMaxUrlRetrievalByteCount = pAuxInfo->dwMaxUrlRetrievalByteCount; } // else // pEntry->AuxInfo = zero'ed via PkiZeroAlloc // pEntry->LastSyncTime // pEntry->fResult // pEntry->dwErr // pEntry->hModule // pEntry->hWaitEvent // pEntry->dwState // pEntry->pNext // pEntry->pPrev if (NULL == (pEntry->hWaitEvent = CreateEvent( NULL, // lpsa FALSE, // fManualReset FALSE, // fInitialState NULL))) // lpszEventName goto CreateWaitEventError; // Inhibit cryptnet.dll from being unloaded until the created thread // exits. pEntry->hModule = DuplicateLibrary(hCrobuModule); pEntry->dwState = CROBU_RUN_STATE; // Create the thread to do the Url retrieval if (NULL == (hThread = CreateThread( NULL, // lpThreadAttributes URL_WITH_TIMEOUT_THREAD_STACK_SIZE, CryptRetrieveObjectByUrlWithTimeoutThreadProc, pEntry, CREATE_SUSPENDED, &dwThreadId ))) goto CreateThreadError; // If we are impersonating, then, the created thread should also impersonate if (OpenThreadToken( GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken )) { // There isn't any security problem if the following fails. // If it fails will do the retrieval using the process's identity. if (!SetThreadToken(&hThread, hToken)) { DWORD dwLastErr = GetLastError(); I_CryptNetDebugErrorPrintfA( "CRYPTNET.DLL --> SetThreadToken failed: %d (0x%x)\n", dwLastErr, dwLastErr); } CloseHandle(hToken); hToken = NULL; } ResumeThread(hThread); CloseHandle(hThread); hThread = NULL; // Wait for either the Url retrieval to complete or a timeout WaitForSingleObjectEx( pEntry->hWaitEvent, dwTimeout, FALSE // bAlertable ); EnterCriticalSection(&CrobuCriticalSection); if (CROBU_DONE_STATE == pEntry->dwState) { // The URL retrieval completed in the created thread. Copy the // results from the entry block shared by this and the created // thread. fResult = pEntry->fResult; dwErr = pEntry->dwErr; *ppvObject = pEntry->pvObject; if ( pAuxInfo && offsetof(CRYPT_RETRIEVE_AUX_INFO, pLastSyncTime) < pAuxInfo->cbSize && pAuxInfo->pLastSyncTime ) { *pAuxInfo->pLastSyncTime = pEntry->LastSyncTime; } LeaveCriticalSection(&CrobuCriticalSection); } else { // The URL retrieval didn't complete in the created thread. // Add to the pending queue and return URL retrieval failure status. // Note, the created thread will be allowed to complete the initiated // retrieval. assert(CROBU_RUN_STATE == pEntry->dwState); CloseHandle(pEntry->hWaitEvent); pEntry->hWaitEvent = NULL; pEntry->dwState = CROBU_PENDING_STATE; // Add to the pending queue if (pCrobuPendingHead) { pCrobuPendingHead->pPrev = pEntry; pEntry->pNext = pCrobuPendingHead; } pCrobuPendingHead = pEntry; I_CryptNetDebugErrorPrintfA( "CRYPTNET.DLL --> CryptRetrieveObjectByUrl, %d timeout for : %S\n", pEntry->dwTimeout, pEntry->pwszUrl); pEntry = NULL; LeaveCriticalSection(&CrobuCriticalSection); goto RetrieveObjectByUrlTimeout; } CommonReturn: if (!fResult) DebugPrintUrlRetrievalError( pwszUrl, dwTimeout, dwErr ); if (pEntry) { if (pEntry->hWaitEvent) CloseHandle(pEntry->hWaitEvent); if (pEntry->hModule) FreeLibrary(pEntry->hModule); PkiFree(pEntry); } SetLastError(dwErr); return fResult; ErrorReturn: dwErr = GetLastError(); fResult = FALSE; goto CommonReturn; TRACE_ERROR(OutOfMemory) TRACE_ERROR(CreateWaitEventError) TRACE_ERROR(CreateThreadError) SET_ERROR(RetrieveObjectByUrlTimeout, ERROR_TIMEOUT) } DWORD GetCryptNetDebugFlags() { HKEY hKey = NULL; DWORD dwType = 0; DWORD dwValue = 0; DWORD cbValue = sizeof(dwValue); DWORD dwLastErr = GetLastError(); if (ERROR_SUCCESS != RegOpenKeyExA( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\crypt32", 0, // dwReserved KEY_READ, &hKey )) goto ErrorReturn; if (ERROR_SUCCESS != RegQueryValueExA( hKey, "DebugFlags", NULL, // pdwReserved &dwType, (BYTE *) &dwValue, &cbValue )) goto ErrorReturn; if (dwType != REG_DWORD || cbValue != sizeof(dwValue)) goto ErrorReturn; CommonReturn: if (NULL != hKey) RegCloseKey(hKey); SetLastError(dwLastErr); return dwValue; ErrorReturn: dwValue = 0; goto CommonReturn; } BOOL I_CryptNetIsDebugErrorPrintEnabled() { return 0 != (GetCryptNetDebugFlags() & 0x1); } BOOL I_CryptNetIsDebugTracePrintEnabled() { static BOOL fIKnow = FALSE; static BOOL fIsDebugTracePrintEnabled = FALSE; if (!fIKnow) { fIsDebugTracePrintEnabled = (0 != (GetCryptNetDebugFlags() & 0x2)); fIKnow = TRUE; } return fIsDebugTracePrintEnabled; } void I_CryptNetDebugPrintfA( LPCSTR szFormat, ... ) { char szBuffer[1024]; va_list arglist; DWORD dwLastErr = GetLastError(); _try { va_start(arglist, szFormat); _vsnprintf(szBuffer, sizeof(szBuffer), szFormat, arglist); szBuffer[sizeof(szBuffer) - 1] = '\0'; va_end(arglist); OutputDebugStringA(szBuffer); } _except( EXCEPTION_EXECUTE_HANDLER) { } SetLastError(dwLastErr); } void I_CryptNetDebugErrorPrintfA( LPCSTR szFormat, ... ) { if (!I_CryptNetIsDebugErrorPrintEnabled()) return; else { char szBuffer[1024]; va_list arglist; DWORD dwLastErr = GetLastError(); _try { va_start(arglist, szFormat); _vsnprintf(szBuffer, sizeof(szBuffer), szFormat, arglist); szBuffer[sizeof(szBuffer) - 1] = '\0'; va_end(arglist); OutputDebugStringA(szBuffer); } _except( EXCEPTION_EXECUTE_HANDLER) { } SetLastError(dwLastErr); } } void I_CryptNetDebugTracePrintfA( LPCSTR szFormat, ... ) { if (!I_CryptNetIsDebugTracePrintEnabled()) return; else { char szBuffer[1024]; va_list arglist; DWORD dwLastErr = GetLastError(); _try { va_start(arglist, szFormat); _vsnprintf(szBuffer, sizeof(szBuffer), szFormat, arglist); szBuffer[sizeof(szBuffer) - 1] = '\0'; va_end(arglist); OutputDebugStringA(szBuffer); } _except( EXCEPTION_EXECUTE_HANDLER) { } SetLastError(dwLastErr); } } void DebugPrintUrlRetrievalError( IN LPCWSTR pwszUrl, IN DWORD dwTimeout, IN DWORD dwErr ) { I_CryptNetDebugErrorPrintfA("CRYPTNET.DLL --> Url retrieval timeout: %d error: %d (0x%x) for::\n %S\n", dwTimeout, dwErr, dwErr, pwszUrl); }