Files
mongoose/src/mg_ssl_if_openssl.c
T
Deomid Ryabkov 86b8a56b05 Add host name verification for OpenSSL
Closes https://github.com/cesanta/mongoose/pull/955

CL: mg: Add host name verification for OpenSSL

PUBLISHED_FROM=e35dd636ba7ce63116f0a38031074d22f6cd5dac
2018-08-13 13:05:21 +00:00

398 lines
14 KiB
C

/*
* Copyright (c) 2014-2016 Cesanta Software Limited
* All rights reserved
*/
#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL
#ifdef __APPLE__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <openssl/ssl.h>
#ifndef KR_VERSION
#include <openssl/tls1.h>
#endif
struct mg_ssl_if_ctx {
SSL *ssl;
SSL_CTX *ssl_ctx;
struct mbuf psk;
size_t identity_len;
};
void mg_ssl_if_init() {
SSL_library_init();
}
enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
struct mg_connection *lc) {
struct mg_ssl_if_ctx *ctx =
(struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));
struct mg_ssl_if_ctx *lc_ctx = (struct mg_ssl_if_ctx *) lc->ssl_if_data;
nc->ssl_if_data = ctx;
if (ctx == NULL || lc_ctx == NULL) return MG_SSL_ERROR;
ctx->ssl_ctx = lc_ctx->ssl_ctx;
if ((ctx->ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
return MG_SSL_ERROR;
}
return MG_SSL_OK;
}
static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,
const char *key, const char **err_msg);
static enum mg_ssl_if_result mg_use_ca_cert(SSL_CTX *ctx, const char *cert);
static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl);
static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
const char *identity,
const char *key_str);
enum mg_ssl_if_result mg_ssl_if_conn_init(
struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,
const char **err_msg) {
struct mg_ssl_if_ctx *ctx =
(struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));
DBG(("%p %s,%s,%s", nc, (params->cert ? params->cert : ""),
(params->key ? params->key : ""),
(params->ca_cert ? params->ca_cert : "")));
if (ctx == NULL) {
MG_SET_PTRPTR(err_msg, "Out of memory");
return MG_SSL_ERROR;
}
nc->ssl_if_data = ctx;
if (nc->flags & MG_F_LISTENING) {
ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
} else {
ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
}
if (ctx->ssl_ctx == NULL) {
MG_SET_PTRPTR(err_msg, "Failed to create SSL context");
return MG_SSL_ERROR;
}
#ifndef KR_VERSION
/* Disable deprecated protocols. */
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv3);
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1);
#ifdef MG_SSL_OPENSSL_NO_COMPRESSION
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_COMPRESSION);
#endif
#ifdef MG_SSL_OPENSSL_CIPHER_SERVER_PREFERENCE
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
#endif
#else
/* Krypton only supports TLSv1.2 anyway. */
#endif
if (params->cert != NULL &&
mg_use_cert(ctx->ssl_ctx, params->cert, params->key, err_msg) !=
MG_SSL_OK) {
return MG_SSL_ERROR;
}
if (params->ca_cert != NULL &&
mg_use_ca_cert(ctx->ssl_ctx, params->ca_cert) != MG_SSL_OK) {
MG_SET_PTRPTR(err_msg, "Invalid SSL CA cert");
return MG_SSL_ERROR;
}
if (mg_set_cipher_list(ctx->ssl_ctx, params->cipher_suites) != MG_SSL_OK) {
MG_SET_PTRPTR(err_msg, "Invalid cipher suite list");
return MG_SSL_ERROR;
}
mbuf_init(&ctx->psk, 0);
if (mg_ssl_if_ossl_set_psk(ctx, params->psk_identity, params->psk_key) !=
MG_SSL_OK) {
MG_SET_PTRPTR(err_msg, "Invalid PSK settings");
return MG_SSL_ERROR;
}
if (!(nc->flags & MG_F_LISTENING) &&
(ctx->ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
MG_SET_PTRPTR(err_msg, "Failed to create SSL session");
return MG_SSL_ERROR;
}
if (params->server_name != NULL) {
#ifdef KR_VERSION
SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name);
#else
SSL_set_tlsext_host_name(ctx->ssl, params->server_name);
#endif
}
nc->flags |= MG_F_SSL;
return MG_SSL_OK;
}
static enum mg_ssl_if_result mg_ssl_if_ssl_err(struct mg_connection *nc,
int res) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
int err = SSL_get_error(ctx->ssl, res);
if (err == SSL_ERROR_WANT_READ) return MG_SSL_WANT_READ;
if (err == SSL_ERROR_WANT_WRITE) return MG_SSL_WANT_WRITE;
DBG(("%p %p SSL error: %d %d", nc, ctx->ssl_ctx, res, err));
nc->err = err;
return MG_SSL_ERROR;
}
enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
int server_side = (nc->listener != NULL);
int res;
/* If descriptor is not yet set, do it now. */
if (SSL_get_fd(ctx->ssl) < 0) {
if (SSL_set_fd(ctx->ssl, nc->sock) != 1) return MG_SSL_ERROR;
}
res = server_side ? SSL_accept(ctx->ssl) : SSL_connect(ctx->ssl);
if (res != 1) return mg_ssl_if_ssl_err(nc, res);
return MG_SSL_OK;
}
int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
int n = SSL_read(ctx->ssl, buf, buf_size);
DBG(("%p %d -> %d", nc, (int) buf_size, n));
if (n < 0) return mg_ssl_if_ssl_err(nc, n);
if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;
return n;
}
int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
int n = SSL_write(ctx->ssl, data, len);
DBG(("%p %d -> %d", nc, (int) len, n));
if (n <= 0) return mg_ssl_if_ssl_err(nc, n);
return n;
}
void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
if (ctx == NULL) return;
SSL_shutdown(ctx->ssl);
}
void mg_ssl_if_conn_free(struct mg_connection *nc) {
struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
if (ctx == NULL) return;
nc->ssl_if_data = NULL;
if (ctx->ssl != NULL) SSL_free(ctx->ssl);
if (ctx->ssl_ctx != NULL && nc->listener == NULL) SSL_CTX_free(ctx->ssl_ctx);
mbuf_free(&ctx->psk);
memset(ctx, 0, sizeof(*ctx));
MG_FREE(ctx);
}
/*
* Cipher suite options used for TLS negotiation.
* https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations
*/
static const char mg_s_cipher_list[] =
#if defined(MG_SSL_CRYPTO_MODERN)
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:"
"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:"
"!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
#elif defined(MG_SSL_CRYPTO_OLD)
"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:"
"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:"
"ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:"
"AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:"
"HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:"
"!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
#else /* Default - intermediate. */
"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:"
"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:"
"AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:"
"DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:"
"!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
#endif
;
/*
* Default DH params for PFS cipher negotiation. This is a 2048-bit group.
* Will be used if none are provided by the user in the certificate file.
*/
#if !MG_DISABLE_PFS && !defined(KR_VERSION)
static const char mg_s_default_dh_params[] =
"\
-----BEGIN DH PARAMETERS-----\n\
MIIBCAKCAQEAlvbgD/qh9znWIlGFcV0zdltD7rq8FeShIqIhkQ0C7hYFThrBvF2E\n\
Z9bmgaP+sfQwGpVlv9mtaWjvERbu6mEG7JTkgmVUJrUt/wiRzwTaCXBqZkdUO8Tq\n\
+E6VOEQAilstG90ikN1Tfo+K6+X68XkRUIlgawBTKuvKVwBhuvlqTGerOtnXWnrt\n\
ym//hd3cd5PBYGBix0i7oR4xdghvfR2WLVu0LgdThTBb6XP7gLd19cQ1JuBtAajZ\n\
wMuPn7qlUkEFDIkAZy59/Hue/H2Q2vU/JsvVhHWCQBL4F1ofEAt50il6ZxR1QfFK\n\
9VGKDC4oOgm9DlxwwBoC2FjqmvQlqVV3kwIBAg==\n\
-----END DH PARAMETERS-----\n";
#endif
static enum mg_ssl_if_result mg_use_ca_cert(SSL_CTX *ctx, const char *cert) {
if (cert == NULL || strcmp(cert, "*") == 0) {
return MG_SSL_OK;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? MG_SSL_OK
: MG_SSL_ERROR;
}
static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,
const char *key,
const char **err_msg) {
if (key == NULL) key = cert;
if (cert == NULL || cert[0] == '\0' || key == NULL || key[0] == '\0') {
return MG_SSL_OK;
} else if (SSL_CTX_use_certificate_file(ctx, cert, 1) == 0) {
MG_SET_PTRPTR(err_msg, "Invalid SSL cert");
return MG_SSL_ERROR;
} else if (SSL_CTX_use_PrivateKey_file(ctx, key, 1) == 0) {
MG_SET_PTRPTR(err_msg, "Invalid SSL key");
return MG_SSL_ERROR;
} else if (SSL_CTX_use_certificate_chain_file(ctx, cert) == 0) {
MG_SET_PTRPTR(err_msg, "Invalid CA bundle");
return MG_SSL_ERROR;
} else {
SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
#if !MG_DISABLE_PFS && !defined(KR_VERSION)
BIO *bio = NULL;
DH *dh = NULL;
/* Try to read DH parameters from the cert/key file. */
bio = BIO_new_file(cert, "r");
if (bio != NULL) {
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
}
/*
* If there are no DH params in the file, fall back to hard-coded ones.
* Not ideal, but better than nothing.
*/
if (dh == NULL) {
bio = BIO_new_mem_buf((void *) mg_s_default_dh_params, -1);
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
}
if (dh != NULL) {
SSL_CTX_set_tmp_dh(ctx, dh);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
DH_free(dh);
}
#if OPENSSL_VERSION_NUMBER > 0x10002000L
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
#endif
}
return MG_SSL_OK;
}
static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl) {
return (SSL_CTX_set_cipher_list(ctx, cl ? cl : mg_s_cipher_list) == 1
? MG_SSL_OK
: MG_SSL_ERROR);
}
#ifndef KR_VERSION
static unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint,
char *identity,
unsigned int max_identity_len,
unsigned char *psk,
unsigned int max_psk_len) {
struct mg_ssl_if_ctx *ctx =
(struct mg_ssl_if_ctx *) SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl));
size_t key_len = ctx->psk.len - ctx->identity_len - 1;
DBG(("hint: '%s'", (hint ? hint : "")));
if (ctx->identity_len + 1 > max_identity_len) {
DBG(("identity too long"));
return 0;
}
if (key_len > max_psk_len) {
DBG(("key too long"));
return 0;
}
memcpy(identity, ctx->psk.buf, ctx->identity_len + 1);
memcpy(psk, ctx->psk.buf + ctx->identity_len + 1, key_len);
(void) ssl;
return key_len;
}
static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
const char *identity,
const char *key_str) {
unsigned char key[32];
size_t key_len;
size_t i = 0;
if (identity == NULL && key_str == NULL) return MG_SSL_OK;
if (identity == NULL || key_str == NULL) return MG_SSL_ERROR;
key_len = strlen(key_str);
if (key_len != 32 && key_len != 64) return MG_SSL_ERROR;
memset(key, 0, sizeof(key));
key_len = 0;
for (i = 0; key_str[i] != '\0'; i++) {
unsigned char c;
char hc = tolower((int) key_str[i]);
if (hc >= '0' && hc <= '9') {
c = hc - '0';
} else if (hc >= 'a' && hc <= 'f') {
c = hc - 'a' + 0xa;
} else {
return MG_SSL_ERROR;
}
key_len = i / 2;
key[key_len] <<= 4;
key[key_len] |= c;
}
key_len++;
DBG(("identity = '%s', key = (%u)", identity, (unsigned int) key_len));
ctx->identity_len = strlen(identity);
mbuf_append(&ctx->psk, identity, ctx->identity_len + 1);
mbuf_append(&ctx->psk, key, key_len);
SSL_CTX_set_psk_client_callback(ctx->ssl_ctx, mg_ssl_if_ossl_psk_cb);
SSL_CTX_set_app_data(ctx->ssl_ctx, ctx);
return MG_SSL_OK;
}
#else
static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
const char *identity,
const char *key_str) {
(void) ctx;
(void) identity;
(void) key_str;
/* Krypton does not support PSK. */
return MG_SSL_ERROR;
}
#endif /* defined(KR_VERSION) */
const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
const char *ca_cert) {
const char *err_msg = NULL;
struct mg_ssl_if_conn_params params;
memset(&params, 0, sizeof(params));
params.cert = cert;
params.ca_cert = ca_cert;
if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {
return err_msg;
}
return NULL;
}
#endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL */