Hi! I needed support in OpenVPN to let an external script verify peer certificates in multi-client mode. I need this to be able to accept individual client certificates signed by different CAs or even self signed certificates, so that I can control exatly who may connect when I do not operate a CA.
I attached a patch for this against OpenVPN 2.0. It adds a new configuration option --tls-verify-cert. When it is turned on, the normal OpenSSL certificate preverification is ignored and an environment variable named peer_cert is exported for the tls-verify script, containing the peer's certificate in base64 encoded form. This is not PEM format because of the missing header, foo- ter and line breaks. Instead if you un-base64 it, you'll get the DER-form certificate. I hope this patch can be useful to someone, or someday be included in OpenVPN. regards, Tom
diff -ur openvpn-2.0/init.c openvpn-2.0-new/init.c --- openvpn-2.0/init.c Mon Apr 11 05:43:56 2005 +++ openvpn-2.0-new/init.c Mon May 9 18:57:45 2005 @@ -1305,6 +1305,7 @@ #endif to.verify_command = options->tls_verify; + to.verify_export_cert = options->tls_verify_cert; to.verify_x509name = options->tls_remote; to.crl_file = options->crl_file; to.ns_cert_type = options->ns_cert_type; diff -ur openvpn-2.0/options.c openvpn-2.0-new/options.c --- openvpn-2.0/options.c Sun Apr 17 00:03:15 2005 +++ openvpn-2.0-new/options.c Mon May 9 18:19:02 2005 @@ -426,6 +426,9 @@ " tests of certification. cmd should return 0 to allow\n" " TLS handshake to proceed, or 1 to fail. (cmd is\n" " executed as 'cmd certificate_depth X509_NAME_oneline')\n" + "--tls-verify-cert: Let --tls-verify script verify the peer certificate\n" + " and disable openssl's built in verification (possibly\n" + " dangerous!)\n" "--tls-remote x509name: Accept connections only from a host with X509 name\n" " x509name. The remote host must also pass all other tests\n" " of verification.\n" @@ -1105,6 +1108,7 @@ #endif SHOW_STR (cipher_list); SHOW_STR (tls_verify); + SHOW_BOOL (tls_verify_cert); SHOW_STR (tls_remote); SHOW_STR (crl_file); SHOW_INT (ns_cert_type); @@ -1625,6 +1629,7 @@ MUST_BE_UNDEF (pkcs12_file); MUST_BE_UNDEF (cipher_list); MUST_BE_UNDEF (tls_verify); + MUST_BE_UNDEF (tls_verify_cert); MUST_BE_UNDEF (tls_remote); MUST_BE_UNDEF (tls_timeout); MUST_BE_UNDEF (renegotiate_bytes); @@ -4355,6 +4360,11 @@ if (!no_more_than_n_args (msglevel, p, 2, NM_QUOTE_HINT)) goto err; options->tls_verify = string_substitute (p[1], ',', ' ', &options->gc); + } + else if (streq (p[0], "tls-verify-cert")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->tls_verify_cert = true; } else if (streq (p[0], "tls-remote") && p[1]) { diff -ur openvpn-2.0/options.h openvpn-2.0-new/options.h --- openvpn-2.0/options.h Mon Apr 11 05:43:57 2005 +++ openvpn-2.0-new/options.h Mon May 9 18:17:35 2005 @@ -362,6 +362,7 @@ const char *pkcs12_file; const char *cipher_list; const char *tls_verify; + bool tls_verify_cert; /* Verify peer cert in tls-verify script _only_ */ const char *tls_remote; const char *crl_file; int ns_cert_type; /* set to 0, NS_SSL_SERVER, or NS_SSL_CLIENT */ diff -ur openvpn-2.0/ssl.c openvpn-2.0-new/ssl.c --- openvpn-2.0/ssl.c Mon Apr 11 05:43:55 2005 +++ openvpn-2.0-new/ssl.c Mon May 9 19:03:19 2005 @@ -52,6 +52,7 @@ #include "perf.h" #include "status.h" #include "gremlin.h" +#include "base64.h" #ifdef WIN32 #include "cryptoapi.h" @@ -398,6 +399,31 @@ } } +static int get_peer_cert(X509_STORE_CTX *ctx, char **out) +{ + X509 *peercert; + int len, b64len; + char *buf, *bufp, *b64buf; + + peercert = X509_STORE_CTX_get_current_cert(ctx); + len = i2d_X509(peercert, NULL); + buf = malloc(len); + if (!buf) { + *out = NULL; + return false; + } + bufp = buf; + i2d_X509(peercert, (unsigned char **) &bufp); + b64len = base64_encode(buf, len, &b64buf); + free(buf); + if (b64len <= 0) { + *out = NULL; + return false; + } + *out = b64buf; + return true; +} + /* * Our verify callback function -- check * that an incoming peer certificate is good. @@ -413,6 +439,7 @@ struct tls_session *session; const struct tls_options *opt; const int max_depth = 8; + char *peer_cert; /* get the tls_session pointer */ ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); @@ -445,9 +472,18 @@ 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 */ + if (opt->verify_export_cert) + { + msg (D_TLS_ERRORS, "IGNORED VERIFY ERROR: depth=%d, error=%s: %s", + ctx->error_depth, X509_verify_cert_error_string (ctx->error), subject); + /* continue checking */ + } + else + { + 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 */ @@ -544,6 +580,14 @@ int ret; setenv_str (opt->es, "script_type", "tls-verify"); + if (opt->verify_export_cert) + { + if (get_peer_cert(ctx, &peer_cert)) + { + setenv_str(opt->es, "peer_cert", peer_cert); + free(peer_cert); + } + } buf_set_write (&out, (uint8_t*)command, sizeof (command)); buf_printf (&out, "%s %d %s", diff -ur openvpn-2.0/ssl.h openvpn-2.0-new/ssl.h --- openvpn-2.0/ssl.h Mon Apr 11 05:43:56 2005 +++ openvpn-2.0-new/ssl.h Mon May 9 19:05:41 2005 @@ -407,6 +407,7 @@ /* cert verification parms */ const char *verify_command; + bool verify_export_cert; const char *verify_x509name; const char *crl_file; int ns_cert_type;