--- /dev/null
+/* 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;
+
+ int ret;
+ do {
+ ret = mbedtls_ssl_handshake( &(s->ssl_ctx) );
+ } while (ret!=0 && (ret== MBEDTLS_ERR_SSL_WANT_READ || ret==MBEDTLS_ERR_SSL_WANT_WRITE));
+
+ return ret;
+}
+
+static int
+tlsmt_session_connect( LDAP *ld, tls_session *sess, const char *name_in )
+{
+ tlsmt_session *s = (tlsmt_session *)sess;
+ if (name_in) {
+ 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 &&
+ ( !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 */