From 9a58b30819080a05dfc30914f5e6af34f3089a43 Mon Sep 17 00:00:00 2001 From: Juan Lang Date: Tue, 14 Aug 2007 15:15:02 -0700 Subject: [PATCH] crypt32: Initial implementation of CertGetCertificateChain and CertFreeCertificateChain. --- dlls/crypt32/chain.c | 300 ++++++++++++++++++++++++++++++++++++- dlls/crypt32/tests/chain.c | 9 -- 2 files changed, 294 insertions(+), 15 deletions(-) diff --git a/dlls/crypt32/chain.c b/dlls/crypt32/chain.c index 127739a29c..161ba291d4 100644 --- a/dlls/crypt32/chain.c +++ b/dlls/crypt32/chain.c @@ -25,6 +25,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(crypt); +static HCERTCHAINENGINE CRYPT_defaultChainEngine; + /* This represents a subset of a certificate chain engine: it doesn't include * the "hOther" store described by MSDN, because I'm not sure how that's used. * It also doesn't include the "hTrust" store, because I don't yet implement @@ -171,22 +173,308 @@ void WINAPI CertFreeCertificateChainEngine(HCERTCHAINENGINE hChainEngine) } } +static HCERTCHAINENGINE CRYPT_GetDefaultChainEngine(void) +{ + if (!CRYPT_defaultChainEngine) + { + CERT_CHAIN_ENGINE_CONFIG config = { 0 }; + HCERTCHAINENGINE engine; + + config.cbSize = sizeof(config); + CertCreateCertificateChainEngine(&config, &engine); + InterlockedCompareExchangePointer(&CRYPT_defaultChainEngine, engine, + NULL); + if (CRYPT_defaultChainEngine != engine) + CertFreeCertificateChainEngine(engine); + } + return CRYPT_defaultChainEngine; +} + +void default_chain_engine_free(void) +{ + CertFreeCertificateChainEngine(CRYPT_defaultChainEngine); +} + +typedef struct _CertificateChain +{ + CERT_CHAIN_CONTEXT context; + LONG ref; +} CertificateChain, *PCertificateChain; + +static inline BOOL WINAPI CRYPT_IsCertificateSelfSigned(PCCERT_CONTEXT cert) +{ + return CertCompareCertificateName(cert->dwCertEncodingType, + &cert->pCertInfo->Subject, &cert->pCertInfo->Issuer); +} + +/* Gets cert's issuer from store, and returns the validity flags associated + * with it. Returns NULL if no issuer whose public key matches cert's + * signature could be found. + */ +static PCCERT_CONTEXT CRYPT_GetIssuerFromStore(HCERTSTORE store, + PCCERT_CONTEXT cert, PDWORD pdwFlags) +{ + PCCERT_CONTEXT issuer = NULL; + + /* There might be more than issuer with the same name, so keep looking until + * one produces the correct signature for this cert. + */ + do { + *pdwFlags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG | + CERT_STORE_TIME_VALIDITY_FLAG; + issuer = CertGetIssuerCertificateFromStore(store, cert, issuer, + pdwFlags); + } while (issuer && (*pdwFlags & CERT_STORE_SIGNATURE_FLAG)); + return issuer; +} + +static BOOL CRYPT_AddCertToSimpleChain(PCERT_SIMPLE_CHAIN chain, + PCCERT_CONTEXT cert, DWORD dwFlags) +{ + BOOL ret = FALSE; + PCERT_CHAIN_ELEMENT element = CryptMemAlloc(sizeof(CERT_CHAIN_ELEMENT)); + + if (element) + { + if (!chain->cElement) + chain->rgpElement = CryptMemAlloc(sizeof(PCERT_CHAIN_ELEMENT)); + else + chain->rgpElement = CryptMemRealloc(chain->rgpElement, + (chain->cElement + 1) * sizeof(PCERT_CHAIN_ELEMENT)); + if (chain->rgpElement) + { + memset(element, 0, sizeof(CERT_CHAIN_ELEMENT)); + element->cbSize = sizeof(CERT_CHAIN_ELEMENT); + element->pCertContext = cert; + if (dwFlags & CERT_STORE_REVOCATION_FLAG && + !(dwFlags & CERT_STORE_NO_CRL_FLAG)) + element->TrustStatus.dwErrorStatus |= CERT_TRUST_IS_REVOKED; + if (dwFlags & CERT_STORE_SIGNATURE_FLAG) + element->TrustStatus.dwErrorStatus |= + CERT_TRUST_IS_NOT_SIGNATURE_VALID; + if (dwFlags & CERT_STORE_TIME_VALIDITY_FLAG) + element->TrustStatus.dwErrorStatus |= + CERT_TRUST_IS_NOT_TIME_VALID; + /* It appears, from every example certificate chain I've found, + * that this flag is always set: + */ + element->TrustStatus.dwInfoStatus = CERT_TRUST_HAS_PREFERRED_ISSUER; + if (chain->cElement) + { + PCERT_CHAIN_ELEMENT prevElement = + chain->rgpElement[chain->cElement - 1]; + + /* This cert is the issuer of the previous one in the chain, so + * retroactively check the previous one's time validity nesting. + */ + if (!CertVerifyValidityNesting( + prevElement->pCertContext->pCertInfo, cert->pCertInfo)) + prevElement->TrustStatus.dwErrorStatus |= + CERT_TRUST_IS_NOT_TIME_NESTED; + } + /* FIXME: check valid usages, name constraints, and for cycles */ + /* FIXME: initialize the rest of element */ + chain->TrustStatus.dwErrorStatus |= + element->TrustStatus.dwErrorStatus; + chain->TrustStatus.dwInfoStatus |= + element->TrustStatus.dwInfoStatus; + chain->rgpElement[chain->cElement++] = element; + ret = TRUE; + } + else + CryptMemFree(element); + } + return ret; +} + +static void CRYPT_FreeSimpleChain(PCERT_SIMPLE_CHAIN chain) +{ + DWORD i; + + for (i = 0; i < chain->cElement; i++) + CryptMemFree(chain->rgpElement[i]); + CryptMemFree(chain->rgpElement); + CryptMemFree(chain); +} + +static BOOL CRYPT_BuildSimpleChain(HCERTCHAINENGINE hChainEngine, + PCCERT_CONTEXT cert, LPFILETIME pTime, HCERTSTORE hAdditionalStore, + PCERT_SIMPLE_CHAIN *ppChain) +{ + BOOL ret = FALSE; + PCertificateChainEngine engine = (PCertificateChainEngine)hChainEngine; + PCERT_SIMPLE_CHAIN chain; + HCERTSTORE world; + + TRACE("(%p, %p, %p, %p)\n", hChainEngine, cert, pTime, hAdditionalStore); + + world = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, 0, + CERT_STORE_CREATE_NEW_FLAG, NULL); + CertAddStoreToCollection(world, engine->hWorld, 0, 0); + if (cert->hCertStore) + CertAddStoreToCollection(world, cert->hCertStore, 0, 0); + if (hAdditionalStore) + CertAddStoreToCollection(world, hAdditionalStore, 0, 0); + chain = CryptMemAlloc(sizeof(CERT_SIMPLE_CHAIN)); + if (chain) + { + memset(chain, 0, sizeof(CERT_SIMPLE_CHAIN)); + chain->cbSize = sizeof(CERT_SIMPLE_CHAIN); + ret = CRYPT_AddCertToSimpleChain(chain, cert, 0); + while (ret && !CRYPT_IsCertificateSelfSigned(cert)) + { + DWORD flags; + PCCERT_CONTEXT issuer = CRYPT_GetIssuerFromStore(world, cert, + &flags); + + if (issuer) + { + ret = CRYPT_AddCertToSimpleChain(chain, issuer, flags); + cert = issuer; + } + else + { + TRACE("Couldn't find issuer, aborting chain creation\n"); + ret = FALSE; + } + } + if (ret) + { + PCCERT_CONTEXT root = chain->rgpElement[chain->cElement - 1]-> + pCertContext; + + if (!(ret = CRYPT_IsCertificateSelfSigned(root))) + TRACE("Last certificate is not self-signed\n"); + else + { + chain->rgpElement[chain->cElement - 1]->TrustStatus.dwInfoStatus + |= CERT_TRUST_IS_SELF_SIGNED; + if (!(ret = CryptVerifyCertificateSignatureEx(0, + root->dwCertEncodingType, CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, + (void *)root, CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void *)root, + 0, NULL))) + TRACE("Last certificate's signature is invalid\n"); + } + if (ret) + { + BYTE hash[20]; + DWORD size = sizeof(hash); + CRYPT_HASH_BLOB blob = { sizeof(hash), hash }; + PCCERT_CONTEXT trustedRoot; + + CertGetCertificateContextProperty(root, CERT_HASH_PROP_ID, hash, + &size); + trustedRoot = CertFindCertificateInStore(engine->hRoot, + root->dwCertEncodingType, 0, CERT_FIND_SHA1_HASH, &blob, NULL); + if (!trustedRoot) + chain->TrustStatus.dwErrorStatus |= + CERT_TRUST_IS_UNTRUSTED_ROOT; + else + CertFreeCertificateContext(trustedRoot); + } + } + if (!ret) + { + CRYPT_FreeSimpleChain(chain); + chain = NULL; + } + *ppChain = chain; + } + CertCloseStore(world, 0); + return ret; +} + +typedef struct _CERT_CHAIN_PARA_NO_EXTRA_FIELDS { + DWORD cbSize; + CERT_USAGE_MATCH RequestedUsage; +} CERT_CHAIN_PARA_NO_EXTRA_FIELDS, *PCERT_CHAIN_PARA_NO_EXTRA_FIELDS; + +typedef struct _CERT_CHAIN_PARA_EXTRA_FIELDS { + DWORD cbSize; + CERT_USAGE_MATCH RequestedUsage; + CERT_USAGE_MATCH RequestedIssuancePolicy; + DWORD dwUrlRetrievalTimeout; + BOOL fCheckRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +} CERT_CHAIN_PARA_EXTRA_FIELDS, *PCERT_CHAIN_PARA_EXTRA_FIELDS; + BOOL WINAPI CertGetCertificateChain(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT* ppChainContext) { - FIXME("(%p, %p, %p, %p, %p, 0x%08X, %p, %p): stub\n", hChainEngine, - pCertContext, pTime, hAdditionalStore, pChainPara, dwFlags, pvReserved, - ppChainContext); + PCERT_SIMPLE_CHAIN simpleChain = NULL; + BOOL ret; + TRACE("(%p, %p, %p, %p, %p, %08x, %p, %p)\n", hChainEngine, pCertContext, + pTime, hAdditionalStore, pChainPara, dwFlags, pvReserved, ppChainContext); + + if (!pChainPara) + { + SetLastError(E_INVALIDARG); + return FALSE; + } if (ppChainContext) *ppChainContext = NULL; - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; + if (!hChainEngine) + hChainEngine = CRYPT_GetDefaultChainEngine(); + /* FIXME: what about HCCE_LOCAL_MACHINE? */ + /* FIXME: pChainPara is for now ignored */ + /* FIXME: only simple chains are supported for now, as CTLs aren't + * supported yet. + */ + if ((ret = CRYPT_BuildSimpleChain(hChainEngine, pCertContext, pTime, + hAdditionalStore, &simpleChain))) + { + PCertificateChain chain = CryptMemAlloc(sizeof(CertificateChain)); + + if (chain) + { + chain->ref = 1; + chain->context.cbSize = sizeof(CERT_CHAIN_CONTEXT); + memcpy(&chain->context.TrustStatus, &simpleChain->TrustStatus, + sizeof(CERT_TRUST_STATUS)); + chain->context.cChain = 1; + chain->context.rgpChain = CryptMemAlloc(sizeof(PCERT_SIMPLE_CHAIN)); + chain->context.rgpChain[0] = simpleChain; + chain->context.cLowerQualityChainContext = 0; + chain->context.rgpLowerQualityChainContext = NULL; + chain->context.fHasRevocationFreshnessTime = FALSE; + chain->context.dwRevocationFreshnessTime = 0; + } + else + ret = FALSE; + if (ppChainContext) + *ppChainContext = (PCCERT_CHAIN_CONTEXT)chain; + else + CertFreeCertificateChain((PCCERT_CHAIN_CONTEXT)chain); + } + TRACE("returning %d\n", ret); + return ret; +} + +static void CRYPT_FreeChainContext(PCertificateChain chain) +{ + DWORD i; + + /* Note the chain's rgpLowerQualityChainContext isn't freed, but + * it's never set, either. + */ + for (i = 0; i < chain->context.cChain; i++) + CRYPT_FreeSimpleChain(chain->context.rgpChain[i]); + CryptMemFree(chain->context.rgpChain); + CryptMemFree(chain); } void WINAPI CertFreeCertificateChain(PCCERT_CHAIN_CONTEXT pChainContext) { - FIXME("(%p): stub\n", pChainContext); + PCertificateChain chain = (PCertificateChain)pChainContext; + + TRACE("(%p)\n", pChainContext); + + if (chain) + { + if (InterlockedDecrement(&chain->ref) == 0) + CRYPT_FreeChainContext(chain); + } } diff --git a/dlls/crypt32/tests/chain.c b/dlls/crypt32/tests/chain.c index 2347feeb17..9b8f9543e3 100644 --- a/dlls/crypt32/tests/chain.c +++ b/dlls/crypt32/tests/chain.c @@ -492,12 +492,10 @@ static void testGetCertChain(void) /* Basic parameter checks */ ret = CertGetCertificateChain(NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL); - todo_wine ok(!ret && GetLastError() == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", GetLastError()); ret = CertGetCertificateChain(NULL, NULL, NULL, NULL, NULL, 0, NULL, &chain); - todo_wine ok(!ret && GetLastError() == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", GetLastError()); /* Crash @@ -509,7 +507,6 @@ static void testGetCertChain(void) sizeof(bigCert)); todo_wine ret = CertGetCertificateChain(NULL, cert, NULL, NULL, NULL, 0, NULL, NULL); - todo_wine ok(!ret && GetLastError() == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", GetLastError()); /* Crash @@ -530,14 +527,11 @@ static void testGetCertChain(void) cert = CertCreateCertificateContext(X509_ASN_ENCODING, selfSignedCert, sizeof(selfSignedCert)); ret = CertGetCertificateChain(NULL, cert, NULL, NULL, NULL, 0, NULL, NULL); - todo_wine ok(!ret && GetLastError() == E_INVALIDARG, "Expected E_INVALIDARG, got %08x\n", GetLastError()); ret = CertGetCertificateChain(NULL, cert, NULL, NULL, ¶, 0, NULL, &chain); - todo_wine ok(ret, "CertGetCertificateChain failed: %08x\n", GetLastError()); - todo_wine ok(chain != NULL, "Expected a chain\n"); if (chain) { @@ -564,7 +558,6 @@ static void testGetCertChain(void) ok(ret, "CertCreateCertificateChainEngine failed: %08x\n", GetLastError()); ret = CertGetCertificateChain(engine, cert, NULL, NULL, ¶, 0, NULL, &chain); - todo_wine ok(chain != NULL, "Expected a chain\n"); if (chain) { @@ -625,14 +618,12 @@ static void testGetCertChain(void) else ret = CertGetCertificateChain(NULL, cert, NULL, config.hRestrictedRoot, ¶, 0, NULL, &chain); - todo_wine ok(ret, "CertGetCertificateChain failed: %08x\n", GetLastError()); if (ret) { /* Since there's no guarantee the Verisign cert is in the Root * store, either error is acceptable. */ - todo_wine ok(chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR || chain->TrustStatus.dwErrorStatus == CERT_TRUST_IS_UNTRUSTED_ROOT, "Expected no error or CERT_TRUST_IS_UNTRUSTED_ROOT, got %08x\n", -- 2.32.0.93.g670b81a890