crypt32(8/8): Initial implementation of CertGetCertificateChain and CertFreeCertificateChain

Juan Lang juan.lang at gmail.com
Tue Aug 14 17:43:49 CDT 2007


--Juan
-------------- next part --------------
From 48ce62d7c01413921c57522642fbfc8f7f6adc53 Mon Sep 17 00:00:00 2001
From: Juan Lang <juan.lang at gmail.com>
Date: Tue, 14 Aug 2007 15:15:02 -0700
Subject: [PATCH] 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 127739a..b4364b6 100644
--- a/dlls/crypt32/chain.c
+++ b/dlls/crypt32/chain.c
@@ -25,6 +25,8 @@ #include "crypt32_private.h"
 
 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 CertFreeCertificateChainEngi
     }
 }
 
+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 2347fee..9b8f954 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, &para, 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, &para, 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, &para, 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",
-- 
1.4.1


More information about the wine-patches mailing list