diff --git a/lib/context.c b/lib/context.c index d79cc242..4dc1c0f0 100644 --- a/lib/context.c +++ b/lib/context.c @@ -611,6 +611,30 @@ lws_create_vhost(struct lws_context *context, else vh->timeout_secs_ah_idle = 10; +#ifdef LWS_OPENSSL_SUPPORT + if (info->ecdh_curve) + strncpy(vh->ecdh_curve, info->ecdh_curve, sizeof(vh->ecdh_curve) - 1); +#endif + + /* carefully allocate and take a copy of cert + key paths if present */ + n = 0; + if (info->ssl_cert_filepath) + n += (int)strlen(info->ssl_cert_filepath) + 1; + if (info->ssl_private_key_filepath) + n += (int)strlen(info->ssl_private_key_filepath) + 1; + + if (n) { + vh->key_path = vh->alloc_cert_path = lws_malloc(n, "vh paths"); + if (info->ssl_cert_filepath) { + n = (int)strlen(info->ssl_cert_filepath) + 1; + memcpy(vh->alloc_cert_path, info->ssl_cert_filepath, n); + vh->key_path += n; + } + if (info->ssl_private_key_filepath) + memcpy(vh->key_path, info->ssl_private_key_filepath, + strlen(info->ssl_private_key_filepath) + 1); + } + /* * give the vhost a unified list of protocols including the * ones that came from plugins @@ -1527,6 +1551,8 @@ lws_vhost_destroy2(struct lws_vhost *vh) close(vh->log_fd); #endif + lws_free_set_NULL(vh->alloc_cert_path); + /* * although async event callbacks may still come for wsi handles with * pending close in the case of asycn event library like libuv, diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index a8efcaf2..cebbb9bc 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -5612,6 +5612,29 @@ LWS_VISIBLE LWS_EXTERN int lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], uint8_t *csr, size_t csr_len, char **privkey_pem, size_t *privkey_len); + +/** + * lws_tls_cert_updated() - update every vhost using the given cert path + * + * \param context: our lws_context + * \param certpath: the filepath to the certificate + * \param keypath: the filepath to the private key of the certificate + * \param mem_cert: copy of the cert in memory + * \param len_mem_cert: length of the copy of the cert in memory + * \param mem_privkey: copy of the private key in memory + * \param len_mem_privkey: length of the copy of the private key in memory + * + * Checks every vhost to see if it is the using certificate described by the + * the given filepaths. If so, it attempts to update the vhost ssl_ctx to use + * the new certificate. + * + * Returns 0 on success or nonzero for failure. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_cert_updated(struct lws_context *context, const char *certpath, + const char *keypath, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t len_mem_privkey); ///@} /** \defgroup lws_ring LWS Ringbuffer APIs diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index f3fb29b6..20bc0134 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -996,6 +996,8 @@ struct lws_vhost { struct lws *lserv_wsi; const char *name; const char *iface; + char *alloc_cert_path; + char *key_path; #if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) int bind_iface; #endif @@ -1008,6 +1010,7 @@ struct lws_vhost { lws_tls_ctx *ssl_ctx; lws_tls_ctx *ssl_client_ctx; struct lws_tls_ss_pieces *ss; /* for acme tls certs */ + char ecdh_curve[16]; #endif #if defined(LWS_WITH_MBEDTLS) lws_tls_x509 *x509_client_CA; @@ -2426,6 +2429,18 @@ lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, union lws_tls_cert_info_results *buf, size_t len); LWS_EXTERN int lws_tls_check_all_cert_lifetimes(struct lws_context *context); +LWS_EXTERN int +lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, + const char *cert, const char *private_key, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t mem_privkey_len); +LWS_EXTERN enum lws_tls_extant +lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert, + const char *private_key); +LWS_EXTERN int +lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename, + const char *inbuf, lws_filepos_t inlen, + uint8_t **buf, lws_filepos_t *amount); #ifndef LWS_NO_SERVER LWS_EXTERN int lws_context_init_server_ssl(struct lws_context_creation_info *info, diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c index cb526e17..af35bbfe 100644 --- a/lib/tls/mbedtls/server.c +++ b/lib/tls/mbedtls/server.c @@ -42,146 +42,6 @@ lws_tls_server_client_cert_verify_config(struct lws_context_creation_info *info, return 0; } -#if defined(LWS_WITH_ESP32) -int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - nvs_handle nvh; - size_t s; - int n = 0; - - ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); - if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { - n = 1; - goto bail; - } - *buf = lws_malloc(s, "alloc_file"); - if (!*buf) { - n = 2; - goto bail; - } - if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { - lws_free(*buf); - n = 1; - goto bail; - } - - *amount = s; - -bail: - nvs_close(nvh); - - return n; -} -#else -int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - FILE *f; - size_t s; - int n = 0; - - f = fopen(filename, "rb"); - if (f == NULL) { - n = 1; - goto bail; - } - - if (fseek(f, 0, SEEK_END) != 0) { - n = 1; - goto bail; - } - - s = ftell(f); - if (s == (size_t)-1) { - n = 1; - goto bail; - } - - if (fseek(f, 0, SEEK_SET) != 0) { - n = 1; - goto bail; - } - - *buf = lws_malloc(s, "alloc_file"); - if (!*buf) { - n = 2; - goto bail; - } - - if (fread(*buf, s, 1, f) != 1) { - lws_free(*buf); - n = 1; - goto bail; - } - - *amount = s; - -bail: - if (f) - fclose(f); - - return n; - -} -#endif - -static int -alloc_pem_to_der_file(struct lws_context *context, const char *filename, - uint8_t **buf, lws_filepos_t *amount) -{ - uint8_t *pem, *p, *q, *end; - lws_filepos_t len; - int n; - - n = alloc_file(context, filename, &pem, &len); - if (n) - return n; - - /* trim the first line */ - - p = pem; - end = p + len; - if (strncmp((char *)p, "-----", 5)) - goto bail; - p += 5; - while (p < end && *p != '\n' && *p != '-') - p++; - - if (*p != '-') - goto bail; - - while (p < end && *p != '\n') - p++; - - if (p >= end) - goto bail; - - p++; - - /* trim the last line */ - - q = end - 2; - - while (q > pem && *q != '\n') - q--; - - if (*q != '\n') - goto bail; - - *q = '\0'; - - *amount = lws_b64_decode_string((char *)p, (char *)pem, len); - *buf = pem; - - return 0; - -bail: - lws_free(pem); - - return 4; -} - static int lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx, const unsigned char *servername, size_t len) @@ -226,14 +86,112 @@ lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx, return 0; } +int +lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, + const char *cert, const char *private_key, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t mem_privkey_len) +{ + int n = lws_tls_generic_cert_checks(vhost, cert, private_key), f = 0; + const char *filepath = private_key; + uint8_t *mem = NULL, *p = NULL; + size_t mem_len = 0; + lws_filepos_t flen; + long err; + + if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey)) + return 0; + + /* + * we can't read the root-privs files. But if mem_cert is provided, + * we should use that. + */ + if (n == LWS_TLS_EXTANT_NO) + n = LWS_TLS_EXTANT_ALTERNATIVE; + + if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey)) + return 1; /* no alternative */ + + if (n == LWS_TLS_EXTANT_ALTERNATIVE) { + /* + * Although we have prepared update certs, we no longer have + * the rights to read our own cert + key we saved. + * + * If we were passed copies in memory buffers, use those + * instead. + * + * The passed memory-buffer cert image is in DER, and the + * memory-buffer private key image is PEM. + */ + /* mem cert is already DER */ + p = (uint8_t *)mem_cert; + flen = len_mem_cert; + /* mem private key is PEM, so go through the motions */ + mem = (uint8_t *)mem_privkey; + mem_len = mem_privkey_len; + filepath = NULL; + } else { + if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, NULL, + 0, &p, &flen)) { + lwsl_err("couldn't find cert file %s\n", cert); + + return 1; + } + f = 1; + } + err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p); + if (!err) { + free(p); + lwsl_err("Problem loading cert\n"); + return 1; + } + + if (f) + free(p); + p = NULL; + + if (private_key || n == LWS_TLS_EXTANT_ALTERNATIVE) { + if (lws_tls_alloc_pem_to_der_file(vhost->context, filepath, + (char *)mem, mem_len, &p, + &flen)) { + lwsl_err("couldn't find private key file %s\n", + private_key); + + return 1; + } + err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); + if (!err) { + free(p); + lwsl_err("Problem loading key\n"); + + return 1; + } + } + + if (p && !mem_privkey) { + free(p); + p = NULL; + } + + if (!private_key && !mem_privkey && + 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; + } + + vhost->skipped_certs = 0; + + return 0; +} + int lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, struct lws_vhost *vhost, struct lws *wsi) { const SSL_METHOD *method = TLS_server_method(); - uint8_t *p; - lws_filepos_t flen; - int n, m, err; vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ if (!vhost->ssl_ctx) { @@ -244,79 +202,10 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, if (!vhost->use_ssl || !info->ssl_cert_filepath) return 0; - /* - * 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. - */ - n = lws_tls_use_any_upgrade_check_extant(info->ssl_cert_filepath); - if (n == LWS_TLS_EXTANT_ALTERNATIVE) - return 1; - m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); - if (m == LWS_TLS_EXTANT_ALTERNATIVE) - return 1; - if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && - (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { - lwsl_notice("Ignoring missing %s or %s\n", - info->ssl_cert_filepath, - info->ssl_private_key_filepath); - vhost->skipped_certs = 1; - return 0; - } - - 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 - - if (!info->ssl_private_key_filepath && 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; - } - - return 0; + return lws_tls_server_certs_load(vhost, wsi, + info->ssl_cert_filepath, + info->ssl_private_key_filepath, + NULL, 0, NULL, 0); } int diff --git a/lib/tls/openssl/server.c b/lib/tls/openssl/server.c index 964bbae5..d04073e9 100644 --- a/lib/tls/openssl/server.c +++ b/lib/tls/openssl/server.c @@ -126,7 +126,7 @@ lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) return SSL_TLSEXT_ERR_OK; } - lwsl_notice("SNI: Found: %s:%d\n", servername, vh->listen_port); + 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); @@ -135,10 +135,15 @@ lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) } #endif +/* + * this may now get called after the vhost creation, when certs become + * available. + */ int -lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, - struct lws_vhost *vhost, - struct lws *wsi) +lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, + const char *cert, const char *private_key, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t mem_privkey_len) { #if defined(LWS_HAVE_OPENSSL_ECDH_H) const char *ecdh_curve = "prime256v1"; @@ -151,9 +156,177 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, STACK_OF(X509) *extra_certs = NULL; #endif #endif - SSL_METHOD *method = (SSL_METHOD *)SSLv23_server_method(); unsigned long error; - int n, m; + uint8_t *p; + lws_filepos_t flen; + + int n = lws_tls_generic_cert_checks(vhost, cert, private_key), m; + + if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey)) + return 0; + + if (n == LWS_TLS_EXTANT_NO) + n = LWS_TLS_EXTANT_ALTERNATIVE; + + if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey)) + return 1; /* no alternative */ + + if (n == LWS_TLS_EXTANT_ALTERNATIVE) { + /* + * Although we have prepared update certs, we no longer have + * the rights to read our own cert + key we saved. + * + * If we were passed copies in memory buffers, use those + * instead. + * + * The passed memory-buffer cert image is in DER, and the + * memory-buffer private key image is PEM. + */ + if (SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, + (int)len_mem_cert, + (uint8_t *)mem_cert) != 1) { + lwsl_err("Problem loading update cert\n"); + + return 1; + } + + if (lws_tls_alloc_pem_to_der_file(vhost->context, NULL, + mem_privkey, mem_privkey_len, + &p, &flen)) { + lwsl_notice("unable to convert memory privkey\n"); + + return 1; + } + if (SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, vhost->ssl_ctx, + p, (long)(long long)flen) != 1) { + lwsl_notice("unable to use memory privkey\n"); + + return 1; + } + + goto check_key; + } + + /* set the local certificate from CertFile */ + m = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx, cert); + if (m != 1) { + error = ERR_get_error(); + lwsl_err("problem getting cert '%s' %lu: %s\n", + cert, error, ERR_error_string(error, + (char *)vhost->context->pt[0].serv_buf)); + + return 1; + } + + if (n != LWS_TLS_EXTANT_ALTERNATIVE && private_key) { + /* set the private key from KeyFile */ + if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx, private_key, + SSL_FILETYPE_PEM) != 1) { + error = ERR_get_error(); + lwsl_err("ssl problem getting key '%s' %lu: %s\n", + private_key, error, + ERR_error_string(error, + (char *)vhost->context->pt[0].serv_buf)); + return 1; + } + } 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; + } + } + +check_key: + /* verify private key */ + if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) { + lwsl_err("Private SSL key doesn't match cert\n"); + + return 1; + } + +#if defined(LWS_HAVE_OPENSSL_ECDH_H) + if (vhost->ecdh_curve[0]) + ecdh_curve = vhost->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); + + if (lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH)) + lwsl_notice(" Using ECDH certificate support\n"); + + /* Get X509 certificate from ssl context */ +#if !defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS) + x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0); +#else + SSL_CTX_get_extra_chain_certs_only(vhost->ssl_ctx, &extra_certs); + if (extra_certs) + x = sk_X509_value(extra_certs, 0); + else + lwsl_err("%s: no extra certs\n", __func__); +#endif + if (!x) { + lwsl_err("%s: x is NULL\n", __func__); + goto post_ecdh; + } + /* 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(EVP_PKEY_id(pkey)); + + 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); +#else + lwsl_notice(" OpenSSL doesn't support ECDH\n"); +#endif + +post_ecdh: + vhost->skipped_certs = 0; + + return 0; +} + +int +lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, + struct lws_vhost *vhost, + struct lws *wsi) +{ + unsigned long error; + SSL_METHOD *method = (SSL_METHOD *)SSLv23_server_method(); if (!method) { error = ERR_get_error(); @@ -212,140 +385,9 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, lws_ssl_bind_passphrase(vhost->ssl_ctx, info); - /* - * 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. - */ - - n = lws_tls_use_any_upgrade_check_extant(info->ssl_cert_filepath); - if (n == LWS_TLS_EXTANT_ALTERNATIVE) - return 1; - m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); - if (m == LWS_TLS_EXTANT_ALTERNATIVE) - return 1; - if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && - (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { - lwsl_notice("Ignoring missing %s or %s\n", - info->ssl_cert_filepath, - info->ssl_private_key_filepath); - vhost->skipped_certs = 1; - return 0; - } - - /* 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 *)vhost->context->pt[0].serv_buf)); - - return 1; - } - - if (info->ssl_private_key_filepath != NULL) { - /* 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 *)vhost->context->pt[0].serv_buf)); - return 1; - } - } 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; - } - - /* verify private key */ - if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) { - lwsl_err("Private SSL key doesn't match cert\n"); - - return 1; - } - -#if defined(LWS_HAVE_OPENSSL_ECDH_H) - 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); - - if (lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH)) - lwsl_notice(" Using ECDH certificate support\n"); - - /* Get X509 certificate from ssl context */ -#if !defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS) - x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0); -#else - SSL_CTX_get_extra_chain_certs_only(vhost->ssl_ctx, &extra_certs); - if (extra_certs) - x = sk_X509_value(extra_certs, 0); - else - lwsl_err("%s: no extra certs\n", __func__); -#endif - if (!x) { - lwsl_err("%s: x is NULL\n", __func__); - return 0; // !!! - } - /* 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(EVP_PKEY_id(pkey)); - - 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); -#else - lwsl_notice(" OpenSSL doesn't support ECDH\n"); -#endif - - return 0; + return lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath, + info->ssl_private_key_filepath, + NULL, 0, NULL, 0); } int @@ -761,7 +803,7 @@ lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], BIO_free(bio); goto bail3; } - memcpy(*privkey_pem, p, bio_len); + memcpy(*privkey_pem, p, (int)(long long)bio_len); BIO_free(bio); ret = lws_ptr_diff(csr, csr_in); diff --git a/lib/tls/tls.c b/lib/tls/tls.c index e6e24b88..9204fa28 100644 --- a/lib/tls/tls.c +++ b/lib/tls/tls.c @@ -98,6 +98,154 @@ lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) wsi->pending_read_list_next = NULL; } +#if defined(LWS_WITH_ESP32) +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + nvs_handle nvh; + size_t s; + int n = 0; + + ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); + if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { + n = 1; + goto bail; + } + *buf = lws_malloc(s, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + +bail: + nvs_close(nvh); + + return n; +} +#else +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + FILE *f; + size_t s; + int n = 0; + + f = fopen(filename, "rb"); + if (f == NULL) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_END) != 0) { + n = 1; + goto bail; + } + + s = ftell(f); + if (s == (size_t)-1) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + n = 1; + goto bail; + } + + *buf = lws_malloc(s, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + + if (fread(*buf, s, 1, f) != 1) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + +bail: + if (f) + fclose(f); + + return n; + +} +#endif + +int +lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename, + const char *inbuf, lws_filepos_t inlen, + uint8_t **buf, lws_filepos_t *amount) +{ + const uint8_t *pem, *p, *end; + uint8_t *q; + lws_filepos_t len; + int n; + + if (filename) { + n = alloc_file(context, filename, (uint8_t **)&pem, &len); + if (n) + return n; + } else { + pem = (const uint8_t *)inbuf; + len = inlen; + } + + /* trim the first line */ + + p = pem; + end = p + len; + if (strncmp((char *)p, "-----", 5)) + goto bail; + p += 5; + while (p < end && *p != '\n' && *p != '-') + p++; + + if (*p != '-') + goto bail; + + while (p < end && *p != '\n') + p++; + + if (p >= end) + goto bail; + + p++; + + /* trim the last line */ + + q = (uint8_t *)end - 2; + + while (q > pem && *q != '\n') + q--; + + if (*q != '\n') + goto bail; + + *q = '\0'; + + *amount = lws_b64_decode_string((char *)p, (char *)pem, + (int)(long long)len); + *buf = (uint8_t *)pem; + + return 0; + +bail: + lws_free((uint8_t *)pem); + + return 4; +} + int lws_tls_check_cert_lifetime(struct lws_vhost *v) { @@ -165,7 +313,8 @@ lws_tls_extant(const char *name) * There are four situations and three results possible: * * 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to - * be provisioned) + * be provisioned). We also feel like this if we need privs we don't have + * any more to look in the directory. * * 2) There are provisioned certs written (xxx.upd) and we still have root * privs... in this case we rename any existing cert to have a backup name @@ -177,6 +326,10 @@ lws_tls_extant(const char *name) * case, indicate that the caller should use temp copies if any we do have * rights to access. This is normal after we have updated the cert. * + * But if we dropped privs, we can't detect the provisioned xxx.upd cert + + * key, because we can't see in the dir. So we have to upgrade NO to + * ALTERNATIVE when we actually have the in-memory alternative. + * * 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we * have the rights to read them. */ @@ -219,6 +372,84 @@ lws_tls_use_any_upgrade_check_extant(const char *name) return LWS_TLS_EXTANT_YES; } +/* + * LWS_TLS_EXTANT_NO : skip adding the cert + * LWS_TLS_EXTANT_YES : use the cert and private key paths normally + * LWS_TLS_EXTANT_ALTERNATIVE: normal paths not usable, try alternate if poss + */ +enum lws_tls_extant +lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert, + const char *private_key) +{ + int n, m; + + /* + * 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. + */ + + n = lws_tls_use_any_upgrade_check_extant(cert); + if (n == LWS_TLS_EXTANT_ALTERNATIVE) + return LWS_TLS_EXTANT_ALTERNATIVE; + m = lws_tls_use_any_upgrade_check_extant(private_key); + if (m == LWS_TLS_EXTANT_ALTERNATIVE) + return LWS_TLS_EXTANT_ALTERNATIVE; + + if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && + (vhost->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { + lwsl_notice("Ignoring missing %s or %s\n", cert, private_key); + vhost->skipped_certs = 1; + + return LWS_TLS_EXTANT_NO; + } + + /* + * the cert + key exist + */ + + return LWS_TLS_EXTANT_YES; +} + +#if !defined(LWS_NO_SERVER) +/* + * update the cert for every vhost using the given path + */ + +LWS_VISIBLE int +lws_tls_cert_updated(struct lws_context *context, const char *certpath, + const char *keypath, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t len_mem_privkey) +{ + struct lws wsi; + + wsi.context = context; + + lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + wsi.vhost = v; + if (v->alloc_cert_path && v->key_path && + !strcmp(v->alloc_cert_path, certpath) && + !strcmp(v->key_path, keypath)) { + lws_tls_server_certs_load(v, &wsi, certpath, keypath, + mem_cert, len_mem_cert, + mem_privkey, len_mem_privkey); + + if (v->skipped_certs) + lwsl_notice("%s: vhost %s: cert unset\n", + __func__, v->name); + } + } lws_end_foreach_ll(v, vhost_next); + + return 0; +} +#endif + int lws_gate_accepts(struct lws_context *context, int on) {