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) */ |