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 -- diff -urp openvpn-2.1_rc4.orig/cryptoapi.c openvpn-2.1_rc4/cryptoapi.c --- openvpn-2.1_rc4.orig/cryptoapi.c 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/cryptoapi.c 2007-09-22 14:08:09.000000000 +0300 @@ -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; +} diff -urp openvpn-2.1_rc4.orig/cryptoapi.h openvpn-2.1_rc4/cryptoapi.h --- openvpn-2.1_rc4.orig/cryptoapi.h 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/cryptoapi.h 2007-09-22 14:08:09.000000000 +0300 @@ -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_ */ diff -urp openvpn-2.1_rc4.orig/init.c openvpn-2.1_rc4/init.c --- openvpn-2.1_rc4.orig/init.c 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/init.c 2007-09-22 14:08:09.000000000 +0300 @@ -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 diff -urp openvpn-2.1_rc4.orig/openvpn.8 openvpn-2.1_rc4/openvpn.8 --- openvpn-2.1_rc4.orig/openvpn.8 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/openvpn.8 2007-09-22 14:33:27.000000000 +0300 @@ -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 diff -urp openvpn-2.1_rc4.orig/options.c openvpn-2.1_rc4/options.c --- openvpn-2.1_rc4.orig/options.c 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/options.c 2007-09-22 14:15:41.000000000 +0300 @@ -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]) { diff -urp openvpn-2.1_rc4.orig/options.h openvpn-2.1_rc4/options.h --- openvpn-2.1_rc4.orig/options.h 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/options.h 2007-09-22 14:08:09.000000000 +0300 @@ -424,6 +424,7 @@ struct options #ifdef WIN32 const char *cryptoapi_cert; + bool cryptoapi_ca; #endif /* data channel key exchange method */ diff -urp openvpn-2.1_rc4.orig/proxy.c openvpn-2.1_rc4/proxy.c --- openvpn-2.1_rc4.orig/proxy.c 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/proxy.c 2007-09-22 14:08:09.000000000 +0300 @@ -39,7 +39,7 @@ #include "base64.h" #include "ntlm.h" -#ifdef WIN32 +#if defined(WIN32) && !defined(__MINGW32__) #include "ieproxy.h" #endif diff -urp openvpn-2.1_rc4.orig/ssl.c openvpn-2.1_rc4/ssl.c --- openvpn-2.1_rc4.orig/ssl.c 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/ssl.c 2007-09-22 14:20:24.000000000 +0300 @@ -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 */ diff -urp openvpn-2.1_rc4.orig/ssl.h openvpn-2.1_rc4/ssl.h --- openvpn-2.1_rc4.orig/ssl.h 2007-04-26 00:38:46.000000000 +0300 +++ openvpn-2.1_rc4/ssl.h 2007-09-22 14:21:06.000000000 +0300 @@ -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 */