Browse Source

[mod_mbedtls] fix acme-tls/1 challenge bootstrap

mbedtls does not provide a callback for ALPN and expects certificate to
be set in SNI callback (if set), while still in MBEDTLS_SSL_CLIENT_HELLO
state.  Waiting until after MBEDTLS_SSL_CLIENT_HELLO would be fine for
using ALPN for "h2", but is too late to set acme-tls/1 challenge cert.
Therefore, parse client hello for ALPN prior to initiating mbedtls
processing of handshake.
master
Glenn Strauss 10 months ago
parent
commit
b80d287df7
  1. 392
      src/mod_mbedtls.c

392
src/mod_mbedtls.c

@ -12,7 +12,6 @@
* https://tls.mbed.org/api/ (generated from mbedTLS headers and code)
*
* mbedTLS limitations:
* - mbedTLS does not currently support TLSv1.3
* - mbedTLS does not currently support OCSP
* https://tls.mbed.org/discussions/feature-request/ocsp-stapling
* TLS/DTLS: OCSP Stapling support #880
@ -58,6 +57,7 @@
#include <mbedtls/config.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/dhm.h>
#include <mbedtls/error.h>
#include <mbedtls/entropy.h>
@ -671,6 +671,23 @@ mod_mbedtls_verify_cb (void *arg, mbedtls_x509_crt *crt, int depth, uint32_t *fl
}
enum {
MOD_MBEDTLS_ALPN_HTTP11 = 1
,MOD_MBEDTLS_ALPN_HTTP10 = 2
,MOD_MBEDTLS_ALPN_H2 = 3
,MOD_MBEDTLS_ALPN_ACME_TLS_1 = 4
};
#ifdef MBEDTLS_SSL_SERVER_NAME_INDICATION
#ifdef MBEDTLS_SSL_ALPN
static int
mod_mbedtls_acme_tls_1 (handler_ctx *hctx);
#endif
#endif
#ifdef MBEDTLS_SSL_SERVER_NAME_INDICATION
static int
mod_mbedtls_SNI (void *arg, mbedtls_ssl_context *ssl, const unsigned char *servername, size_t len)
@ -707,6 +724,12 @@ mod_mbedtls_SNI (void *arg, mbedtls_ssl_context *ssl, const unsigned char *serve
/*config_cond_cache_reset_item(r, COMP_HTTP_HOST);*/
/*buffer_clear(&r->uri.authority);*/
#ifdef MBEDTLS_SSL_ALPN
/* TLS-ALPN-01 (ALPN "acme-tls/1") requires SNI */
if (hctx->alpn == MOD_MBEDTLS_ALPN_ACME_TLS_1)
return mod_mbedtls_acme_tls_1(hctx);
#endif
/*(compare strings as ssl.pemfile might repeat same file in lighttpd.conf
* and mod_mbedtls does not attempt to de-dup)*/
if (!buffer_is_equal(hctx->conf.pc->ssl_pemfile, ssl_pemfile)) {
@ -953,6 +976,7 @@ network_mbedtls_load_pemfile (server *srv, const buffer *pemfile, const buffer *
#ifdef MBEDTLS_SSL_ALPN
#ifdef MBEDTLS_SSL_SERVER_NAME_INDICATION
static int
mod_mbedtls_acme_tls_1 (handler_ctx *hctx)
{
@ -1036,58 +1060,48 @@ mod_mbedtls_acme_tls_1 (handler_ctx *hctx)
return rc;
}
enum {
MOD_MBEDTLS_ALPN_HTTP11 = 1
,MOD_MBEDTLS_ALPN_HTTP10 = 2
,MOD_MBEDTLS_ALPN_H2 = 3
,MOD_MBEDTLS_ALPN_ACME_TLS_1 = 4
};
#endif
static int
mod_mbedtls_alpn_select_cb (handler_ctx *hctx, const char *in)
mod_mbedtls_alpn_select_cb (handler_ctx *hctx, const unsigned char *in, const unsigned int inlen)
{
const int n = (int)strlen(in);
const int i = 0;
unsigned short proto;
switch (n) {
case 2: /* "h2" */
if (in[i] == 'h' && in[i+1] == '2') {
proto = MOD_MBEDTLS_ALPN_H2;
hctx->r->http_version = HTTP_VERSION_2;
break;
}
return 0;
case 8: /* "http/1.1" "http/1.0" */
if (0 == memcmp(in+i, "http/1.", 7)) {
if (in[i+7] == '1') {
proto = MOD_MBEDTLS_ALPN_HTTP11;
break;
/*(skip first two bytes which should match inlen-2)*/
for (unsigned int i = 2, n; i < inlen; i += n) {
n = in[i++];
if (i+n > inlen || 0 == n) break;
switch (n) {
case 2: /* "h2" */
if (in[i] == 'h' && in[i+1] == '2') {
if (!hctx->r->conf.h2proto) continue;
hctx->alpn = MOD_MBEDTLS_ALPN_H2;
hctx->r->http_version = HTTP_VERSION_2;
return 0;
}
if (in[i+7] == '0') {
proto = MOD_MBEDTLS_ALPN_HTTP10;
break;
continue;
case 8: /* "http/1.1" "http/1.0" */
if (0 == memcmp(in+i, "http/1.", 7)) {
if (in[i+7] == '1') {
hctx->alpn = MOD_MBEDTLS_ALPN_HTTP11;
return 0;
}
if (in[i+7] == '0') {
hctx->alpn = MOD_MBEDTLS_ALPN_HTTP10;
return 0;
}
}
}
return 0;
case 10: /* "acme-tls/1" */
if (0 == memcmp(in+i, "acme-tls/1", 10)) {
int rc = mod_mbedtls_acme_tls_1(hctx);
if (0 == rc) {
proto = MOD_MBEDTLS_ALPN_ACME_TLS_1;
break;
continue;
case 10: /* "acme-tls/1" */
if (0 == memcmp(in+i, "acme-tls/1", 10)) {
hctx->alpn = MOD_MBEDTLS_ALPN_ACME_TLS_1;
return 0;
}
return rc;
continue;
default:
continue;
}
return 0;
default:
return 0;
}
hctx->alpn = proto;
return 0;
}
@ -1910,6 +1924,9 @@ connection_write_cq_ssl (connection *con, chunkqueue *cq, off_t max_bytes)
}
static int ssl_parse_client_hello( mbedtls_ssl_context *ssl, handler_ctx *hctx );
static int
mod_mbedtls_ssl_handshake (handler_ctx *hctx)
{
@ -1929,15 +1946,18 @@ mod_mbedtls_ssl_handshake (handler_ctx *hctx)
if (hctx->ssl.state < MBEDTLS_SSL_SERVER_HELLO) {
while (hctx->ssl.state != MBEDTLS_SSL_SERVER_HELLO
&& hctx->ssl.state != MBEDTLS_SSL_HANDSHAKE_OVER) {
rc = mbedtls_ssl_handshake_step(&hctx->ssl);
if (0 != rc) break;
}
if (0 == rc && hctx->ssl.state == MBEDTLS_SSL_SERVER_HELLO) {
#ifdef MBEDTLS_SSL_SERVER_NAME_INDICATION
#ifdef MBEDTLS_SSL_ALPN
const char *alpn = mbedtls_ssl_get_alpn_protocol(&hctx->ssl);
if (NULL != alpn)
rc = mod_mbedtls_alpn_select_cb(hctx, alpn);
/* parse client_hello for ALPN extension prior to mbedtls handshake
* in order to perform certificate selection in mod_mbedtls_SNI() */
if (!buffer_string_is_empty(hctx->conf.ssl_acme_tls_1)) {
rc = ssl_parse_client_hello(&hctx->ssl, hctx);
if (0 != rc) break;
}
#endif
#endif
rc = mbedtls_ssl_handshake_step(&hctx->ssl);
if (0 != rc) break;
}
}
@ -2130,9 +2150,13 @@ CONNECTION_FUNC(mod_mbedtls_handle_con_accept)
/* (mbedtls_ssl_config *) is shared across multiple connections, which may
* overlap, and so this debug setting is not reset upon connection close.
* Once enabled, debug hook will remain so for this mbedtls_ssl_config */
if (hctx->conf.ssl_log_noise) /* volume level for debug message callback */
if (hctx->conf.ssl_log_noise) {/* volume level for debug message callback */
#if MBEDTLS_VERSION_NUMBER >= 0x02000000 /* mbedtls 2.0.0 */
mbedtls_debug_set_threshold(hctx->conf.ssl_log_noise);
#endif
mbedtls_ssl_conf_dbg(s->ssl_ctx, mod_mbedtls_debug_cb,
(void *)(intptr_t)hctx->conf.ssl_log_noise);
}
/* (mbedtls_ssl_config *) is shared across multiple connections, which may
* overlap, and so renegotiation setting is not reset upon connection close.
@ -3771,3 +3795,269 @@ mod_mbedtls_ssl_conf_proto (server *srv, plugin_config_socket *s, const buffer *
? mbedtls_ssl_conf_max_version(s->ssl_ctx,MBEDTLS_SSL_MAJOR_VERSION_3,v)
: mbedtls_ssl_conf_min_version(s->ssl_ctx,MBEDTLS_SSL_MAJOR_VERSION_3,v);
}
/*
* XXX: forked from mbedtls
*
* ssl_parse_client_hello() is forked and modified from mbedtls
* library/ssl_srv.c:ssl_parse_client_hello()
* due to limitations in mbedtls hooks. Other than fetching input, ssl is not
* modified here so that it can be reprocessed during handshake.
*
* It would be beneficial to have a callback after parsing client hello and all
* extensions, and before certificate selection. (SNI extension might occur
* prior to ALPN extension, and a different certificate may be needed by
* ALPN "acme-tls/1".) Alternatively, mbedtls could provide an API to clear
* &ssl->handshake->sni_key_cert, rather than forcing ssl_append_key_cert() with
* no other option.
*/
static int ssl_parse_client_hello( mbedtls_ssl_context *ssl, handler_ctx *hctx )
{
int ret;
size_t msg_len;
unsigned char *buf;
/*
* If renegotiating, then the input was read with mbedtls_ssl_read_record(),
* otherwise read it ourselves manually in order to support SSLv2
* ClientHello, which doesn't use the same record layer format.
*/
#if defined(MBEDTLS_SSL_RENEGOTIATION)
if( ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE )
#endif
{
if( ( ret = mbedtls_ssl_fetch_input( ssl, 5 ) ) != 0 )
{
/* No alert on a read error. */
MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ssl_fetch_input", ret );
return( ret );
}
}
buf = ssl->in_hdr;
/*
* SSLv3/TLS Client Hello
*
* Record layer:
* 0 . 0 message type
* 1 . 2 protocol version
* 3 . 11 DTLS: epoch + record sequence number
* 3 . 4 message length
*/
if( buf[0] != MBEDTLS_SSL_MSG_HANDSHAKE )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
#if defined(MBEDTLS_SSL_PROTO_DTLS)
/*(not supported in lighttpd for now)*/
/*(mbedtls_ssl_in_hdr_len() and mbedtls_ssl_hs_hdr_len() are in
* mbedtls/ssl_internal.h but simple enough to repeat here) */
if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM )
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
#endif /* MBEDTLS_SSL_PROTO_DTLS */
msg_len = ( ssl->in_len[0] << 8 ) | ssl->in_len[1];
#if defined(MBEDTLS_SSL_RENEGOTIATION)
if( ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE )
{
/* Set by mbedtls_ssl_read_record() */
msg_len = ssl->in_hslen;
}
else
#endif
{
if( msg_len > MBEDTLS_SSL_IN_CONTENT_LEN )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
if( ( ret = mbedtls_ssl_fetch_input( ssl,
5 /*mbedtls_ssl_in_hdr_len( ssl )*/ + msg_len ) ) != 0 )
{
MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ssl_fetch_input", ret );
return( ret );
}
}
buf = ssl->in_msg;
/*
* Handshake layer:
* 0 . 0 handshake type
* 1 . 3 handshake length
* 4 . 5 DTLS only: message seqence number
* 6 . 8 DTLS only: fragment offset
* 9 . 11 DTLS only: fragment length
*/
if( msg_len < 4 /*mbedtls_ssl_hs_hdr_len( ssl )*/ )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
if( buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
/* We don't support fragmentation of ClientHello (yet?) */
if( buf[1] != 0 ||
msg_len != 4u /*mbedtls_ssl_hs_hdr_len( ssl )*/ + ( ( buf[2] << 8 ) | buf[3] ) )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
buf += 4; /* mbedtls_ssl_hs_hdr_len( ssl ); */
msg_len -= 4; /* mbedtls_ssl_hs_hdr_len( ssl ); */
/*
* ClientHello layer:
* 0 . 1 protocol version
* 2 . 33 random bytes (starting with 4 bytes of Unix time)
* 34 . 35 session id length (1 byte)
* 35 . 34+x session id
* 35+x . 35+x DTLS only: cookie length (1 byte)
* 36+x . .. DTLS only: cookie
* .. . .. ciphersuite list length (2 bytes)
* .. . .. ciphersuite list
* .. . .. compression alg. list length (1 byte)
* .. . .. compression alg. list
* .. . .. extensions length (2 bytes, optional)
* .. . .. extensions (optional)
*/
/*
* Minimal length (with everything empty and extensions omitted) is
* 2 + 32 + 1 + 2 + 1 = 38 bytes. Check that first, so that we can
* read at least up to session id length without worrying.
*/
if( msg_len < 38 )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
/*
* Check and save the protocol version
*/
int major_ver, minor_ver;
mbedtls_ssl_read_version( &major_ver, &minor_ver,
ssl->conf->transport, buf );
/*
* Check the session ID length and save session ID
*/
const size_t sess_len = buf[34];
if( sess_len > sizeof( ssl->session_negotiate->id ) ||
sess_len + 34 + 2 > msg_len ) /* 2 for cipherlist length field */
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
/*
* Check the cookie length and content
*/
const size_t ciph_offset = 35 + sess_len;
const size_t ciph_len = ( buf[ciph_offset + 0] << 8 )
| ( buf[ciph_offset + 1] );
if( ciph_len < 2 ||
ciph_len + 2 + ciph_offset + 1 > msg_len || /* 1 for comp. alg. len */
( ciph_len % 2 ) != 0 )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
/*
* Check the compression algorithms length and pick one
*/
const size_t comp_offset = ciph_offset + 2 + ciph_len;
const size_t comp_len = buf[comp_offset];
if( comp_len < 1 ||
comp_len > 16 ||
comp_len + comp_offset + 1 > msg_len )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
/* Do not parse the extensions if the protocol is SSLv3 */
#if defined(MBEDTLS_SSL_PROTO_SSL3)
if( ( major_ver != 3 ) || ( minor_ver != 0 ) )
{
#endif
/*
* Check the extension length
*/
const size_t ext_offset = comp_offset + 1 + comp_len;
size_t ext_len;
if( msg_len > ext_offset )
{
if( msg_len < ext_offset + 2 )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
ext_len = ( buf[ext_offset + 0] << 8 )
| ( buf[ext_offset + 1] );
if( ( ext_len > 0 && ext_len < 4 ) ||
msg_len != ext_offset + 2 + ext_len )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
}
else
ext_len = 0;
unsigned char *ext = buf + ext_offset + 2;
while( ext_len != 0 )
{
unsigned int ext_id;
unsigned int ext_size;
if ( ext_len < 4 ) {
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
ext_id = ( ( ext[0] << 8 ) | ( ext[1] ) );
ext_size = ( ( ext[2] << 8 ) | ( ext[3] ) );
if( ext_size + 4 > ext_len )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
switch( ext_id )
{
#if defined(MBEDTLS_SSL_ALPN)
case MBEDTLS_TLS_EXT_ALPN:
MBEDTLS_SSL_DEBUG_MSG( 3, ( "found alpn extension" ) );
/*(lighttpd-specific)*/
ret = mod_mbedtls_alpn_select_cb(hctx, ext + 4, ext_size);
if( ret != 0 )
return( ret );
break;
#endif /* MBEDTLS_SSL_ALPN */
default:
break;
}
ext_len -= 4 + ext_size;
ext += 4 + ext_size;
if( ext_len > 0 && ext_len < 4 )
{
return( MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO );
}
}
#if defined(MBEDTLS_SSL_PROTO_SSL3)
}
#endif
return( 0 );
}

Loading…
Cancel
Save