openldap/libraries/libldap/tls_mt.c

1011 lines
26 KiB
C
Raw Normal View History

/* tls_mt.c - Handle tls/ssl using MbedTLS */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2010-2023 Belledonne Communications SARL.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
*/
#include "portable.h"
#ifdef HAVE_MBEDTLS
#include "ldap_config.h"
#include <stdio.h>
#include <ac/stdlib.h>
#include <ac/errno.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/ctype.h>
#include "ldap-int.h"
#include "ldap-tls.h"
#include <mbedtls/ssl.h>
#include <mbedtls/oid.h>
#include <mbedtls/x509.h>
#include <mbedtls/version.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
typedef struct tlsmt_ctx {
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_config ssl_config;
mbedtls_x509_crt own_cert;
mbedtls_pk_context own_cert_key;
mbedtls_x509_crt ca_chain;
unsigned long verify_depth;
int refcount;
#ifdef LDAP_R_COMPILE
ldap_pvt_thread_mutex_t ref_mutex;
#endif
} tlsmt_ctx;
typedef struct tlsmt_session {
mbedtls_ssl_context ssl_ctx;
tlsmt_ctx *config;
} tlsmt_session;
#ifdef LDAP_R_COMPILE
static void tlsmt_thr_init( void )
{
}
#endif /* LDAP_R_COMPILE */
/*
* Initialize TLS subsystem. Should be called only once.
*/
static int
tlsmt_init( void )
{
return 0;
}
/*
* Tear down the TLS subsystem. Should only be called once.
*/
static void
tlsmt_destroy( void )
{
}
static tls_ctx *
tlsmt_ctx_new( struct ldapoptions *lo )
{
tlsmt_ctx *ctx;
ctx = ber_memcalloc ( 1, sizeof (*ctx) );
if ( ctx ) {
int ret = 0;
ctx->refcount = 1;
mbedtls_entropy_init( &ctx->entropy );
mbedtls_ctr_drbg_init( &ctx->ctr_drbg );
if( ( ret = mbedtls_ctr_drbg_seed( &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy, NULL, 0 ) ) != 0 )
{
mbedtls_ctr_drbg_free( &ctx->ctr_drbg );
mbedtls_entropy_free( &ctx->entropy );
ber_memfree ( ctx );
Debug1(LDAP_DEBUG_ANY, "Mbedtls can't init ctr_drbg: [-0x%x]. Unable to create tls context", -ret);
return NULL;
}
mbedtls_ssl_config_init( &ctx->ssl_config );
mbedtls_ssl_conf_rng( &ctx->ssl_config, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
mbedtls_x509_crt_init( &ctx->own_cert );
mbedtls_pk_init( &ctx->own_cert_key );
mbedtls_x509_crt_init( &ctx->ca_chain );
#ifdef LDAP_R_COMPILE
ldap_pvt_thread_mutex_init( &ctx->ref_mutex );
#endif
}
return (tls_ctx *)ctx;
}
static void
tlsmt_ctx_ref( tls_ctx *ctx )
{
tlsmt_ctx *c = (tlsmt_ctx *)ctx;
LDAP_MUTEX_LOCK( &c->ref_mutex );
c->refcount++;
LDAP_MUTEX_UNLOCK( &c->ref_mutex );
}
static void
tlsmt_ctx_free ( tls_ctx *ctx )
{
tlsmt_ctx *c = (tlsmt_ctx *)ctx;
int refcount;
if ( !c ) return;
LDAP_MUTEX_LOCK( &c->ref_mutex );
refcount = --c->refcount;
LDAP_MUTEX_UNLOCK( &c->ref_mutex );
if ( refcount )
return;
mbedtls_ssl_config_free( &c->ssl_config );
mbedtls_ctr_drbg_free( &c->ctr_drbg );
mbedtls_entropy_free( &c->entropy );
mbedtls_x509_crt_free( &c->own_cert );
mbedtls_pk_free( &c->own_cert_key );
mbedtls_x509_crt_free( &c->ca_chain );
ber_memfree ( c );
}
/*
* initialize a new TLS context
*/
static int
tlsmt_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server, char *errmsg )
{
tlsmt_ctx *ctx = (tlsmt_ctx *)lo->ldo_tls_ctx;
mbedtls_ssl_config *ssl_config = &ctx->ssl_config;
// Set all options for the connection
int ret = mbedtls_ssl_config_defaults(ssl_config, is_server?MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
// MBedtls v3 deprecated SSLv3, TLS1.0, TLS1.1
#if MBEDTLS_VERSION_NUMBER < 0x03000000
if ( lo->ldo_tls_protocol_min ) {
int minor = MBEDTLS_SSL_MINOR_VERSION_0; // SSLv3.0 shall be avoided
switch (lo->ldo_tls_protocol_min) {
case LDAP_OPT_X_TLS_PROTOCOL_SSL2: // SSL2 not supported, set min to SSLv3
case LDAP_OPT_X_TLS_PROTOCOL_SSL3:
minor = MBEDTLS_SSL_MINOR_VERSION_0;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_0:
minor = MBEDTLS_SSL_MINOR_VERSION_1;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_1:
minor = MBEDTLS_SSL_MINOR_VERSION_2;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_2:
default:
minor = MBEDTLS_SSL_MINOR_VERSION_3;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_3:
Debug0 ( LDAP_DEBUG_ANY, "MbedTLSv2 backend does not support TLSv1.3, keep minimum version to 1.2" );
minor = MBEDTLS_SSL_MINOR_VERSION_3;
break;
}
mbedtls_ssl_conf_min_version ( ssl_config, MBEDTLS_SSL_MAJOR_VERSION_3, minor );
}
if ( lo->ldo_tls_protocol_max ) {
int minor = MBEDTLS_SSL_MINOR_VERSION_3;
switch (lo->ldo_tls_protocol_max) {
case LDAP_OPT_X_TLS_PROTOCOL_SSL2: // SSL2 not supported, set min to SSLv3
case LDAP_OPT_X_TLS_PROTOCOL_SSL3:
minor = MBEDTLS_SSL_MINOR_VERSION_0;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_0:
minor = MBEDTLS_SSL_MINOR_VERSION_1;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_1:
minor = MBEDTLS_SSL_MINOR_VERSION_2;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_2:
default:
minor = MBEDTLS_SSL_MINOR_VERSION_3;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_3:
Debug0 ( LDAP_DEBUG_ANY, "MbedTLSv2 backend does not support TLSv1.3, keep maximum version to 1.2" );
minor = MBEDTLS_SSL_MINOR_VERSION_3;
break;
}
mbedtls_ssl_conf_max_version ( ssl_config, MBEDTLS_SSL_MAJOR_VERSION_3, minor );
}
#else /* MBEDTLS_VERSION_NUMBER < 0x03000000 : MBEDTLS version 3 and above: No SSLv3, TLSv1.0, TLSv1.1 */
if ( lo->ldo_tls_protocol_min ) {
mbedtls_ssl_protocol_version version = MBEDTLS_SSL_VERSION_TLS1_2; // TLSv1.2 is the lowest version available
switch (lo->ldo_tls_protocol_max) {
case LDAP_OPT_X_TLS_PROTOCOL_SSL2:
case LDAP_OPT_X_TLS_PROTOCOL_SSL3:
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_0:
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_1:
/* for all non supported version request, force TLSv1.2 */
Debug0 ( LDAP_DEBUG_ANY, "MbedTLSv3 backend does not support TLS version under 1.2, switch the minimum version requested to it" );
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_2:
default:
version = MBEDTLS_SSL_VERSION_TLS1_2;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_3:
version = MBEDTLS_SSL_VERSION_TLS1_3;
break;
}
mbedtls_ssl_conf_min_tls_version ( ssl_config, version );
}
if ( lo->ldo_tls_protocol_max ) {
mbedtls_ssl_protocol_version version = MBEDTLS_SSL_VERSION_TLS1_3;
switch (lo->ldo_tls_protocol_min) {
case LDAP_OPT_X_TLS_PROTOCOL_SSL2:
case LDAP_OPT_X_TLS_PROTOCOL_SSL3:
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_0:
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_1:
/* for all non supported version request, force TLSv1.2 */
Debug0 ( LDAP_DEBUG_ANY, "MbedTLSv3 backend does not support TLS version under 1.2, switch the maximum version requested to it" );
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_2:
default:
version = MBEDTLS_SSL_VERSION_TLS1_2;
break;
case LDAP_OPT_X_TLS_PROTOCOL_TLS1_3:
version = MBEDTLS_SSL_VERSION_TLS1_3;
break;
}
mbedtls_ssl_conf_max_tls_version ( ssl_config, version );
}
#endif /* MBEDTLS_VERSION_NUMBER < 0x03000000 */
if ( lo->ldo_tls_ciphersuite ) {
Debug1 (LDAP_DEBUG_ANY, "tlsmt_ctx_init Cipher suite selection is not supported by MbedTLS backend, ignore setting %s\n", lt->lt_ciphersuite);
}
if (lo->ldo_tls_cacertdir != NULL) {
char **dirs = ldap_str2charray( lt->lt_cacertdir, CERTPATHSEP );
int i;
for ( i=0; dirs[i]; i++ ) {
int ret = mbedtls_x509_crt_parse_path( &ctx->ca_chain, dirs[i] );
if ( ret < 0 ) {
Debug1( LDAP_DEBUG_ANY,
"TLS: warning: no certificate found in CA certificate directory `%s'.\n",
dirs[i] );
/* only warn, no return */
mbedtls_strerror( ret, errmsg, ERRBUFSIZE );
}
}
ldap_charray_free( dirs );
}
if (lo->ldo_tls_cacertfile != NULL) {
int ret = mbedtls_x509_crt_parse_file( &ctx->ca_chain, lt->lt_cacertfile );
if ( ret < 0 ) {
char errParseFile[ERRBUFSIZE];
mbedtls_strerror( ret, errParseFile, ERRBUFSIZE );
Debug3( LDAP_DEBUG_ANY,
"TLS: could not use CA certificate file `%s': %s (%d)\n",
lo->ldo_tls_cacertfile,
errParseFile,
ret );
return -1;
}
}
mbedtls_ssl_conf_ca_chain(ssl_config, &ctx->ca_chain, NULL); // CRL not supported
if (( lo->ldo_tls_certfile && lo->ldo_tls_keyfile ) ||
( lo->ldo_tls_cert.bv_val && lo->ldo_tls_key.bv_val )) {
#if MBEDTLS_VERSION_NUMBER < 0x03000000
if ( lo->ldo_tls_key.bv_val ) {
ret = mbedtls_pk_parse_key(&ctx->own_cert_key, (unsigned char *)lo->ldo_tls_key.bv_val, lo->ldo_tls_key.bv_len, NULL, 0);
} else {
ret = mbedtls_pk_parse_keyfile(&ctx->own_cert_key, lt->lt_keyfile, NULL);
}
#else /* MBEDTLS_VERSION_NUMBER < 0x03000000 */
if ( lo->ldo_tls_key.bv_val ) {
ret = mbedtls_pk_parse_key(&ctx->own_cert_key, (unsigned char *)lo->ldo_tls_key.bv_val, lo->ldo_tls_key.bv_len, NULL, 0, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
} else {
ret = mbedtls_pk_parse_keyfile(&ctx->own_cert_key, lt->lt_keyfile, NULL, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
}
#endif /* MBEDTLS_VERSION_NUMBER < 0x03000000 */
if (ret != 0) {
return -1;
}
if ( lo->ldo_tls_cert.bv_val ) {
ret = mbedtls_x509_crt_parse( &ctx->own_cert, (unsigned char *)lo->ldo_tls_cert.bv_val, lo->ldo_tls_cert.bv_len);
} else {
ret = mbedtls_x509_crt_parse_file( &ctx->own_cert, lt->lt_certfile);
}
if (ret != 0) {
return -1;
}
if ( (ret = mbedtls_ssl_conf_own_cert(ssl_config, &ctx->own_cert, &ctx->own_cert_key ) ) != 0) {
return -1;
}
}
switch ( lo->ldo_tls_require_cert ) {
case LDAP_OPT_X_TLS_NEVER :
mbedtls_ssl_conf_authmode( ssl_config, MBEDTLS_SSL_VERIFY_NONE );
break;
case LDAP_OPT_X_TLS_HARD:
case LDAP_OPT_X_TLS_DEMAND:
default:
mbedtls_ssl_conf_authmode( ssl_config, MBEDTLS_SSL_VERIFY_REQUIRED );
break;
case LDAP_OPT_X_TLS_ALLOW:
case LDAP_OPT_X_TLS_TRY:
mbedtls_ssl_conf_authmode( ssl_config, MBEDTLS_SSL_VERIFY_OPTIONAL );
break;
}
if ( is_server && lo->ldo_tls_dhfile ) {
Debug1 (LDAP_DEBUG_ANY, "tlsmt_ctx_init DH params from file is not supported by MbedTLS backend, ignore setting %s\n", lo->ldo_tls_dhfile);
}
return 0;
}
static tls_session *
tlsmt_session_new( tls_ctx *ctx, int is_server )
{
tlsmt_ctx *c = (tlsmt_ctx *)ctx;
tlsmt_session *session;
session = ber_memcalloc ( 1, sizeof (*session) );
if ( !session )
return NULL;
session->config = c;
mbedtls_ssl_init(&(session->ssl_ctx));
mbedtls_ssl_setup(&(session->ssl_ctx), &session->config->ssl_config);
return (tls_session *)session;
}
static int
tlsmt_session_accept( tls_session *sess )
{
tlsmt_session *s = (tlsmt_session *)sess;
return mbedtls_ssl_handshake( &(s->ssl_ctx) );
}
static int
tlsmt_session_connect( LDAP *ld, tls_session *sess, const char *name_in )
{
tlsmt_session *s = (tlsmt_session *)sess;
int ret = mbedtls_ssl_set_hostname( &(s->ssl_ctx), name_in );
if ( ret != 0 ) {
return ret;
}
return tlsmt_session_accept(sess);
}
static int
tlsmt_session_upflags( Sockbuf *sb, tls_session *sess, int rc )
{
if (rc == MBEDTLS_ERR_SSL_WANT_READ) {
sb->sb_trans_needs_read = 1;
return 1;
} else if (rc == MBEDTLS_ERR_SSL_WANT_WRITE) {
sb->sb_trans_needs_write = 1;
return 1;
}
return 0;
}
static char *
tlsmt_session_errmsg( tls_session *sess, int rc, char *buf, size_t len )
{
if ( rc ) {
mbedtls_strerror(rc, buf, len);
return buf;
}
return NULL;
}
static int
tlsmt_session_my_dn( tls_session *sess, struct berval *der_dn )
{
// Session cannot give us our own certificate but it is stored in the config context
tlsmt_session *s = (tlsmt_session *)sess;
der_dn->bv_len = s->config->own_cert.subject_raw.len;
der_dn->bv_val = s->config->own_cert.subject_raw.p;
return 0;
}
static int
tlsmt_session_peer_dn( tls_session *sess, struct berval *der_dn )
{
tlsmt_session *s = (tlsmt_session *)sess;
const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert( &s->ssl_ctx );
if ( peer_cert == NULL ) {
return LDAP_INVALID_CREDENTIALS;
}
der_dn->bv_len = peer_cert->subject_raw.len;
der_dn->bv_val = peer_cert->subject_raw.p;
return 0;
}
/* what kind of hostname were we given? */
#define IS_DNS 0
#define IS_IP4 1
#define IS_IP6 2
static int
tlsmt_session_chkhost( LDAP *ld, tls_session *sess, const char *name_in )
{
tlsmt_session *s = (tlsmt_session *)sess;
int i, ret = LDAP_LOCAL_ERROR;
int chkSAN = ld->ld_options.ldo_tls_require_san, gotSAN = 0;
const char *name;
char *ptr;
char *domain = NULL;
int len1 = 0, len2 = 0;
int ntype = IS_DNS, nlen;
#ifdef LDAP_PF_INET6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
if ( !ldap_int_hostname )
ldap_int_resolve_hostname();
if( ldap_int_hostname &&
( !name_in || !strcasecmp( name_in, "localhost" ) ) )
{
name = ldap_int_hostname;
} else {
name = name_in;
}
nlen = strlen(name);
const mbedtls_x509_crt* x = mbedtls_ssl_get_peer_cert( &s->ssl_ctx );
if (!x) {
Debug0( LDAP_DEBUG_ANY,
"TLS: unable to get peer certificate.\n" );
/* If this was a fatal condition, things would have
* aborted long before now.
*/
return LDAP_SUCCESS;
}
#ifdef LDAP_PF_INET6
if (inet_pton(AF_INET6, name, &addr)) {
ntype = IS_IP6;
} else
#endif
if ((ptr = strrchr(name, '.')) && isdigit((unsigned char)ptr[1])) {
if (inet_aton(name, (struct in_addr *)&addr)) ntype = IS_IP4;
}
if (ntype == IS_DNS) {
len1 = strlen(name);
domain = strchr(name, '.');
if (domain) {
len2 = len1 - (domain-name);
}
}
#if MBEDTLS_VERSION_NUMBER < 0x03000000
if ( chkSAN && ( ret != LDAP_SUCCESS ) && ( x->ext_types & MBEDTLS_X509_EXT_SUBJECT_ALT_NAME ) ) {
#else
if ( chkSAN && ( ret != LDAP_SUCCESS ) && ( mbedtls_x509_crt_has_ext_type(x, MBEDTLS_X509_EXT_SUBJECT_ALT_NAME ) != 0 ) ) {
#endif
mbedtls_x509_sequence *SANs = (mbedtls_x509_sequence *)&x->subject_alt_names;
while ( SANs != NULL && ret != LDAP_SUCCESS ) {
gotSAN = 1;
const mbedtls_x509_buf *san_buf = &SANs->buf;
/* mbedtls does not support SAN ip address type, so parse it here instead of using x509_crt_check_san */
switch ( san_buf->tag & ( MBEDTLS_ASN1_TAG_CLASS_MASK | MBEDTLS_ASN1_TAG_VALUE_MASK )) {
/* DNS type */
case (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_X509_SAN_DNS_NAME ) :
{
/* Is this an exact match? */
if ((len1 == san_buf->len) && !strncasecmp(name, san_buf->p, len1)) {
ret = LDAP_SUCCESS;
}
/* Is this a wildcard match? */
if (domain && (san_buf->p[0] == '*') && (san_buf->p[1] == '.') &&
(len2 == san_buf->len-1) && !strncasecmp(domain, (san_buf->p)+1, len2))
{
ret = LDAP_SUCCESS;
}
}
break;
/* IPADDRESS type */
case (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_X509_SAN_IP_ADDRESS ) :
{
if (
(ntype == IS_IP4 && san_buf->len == sizeof(struct in_addr))
#ifdef LDAP_PF_INET6
|| (ntype == IS_IP6 && san_buf->len == sizeof(struct in6_addr))
#endif
) {
if (!memcmp(san_buf->p, &addr, san_buf->len)) {
ret = LDAP_SUCCESS;
}
}
}
break;
default:
Debug0(LDAP_DEBUG_ANY, "Unsupported SAN type. Only DNS and IP ADDRESS are supported");
}
SANs = SANs->next;
}
}
if (ret != LDAP_SUCCESS && chkSAN) {
switch(chkSAN) {
case LDAP_OPT_X_TLS_DEMAND:
case LDAP_OPT_X_TLS_HARD:
if (!gotSAN) {
Debug0( LDAP_DEBUG_ANY,
"TLS: unable to get subjectAltName from peer certificate.\n" );
ret = LDAP_CONNECT_ERROR;
if ( ld->ld_error ) {
LDAP_FREE( ld->ld_error );
}
ld->ld_error = LDAP_STRDUP(
_("TLS: unable to get subjectAltName from peer certificate"));
goto done;
}
/* FALLTHRU */
case LDAP_OPT_X_TLS_TRY:
if (gotSAN) {
Debug1( LDAP_DEBUG_ANY, "TLS: hostname (%s) does not match "
"subjectAltName in certificate.\n",
name );
ret = LDAP_CONNECT_ERROR;
if ( ld->ld_error ) {
LDAP_FREE( ld->ld_error );
}
ld->ld_error = LDAP_STRDUP(
_("TLS: hostname does not match subjectAltName in peer certificate"));
goto done;
}
break;
case LDAP_OPT_X_TLS_ALLOW:
break;
}
}
if (ret != LDAP_SUCCESS) {
/* find the last CN */
const mbedtls_x509_name *subject;
for( subject = &x->subject; subject != NULL && ret != LDAP_SUCCESS; subject = subject->next ) {
if( MBEDTLS_OID_CMP( MBEDTLS_OID_AT_CN, &subject->oid ) == 0 ) {
const mbedtls_x509_buf *cn=&subject->val;
/* Is this an exact match? */
if ((len1 == cn->len) && !strncasecmp(name, cn->p, len1)) {
ret = LDAP_SUCCESS;
}
/* Is this a wildcard match? */
if (domain && (cn->p[0] == '*') && (cn->p[1] == '.') &&
(len2 == cn->len-1) && !strncasecmp(domain, (cn->p)+1, len2))
{
ret = LDAP_SUCCESS;
}
}
}
}
done:
return ret;
}
static int
tlsmt_session_strength( tls_session *sess )
{
tlsmt_session *s = (tlsmt_session *)sess;
#if MBEDTLS_VERSION_NUMBER < 0x03000000
const mbedtls_ssl_ciphersuite_t *currentCipherSuite = mbedtls_ssl_ciphersuite_from_string( mbedtls_ssl_get_ciphersuite( &s->ssl_ctx ) );
if (currentCipherSuite == NULL) return 0;
return ( ( mbedtls_cipher_info_from_type( currentCipherSuite->cipher )->key_bitlen ) );
#else /* MBEDTLS_VERSION_NUMBER < 0x03000000 */
const mbedtls_ssl_ciphersuite_t *currentCipherSuite = mbedtls_ssl_ciphersuite_from_id( mbedtls_ssl_get_ciphersuite_id_from_ssl( &s->ssl_ctx ) );
if (currentCipherSuite == NULL) return 0;
return ( ( mbedtls_ssl_ciphersuite_get_cipher_key_bitlen( currentCipherSuite ) ) );
#endif /* MBEDTLS_VERSION_NUMBER < 0x03000000 */
}
static int
tlsmt_session_unique( tls_session *sess, struct berval *buf, int is_server)
{
Debug0(LDAP_DEBUG_ANY, "tlsmt_session_unique channel binding using unique is not available with MbedTLS backend\n");
return 0;
}
static int
tlsmt_session_endpoint( tls_session *sess, struct berval *buf, int is_server )
{
tlsmt_session *s = (tlsmt_session *)sess;
const mbedtls_x509_crt* cert = NULL;
/* get server certificate */
if ( is_server ) {
cert = &s->config->own_cert;
} else {
cert = mbedtls_ssl_get_peer_cert( &s->ssl_ctx );
}
#if MBEDTLS_VERSION_NUMBER < 0x03000000
mbedtls_md_type_t mdt = cert->sig_md;
#else
mbedtls_md_type_t mdt;
mbedtls_pk_type_t pk;
mbedtls_oid_get_sig_alg(&(cert->sig_oid), &mdt, &pk);
#endif
/* RFC 5929 */
switch (mdt) {
case MBEDTLS_MD_NONE:
#if MBEDTLS_VERSION_NUMBER < 0x03000000
case MBEDTLS_MD_MD2:
case MBEDTLS_MD_MD4:
#endif
case MBEDTLS_MD_MD5:
case MBEDTLS_MD_SHA1:
mdt = MBEDTLS_MD_SHA256;
}
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(mdt);
int md_len = mbedtls_md_get_size(md);
if ( md_len > buf->bv_len) {
return 0;
}
if ( mbedtls_md( md, cert->raw.p, cert->raw.len, buf->bv_val ) != 0 ) {
return 0;
}
buf->bv_len = md_len;
return md_len;
}
static const char *
tlsmt_session_version( tls_session *sess )
{
tlsmt_session *s = (tlsmt_session *)sess;
return mbedtls_ssl_get_version( &s->ssl_ctx );
}
static const char *
tlsmt_session_cipher( tls_session *sess )
{
tlsmt_session *s = (tlsmt_session *)sess;
return mbedtls_ssl_get_ciphersuite( &s->ssl_ctx );
}
static int
tlsmt_session_peercert( tls_session *sess, struct berval *der )
{
tlsmt_session *s = (tlsmt_session *)sess;
const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert( &s->ssl_ctx );
if ( peer_cert == NULL ) {
return -1;
}
der->bv_len = peer_cert->raw.len;
der->bv_val = LDAP_MALLOC( der->bv_len );
if (!der->bv_val)
return -1;
memcpy(der->bv_val, peer_cert->raw.p, der->bv_len);
return 0;
}
static int
tlsmt_session_pinning( LDAP *ld, tls_session *sess, char *hashalg, struct berval *hash )
{
int ret;
tlsmt_session *s = (tlsmt_session *)sess;
const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert(&s->ssl_ctx);
if (peer_cert == NULL) {
return -1;
}
const mbedtls_md_info_t *mbedtls_hash;
if ( hashalg ) {
// mbedtls hash algo parser requires all uppercase characters in algo name
size_t hashalg_len = strlen(hashalg);
char *hashalgUpper = ber_memcalloc ( 1, hashalg_len + 1 );
for (int i=0; i<hashalg_len; i++) {
hashalgUpper[i] = toupper(hashalg[i]);
}
mbedtls_hash = mbedtls_md_info_from_string( hashalgUpper );
ber_memfree( hashalgUpper );
if ( mbedtls_hash == NULL ) {
Debug1( LDAP_DEBUG_ANY, "tlsmt_session_pinning: "
"unknown hashing algorithm for MbedTLS: '%s'\n",
hashalg );
return -1;
}
}
// Extract certificate pk in DER format
const mbedtls_pk_context *pk = &peer_cert->pk;
size_t pk_size = mbedtls_pk_get_len( pk );
unsigned char *der_pk = ber_memcalloc ( 1, 2*pk_size );
#if MBEDTLS_VERSION_NUMBER < 0x03000000
int der_pk_len = mbedtls_pk_write_pubkey_der( (mbedtls_pk_context *)pk, der_pk, pk_size*2 );
#else
int der_pk_len = mbedtls_pk_write_pubkey_der( pk, der_pk, pk_size*2 );
#endif
unsigned char *digest[MBEDTLS_MD_MAX_SIZE];
struct berval keyhash;
if ( hashalg ) {
keyhash.bv_len = mbedtls_md_get_size(mbedtls_hash);
keyhash.bv_val = (char *)digest;
mbedtls_md(mbedtls_hash, der_pk+2*pk_size-der_pk_len, der_pk_len, keyhash.bv_val );
} else {
keyhash.bv_len = der_pk_len;
keyhash.bv_val = der_pk+2*pk_size-der_pk_len;
}
ber_memfree(der_pk);
if ( ber_bvcmp( hash, &keyhash ) ) {
ret = LDAP_CONNECT_ERROR;
Debug0( LDAP_DEBUG_ANY, "tlsmt_session_pinning: "
"public key hash does not match provided pin.\n" );
if ( ld->ld_error ) {
LDAP_FREE( ld->ld_error );
}
ld->ld_error = LDAP_STRDUP(
_("TLS: public key hash does not match provided pin"));
} else {
ret = LDAP_SUCCESS;
}
return ret;
}
/*
* TLS support for LBER Sockbufs
*/
struct tls_data {
tlsmt_session *session;
Sockbuf_IO_Desc *sbiod;
};
static int
tlsmt_read ( void *ptr, unsigned char *buf, size_t len )
{
struct tls_data *p;
if ( buf == NULL || len <= 0 ) return 0;
p = (struct tls_data *)ptr;
if ( p == NULL || p->sbiod == NULL ) {
return 0;
}
int ret = LBER_SBIOD_READ_NEXT( p->sbiod, buf, len );
if ( ret < 0 ) {
int err = sock_errno();
if ( err == EAGAIN || err == EWOULDBLOCK ) {
return MBEDTLS_ERR_SSL_WANT_READ;
}
}
return ret;
}
static int
tlsmt_write( void *ptr, const unsigned char *buf, size_t len )
{
struct tls_data *p;
if ( buf == NULL || len <= 0 ) return 0;
p = (struct tls_data *)ptr;
if ( p == NULL || p->sbiod == NULL ) {
return 0;
}
int ret = LBER_SBIOD_WRITE_NEXT( p->sbiod, (char *)buf, len );
if ( ret < 0 ) {
int err = sock_errno();
if ( err == EAGAIN || err == EWOULDBLOCK ) {
return MBEDTLS_ERR_SSL_WANT_WRITE;
}
}
return ret;
}
static int
tlsmt_sb_setup( Sockbuf_IO_Desc *sbiod, void *arg )
{
struct tls_data *p;
tlsmt_session *session = (tlsmt_session *) arg;
assert( sbiod != NULL );
p = LBER_MALLOC( sizeof( *p ) );
if ( p == NULL ) {
return -1;
}
mbedtls_ssl_set_bio(&(session->ssl_ctx), p, tlsmt_write, tlsmt_read, NULL);
p->session = session;
p->sbiod = sbiod;
sbiod->sbiod_pvt = p;
return 0;
}
static int
tlsmt_sb_remove( Sockbuf_IO_Desc *sbiod )
{
struct tls_data *p;
assert( sbiod != NULL );
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
mbedtls_ssl_free( &p->session->ssl_ctx );
LBER_FREE( p->session );
LBER_FREE( sbiod->sbiod_pvt );
sbiod->sbiod_pvt = NULL;
return 0;
}
static int
tlsmt_sb_close( Sockbuf_IO_Desc *sbiod )
{
struct tls_data *p;
assert( sbiod != NULL );
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
int ret = MBEDTLS_ERR_SSL_WANT_WRITE;
do { ret = mbedtls_ssl_close_notify( &(p->session->ssl_ctx) ); }
while (ret == MBEDTLS_ERR_SSL_WANT_WRITE);
return 0;
}
static int
tlsmt_sb_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
{
struct tls_data *p;
assert( sbiod != NULL );
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
if ( opt == LBER_SB_OPT_GET_SSL ) {
*((tlsmt_session **)arg) = p->session;
return 1;
} else if ( opt == LBER_SB_OPT_DATA_READY ) {
return mbedtls_ssl_check_pending( &(p->session->ssl_ctx) );
}
return LBER_SBIOD_CTRL_NEXT( sbiod, opt, arg );
}
static ber_slen_t
tlsmt_sb_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
{
struct tls_data *p;
assert( sbiod != NULL );
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
int ret = mbedtls_ssl_read( &(p->session->ssl_ctx), buf, len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) {
sbiod->sbiod_sb->sb_trans_needs_read = 1;
sock_errset(EWOULDBLOCK);
return 0;
}
else {
sbiod->sbiod_sb->sb_trans_needs_read = 0;
}
return ret;
}
static ber_slen_t
tlsmt_sb_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
{
struct tls_data *p;
assert( sbiod != NULL );
assert( sbiod->sbiod_pvt != NULL );
p = (struct tls_data *)sbiod->sbiod_pvt;
int ret = mbedtls_ssl_write( &(p->session->ssl_ctx), buf, len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) {
sbiod->sbiod_sb->sb_trans_needs_write = 1;
sock_errset(EWOULDBLOCK);
return 0;
}
else {
sbiod->sbiod_sb->sb_trans_needs_write = 0;
}
return ret;
}
static Sockbuf_IO tlsmt_sbio =
{
tlsmt_sb_setup, /* sbi_setup */
tlsmt_sb_remove, /* sbi_remove */
tlsmt_sb_ctrl, /* sbi_ctrl */
tlsmt_sb_read, /* sbi_read */
tlsmt_sb_write, /* sbi_write */
tlsmt_sb_close /* sbi_close */
};
tls_impl ldap_int_tls_impl = {
"MbedTLS",
tlsmt_init,
tlsmt_destroy,
tlsmt_ctx_new,
tlsmt_ctx_ref,
tlsmt_ctx_free,
tlsmt_ctx_init,
tlsmt_session_new,
tlsmt_session_connect,
tlsmt_session_accept,
tlsmt_session_upflags,
tlsmt_session_errmsg,
tlsmt_session_my_dn,
tlsmt_session_peer_dn,
tlsmt_session_chkhost,
tlsmt_session_strength,
tlsmt_session_unique,
tlsmt_session_endpoint,
tlsmt_session_version,
tlsmt_session_cipher,
tlsmt_session_peercert,
tlsmt_session_pinning,
&tlsmt_sbio,
#ifdef LDAP_R_COMPILE
tlsmt_thr_init,
#else
NULL,
#endif
0
};
#endif /* HAVE_MBEDTLS */