summaryrefslogtreecommitdiff
path: root/openvpn/src/openvpn/ssl_verify_openssl.c
diff options
context:
space:
mode:
Diffstat (limited to 'openvpn/src/openvpn/ssl_verify_openssl.c')
-rw-r--r--openvpn/src/openvpn/ssl_verify_openssl.c610
1 files changed, 610 insertions, 0 deletions
diff --git a/openvpn/src/openvpn/ssl_verify_openssl.c b/openvpn/src/openvpn/ssl_verify_openssl.c
new file mode 100644
index 00000000..f5dce0dc
--- /dev/null
+++ b/openvpn/src/openvpn/ssl_verify_openssl.c
@@ -0,0 +1,610 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ * Copyright (C) 2010 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/**
+ * @file Control Channel Verification Module OpenSSL implementation
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#if defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL)
+
+#include "ssl_verify.h"
+#include "ssl_verify_backend.h"
+#include "ssl_openssl.h"
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+
+int
+verify_callback (int preverify_ok, X509_STORE_CTX * ctx)
+{
+ int ret = 0;
+ struct tls_session *session;
+ SSL *ssl;
+ struct gc_arena gc = gc_new();
+
+ /* get the tls_session pointer */
+ ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ ASSERT (ssl);
+ session = (struct tls_session *) SSL_get_ex_data (ssl, mydata_index);
+ ASSERT (session);
+
+ cert_hash_remember (session, ctx->error_depth,
+ x509_get_sha1_hash(ctx->current_cert, &gc));
+
+ /* did peer present cert which was signed by our root cert? */
+ if (!preverify_ok)
+ {
+ /* get the X509 name */
+ char *subject = x509_get_subject(ctx->current_cert, &gc);
+
+ if (subject)
+ {
+ /* 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);
+ }
+
+ ERR_clear_error();
+
+ session->verified = false;
+ goto cleanup;
+ }
+
+ if (SUCCESS != verify_cert(session, ctx->current_cert, ctx->error_depth))
+ goto cleanup;
+
+ ret = 1;
+
+cleanup:
+ gc_free(&gc);
+
+ return ret;
+}
+
+#ifdef ENABLE_X509ALTUSERNAME
+static
+bool extract_x509_extension(X509 *cert, char *fieldname, char *out, int size)
+{
+ bool retval = false;
+ X509_EXTENSION *pExt;
+ char *buf = 0;
+ int length = 0;
+ GENERAL_NAMES *extensions;
+ int nid = OBJ_txt2nid(fieldname);
+
+ extensions = (GENERAL_NAMES *)X509_get_ext_d2i(cert, nid, NULL, NULL);
+ if ( extensions )
+ {
+ int numalts;
+ int i;
+ /* get amount of alternatives,
+ * RFC2459 claims there MUST be at least
+ * one, but we don't depend on it...
+ */
+
+ numalts = sk_GENERAL_NAME_num(extensions);
+
+ /* loop through all alternatives */
+ for (i=0; i<numalts; i++)
+ {
+ /* get a handle to alternative name number i */
+ const GENERAL_NAME *name = sk_GENERAL_NAME_value (extensions, i );
+
+ switch (name->type)
+ {
+ case GEN_EMAIL:
+ ASN1_STRING_to_UTF8((unsigned char**)&buf, name->d.ia5);
+ if ( strlen (buf) != name->d.ia5->length )
+ {
+ msg (D_TLS_ERRORS, "ASN1 ERROR: string contained terminating zero");
+ OPENSSL_free (buf);
+ } else {
+ strncpynt(out, buf, size);
+ OPENSSL_free(buf);
+ retval = true;
+ }
+ break;
+ default:
+ msg (D_TLS_ERRORS, "ASN1 ERROR: can not handle field type %i",
+ name->type);
+ break;
+ }
+ }
+ sk_GENERAL_NAME_free (extensions);
+ }
+ return retval;
+}
+#endif /* ENABLE_X509ALTUSERNAME */
+
+/*
+ * Extract a field from an X509 subject name.
+ *
+ * Example:
+ *
+ * /C=US/ST=CO/L=Denver/O=ORG/CN=First-CN/CN=Test-CA/Email=jim@yonan.net
+ *
+ * The common name is 'Test-CA'
+ *
+ * Return true on success, false on error (insufficient buffer size in 'out'
+ * to contain result is grounds for error).
+ */
+static result_t
+extract_x509_field_ssl (X509_NAME *x509, const char *field_name, char *out,
+ int size)
+{
+ int lastpos = -1;
+ int tmp = -1;
+ X509_NAME_ENTRY *x509ne = 0;
+ ASN1_STRING *asn1 = 0;
+ unsigned char *buf = (unsigned char *)1; /* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
+ int nid = OBJ_txt2nid((char *)field_name);
+
+ ASSERT (size > 0);
+ *out = '\0';
+ do {
+ lastpos = tmp;
+ tmp = X509_NAME_get_index_by_NID(x509, nid, lastpos);
+ } while (tmp > -1);
+
+ /* Nothing found */
+ if (lastpos == -1)
+ return FAILURE;
+
+ x509ne = X509_NAME_get_entry(x509, lastpos);
+ if (!x509ne)
+ return FAILURE;
+
+ asn1 = X509_NAME_ENTRY_get_data(x509ne);
+ if (!asn1)
+ return FAILURE;
+ tmp = ASN1_STRING_to_UTF8(&buf, asn1);
+ if (tmp <= 0)
+ return FAILURE;
+
+ strncpynt(out, (char *)buf, size);
+
+ {
+ const result_t ret = (strlen ((char *)buf) < size) ? SUCCESS: FAILURE;
+ OPENSSL_free (buf);
+ return ret;
+ }
+}
+
+result_t
+x509_get_username (char *common_name, int cn_len,
+ char * x509_username_field, X509 *peer_cert)
+{
+#ifdef ENABLE_X509ALTUSERNAME
+ if (strncmp("ext:",x509_username_field,4) == 0)
+ {
+ if (!extract_x509_extension (peer_cert, x509_username_field+4, common_name, cn_len))
+ return FAILURE;
+ } else
+#endif
+ if (FAILURE == extract_x509_field_ssl (X509_get_subject_name (peer_cert),
+ x509_username_field, common_name, cn_len))
+ return FAILURE;
+
+ return SUCCESS;
+}
+
+char *
+x509_get_serial (openvpn_x509_cert_t *cert, struct gc_arena *gc)
+{
+ ASN1_INTEGER *asn1_i;
+ BIGNUM *bignum;
+ char *openssl_serial, *serial;
+
+ asn1_i = X509_get_serialNumber(cert);
+ bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
+ openssl_serial = BN_bn2dec(bignum);
+
+ serial = string_alloc(openssl_serial, gc);
+
+ BN_free(bignum);
+ OPENSSL_free(openssl_serial);
+
+ return serial;
+}
+
+unsigned char *
+x509_get_sha1_hash (X509 *cert, struct gc_arena *gc)
+{
+ char *hash = gc_malloc(SHA_DIGEST_LENGTH, false, gc);
+ memcpy(hash, cert->sha1_hash, SHA_DIGEST_LENGTH);
+ return hash;
+}
+
+char *
+x509_get_subject (X509 *cert, struct gc_arena *gc)
+{
+ BIO *subject_bio = NULL;
+ BUF_MEM *subject_mem;
+ char *subject = NULL;
+ int maxlen = 0;
+
+ subject_bio = BIO_new (BIO_s_mem ());
+ if (subject_bio == NULL)
+ goto err;
+
+ X509_NAME_print_ex (subject_bio, X509_get_subject_name (cert),
+ 0, XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_FN_SN |
+ ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_ESC_CTRL);
+
+ if (BIO_eof (subject_bio))
+ goto err;
+
+ BIO_get_mem_ptr (subject_bio, &subject_mem);
+
+ maxlen = subject_mem->length + 1;
+ subject = gc_malloc (maxlen, false, gc);
+
+ memcpy (subject, subject_mem->data, maxlen);
+ subject[maxlen - 1] = '\0';
+
+err:
+ if (subject_bio)
+ BIO_free (subject_bio);
+
+ return subject;
+}
+
+
+#ifdef ENABLE_X509_TRACK
+
+void
+x509_track_add (const struct x509_track **ll_head, const char *name, int msglevel, struct gc_arena *gc)
+{
+ struct x509_track *xt;
+ ALLOC_OBJ_CLEAR_GC (xt, struct x509_track, gc);
+ if (*name == '+')
+ {
+ xt->flags |= XT_FULL_CHAIN;
+ ++name;
+ }
+ xt->name = name;
+ xt->nid = OBJ_txt2nid(name);
+ if (xt->nid != NID_undef)
+ {
+ xt->next = *ll_head;
+ *ll_head = xt;
+ }
+ else
+ msg(msglevel, "x509_track: no such attribute '%s'", name);
+}
+
+/* worker method for setenv_x509_track */
+static void
+do_setenv_x509 (struct env_set *es, const char *name, char *value, int depth)
+{
+ char *name_expand;
+ size_t name_expand_size;
+
+ string_mod (value, CC_ANY, CC_CRLF, '?');
+ msg (D_X509_ATTR, "X509 ATTRIBUTE name='%s' value='%s' depth=%d", name, value, depth);
+ name_expand_size = 64 + strlen (name);
+ name_expand = (char *) malloc (name_expand_size);
+ check_malloc_return (name_expand);
+ openvpn_snprintf (name_expand, name_expand_size, "X509_%d_%s", depth, name);
+ setenv_str (es, name_expand, value);
+ free (name_expand);
+}
+
+void
+x509_setenv_track (const struct x509_track *xt, struct env_set *es, const int depth, X509 *x509)
+{
+ X509_NAME *x509_name = X509_get_subject_name (x509);
+ const char nullc = '\0';
+ int i;
+
+ while (xt)
+ {
+ if (depth == 0 || (xt->flags & XT_FULL_CHAIN))
+ {
+ i = X509_NAME_get_index_by_NID(x509_name, xt->nid, -1);
+ if (i >= 0)
+ {
+ X509_NAME_ENTRY *ent = X509_NAME_get_entry(x509_name, i);
+ if (ent)
+ {
+ ASN1_STRING *val = X509_NAME_ENTRY_get_data (ent);
+ unsigned char *buf;
+ buf = (unsigned char *)1; /* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
+ if (ASN1_STRING_to_UTF8 (&buf, val) > 0)
+ {
+ do_setenv_x509(es, xt->name, (char *)buf, depth);
+ OPENSSL_free (buf);
+ }
+ }
+ }
+ else
+ {
+ i = X509_get_ext_by_NID(x509, xt->nid, -1);
+ if (i >= 0)
+ {
+ X509_EXTENSION *ext = X509_get_ext(x509, i);
+ if (ext)
+ {
+ BIO *bio = BIO_new(BIO_s_mem());
+ if (bio)
+ {
+ if (X509V3_EXT_print(bio, ext, 0, 0))
+ {
+ if (BIO_write(bio, &nullc, 1) == 1)
+ {
+ char *str;
+ BIO_get_mem_data(bio, &str);
+ do_setenv_x509(es, xt->name, str, depth);
+ }
+ }
+ BIO_free(bio);
+ }
+ }
+ }
+ }
+ }
+ xt = xt->next;
+ }
+}
+#endif
+
+/*
+ * Save X509 fields to environment, using the naming convention:
+ *
+ * X509_{cert_depth}_{name}={value}
+ */
+void
+x509_setenv (struct env_set *es, int cert_depth, openvpn_x509_cert_t *peer_cert)
+{
+ int i, n;
+ int fn_nid;
+ ASN1_OBJECT *fn;
+ ASN1_STRING *val;
+ X509_NAME_ENTRY *ent;
+ const char *objbuf;
+ unsigned char *buf;
+ char *name_expand;
+ size_t name_expand_size;
+ X509_NAME *x509 = X509_get_subject_name (peer_cert);
+
+ n = X509_NAME_entry_count (x509);
+ for (i = 0; i < n; ++i)
+ {
+ ent = X509_NAME_get_entry (x509, i);
+ if (!ent)
+ continue;
+ fn = X509_NAME_ENTRY_get_object (ent);
+ if (!fn)
+ continue;
+ val = X509_NAME_ENTRY_get_data (ent);
+ if (!val)
+ continue;
+ fn_nid = OBJ_obj2nid (fn);
+ if (fn_nid == NID_undef)
+ continue;
+ objbuf = OBJ_nid2sn (fn_nid);
+ if (!objbuf)
+ continue;
+ buf = (unsigned char *)1; /* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
+ if (ASN1_STRING_to_UTF8 (&buf, val) <= 0)
+ continue;
+ name_expand_size = 64 + strlen (objbuf);
+ name_expand = (char *) malloc (name_expand_size);
+ check_malloc_return (name_expand);
+ openvpn_snprintf (name_expand, name_expand_size, "X509_%d_%s", cert_depth,
+ objbuf);
+ string_mod (name_expand, CC_PRINT, CC_CRLF, '_');
+ string_mod ((char*)buf, CC_PRINT, CC_CRLF, '_');
+ setenv_str (es, name_expand, (char*)buf);
+ free (name_expand);
+ OPENSSL_free (buf);
+ }
+}
+
+result_t
+x509_verify_ns_cert_type(const openvpn_x509_cert_t *peer_cert, const int usage)
+{
+ if (usage == NS_CERT_CHECK_NONE)
+ return SUCCESS;
+ if (usage == NS_CERT_CHECK_CLIENT)
+ return ((peer_cert->ex_flags & EXFLAG_NSCERT)
+ && (peer_cert->ex_nscert & NS_SSL_CLIENT)) ? SUCCESS: FAILURE;
+ if (usage == NS_CERT_CHECK_SERVER)
+ return ((peer_cert->ex_flags & EXFLAG_NSCERT)
+ && (peer_cert->ex_nscert & NS_SSL_SERVER)) ? SUCCESS: FAILURE;
+
+ return FAILURE;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+
+result_t
+x509_verify_cert_ku (X509 *x509, const unsigned * const expected_ku,
+ int expected_len)
+{
+ ASN1_BIT_STRING *ku = NULL;
+ result_t fFound = FAILURE;
+
+ if ((ku = (ASN1_BIT_STRING *) X509_get_ext_d2i (x509, NID_key_usage, NULL,
+ NULL)) == NULL)
+ {
+ msg (D_HANDSHAKE, "Certificate does not have key usage extension");
+ }
+ else
+ {
+ unsigned nku = 0;
+ int i;
+ for (i = 0; i < 8; i++)
+ {
+ if (ASN1_BIT_STRING_get_bit (ku, i))
+ nku |= 1 << (7 - i);
+ }
+
+ /*
+ * Fixup if no LSB bits
+ */
+ if ((nku & 0xff) == 0)
+ {
+ nku >>= 8;
+ }
+
+ msg (D_HANDSHAKE, "Validating certificate key usage");
+ for (i = 0; fFound != SUCCESS && i < expected_len; i++)
+ {
+ if (expected_ku[i] != 0)
+ {
+ msg (D_HANDSHAKE, "++ Certificate has key usage %04x, expects "
+ "%04x", nku, expected_ku[i]);
+
+ if (nku == expected_ku[i])
+ fFound = SUCCESS;
+ }
+ }
+ }
+
+ if (ku != NULL)
+ ASN1_BIT_STRING_free (ku);
+
+ return fFound;
+}
+
+result_t
+x509_verify_cert_eku (X509 *x509, const char * const expected_oid)
+{
+ EXTENDED_KEY_USAGE *eku = NULL;
+ result_t fFound = FAILURE;
+
+ if ((eku = (EXTENDED_KEY_USAGE *) X509_get_ext_d2i (x509, NID_ext_key_usage,
+ NULL, NULL)) == NULL)
+ {
+ msg (D_HANDSHAKE, "Certificate does not have extended key usage extension");
+ }
+ else
+ {
+ int i;
+
+ msg (D_HANDSHAKE, "Validating certificate extended key usage");
+ for (i = 0; SUCCESS != fFound && i < sk_ASN1_OBJECT_num (eku); i++)
+ {
+ ASN1_OBJECT *oid = sk_ASN1_OBJECT_value (eku, i);
+ char szOid[1024];
+
+ if (SUCCESS != fFound && OBJ_obj2txt (szOid, sizeof(szOid), oid, 0) != -1)
+ {
+ msg (D_HANDSHAKE, "++ Certificate has EKU (str) %s, expects %s",
+ szOid, expected_oid);
+ if (!strcmp (expected_oid, szOid))
+ fFound = SUCCESS;
+ }
+ if (SUCCESS != fFound && OBJ_obj2txt (szOid, sizeof(szOid), oid, 1) != -1)
+ {
+ msg (D_HANDSHAKE, "++ Certificate has EKU (oid) %s, expects %s",
+ szOid, expected_oid);
+ if (!strcmp (expected_oid, szOid))
+ fFound = SUCCESS;
+ }
+ }
+ }
+
+ if (eku != NULL)
+ sk_ASN1_OBJECT_pop_free (eku, ASN1_OBJECT_free);
+
+ return fFound;
+}
+
+result_t
+x509_write_pem(FILE *peercert_file, X509 *peercert)
+{
+ if (PEM_write_X509(peercert_file, peercert) < 0)
+ {
+ msg (M_ERR, "Failed to write peer certificate in PEM format");
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+
+#endif /* OPENSSL_VERSION_NUMBER */
+
+/*
+ * check peer cert against CRL
+ */
+result_t
+x509_verify_crl(const char *crl_file, X509 *peer_cert, const char *subject)
+{
+ X509_CRL *crl=NULL;
+ X509_REVOKED *revoked;
+ BIO *in=NULL;
+ int n,i;
+ result_t retval = FAILURE;
+
+ in = BIO_new_file (crl_file, "r");
+
+ if (in == NULL) {
+ msg (M_ERR, "CRL: cannot read: %s", crl_file);
+ goto end;
+ }
+ crl=PEM_read_bio_X509_CRL(in,NULL,NULL,NULL);
+ if (crl == NULL) {
+ msg (M_ERR, "CRL: cannot read CRL from file %s", crl_file);
+ goto end;
+ }
+
+ if (X509_NAME_cmp(X509_CRL_get_issuer(crl), X509_get_issuer_name(peer_cert)) != 0) {
+ msg (M_WARN, "CRL: CRL %s is from a different issuer than the issuer of "
+ "certificate %s", crl_file, subject);
+ retval = SUCCESS;
+ goto end;
+ }
+
+ n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
+ for (i = 0; i < n; i++) {
+ revoked = (X509_REVOKED *)sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
+ if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(peer_cert)) == 0) {
+ msg (D_HANDSHAKE, "CRL CHECK FAILED: %s is REVOKED",subject);
+ goto end;
+ }
+ }
+
+ retval = SUCCESS;
+ msg (D_HANDSHAKE, "CRL CHECK OK: %s",subject);
+
+end:
+ BIO_free(in);
+ if (crl)
+ X509_CRL_free (crl);
+
+ return retval;
+}
+
+#endif /* defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL) */