From f6264bdb9ee32be1efc52f197eec15fd771d5f0e Mon Sep 17 00:00:00 2001 From: Hakase Date: Tue, 26 Feb 2019 16:40:49 +0900 Subject: [PATCH] Latest update - 7466 --- src/event/ngx_event_openssl.c | 550 ++++++++++++++++++------- src/event/ngx_event_openssl.h | 9 +- src/http/modules/ngx_http_ssl_module.c | 123 +++++- src/http/modules/ngx_http_ssl_module.h | 3 + src/http/ngx_http.h | 4 + src/http/ngx_http_request.c | 126 +++++- src/mail/ngx_mail_ssl_module.c | 2 +- src/stream/ngx_stream_ssl_module.c | 182 +++++++- src/stream/ngx_stream_ssl_module.h | 3 + 9 files changed, 822 insertions(+), 180 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 394a8c8..3315daa 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -18,6 +18,10 @@ typedef struct { } ngx_openssl_conf_t; +static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, + ngx_str_t *cert, STACK_OF(X509) **chain); +static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, + ngx_str_t *key, ngx_array_t *passwords); static int ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata); static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); @@ -50,7 +54,7 @@ static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, static void ngx_ssl_clear_error(ngx_log_t *log); static ngx_int_t ngx_ssl_session_id_context(ngx_ssl_t *ssl, - ngx_str_t *sess_ctx); + ngx_str_t *sess_ctx, ngx_array_t *certificates); static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess); static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, @@ -415,16 +419,209 @@ ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords) { - BIO *bio; - X509 *x509; - u_long n; - ngx_str_t *pwd; - ngx_uint_t tries; + char *err; + X509 *x509; + EVP_PKEY *pkey; + STACK_OF(X509) *chain; + + x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); + if (x509 == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { return NGX_ERROR; } + if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_use_certificate(\"%s\") failed", cert->data); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index, + SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index)) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + /* + * Note that x509 is not freed here, but will be instead freed in + * ngx_ssl_cleanup_ctx(). This is because we need to preserve all + * certificates to be able to iterate all of them through exdata + * (ngx_ssl_certificate_index, ngx_ssl_next_certificate_index), + * while OpenSSL can free a certificate if it is replaced with another + * certificate of the same type. + */ + +#ifdef SSL_CTX_set0_chain + + if (SSL_CTX_set0_chain(ssl->ctx, chain) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set0_chain(\"%s\") failed", cert->data); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + +#else + { + int n; + + /* SSL_CTX_set0_chain() is only available in OpenSSL 1.0.2+ */ + + n = sk_X509_num(chain); + + while (n--) { + x509 = sk_X509_shift(chain); + + if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_add_extra_chain_cert(\"%s\") failed", + cert->data); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + } + + sk_X509_free(chain); + } +#endif + + pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords); + if (pkey == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate key \"%s\": %s", + key->data, err); + } + + return NGX_ERROR; + } + + if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_use_PrivateKey(\"%s\") failed", key->data); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + + EVP_PKEY_free(pkey); + + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords) +{ + char *err; + X509 *x509; + EVP_PKEY *pkey; + STACK_OF(X509) *chain; + + x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain); + if (x509 == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } + + return NGX_ERROR; + } + + if (SSL_use_certificate(c->ssl->connection, x509) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_use_certificate(\"%s\") failed", cert->data); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + X509_free(x509); + +#ifdef SSL_set0_chain + + /* + * SSL_set0_chain() is only available in OpenSSL 1.0.2+, + * but this function is only called via certificate callback, + * which is only available in OpenSSL 1.0.2+ as well + */ + + if (SSL_set0_chain(c->ssl->connection, chain) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_set0_chain(\"%s\") failed", cert->data); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + +#endif + + pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords); + if (pkey == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "cannot load certificate key \"%s\": %s", + key->data, err); + } + + return NGX_ERROR; + } + + if (SSL_use_PrivateKey(c->ssl->connection, pkey) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_use_PrivateKey(\"%s\") failed", key->data); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + + EVP_PKEY_free(pkey); + + return NGX_OK; +} + + +static X509 * +ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, + STACK_OF(X509) **chain) +{ + BIO *bio; + X509 *x509, *temp; + u_long n; + + if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) + != NGX_OK) + { + *err = NULL; + return NULL; + } + /* * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't * allow to access certificate later from SSL_CTX, so we reimplement @@ -433,62 +630,33 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, bio = BIO_new_file((char *) cert->data, "r"); if (bio == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "BIO_new_file(\"%s\") failed", cert->data); - return NGX_ERROR; + *err = "BIO_new_file() failed"; + return NULL; } + /* certificate itself */ + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); if (x509 == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data); + *err = "PEM_read_bio_X509_AUX() failed"; BIO_free(bio); - return NGX_ERROR; + return NULL; } - if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_use_certificate(\"%s\") failed", cert->data); + /* rest of the chain */ + + *chain = sk_X509_new_null(); + if (*chain == NULL) { + *err = "sk_X509_new_null() failed"; + BIO_free(bio); X509_free(x509); - BIO_free(bio); - return NGX_ERROR; + return NULL; } - if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); - X509_free(x509); - BIO_free(bio); - return NGX_ERROR; - } - - if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index, - SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index)) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); - X509_free(x509); - BIO_free(bio); - return NGX_ERROR; - } - - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_set_ex_data() failed"); - X509_free(x509); - BIO_free(bio); - return NGX_ERROR; - } - - /* read rest of the chain */ - for ( ;; ) { - x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) { + temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (temp == NULL) { n = ERR_peek_last_error(); if (ERR_GET_LIB(n) == ERR_LIB_PEM @@ -501,43 +669,38 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, /* some real error */ - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "PEM_read_bio_X509(\"%s\") failed", cert->data); + *err = "PEM_read_bio_X509() failed"; BIO_free(bio); - return NGX_ERROR; - } - -#ifdef SSL_CTRL_CHAIN_CERT - - /* - * SSL_CTX_add0_chain_cert() is needed to add chain to - * a particular certificate when multiple certificates are used; - * only available in OpenSSL 1.0.2+ - */ - - if (SSL_CTX_add0_chain_cert(ssl->ctx, x509) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_add0_chain_cert(\"%s\") failed", - cert->data); X509_free(x509); - BIO_free(bio); - return NGX_ERROR; + sk_X509_pop_free(*chain, X509_free); + return NULL; } -#else - if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_add_extra_chain_cert(\"%s\") failed", - cert->data); - X509_free(x509); + if (sk_X509_push(*chain, temp) == 0) { + *err = "sk_X509_push() failed"; BIO_free(bio); - return NGX_ERROR; + X509_free(x509); + sk_X509_pop_free(*chain, X509_free); + return NULL; } -#endif } BIO_free(bio); + return x509; +} + + +static EVP_PKEY * +ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, + ngx_str_t *key, ngx_array_t *passwords) +{ + BIO *bio; + EVP_PKEY *pkey; + ngx_str_t *pwd; + ngx_uint_t tries; + pem_password_cb *cb; + if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) { #ifndef OPENSSL_NO_ENGINE @@ -550,9 +713,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, last = (u_char *) ngx_strchr(p, ':'); if (last == NULL) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "invalid syntax in \"%V\"", key); - return NGX_ERROR; + *err = "invalid syntax"; + return NULL; } *last = '\0'; @@ -560,9 +722,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, engine = ENGINE_by_id((char *) p); if (engine == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "ENGINE_by_id(\"%s\") failed", p); - return NGX_ERROR; + *err = "ENGINE_by_id() failed"; + return NULL; } *last++ = ':'; @@ -570,76 +731,69 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); if (pkey == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "ENGINE_load_private_key(\"%s\") failed", last); + *err = "ENGINE_load_private_key() failed"; ENGINE_free(engine); - return NGX_ERROR; + return NULL; } ENGINE_free(engine); - if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_use_PrivateKey(\"%s\") failed", last); - EVP_PKEY_free(pkey); - return NGX_ERROR; - } - - EVP_PKEY_free(pkey); - - return NGX_OK; + return pkey; #else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "loading \"engine:...\" certificate keys " - "is not supported"); - return NGX_ERROR; + *err = "loading \"engine:...\" certificate keys is not supported"; + return NULL; #endif } - if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) { - return NGX_ERROR; + if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) + != NGX_OK) + { + *err = NULL; + return NULL; + } + + bio = BIO_new_file((char *) key->data, "r"); + if (bio == NULL) { + *err = "BIO_new_file() failed"; + return NULL; } if (passwords) { tries = passwords->nelts; pwd = passwords->elts; - - SSL_CTX_set_default_passwd_cb(ssl->ctx, ngx_ssl_password_callback); - SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, pwd); + cb = ngx_ssl_password_callback; } else { tries = 1; -#if (NGX_SUPPRESS_WARN) pwd = NULL; -#endif + cb = NULL; } for ( ;; ) { - if (SSL_CTX_use_PrivateKey_file(ssl->ctx, (char *) key->data, - SSL_FILETYPE_PEM) - != 0) - { + pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); + if (pkey != NULL) { break; } - if (--tries) { + if (tries-- > 1) { ERR_clear_error(); - SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, ++pwd); + (void) BIO_reset(bio); + pwd++; continue; } - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_use_PrivateKey_file(\"%s\") failed", key->data); - return NGX_ERROR; + *err = "PEM_read_bio_PrivateKey() failed"; + BIO_free(bio); + return NULL; } - SSL_CTX_set_default_passwd_cb(ssl->ctx, NULL); + BIO_free(bio); - return NGX_OK; + return pkey; } @@ -654,6 +808,10 @@ ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata) return 0; } + if (pwd == NULL) { + return 0; + } + if (pwd->len > (size_t) size) { ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "password is truncated to %d bytes", size); @@ -1069,6 +1227,69 @@ cleanup: } +ngx_array_t * +ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords) +{ + ngx_str_t *opwd, *pwd; + ngx_uint_t i; + ngx_array_t *pwds; + ngx_pool_cleanup_t *cln; + static ngx_array_t empty_passwords; + + if (passwords == NULL) { + + /* + * If there are no passwords, an empty array is used + * to make sure OpenSSL's default password callback + * won't block on reading from stdin. + */ + + return &empty_passwords; + } + + /* + * Passwords are normally allocated from the temporary pool + * and cleared after parsing configuration. To be used at + * runtime they have to be copied to the configuration pool. + */ + + pwds = ngx_array_create(cf->pool, passwords->nelts, sizeof(ngx_str_t)); + if (pwds == NULL) { + return NULL; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_ssl_passwords_cleanup; + cln->data = pwds; + + opwd = passwords->elts; + + for (i = 0; i < passwords->nelts; i++) { + + pwd = ngx_array_push(pwds); + if (pwd == NULL) { + return NULL; + } + + pwd->len = opwd[i].len; + pwd->data = ngx_pnalloc(cf->pool, pwd->len); + + if (pwd->data == NULL) { + pwds->nelts--; + return NULL; + } + + ngx_memcpy(pwd->data, opwd[i].data, opwd[i].len); + } + + return pwds; +} + + static void ngx_ssl_passwords_cleanup(void *data) { @@ -2732,6 +2953,9 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #ifdef SSL_R_INAPPROPRIATE_FALLBACK || n == SSL_R_INAPPROPRIATE_FALLBACK /* 373 */ #endif +#ifdef SSL_R_CERT_CB_ERROR + || n == SSL_R_CERT_CB_ERROR /* 377 */ +#endif #ifdef SSL_R_VERSION_TOO_LOW || n == SSL_R_VERSION_TOO_LOW /* 396 */ #endif @@ -2811,53 +3035,60 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...) p = ngx_vslprintf(errstr, last - 1, fmt, args); va_end(args); - p = ngx_cpystrn(p, (u_char *) " (SSL:", last - p); + if (ERR_peek_error()) { + p = ngx_cpystrn(p, (u_char *) " (SSL:", last - p); - for ( ;; ) { + for ( ;; ) { - n = ERR_peek_error_line_data(NULL, NULL, &data, &flags); + n = ERR_peek_error_line_data(NULL, NULL, &data, &flags); - if (n == 0) { - break; + if (n == 0) { + break; + } + + /* ERR_error_string_n() requires at least one byte */ + + if (p >= last - 1) { + goto next; + } + + *p++ = ' '; + + ERR_error_string_n(n, (char *) p, last - p); + + while (p < last && *p) { + p++; + } + + if (p < last && *data && (flags & ERR_TXT_STRING)) { + *p++ = ':'; + p = ngx_cpystrn(p, (u_char *) data, last - p); + } + + next: + + (void) ERR_get_error(); } - /* ERR_error_string_n() requires at least one byte */ - - if (p >= last - 1) { - goto next; + if (p < last) { + *p++ = ')'; } - - *p++ = ' '; - - ERR_error_string_n(n, (char *) p, last - p); - - while (p < last && *p) { - p++; - } - - if (p < last && *data && (flags & ERR_TXT_STRING)) { - *p++ = ':'; - p = ngx_cpystrn(p, (u_char *) data, last - p); - } - - next: - - (void) ERR_get_error(); } - ngx_log_error(level, log, err, "%*s)", p - errstr, errstr); + ngx_log_error(level, log, err, "%*s", p - errstr, errstr); } ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, - ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout) + ngx_array_t *certificates, ssize_t builtin_session_cache, + ngx_shm_zone_t *shm_zone, time_t timeout) { long cache_mode; SSL_CTX_set_timeout(ssl->ctx, (long) timeout); - if (ngx_ssl_session_id_context(ssl, sess_ctx) != NGX_OK) { + if (ngx_ssl_session_id_context(ssl, sess_ctx, certificates) != NGX_OK) { return NGX_ERROR; } @@ -2923,11 +3154,14 @@ ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, static ngx_int_t -ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx) +ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, + ngx_array_t *certificates) { int n, i; X509 *cert; X509_NAME *name; + ngx_str_t *certs; + ngx_uint_t k; EVP_MD_CTX *md; unsigned int len; STACK_OF(X509_NAME) *list; @@ -2972,6 +3206,24 @@ ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx) } } + if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL) { + + /* + * If certificates are loaded dynamically, we use certificate + * names as specified in the configuration (with variables). + */ + + certs = certificates->elts; + for (k = 0; k < certificates->nelts; k++) { + + if (EVP_DigestUpdate(md, certs[k].data, certs[k].len) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "EVP_DigestUpdate() failed"); + goto failed; + } + } + } + list = SSL_CTX_get_client_CA_list(ssl->ctx); if (list != NULL) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 31d0923..cd2759e 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -174,10 +174,14 @@ typedef struct { ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); + ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords); ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords); +ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords); + ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, ngx_uint_t prefer_server_ciphers); ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, @@ -192,6 +196,8 @@ ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, RSA *ngx_ssl_rsa512_key_callback(ngx_ssl_conn_t *ssl_conn, int is_export, int key_length); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); +ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, + ngx_array_t *passwords); ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, @@ -199,7 +205,8 @@ ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, - ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout); + ngx_array_t *certificates, ssize_t builtin_session_cache, + ngx_shm_zone_t *shm_zone, time_t timeout); ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths); ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data); diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index c5ed248..4b7e89a 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -41,6 +41,9 @@ static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); +static ngx_int_t ngx_http_ssl_compile_certificates(ngx_conf_t *cf, + ngx_http_ssl_srv_conf_t *conf); + static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, @@ -585,6 +588,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * set by ngx_pcalloc(): * * sscf->protocols = 0; + * sscf->certificate_values = NULL; * sscf->dhparam = { 0, NULL }; * sscf->ecdh_curve = { 0, NULL }; * sscf->client_certificate = { 0, NULL }; @@ -781,13 +785,38 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) cln->handler = ngx_ssl_cleanup_ctx; cln->data = &conf->ssl; - if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, - conf->certificate_keys, conf->passwords) - != NGX_OK) - { + if (ngx_http_ssl_compile_certificates(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } + if (conf->certificate_values) { + +#ifdef SSL_R_CERT_CB_ERROR + + /* install callback to lookup certificates */ + + SSL_CTX_set_cert_cb(conf->ssl.ctx, ngx_http_ssl_certificate, conf); + +#else + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "variables in " + "\"ssl_certificate\" and \"ssl_certificate_key\" " + "directives are not supported on this platform"); + return NGX_CONF_ERROR; +#endif + + } else { + + /* configure certificates */ + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, + conf->certificate_keys, conf->passwords) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, conf->prefer_server_ciphers) != NGX_OK) @@ -842,7 +871,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } if (ngx_ssl_session_cache(&conf->ssl, &ngx_http_ssl_sess_id_ctx, - conf->builtin_session_cache, + conf->certificates, conf->builtin_session_cache, conf->shm_zone, conf->session_timeout) != NGX_OK) { @@ -907,6 +936,90 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } +static ngx_int_t +ngx_http_ssl_compile_certificates(ngx_conf_t *cf, + ngx_http_ssl_srv_conf_t *conf) +{ + ngx_str_t *cert, *key; + ngx_uint_t i, nelts; + ngx_http_complex_value_t *cv; + ngx_http_compile_complex_value_t ccv; + + cert = conf->certificates->elts; + key = conf->certificate_keys->elts; + nelts = conf->certificates->nelts; + + for (i = 0; i < nelts; i++) { + + if (ngx_http_script_variables_count(&cert[i])) { + goto found; + } + + if (ngx_http_script_variables_count(&key[i])) { + goto found; + } + } + + return NGX_OK; + +found: + + conf->certificate_values = ngx_array_create(cf->pool, nelts, + sizeof(ngx_http_complex_value_t)); + if (conf->certificate_values == NULL) { + return NGX_ERROR; + } + + conf->certificate_key_values = ngx_array_create(cf->pool, nelts, + sizeof(ngx_http_complex_value_t)); + if (conf->certificate_key_values == NULL) { + return NGX_ERROR; + } + + for (i = 0; i < nelts; i++) { + + cv = ngx_array_push(conf->certificate_values); + if (cv == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &cert[i]; + ccv.complex_value = cv; + ccv.zero = 1; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + + cv = ngx_array_push(conf->certificate_key_values); + if (cv == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &key[i]; + ccv.complex_value = cv; + ccv.zero = 1; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + } + + conf->passwords = ngx_ssl_preserve_passwords(cf, conf->passwords); + if (conf->passwords == NULL) { + return NGX_ERROR; + } + + return NGX_OK; +} + + static char * ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index deeadc3..b14b52a 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -36,6 +36,9 @@ typedef struct { ngx_array_t *certificates; ngx_array_t *certificate_keys; + ngx_array_t *certificate_values; + ngx_array_t *certificate_key_values; + ngx_str_t dhparam; ngx_str_t ecdh_curve; ngx_str_t client_certificate; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index afab4f6..8b43857 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -88,6 +88,10 @@ void ngx_http_close_connection(ngx_connection_t *c); #if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME) int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); #endif +#if (NGX_HTTP_SSL && defined SSL_R_CERT_CB_ERROR) +int ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg); +#endif + ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 1901129..bc3c525 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -11,6 +11,7 @@ static void ngx_http_wait_request_handler(ngx_event_t *ev); +static ngx_http_request_t *ngx_http_alloc_request(ngx_connection_t *c); static void ngx_http_process_request_line(ngx_event_t *rev); static void ngx_http_process_request_headers(ngx_event_t *rev); static ssize_t ngx_http_read_request_header(ngx_http_request_t *r); @@ -502,18 +503,46 @@ ngx_http_wait_request_handler(ngx_event_t *rev) ngx_http_request_t * ngx_http_create_request(ngx_connection_t *c) +{ + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + + r = ngx_http_alloc_request(c); + if (r == NULL) { + return NULL; + } + + c->requests++; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + ngx_set_connection_log(c, clcf->error_log); + + ctx = c->log->data; + ctx->request = r; + ctx->current_request = r; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_reading, 1); + r->stat_reading = 1; + (void) ngx_atomic_fetch_add(ngx_stat_requests, 1); +#endif + + return r; +} + + +static ngx_http_request_t * +ngx_http_alloc_request(ngx_connection_t *c) { ngx_pool_t *pool; ngx_time_t *tp; ngx_http_request_t *r; - ngx_http_log_ctx_t *ctx; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; - ngx_http_core_loc_conf_t *clcf; ngx_http_core_main_conf_t *cmcf; - c->requests++; - hc = c->data; cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); @@ -541,10 +570,6 @@ ngx_http_create_request(ngx_connection_t *c) r->read_event_handler = ngx_http_block_reading; - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - - ngx_set_connection_log(r->connection, clcf->error_log); - r->header_in = hc->busy ? hc->busy->buf : c->buffer; if (ngx_list_init(&r->headers_out.headers, r->pool, 20, @@ -604,17 +629,8 @@ ngx_http_create_request(ngx_connection_t *c) r->http_state = NGX_HTTP_READING_REQUEST_STATE; - ctx = c->log->data; - ctx->request = r; - ctx->current_request = r; r->log_handler = ngx_http_log_error_handler; -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_reading, 1); - r->stat_reading = 1; - (void) ngx_atomic_fetch_add(ngx_stat_requests, 1); -#endif - return r; } @@ -833,6 +849,7 @@ ngx_http_ssl_handshake_handler(ngx_connection_t *c) ngx_http_close_connection(c); } + #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME int @@ -930,6 +947,75 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #endif + +#ifdef SSL_R_CERT_CB_ERROR + +int +ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) +{ + ngx_str_t cert, key; + ngx_uint_t i, nelts; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_complex_value_t *certs, *keys; + + c = ngx_ssl_get_connection(ssl_conn); + + if (c->ssl->handshaked) { + return 0; + } + + r = ngx_http_alloc_request(c); + if (r == NULL) { + return 0; + } + + r->logged = 1; + + sscf = arg; + + nelts = sscf->certificate_values->nelts; + certs = sscf->certificate_values->elts; + keys = sscf->certificate_key_values->elts; + + for (i = 0; i < nelts; i++) { + + if (ngx_http_complex_value(r, &certs[i], &cert) != NGX_OK) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl cert: \"%s\"", cert.data); + + if (ngx_http_complex_value(r, &keys[i], &key) != NGX_OK) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl key: \"%s\"", key.data); + + if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, + sscf->passwords) + != NGX_OK) + { + goto failed; + } + } + + ngx_http_free_request(r, 0); + c->destroyed = 0; + return 1; + +failed: + + ngx_http_free_request(r, 0); + c->destroyed = 0; + return 0; +} + +#endif + #endif @@ -3523,9 +3609,11 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) r->headers_out.status = rc; } - log->action = "logging request"; + if (!r->logged) { + log->action = "logging request"; - ngx_http_log_request(r); + ngx_http_log_request(r); + } log->action = "closing request"; diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c index bc3f862..b7552a9 100644 --- a/src/mail/ngx_mail_ssl_module.c +++ b/src/mail/ngx_mail_ssl_module.c @@ -435,7 +435,7 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } if (ngx_ssl_session_cache(&conf->ssl, &ngx_mail_ssl_sess_id_ctx, - conf->builtin_session_cache, + conf->certificates, conf->builtin_session_cache, conf->shm_zone, conf->session_timeout) != NGX_OK) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 96aaaf5..783754d 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -22,6 +22,9 @@ static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s); static ngx_int_t ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c); static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c); +#ifdef SSL_R_CERT_CB_ERROR +static int ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg); +#endif static ngx_int_t ngx_stream_ssl_static_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_variable(ngx_stream_session_t *s, @@ -32,6 +35,9 @@ static void *ngx_stream_ssl_create_conf(ngx_conf_t *cf); static char *ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child); +static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, + ngx_stream_ssl_conf_t *conf); + static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, @@ -408,6 +414,62 @@ ngx_stream_ssl_handshake_handler(ngx_connection_t *c) } +#ifdef SSL_R_CERT_CB_ERROR + +int +ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) +{ + ngx_str_t cert, key; + ngx_uint_t i, nelts; + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_ssl_conf_t *sslcf; + ngx_stream_complex_value_t *certs, *keys; + + c = ngx_ssl_get_connection(ssl_conn); + + if (c->ssl->handshaked) { + return 0; + } + + s = c->data; + + sslcf = arg; + + nelts = sslcf->certificate_values->nelts; + certs = sslcf->certificate_values->elts; + keys = sslcf->certificate_key_values->elts; + + for (i = 0; i < nelts; i++) { + + if (ngx_stream_complex_value(s, &certs[i], &cert) != NGX_OK) { + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "ssl cert: \"%s\"", cert.data); + + if (ngx_stream_complex_value(s, &keys[i], &key) != NGX_OK) { + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "ssl key: \"%s\"", key.data); + + if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, + sslcf->passwords) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + +#endif + + static ngx_int_t ngx_stream_ssl_static_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) @@ -505,6 +567,7 @@ ngx_stream_ssl_create_conf(ngx_conf_t *cf) * * scf->listen = 0; * scf->protocols = 0; + * scf->certificate_values = NULL; * scf->dhparam = { 0, NULL }; * scf->ecdh_curve = { 0, NULL }; * scf->client_certificate = { 0, NULL }; @@ -619,13 +682,38 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) cln->handler = ngx_ssl_cleanup_ctx; cln->data = &conf->ssl; - if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, - conf->certificate_keys, conf->passwords) - != NGX_OK) - { + if (ngx_stream_ssl_compile_certificates(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } + if (conf->certificate_values) { + +#ifdef SSL_R_CERT_CB_ERROR + + /* install callback to lookup certificates */ + + SSL_CTX_set_cert_cb(conf->ssl.ctx, ngx_stream_ssl_certificate, conf); + +#else + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "variables in " + "\"ssl_certificate\" and \"ssl_certificate_key\" " + "directives are not supported on this platform"); + return NGX_CONF_ERROR; +#endif + + } else { + + /* configure certificates */ + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, + conf->certificate_keys, conf->passwords) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, conf->prefer_server_ciphers) != NGX_OK) @@ -678,7 +766,7 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } if (ngx_ssl_session_cache(&conf->ssl, &ngx_stream_ssl_sess_id_ctx, - conf->builtin_session_cache, + conf->certificates, conf->builtin_session_cache, conf->shm_zone, conf->session_timeout) != NGX_OK) { @@ -707,6 +795,90 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } +static ngx_int_t +ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, + ngx_stream_ssl_conf_t *conf) +{ + ngx_str_t *cert, *key; + ngx_uint_t i, nelts; + ngx_stream_complex_value_t *cv; + ngx_stream_compile_complex_value_t ccv; + + cert = conf->certificates->elts; + key = conf->certificate_keys->elts; + nelts = conf->certificates->nelts; + + for (i = 0; i < nelts; i++) { + + if (ngx_stream_script_variables_count(&cert[i])) { + goto found; + } + + if (ngx_stream_script_variables_count(&key[i])) { + goto found; + } + } + + return NGX_OK; + +found: + + conf->certificate_values = ngx_array_create(cf->pool, nelts, + sizeof(ngx_stream_complex_value_t)); + if (conf->certificate_values == NULL) { + return NGX_ERROR; + } + + conf->certificate_key_values = ngx_array_create(cf->pool, nelts, + sizeof(ngx_stream_complex_value_t)); + if (conf->certificate_key_values == NULL) { + return NGX_ERROR; + } + + for (i = 0; i < nelts; i++) { + + cv = ngx_array_push(conf->certificate_values); + if (cv == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &cert[i]; + ccv.complex_value = cv; + ccv.zero = 1; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + + cv = ngx_array_push(conf->certificate_key_values); + if (cv == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &key[i]; + ccv.complex_value = cv; + ccv.zero = 1; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_ERROR; + } + } + + conf->passwords = ngx_ssl_preserve_passwords(cf, conf->passwords); + if (conf->passwords == NULL) { + return NGX_ERROR; + } + + return NGX_OK; +} + + static char * ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h index 9f8f01c..6cb4140 100644 --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -34,6 +34,9 @@ typedef struct { ngx_array_t *certificates; ngx_array_t *certificate_keys; + ngx_array_t *certificate_values; + ngx_array_t *certificate_key_values; + ngx_str_t dhparam; ngx_str_t ecdh_curve; ngx_str_t client_certificate;