// // WinVerifyTrust implementation // // HansHu 2/18/96 created // #include "stdpch.h" #include "common.h" #if defined(UNICODE) || defined(_UNICODE) #error "no unicode hosting yet" //TBD: fix this (filenames, messages, ...) #endif #define FAIL { /*ASSERT(0);*/ goto fail; } #define DONE { /*ASSERT(0);*/ goto done; } ///// const GUID guidWSAPS = WIN_SPUB_ACTION_PUBLISHED_SOFTWARE; const GUID guidWSAPSNoBad = WIN_SPUB_ACTION_PUBLISHED_SOFTWARE_NOBADUI; const GUID guidWTSPI = WIN_TRUST_SUBJTYPE_PE_IMAGE; const GUID guidWTSJC = WIN_TRUST_SUBJTYPE_JAVA_CLASS; const GUID guidWTSC = WIN_TRUST_SUBJTYPE_CABINET; ///////////////////////////////////////////////////////////////////////////////////// // // Bit field definitions for REGVAL_STATE. These are entries for // parameterizing our behaviour. // // 00000001 was 'always trust everything' // 00000002 was 'use fancy dialog' // 00000004 was 'force a dialog in all cases' // 00000008 was STATE_TRUSTTEST in Beta 1 (but not B1 SDK) // 00000010 was STATE_TESTASREAL in Beta 1 (but not B1 SDK) // 00000020 was STATE_TRUSTTEST in B1 SDK // 00000040 was STATE_TESTASREAL in B1 SDK // #define STATE_TRUSTTEST 0x00000020 // Enable the (potential) trust of the testing root // Meaning: w/o this, for test certs, we don't bother // to look 'em up in the trust database // //#define STATE_TESTASREAL 0x00000040 // Treat testing root as the real root UI-wise // Meaning: for test dialogs we put up the non-void // UI instead of the 'void' ui. // NO LONGER SUPPORTED // #define STATE_TESTCANBEVALID 0x00000080 // Unless set, all test roots are treated as invalid // // State bits that we ALWAYS set. // #define STATE_DEFAULT 0 // (STATE_TRUSTTEST | STATE_TESTCANBEVALID) // set to 0 for rtm !!!! ///////////////////////////////////////////////////////////////////////////////////// typedef BOOL (WINAPI *PCP7SD)(IUnknown*,REFIID,LPVOID*); // DigSig entry points typedef BOOL (WINAPI *POCS) (IUnknown*,REFIID,LPVOID*); typedef BOOL (WINAPI *PCCS) (IUnknown*,REFIID,LPVOID*); ///////////////////////////////////////////////////////////////////////////////////// // // There are the following roots to the world // // 1. A testing root intended to be used for testing purposes only. // 2. The real root of the world, the one that we expect to use in practice // for real certificates, etc. // 3. The (testing) root that went out in the PDC release // 4. The (testing) root that went out in the IE3 B1 // 5. The (testing) root that went out in the IE3 SDK B1 // 6. A 2048 bit key that will be treated as non-testing (ie: REAL). Generated on // BobAtk's BBN box 7 June 1996. Intent is to issue cert with only a limited // enough validity period to get us through Beta2 and a small amount of grace // period following IE3 shipment. // // Notes: // // (3) is of historical value only; nothing treats this as in any way significant // anymore. // // (4) would have been the same as (5) but for a UI mixup. IE3 when out with (4), but // we distributed the corresponding signing tools to (4) only on a very limited // basis. The IE3 Beta 1 SDK when out with both (4) and (5) as OK testing roots, but // the corresponding signing tools only generated certs with (5). // // Net effect: code signed with the B1 SDK doesn't appear valid unless you have both // IE3 B1 _and_ the IE3 B1 SDK installed. // // More will be added here as time goes on to describe our release process over time. // BYTE testingroot[] = { #include "root.txt" // root.txt is generated with the dumppk utility }; BYTE testingrootBeta1[] = { #include "rootb1.txt" // the root that went out with IE3.0 Beta 1 }; BYTE realroot[] = // // This is the DER encoding of the root key that we trust as the root key of // the real hieararchy. REVIEW: Replace with real official root key before // shipment. // { #include "realroot.txt" // realroot.txt is generated with the dumppk utility /* this is the old one, used for the PDK release 0x30,0x5B,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05, 0x00,0x03,0x4A,0x00,0x30,0x47,0x02,0x40,0xF5,0xAF,0x79,0x26,0x26,0xDE,0x49,0x2A, 0x23,0x1D,0xA2,0x7C,0x60,0xDA,0x03,0x38,0xBE,0xEA,0xD2,0xB1,0x23,0x61,0x7E,0x2E, 0xC5,0x06,0x9A,0xE5,0xA4,0xF8,0xF9,0xB1,0x44,0x7D,0x67,0xFC,0xA3,0xC9,0x31,0x7F, 0x18,0x9F,0x42,0xAE,0xE8,0x59,0x43,0x1A,0x45,0x28,0x54,0x62,0x9B,0x50,0xD3,0x8B, 0x51,0x60,0x23,0x3E,0x6C,0x7F,0x95,0x52,0x02,0x03,0x01,0x00,0x01 */ }; BOOL IsRootKey ( BLOB* key, BOOL* pfTestingOnly ) { BOOL b = key->cbSize == sizeof(realroot) && memcmp ( key->pBlobData, realroot, sizeof(realroot) ) ==0; if (b) { *pfTestingOnly = FALSE; } else { *pfTestingOnly = TRUE; b = ( key->cbSize == sizeof(testingroot) && memcmp ( key->pBlobData, testingroot, sizeof(testingroot) ) ==0 ) || ( key->cbSize == sizeof(testingrootBeta1) && memcmp ( key->pBlobData, testingrootBeta1, sizeof(testingrootBeta1) ) ==0 ); } return b; } ////////////////////////////////////////////////////////////////// HRESULT LoadCTRoot(ICertificateStore* pstore) // // Load the GTE CyberTrust root certificate into the certificate store. // It needs to have special treatment since it can't be placed inside // of an .SPC because of it's internal encoding problems. Sigh. // { HRESULT hr = S_OK; HRSRC hrsrc = FindResource(hinst, MAKEINTRESOURCE(IDR_CTROOT), TEXT("CER")); if (hrsrc) { HGLOBAL hglobRes = LoadResource(hinst, hrsrc); if (hglobRes) { BLOB b; b.cbSize = SizeofResource(hinst, hrsrc); b.pBlobData = (BYTE*)LockResource(hglobRes); hr = pstore->ImportCertificate(&b, NULL); UnlockResource(hglobRes); FreeResource(hglobRes); } else hr = HError(); } else hr = HError(); return hr; } ////////////////////////////////////////////////////////////////// HRESULT InstallHelpFile(LPTSTR szFile /*must be MAX_PATH*/) { HRESULT hr = S_OK; HRSRC hrsrc = FindResource(hinst, MAKEINTRESOURCE(IDR_WINTRUSTHELPFILE), TEXT("HELP")); if ( hrsrc ) { HGLOBAL hglobRes = LoadResource( hinst, hrsrc ); if ( hglobRes ) { ULONG cbRes = SizeofResource( hinst, hrsrc ); BYTE* pbRes = (BYTE*)LockResource( hglobRes ); UINT cch = lstrlen(szFile); if (cch) { ASSERT(szFile[cch] == 0); lstrcpy(&szFile[cch], TEXT("\\WinTrust.hlp")); HANDLE hFile = CreateFile(szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { ULONG cbWritten; if (WriteFile(hFile, pbRes, cbRes, &cbWritten, NULL)) { } else hr = HError(); CloseHandle(hFile); } else hr = HError(); } else hr = HError(); UnlockResource( hglobRes ); FreeResource( hglobRes ); } else hr = HError(); } else hr = HError(); return hr; } ////////////////////////////////////////////////////////////////// STDAPI DllRegisterServer ( void ) { HRESULT hr = S_OK; // // Copy our wintrust help file from our resource into the System // directory if we can; otherwise the Windows directory // TCHAR szFile[MAX_PATH]; UINT cch = GetSystemDirectory(&szFile[0], MAX_PATH); if (cch) { hr = InstallHelpFile(&szFile[0]); } else hr = E_UNEXPECTED; if (hr != S_OK) { cch = GetWindowsDirectory(&szFile[0], MAX_PATH); if (cch) hr = InstallHelpFile(&szFile[0]); } if (hr != S_OK) return hr; // // pre-populate certificate store with CA certificates // HRSRC hrsrc = FindResource(hinst, MAKEINTRESOURCE(IDR_PREINST), TEXT("SPC")); if ( hrsrc ) { HGLOBAL hglobRes = LoadResource( hinst, hrsrc ); if ( hglobRes ) { ULONG cbRes = SizeofResource( hinst, hrsrc ); IPersistMemBlob* pPerMem; if ( pdigsig->CreatePkcs7SignedData(NULL, IID_IPersistMemBlob, (LPVOID*)&pPerMem) ) { BLOB b; b.pBlobData = (BYTE*)LockResource( hglobRes ); b.cbSize = cbRes; hr = pPerMem->Load( &b ); if ( hr == S_OK ) { ICertificateStore* from; hr = pPerMem->QueryInterface ( IID_ICertificateStore, ( LPVOID*)&from ); if ( hr == S_OK ) { ICertificateStore* to; if ( pdigsig->OpenCertificateStore ( NULL, IID_ICertificateStore, (LPVOID*)&to ) ) { hr = from->CopyTo ( to ); if (hr==S_OK) { hr = LoadCTRoot(to); } to->Release(); } else hr = E_UNEXPECTED; from->Release(); } } UnlockResource( hglobRes ); pPerMem->Release(); } else hr = E_UNEXPECTED; FreeResource( hglobRes ); } else hr = HError(); } else hr = HError(); return hr; } ////////////////////////////////////////////////////////////////// typedef BOOL (WINAPI *PIGCH)(HANDLE,DWORD,LPWIN_CERTIFICATE ); typedef BOOL (WINAPI *PIGCD)(HANDLE,DWORD,LPWIN_CERTIFICATE,PDWORD ); ////////////////////////////////////////////////////////////////// HRESULT GetSignedDataBlobFromImageFile // // Load the appropriate SignedData from the image. // Use the TASK allocator // ( HANDLE filehandle, BLOB& blob ) { HRESULT hr = S_OK; blob.pBlobData = NULL; // // Dynaload ImageHlp library // HINSTANCE imagehlp; PIGCH pImageGetCertificateHeader; PIGCD pImageGetCertificateData; imagehlp = LoadLibrary("IMAGEHLP"); if (imagehlp == NULL) return HError (); pImageGetCertificateHeader = (PIGCH)GetProcAddress(imagehlp, "ImageGetCertificateHeader"); pImageGetCertificateData = (PIGCD)GetProcAddress(imagehlp, "ImageGetCertificateData"); if (pImageGetCertificateHeader == NULL || pImageGetCertificateData == NULL) { FreeLibrary(imagehlp); return HError(); } // // On with it // LPWIN_CERTIFICATE cert = NULL; BOOL b; unsigned i; // // Look for pkcs#7 embedded in image // for (i = 0; ; i++) { WIN_CERTIFICATE hdr; if ((*pImageGetCertificateHeader)(filehandle, i, &hdr)) { if (hdr.wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA) { // found one break; } } else { // // Didn't find usable trust material // hr = TRUST_E_NOSIGNATURE; break; } } if (hr==S_OK) { // // Get pkcs#7 blob from image // DWORD size = 0; b = (*pImageGetCertificateData)(filehandle, i, NULL, &size); if (b) hr = E_UNEXPECTED; else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) hr = HError(); else { cert = (LPWIN_CERTIFICATE)_alloca(size); if (cert) { if ((*pImageGetCertificateData)(filehandle, i, cert, &size)) { // // Copy it to return it to the caller // blob.cbSize = cert->dwLength - OFFSETOF(WIN_CERTIFICATE,bCertificate); blob.pBlobData = (BYTE*)CoTaskMemAlloc(blob.cbSize); if (blob.pBlobData) { memcpy(blob.pBlobData, (BYTE*)&cert->bCertificate, blob.cbSize); } else hr = E_OUTOFMEMORY; } else hr = HError(); } else hr = E_OUTOFMEMORY; } } FreeLibrary(imagehlp); return hr; } ////////////////////////////// HRESULT DoPeFile ( HANDLE filehandle, IPkcs7SignedData* pkcs7 ) { // // Get the blob from the image file // BLOB blob; HRESULT hr = GetSignedDataBlobFromImageFile(filehandle, blob); if (hr==S_OK) { // // give pkcs#7 blob to somebody who understands it // IPersistMemBlob* memory; hr = pkcs7->QueryInterface(IID_IPersistMemBlob, (LPVOID*)&memory); if (hr==S_OK) { hr = memory->Load(&blob); memory->Release(); } CoTaskMemFree(blob.pBlobData); } if (hr==S_OK) { // // Find out what part of the image file is actually signed and verify // that in fact that matches // hr = pkcs7->VerifyImageFile( 0, filehandle, NULL, NULL, 0 ); } return hr; } ////////////////////////////////////////////////////////////////// HRESULT DoJavaFile ( HANDLE filehandle, IPkcs7SignedData* pkcs7 ) { HRESULT hr = S_OK; if ( hr == S_OK ) hr = pkcs7->LoadFromJavaClassFile ( filehandle, NULL ); if ( hr == S_OK ) hr = pkcs7->VerifyJavaClassFile ( filehandle, NULL, NULL, 0 ); return hr; } ////////////////////////////////////////////////////////////////// HRESULT DoSignableDocument ( ISignableDocument* signable, IPkcs7SignedData* pkcs7 ) { HRESULT hr = S_OK; if ( hr == S_OK ) hr = pkcs7->LoadFromSignableDocument ( signable ); if ( hr == S_OK ) hr = pkcs7->VerifySignableDocument ( signable, NULL, 0 ); return hr; } ////////////////////////////////////////////////////////////////// const ULONG rgIndividual[] = { CERT_PURPOSE_INDIVIDUALSOFTWAREPUBLISHING }; const ULONG rgCommercial[] = { CERT_PURPOSE_COMMERCIALSOFTWAREPUBLISHING }; const OSIOBJECTID * pPurposeIndividual = (const OSIOBJECTID *)&rgIndividual; const OSIOBJECTID * pPurposeCommercial = (const OSIOBJECTID *)&rgCommercial; HRESULT GetSignerInfo(IPkcs7SignedData* pkcs7, REFIID iid, LPVOID*ppv, BOOL* pfCommercial) // // Return the appropriate signer info from this Pkcs7 SignedData. // We take the first signer info which asserts that it is either an // indvidual or commercial publisher. // { LONG cSignerInfo; ISignerInfo* pinfoFound = NULL; HRESULT hr = pkcs7->get_SignerInfoCount(&cSignerInfo); if (hr==S_OK) { // // Try each signer info // LONG iSignerInfo; for (iSignerInfo = 0; pinfoFound==NULL && hr==S_OK && iSignerInfo < cSignerInfo; iSignerInfo++) { ISignerInfo* pinfo; hr = pkcs7->get_SignerInfo(iSignerInfo, IID_ISignerInfo, (LPVOID*)&pinfo); if (hr == S_OK) { // // Does that signer info have an authenticated 'statement type' attribute? // ISelectedAttributes* pattrs; hr = pinfo->get_AuthenticatedAttributes(IID_ISelectedAttributes, (LPVOID*)&pattrs); if (hr==S_OK) { CERT_PURPOSES* rgPurposes; hr = pattrs->get_StatementType(&rgPurposes); if (hr==S_OK) { // // Does it use one of the purposes we need? // BOOL fIndividual = IsIncludedIn(rgPurposes, pPurposeIndividual); BOOL fCommercial = IsIncludedIn(rgPurposes, pPurposeCommercial); if (fIndividual || fCommercial) { *pfCommercial = fCommercial; pinfoFound = pinfo; pinfoFound->AddRef(); } CoTaskMemFree(rgPurposes); } else hr = S_OK; // try the next signer info pattrs->Release(); } else hr = S_OK; // try the next signer info pinfo->Release(); } } } if (pinfoFound) { hr = pinfoFound->QueryInterface(iid, ppv); pinfoFound->Release(); } else if (hr==S_OK) hr = STG_E_FILENOTFOUND; return hr; } ////////////////////////////////////////////////////////////////// // // Data used in chain processing // // // Which extensions do we process // const ULONG extAuthorityKeyIdentifier[] = { 4, 2, 5, 29, 1 }; const ULONG extKeyUsageRestriction[] = { 4, 2, 5, 29, 4 }; const ULONG extBasicContraints[] = { 4, 2, 5, 29, 10}; const ULONG extAgencyInfo[] = { 10, 1,3,6,1,4,1,311,2,1,10 }; const ULONG extMinimalCriteria[] = { 10, 1,3,6,1,4,1,311,2,1,26 }; const ULONG extFinancialCriteria[] = { 10, 1,3,6,1,4,1,311,2,1,27 }; // // This extension, certificate policies, { ie-ce 3 }, should // according to the X.509v3 specification NEVER be marked // critical. However, it is being so marked in some certs, // notably Verisign's. // // Workaround: deem that we have in fact carried out all the // processing there is to do on this, namely nothing // // For good measure, we throw in all the other 'never critical' // extensions from the spec // const ULONG extCertPolicies[] = { 4, 2, 5, 29, 3 }; const ULONG extKeyAttributes[] = { 4, 2, 5, 29, 2 }; const ULONG extKeyPolicyMappings[] = { 4, 2, 5, 29, 5 }; const ULONG extSubjectAltName[] = { 4, 2, 5, 29, 7 }; const ULONG extIssuerAltName[] = { 4, 2, 5, 29, 8 }; const ULONG extSubjDirAttrs[] = { 4, 2, 5, 29, 9 }; const OSIOBJECTID * rgProcessedExtensions[] = { (const OSIOBJECTID *)&extAuthorityKeyIdentifier, (const OSIOBJECTID *)&extBasicContraints, (const OSIOBJECTID *)&extKeyUsageRestriction, (const OSIOBJECTID *)extAgencyInfo, (const OSIOBJECTID *)extMinimalCriteria, (const OSIOBJECTID *)extFinancialCriteria, (const OSIOBJECTID *)&extCertPolicies, (const OSIOBJECTID *)&extKeyAttributes, (const OSIOBJECTID *)&extKeyPolicyMappings, (const OSIOBJECTID *)&extSubjectAltName, (const OSIOBJECTID *)&extIssuerAltName, (const OSIOBJECTID *)&extSubjDirAttrs, }; const int cProcessedExtensions = sizeof(rgProcessedExtensions) / sizeof(const OSIOBJECTID *); // // A guard against a complete loop in the cert chain cycle. // Note: the actual maximum length is managed through the use // of pathLengthConstraints in the root cert and / or the CA // cert immediately below the root. // #define MAX_CHAIN_LENGTH 12 // // object id of the RDN that indicates a glue subject name // const ULONG attrGlueRdn[] = { 10, 1,3,6,1,4,1,311,2,1,25 }; const OSIOBJECTID* pidGlueRdn = (const OSIOBJECTID*)&attrGlueRdn[0]; ////////////////////////////////////////////////////////////////// HRESULT IsSameCert(IX509* pMe, IX509* pHim) // // Answer S_OK, S_FALSE as to whether these are in fact the same // actual certificate. // { CERTIFICATENAMES namesMe, namesHim; memset(&namesMe, 0, sizeof(namesMe)); memset(&namesHim, 0, sizeof(namesHim)); HRESULT hr = pMe ->get_CertificateNames(NULL, &namesMe); if (hr==S_OK) hr = pHim->get_CertificateNames(NULL, &namesHim); if (hr==S_OK && (namesMe.flags & CERTIFICATENAME_ISSUERSERIAL) && (namesHim.flags & CERTIFICATENAME_ISSUERSERIAL)) { if (IsEqual(namesMe.issuerSerial.issuerName, namesHim.issuerSerial.issuerName) && IsEqual(namesMe.issuerSerial.serialNumber, namesHim.issuerSerial.serialNumber)) { hr = S_OK; } else { hr = S_FALSE; } } else hr = E_UNEXPECTED; // all certs should have these FreeNames(&namesMe); FreeNames(&namesHim); return hr; } ////////////////////////////////////////////////////////////////// HRESULT SubjectIsIssuer(IX509* p509) // // Answer S_OK, S_FALSE, as to whether the subject name // of this cert is the same as its issuer // { IPersistMemBlob* pSubject = NULL; IPersistMemBlob* pIssuer = NULL; HRESULT hr = p509->get_Issuer (IID_IPersistMemBlob, (LPVOID*)&pSubject); if (hr==S_OK) hr = p509->get_Subject(IID_IPersistMemBlob, (LPVOID*)&pIssuer); if (hr==S_OK) { BLOB b1, b2; hr = pSubject->Save(&b1, FALSE); if (hr==S_OK) { hr = pIssuer->Save(&b2, FALSE); if (hr==S_OK) { if (IsEqual(b1, b2)) { hr = S_OK; // they match! } else { hr = S_FALSE; // they don't! } CoTaskMemFree(b2.pBlobData); } CoTaskMemFree(b1.pBlobData); } } if (pSubject) pSubject->Release(); if (pIssuer) pIssuer->Release(); return hr; } ////////////////////////////////////////////////////////////////// HRESULT SaveTheSame(IUnknown* pnameIssuer, IUnknown* pnameSubject) // // Answer S_OK, S_FALSE as to whether these objects save to the // same thing // { IPersistMemBlob* pIssuer = NULL; IPersistMemBlob* pSubject = NULL; HRESULT hr = S_OK; if (hr==S_OK) hr = pnameIssuer->QueryInterface(IID_IPersistMemBlob, (LPVOID*)&pIssuer); if (hr==S_OK) hr = pnameSubject->QueryInterface(IID_IPersistMemBlob, (LPVOID*)&pSubject); if (hr==S_OK) { BLOB bIssuer; hr = pIssuer->Save(&bIssuer, FALSE); if (hr==S_OK) { BLOB bSubject; hr = pSubject->Save(&bSubject, FALSE); if (hr==S_OK) { // // Do they match? // if (!IsEqual(bSubject, bIssuer)) { hr = S_FALSE; } CoTaskMemFree(bSubject.pBlobData); } CoTaskMemFree(bIssuer.pBlobData); } } if (pIssuer) pIssuer->Release(); if (pSubject) pSubject->Release(); return hr; } ////////////////////////////////////////////////////////////////// HRESULT AnyUnknownCriticalExtensions // // Answer S_OK, S_FALSE as to whether there any attributes in this // object that are critical yet aren't in the list of extensions // processed // ( ISelectedAttributes* pattrs, ULONG cExt, const OSIOBJECTID** rgExt ) { OSIOBJECTIDLIST* plist; HRESULT hr = pattrs->get_OsiIdList(&plist); if (hr==S_OK) { ULONG i,j; // // Loop through each extension in the certificate // for (i=0; hr==S_OK && i < plist->cid; i++) { OSIOBJECTID* pid = (OSIOBJECTID*)( (BYTE*)plist + plist->rgwOffset[i] ); // // Is that extension one that we process? // for (j=0; jget_Extension(pid, &fCritical, NULL); if (hr==S_OK) { if (fCritical) hr = S_FALSE; } DoNextExtensionInCert: /* empty */; } CoTaskMemFree(plist); } return hr; } ////////////////////////////////////////////////////////////////// #ifdef _DEBUG HRESULT GetNameOfCert(IX509*p509, LPWSTR* pwsz) { *pwsz = NULL; HRESULT hr = S_OK; IX500Name* pname; hr = p509->get_Subject(IID_IX500Name, (LPVOID*)&pname); if (hr==S_OK) { hr = pname->get_String(pwsz); pname->Release(); } if (hr==S_OK) { ISelectedAttributes* pattrs; hr = p509->QueryInterface(IID_ISelectedAttributes, (LPVOID*)&pattrs); if (hr == S_OK) { LPWSTR wszCommon; hr = pattrs->get_CommonName(&wszCommon); if (hr==S_OK) { LPWSTR wszX500 = *pwsz; int cch500 = lstrlenW(wszX500); int cchCom = lstrlenW(wszCommon); int cch = cch500 + 1 + cchCom + 1; int cb = cch * sizeof(WCHAR); *pwsz = (WCHAR*)CoTaskMemAlloc(cb); if (*pwsz) { memcpy(&(*pwsz)[0], wszX500, cch500*sizeof(WCHAR)); (*pwsz)[cch500] = '|'; memcpy(&(*pwsz)[cch500+1], wszCommon, (cchCom+1)*sizeof(WCHAR)); } CoTaskMemFree(wszX500); CoTaskMemFree(wszCommon); } hr = S_OK; pattrs->Release(); } } return hr; } #endif ////////////////////////////////////////////////////////////////// // // Verify the chain of certificates // HRESULT VerifyChain ( IX509* & parent, // in,out ICertificateStore* store, // in HCRYPTPROV prov, // in BOOL fCommercial, // in IX509* & p509Publisher, // out IX509* & p509Agency, // out BOOL & fTestingOnly // out ) { IX509* child = NULL; BOOL fHitRoot, fPathConstRootButOne, fPathConstRoot; BOOL fParentMayBeSelf; // whether the parent is allowed to be the same as the child int iLevel; // the level we are from the bottom int cSkipValidity; // how many certs to skip validity period testing on FILETIME ftNow, ftMin, ftMax; HRESULT hr = S_OK; HRESULT hrExpire = S_OK; // // Get the current UTC time // GetSystemTimeAsFileTime(&ftNow); // // Set up validity boundaries for later verification // ftMax.dwLowDateTime = 0; ftMax.dwHighDateTime = 0; ftMin.dwLowDateTime = 0xFFFFFFFF; ftMin.dwHighDateTime = 0x7FFFFFFF; // // These record whether the root and / or the CA just below the root // has an explicit path length constraint // fPathConstRootButOne = FALSE; fPathConstRoot = FALSE; cSkipValidity = 0; // // It is ok that the parent of this certificate in fact be this self- // same cert. We only allow this to happen for at most one link in the // certificate processing chain. // // REVIEW: We should probably be even more restrictive // fParentMayBeSelf = TRUE; ASSERT(parent); #ifdef _DEBUG LPWSTR wszParent = NULL; LPWSTR wszChild = NULL; GetNameOfCert(parent, &wszParent); #endif // // // MAIN LOOP // // for ( iLevel = 0, fHitRoot = FALSE; hr==S_OK // everything is all right && !fHitRoot // we haven't hit the root cert && iLevel <= MAX_CHAIN_LENGTH; // we haven't gone too far up the chain ++iLevel ) { // // Record certain of the certificates for later // if (iLevel == 0) { p509Publisher = parent; p509Publisher->AddRef(); } else if (iLevel == 1) { p509Agency = parent; p509Agency->AddRef(); } // // Verify that parent certificates don't have narrower // validity periods than child certificates. Also check // whether this certificate is current. // if (hr==S_OK) { if (cSkipValidity == 0) { FILETIME ftNotBefore, ftNotAfter; hr = parent->get_Validity(&ftNotBefore, &ftNotAfter); if (hr==S_OK) { if (CompareFileTime(&ftNow, &ftNotBefore) < 0 || CompareFileTime(&ftNow, &ftNotAfter) > 0) { // // The certificate is outside it's validity period // if (hrExpire == S_OK) hrExpire = CERT_E_EXPIRED; } if (hr == S_OK && (CompareFileTime(&ftMin, &ftNotBefore) < 0 || CompareFileTime(&ftMax, &ftNotAfter) > 0)) { // // Some child of this cert has a validity period // which falls outside of this cert's validity period // if (hrExpire == S_OK) hrExpire = CERT_E_VALIDIYPERIODNESTING; } // // Update the allowed time bounds // if (CompareFileTime(&ftNotBefore, &ftMin) < 0) { ftMin = ftNotBefore; // this is a 'min' in the 'ok' case } if (CompareFileTime(&ftNotAfter, &ftMax) > 0) { ftMax = ftNotAfter; // this is a 'max' in the 'ok' case } } } else { cSkipValidity--; } } // // Shift the parent / child relationship // if (child) child->Release(); child = parent; parent = NULL; #ifdef _DEBUG CoTaskMemFree(wszParent); wszParent = NULL; CoTaskMemFree(wszChild); GetNameOfCert(child, &wszChild); #endif // // Get the attributes of the child so we can look at a few // ISelectedAttributes* pChildAttrs = NULL; if (hr==S_OK) { hr = child->QueryInterface(IID_ISelectedAttributes, (LPVOID*)&pChildAttrs); } // // Check the basic constraints, if there are any // BOOL fThisCertHasPathLenConst = FALSE; if (hr==S_OK) { CERT_BASICCONSTRAINTS cts; BOOL pfSubtreesPresent; hr = pChildAttrs->get_BasicConstraints(&cts, NULL, &pfSubtreesPresent); if (hr==S_OK) { // // check that the certs are being used in the right role // if (iLevel==0) { if (!(cts.grfCanCertify & CERT_TYPE_ENDENTITY)) hr = CERT_E_ROLE; } else { // // iLevel > 0 implies this cert is being used as a CA // if (!(cts.grfCanCertify & CERT_TYPE_CA)) hr = CERT_E_ROLE; // // For CA's, also check the pathlen part of the constraint // ULONG cChildCAs = (ULONG)(iLevel-1); // number of CA's in use below this one if (!( cts.pathLengthConstraint == CERT_NOPATHLENGTHCONSTRAINT || cts.pathLengthConstraint >= cChildCAs ) ) { hr = CERT_E_PATHLENCONST; } // // If there is an explicit path length constraint in the CA // remember that for later // fThisCertHasPathLenConst = (cts.pathLengthConstraint != CERT_NOPATHLENGTHCONSTRAINT); } // // If there are any name processing constraints expressed in // the basicConstraints, then reject the cert since we don't // presently implement that checking (permittedSubtrees, // excludedSubtrees). // if (hr==S_OK && pfSubtreesPresent) { hr = CERT_E_MALFORMED; } } else if (hr==STG_E_FILENOTFOUND) { // // Missing basic contraints are ok // hr = S_OK; } } fPathConstRootButOne = fPathConstRoot; fPathConstRoot = fThisCertHasPathLenConst; // // check that this cert doesn't have any critical extensions // that we don't process // if (hr==S_OK) { hr = AnyUnknownCriticalExtensions(pChildAttrs, cProcessedExtensions, &rgProcessedExtensions[0]); if (hr != S_OK) hr = CERT_E_CRITICAL; } // // check that this cert can be used for signing either individual // or commercial software publishing, per the statement type made // in the SignerInfo // if (hr==S_OK) { if (fCommercial) hr = pChildAttrs->get_KeyCanBeUsedForSigning((OSIOBJECTID*)pPurposeCommercial, FALSE); else hr = pChildAttrs->get_KeyCanBeUsedForSigning((OSIOBJECTID*)pPurposeIndividual, FALSE); if (hr != S_OK) hr = CERT_E_PURPOSE; } if (pChildAttrs) pChildAttrs->Release(); pChildAttrs = NULL; if (FAILED(hr)) continue; // bail out of the loop over the cert chain // // Time to look at the parent of this guy // Find names of the parent certificate // CERTIFICATENAMES parentNames; hr = child->get_CertificateUsed(&parentNames); FindParent: if (hr==S_OK) { // // get parent certificate. parent==self in root case // ASSERT(!parent); hr = store->get_ReadOnlyCertificate(&parentNames, NULL, IID_IX509, (LPVOID*)&parent); if (hr==S_OK) { #ifdef _DEBUG CoTaskMemFree(wszParent); GetNameOfCert(parent, &wszParent); #endif // // Verify the signature on the child // // REVIEW: in future, we'll need to parameterize the provider based on // the algorithm used in the signature. // HCRYPTKEY key; hr = parent->get_PublicKey(prov, &key); if (hr==S_OK) { IAmSigned* pSigned; hr = child->QueryInterface(IID_IAmSigned, (LPVOID*)&pSigned); if (hr==S_OK) { hr = pSigned->Verify(prov, key); // hr = S_OK; // HACK HACK HACK bogus bogus bogus pSigned->Release(); } CryptDestroyKey(key); if (hr==S_OK) { // // Verify that the parent in fact issued the child // // REVIEW: With more careful and intricate coding, one // can probably get some speed wins by managing these // names over the looping process // IX500Name* pSubject = NULL; IX500Name* pIssuer = NULL; parent->get_Subject(IID_IX500Name, (LPVOID*)&pSubject); child ->get_Issuer (IID_IX500Name, (LPVOID*)&pIssuer); if (pIssuer && pSubject) { hr = SaveTheSame(pIssuer, pSubject); if (hr==S_OK) { // all is well } else if (hr==S_FALSE) { // // Maybe the parent is a glue cert for the child? // // NOTE: Because we retrieve read-only certificates // from the cert store, we can mess with the subject // name without harming anyone else // BOOL fDelete = FALSE; // can't delete while we have it open ISelectedAttributes* prdn; hr = pSubject->get_RelativeDistinguishedName(0, IID_ISelectedAttributes, (LPVOID*)&prdn); if (hr==S_OK) { hr = prdn->get_DirectoryString((OSIOBJECTID*)pidGlueRdn, NULL); if (hr==S_OK) { fDelete = TRUE; // yes, it starts with a glue rdn } prdn->Release(); } if (fDelete) { ASSERT(hr==S_OK); hr = pSubject->remove_RelativeDistinguishedName(0); if (hr==S_OK) { // Try the match again hr = SaveTheSame(pIssuer, pSubject); } } if (hr!=S_OK) hr = CERT_E_ISSUERCHAINING; } } else hr = CERT_E_MALFORMED; if (pIssuer) pIssuer->Release(); if (pSubject) pSubject->Release(); } if (hr==S_OK) { // // Check to see if child is in fact a root key. // Terminate the loop if so. // BLOB blob; hr = child->get_PublicKeyBlob(&blob); if (hr==S_OK) { BOOL b = IsRootKey(&blob, &fTestingOnly); CoTaskMemFree(blob.pBlobData); if (b) { fHitRoot = TRUE; } } } if (hr==S_OK && !fHitRoot) { // // Check for self-referential certificate to avoid infinite looping // if (hr==S_OK) { // // We have a self referential cert and thus a loop in the chain // iff the parent and child certs are the same, which is // iff they both have the same issuer name and serial number. // if (IsSameCert(parent, child)==S_OK) { // // We only allow one glue cert in the chain processing. // This is paranoia, mostly, but probably prevents some // sneaky attack of some sort or other. // if (fParentMayBeSelf) { // // We have a read-only certificate from the store. // So, we can doctor its subject name w/o harming anyone. // IX500Name* pSubject; hr = parent->get_Subject(IID_IX500Name, (LPVOID*)&pSubject); if (hr==S_OK) { ISelectedAttributes* prdn; hr = pSubject->create_RelativeDistinguishedName(0, IID_ISelectedAttributes, (LPVOID*)&prdn); if (hr==S_OK) { hr = prdn->put_DirectoryString((OSIOBJECTID*)pidGlueRdn, L"Glue"); prdn->Release(); } pSubject->Release(); } // // Done the doctoring, try looking up glue cert // if (hr==S_OK) { // Get the new names for the parent, but only // the subject name // FreeNames(&parentNames); hr = parent->get_CertificateNames(NULL, &parentNames); FreeNames(&parentNames, CERTIFICATENAME_SUBJECT); if (hr==S_OK) { // // Avoid certain (unspecified) attacks // fParentMayBeSelf = FALSE; // // The validity period relationship between this other parent and // us is _not_ required to nest as all other relationships are. // ftMax.dwLowDateTime = 0; ftMax.dwHighDateTime = 0; ftMin.dwLowDateTime = 0xFFFFFFFF; ftMin.dwHighDateTime = 0x7FFFFFFF; cSkipValidity = 1; parent->Release(); parent = NULL; #ifdef _DEBUG CoTaskMemFree(wszParent); wszParent = NULL; #endif goto FindParent; } } } // // Else it's self referential, but not trusted // hr = CERT_E_UNTRUSTEDROOT; } } } } } FreeNames(&parentNames); } } // end loop over certificate chain #ifdef _DEBUG CoTaskMemFree(wszParent); wszParent = NULL; CoTaskMemFree(wszChild); wszChild = NULL; #endif // // If nothing else went wrong, but something is expired, then note that // if (hr==S_OK && hrExpire != S_OK) { hr = hrExpire; } // // In order to manage the path length, check that either // 1. The root has an explicit path length constraint, and / or // 2. The CA immediately below the root has an explicit path length constraint, and / or // 3. The whole path is of length less than or equal to three // // NOTE: We no longer do this as we ourselves issue the glue certs and we can // just stick in a BasicConstraints in that cert if we find that we need to // if (hr==S_OK) { if (iLevel >= 3) // ie: a path length of four or more { if (!fPathConstRoot && !fPathConstRootButOne) { // hr = CERT_E_MALFORMED; // Disabled per above } } } // // Clean up // if (parent) { parent->Release(); parent = NULL; } if (child) { child->Release(); child = NULL; } return hr; } ////////////////////////////////////////////////////////////////// HRESULT GetRegistryState(BOOL& fTrustTesting, BOOL& fTestCanBeValid) // // Fetch our state from the registry. It is stored per-user // in Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\Software Publishing" // in the variable named 'state'. // { HRESULT hr = S_OK; DWORD state = 0; HKEY hkey; LONG r; // // Try to fetch our state from the registry // r = RegOpenKeyEx(HKEY_CURRENT_USER, REGPATH_WINTRUST_USER REGPATH_SPUB, 0, KEY_QUERY_VALUE, &hkey); if (r == ERROR_SUCCESS) { DWORD size = sizeof(state); DWORD dwType; r = RegQueryValueEx(hkey, TEXT("State"), NULL, &dwType, (BYTE*)&state, &size); RegCloseKey(hkey); if ( !(r==ERROR_SUCCESS && dwType==REG_DWORD) ) { state = 0; } } // // Force some settings on // state |= STATE_DEFAULT; if (state & STATE_TRUSTTEST) { // // Enable the ability to develop trust in the things signed // with the testing root // fTrustTesting = TRUE; } if (state & STATE_TESTCANBEVALID) { // // The test root can in fact be valid // fTestCanBeValid = TRUE; } return hr; } ////////////////////////////////////////////////////////////////// HRESULT VerifySeven( IPkcs7SignedData* pkcs7, // in ICertificateStore*& store, // out BOOL& fCommercial, // out ISignerInfo*& signer, // out HCRYPTPROV& prov, // out IX509*& parent // out ) { HRESULT hr = S_OK; store = NULL; signer = NULL; prov = NULL; fCommercial = FALSE; ////////////////////////////////////////////////////////////////////////// // // extract x509 certificates and store them persistently // BOOL b = (pdigsig->OpenCertificateStore) ( NULL, IID_ICertificateStore, (LPVOID*)&store ); if ( !b ) //note: we could support a store-less mode where all FAIL // the certificates come from the pkcs#7 // BLOCK { ICertificateStore* store7; hr = pkcs7->QueryInterface ( IID_ICertificateStore, (LPVOID*)&store7 ); if ( FAILED ( hr ) ) DONE hr = store7->CopyTo ( store ); store7->Release (); } if ( FAILED ( hr ) ) DONE // not realy fatal, but does indicate something fishy ////////////////////////////////////////////////////////////////////////// // // Find the appropriate signer info. // hr = GetSignerInfo(pkcs7, IID_ISignerInfo, (LPVOID*)&signer, &fCommercial); if ( FAILED ( hr ) ) DONE ////////////////////////////////////////////////////////////////////////// // // get x509 for signer of pkcs#7 out of store // { CERTIFICATENAMES names; hr = signer->get_CertificateUsed(&names); if (FAILED(hr)) DONE hr = store->get_ReadOnlyCertificate (&names, NULL, IID_IX509, (LPVOID*)&parent); FreeNames(&names); if (FAILED(hr)) DONE } ////////////////////////////////////////////////////////////////////////// // // verify signature on signer info // HCRYPTKEY key; // // REVIEW: Is it ALWAYS the case that CRYPT_VERIFYCONTEXT is sufficient? I don't // think so ... // b = CryptAcquireContext ( &prov, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ); if ( !b ) FAIL hr = parent->get_PublicKey ( prov, &key ); if ( FAILED ( hr ) ) DONE IAmSigned* signature; hr = signer->QueryInterface ( IID_IAmSigned, (LPVOID*)&signature ); if ( SUCCEEDED ( hr ) ) { //note: this method also checks that the message-digest authenticated // attribute (that is actually signed) matches the digest in the // contentinfo of the enclosing pkcs#7 (right, Bob?) hr = signature->Verify ( prov, key ); signature->Release (); } CryptDestroyKey ( key ); if ( FAILED ( hr ) ) DONE goto done; fail: hr = HError (); done: return hr; } ////////////////////////////////////////////////////////////////// HRESULT WINAPI WinVerifyTrust ( HWND hwnd, GUID* pguidActionID, LPVOID lpActionData ) { HRESULT hr; BOOL b; HANDLE myfilehandle = NULL; IPkcs7SignedData* pkcs7 = NULL; ICertificateStore* store = NULL; ISignerInfo* signer = NULL; IX509* parent = NULL; IX509* p509Publisher = NULL; IX509* p509Agency = NULL; HCRYPTPROV prov = NULL; BOOL fTrustTesting = FALSE; // should we ever trust a testing root? BOOL fTestingOnly = FALSE; // is this chain in fact in the testing case? BOOL fCommercial = FALSE; // if chain is valid, whether individ or commercial BOOL fTestCanBeValid = FALSE; BOOL fNoBadUI = FALSE; // filter out the trust providers we implement if (*pguidActionID == guidWSAPS) { fNoBadUI = FALSE; } else if (*pguidActionID == guidWSAPSNoBad) { fNoBadUI = TRUE; } else return TRUST_E_ACTION_UNKNOWN; /////////////////////////////////// // // Check our registry control settings. These are stored under the // per-user setting in the variable named 'State'. // hr = GetRegistryState(fTrustTesting, fTestCanBeValid); if (hr != S_OK) return hr; /////////////////////////////////// // filter out the action types we implement LPWIN_TRUST_ACTDATA_CONTEXT_WITH_SUBJECT lpActionDataContextWithSubject = (LPWIN_TRUST_ACTDATA_CONTEXT_WITH_SUBJECT)lpActionData; if ( *lpActionDataContextWithSubject->SubjectType != guidWTSPI && *lpActionDataContextWithSubject->SubjectType != guidWTSJC && *lpActionDataContextWithSubject->SubjectType != guidWTSC ) return TRUST_E_SUBJECT_FORM_UNKNOWN; LPWIN_TRUST_SUBJECT_FILE lpFile = (LPWIN_TRUST_SUBJECT_FILE)lpActionDataContextWithSubject->Subject; // get subject file info HANDLE filehandle = INVALID_HANDLE_VALUE; LPCWSTR filename = lpFile->lpPath; LPCWSTR displayname= filename; WCHAR* filechar = (WCHAR*)filename; //NOTE: prepare to stomp input buffer while ( *filechar != 0 && *filechar != '|' ) ++filechar; if ( *filechar != 0 ) { // file-name is followed by display-name *filechar = 0; displayname = filechar+1; } if ( lpFile->hFile == INVALID_HANDLE_VALUE ) { // need to open it ourselves DWORD size = 2*_MAX_PATH; LPSTR str = (LPSTR)HEAPALLOC ( size ); if ( str == NULL ) { hr = E_OUTOFMEMORY; DONE } WideCharToMultiByte ( CP_ACP, 0, lpFile->lpPath, -1, str, size, NULL, NULL ); myfilehandle = CreateFile ( str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); HEAPFREE ( str ); if ( myfilehandle == INVALID_HANDLE_VALUE ) { myfilehandle = NULL; hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); DONE } filehandle = myfilehandle; } else filehandle = lpFile->hFile; ////////////////////////////////////////////////////////////////////////// // // Extract the embedded pkcs7 and check that it matches file content // b = (pdigsig->CreatePkcs7SignedData) ( NULL, IID_IPkcs7SignedData, (LPVOID*)&pkcs7 ); if ( !b ) FAIL if ( *lpActionDataContextWithSubject->SubjectType == guidWTSPI ) { hr = DoPeFile( filehandle, pkcs7 ); } else if ( *lpActionDataContextWithSubject->SubjectType == guidWTSJC ) { hr = DoJavaFile( filehandle, pkcs7 ); } else if ( *lpActionDataContextWithSubject->SubjectType == guidWTSC ) { ISignableDocument* signable; hr = E_UNEXPECTED; if ( (pdigsig->CreateCABSigner) ( NULL, IID_ISignableDocument, (LPVOID*)&signable ) ) hr = S_OK; if ( hr == S_OK ) { IPersistFileHandle* file; hr = signable->QueryInterface ( IID_IPersistFileHandle, (LPVOID*)&file ); if ( hr == S_OK ) { hr = file->Load ( filehandle, GENERIC_READ, FILE_SHARE_READ ); file->Release(); } if ( hr == S_OK ) hr = DoSignableDocument ( signable, pkcs7 ); signable->Release(); } } else hr = E_UNEXPECTED; if (FAILED(hr)) DONE ////////////////////////////////////////////////////////////////////////// // // Verify and fetch the signer info from the #7 // hr = VerifySeven(pkcs7, store, fCommercial, signer, prov, parent); ////////////////////////////////////////////////////////////////////////// // // verify the chain of x509 certificates if (hr==S_OK) { hr = VerifyChain ( parent, // in,out store, // in prov, // in fCommercial, // in p509Publisher, // out p509Agency, // out fTestingOnly // out ); } goto done; ////////////////////////////////////////////////////////////////////////// fail: hr = HError (); done: // if (hr == S_OK) { hr = VerifyFinish(hr, hwnd, displayname, fTestingOnly, fTrustTesting, fTestCanBeValid, fCommercial, p509Publisher, p509Agency, signer, store, fNoBadUI); } // // clean up as appropriate // if ( prov != NULL ) CryptReleaseContext ( prov, 0 ); if ( parent != NULL ) parent->Release (); if ( signer != NULL ) signer->Release (); if ( store != NULL ) store->Release (); if ( pkcs7 != NULL ) pkcs7->Release (); if ( myfilehandle != NULL ) CloseHandle ( myfilehandle ); return hr; } ////////////////////////////////////////////////////////////////////////////// // // Closing routine of the trust logic // HRESULT VerifyFinish( HRESULT hr, HWND hwnd, LPCWSTR displayname, BOOL fTestingOnly, BOOL fTrustTesting, BOOL fTestCanBeValid, BOOL fCommercial, IX509* p509Publisher, IX509* p509Agency, ISignerInfo* signer, ICertificateStore* store, BOOL fNoBadUI ) { // // Figure out whether we need a UI or not // BOOL fValid = (hr==S_OK) && !(fTestingOnly && !fTestCanBeValid); BOOL fTrusted = FALSE; // default to not trusted IPersonalTrustDB*pTrustDB = NULL; if (!fValid && hr==S_OK) { // // Pretend that there is no signature. This is necessary // to get the correct UI on the 'valid but is testing // root' case. // hr = TRUST_E_NOSIGNATURE; } if (fValid) { // // Query the trust database // OpenTrustDB(NULL, IID_IPersonalTrustDB, (LPVOID*)&pTrustDB); if ( pTrustDB ) { if ( pTrustDB->IsTrustedCert(p509Publisher,0, fCommercial) == S_OK || pTrustDB->IsTrustedCert(p509Agency, 1, FALSE) == S_OK ) { fTrusted = !fTestingOnly || fTrustTesting; } } else fTrusted = FALSE; // If we can't open the trust DB, then we don't trust it } else { // // Invalid things are definitely not trusted // fTrusted = FALSE; signer = NULL; // so we dont get the program name from the untrusted signature } // // Put up the UI if appropriate // if ( !fTrusted ) { if ( hwnd == NULL ) hwnd = GetDesktopWindow(); if ( hwnd != INVALID_HANDLE_VALUE ) { if (fNoBadUI && !fValid) { // // Something isn't kosher, so we'd put up the dull dialog, but the caller // has specifically asked us not to. So we just return the failure // ASSERT(hr != TRUST_E_SUBJECT_NOT_TRUSTED); if (!FAILED(hr)) hr = E_FAIL; // paranoia: shouldn't ever happen } else if (UserOverride ( hwnd, NULL, // REVIEW: later add dialog title from WinTrust caller displayname, signer, store, fValid, hr, fTestingOnly, fTrustTesting, fCommercial, p509Publisher, p509Agency, pTrustDB ) ) { hr = S_OK; } else { hr = TRUST_E_SUBJECT_NOT_TRUSTED; } } else { // // Everything is ok (sig checks, etc) but it's not trusted and we've been // given no parent window with which to ask the user. // ASSERT(hr != TRUST_E_SUBJECT_NOT_TRUSTED); if ( !FAILED ( hr ) ) { hr = E_FAIL; } } } if ( pTrustDB ) pTrustDB->Release(); return hr; }