src/openvpn/ssl_verify_openssl.c
9a160b79
 /*
  *  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
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
31ea2ee4
 #include "syshead.h"
 
9b33b5a4
 #if defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL)
31ea2ee4
 
9a160b79
 #include "ssl_verify.h"
 #include "ssl_verify_backend.h"
 #include "ssl_openssl.h"
 #include <openssl/x509v3.h>
be960aad
 #include <openssl/err.h>
0a67e462
 
 int
 verify_callback (int preverify_ok, X509_STORE_CTX * ctx)
 {
   struct tls_session *session;
   SSL *ssl;
fceecbab
   unsigned char *sha1_hash = NULL;
0a67e462
 
   /* 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);
 
fceecbab
   sha1_hash = x509_get_sha1_hash(ctx->current_cert);
   cert_hash_remember (session, ctx->error_depth, sha1_hash);
   x509_free_sha1_hash(sha1_hash);
0a67e462
 
   /* did peer present cert which was signed by our root cert? */
   if (!preverify_ok)
     {
       /* get the X509 name */
58ddb7b8
       char *subject = x509_get_subject(ctx->current_cert);
0a67e462
 
       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);
fceecbab
 	  x509_free_subject(subject);
0a67e462
 	}
 
       ERR_clear_error();
 
       session->verified = false;
 
4ce976fb
       return 0;
0a67e462
     }
 
4ce976fb
   if (SUCCESS == verify_cert(session, ctx->current_cert, ctx->error_depth))
     return 1;
   return 0;
0a67e462
 }
971790da
 
dd4cdb9e
 #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).
  */
8a840d83
 static result_t
dd4cdb9e
 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)
8a840d83
     return FAILURE;
dd4cdb9e
 
   x509ne = X509_NAME_get_entry(x509, lastpos);
   if (!x509ne)
8a840d83
     return FAILURE;
dd4cdb9e
 
   asn1 = X509_NAME_ENTRY_get_data(x509ne);
   if (!asn1)
8a840d83
     return FAILURE;
dd4cdb9e
   tmp = ASN1_STRING_to_UTF8(&buf, asn1);
   if (tmp <= 0)
8a840d83
     return FAILURE;
dd4cdb9e
 
   strncpynt(out, (char *)buf, size);
 
   {
8a840d83
     const result_t ret = (strlen ((char *)buf) < size) ? SUCCESS: FAILURE;
dd4cdb9e
     OPENSSL_free (buf);
     return ret;
   }
 }
 
8a840d83
 result_t
bb53a20a
 x509_get_username (char *common_name, int cn_len,
dd4cdb9e
     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))
8a840d83
 	return FAILURE;
dd4cdb9e
     } else
 #endif
8a840d83
   if (FAILURE == extract_x509_field_ssl (X509_get_subject_name (peer_cert),
dd4cdb9e
       x509_username_field, common_name, cn_len))
8a840d83
       return FAILURE;
dd4cdb9e
 
8a840d83
   return SUCCESS;
dd4cdb9e
 }
fe100528
 
 char *
9b33b5a4
 x509_get_serial (openvpn_x509_cert_t *cert)
fe100528
 {
   ASN1_INTEGER *asn1_i;
   BIGNUM *bignum;
   char *serial;
 
   asn1_i = X509_get_serialNumber(cert);
   bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
   serial = BN_bn2dec(bignum);
 
   BN_free(bignum);
   return serial;
 }
 
 void
bb53a20a
 x509_free_serial (char *serial)
fe100528
 {
   if (serial)
     OPENSSL_free(serial);
 }
 
fceecbab
 unsigned char *
 x509_get_sha1_hash (X509 *cert)
 {
   char *hash = malloc(SHA_DIGEST_LENGTH);
   memcpy(hash, cert->sha1_hash, SHA_DIGEST_LENGTH);
f25d29c9
   return hash;
fceecbab
 }
 
 void
 x509_free_sha1_hash (unsigned char *hash)
 {
   if (hash)
     free(hash);
 }
 
3cb348e4
 char *
5e86fd93
 _openssl_get_subject (X509 *cert, char *buf, int size)
 {
1951b415
   BIO *subject_bio = NULL;
5e86fd93
   BUF_MEM *subject_mem;
   char *subject = buf;
   int maxlen = size;
 
   subject_bio = BIO_new (BIO_s_mem ());
   if (subject_bio == NULL)
1951b415
     goto err;
5e86fd93
 
   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))
1951b415
     goto err;
5e86fd93
 
   BIO_get_mem_ptr (subject_bio, &subject_mem);
   if (subject == NULL)
     {
       maxlen = subject_mem->length + 1;
       subject = malloc (maxlen);
       check_malloc_return (subject);
     }
 
   memcpy (subject, subject_mem->data, maxlen);
   subject[maxlen - 1] = '\0';
 
1951b415
 err:
   if (subject_bio)
     BIO_free (subject_bio);
 
5e86fd93
   return subject;
 }
 
 char *
bb53a20a
 x509_get_subject (X509 *cert)
3cb348e4
 {
5e86fd93
   return _openssl_get_subject (cert, NULL, 0);
3cb348e4
 }
 
 void
bb53a20a
 x509_free_subject (char *subject)
3cb348e4
 {
   if (subject)
5e86fd93
     free(subject);
3cb348e4
 }
 
 
fe100528
 #ifdef ENABLE_X509_TRACK
72533628
 
 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);
 }
fe100528
 
 /* 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
bb53a20a
 x509_setenv_track (const struct x509_track *xt, struct env_set *es, const int depth, X509 *x509)
fe100528
 {
   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
9b33b5a4
 x509_setenv (struct env_set *es, int cert_depth, openvpn_x509_cert_t *peer_cert)
fe100528
 {
   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);
     }
 }
06d22777
 
8a840d83
 result_t
9b33b5a4
 x509_verify_ns_cert_type(const openvpn_x509_cert_t *peer_cert, const int usage)
06d22777
 {
   if (usage == NS_CERT_CHECK_NONE)
8a840d83
     return SUCCESS;
06d22777
   if (usage == NS_CERT_CHECK_CLIENT)
     return ((peer_cert->ex_flags & EXFLAG_NSCERT)
8a840d83
 	&& (peer_cert->ex_nscert & NS_SSL_CLIENT)) ? SUCCESS: FAILURE;
06d22777
   if (usage == NS_CERT_CHECK_SERVER)
     return ((peer_cert->ex_flags & EXFLAG_NSCERT)
8a840d83
 	&& (peer_cert->ex_nscert & NS_SSL_SERVER))  ? SUCCESS: FAILURE;
06d22777
 
8a840d83
   return FAILURE;
06d22777
 }
876752ae
 
 #if OPENSSL_VERSION_NUMBER >= 0x00907000L
 
8a840d83
 result_t
bb53a20a
 x509_verify_cert_ku (X509 *x509, const unsigned * const expected_ku,
876752ae
     int expected_len)
 {
   ASN1_BIT_STRING *ku = NULL;
8a840d83
   result_t fFound = FAILURE;
876752ae
 
   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");
8a840d83
       for (i = 0; fFound != SUCCESS && i < expected_len; i++)
876752ae
 	{
 	  if (expected_ku[i] != 0)
 	    {
 	      msg (D_HANDSHAKE, "++ Certificate has key usage  %04x, expects "
 		  "%04x", nku, expected_ku[i]);
 
 	      if (nku == expected_ku[i])
8a840d83
 		fFound = SUCCESS;
876752ae
 	    }
 	}
     }
 
   if (ku != NULL)
     ASN1_BIT_STRING_free (ku);
 
   return fFound;
 }
 
8a840d83
 result_t
bb53a20a
 x509_verify_cert_eku (X509 *x509, const char * const expected_oid)
587f419b
 {
   EXTENDED_KEY_USAGE *eku = NULL;
8a840d83
   result_t fFound = FAILURE;
587f419b
 
   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");
8a840d83
       for (i = 0; SUCCESS != fFound && i < sk_ASN1_OBJECT_num (eku); i++)
587f419b
 	{
 	  ASN1_OBJECT *oid = sk_ASN1_OBJECT_value (eku, i);
 	  char szOid[1024];
 
8a840d83
 	  if (SUCCESS != fFound && OBJ_obj2txt (szOid, sizeof(szOid), oid, 0) != -1)
587f419b
 	    {
 	      msg (D_HANDSHAKE, "++ Certificate has EKU (str) %s, expects %s",
 		  szOid, expected_oid);
 	      if (!strcmp (expected_oid, szOid))
8a840d83
 		fFound = SUCCESS;
587f419b
 	    }
8a840d83
 	  if (SUCCESS != fFound && OBJ_obj2txt (szOid, sizeof(szOid), oid, 1) != -1)
587f419b
 	    {
 	      msg (D_HANDSHAKE, "++ Certificate has EKU (oid) %s, expects %s",
 		  szOid, expected_oid);
 	      if (!strcmp (expected_oid, szOid))
8a840d83
 		fFound = SUCCESS;
587f419b
 	    }
 	}
     }
 
   if (eku != NULL)
     sk_ASN1_OBJECT_pop_free (eku, ASN1_OBJECT_free);
 
   return fFound;
 }
 
8a840d83
 result_t
8bb72fbc
 x509_write_pem(FILE *peercert_file, X509 *peercert)
3e44ea55
 {
8bb72fbc
   if (PEM_write_X509(peercert_file, peercert) < 0)
3e44ea55
     {
       msg (M_ERR, "Failed to write peer certificate in PEM format");
8a840d83
       return FAILURE;
3e44ea55
     }
8a840d83
   return SUCCESS;
3e44ea55
 }
 
876752ae
 #endif /* OPENSSL_VERSION_NUMBER */
3e44ea55
 
83c49a3e
 /*
  * check peer cert against CRL
  */
8a840d83
 result_t
bb53a20a
 x509_verify_crl(const char *crl_file, X509 *peer_cert, const char *subject)
83c49a3e
 {
   X509_CRL *crl=NULL;
   X509_REVOKED *revoked;
   BIO *in=NULL;
8a840d83
   int n,i;
   result_t retval = FAILURE;
83c49a3e
 
71bbbd76
   in = BIO_new_file (crl_file, "r");
83c49a3e
 
   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);
8a840d83
     retval = SUCCESS;
83c49a3e
     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;
     }
   }
 
8a840d83
   retval = SUCCESS;
83c49a3e
   msg (D_HANDSHAKE, "CRL CHECK OK: %s",subject);
 
 end:
   BIO_free(in);
   if (crl)
     X509_CRL_free (crl);
 
8a840d83
   return retval;
83c49a3e
 }
31ea2ee4
 
9b33b5a4
 #endif /* defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL) */