/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation: * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include "private-libwebsockets.h" extern int openssl_websocket_private_data_index, openssl_SSL_CTX_private_data_index; extern void lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); #if !defined(LWS_WITH_MBEDTLS) static int OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) { SSL *ssl; int n; struct lws *wsi; ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); /* * !!! nasty openssl requires the index to come as a library-scope * static */ wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); n = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION, x509_ctx, ssl, preverify_ok); /* convert return code from 0 = OK to 1 = OK */ return !n; } #endif static int lws_context_ssl_init_ecdh(struct lws_vhost *vhost) { #ifdef LWS_SSL_SERVER_WITH_ECDH_CERT EC_KEY *EC_key = NULL; EVP_PKEY *pkey; int KeyType; X509 *x; if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH)) return 0; lwsl_notice(" Using ECDH certificate support\n"); /* Get X509 certificate from ssl context */ x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0); if (!x) { lwsl_err("%s: x is NULL\n", __func__); return 1; } /* Get the public key from certificate */ pkey = X509_get_pubkey(x); if (!pkey) { lwsl_err("%s: pkey is NULL\n", __func__); return 1; } /* Get the key type */ KeyType = EVP_PKEY_type(pkey->type); if (EVP_PKEY_EC != KeyType) { lwsl_notice("Key type is not EC\n"); return 0; } /* Get the key */ EC_key = EVP_PKEY_get1_EC_KEY(pkey); /* Set ECDH parameter */ if (!EC_key) { lwsl_err("%s: ECDH key is NULL \n", __func__); return 1; } SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, EC_key); EC_KEY_free(EC_key); #endif return 0; } static int lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info, struct lws_vhost *vhost) { #if defined(LWS_HAVE_OPENSSL_ECDH_H) && !defined(LWS_WITH_MBEDTLS) EC_KEY *ecdh; int ecdh_nid; const char *ecdh_curve = "prime256v1"; if (info->ecdh_curve) ecdh_curve = info->ecdh_curve; ecdh_nid = OBJ_sn2nid(ecdh_curve); if (NID_undef == ecdh_nid) { lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve); return 1; } ecdh = EC_KEY_new_by_curve_name(ecdh_nid); if (NULL == ecdh) { lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve); return 1; } SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, ecdh); EC_KEY_free(ecdh); SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_ECDH_USE); lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve); #else #if !defined(LWS_WITH_MBEDTLS) lwsl_notice(" OpenSSL doesn't support ECDH\n"); #endif #endif return 0; } #if !defined(LWS_WITH_MBEDTLS) && defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT) static int lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) { struct lws_context *context = (struct lws_context *)arg; struct lws_vhost *vhost, *vh; const char *servername; if (!ssl) return SSL_TLSEXT_ERR_NOACK; /* * We can only get ssl accepted connections by using a vhost's ssl_ctx * find out which listening one took us and only match vhosts on the * same port. */ vh = context->vhost_list; while (vh) { if (!vh->being_destroyed && ssl && vh->ssl_ctx == SSL_get_SSL_CTX(ssl)) break; vh = vh->vhost_next; } if (!vh) { assert(vh); /* can't match the incoming vh? */ return SSL_TLSEXT_ERR_OK; } servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!servername) { /* the client doesn't know what hostname it wants */ lwsl_info("SNI: Unknown ServerName: %s\n", servername); return SSL_TLSEXT_ERR_OK; } vhost = lws_select_vhost(context, vh->listen_port, servername); if (!vhost) { lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); return SSL_TLSEXT_ERR_OK; } lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port); /* select the ssl ctx from the selected vhost for this conn */ SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); return SSL_TLSEXT_ERR_OK; } #endif LWS_VISIBLE int lws_context_init_server_ssl(struct lws_context_creation_info *info, struct lws_vhost *vhost) { struct lws_context *context = vhost->context; struct lws wsi; unsigned long error; int n; if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { vhost->use_ssl = 0; return 0; } /* * If he is giving a cert filepath, take it as a sign he wants to use * it on this vhost. User code can leave the cert filepath NULL and * set the LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX option itself, in * which case he's expected to set up the cert himself at * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which * provides the vhost SSL_CTX * in the user parameter. */ if (info->ssl_cert_filepath) info->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; if (info->port != CONTEXT_PORT_NO_LISTEN) { vhost->use_ssl = lws_check_opt(info->options, LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX); if (vhost->use_ssl && info->ssl_cipher_list) lwsl_notice(" SSL ciphers: '%s'\n", info->ssl_cipher_list); if (vhost->use_ssl) lwsl_notice(" Using SSL mode\n"); else lwsl_notice(" Using non-SSL mode\n"); } /* * give him a fake wsi with context + vhost set, so he can use * lws_get_context() in the callback */ memset(&wsi, 0, sizeof(wsi)); wsi.vhost = vhost; wsi.context = context; (void)n; (void)error; /* * Firefox insists on SSLv23 not SSLv3 * Konq disables SSLv2 by default now, SSLv23 works * * SSLv23_server_method() is the openssl method for "allow all TLS * versions", compared to e.g. TLSv1_2_server_method() which only allows * tlsv1.2. Unwanted versions must be disabled using SSL_CTX_set_options() */ #if !defined(LWS_WITH_MBEDTLS) { SSL_METHOD *method; method = (SSL_METHOD *)SSLv23_server_method(); if (!method) { error = ERR_get_error(); lwsl_err("problem creating ssl method %lu: %s\n", error, ERR_error_string(error, (char *)context->pt[0].serv_buf)); return 1; } vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ if (!vhost->ssl_ctx) { error = ERR_get_error(); lwsl_err("problem creating ssl context %lu: %s\n", error, ERR_error_string(error, (char *)context->pt[0].serv_buf)); return 1; } } #else { const SSL_METHOD *method = TLSv1_2_server_method(); vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ if (!vhost->ssl_ctx) { lwsl_err("problem creating ssl context\n"); return 1; } } #endif #if !defined(LWS_WITH_MBEDTLS) /* associate the lws context with the SSL_CTX */ SSL_CTX_set_ex_data(vhost->ssl_ctx, openssl_SSL_CTX_private_data_index, (char *)vhost->context); /* Disable SSLv2 and SSLv3 */ SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); #ifdef SSL_OP_NO_COMPRESSION SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_COMPRESSION); #endif SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_DH_USE); SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); if (info->ssl_cipher_list) SSL_CTX_set_cipher_list(vhost->ssl_ctx, info->ssl_cipher_list); #endif /* as a server, are we requiring clients to identify themselves? */ if (lws_check_opt(info->options, LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) { int verify_options = SSL_VERIFY_PEER; if (!lws_check_opt(info->options, LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED)) verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; #if !defined(LWS_WITH_MBEDTLS) SSL_CTX_set_session_id_context(vhost->ssl_ctx, (unsigned char *)context, sizeof(void *)); /* absolutely require the client cert */ SSL_CTX_set_verify(vhost->ssl_ctx, verify_options, OpenSSL_verify_callback); #endif } #if !defined(LWS_WITH_MBEDTLS) && !defined(OPENSSL_NO_TLSEXT) SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx, lws_ssl_server_name_cb); SSL_CTX_set_tlsext_servername_arg(vhost->ssl_ctx, context); #endif /* * give user code a chance to load certs into the server * allowing it to verify incoming client certs */ #if !defined(LWS_WITH_MBEDTLS) if (info->ssl_ca_filepath && !SSL_CTX_load_verify_locations(vhost->ssl_ctx, info->ssl_ca_filepath, NULL)) { lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n", __func__); } #endif if (vhost->use_ssl) { if (lws_context_ssl_init_ecdh_curve(info, vhost)) return -1; vhost->protocols[0].callback(&wsi, LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, vhost->ssl_ctx, NULL, 0); } if (lws_check_opt(info->options, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT)) /* Normally SSL listener rejects non-ssl, optionally allow */ vhost->allow_non_ssl_on_ssl_port = 1; if (info->ssl_options_set) SSL_CTX_set_options(vhost->ssl_ctx, info->ssl_options_set); /* SSL_clear_options introduced in 0.9.8m */ #if !defined(LWS_WITH_MBEDTLS) #if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL) if (info->ssl_options_clear) SSL_CTX_clear_options(vhost->ssl_ctx, info->ssl_options_clear); #endif #endif lwsl_info(" SSL options 0x%lX\n", SSL_CTX_get_options(vhost->ssl_ctx)); if (vhost->use_ssl && info->ssl_cert_filepath) { /* * The user code can choose to either pass the cert and * key filepaths using the info members like this, or it can * leave them NULL; force the vhost SSL_CTX init using the info * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and * set up the cert himself using the user callback * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which * happened just above and has the vhost SSL_CTX * in the user * parameter. */ #if !defined(LWS_WITH_MBEDTLS) /* set the local certificate from CertFile */ n = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx, info->ssl_cert_filepath); if (n != 1) { error = ERR_get_error(); lwsl_err("problem getting cert '%s' %lu: %s\n", info->ssl_cert_filepath, error, ERR_error_string(error, (char *)context->pt[0].serv_buf)); return 1; } lws_ssl_bind_passphrase(vhost->ssl_ctx, info); #else uint8_t *p; lws_filepos_t flen; int err; if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p, &flen)) { lwsl_err("couldn't find cert file %s\n", info->ssl_cert_filepath); return 1; } err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p); if (!err) { lwsl_err("Problem loading cert\n"); return 1; } #if !defined(LWS_WITH_ESP32) free(p); p = NULL; #endif if (info->ssl_private_key_filepath) { if (alloc_pem_to_der_file(vhost->context, info->ssl_private_key_filepath, &p, &flen)) { lwsl_err("couldn't find cert file %s\n", info->ssl_cert_filepath); return 1; } err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); if (!err) { lwsl_err("Problem loading key\n"); return 1; } } #if !defined(LWS_WITH_ESP32) free(p); p = NULL; #endif #endif if (info->ssl_private_key_filepath != NULL) { #if !defined(LWS_WITH_MBEDTLS) /* set the private key from KeyFile */ if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx, info->ssl_private_key_filepath, SSL_FILETYPE_PEM) != 1) { error = ERR_get_error(); lwsl_err("ssl problem getting key '%s' %lu: %s\n", info->ssl_private_key_filepath, error, ERR_error_string(error, (char *)context->pt[0].serv_buf)); return 1; } #endif } else if (vhost->protocols[0].callback(&wsi, LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY, vhost->ssl_ctx, NULL, 0)) { lwsl_err("ssl private key not set\n"); return 1; } #if !defined(LWS_WITH_MBEDTLS) /* verify private key */ if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) { lwsl_err("Private SSL key doesn't match cert\n"); return 1; } #endif } if (vhost->use_ssl) { if (lws_context_ssl_init_ecdh(vhost)) return 1; /* * SSL is happy and has a cert it's content with * If we're supporting HTTP2, initialize that */ lws_context_init_http2_ssl(vhost); } return 0; }