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;