blob: c767178a5555d0f0544705b8ea806acb11556795 [file] [log] [blame]
/*
* 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-2021 OpenVPN Inc <sales@openvpn.net>
* Copyright (C) 2010-2021 Fox Crypto B.V. <openvpn@foxcrypto.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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* @file Control Channel Verification Module mbed TLS backend
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#if defined(ENABLE_CRYPTO_MBEDTLS)
#include "crypto_mbedtls.h"
#include "ssl_verify.h"
#include <mbedtls/asn1.h>
#include <mbedtls/error.h>
#include <mbedtls/bignum.h>
#include <mbedtls/oid.h>
#include <mbedtls/sha1.h>
#define MAX_SUBJECT_LENGTH 256
int
verify_callback(void *session_obj, mbedtls_x509_crt *cert, int cert_depth,
uint32_t *flags)
{
struct tls_session *session = (struct tls_session *) session_obj;
struct gc_arena gc = gc_new();
ASSERT(cert);
ASSERT(session);
session->verified = false;
/* Remember certificate hash */
struct buffer cert_fingerprint = x509_get_sha256_fingerprint(cert, &gc);
cert_hash_remember(session, cert_depth, &cert_fingerprint);
/* did peer present cert which was signed by our root cert? */
if (*flags != 0)
{
int ret = 0;
char errstr[512] = { 0 };
char *subject = x509_get_subject(cert, &gc);
char *serial = backend_x509_get_serial(cert, &gc);
ret = mbedtls_x509_crt_verify_info(errstr, sizeof(errstr)-1, "", *flags);
if (ret <= 0 && !openvpn_snprintf(errstr, sizeof(errstr),
"Could not retrieve error string, flags=%" PRIx32, *flags))
{
errstr[0] = '\0';
}
else
{
chomp(errstr);
}
if (subject)
{
msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, subject=%s, serial=%s: %s",
cert_depth, subject, serial ? serial : "<not available>", errstr);
}
else
{
msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, (could not extract X509 "
"subject string from certificate): %s", cert_depth, errstr);
}
/* Leave flags set to non-zero to indicate that the cert is not ok */
}
else if (SUCCESS != verify_cert(session, cert, cert_depth))
{
*flags |= MBEDTLS_X509_BADCERT_OTHER;
}
gc_free(&gc);
/*
* PolarSSL/mbed TLS-1.2.0+ expects 0 on anything except fatal errors.
*/
return 0;
}
#ifdef ENABLE_X509ALTUSERNAME
#warning "X509 alt user name not yet supported for mbed TLS"
#endif
result_t
backend_x509_get_username(char *cn, int cn_len,
char *x509_username_field, mbedtls_x509_crt *cert)
{
mbedtls_x509_name *name;
ASSERT( cn != NULL );
name = &cert->subject;
/* Find common name */
while (name != NULL)
{
if (0 == memcmp(name->oid.p, MBEDTLS_OID_AT_CN,
MBEDTLS_OID_SIZE(MBEDTLS_OID_AT_CN)))
{
break;
}
name = name->next;
}
/* Not found, return an error if this is the peer's certificate */
if (name == NULL)
{
return FAILURE;
}
/* Found, extract CN */
if (cn_len > name->val.len)
{
memcpy( cn, name->val.p, name->val.len );
cn[name->val.len] = '\0';
}
else
{
memcpy( cn, name->val.p, cn_len);
cn[cn_len-1] = '\0';
}
return SUCCESS;
}
char *
backend_x509_get_serial(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
char *buf = NULL;
size_t buflen = 0;
mbedtls_mpi serial_mpi = { 0 };
/* Transform asn1 integer serial into mbed TLS MPI */
mbedtls_mpi_init(&serial_mpi);
if (!mbed_ok(mbedtls_mpi_read_binary(&serial_mpi, cert->serial.p,
cert->serial.len)))
{
msg(M_WARN, "Failed to retrieve serial from certificate.");
goto end;
}
/* Determine decimal representation length, allocate buffer */
mbedtls_mpi_write_string(&serial_mpi, 10, NULL, 0, &buflen);
buf = gc_malloc(buflen, true, gc);
/* Write MPI serial as decimal string into buffer */
if (!mbed_ok(mbedtls_mpi_write_string(&serial_mpi, 10, buf, buflen, &buflen)))
{
msg(M_WARN, "Failed to write serial to string.");
buf = NULL;
goto end;
}
end:
mbedtls_mpi_free(&serial_mpi);
return buf;
}
char *
backend_x509_get_serial_hex(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
char *buf = NULL;
size_t len = cert->serial.len * 3 + 1;
buf = gc_malloc(len, true, gc);
if (mbedtls_x509_serial_gets(buf, len-1, &cert->serial) < 0)
{
buf = NULL;
}
return buf;
}
static struct buffer
x509_get_fingerprint(const mbedtls_md_info_t *md_info, mbedtls_x509_crt *cert,
struct gc_arena *gc)
{
const size_t md_size = mbedtls_md_get_size(md_info);
struct buffer fingerprint = alloc_buf_gc(md_size, gc);
mbedtls_md(md_info, cert->raw.p, cert->raw.len, BPTR(&fingerprint));
ASSERT(buf_inc_len(&fingerprint, md_size));
return fingerprint;
}
struct buffer
x509_get_sha1_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1),
cert, gc);
}
struct buffer
x509_get_sha256_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
cert, gc);
}
char *
x509_get_subject(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
char tmp_subject[MAX_SUBJECT_LENGTH] = {0};
char *subject = NULL;
int ret = 0;
ret = mbedtls_x509_dn_gets( tmp_subject, MAX_SUBJECT_LENGTH-1, &cert->subject );
if (ret > 0)
{
/* Allocate the required space for the subject */
subject = string_alloc(tmp_subject, gc);
}
return subject;
}
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);
}
static char *
asn1_buf_to_c_string(const mbedtls_asn1_buf *orig, struct gc_arena *gc)
{
size_t i;
char *val;
if (!(orig->tag == MBEDTLS_ASN1_UTF8_STRING
|| orig->tag == MBEDTLS_ASN1_PRINTABLE_STRING
|| orig->tag == MBEDTLS_ASN1_IA5_STRING))
{
/* Only support C-string compatible types */
return string_alloc("ERROR: unsupported ASN.1 string type", gc);
}
for (i = 0; i < orig->len; ++i)
{
if (orig->p[i] == '\0')
{
return string_alloc("ERROR: embedded null value", gc);
}
}
val = gc_malloc(orig->len+1, false, gc);
memcpy(val, orig->p, orig->len);
val[orig->len] = '\0';
return val;
}
static void
do_setenv_name(struct env_set *es, const struct x509_track *xt,
const mbedtls_x509_crt *cert, int depth, struct gc_arena *gc)
{
const mbedtls_x509_name *xn;
for (xn = &cert->subject; xn != NULL; xn = xn->next)
{
const char *xn_short_name = NULL;
if (0 == mbedtls_oid_get_attr_short_name(&xn->oid, &xn_short_name)
&& 0 == strcmp(xt->name, xn_short_name))
{
char *val_str = asn1_buf_to_c_string(&xn->val, gc);
do_setenv_x509(es, xt->name, val_str, depth);
}
}
}
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->next = *ll_head;
*ll_head = xt;
}
void
x509_setenv_track(const struct x509_track *xt, struct env_set *es,
const int depth, mbedtls_x509_crt *cert)
{
struct gc_arena gc = gc_new();
while (xt)
{
if (depth == 0 || (xt->flags & XT_FULL_CHAIN))
{
if (0 == strcmp(xt->name, "SHA1") || 0 == strcmp(xt->name, "SHA256"))
{
/* Fingerprint is not part of X509 structure */
struct buffer cert_hash;
char *fingerprint;
if (0 == strcmp(xt->name, "SHA1"))
{
cert_hash = x509_get_sha1_fingerprint(cert, &gc);
}
else
{
cert_hash = x509_get_sha256_fingerprint(cert, &gc);
}
fingerprint = format_hex_ex(BPTR(&cert_hash),
BLEN(&cert_hash), 0, 1 | FHE_CAPS, ":", &gc);
do_setenv_x509(es, xt->name, fingerprint, depth);
}
else
{
do_setenv_name(es, xt, cert, depth, &gc);
}
}
xt = xt->next;
}
gc_free(&gc);
}
/*
* Save X509 fields to environment, using the naming convention:
*
* X509_{cert_depth}_{name}={value}
*/
void
x509_setenv(struct env_set *es, int cert_depth, mbedtls_x509_crt *cert)
{
int i;
unsigned char c;
const mbedtls_x509_name *name;
char s[128] = { 0 };
name = &cert->subject;
while (name != NULL)
{
char name_expand[64+8];
const char *shortname;
if (0 == mbedtls_oid_get_attr_short_name(&name->oid, &shortname) )
{
openvpn_snprintf(name_expand, sizeof(name_expand), "X509_%d_%s",
cert_depth, shortname);
}
else
{
openvpn_snprintf(name_expand, sizeof(name_expand), "X509_%d_\?\?",
cert_depth);
}
for (i = 0; i < name->val.len; i++)
{
if (i >= (int) sizeof( s ) - 1)
{
break;
}
c = name->val.p[i];
if (c < 32 || c == 127 || ( c > 128 && c < 160 ) )
{
s[i] = '?';
}
else
{
s[i] = c;
}
}
s[i] = '\0';
/* Check both strings, set environment variable */
string_mod(name_expand, CC_PRINT, CC_CRLF, '_');
string_mod((char *)s, CC_PRINT, CC_CRLF, '_');
setenv_str_incr(es, name_expand, (char *)s);
name = name->next;
}
}
result_t
x509_verify_ns_cert_type(mbedtls_x509_crt *cert, const int usage)
{
if (usage == NS_CERT_CHECK_NONE)
{
return SUCCESS;
}
if (usage == NS_CERT_CHECK_CLIENT)
{
return ((cert->ext_types & MBEDTLS_X509_EXT_NS_CERT_TYPE)
&& (cert->ns_cert_type & MBEDTLS_X509_NS_CERT_TYPE_SSL_CLIENT)) ?
SUCCESS : FAILURE;
}
if (usage == NS_CERT_CHECK_SERVER)
{
return ((cert->ext_types & MBEDTLS_X509_EXT_NS_CERT_TYPE)
&& (cert->ns_cert_type & MBEDTLS_X509_NS_CERT_TYPE_SSL_SERVER)) ?
SUCCESS : FAILURE;
}
return FAILURE;
}
result_t
x509_verify_cert_ku(mbedtls_x509_crt *cert, const unsigned *const expected_ku,
int expected_len)
{
msg(D_HANDSHAKE, "Validating certificate key usage");
if (!(cert->ext_types & MBEDTLS_X509_EXT_KEY_USAGE))
{
msg(D_TLS_ERRORS,
"ERROR: Certificate does not have key usage extension");
return FAILURE;
}
if (expected_ku[0] == OPENVPN_KU_REQUIRED)
{
/* Extension required, value checked by TLS library */
return SUCCESS;
}
result_t fFound = FAILURE;
for (size_t i = 0; SUCCESS != fFound && i<expected_len; i++)
{
if (expected_ku[i] != 0
&& 0 == mbedtls_x509_crt_check_key_usage(cert, expected_ku[i]))
{
fFound = SUCCESS;
}
}
if (fFound != SUCCESS)
{
msg(D_TLS_ERRORS,
"ERROR: Certificate has key usage %04x, expected one of:",
cert->key_usage);
for (size_t i = 0; i < expected_len && expected_ku[i]; i++)
{
msg(D_TLS_ERRORS, " * %04x", expected_ku[i]);
}
}
return fFound;
}
result_t
x509_verify_cert_eku(mbedtls_x509_crt *cert, const char *const expected_oid)
{
result_t fFound = FAILURE;
if (!(cert->ext_types & MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE))
{
msg(D_HANDSHAKE, "Certificate does not have extended key usage extension");
}
else
{
mbedtls_x509_sequence *oid_seq = &(cert->ext_key_usage);
msg(D_HANDSHAKE, "Validating certificate extended key usage");
while (oid_seq != NULL)
{
mbedtls_x509_buf *oid = &oid_seq->buf;
char oid_num_str[1024];
const char *oid_str;
if (0 == mbedtls_oid_get_extended_key_usage( oid, &oid_str ))
{
msg(D_HANDSHAKE, "++ Certificate has EKU (str) %s, expects %s",
oid_str, expected_oid);
if (!strcmp(expected_oid, oid_str))
{
fFound = SUCCESS;
break;
}
}
if (0 < mbedtls_oid_get_numeric_string( oid_num_str,
sizeof(oid_num_str), oid))
{
msg(D_HANDSHAKE, "++ Certificate has EKU (oid) %s, expects %s",
oid_num_str, expected_oid);
if (!strcmp(expected_oid, oid_num_str))
{
fFound = SUCCESS;
break;
}
}
oid_seq = oid_seq->next;
}
}
return fFound;
}
result_t
x509_write_pem(FILE *peercert_file, mbedtls_x509_crt *peercert)
{
msg(M_WARN, "mbed TLS does not support writing peer certificate in PEM format");
return FAILURE;
}
bool
tls_verify_crl_missing(const struct tls_options *opt)
{
if (opt->crl_file && !(opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
&& (opt->ssl_ctx.crl == NULL || opt->ssl_ctx.crl->version == 0))
{
return true;
}
return false;
}
#endif /* #if defined(ENABLE_CRYPTO_MBEDTLS) */