Skip to contentSkip to conversation

Use CryptoAPI CA store

PATCH In review Faidon Liambotis 2 participants 9 messages Sep 22, 2007 – Sep 27, 2007

lore
Share & tools
lore
#0 lore raw

PATCH v3 1/1 Use CryptoAPI CA store

Hello,
Below you will find a revised version of a patch that I sent almost 9 months before.

It allows OpenVPN to verify certificates agains the Windows Certificate Store.

Changed since v2:
* Replace the global variable by a TLS options variable
* Added relevant man page entry
* Minor bugfixes (more #ifdef WIN32 to guard the variable declaration, spelling error etc.)
* diff against 2.1-rc4 instead of rc1

Please review and apply :-)

Regards,
Faidon

--

cryptoapi.c +85 -1
diff -urp openvpn-2.1_rc4.orig/cryptoapi.c openvpn-2.1_rc4/cryptoapi.c
@@ -48,6 +48,8 @@
 static HINSTANCE crypt32dll = NULL;
 static BOOL WINAPI (*CryptAcquireCertificatePrivateKey) (PCCERT_CONTEXT pCert, DWORD dwFlags,
     void *pvReserved, HCRYPTPROV *phCryptProv, DWORD *pdwKeySpec, BOOL *pfCallerFreeProv) = NULL;
+static PCCERT_CONTEXT WINAPI (*CertCreateCertificateContext) (DWORD dwCertEncodingType,
+     const BYTE *pbCertEncoded, DWORD cbCertEncoded) = NULL;
 #endif
 
 /* Size of an SSL signature: MD5+SHA1 */
@@ -65,6 +67,8 @@ static BOOL WINAPI (*CryptAcquireCertifi
 #define CRYPTOAPI_F_CRYPT_SIGN_HASH			    106
 #define CRYPTOAPI_F_LOAD_LIBRARY			    107
 #define CRYPTOAPI_F_GET_PROC_ADDRESS			    108
+#define CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT		    109
+#define CRYPTOAPI_F_CERT_GET_CERT_CHAIN			    110
 
 static ERR_STRING_DATA CRYPTOAPI_str_functs[] =	{
     { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0),				    "microsoft cryptoapi"},
@@ -77,6 +81,8 @@ static ERR_STRING_DATA CRYPTOAPI_str_fun
     { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH, 0),			    "CryptSignHash" },
     { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY, 0),			    	    "LoadLibrary" },
     { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS, 0),			    "GetProcAddress" },
+    { ERR_PACK(0, CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT, 0),		    "CertCreateCertificateContext" },
+    { ERR_PACK(0, CRYPTOAPI_F_CERT_GET_CERT_CHAIN, 0),			    "CertGetCertificateChain" },
     { 0, NULL }
 };
 
@@ -364,7 +370,7 @@ int SSL_CTX_use_CryptoAPI_certificate(SS
     }
 
     /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
-    cert = d2i_X509(NULL, (unsigned char **) &cd->cert_context->pbCertEncoded,
+    cert = d2i_X509(NULL, (const unsigned char **) &cd->cert_context->pbCertEncoded,
 		    cd->cert_context->cbCertEncoded);
     if (cert == NULL) {
 	SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
@@ -461,3 +467,81 @@ int SSL_CTX_use_CryptoAPI_certificate(SS
     }
     return 0;
 }
+
+int CryptoAPI_verify_certificate(X509 *x509)
+{
+  int ret = -1;
+  int len;
+  unsigned char *buf = NULL;
+
+  PCCERT_CONTEXT pCertContext = NULL;
+  PCCERT_CHAIN_CONTEXT pChainContext = NULL;
+  CERT_ENHKEY_USAGE EnhkeyUsage;
+  CERT_USAGE_MATCH CertUsage;  
+  CERT_CHAIN_PARA ChainPara;
+
+  /* Convert from internal X509 format to DER */
+  len = i2d_X509(x509, &buf);
+  if (len < 0) {
+	SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
+	goto err;
+  }
+
+#ifdef __MINGW32_VERSION
+    /* MinGW w32api is incomplete when it comes to CryptoAPI, as per version 3.1
+     * anyway. This is a hack around that problem. */
+    if (crypt32dll == NULL) {
+	crypt32dll = LoadLibrary("crypt32");
+	if (crypt32dll == NULL) {
+	    CRYPTOAPIerr(CRYPTOAPI_F_LOAD_LIBRARY);
+	    goto err;
+	}
+    }
+    if (CertCreateCertificateContext == NULL) {
+	CertCreateCertificateContext = GetProcAddress(crypt32dll,
+		"CertCreateCertificateContext");
+	if (CertCreateCertificateContext == NULL) {
+	    CRYPTOAPIerr(CRYPTOAPI_F_GET_PROC_ADDRESS);
+	    goto err;
+	}
+    }
+#endif
+
+  /* Create a certificate context based on the above certificate */
+  pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+  	buf, len);
+  if (pCertContext == NULL) {
+	CRYPTOAPIerr(CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT);
+	goto err;
+  }
+
+  /* Create an empty issuer list */
+  EnhkeyUsage.cUsageIdentifier = 0;
+  EnhkeyUsage.rgpszUsageIdentifier = NULL;
+  CertUsage.dwType = USAGE_MATCH_TYPE_AND;
+  CertUsage.Usage  = EnhkeyUsage;
+
+  /* Searching and matching criteria to be used when building the chain */
+  ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
+  ChainPara.RequestedUsage = CertUsage;
+
+  /* Get the certificate chain of our certificate */
+  if (!CertGetCertificateChain(NULL, pCertContext, NULL, NULL, &ChainPara,
+	0, NULL, &pChainContext)) {
+	CRYPTOAPIerr(CRYPTOAPI_F_CERT_GET_CERT_CHAIN);
+	goto err;
+  }
+
+  /* return 1 when the certificate is trusted, 0 when it's not; -1 on error */
+  ret = (pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR);
+
+  err:
+    if (buf)
+	OPENSSL_free(buf);
+    if (pChainContext)
+	CertFreeCertificateChain(pChainContext);
+    if (pCertContext)
+	CertFreeCertificateContext(pCertContext);
+
+  return ret;
+}
cryptoapi.h +1 -0
diff -urp openvpn-2.1_rc4.orig/cryptoapi.h openvpn-2.1_rc4/cryptoapi.h
@@ -2,6 +2,7 @@
 #define _CRYPTOAPI_H_
 
 int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);
+int CryptoAPI_verify_certificate(X509 *x509);
 
 
 #endif /* !_CRYPTOAPI_H_ */
init.c +4 -0
diff -urp openvpn-2.1_rc4.orig/init.c openvpn-2.1_rc4/init.c
@@ -1558,6 +1558,10 @@ do_init_crypto_tls (struct context *c, c
   to.remote_cert_eku = options->remote_cert_eku;
   to.es = c->c2.es;
 
+#ifdef WIN32
+  to.cryptoapi_ca = options->cryptoapi_ca;
+#endif
+
 #ifdef ENABLE_DEBUG
   to.gremlin = c->options.gremlin;
 #endif
openvpn.8 +20 -0
diff -urp openvpn-2.1_rc4.orig/openvpn.8 openvpn-2.1_rc4/openvpn.8
@@ -124,6 +124,7 @@ openvpn \- secure IP tunnel daemon.
 [\ \fB\-\-connect\-retry\-max\fR\ \fIn\fR\ ]
 [\ \fB\-\-crl\-verify\fR\ \fIcrl\fR\ ]
 [\ \fB\-\-cryptoapicert\fR\ \fIselect\-string\fR\ ]
+[\ \fB\-\-cryptoapica\fR\ ]
 [\ \fB\-\-daemon\fR\ \fI[progname]\fR\ ]
 [\ \fB\-\-dev\-node\fR\ \fInode\fR\ ]
 [\ \fB\-\-dev\-type\fR\ \fIdevice\-type\fR\ ]
@@ -3769,7 +3770,26 @@ To select a certificate, based on certif
 
 The thumbprint hex string can easily be copy-and-pasted from the Windows
 Certificate Store GUI.
+.\"*********************************************************
+.TP
+.B --cryptoapica
+Use the Windows Certficate System Store to verify presented certificates
+(Windows Only).
+
+Use this option instead of or in addition to
+.B --ca.
+
+This makes it possible to verify certificates against the Certificate Store
+instead of having OpenVPN read the certificate authority (CA) file.
+Windows Certificate Store checks Certificate Revocation Lists (CRLs)
+automatically and will reject revoked certificates.
 
+The Certificate Store includes by default many known Certificate Authorities.
+Use this setting with extreme caution and preferrably in combination with
+.B --tls-verify
+or
+.B --tls-remote
+options.
 .\"*********************************************************
 .TP
 .B --key-method m
options.c +15 -2
diff -urp openvpn-2.1_rc4.orig/options.c openvpn-2.1_rc4/options.c
@@ -450,6 +450,8 @@ static const char usage_message[] =
 #ifdef WIN32
   "--cryptoapicert select-string : Load the certificate and private key from the\n"
   "                  Windows Certificate System Store.\n"
+  "--cryptoapica   : Check against Certificate Authorities stored in Windows\n"
+  "                  Certificate System Store.\n"
 #endif
   "--tls-cipher l  : A list l of allowable TLS ciphers separated by : (optional).\n"
   "                : Use --show-tls to see a list of supported TLS ciphers.\n"
@@ -1236,6 +1238,7 @@ show_settings (const struct options *o)
   SHOW_STR (pkcs12_file);
 #ifdef WIN32
   SHOW_STR (cryptoapi_cert);
+  SHOW_BOOL (cryptoapi_ca);
 #endif
   SHOW_STR (cipher_list);
   SHOW_STR (tls_verify);
@@ -1809,8 +1812,8 @@ options_postprocess (struct options *opt
 #ifdef WIN32
       if (options->cryptoapi_cert)
 	{
-	  if ((!(options->ca_file)) && (!(options->ca_path)))
-	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath)");
+	  if ((!(options->ca_file)) && (!(options->ca_path)) && (!(options->cryptoapi_ca)))
+	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath) or CryptoAPI CA (--cryptoapica)");
           if (options->cert_file)
 	    msg(M_USAGE, "Parameter --cert cannot be used when --cryptoapicert is also specified.");
           if (options->priv_key_file)
@@ -1831,8 +1834,13 @@ options_postprocess (struct options *opt
         }
       else
         {
+#ifdef WIN32
+	  if ((!(options->ca_file)) && (!(options->ca_path)) && (!(options->cryptoapi_ca)))
+	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath) or CryptoAPI CAs (--cryptoapica)");
+#else
 	  if ((!(options->ca_file)) && (!(options->ca_path)))
 	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath)");
+#endif
 	  if (pull)
 	    {
 	      const int sum = (options->cert_file != NULL) + (options->priv_key_file != NULL);
@@ -4889,6 +4897,11 @@ add_option (struct options *options,
       VERIFY_PERMISSION (OPT_P_GENERAL);
       options->cryptoapi_cert = p[1];
     }
+  else if (streq (p[0], "cryptoapica"))
+    {
+      VERIFY_PERMISSION (OPT_P_GENERAL);
+      options->cryptoapi_ca = true;
+    }
 #endif
   else if (streq (p[0], "key") && p[1])
     {
options.h +1 -0
diff -urp openvpn-2.1_rc4.orig/options.h openvpn-2.1_rc4/options.h
@@ -424,6 +424,7 @@ struct options
 
 #ifdef WIN32
   const char *cryptoapi_cert;
+  bool cryptoapi_ca;
 #endif
 
   /* data channel key exchange method */
proxy.c +1 -1
diff -urp openvpn-2.1_rc4.orig/proxy.c openvpn-2.1_rc4/proxy.c
@@ -39,7 +39,7 @@
 #include "base64.h"
 #include "ntlm.h"
 
-#ifdef WIN32
+#if defined(WIN32) && !defined(__MINGW32__)
 #include "ieproxy.h"
 #endif
 
ssl.c +11 -5
diff -urp openvpn-2.1_rc4.orig/ssl.c openvpn-2.1_rc4/ssl.c
@@ -543,13 +543,19 @@ verify_callback (int preverify_ok, X509_
   msg (D_LOW, "X509: %s", subject);
 #endif
 
-  /* did peer present cert which was signed our root cert? */
+  /* did peer present cert which was signed by our root cert? */
   if (!preverify_ok)
     {
-      /* Remote site specified a certificate, but it's not correct */
-      msg (D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s",
-	   ctx->error_depth, X509_verify_cert_error_string (ctx->error), subject);
-      goto err;			/* Reject connection */
+#ifdef WIN32
+     /* if cryptoapica was not enabled, fail; otherwise check against the CA Store */
+     if (!(opt->cryptoapi_ca && CryptoAPI_verify_certificate(ctx->current_cert)))
+#endif
+      {
+        /* Remote site specified a certificate, but it's not correct */
+        msg (D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s",
+	     ctx->error_depth, X509_verify_cert_error_string (ctx->error), subject);
+        goto err;			/* Reject connection */
+      }
     }
 
   /* warn if cert chain is too deep */
ssl.h +3 -0
diff -urp openvpn-2.1_rc4.orig/ssl.h openvpn-2.1_rc4/ssl.h
@@ -415,6 +415,9 @@ struct tls_options
   int ns_cert_type;
   unsigned remote_cert_ku[MAX_PARMS];
   const char *remote_cert_eku;
+#ifdef WIN32
+  bool cryptoapi_ca;
+#endif
 
   /* allow openvpn config info to be
      passed over control channel */
#1 lore raw
Quoted 4 lines · Faidon Liambotis

Hello,
Below you will find a revised version of a patch that I sent almost 9 months before.

Quoted 8 lines

It allows OpenVPN to verify certificates agains the Windows Certificate Store.

Changed since v2:
* Replace the global variable by a TLS options variable
* Added relevant man page entry
* Minor bugfixes (more #ifdef WIN32 to guard the variable declaration, spelling error etc.)
* diff against 2.1-rc4 instead of rc1

Why not use SSL_CTX_add_client_CA and add all CAPI root store into OpenSSL context?

Alon.

#2 lore raw
Quoted 2 lines · Alon Bar-Lev

Why not use SSL_CTX_add_client_CA and add all CAPI root store into OpenSSL context?

My initial approach (v1) was that (albeit not with SSL_CTX_add_client_CA but with X509_STORE_add_cert).
This was flawed, for the reasons I mentioned in v2 changelog (which btw, you have seen and commented on :-):
- CRLs are not handled that way.
- It is hard, if not impossible, to build proper chains from the imported CAs. e.g. you can have an intermediate CA in Windows Certificate Store that is *not* trusted by Windows because the root CA doesn't exist in the respective store.
- It felt like a gross hack.

It seems that is much better to ask Windows to check whether the presented certificate is trusted or not than try to build the trust path yourself and import it to OpenSSL.

Regards,
Faidon

#3 lore raw
Quoted 3 lines · Faidon Liambotis

It seems that is much better to ask Windows to check whether the presented certificate is trusted or not than try to build the trust path yourself and import it to OpenSSL.

OK...
So you need to use CertVerifyCertificateChainPolicy() with CERT_CHAIN_POLICY_SSL

Alon.

#4 lore raw
Quoted 1 line · Alon Bar-Lev

So you need to use CertVerifyCertificateChainPolicy() with CERT_CHAIN_POLICY_SSL

I'm no Microsoft developer (adn I don't want to be to be honest) but if I understand it right, it's better to call CertGetCertificateChain() as I am doing.

MSDN for CertVerifyCertificateChainPolicy()[1] says on the remarks:
"Use the CertGetCertificateChain function to enable and perform certificate revocation checking. The CertVerifyCertificateChainPolicy function does not check if certificates in the certificate chain are revoked."

I could be far off in reading the documentation; my tests however suggest that I got it right.

If you disagree, can you explain in more words your objection? :)

Thanks,
Faidon

1: http://msdn2.microsoft.com/en-us/library/aa377163.aspx

#5 lore raw
Quoted 5 lines · Faidon Liambotis
Quoted 1 line · Alon Bar-Lev

So you need to use CertVerifyCertificateChainPolicy() with CERT_CHAIN_POLICY_SSL

I'm no Microsoft developer (adn I don't want to be to be honest) but if I understand it right, it's better to call CertGetCertificateChain() as I am doing.

You need to use both, one for create the chain and the other to verify that it meets with system CTL for SSL.

Alon.

#6 lore raw

PATCH v4 1/1 Use CryptoAPI CA store (was Re: [PATCH v3] Use CryptoAPI CA store)

Quoted 9 lines · Alon Bar-Lev
Quoted 5 lines · Faidon Liambotis
Quoted 1 line · Alon Bar-Lev

So you need to use CertVerifyCertificateChainPolicy() with CERT_CHAIN_POLICY_SSL

I'm no Microsoft developer (adn I don't want to be to be honest) but if I understand it right, it's better to call CertGetCertificateChain() as I am doing.

You need to use both, one for create the chain and the other to verify that it meets with system CTL for SSL.

Seems that you are right. Below you will find -v4 of the patch that does that.

Also, my previous version didn't actually check for revocations, contrary to what I documented.
I added CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT to the dwFlags of CertGetCertificateChain.

Let me know what you think.

Thanks,
Faidon

--

cryptoapi.c +106 -1
diff -urp openvpn-2.1_rc4.orig/cryptoapi.c openvpn-2.1_rc4/cryptoapi.c
@@ -45,9 +45,15 @@
 #define CERT_STORE_READONLY_FLAG 0x00008000
 #define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000
 #define CRYPT_ACQUIRE_COMPARE_KEY_FLAG 0x00000004
+#define CERT_CHAIN_REVOCATION_CHECK_END_CERT           0x10000000
+#define CERT_CHAIN_REVOCATION_CHECK_CHAIN              0x20000000
+#define CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT 0x40000000
+#define CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY         0x80000000
 static HINSTANCE crypt32dll = NULL;
 static BOOL WINAPI (*CryptAcquireCertificatePrivateKey) (PCCERT_CONTEXT pCert, DWORD dwFlags,
     void *pvReserved, HCRYPTPROV *phCryptProv, DWORD *pdwKeySpec, BOOL *pfCallerFreeProv) = NULL;
+static PCCERT_CONTEXT WINAPI (*CertCreateCertificateContext) (DWORD dwCertEncodingType,
+     const BYTE *pbCertEncoded, DWORD cbCertEncoded) = NULL;
 #endif
 
 /* Size of an SSL signature: MD5+SHA1 */
@@ -65,6 +71,9 @@ static BOOL WINAPI (*CryptAcquireCertifi
 #define CRYPTOAPI_F_CRYPT_SIGN_HASH			    106
 #define CRYPTOAPI_F_LOAD_LIBRARY			    107
 #define CRYPTOAPI_F_GET_PROC_ADDRESS			    108
+#define CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT		    109
+#define CRYPTOAPI_F_CERT_GET_CERT_CHAIN			    110
+#define CRYPTOAPI_F_CERT_VERIFY_CERT_CHAIN_POLICY	    111
 
 static ERR_STRING_DATA CRYPTOAPI_str_functs[] =	{
     { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0),				    "microsoft cryptoapi"},
@@ -77,6 +86,9 @@ static ERR_STRING_DATA CRYPTOAPI_str_fun
     { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH, 0),			    "CryptSignHash" },
     { ERR_PACK(0, CRYPTOAPI_F_LOAD_LIBRARY, 0),			    	    "LoadLibrary" },
     { ERR_PACK(0, CRYPTOAPI_F_GET_PROC_ADDRESS, 0),			    "GetProcAddress" },
+    { ERR_PACK(0, CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT, 0),		    "CertCreateCertificateContext" },
+    { ERR_PACK(0, CRYPTOAPI_F_CERT_GET_CERT_CHAIN, 0),			    "CertGetCertificateChain" },
+    { ERR_PACK(0, CRYPTOAPI_F_CERT_VERIFY_CERT_CHAIN_POLICY, 0),	    "CertVerifyCertificateChainPolicy" },
     { 0, NULL }
 };
 
@@ -364,7 +376,7 @@ int SSL_CTX_use_CryptoAPI_certificate(SS
     }
 
     /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
-    cert = d2i_X509(NULL, (unsigned char **) &cd->cert_context->pbCertEncoded,
+    cert = d2i_X509(NULL, (const unsigned char **) &cd->cert_context->pbCertEncoded,
 		    cd->cert_context->cbCertEncoded);
     if (cert == NULL) {
 	SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
@@ -461,3 +473,96 @@ int SSL_CTX_use_CryptoAPI_certificate(SS
     }
     return 0;
 }
+
+int CryptoAPI_verify_certificate(X509 *x509)
+{
+  int ret = -1;
+  int len;
+  unsigned char *buf = NULL;
+
+  PCCERT_CONTEXT pCertContext = NULL;
+  PCCERT_CHAIN_CONTEXT pChainContext = NULL;
+  CERT_ENHKEY_USAGE EnhkeyUsage;
+  CERT_USAGE_MATCH CertUsage;  
+  CERT_CHAIN_PARA ChainPara;
+  CERT_CHAIN_POLICY_PARA PolicyPara;
+  CERT_CHAIN_POLICY_STATUS PolicyStatus;
+
+  /* Convert from internal X509 format to DER */
+  len = i2d_X509(x509, &buf);
+  if (len < 0) {
+	SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
+	goto err;
+  }
+
+#ifdef __MINGW32_VERSION
+    /* MinGW w32api is incomplete when it comes to CryptoAPI, as per version 3.1
+     * anyway. This is a hack around that problem. */
+    if (crypt32dll == NULL) {
+	crypt32dll = LoadLibrary("crypt32");
+	if (crypt32dll == NULL) {
+	    CRYPTOAPIerr(CRYPTOAPI_F_LOAD_LIBRARY);
+	    goto err;
+	}
+    }
+    if (CertCreateCertificateContext == NULL) {
+	CertCreateCertificateContext = GetProcAddress(crypt32dll,
+		"CertCreateCertificateContext");
+	if (CertCreateCertificateContext == NULL) {
+	    CRYPTOAPIerr(CRYPTOAPI_F_GET_PROC_ADDRESS);
+	    goto err;
+	}
+    }
+#endif
+
+  /* Create a certificate context based on the above certificate */
+  pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+  	buf, len);
+  if (pCertContext == NULL) {
+	CRYPTOAPIerr(CRYPTOAPI_F_CERT_CREATE_CERT_CONTEXT);
+	goto err;
+  }
+
+  /* Create an empty issuer list */
+  EnhkeyUsage.cUsageIdentifier = 0;
+  EnhkeyUsage.rgpszUsageIdentifier = NULL;
+  CertUsage.dwType = USAGE_MATCH_TYPE_AND;
+  CertUsage.Usage  = EnhkeyUsage;
+
+  /* Searching and matching criteria to be used when building the chain */
+  ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
+  ChainPara.RequestedUsage = CertUsage;
+
+  /* Get the certificate chain of our certificate */
+  if (!CertGetCertificateChain(NULL, pCertContext, NULL, NULL, &ChainPara,
+	CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
+	NULL, &pChainContext)) {
+	CRYPTOAPIerr(CRYPTOAPI_F_CERT_GET_CERT_CHAIN);
+	goto err;
+  }
+
+  PolicyPara.cbSize = sizeof(CERT_CHAIN_POLICY_PARA);
+  PolicyPara.dwFlags = 0;
+  PolicyPara.pvExtraPolicyPara = NULL;
+
+  PolicyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);
+
+  if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext,
+					&PolicyPara, &PolicyStatus)) {
+	CRYPTOAPIerr(CRYPTOAPI_F_CERT_VERIFY_CERT_CHAIN_POLICY);
+	goto err;
+  }
+
+  /* return 1 when the certificate is trusted, 0 when it's not */
+  ret = !PolicyStatus.dwError;
+
+  err:
+    if (buf)
+	OPENSSL_free(buf);
+    if (pChainContext)
+	CertFreeCertificateChain(pChainContext);
+    if (pCertContext)
+	CertFreeCertificateContext(pCertContext);
+
+  return ret;
+}
cryptoapi.h +1 -0
diff -urp openvpn-2.1_rc4.orig/cryptoapi.h openvpn-2.1_rc4/cryptoapi.h
@@ -2,6 +2,7 @@
 #define _CRYPTOAPI_H_
 
 int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);
+int CryptoAPI_verify_certificate(X509 *x509);
 
 
 #endif /* !_CRYPTOAPI_H_ */
init.c +4 -0
diff -urp openvpn-2.1_rc4.orig/init.c openvpn-2.1_rc4/init.c
@@ -1558,6 +1558,10 @@ do_init_crypto_tls (struct context *c, c
   to.remote_cert_eku = options->remote_cert_eku;
   to.es = c->c2.es;
 
+#ifdef WIN32
+  to.cryptoapi_ca = options->cryptoapi_ca;
+#endif
+
 #ifdef ENABLE_DEBUG
   to.gremlin = c->options.gremlin;
 #endif
openvpn.8 +20 -0
diff -urp openvpn-2.1_rc4.orig/openvpn.8 openvpn-2.1_rc4/openvpn.8
@@ -124,6 +124,7 @@ openvpn \- secure IP tunnel daemon.
 [\ \fB\-\-connect\-retry\-max\fR\ \fIn\fR\ ]
 [\ \fB\-\-crl\-verify\fR\ \fIcrl\fR\ ]
 [\ \fB\-\-cryptoapicert\fR\ \fIselect\-string\fR\ ]
+[\ \fB\-\-cryptoapica\fR\ ]
 [\ \fB\-\-daemon\fR\ \fI[progname]\fR\ ]
 [\ \fB\-\-dev\-node\fR\ \fInode\fR\ ]
 [\ \fB\-\-dev\-type\fR\ \fIdevice\-type\fR\ ]
@@ -3769,7 +3770,26 @@ To select a certificate, based on certif
 
 The thumbprint hex string can easily be copy-and-pasted from the Windows
 Certificate Store GUI.
+.\"*********************************************************
+.TP
+.B --cryptoapica
+Use the Windows Certficate System Store to verify presented certificates
+(Windows Only).
+
+Use this option instead of or in addition to
+.B --ca.
+
+This makes it possible to verify certificates against the Certificate Store
+instead of having OpenVPN read the certificate authority (CA) file.
+Windows Certificate Store checks Certificate Revocation Lists (CRLs)
+automatically and will reject revoked certificates.
 
+The Certificate Store includes by default many known Certificate Authorities.
+Use this setting with extreme caution and preferrably in combination with
+.B --tls-verify
+or
+.B --tls-remote
+options.
 .\"*********************************************************
 .TP
 .B --key-method m
options.c +15 -2
diff -urp openvpn-2.1_rc4.orig/options.c openvpn-2.1_rc4/options.c
@@ -450,6 +450,8 @@ static const char usage_message[] =
 #ifdef WIN32
   "--cryptoapicert select-string : Load the certificate and private key from the\n"
   "                  Windows Certificate System Store.\n"
+  "--cryptoapica   : Check against Certificate Authorities stored in Windows\n"
+  "                  Certificate System Store.\n"
 #endif
   "--tls-cipher l  : A list l of allowable TLS ciphers separated by : (optional).\n"
   "                : Use --show-tls to see a list of supported TLS ciphers.\n"
@@ -1236,6 +1238,7 @@ show_settings (const struct options *o)
   SHOW_STR (pkcs12_file);
 #ifdef WIN32
   SHOW_STR (cryptoapi_cert);
+  SHOW_BOOL (cryptoapi_ca);
 #endif
   SHOW_STR (cipher_list);
   SHOW_STR (tls_verify);
@@ -1809,8 +1812,8 @@ options_postprocess (struct options *opt
 #ifdef WIN32
       if (options->cryptoapi_cert)
 	{
-	  if ((!(options->ca_file)) && (!(options->ca_path)))
-	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath)");
+	  if ((!(options->ca_file)) && (!(options->ca_path)) && (!(options->cryptoapi_ca)))
+	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath) or CryptoAPI CA (--cryptoapica)");
           if (options->cert_file)
 	    msg(M_USAGE, "Parameter --cert cannot be used when --cryptoapicert is also specified.");
           if (options->priv_key_file)
@@ -1831,8 +1834,13 @@ options_postprocess (struct options *opt
         }
       else
         {
+#ifdef WIN32
+	  if ((!(options->ca_file)) && (!(options->ca_path)) && (!(options->cryptoapi_ca)))
+	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath) or CryptoAPI CAs (--cryptoapica)");
+#else
 	  if ((!(options->ca_file)) && (!(options->ca_path)))
 	    msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath)");
+#endif
 	  if (pull)
 	    {
 	      const int sum = (options->cert_file != NULL) + (options->priv_key_file != NULL);
@@ -4889,6 +4897,11 @@ add_option (struct options *options,
       VERIFY_PERMISSION (OPT_P_GENERAL);
       options->cryptoapi_cert = p[1];
     }
+  else if (streq (p[0], "cryptoapica"))
+    {
+      VERIFY_PERMISSION (OPT_P_GENERAL);
+      options->cryptoapi_ca = true;
+    }
 #endif
   else if (streq (p[0], "key") && p[1])
     {
options.h +1 -0
diff -urp openvpn-2.1_rc4.orig/options.h openvpn-2.1_rc4/options.h
@@ -424,6 +424,7 @@ struct options
 
 #ifdef WIN32
   const char *cryptoapi_cert;
+  bool cryptoapi_ca;
 #endif
 
   /* data channel key exchange method */
proxy.c +1 -1
diff -urp openvpn-2.1_rc4.orig/proxy.c openvpn-2.1_rc4/proxy.c
@@ -39,7 +39,7 @@
 #include "base64.h"
 #include "ntlm.h"
 
-#ifdef WIN32
+#if defined(WIN32) && !defined(__MINGW32__)
 #include "ieproxy.h"
 #endif
 
ssl.c +11 -5
diff -urp openvpn-2.1_rc4.orig/ssl.c openvpn-2.1_rc4/ssl.c
@@ -543,13 +543,19 @@ verify_callback (int preverify_ok, X509_
   msg (D_LOW, "X509: %s", subject);
 #endif
 
-  /* did peer present cert which was signed our root cert? */
+  /* did peer present cert which was signed by our root cert? */
   if (!preverify_ok)
     {
-      /* Remote site specified a certificate, but it's not correct */
-      msg (D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s",
-	   ctx->error_depth, X509_verify_cert_error_string (ctx->error), subject);
-      goto err;			/* Reject connection */
+#ifdef WIN32
+     /* if cryptoapica was not enabled, fail; otherwise check against the CA Store */
+     if (!(opt->cryptoapi_ca && CryptoAPI_verify_certificate(ctx->current_cert)))
+#endif
+      {
+        /* Remote site specified a certificate, but it's not correct */
+        msg (D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s",
+	     ctx->error_depth, X509_verify_cert_error_string (ctx->error), subject);
+        goto err;			/* Reject connection */
+      }
     }
 
   /* warn if cert chain is too deep */
ssl.h +3 -0
diff -urp openvpn-2.1_rc4.orig/ssl.h openvpn-2.1_rc4/ssl.h
@@ -415,6 +415,9 @@ struct tls_options
   int ns_cert_type;
   unsigned remote_cert_ku[MAX_PARMS];
   const char *remote_cert_eku;
+#ifdef WIN32
+  bool cryptoapi_ca;
+#endif
 
   /* allow openvpn config info to be
      passed over control channel */
#7 lore raw

Re: [Openvpn-devel] [PATCH v4] Use CryptoAPI CA store (was Re: [PATCH v3] Use CryptoAPI CA store)

Quoted 6 lines · Faidon Liambotis
Quoted 2 lines · Alon Bar-Lev

You need to use both, one for create the chain and the other to verify that it meets with system CTL for SSL.

Seems that you are right. Below you will find -v4 of the patch that does that.

Quoted 6 lines

Also, my previous version didn't actually check for revocations, contrary to what I documented.
I added CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT to the dwFlags of CertGetCertificateChain.

Let me know what you think.

Alon, ping?

I'd really like to have this patch merged this time.

If you have any more comments or objections, just say so so I can fix them.

If you don't, just say so, so it can get merged.

Thanks,
Faidon

#8 lore raw

Re: [Openvpn-devel] [PATCH v4] Use CryptoAPI CA store (was Re: [PATCH v3] Use CryptoAPI CA store)

Quoted 1 line · Faidon Liambotis

Alon, ping?

Oh... missed your post.
Please don't keep change the subject line.
And replay all to every correction.

Quoted 2 lines

I'd really like to have this patch merged this time. If you don't, just say so, so it can get merged.

Only James can merge patches...
I only review this as I can help you improve it.

Quoted 1 line

If you have any more comments or objections, just say so so I can fix them.

Some more notes:
1. For CertGetCertificateChain() you should use the following flags:
CERT_CHAIN_REVOCATION_CHECK_CHAIN
So that CA may suicide.

2. CertVerifyCertificateChainPolicy()

PolicyPara extra should be set:
fdwChecks should be SECURITY_FLAG_IGNORE_CERT_CN_INVALID as we check subject name within OpenVPN.

dwAuthType should be AUTHTYPE_CLIENT or AUTHTYPE_SERVER depending on the configuration. I think that peer-to-peer should be AUTHTYPE_SERVER.

3. I am not sure you require:
ChainPara.RequestedUsage = <something>
Have you tried to leave it zero?

4. I guess a cleaner code would load crypt32.dll once in the module, moving the LoadLibrary and GetProcAddress to a single function, calling it from initialization code.
Also if you already mess with this module, I guess a cleanup code should be added to free the library during termination... :)

Best Regards,
Alon Bar-Lev.