diff options
author | Valentin Bartenev <vbart@nginx.com> | 2018-09-20 15:27:08 +0300 |
---|---|---|
committer | Valentin Bartenev <vbart@nginx.com> | 2018-09-20 15:27:08 +0300 |
commit | 8d844bc2aa78d5acc789c5865a62bedbeba76902 (patch) | |
tree | cbc6e225402434d5b14cb454a2b7c02cebc93bbf /src | |
parent | 2dfd8ffc2fa36712c8987afa870c355185d61af9 (diff) | |
download | unit-8d844bc2aa78d5acc789c5865a62bedbeba76902.tar.gz unit-8d844bc2aa78d5acc789c5865a62bedbeba76902.tar.bz2 |
Controller: certificates storage interface.
Diffstat (limited to '')
-rw-r--r-- | src/nxt_cert.c | 1204 | ||||
-rw-r--r-- | src/nxt_cert.h | 32 | ||||
-rw-r--r-- | src/nxt_conf.c | 99 | ||||
-rw-r--r-- | src/nxt_conf.h | 12 | ||||
-rw-r--r-- | src/nxt_conf_validation.c | 52 | ||||
-rw-r--r-- | src/nxt_controller.c | 422 | ||||
-rw-r--r-- | src/nxt_main_process.c | 54 | ||||
-rw-r--r-- | src/nxt_main_process.h | 8 | ||||
-rw-r--r-- | src/nxt_openssl.c | 94 | ||||
-rw-r--r-- | src/nxt_port.h | 6 | ||||
-rw-r--r-- | src/nxt_router.c | 136 | ||||
-rw-r--r-- | src/nxt_router.h | 4 | ||||
-rw-r--r-- | src/nxt_runtime.c | 17 | ||||
-rw-r--r-- | src/nxt_runtime.h | 2 | ||||
-rw-r--r-- | src/nxt_tls.h | 3 |
15 files changed, 2102 insertions, 43 deletions
diff --git a/src/nxt_cert.c b/src/nxt_cert.c new file mode 100644 index 00000000..ee258646 --- /dev/null +++ b/src/nxt_cert.c @@ -0,0 +1,1204 @@ + +/* + * Copyright (C) Valentin V. Bartenev + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_main.h> +#include <nxt_conf.h> +#include <nxt_cert.h> + +#include <dirent.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/rsa.h> +#include <openssl/err.h> + + +struct nxt_cert_s { + EVP_PKEY *key; + nxt_uint_t count; + X509 *chain[]; +}; + + +typedef struct { + nxt_str_t name; + nxt_conf_value_t *value; + nxt_mp_t *mp; +} nxt_cert_info_t; + + +typedef struct { + nxt_str_t name; + nxt_fd_t fd; +} nxt_cert_item_t; + + +static nxt_cert_t *nxt_cert_fd(nxt_task_t *task, nxt_fd_t fd); +static nxt_cert_t *nxt_cert_bio(nxt_task_t *task, BIO *bio); +static int nxt_nxt_cert_pem_suffix(char *pem_str, const char *suffix); + +static nxt_conf_value_t *nxt_cert_details(nxt_mp_t *mp, nxt_cert_t *cert); +static nxt_conf_value_t *nxt_cert_name_details(nxt_mp_t *mp, X509 *x509, + nxt_bool_t issuer); + + +static nxt_lvlhsh_t nxt_cert_info; + + +nxt_cert_t * +nxt_cert_mem(nxt_task_t *task, nxt_buf_mem_t *mbuf) +{ + BIO *bio; + nxt_cert_t *cert; + + bio = BIO_new_mem_buf(mbuf->pos, nxt_buf_mem_used_size(mbuf)); + if (nxt_slow_path(bio == NULL)) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, "BIO_new_mem_buf() failed"); + return NULL; + } + + cert = nxt_cert_bio(task, bio); + + BIO_free(bio); + + return cert; +} + + +static nxt_cert_t * +nxt_cert_fd(nxt_task_t *task, nxt_fd_t fd) +{ + BIO *bio; + nxt_cert_t *cert; + + bio = BIO_new_fd(fd, 0); + if (nxt_slow_path(bio == NULL)) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, "BIO_new_fd() failed"); + return NULL; + } + + cert = nxt_cert_bio(task, bio); + + BIO_free(bio); + + return cert; +} + + +static nxt_cert_t * +nxt_cert_bio(nxt_task_t *task, BIO *bio) +{ + int ret, suffix, key_id; + long length, reason; + char *type, *header; + X509 *x509; + EVP_PKEY *key; + nxt_uint_t nalloc; + nxt_cert_t *cert, *new_cert; + u_char *data; + const u_char *data_copy; + PKCS8_PRIV_KEY_INFO *p8inf; + const EVP_PKEY_ASN1_METHOD *ameth; + + nalloc = 4; + + cert = nxt_zalloc(sizeof(nxt_cert_t) + nalloc * sizeof(X509 *)); + if (cert == NULL) { + return NULL; + } + + for ( ;; ) { + ret = PEM_read_bio(bio, &type, &header, &data, &length); + + if (ret == 0) { + reason = ERR_GET_REASON(ERR_peek_last_error()); + if (reason != PEM_R_NO_START_LINE) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "PEM_read_bio() failed"); + goto fail; + } + + ERR_clear_error(); + break; + } + + nxt_debug(task, "PEM type: \"%s\"", type); + + key = NULL; + x509 = NULL; +/* + EVP_CIPHER_INFO cipher; + + if (PEM_get_EVP_CIPHER_INFO(header, &cipher) != 0) { + nxt_alert(task, "encrypted PEM isn't supported"); + goto done; + } +*/ + if (nxt_strcmp(type, PEM_STRING_PKCS8) == 0) { + nxt_alert(task, "PEM PKCS8 isn't supported"); + goto done; + } + + if (nxt_strcmp(type, PEM_STRING_PKCS8INF) == 0) { + data_copy = data; + + p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &data_copy, length); + + if (p8inf == NULL) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "d2i_PKCS8_PRIV_KEY_INFO() failed"); + goto done; + } + + key = EVP_PKCS82PKEY(p8inf); + + PKCS8_PRIV_KEY_INFO_free(p8inf); + goto done; + } + + suffix = nxt_nxt_cert_pem_suffix(type, PEM_STRING_PKCS8INF); + + if (suffix != 0) { + + ameth = EVP_PKEY_asn1_find_str(NULL, type, suffix); + if (ameth == NULL) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "EVP_PKEY_asn1_find_str() failed"); + goto done; + } + + EVP_PKEY_asn1_get0_info(&key_id, NULL, NULL, NULL, NULL, ameth); + + data_copy = data; + + key = d2i_PrivateKey(key_id, NULL, &data_copy, length); + goto done; + } + + if (nxt_strcmp(type, PEM_STRING_X509) == 0 + || nxt_strcmp(type, PEM_STRING_X509_OLD) == 0) + { + data_copy = data; + + x509 = d2i_X509(NULL, &data_copy, length); + if (x509 == NULL) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "d2i_X509() failed"); + } + + goto done; + } + + if (nxt_strcmp(type, PEM_STRING_X509_TRUSTED) == 0) { + data_copy = data; + + x509 = d2i_X509_AUX(NULL, &data_copy, length); + if (x509 == NULL) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "d2i_X509_AUX() failed"); + } + + goto done; + } + + nxt_alert(task, "unsupported PEM type: \"%s\"", type); + + done: + + OPENSSL_free(data); + OPENSSL_free(header); + OPENSSL_free(type); + + if (key != NULL) { + if (cert->key != NULL) { + EVP_PKEY_free(key); + nxt_alert(task, "multiple private keys in PEM"); + goto fail; + } + + cert->key = key; + continue; + } + + if (x509 != NULL) { + + if (cert->count == nalloc) { + nalloc += 4; + + new_cert = nxt_realloc(cert, sizeof(nxt_cert_t) + + nalloc * sizeof(X509 *)); + if (new_cert == NULL) { + X509_free(x509); + goto fail; + } + + nxt_free(cert); + cert = new_cert; + } + + cert->chain[cert->count++] = x509; + continue; + } + + goto fail; + } + + if (cert->key == NULL) { + nxt_alert(task, "no key found"); + goto fail; + } + + if (cert->count == 0) { + nxt_alert(task, "no certificates found"); + goto fail; + } + + return cert; + +fail: + + nxt_cert_destroy(cert); + + return NULL; +} + + +static int +nxt_nxt_cert_pem_suffix(char *pem_str, const char *suffix) +{ + char *p; + nxt_uint_t pem_len, suffix_len; + + pem_len = strlen(pem_str); + suffix_len = strlen(suffix); + + if (suffix_len + 1 >= pem_len) { + return 0; + } + + p = pem_str + pem_len - suffix_len; + + if (nxt_strcmp(p, suffix) != 0) { + return 0; + } + + p--; + + if (*p != ' ') { + return 0; + } + + return p - pem_str; +} + + +void +nxt_cert_destroy(nxt_cert_t *cert) +{ + nxt_uint_t i; + + EVP_PKEY_free(cert->key); + + for (i = 0; i != cert->count; i++) { + X509_free(cert->chain[i]); + } + + nxt_free(cert); +} + + + +static nxt_int_t +nxt_cert_info_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + nxt_cert_info_t *info; + + info = data; + + if (nxt_strcasestr_eq(&lhq->key, &info->name)) { + return NXT_OK; + } + + return NXT_DECLINED; +} + + +static const nxt_lvlhsh_proto_t nxt_cert_info_hash_proto + nxt_aligned(64) = +{ + NXT_LVLHSH_DEFAULT, + nxt_cert_info_hash_test, + nxt_lvlhsh_alloc, + nxt_lvlhsh_free, +}; + + +void +nxt_cert_info_init(nxt_task_t *task, nxt_array_t *certs) +{ + uint32_t i; + nxt_cert_t *cert; + nxt_cert_item_t *items; + + for (items = certs->elts, i = 0; i < certs->nelts; i++) { + cert = nxt_cert_fd(task, items[i].fd); + + if (nxt_slow_path(cert == NULL)) { + continue; + } + + (void) nxt_cert_info_save(&items[i].name, cert); + + nxt_cert_destroy(cert); + } +} + + +nxt_int_t +nxt_cert_info_save(nxt_str_t *name, nxt_cert_t *cert) +{ + nxt_mp_t *mp; + nxt_int_t ret; + nxt_cert_info_t *info; + nxt_conf_value_t *value; + nxt_lvlhsh_query_t lhq; + + mp = nxt_mp_create(1024, 128, 256, 32); + if (nxt_slow_path(mp == NULL)) { + return NXT_ERROR; + } + + info = nxt_mp_get(mp, sizeof(nxt_cert_info_t)); + if (nxt_slow_path(info == NULL)) { + goto fail; + } + + name = nxt_str_dup(mp, &info->name, name); + if (nxt_slow_path(name == NULL)) { + goto fail; + } + + value = nxt_cert_details(mp, cert); + if (nxt_slow_path(value == NULL)) { + goto fail; + } + + info->mp = mp; + info->value = value; + + lhq.key_hash = nxt_djb_hash(name->start, name->length); + lhq.replace = 1; + lhq.key = *name; + lhq.value = info; + lhq.proto = &nxt_cert_info_hash_proto; + + ret = nxt_lvlhsh_insert(&nxt_cert_info, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + + if (lhq.value != info) { + info = lhq.value; + nxt_mp_destroy(info->mp); + } + + return NXT_OK; + +fail: + + nxt_mp_destroy(mp); + return NXT_ERROR; +} + + +nxt_conf_value_t * +nxt_cert_info_get(nxt_str_t *name) +{ + nxt_int_t ret; + nxt_cert_info_t *info; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = nxt_djb_hash(name->start, name->length); + lhq.key = *name; + lhq.proto = &nxt_cert_info_hash_proto; + + ret = nxt_lvlhsh_find(&nxt_cert_info, &lhq); + if (ret != NXT_OK) { + return NULL; + } + + info = lhq.value; + + return info->value; +} + + +nxt_conf_value_t * +nxt_cert_info_get_all(nxt_mp_t *mp) +{ + uint32_t i; + nxt_cert_info_t *info; + nxt_conf_value_t *all; + nxt_lvlhsh_each_t lhe; + + nxt_lvlhsh_each_init(&lhe, &nxt_cert_info_hash_proto); + + i = 0; + + for ( ;; ) { + info = nxt_lvlhsh_each(&nxt_cert_info, &lhe); + + if (info == NULL) { + break; + } + + i++; + } + + all = nxt_conf_create_object(mp, i); + if (nxt_slow_path(all == NULL)) { + return NULL; + } + + nxt_lvlhsh_each_init(&lhe, &nxt_cert_info_hash_proto); + + i = 0; + + for ( ;; ) { + info = nxt_lvlhsh_each(&nxt_cert_info, &lhe); + + if (info == NULL) { + break; + } + + nxt_conf_set_member(all, &info->name, info->value, i); + + i++; + } + + return all; +} + + +static nxt_conf_value_t * +nxt_cert_details(nxt_mp_t *mp, nxt_cert_t *cert) +{ + BIO *bio; + X509 *x509; + u_char *end; + EVP_PKEY *key; + ASN1_TIME *asn1_time; + nxt_str_t str; + nxt_int_t ret; + nxt_uint_t i; + nxt_conf_value_t *object, *chain, *element, *value; + u_char buf[256]; + + static nxt_str_t key_str = nxt_string("key"); + static nxt_str_t chain_str = nxt_string("chain"); + static nxt_str_t since_str = nxt_string("since"); + static nxt_str_t until_str = nxt_string("until"); + static nxt_str_t issuer_str = nxt_string("issuer"); + static nxt_str_t subject_str = nxt_string("subject"); + static nxt_str_t validity_str = nxt_string("validity"); + + object = nxt_conf_create_object(mp, 2); + if (nxt_slow_path(object == NULL)) { + return NULL; + } + + if (cert->key != NULL) { + key = cert->key; + + switch (EVP_PKEY_base_id(key)) { + case EVP_PKEY_RSA: + end = nxt_sprintf(buf, buf + sizeof(buf), "RSA (%d bits)", + EVP_PKEY_bits(key)); + + str.length = end - buf; + str.start = buf; + break; + + case EVP_PKEY_DH: + end = nxt_sprintf(buf, buf + sizeof(buf), "DH (%d bits)", + EVP_PKEY_bits(key)); + + str.length = end - buf; + str.start = buf; + break; + + case EVP_PKEY_EC: + nxt_str_set(&str, "ECDH"); + break; + + default: + nxt_str_set(&str, "unknown"); + } + + ret = nxt_conf_set_member_string_dup(object, mp, &key_str, &str, 0); + + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + } else { + nxt_conf_set_member_null(object, &key_str, 0); + } + + chain = nxt_conf_create_array(mp, cert->count); + if (nxt_slow_path(chain == NULL)) { + return NULL; + } + + for (i = 0; i < cert->count; i++) { + element = nxt_conf_create_object(mp, 3); + if (nxt_slow_path(element == NULL)) { + return NULL; + } + + x509 = cert->chain[i]; + + value = nxt_cert_name_details(mp, x509, 0); + if (value == NULL) { + return NULL; + } + + nxt_conf_set_member(element, &subject_str, value, 0); + + value = nxt_cert_name_details(mp, x509, 1); + if (value == NULL) { + return NULL; + } + + nxt_conf_set_member(element, &issuer_str, value, 1); + + value = nxt_conf_create_object(mp, 2); + if (nxt_slow_path(value == NULL)) { + return NULL; + } + + bio = BIO_new(BIO_s_mem()); + if (nxt_slow_path(bio == NULL)) { + return NULL; + } + + asn1_time = X509_get_notBefore(x509); + + ret = ASN1_TIME_print(bio, asn1_time); + + if (nxt_fast_path(ret == 1)) { + str.length = BIO_get_mem_data(bio, &str.start); + ret = nxt_conf_set_member_string_dup(value, mp, &since_str, &str, + 0); + } else { + ret = NXT_ERROR; + } + + BIO_free(bio); + + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + bio = BIO_new(BIO_s_mem()); + if (nxt_slow_path(bio == NULL)) { + return NULL; + } + + asn1_time = X509_get_notAfter(x509); + + ret = ASN1_TIME_print(bio, asn1_time); + + if (nxt_fast_path(ret == 1)) { + str.length = BIO_get_mem_data(bio, &str.start); + ret = nxt_conf_set_member_string_dup(value, mp, &until_str, &str, + 1); + } else { + ret = NXT_ERROR; + } + + BIO_free(bio); + + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + nxt_conf_set_member(element, &validity_str, value, 2); + + nxt_conf_set_element(chain, i, element); + } + + nxt_conf_set_member(object, &chain_str, chain, 1); + + return object; +} + + +typedef struct { + int nid; + nxt_str_t name; +} nxt_cert_nid_t; + + +static nxt_conf_value_t * +nxt_cert_name_details(nxt_mp_t *mp, X509 *x509, nxt_bool_t issuer) +{ + int len; + X509_NAME *x509_name; + nxt_str_t str; + nxt_int_t ret; + nxt_uint_t i, n, count; + GENERAL_NAME *name; + nxt_conf_value_t *object, *names; + STACK_OF(GENERAL_NAME) *alt_names; + u_char buf[256]; + + static nxt_cert_nid_t nids[] = { + { NID_commonName, nxt_string("common_name") }, + { NID_countryName, nxt_string("country") }, + { NID_stateOrProvinceName, nxt_string("state_or_province") }, + { NID_localityName, nxt_string("locality") }, + { NID_organizationName, nxt_string("organization") }, + { NID_organizationalUnitName, nxt_string("department") }, + }; + + static nxt_str_t alt_names_str = nxt_string("alt_names"); + + count = 0; + + x509_name = issuer ? X509_get_issuer_name(x509) + : X509_get_subject_name(x509); + + for (n = 0; n != nxt_nitems(nids); n++) { + + if (X509_NAME_get_index_by_NID(x509_name, nids[n].nid, -1) < 0) { + continue; + } + + count++; + } + + alt_names = X509_get_ext_d2i(x509, issuer ? NID_issuer_alt_name + : NID_subject_alt_name, + NULL, NULL); + + if (alt_names != NULL) { + count++; + } + + object = nxt_conf_create_object(mp, count); + if (nxt_slow_path(object == NULL)) { + goto fail; + } + + for (n = 0, i = 0; n != nxt_nitems(nids) && i != count; n++) { + + len = X509_NAME_get_text_by_NID(x509_name, nids[n].nid, + (char *) buf, sizeof(buf)); + + if (len < 0) { + continue; + } + + if (i == 1 && alt_names != NULL) { + i++; + } + + str.length = len; + str.start = buf; + + ret = nxt_conf_set_member_string_dup(object, mp, &nids[n].name, + &str, i++); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } + + if (alt_names != NULL) { + count = sk_GENERAL_NAME_num(alt_names); + + for (n = 0; n != count; n++) { + name = sk_GENERAL_NAME_value(alt_names, n); + + if (name->type != GEN_DNS) { + continue; + } + } + + names = nxt_conf_create_array(mp, n); + if (nxt_slow_path(names == NULL)) { + goto fail; + } + + for (n = 0, i = 0; n != count; n++) { + name = sk_GENERAL_NAME_value(alt_names, n); + + if (name->type != GEN_DNS) { + continue; + } + + str.length = ASN1_STRING_length(name->d.dNSName); +#if OPENSSL_VERSION_NUMBER > 0x10100000L + str.start = (u_char *) ASN1_STRING_get0_data(name->d.dNSName); +#else + str.start = ASN1_STRING_data(name->d.dNSName); +#endif + + ret = nxt_conf_set_element_string_dup(names, mp, i++, &str); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } + + sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free); + + nxt_conf_set_member(object, &alt_names_str, names, 1); + } + + return object; + +fail: + + if (alt_names != NULL) { + sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free); + } + + return NULL; +} + + +nxt_int_t +nxt_cert_info_delete(nxt_str_t *name) +{ + nxt_int_t ret; + nxt_cert_info_t *info; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = nxt_djb_hash(name->start, name->length); + lhq.key = *name; + lhq.proto = &nxt_cert_info_hash_proto; + + ret = nxt_lvlhsh_delete(&nxt_cert_info, &lhq); + + if (ret == NXT_OK) { + info = lhq.value; + nxt_mp_destroy(info->mp); + } + + return ret; +} + + + +nxt_array_t * +nxt_cert_store_load(nxt_task_t *task) +{ + DIR *dir; + size_t size, alloc; + u_char *buf, *p; + nxt_mp_t *mp; + nxt_str_t name; + nxt_int_t ret; + nxt_file_t file; + nxt_array_t *certs; + nxt_runtime_t *rt; + struct dirent *de; + nxt_cert_item_t *item; + + rt = task->thread->runtime; + + if (nxt_slow_path(rt->certs.start == NULL)) { + nxt_alert(task, "no certificates storage directory"); + return NULL; + } + + mp = nxt_mp_create(1024, 128, 256, 32); + if (nxt_slow_path(mp == NULL)) { + return NULL; + } + + certs = nxt_array_create(mp, 16, sizeof(nxt_cert_item_t)); + if (nxt_slow_path(certs == NULL)) { + nxt_mp_destroy(mp); + return NULL; + } + + buf = NULL; + alloc = 0; + + dir = opendir((char *) rt->certs.start); + if (nxt_slow_path(dir == NULL)) { + nxt_alert(task, "opendir(\"%s\") failed %E", + rt->certs.start, nxt_errno); + goto fail; + } + + for ( ;; ) { + de = readdir(dir); + if (de == NULL) { + break; + } + + if (de->d_type != DT_REG) { + continue; + } + + item = nxt_array_add(certs); + if (nxt_slow_path(item == NULL)) { + goto fail; + } + + item->fd = -1; + + name.length = nxt_strlen(de->d_name); + name.start = (u_char *) de->d_name; + + size = rt->certs.length + name.length + 1; + + if (size > alloc) { + size += 32; + + p = nxt_realloc(buf, size); + if (p == NULL) { + goto fail; + } + + alloc = size; + buf = p; + } + + p = nxt_cpymem(buf, rt->certs.start, rt->certs.length); + p = nxt_cpymem(p, name.start, name.length + 1); + + nxt_memzero(&file, sizeof(nxt_file_t)); + + file.name = buf; + + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, + NXT_FILE_OWNER_ACCESS); + + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_array_remove_last(certs); + continue; + } + + item->fd = file.fd; + + if (nxt_slow_path(nxt_str_dup(mp, &item->name, &name) == NULL)) { + goto fail; + } + } + + if (buf != NULL) { + nxt_free(buf); + } + + (void) closedir(dir); + + return certs; + +fail: + + if (buf != NULL) { + nxt_free(buf); + } + + if (dir != NULL) { + (void) closedir(dir); + } + + nxt_cert_store_release(certs); + + return NULL; +} + + +void +nxt_cert_store_release(nxt_array_t *certs) +{ + uint32_t i; + nxt_cert_item_t *items; + + for (items = certs->elts, i = 0; + i < certs->nelts; + i++) + { + nxt_fd_close(items[i].fd); + } + + nxt_mp_destroy(certs->mem_pool); +} + + +#if 0 + +void +nxt_cert_store_discovery_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + DIR *dir; + size_t size; + nxt_buf_t *b; + nxt_int_t ret; + nxt_port_t *port; + nxt_runtime_t *rt; + struct dirent *de; + + port = nxt_runtime_port_find(task->thread->runtime, msg->port_msg.pid, + msg->port_msg.reply_port); + + if (nxt_slow_path(port == NULL)) { + return; + } + + b = NULL; + dir = NULL; + + rt = task->thread->runtime; + + if (nxt_slow_path(rt->certs.start == NULL)) { + nxt_alert(task, "no certificates storage directory"); + goto fail; + } + + dir = opendir((char *) rt->certs.start); + if (nxt_slow_path(dir == NULL)) { + nxt_alert(task, "opendir(\"%s\") failed %E", + rt->certs.start, nxt_errno); + goto fail; + } + + size = 0; + + for ( ;; ) { + de = readdir(dir); + if (de == NULL) { + break; + } + + if (de->d_type != DT_REG) { + continue; + } + + size += nxt_strlen(de->d_name) + 1; + } + + b = nxt_port_mmap_get_buf(task, port, size); + if (nxt_slow_path(b == NULL)) { + goto fail; + } + + rewinddir(dir); + + for ( ;; ) { + de = readdir(dir); + if (de == NULL) { + break; + } + + if (de->d_type != DT_REG) { + continue; + } + + size = nxt_strlen(de->d_name) + 1; + + if (nxt_slow_path(size > (size_t) nxt_buf_mem_free_size(&b->mem))) { + b->mem.free = b->mem.start; + break; + } + + b->mem.free = nxt_cpymem(b->mem.free, de->d_name, size); + } + + (void) closedir(dir); + dir = NULL; + + if (nxt_slow_path(nxt_buf_mem_free_size(&b->mem) != 0)) { + nxt_alert(task, "certificates storage directory " + "has changed while reading it"); + goto fail; + } + + ret = nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_READY_LAST, -1, + msg->port_msg.stream, 0, b); + + if (nxt_fast_path(ret == NXT_OK)) { + return; + } + +fail: + + if (dir != NULL) { + (void) closedir(dir); + } + + if (b != NULL) { + b->completion_handler(task, b, b->parent); + } + + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_ERROR, -1, + msg->port_msg.stream, 0, NULL); +} + +#endif + + +void +nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp, + nxt_port_rpc_handler_t handler, void *ctx) +{ + uint32_t stream; + nxt_int_t ret; + nxt_buf_t *b; + nxt_port_t *main_port, *recv_port; + nxt_runtime_t *rt; + + b = nxt_buf_mem_alloc(mp, name->length + 1, 0); + if (nxt_slow_path(b == NULL)) { + goto fail; + } + + nxt_buf_cpystr(b, name); + *b->mem.free++ = '\0'; + + rt = task->thread->runtime; + main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + recv_port = rt->port_by_type[rt->type]; + + stream = nxt_port_rpc_register_handler(task, recv_port, handler, handler, + -1, ctx); + if (nxt_slow_path(stream == 0)) { + goto fail; + } + + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_CERT_GET, -1, + stream, recv_port->id, b); + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_port_rpc_cancel(task, recv_port, stream); + goto fail; + } + + return; + +fail: + + handler(task, NULL, ctx); +} + + +void +nxt_cert_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + u_char *p; + nxt_int_t ret; + nxt_str_t name; + nxt_file_t file; + nxt_port_t *port; + nxt_runtime_t *rt; + nxt_port_msg_type_t type; + + port = nxt_runtime_port_find(task->thread->runtime, msg->port_msg.pid, + msg->port_msg.reply_port); + + if (port == NULL) { + return; + } + + nxt_memzero(&file, sizeof(nxt_file_t)); + + file.fd = -1; + type = NXT_PORT_MSG_RPC_ERROR; + + rt = task->thread->runtime; + + if (nxt_slow_path(rt->certs.start == NULL)) { + nxt_alert(task, "no certificates storage directory"); + goto error; + } + + name.start = msg->buf->mem.pos; + name.length = nxt_strlen(name.start); + + file.name = nxt_malloc(rt->certs.length + name.length + 1); + + if (nxt_slow_path(file.name == NULL)) { + goto error; + } + + p = nxt_cpymem(file.name, rt->certs.start, rt->certs.length); + p = nxt_cpymem(p, name.start, name.length + 1); + + ret = nxt_file_open(task, &file, NXT_FILE_RDWR, NXT_FILE_CREATE_OR_OPEN, + NXT_FILE_OWNER_ACCESS); + + nxt_free(file.name); + + if (nxt_fast_path(ret == NXT_OK)) { + type = NXT_PORT_MSG_RPC_READY_LAST | NXT_PORT_MSG_CLOSE_FD; + } + +error: + + (void) nxt_port_socket_write(task, port, type, file.fd, + msg->port_msg.stream, 0, NULL); +} + + +void +nxt_cert_store_delete(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp) +{ + nxt_buf_t *b; + nxt_port_t *main_port; + nxt_runtime_t *rt; + + b = nxt_buf_mem_alloc(mp, name->length + 1, 0); + + if (nxt_fast_path(b != NULL)) { + nxt_buf_cpystr(b, name); + *b->mem.free++ = '\0'; + + rt = task->thread->runtime; + main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + + (void) nxt_port_socket_write(task, main_port, NXT_PORT_MSG_CERT_DELETE, + -1, 0, 0, b); + } +} + + +void +nxt_cert_store_delete_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + u_char *p; + nxt_str_t name; + nxt_runtime_t *rt; + nxt_file_name_t *path; + + rt = task->thread->runtime; + + if (nxt_slow_path(rt->certs.start == NULL)) { + nxt_alert(task, "no certificates storage directory"); + return; + } + + name.start = msg->buf->mem.pos; + name.length = nxt_strlen(name.start); + + path = nxt_malloc(rt->certs.length + name.length + 1); + + if (nxt_fast_path(path != NULL)) { + p = nxt_cpymem(path, rt->certs.start, rt->certs.length); + p = nxt_cpymem(p, name.start, name.length + 1); + + (void) nxt_file_delete(path); + + nxt_free(path); + } +} diff --git a/src/nxt_cert.h b/src/nxt_cert.h new file mode 100644 index 00000000..319d5d3c --- /dev/null +++ b/src/nxt_cert.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Valentin V. Bartenev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_CERT_INCLUDED_ +#define _NXT_CERT_INCLUDED_ + + +typedef struct nxt_cert_s nxt_cert_t; + +nxt_cert_t *nxt_cert_mem(nxt_task_t *task, nxt_buf_mem_t *mbuf); +void nxt_cert_destroy(nxt_cert_t *cert); + +void nxt_cert_info_init(nxt_task_t *task, nxt_array_t *certs); +nxt_int_t nxt_cert_info_save(nxt_str_t *name, nxt_cert_t *cert); +nxt_conf_value_t *nxt_cert_info_get(nxt_str_t *name); +nxt_conf_value_t *nxt_cert_info_get_all(nxt_mp_t *mp); +nxt_int_t nxt_cert_info_delete(nxt_str_t *name); + +nxt_array_t *nxt_cert_store_load(nxt_task_t *task); +void nxt_cert_store_release(nxt_array_t *certs); + +void nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp, + nxt_port_rpc_handler_t handler, void *ctx); +void nxt_cert_store_delete(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp); + +void nxt_cert_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); +void nxt_cert_store_delete_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); + +#endif /* _NXT_CERT_INCLUDED_ */ diff --git a/src/nxt_conf.c b/src/nxt_conf.c index 1bd82f58..58eb2080 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -180,6 +180,33 @@ nxt_conf_set_string(nxt_conf_value_t *value, nxt_str_t *str) } +nxt_int_t +nxt_conf_set_string_dup(nxt_conf_value_t *value, nxt_mp_t *mp, nxt_str_t *str) +{ + nxt_str_t tmp, *ptr; + + if (str->length > NXT_CONF_MAX_SHORT_STRING) { + value->type = NXT_CONF_VALUE_STRING; + + ptr = nxt_str_dup(mp, &tmp, str); + if (nxt_slow_path(ptr == NULL)) { + return NXT_ERROR; + } + + value->u.string.length = tmp.length; + value->u.string.start = tmp.start; + + } else { + value->type = NXT_CONF_VALUE_SHORT_STRING; + value->u.str.length = str->length; + + nxt_memcpy(value->u.str.start, str->start, str->length); + } + + return NXT_OK; +} + + int64_t nxt_conf_get_integer(nxt_conf_value_t *value) { @@ -246,6 +273,20 @@ nxt_conf_set_member_string(nxt_conf_value_t *object, nxt_str_t *name, } +nxt_int_t +nxt_conf_set_member_string_dup(nxt_conf_value_t *object, nxt_mp_t *mp, + nxt_str_t *name, nxt_str_t *value, uint32_t index) +{ + nxt_conf_object_member_t *member; + + member = &object->u.object->members[index]; + + nxt_conf_set_string(&member->name, name); + + return nxt_conf_set_string_dup(&member->value, mp, value); +} + + void nxt_conf_set_member_integer(nxt_conf_value_t *object, nxt_str_t *name, int64_t value, uint32_t index) @@ -261,6 +302,64 @@ nxt_conf_set_member_integer(nxt_conf_value_t *object, nxt_str_t *name, } +void +nxt_conf_set_member_null(nxt_conf_value_t *object, nxt_str_t *name, + uint32_t index) +{ + nxt_conf_object_member_t *member; + + member = &object->u.object->members[index]; + + nxt_conf_set_string(&member->name, name); + + member->value.type = NXT_CONF_VALUE_NULL; +} + + +nxt_conf_value_t * +nxt_conf_create_array(nxt_mp_t *mp, nxt_uint_t count) +{ + size_t size; + nxt_conf_value_t *value; + + size = sizeof(nxt_conf_value_t) + + sizeof(nxt_conf_array_t) + + count * sizeof(nxt_conf_value_t); + + value = nxt_mp_get(mp, size); + if (nxt_slow_path(value == NULL)) { + return NULL; + } + + value->u.array = nxt_pointer_to(value, sizeof(nxt_conf_value_t)); + value->u.array->count = count; + + value->type = NXT_CONF_VALUE_ARRAY; + + return value; +} + + +void +nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, + nxt_conf_value_t *value) +{ + array->u.array->elements[index] = *value; +} + + +nxt_int_t +nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, + nxt_uint_t index, nxt_str_t *value) +{ + nxt_conf_value_t *element; + + element = &array->u.array->elements[index]; + + return nxt_conf_set_string_dup(element, mp, value); +} + + nxt_uint_t nxt_conf_type(nxt_conf_value_t *value) { diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 3ddff801..48521917 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -102,6 +102,8 @@ nxt_int_t nxt_conf_validate(nxt_conf_validation_t *vldt); NXT_EXPORT void nxt_conf_get_string(nxt_conf_value_t *value, nxt_str_t *str); NXT_EXPORT void nxt_conf_set_string(nxt_conf_value_t *value, nxt_str_t *str); +NXT_EXPORT nxt_int_t nxt_conf_set_string_dup(nxt_conf_value_t *value, + nxt_mp_t *mp, nxt_str_t *str); NXT_EXPORT int64_t nxt_conf_get_integer(nxt_conf_value_t *value); // FIXME reimplement and reorder functions below @@ -111,8 +113,18 @@ void nxt_conf_set_member(nxt_conf_value_t *object, nxt_str_t *name, nxt_conf_value_t *value, uint32_t index); void nxt_conf_set_member_string(nxt_conf_value_t *object, nxt_str_t *name, nxt_str_t *value, uint32_t index); +nxt_int_t nxt_conf_set_member_string_dup(nxt_conf_value_t *object, nxt_mp_t *mp, + nxt_str_t *name, nxt_str_t *value, uint32_t index); void nxt_conf_set_member_integer(nxt_conf_value_t *object, nxt_str_t *name, int64_t value, uint32_t index); +void nxt_conf_set_member_null(nxt_conf_value_t *object, nxt_str_t *name, + uint32_t index); + +nxt_conf_value_t *nxt_conf_create_array(nxt_mp_t *mp, nxt_uint_t count); +void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, + nxt_conf_value_t *value); +nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, + nxt_uint_t index, nxt_str_t *value); #endif /* _NXT_CONF_INCLUDED_ */ diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index d51ff0ef..ca7107cc 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -6,6 +6,7 @@ #include <nxt_main.h> #include <nxt_conf.h> +#include <nxt_cert.h> #include <nxt_router.h> @@ -49,6 +50,10 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +#if (NXT_TLS) +static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +#endif static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -138,12 +143,35 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_root_members[] = { }; +#if (NXT_TLS) + +static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { + { nxt_string("certificate"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_certificate, + NULL }, + + NXT_CONF_VLDT_END +}; + +#endif + + static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { { nxt_string("application"), NXT_CONF_VLDT_STRING, &nxt_conf_vldt_app_name, NULL }, +#if (NXT_TLS) + + { nxt_string("tls"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_tls_members }, + +#endif + NXT_CONF_VLDT_END }; @@ -467,6 +495,30 @@ nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, } +#if (NXT_TLS) + +static nxt_int_t +nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_str_t name; + nxt_conf_value_t *cert; + + nxt_conf_get_string(value, &name); + + cert = nxt_cert_info_get(&name); + + if (cert == NULL) { + return nxt_conf_vldt_error(vldt, "Certificate \"%V\" is not found.", + &name); + } + + return NXT_OK; +} + +#endif + + static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) diff --git a/src/nxt_controller.c b/src/nxt_controller.c index 4b8d009b..28f0425b 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -9,6 +9,7 @@ #include <nxt_runtime.h> #include <nxt_main_process.h> #include <nxt_conf.h> +#include <nxt_cert.h> typedef struct { @@ -72,6 +73,15 @@ static nxt_int_t nxt_controller_request_content_length(void *ctx, static void nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req); +static void nxt_controller_process_config(nxt_task_t *task, + nxt_controller_request_t *req, nxt_str_t *path); +#if (NXT_TLS) +static void nxt_controller_process_cert(nxt_task_t *task, + nxt_controller_request_t *req, nxt_str_t *path); +static void nxt_controller_process_cert_save(nxt_task_t *task, + nxt_port_recv_msg_t *msg, void *data); +static nxt_bool_t nxt_controller_cert_in_use(nxt_str_t *name); +#endif static void nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static void nxt_controller_conf_store(nxt_task_t *task, @@ -124,6 +134,7 @@ nxt_controller_start(nxt_task_t *task, void *data) nxt_conf_value_t *conf; nxt_event_engine_t *engine; nxt_conf_validation_t vldt; + nxt_controller_init_t *init; rt = task->thread->runtime; @@ -144,9 +155,20 @@ nxt_controller_start(nxt_task_t *task, void *data) nxt_queue_init(&nxt_controller_waiting_requests); - json = data; + init = data; + +#if (NXT_TLS) + + if (init->certs != NULL) { + nxt_cert_info_init(task, init->certs); + nxt_cert_store_release(init->certs); + } - if (json->length == 0) { +#endif + + json = &init->conf; + + if (json->start == NULL) { return NXT_OK; } @@ -790,42 +812,147 @@ nxt_controller_request_content_length(void *ctx, nxt_http_field_t *field, static void nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req) { - nxt_mp_t *mp; - nxt_int_t rc; + uint32_t i, count; nxt_str_t path; nxt_conn_t *c; - nxt_buf_mem_t *mbuf; - nxt_conf_op_t *ops; nxt_conf_value_t *value; - nxt_conf_validation_t vldt; - nxt_conf_json_error_t error; nxt_controller_response_t resp; +#if (NXT_TLS) + nxt_conf_value_t *certs; - static const nxt_str_t empty_obj = nxt_string("{}"); + static nxt_str_t certificates = nxt_string("certificates"); +#endif + static nxt_str_t config = nxt_string("config"); c = req->conn; path = req->parser.path; - if (nxt_str_start(&path, "/config", 7)) { + if (path.length > 1 && path.start[path.length - 1] == '/') { + path.length--; + } + if (nxt_str_start(&path, "/config", 7) + && (path.length == 7 || path.start[7] == '/')) + { if (path.length == 7) { path.length = 1; - } else if (path.start[7] == '/') { + } else { path.length -= 7; path.start += 7; } + + nxt_controller_process_config(task, req, &path); + return; } - if (path.length > 1 && path.start[path.length - 1] == '/') { - path.length--; +#if (NXT_TLS) + + if (nxt_str_start(&path, "/certificates", 13) + && (path.length == 13 || path.start[13] == '/')) + { + if (path.length == 13) { + path.length = 1; + + } else { + path.length -= 13; + path.start += 13; + } + + nxt_controller_process_cert(task, req, &path); + return; } +#endif + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + if (path.length == 1 && path.start[0] == '/') { + + if (!nxt_str_eq(&req->parser.method, "GET", 3)) { + goto invalid_method; + } + + count = 1; +#if (NXT_TLS) + count++; +#endif + + value = nxt_conf_create_object(c->mem_pool, count); + if (nxt_slow_path(value == NULL)) { + goto alloc_fail; + } + + i = 0; + +#if (NXT_TLS) + certs = nxt_cert_info_get_all(c->mem_pool); + if (nxt_slow_path(certs == NULL)) { + goto alloc_fail; + } + + nxt_conf_set_member(value, &certificates, certs, i++); +#endif + + nxt_conf_set_member(value, &config, nxt_controller_conf.root, i); + + resp.status = 200; + resp.conf = value; + + nxt_controller_response(task, req, &resp); + return; + } + + resp.status = 404; + resp.title = (u_char *) "Value doesn't exist."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +invalid_method: + + resp.status = 405; + resp.title = (u_char *) "Invalid method."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +alloc_fail: + + resp.status = 500; + resp.title = (u_char *) "Memory allocation failed."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; +} + + +static void +nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, + nxt_str_t *path) +{ + nxt_mp_t *mp; + nxt_int_t rc; + nxt_conn_t *c; + nxt_buf_mem_t *mbuf; + nxt_conf_op_t *ops; + nxt_conf_value_t *value; + nxt_conf_validation_t vldt; + nxt_conf_json_error_t error; + nxt_controller_response_t resp; + + static const nxt_str_t empty_obj = nxt_string("{}"); + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + c = req->conn; + if (nxt_str_eq(&req->parser.method, "GET", 3)) { - value = nxt_conf_get_path(nxt_controller_conf.root, &path); + value = nxt_conf_get_path(nxt_controller_conf.root, path); if (value == NULL) { goto not_found; @@ -877,10 +1004,10 @@ nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req) return; } - if (path.length != 1) { + if (path->length != 1) { rc = nxt_conf_op_compile(c->mem_pool, &ops, nxt_controller_conf.root, - &path, value); + path, value); if (rc != NXT_OK) { nxt_mp_destroy(mp); @@ -948,7 +1075,7 @@ nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req) return; } - if (path.length == 1) { + if (path->length == 1) { mp = nxt_mp_create(1024, 128, 256, 32); if (nxt_slow_path(mp == NULL)) { @@ -960,7 +1087,7 @@ nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req) } else { rc = nxt_conf_op_compile(c->mem_pool, &ops, nxt_controller_conf.root, - &path, NULL); + path, NULL); if (rc != NXT_OK) { if (rc == NXT_DECLINED) { @@ -1070,6 +1197,265 @@ no_router: } +#if (NXT_TLS) + +static void +nxt_controller_process_cert(nxt_task_t *task, + nxt_controller_request_t *req, nxt_str_t *path) +{ + u_char *p; + nxt_str_t name; + nxt_int_t ret; + nxt_conn_t *c; + nxt_cert_t *cert; + nxt_conf_value_t *value; + nxt_controller_response_t resp; + + name.length = path->length - 1; + name.start = path->start + 1; + + p = nxt_memchr(name.start, '/', name.length); + + if (p != NULL) { + name.length = p - name.start; + + path->length -= p - path->start; + path->start = p; + + } else { + path = NULL; + } + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + c = req->conn; + + if (nxt_str_eq(&req->parser.method, "GET", 3)) { + + if (name.length != 0) { + value = nxt_cert_info_get(&name); + if (value == NULL) { + goto cert_not_found; + } + + if (path != NULL) { + value = nxt_conf_get_path(value, path); + if (value == NULL) { + goto not_found; + } + } + + } else { + value = nxt_cert_info_get_all(c->mem_pool); + if (value == NULL) { + goto alloc_fail; + } + } + + resp.status = 200; + resp.conf = value; + + nxt_controller_response(task, req, &resp); + return; + } + + if (name.length == 0 || path != NULL) { + goto invalid_name; + } + + if (nxt_str_eq(&req->parser.method, "PUT", 3)) { + value = nxt_cert_info_get(&name); + if (value != NULL) { + goto exists_cert; + } + + cert = nxt_cert_mem(task, &c->read->mem); + if (cert == NULL) { + goto invalid_cert; + } + + ret = nxt_cert_info_save(&name, cert); + + nxt_cert_destroy(cert); + + if (nxt_slow_path(ret != NXT_OK)) { + goto alloc_fail; + } + + nxt_cert_store_get(task, &name, c->mem_pool, + nxt_controller_process_cert_save, req); + return; + } + + if (nxt_str_eq(&req->parser.method, "DELETE", 6)) { + + if (nxt_controller_cert_in_use(&name)) { + goto cert_in_use; + } + + if (nxt_cert_info_delete(&name) != NXT_OK) { + goto cert_not_found; + } + + nxt_cert_store_delete(task, &name, c->mem_pool); + + resp.status = 200; + resp.title = (u_char *) "Certificate deleted."; + + nxt_controller_response(task, req, &resp); + return; + } + + resp.status = 405; + resp.title = (u_char *) "Invalid method."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +invalid_name: + + resp.status = 400; + resp.title = (u_char *) "Invalid certificate name."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +invalid_cert: + + resp.status = 400; + resp.title = (u_char *) "Invalid certificate."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +exists_cert: + + resp.status = 400; + resp.title = (u_char *) "Certificate already exists."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +cert_in_use: + + resp.status = 400; + resp.title = (u_char *) "Certificate is used in the configuration."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +cert_not_found: + + resp.status = 404; + resp.title = (u_char *) "Certificate doesn't exist."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +not_found: + + resp.status = 404; + resp.title = (u_char *) "Invalid path."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; + +alloc_fail: + + resp.status = 500; + resp.title = (u_char *) "Memory allocation failed."; + resp.offset = -1; + + nxt_controller_response(task, req, &resp); + return; +} + + +static void +nxt_controller_process_cert_save(nxt_task_t *task, nxt_port_recv_msg_t *msg, + void *data) +{ + nxt_conn_t *c; + nxt_buf_mem_t *mbuf; + nxt_controller_request_t *req; + nxt_controller_response_t resp; + + req = data; + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) { + resp.status = 500; + resp.title = (u_char *) "Failed to store certificate."; + + nxt_controller_response(task, req, &resp); + return; + } + + c = req->conn; + + mbuf = &c->read->mem; + + nxt_fd_write(msg->fd, mbuf->pos, nxt_buf_mem_used_size(mbuf)); + + nxt_fd_close(msg->fd); + + nxt_memzero(&resp, sizeof(nxt_controller_response_t)); + + resp.status = 200; + resp.title = (u_char *) "Certificate chain uploaded."; + + nxt_controller_response(task, req, &resp); +} + + +static nxt_bool_t +nxt_controller_cert_in_use(nxt_str_t *name) +{ + uint32_t next; + nxt_str_t str; + nxt_conf_value_t *listeners, *listener, *value; + + static nxt_str_t listeners_path = nxt_string("/listeners"); + static nxt_str_t certificate_path = nxt_string("/tls/certificate"); + + listeners = nxt_conf_get_path(nxt_controller_conf.root, &listeners_path); + + if (listeners != NULL) { + next = 0; + + for ( ;; ) { + listener = nxt_conf_next_object_member(listeners, &str, &next); + if (listener == NULL) { + break; + } + + value = nxt_conf_get_path(listener, &certificate_path); + if (value == NULL) { + continue; + } + + nxt_conf_get_string(value, &str); + + if (nxt_strstr_eq(&str, name)) { + return 1; + } + } + } + + return 0; +} + +#endif + + static void nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 0ecac06a..37e09911 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -10,6 +10,9 @@ #include <nxt_main_process.h> #include <nxt_conf.h> #include <nxt_router.h> +#if (NXT_TLS) +#include <nxt_cert.h> +#endif typedef struct { @@ -336,6 +339,10 @@ static nxt_port_handlers_t nxt_main_process_port_handlers = { .socket = nxt_main_port_socket_handler, .modules = nxt_main_port_modules_handler, .conf_store = nxt_main_port_conf_store_handler, +#if (NXT_TLS) + .cert_get = nxt_cert_store_get_handler, + .cert_delete = nxt_cert_store_delete_handler, +#endif .access_log = nxt_main_port_access_log_handler, .rpc_ready = nxt_port_rpc_handler, .rpc_error = nxt_port_rpc_handler, @@ -439,13 +446,16 @@ static nxt_int_t nxt_main_create_controller_process(nxt_task_t *task, nxt_runtime_t *rt, nxt_process_init_t *init) { - ssize_t n; - nxt_int_t ret; - nxt_str_t conf; - nxt_file_t file; - nxt_file_info_t fi; + ssize_t n; + nxt_int_t ret; + nxt_str_t *conf; + nxt_file_t file; + nxt_file_info_t fi; + nxt_controller_init_t ctrl_init; - conf.length = 0; + nxt_memzero(&ctrl_init, sizeof(nxt_controller_init_t)); + + conf = &ctrl_init.conf; nxt_memzero(&file, sizeof(nxt_file_t)); @@ -457,19 +467,19 @@ nxt_main_create_controller_process(nxt_task_t *task, nxt_runtime_t *rt, ret = nxt_file_info(&file, &fi); if (nxt_fast_path(ret == NXT_OK && nxt_is_file(&fi))) { - conf.length = nxt_file_size(&fi); - conf.start = nxt_malloc(conf.length); + conf->length = nxt_file_size(&fi); + conf->start = nxt_malloc(conf->length); - if (nxt_slow_path(conf.start == NULL)) { + if (nxt_slow_path(conf->start == NULL)) { nxt_file_close(task, &file); return NXT_ERROR; } - n = nxt_file_read(&file, conf.start, conf.length, 0); + n = nxt_file_read(&file, conf->start, conf->length, 0); - if (nxt_slow_path(n != (ssize_t) conf.length)) { - conf.length = 0; - nxt_free(conf.start); + if (nxt_slow_path(n != (ssize_t) conf->length)) { + nxt_free(conf->start); + conf->start = NULL; nxt_alert(task, "failed to restore previous configuration: " "cannot read the file"); @@ -479,12 +489,24 @@ nxt_main_create_controller_process(nxt_task_t *task, nxt_runtime_t *rt, nxt_file_close(task, &file); } - init->data = &conf; +#if (NXT_TLS) + ctrl_init.certs = nxt_cert_store_load(task); +#endif + + init->data = &ctrl_init; ret = nxt_main_create_worker_process(task, rt, init); - if (ret == NXT_OK && conf.length != 0) { - nxt_free(conf.start); + if (ret == NXT_OK) { + if (conf->start != NULL) { + nxt_free(conf->start); + } + +#if (NXT_TLS) + if (ctrl_init.certs != NULL) { + nxt_cert_store_release(ctrl_init.certs); + } +#endif } return ret; diff --git a/src/nxt_main_process.h b/src/nxt_main_process.h index 9d74787e..d932e11f 100644 --- a/src/nxt_main_process.h +++ b/src/nxt_main_process.h @@ -19,6 +19,14 @@ typedef enum { } nxt_socket_error_t; +typedef struct { + nxt_str_t conf; +#if (NXT_TLS) + nxt_array_t *certs; +#endif +} nxt_controller_init_t; + + nxt_int_t nxt_main_process_start(nxt_thread_t *thr, nxt_task_t *task, nxt_runtime_t *runtime); void nxt_main_stop_all_processes(nxt_task_t *task, nxt_runtime_t *runtime); diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index ca0e698c..91ba3cb0 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -40,6 +40,7 @@ static void nxt_openssl_locks_free(void); #endif static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf); +static nxt_uint_t nxt_openssl_chain_file(SSL_CTX *ctx, nxt_fd_t fd); static void nxt_openssl_server_free(nxt_task_t *task, nxt_tls_conf_t *conf); static void nxt_openssl_conn_init(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_conn_t *c); @@ -246,7 +247,8 @@ static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf) { SSL_CTX *ctx; - const char *certificate, *key, *ciphers, *ca_certificate; + nxt_fd_t fd; + const char *ciphers, *ca_certificate; STACK_OF(X509_NAME) *list; ctx = SSL_CTX_new(SSLv23_server_method()); @@ -284,15 +286,14 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf) #endif - certificate = conf->certificate; + fd = conf->chain_file; - if (SSL_CTX_use_certificate_chain_file(ctx, certificate) == 0) { + if (nxt_openssl_chain_file(ctx, fd) != NXT_OK) { nxt_openssl_log_error(task, NXT_LOG_ALERT, - "SSL_CTX_use_certificate_file(\"%s\") failed", - certificate); + "nxt_openssl_chain_file() failed"); goto fail; } - +/* key = conf->certificate_key; if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) == 0) { @@ -301,7 +302,7 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf) key); goto fail; } - +*/ ciphers = (conf->ciphers != NULL) ? conf->ciphers : "HIGH:!aNULL:!MD5"; if (SSL_CTX_set_cipher_list(ctx, ciphers) == 0) { @@ -358,6 +359,85 @@ fail: } +static nxt_uint_t +nxt_openssl_chain_file(SSL_CTX *ctx, nxt_fd_t fd) +{ + BIO *bio; + X509 *cert, *ca; + long reason; + EVP_PKEY *key; + nxt_uint_t ret; + + bio = BIO_new(BIO_s_fd()); + if (bio == NULL) { + return NXT_ERROR; + } + + BIO_set_fd(bio, fd, BIO_CLOSE); + + ret = NXT_ERROR; + + cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); + if (cert == NULL) { + goto end; + } + + if (SSL_CTX_use_certificate(ctx, cert) != 1) { + goto end; + } + + for ( ;; ) { + ca = PEM_read_bio_X509(bio, NULL, NULL, NULL); + + if (ca == NULL) { + reason = ERR_GET_REASON(ERR_peek_last_error()); + if (reason != PEM_R_NO_START_LINE) { + goto end; + } + + ERR_clear_error(); + break; + } + + /* + * Note that ca isn't freed if it was successfully added to the chain, + * while the main certificate needs a X509_free() call, since + * its reference count is increased by SSL_CTX_use_certificate(). + */ +#if OPENSSL_VERSION_NUMBER > 0x10002000L + if (SSL_CTX_add0_chain_cert(ctx, ca) != 1) { +#else + if (SSL_CTX_add_extra_chain_cert(ctx, ca) != 1) { +#endif + X509_free(ca); + goto end; + } + } + + if (BIO_reset(bio) != 0) { + goto end; + } + + key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (key == NULL) { + goto end; + } + + if (SSL_CTX_use_PrivateKey(ctx, key) == 1) { + ret = NXT_OK; + } + + EVP_PKEY_free(key); + +end: + + X509_free(cert); + BIO_free(bio); + + return ret; +} + + static void nxt_openssl_server_free(nxt_task_t *task, nxt_tls_conf_t *conf) { diff --git a/src/nxt_port.h b/src/nxt_port.h index 1d070a89..76faa7d2 100644 --- a/src/nxt_port.h +++ b/src/nxt_port.h @@ -18,6 +18,8 @@ struct nxt_port_handlers_s { nxt_port_handler_t socket; nxt_port_handler_t modules; nxt_port_handler_t conf_store; + nxt_port_handler_t cert_get; + nxt_port_handler_t cert_delete; nxt_port_handler_t access_log; /* File descriptor exchange. */ @@ -57,6 +59,8 @@ typedef enum { _NXT_PORT_MSG_SOCKET = nxt_port_handler_idx(socket), _NXT_PORT_MSG_MODULES = nxt_port_handler_idx(modules), _NXT_PORT_MSG_CONF_STORE = nxt_port_handler_idx(conf_store), + _NXT_PORT_MSG_CERT_GET = nxt_port_handler_idx(cert_get), + _NXT_PORT_MSG_CERT_DELETE = nxt_port_handler_idx(cert_delete), _NXT_PORT_MSG_ACCESS_LOG = nxt_port_handler_idx(access_log), _NXT_PORT_MSG_CHANGE_FILE = nxt_port_handler_idx(change_file), @@ -81,6 +85,8 @@ typedef enum { NXT_PORT_MSG_SOCKET = _NXT_PORT_MSG_SOCKET | NXT_PORT_MSG_LAST, NXT_PORT_MSG_MODULES = _NXT_PORT_MSG_MODULES | NXT_PORT_MSG_LAST, NXT_PORT_MSG_CONF_STORE = _NXT_PORT_MSG_CONF_STORE | NXT_PORT_MSG_LAST, + NXT_PORT_MSG_CERT_GET = _NXT_PORT_MSG_CERT_GET | NXT_PORT_MSG_LAST, + NXT_PORT_MSG_CERT_DELETE = _NXT_PORT_MSG_CERT_DELETE | NXT_PORT_MSG_LAST, NXT_PORT_MSG_ACCESS_LOG = _NXT_PORT_MSG_ACCESS_LOG | NXT_PORT_MSG_LAST, NXT_PORT_MSG_CHANGE_FILE = _NXT_PORT_MSG_CHANGE_FILE | NXT_PORT_MSG_LAST, diff --git a/src/nxt_router.c b/src/nxt_router.c index 87721ba4..21a7b4d7 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -7,6 +7,9 @@ #include <nxt_router.h> #include <nxt_conf.h> +#if (NXT_TLS) +#include <nxt_cert.h> +#endif #include <nxt_http.h> #include <nxt_port_memory_int.h> #include <nxt_unit_request.h> @@ -32,6 +35,18 @@ typedef struct { } nxt_router_listener_conf_t; +#if (NXT_TLS) + +typedef struct { + nxt_str_t name; + nxt_socket_conf_t *conf; + + nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */ +} nxt_router_tlssock_t; + +#endif + + typedef struct nxt_msg_info_s { nxt_buf_t *buf; nxt_port_mmap_tracking_t tracking; @@ -156,6 +171,12 @@ static void nxt_router_listen_socket_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static void nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); +#if (NXT_TLS) +static void nxt_router_tls_rpc_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_router_tlssock_t *tls); +static void nxt_router_tls_rpc_handler(nxt_task_t *task, + nxt_port_recv_msg_t *msg, void *data); +#endif static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app); static void nxt_router_app_prefork_ready(nxt_task_t *task, @@ -957,6 +978,10 @@ nxt_router_temp_conf(nxt_task_t *task) nxt_queue_init(&tmcf->pending); nxt_queue_init(&tmcf->creating); +#if (NXT_TLS) + nxt_queue_init(&tmcf->tls); +#endif + nxt_queue_init(&tmcf->apps); nxt_queue_init(&tmcf->previous); @@ -1002,6 +1027,9 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data) nxt_router_conf_t *rtcf; nxt_router_temp_conf_t *tmcf; const nxt_event_interface_t *interface; +#if (NXT_TLS) + nxt_router_tlssock_t *tls; +#endif tmcf = obj; @@ -1018,6 +1046,19 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data) return; } +#if (NXT_TLS) + qlk = nxt_queue_first(&tmcf->tls); + + if (qlk != nxt_queue_tail(&tmcf->tls)) { + nxt_queue_remove(qlk); + + tls = nxt_queue_link_data(qlk, nxt_router_tlssock_t, link); + + nxt_router_tls_rpc_create(task, tmcf, tls); + return; + } +#endif + nxt_queue_each(app, &tmcf->apps, nxt_app_t, link) { if (nxt_router_app_need_start(app)) { @@ -1332,11 +1373,17 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_app_conf_t apcf; nxt_router_access_log_t *access_log; nxt_router_listener_conf_t lscf; +#if (NXT_TLS) + nxt_router_tlssock_t *tls; +#endif static nxt_str_t http_path = nxt_string("/settings/http"); static nxt_str_t applications_path = nxt_string("/applications"); static nxt_str_t listeners_path = nxt_string("/listeners"); static nxt_str_t access_log_path = nxt_string("/access_log"); +#if (NXT_TLS) + static nxt_str_t certificate_path = nxt_string("/tls/certificate"); +#endif conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL); if (conf == NULL) { @@ -1588,6 +1635,26 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } } +#if (NXT_TLS) + + value = nxt_conf_get_path(listener, &certificate_path); + + if (value != NULL) { + nxt_conf_get_string(value, &name); + + tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t)); + if (nxt_slow_path(tls == NULL)) { + goto fail; + } + + tls->name = name; + tls->conf = skcf; + + nxt_queue_insert_tail(&tmcf->tls, &tls->link); + } + +#endif + skcf->listen->handler = nxt_http_conn_init; skcf->router_conf = tmcf->router_conf; skcf->router_conf->count++; @@ -1956,6 +2023,75 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, } +#if (NXT_TLS) + +static void +nxt_router_tls_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_router_tlssock_t *tls) +{ + nxt_socket_rpc_t *rpc; + + rpc = nxt_mp_alloc(tmcf->mem_pool, sizeof(nxt_socket_rpc_t)); + if (rpc == NULL) { + nxt_router_conf_error(task, tmcf); + return; + } + + rpc->socket_conf = tls->conf; + rpc->temp_conf = tmcf; + + nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, + nxt_router_tls_rpc_handler, rpc); +} + + +static void +nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, + void *data) +{ + nxt_mp_t *mp; + nxt_int_t ret; + nxt_tls_conf_t *tlscf; + nxt_socket_rpc_t *rpc; + nxt_router_temp_conf_t *tmcf; + + nxt_debug(task, "tls rpc handler"); + + rpc = data; + tmcf = rpc->temp_conf; + + if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) { + goto fail; + } + + mp = tmcf->router_conf->mem_pool; + + tlscf = nxt_mp_zget(mp, sizeof(nxt_tls_conf_t)); + if (nxt_slow_path(tlscf == NULL)) { + goto fail; + } + + tlscf->chain_file = msg->fd; + + ret = task->thread->runtime->tls->server_init(task, tlscf); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + + rpc->socket_conf->tls = tlscf; + + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + nxt_router_conf_apply, task, tmcf, NULL); + return; + +fail: + + nxt_router_conf_error(task, tmcf); +} + +#endif + + static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app) diff --git a/src/nxt_router.h b/src/nxt_router.h index a75637ac..a2da8ff9 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -59,6 +59,10 @@ typedef struct { nxt_queue_t keeping; /* of nxt_socket_conf_t */ nxt_queue_t deleting; /* of nxt_socket_conf_t */ +#if (NXT_TLS) + nxt_queue_t tls; /* of nxt_router_tlssock_t */ +#endif + nxt_queue_t apps; /* of nxt_app_t */ nxt_queue_t previous; /* of nxt_app_t */ diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index acc2820a..311c2c08 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -762,6 +762,23 @@ nxt_runtime_conf_init(nxt_task_t *task, nxt_runtime_t *rt) rt->conf_tmp = (char *) file_name.start; + ret = nxt_file_name_create(rt->mem_pool, &file_name, "%s%scerts/%Z", + rt->state, slash); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + ret = mkdir((char *) file_name.start, S_IRWXU); + + if (nxt_fast_path(ret == 0 || nxt_errno == EEXIST)) { + rt->certs.length = file_name.len; + rt->certs.start = file_name.start; + + } else { + nxt_alert(task, "Unable to create certificates storage directory: " + "mkdir(%s) failed %E", file_name.start, nxt_errno); + } + control.length = nxt_strlen(rt->control); control.start = (u_char *) rt->control; diff --git a/src/nxt_runtime.h b/src/nxt_runtime.h index 2e121f88..e4c9e2a1 100644 --- a/src/nxt_runtime.h +++ b/src/nxt_runtime.h @@ -68,6 +68,8 @@ struct nxt_runtime_s { const char *conf_tmp; const char *control; + nxt_str_t certs; + nxt_queue_t engines; /* of nxt_event_engine_t */ nxt_sockaddr_t *controller_listen; diff --git a/src/nxt_tls.h b/src/nxt_tls.h index 6f342edd..d9fcc6a8 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -44,8 +44,7 @@ struct nxt_tls_conf_s { const nxt_tls_lib_t *lib; - char *certificate; - char *certificate_key; + nxt_fd_t chain_file; char *ciphers; char *ca_certificate; |