diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index e66bec27..112a390c 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -5568,6 +5568,44 @@ lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, LWS_VISIBLE LWS_EXTERN int lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, const char *san_b); + +enum { + LWS_TLS_REQ_ELEMENT_COUNTRY, + LWS_TLS_REQ_ELEMENT_STATE, + LWS_TLS_REQ_ELEMENT_LOCALITY, + LWS_TLS_REQ_ELEMENT_ORGANIZATION, + LWS_TLS_REQ_ELEMENT_COMMON_NAME, + LWS_TLS_REQ_ELEMENT_EMAIL, + + LWS_TLS_REQ_ELEMENT_COUNT +}; + +/** + * lws_tls_acme_sni_csr_create() - creates a CSR and related private key PEM + * + * \param context: lws_context used for random + * \param elements: array of LWS_TLS_REQ_ELEMENT_COUNT const char * + * \param csr: buffer that will get the b64URL(ASN-1 CSR) + * \param csr_len: max length of the csr buffer + * \param privkey_pem: pointer to pointer allocated to hold the privkey_pem + * \param privkey_len: pointer to size_t set to the length of the privkey_pem + * + * Creates a CSR according to the information in \p elements, and a private + * RSA key used to sign the CSR. + * + * The outputs are the b64URL(ASN-1 CSR) into csr, and the PEM private key into + * privkey_pem. + * + * Notice that \p elements points to an array of const char *s pointing to the + * information listed in the enum above. If an entry is NULL or an empty + * string, the element is set to "none" in the CSR. + * + * Returns 0 on success or nonzero for failure. + */ +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); ///@} /** \defgroup lws_ring LWS Ringbuffer APIs diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index c309abcc..93a3ced3 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -292,6 +292,7 @@ lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen); #if defined(LWS_WITH_MBEDTLS) #include #include +#include #include "tls/mbedtls/wrapper/include/openssl/ssl.h" /* wrapper !!!! */ #else #include diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c index f12731b7..69c08a59 100644 --- a/lib/tls/mbedtls/server.c +++ b/lib/tls/mbedtls/server.c @@ -20,6 +20,7 @@ */ #include "private-libwebsockets.h" +#include int lws_tls_server_client_cert_verify_config(struct lws_context_creation_info *info, @@ -181,6 +182,50 @@ bail: return 4; } +static int +lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx, + const unsigned char *servername, size_t len) +{ + SSL *ssl = SSL_SSL_from_mbedtls_ssl_context(mbedtls_ctx); + struct lws_context *context = (struct lws_context *)arg; + struct lws_vhost *vhost, *vh; + + lwsl_notice("%s: %s\n", __func__, servername); + + /* + * 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 && + vh->ssl_ctx == SSL_get_SSL_CTX(ssl)) + break; + vh = vh->vhost_next; + } + + if (!vh) { + assert(vh); /* can't match the incoming vh? */ + return 0; + } + + vhost = lws_select_vhost(context, vh->listen_port, + (const char *)servername); + if (!vhost) { + lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); + + return 0; + } + + lwsl_notice("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 0; +} + int lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, struct lws_vhost *vhost, struct lws *wsi) @@ -188,7 +233,7 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, const SSL_METHOD *method = TLS_server_method(); uint8_t *p; lws_filepos_t flen; - int err; + int n, m, err; vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ if (!vhost->ssl_ctx) { @@ -209,6 +254,20 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, * 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 < 0) + return 1; + m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); + if (m < 0) + return 1; + if ((n || m) && (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", @@ -276,6 +335,8 @@ lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) if (wsi->vhost->ssl_info_event_mask) SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); + SSL_set_sni_callback(wsi->ssl, lws_mbedtls_sni_cb, wsi->context); + return 0; } @@ -336,21 +397,295 @@ lws_tls_server_accept(struct lws *wsi) return LWS_SSL_CAPABLE_ERROR; } +/* + * mbedtls doesn't support SAN for cert creation. So we use a known-good + * tls-sni-01 cert from OpenSSL that worked on Let's Encrypt, and just replace + * the pubkey n part and the signature part. + * + * This will need redoing for tls-sni-02... + */ -struct lws_tls_ss_pieces { - mbedtls_x509_crt x509; +static uint8_t ss_cert_leadin[] = { + 0x30, 0x82, + 0x05, 0x51 + 5, /* total length */ -}; + 0x30, 0x82, 0x03, 0x39 + 5, + + /* addition: v3 cert (+5 bytes)*/ + 0xa0, 0x03, + 0x02, 0x01, 0x02, + + 0x02, 0x01, 0x01, + 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x3f, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, + 0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, + 0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x31, + 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11, 0x74, 0x65, + 0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x30, 0x1e, 0x17, 0x0d, + + /* from 2017-10-29 ... */ + 0x31, 0x37, 0x31, 0x30, 0x32, 0x39, 0x31, 0x31, 0x34, 0x39, 0x34, 0x35, + 0x5a, 0x17, 0x0d, + + /* thru 2049 (we immediately discard the private key, no worries */ + 0x34, 0x39, 0x31, 0x30, 0x32, 0x39, 0x31, 0x32, 0x34, 0x39, 0x34, 0x35, + 0x5a, + + 0x30, 0x3f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x47, 0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x0b, 0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x79, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11, + 0x74, 0x65, 0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, + 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, + + 0x02, 0x01, /* length of n in bytes (including leading 00 if any) */ + }, + + /* 513 bytes - 0x00 + 512-byte n */ + + ss_cert_san_leadin[] = { + /* e - fixed */ + 0x02, 0x03, 0x01, 0x00, 0x01, + + 0xa3, 0x5d, 0x30, 0x5b, 0x30, 0x59, 0x06, 0x03, 0x55, 0x1d, + 0x11, 0x04, 0x52, 0x30, 0x50, /* <-- SAN length + 2 */ + + 0x82, 0x4e, /* <-- SAN length */ + }, + + /* 78 bytes of SAN (tls-sni-01) + 0x61, 0x64, 0x34, 0x31, 0x61, 0x66, 0x62, 0x65, 0x30, 0x63, 0x61, 0x34, + 0x36, 0x34, 0x32, 0x66, 0x30, 0x61, 0x34, 0x34, 0x39, 0x64, 0x39, 0x63, + 0x61, 0x37, 0x36, 0x65, 0x62, 0x61, 0x61, 0x62, 0x2e, 0x32, 0x38, 0x39, + 0x34, 0x64, 0x34, 0x31, 0x36, 0x63, 0x39, 0x38, 0x33, 0x66, 0x31, 0x32, + 0x65, 0x64, 0x37, 0x33, 0x31, 0x61, 0x33, 0x30, 0x66, 0x35, 0x63, 0x34, + 0x34, 0x37, 0x37, 0x66, 0x65, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, */ + ss_cert_sig_leadin[] = { + /* it's saying that the signature is SHA256 + RSA */ + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, + }; + + /* 512-byte / 4096-bit signature to end */ LWS_VISIBLE int lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, const char *san_b) { + int buflen = 0x560; + uint8_t *buf = lws_malloc(buflen, "temp cert buf"), *p = buf, *pkey_asn1; + struct lws_genrsa_ctx ctx; + struct lws_genrsa_elements el; + uint8_t digest[32]; + struct lws_genhash_ctx hash_ctx; + int pkey_asn1_len = 3 * 1024; + int n; - return 1; + if (!buf) + return 1; + + n = lws_genrsa_new_keypair(vhost->context, &ctx, &el, 4096); + if (n < 0) { + lws_jwk_destroy_genrsa_elements(&el); + goto bail1; + } + + memcpy(p, ss_cert_leadin, sizeof(ss_cert_leadin)); + p += sizeof(ss_cert_leadin); + + /* we need to drop 513 bytes of n in here 00 + 512-bytes */ + + *p++ = 0x00; + memcpy(p, el.e[JWK_KEY_N].buf, el.e[JWK_KEY_N].len); + p += 512; + + memcpy(p, ss_cert_san_leadin, sizeof(ss_cert_san_leadin)); + p += sizeof(ss_cert_san_leadin); + + /* drop in 78 bytes of san_a */ + + memcpy(p, san_a, 78); + p += 78; + memcpy(p, ss_cert_sig_leadin, sizeof(ss_cert_sig_leadin)); + p += sizeof(ss_cert_sig_leadin); + + /* hash the cert plaintext */ + + if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256)) + goto bail2; + + if (lws_genhash_update(&hash_ctx, buf, lws_ptr_diff(p, buf))) { + lws_genhash_destroy(&hash_ctx, NULL); + + goto bail2; + } + if (lws_genhash_destroy(&hash_ctx, digest)) + goto bail2; + + /* sign the hash */ + + n = lws_genrsa_public_sign(&ctx, digest, LWS_GENHASH_TYPE_SHA256, p, + buflen - lws_ptr_diff(p, buf)); + if (n < 0) + goto bail2; + p += n; + + pkey_asn1 = lws_malloc(pkey_asn1_len, "mbed crt tmp"); + if (!pkey_asn1) + goto bail2; + + n = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len); + if (n < 0) { + lws_free(pkey_asn1); + goto bail2; + } + lwsl_debug("private key\n"); + lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n); + + /* and to use our generated private key */ + n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, pkey_asn1, n); + lws_free(pkey_asn1); + if (n != 1) { + lwsl_notice("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n", + __func__); + } + + lws_genrsa_destroy(&ctx); + lws_jwk_destroy_genrsa_elements(&el); + + lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf)); + + n = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, + lws_ptr_diff(p, buf), buf); + if (n != 1) + lwsl_notice("%s: generated cert failed to load 0x%x\n", + __func__, -n); + + lws_free(buf); + + return n != 1; + +bail2: + lws_genrsa_destroy(&ctx); + lws_jwk_destroy_genrsa_elements(&el); +bail1: + lws_free(buf); + + return -1; } void lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost) { } + +#if defined(LWS_WITH_JWS) +static int +_rngf(void *context, unsigned char *buf, size_t len) +{ + if ((size_t)lws_get_random(context, buf, len) == len) + return 0; + + return -1; +} + +/* + * CSR is output formatted as b64url(DER) + * Private key is output as a PEM in memory + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], + uint8_t *dcsr, size_t csr_len, char **privkey_pem, + size_t *privkey_len) +{ + mbedtls_x509write_csr csr; + char subject[200]; + mbedtls_pk_context mpk; + int buf_size = 4096, n; + uint8_t *buf = malloc(buf_size); /* malloc because given to user code */ + + if (!buf) + return -1; + + mbedtls_x509write_csr_init(&csr); + + mbedtls_pk_init(&mpk); + if (mbedtls_pk_setup(&mpk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) { + lwsl_notice("%s: pk_setup failed\n", __func__); + goto fail; + } + + n = mbedtls_rsa_gen_key(mbedtls_pk_rsa(mpk), _rngf, context, + 4096, 65537); + if (n) { + lwsl_notice("%s: failed to generate keys\n", __func__); + + goto fail1; + } + + /* subject must be formatted like "C=TW,O=warmcat,CN=myserver" */ + + lws_snprintf(subject, sizeof(subject) - 1, + "C=%s,ST=%s,L=%s,O=%s,CN=%s", + elements[LWS_TLS_REQ_ELEMENT_COUNTRY], + elements[LWS_TLS_REQ_ELEMENT_STATE], + elements[LWS_TLS_REQ_ELEMENT_LOCALITY], + elements[LWS_TLS_REQ_ELEMENT_ORGANIZATION], + elements[LWS_TLS_REQ_ELEMENT_COMMON_NAME]); + if (mbedtls_x509write_csr_set_subject_name(&csr, subject)) + goto fail1; + + mbedtls_x509write_csr_set_key(&csr, &mpk); + mbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256); + + /* + * data is written at the end of the buffer! Use the + * return value to determine where you should start + * using the buffer + */ + n = mbedtls_x509write_csr_der(&csr, buf, buf_size, _rngf, context); + if (n < 0) { + lwsl_notice("%s: write csr der failed\n", __func__); + goto fail1; + } + + /* we have it in DER, we need it in b64URL */ + + n = lws_jws_base64_enc((char *)(buf + buf_size) - n, n, + (char *)dcsr, csr_len); + if (n < 0) + goto fail1; + + /* + * okay, the CSR is done, last we need the private key in PEM + * re-use the DER CSR buf as the result buffer since we cn do it in + * one step + */ + + if (mbedtls_pk_write_key_pem(&mpk, buf, buf_size)) { + lwsl_notice("write key pem failed\n"); + goto fail1; + } + + *privkey_pem = (char *)buf; + *privkey_len = strlen((const char *)buf); + + mbedtls_pk_free(&mpk); + mbedtls_x509write_csr_free(&csr); + + return n; + +fail1: + mbedtls_pk_free(&mpk); +fail: + mbedtls_x509write_csr_free(&csr); + free(buf); + + return -1; +} +#endif diff --git a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h index fc57d1fd..232a6ee6 100755 --- a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h +++ b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h @@ -41,6 +41,13 @@ mbedtls_x509_crt * ssl_get_peer_mbedtls_x509_crt(SSL *ssl); + int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *, + const unsigned char *, size_t), void *param); + + void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx); + + SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc); + /** * @brief create a SSL context * diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index 8c89a698..e14173b1 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -41,6 +41,8 @@ struct ssl_pm mbedtls_ssl_context ssl; mbedtls_entropy_context entropy; + + SSL *owner; }; struct x509_pm @@ -109,6 +111,8 @@ int ssl_pm_new(SSL *ssl) goto no_mem; } + ssl_pm->owner = ssl; + if (!ssl->ctx->read_buffer_len) ssl->ctx->read_buffer_len = 2048; @@ -611,22 +615,27 @@ int x509_pm_load(X509 *x, const unsigned char *buffer, int len) } } - load_buf = ssl_mem_malloc(len + 1); - if (!load_buf) { - SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)"); - goto failed; + mbedtls_x509_crt_init(x509_pm->x509_crt); + if (buffer[0] != 0x30) { + load_buf = ssl_mem_malloc(len + 1); + if (!load_buf) { + SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)"); + goto failed; + } + + ssl_memcpy(load_buf, buffer, len); + load_buf[len] = '\0'; + + ret = mbedtls_x509_crt_parse(x509_pm->x509_crt, load_buf, len + 1); + ssl_mem_free(load_buf); + } else { + printf("parsing as der\n"); + + ret = mbedtls_x509_crt_parse_der(x509_pm->x509_crt, buffer, len); } - ssl_memcpy(load_buf, buffer, len); - load_buf[len] = '\0'; - - mbedtls_x509_crt_init(x509_pm->x509_crt); - - ret = mbedtls_x509_crt_parse(x509_pm->x509_crt, load_buf, len + 1); - ssl_mem_free(load_buf); - if (ret) { - SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_x509_crt_parse return -0x%x", -ret); + printf("mbedtls_x509_crt_parse return -0x%x", -ret); goto failed; } @@ -791,3 +800,40 @@ void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, *len = 0; } +int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *, + const unsigned char *, size_t), void *param) +{ + struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; + + mbedtls_ssl_conf_sni(&ssl_pm->conf, cb, param); + + return 0; +} + +SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc) +{ + struct ssl_pm *ssl_pm = (struct ssl_pm *)((char *)msc - offsetof(struct ssl_pm, ssl)); + + return ssl_pm->owner; +} + +#include "ssl_cert.h" + +void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) +{ + struct ssl_pm *ssl_pm = ssl->ssl_pm; + struct x509_pm *x509_pm = (struct x509_pm *)ctx->cert->x509->x509_pm; + struct pkey_pm *pkey_pm = (struct pkey_pm *)ctx->cert->pkey->pkey_pm; + + if (ssl->cert) + ssl_cert_free(ssl->cert); + ssl->ctx = ctx; + ssl->cert = __ssl_cert_new(ctx->cert); + + /* apply new ctx cert to ssl */ + + mbedtls_ssl_set_hs_own_cert(&ssl_pm->ssl, x509_pm->x509_crt, pkey_pm->pkey); + mbedtls_ssl_set_hs_authmode(&ssl_pm->ssl, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_set_hs_ca_chain(&ssl_pm->ssl, x509_pm->x509_crt, NULL); + +} diff --git a/lib/tls/openssl/server.c b/lib/tls/openssl/server.c index 5e0b3e14..9fb4ee8b 100644 --- a/lib/tls/openssl/server.c +++ b/lib/tls/openssl/server.c @@ -153,7 +153,7 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, #endif SSL_METHOD *method = (SSL_METHOD *)SSLv23_server_method(); unsigned long error; - int n; + int n, m; if (!method) { error = ERR_get_error(); @@ -184,7 +184,7 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, if (info->ssl_cipher_list) SSL_CTX_set_cipher_list(vhost->ssl_ctx, info->ssl_cipher_list); -#if !defined(LWS_WITH_MBEDTLS) && !defined(OPENSSL_NO_TLSEXT) +#if !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, vhost->context); @@ -210,6 +210,8 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, if (!vhost->use_ssl || !info->ssl_cert_filepath) return 0; + 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 @@ -220,6 +222,21 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, * 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 < 0) + return 1; + m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); + if (m < 0) + return 1; + if ((n || m) && (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); @@ -231,7 +248,6 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, return 1; } - lws_ssl_bind_passphrase(vhost->ssl_ctx, info); if (info->ssl_private_key_filepath != NULL) { /* set the private key from KeyFile */ @@ -413,7 +429,8 @@ lws_tls_server_accept(struct lws *wsi) if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) { if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { - lwsl_info("%s: WANT_READ change_pollfd failed\n", __func__); + lwsl_info("%s: WANT_READ change_pollfd failed\n", + __func__); return LWS_SSL_CAPABLE_ERROR; } @@ -424,7 +441,8 @@ lws_tls_server_accept(struct lws *wsi) lwsl_debug("%s: WANT_WRITE\n", __func__); if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { - lwsl_info("%s: WANT_WRITE change_pollfd failed\n", __func__); + lwsl_info("%s: WANT_WRITE change_pollfd failed\n", + __func__); return LWS_SSL_CAPABLE_ERROR; } return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; @@ -433,6 +451,37 @@ lws_tls_server_accept(struct lws *wsi) return LWS_SSL_CAPABLE_ERROR; } +static int +lws_tls_openssl_rsa_new_key(RSA **rsa, int bits) +{ + BIGNUM *bn = BN_new(); + int n; + + if (!bn) + return 1; + + if (BN_set_word(bn, RSA_F4) != 1) { + BN_free(bn); + return 1; + } + + *rsa = RSA_new(); + if (!*rsa) { + BN_free(bn); + return 1; + } + + n = RSA_generate_key_ex(*rsa, bits, bn, NULL); + BN_free(bn); + if (n == 1) + return 0; + + RSA_free(*rsa); + *rsa = NULL; + + return 1; +} + struct lws_tls_ss_pieces { X509 *x509; EVP_PKEY *pkey; @@ -447,8 +496,6 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, GENERAL_NAME *gen = NULL; ASN1_IA5STRING *ia5 = NULL; X509_NAME *name; - BIGNUM *bn; - int n; if (!gens) return 1; @@ -471,24 +518,8 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, if (!vhost->ss->pkey) goto bail0; - bn = BN_new(); - if (!bn) + if (lws_tls_openssl_rsa_new_key(&vhost->ss->rsa, 4096)) goto bail1; - if (BN_set_word(bn, RSA_F4) != 1) { - BN_free(bn); - goto bail1; - } - - vhost->ss->rsa = RSA_new(); - if (!vhost->ss->rsa) { - BN_free(bn); - goto bail1; - } - - n = RSA_generate_key_ex(vhost->ss->rsa, 4096, bn, NULL); - BN_free(bn); - if (n != 1) - goto bail2; if (!EVP_PKEY_assign_RSA(vhost->ss->pkey, vhost->ss->rsa)) goto bail2; @@ -549,6 +580,15 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, if (!X509_sign(vhost->ss->x509, vhost->ss->pkey, EVP_sha256())) goto bail2; +#if 0 + {/* useful to take a sample of a working cert for mbedtls to crib */ + FILE *fp = fopen("/tmp/acme-temp-cert", "w+"); + + i2d_X509_fp(fp, vhost->ss->x509); + fclose(fp); + } +#endif + /* tell the vhost to use our crafted certificate */ SSL_CTX_use_certificate(vhost->ssl_ctx, vhost->ss->x509); /* and to use our generated private key */ @@ -579,3 +619,160 @@ lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost) X509_free(vhost->ss->x509); lws_free_set_NULL(vhost->ss); } + +static int +lws_tls_openssl_add_nid(X509_NAME *name, int nid, const char *value) +{ + X509_NAME_ENTRY *e; + int n; + + if (!value || value[0] == '\0') + value = "none"; + + e = X509_NAME_ENTRY_create_by_NID(NULL, nid, MBSTRING_ASC, + (unsigned char *)value, -1); + if (!e) + return 1; + n = X509_NAME_add_entry(name, e, -1, 0); + X509_NAME_ENTRY_free(e); + + return n != 1; +} + +static int nid_list[] = { + NID_countryName, /* LWS_TLS_REQ_ELEMENT_COUNTRY */ + NID_stateOrProvinceName, /* LWS_TLS_REQ_ELEMENT_STATE */ + NID_localityName, /* LWS_TLS_REQ_ELEMENT_LOCALITY */ + NID_organizationName, /* LWS_TLS_REQ_ELEMENT_ORGANIZATION */ + NID_commonName, /* LWS_TLS_REQ_ELEMENT_COMMON_NAME */ + NID_organizationalUnitName, /* LWS_TLS_REQ_ELEMENT_EMAIL */ +}; + +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) +{ + uint8_t *csr_in = csr; + RSA *rsakey; + X509_REQ *req; + X509_NAME *subj; + EVP_PKEY *pkey; + char *p, *end; + BIO *bio; + long bio_len; + int n, ret = -1; + + if (lws_tls_openssl_rsa_new_key(&rsakey, 4096)) + return -1; + + pkey = EVP_PKEY_new(); + if (!pkey) + goto bail0; + if (!EVP_PKEY_set1_RSA(pkey, rsakey)) + goto bail1; + + req = X509_REQ_new(); + if (!req) + goto bail1; + + X509_REQ_set_pubkey(req, pkey); + + subj = X509_NAME_new(); + if (!subj) + goto bail2; + + for (n = 0; n < LWS_TLS_REQ_ELEMENT_COUNT; n++) + if (lws_tls_openssl_add_nid(subj, nid_list[n], elements[n])) { + lwsl_notice("%s: failed to add element %d\n", __func__, n); + goto bail3; + } + + if (X509_REQ_set_subject_name(req, subj) != 1) + goto bail3; + + if (!X509_REQ_sign(req, pkey, EVP_sha256())) + goto bail3; + + /* + * issue the CSR as PEM to a BIO, and translate to b64urlenc without + * headers, trailers, or whitespace + */ + + bio = BIO_new(BIO_s_mem()); + if (!bio) + goto bail3; + + if (PEM_write_bio_X509_REQ(bio, req) != 1) { + BIO_free(bio); + goto bail3; + } + + bio_len = BIO_get_mem_data(bio, &p); + end = p + bio_len; + + /* strip the header line */ + while (p < end && *p != '\n') + p++; + + while (p < end && csr_len) { + if (*p == '\n') { + p++; + continue; + } + + if (*p == '-') + break; + + if (*p == '+') + *csr++ = '-'; + else + if (*p == '/') + *csr++ = '_'; + else + *csr++ = *p; + p++; + csr_len--; + } + BIO_free(bio); + if (!csr_len) { + lwsl_notice("%s: need %ld for CSR\n", __func__, bio_len); + goto bail3; + } + + /* + * Also return the private key as a PEM in memory + * (platform may not have a filesystem) + */ + bio = BIO_new(BIO_s_mem()); + if (!bio) + goto bail3; + + if (PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, 0, NULL) != 1) { + BIO_free(bio); + goto bail3; + } + bio_len = BIO_get_mem_data(bio, &p); + *privkey_pem = malloc(bio_len); /* malloc so user code can own / free */ + *privkey_len = (size_t)bio_len; + if (!*privkey_pem) { + lwsl_notice("%s: need %ld for private key\n", __func__, bio_len); + BIO_free(bio); + goto bail3; + } + memcpy(*privkey_pem, p, bio_len); + BIO_free(bio); + + ret = lws_ptr_diff(csr, csr_in); + +bail3: + X509_NAME_free(subj); +bail2: + X509_REQ_free(req); +bail1: + EVP_PKEY_free(pkey); +bail0: + RSA_free(rsakey); + + return ret; +}