From bd90c5a5685318f01799cde635ff98b60f675dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Uhliarik?= Date: Fri, 21 Jul 2023 17:35:13 +0200 Subject: [PATCH] Resolves: #2224582 - Running nginx with systemctl and entering ssl private key's pass phrase - added new ssl_pass_phrase_dialog directive which enables setting external program for entering password for encrypted private key --- 0003-add-ssl-pass-phrase-dialog.patch | 754 ++++++++++++++++++++++++++ nginx-ssl-pass-dialog | 3 + nginx.spec | 11 + 3 files changed, 768 insertions(+) create mode 100644 0003-add-ssl-pass-phrase-dialog.patch create mode 100755 nginx-ssl-pass-dialog diff --git a/0003-add-ssl-pass-phrase-dialog.patch b/0003-add-ssl-pass-phrase-dialog.patch new file mode 100644 index 0000000..6e5986b --- /dev/null +++ b/0003-add-ssl-pass-phrase-dialog.patch @@ -0,0 +1,754 @@ +diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim +index 7d587fc..15b21e2 100644 +--- a/contrib/vim/syntax/nginx.vim ++++ b/contrib/vim/syntax/nginx.vim +@@ -617,6 +617,7 @@ syn keyword ngxDirective contained ssl_ocsp + syn keyword ngxDirective contained ssl_ocsp_cache + syn keyword ngxDirective contained ssl_ocsp_responder + syn keyword ngxDirective contained ssl_password_file ++syn keyword ngxDirective contained ssl_pass_phrase_dialog + syn keyword ngxDirective contained ssl_prefer_server_ciphers + syn keyword ngxDirective contained ssl_preread + syn keyword ngxDirective contained ssl_protocols +diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c +index 104e8da..8cf777e 100644 +--- a/src/event/ngx_event_openssl.c ++++ b/src/event/ngx_event_openssl.c +@@ -9,9 +9,8 @@ + #include + #include + +- + #define NGX_SSL_PASSWORD_BUFFER_SIZE 4096 +- ++#define NGX_PASS_PHRASE_ARG_MAX_LEN 255 + + typedef struct { + ngx_uint_t engine; /* unsigned engine:1; */ +@@ -20,8 +19,8 @@ typedef struct { + + 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 EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, ++ char **err, ngx_str_t *key, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg); + 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); +@@ -88,6 +87,12 @@ static time_t ngx_ssl_parse_time( + #endif + ASN1_TIME *asn1time, ngx_log_t *log); + ++static int ngx_ssl_read_pstream(const char *cmd, char *buf, ++ ngx_int_t bufsize); ++ ++static int ngx_ssl_pass_phrase_callback(char *buf, int bufsize, ++ int rwflag, void *u); ++ + static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); + static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + static void ngx_openssl_exit(ngx_cycle_t *cycle); +@@ -398,7 +403,7 @@ 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_array_t *keys, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg) + { + ngx_str_t *cert, *key; + ngx_uint_t i; +@@ -408,7 +413,7 @@ ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs, + + for (i = 0; i < certs->nelts; i++) { + +- if (ngx_ssl_certificate(cf, ssl, &cert[i], &key[i], passwords) ++ if (ngx_ssl_certificate(cf, ssl, &cert[i], &key[i], passwords, dlg) + != NGX_OK) + { + return NGX_ERROR; +@@ -421,12 +426,13 @@ ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs, + + 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_str_t *key, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg) + { + char *err; + X509 *x509; + EVP_PKEY *pkey; + STACK_OF(X509) *chain; ++ EVP_PKEY *pubkey; + + x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); + if (x509 == NULL) { +@@ -516,8 +522,19 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, + } + #endif + +- pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords); +- if (pkey == NULL) { ++ pubkey = X509_get_pubkey(x509); ++ if (!pubkey){ ++ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, ++ "X509_get_pubkey() failed"); ++ return NGX_ERROR; ++ } ++ dlg->cryptosystem = EVP_PKEY_get_base_id(pubkey); ++ EVP_PKEY_free(pubkey); ++ ++ pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords, dlg); ++ if (ngx_test_config){ ++ return NGX_OK; ++ } else if (pkey == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate key \"%s\": %s", +@@ -587,7 +604,7 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, + + #endif + +- pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords); ++ pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords, NULL); + if (pkey == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, +@@ -700,10 +717,81 @@ ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, + return x509; + } + ++static int ++ngx_ssl_read_pstream(const char *cmd, char *buf, ngx_int_t bufsize) ++{ ++ FILE *fp; ++ ngx_int_t i; ++ char c; ++ ++ fp = popen(cmd, "r"); ++ if (fp == NULL) { ++ return -1; ++ } ++ ++ for (i = 0; (c = fgetc(fp)) != EOF && ++ (i < bufsize - 1); i++) { ++ ++ if (c == '\n' || c == '\r'){ ++ break; ++ } ++ ++ buf[i] = c; ++ } ++ buf[i] = '\0'; ++ ++ pclose(fp); ++ ++ return 0; ++} ++ ++static int ++ngx_ssl_pass_phrase_callback(char *buf, int bufsize, int rwflag, void *u) ++{ ++ u_char cmd[NGX_PASS_PHRASE_ARG_MAX_LEN + 1] = {0}; ++ u_char *cmd_end; ++ ngx_ssl_ppdialog_conf_t *dlg = (ngx_ssl_ppdialog_conf_t *)u; ++ ngx_str_t *pass_phrase_dialog = dlg->data; ++ char cryptosystem[4] = {0}; ++ int ret; ++ ++ /* remove exec: str from pass_phrase_dialog */ ++ pass_phrase_dialog->data = pass_phrase_dialog->data + 5; ++ pass_phrase_dialog->len = pass_phrase_dialog->len - 5; ++ ++ switch (dlg->cryptosystem){ ++ case EVP_PKEY_RSA: ++ strncpy(cryptosystem, "RSA", 4); ++ break; ++ case EVP_PKEY_DSA: ++ strncpy(cryptosystem, "DSA", 4); ++ break; ++ case EVP_PKEY_EC: ++ strncpy(cryptosystem, "EC", 3); ++ break; ++ case EVP_PKEY_DH: ++ strncpy(cryptosystem, "DH", 3); ++ break; ++ default: ++ strncpy(cryptosystem, "UNK", 4); ++ break; ++ } ++ ++ cmd_end = ngx_snprintf(cmd, NGX_PASS_PHRASE_ARG_MAX_LEN, "%V %V %s", ++ pass_phrase_dialog, dlg->server, cryptosystem); ++ *cmd_end = '\0'; ++ ++ ngx_log_stderr(0, "Executing external script: %s\n", cmd); ++ ++ if ((ret = ngx_ssl_read_pstream((char *)cmd, buf, bufsize)) != 0){ ++ return -1; ++ } ++ ++ return strlen(buf); ++} + + static EVP_PKEY * +-ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, +- ngx_str_t *key, ngx_array_t *passwords) ++ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, ngx_str_t *key, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg) + { + BIO *bio; + EVP_PKEY *pkey; +@@ -791,11 +879,26 @@ ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, + tries = 1; + pwd = NULL; + cb = NULL; ++ ++ /** directive format: ssl_pass_phrase_dialog buildin|exec:filepath */ ++ if (dlg && ngx_strncasecmp(dlg->data->data, (u_char *)"exec:", 5) == 0){ ++ pwd = (void *)dlg; ++ cb = ngx_ssl_pass_phrase_callback; ++ } else { ++ pwd = NULL; ++ cb = NULL; ++ } + } + +- for ( ;; ) { ++ /* skip decrypting private keys in config test phase to avoid ++ asking for pass phase twice */ ++ if (ngx_test_config){ ++ return NULL; ++ } + ++ for ( ;; ) { + pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); ++ + if (pkey != NULL) { + break; + } +diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h +index 860ea26..41f4501 100644 +--- a/src/event/ngx_event_openssl.h ++++ b/src/event/ngx_event_openssl.h +@@ -74,9 +74,19 @@ + #define ERR_peek_error_data(d, f) ERR_peek_error_line_data(NULL, NULL, d, f) + #endif + ++#define NGX_SSL_PASS_PHRASE_ARG_MAX_LEN 255 ++#define NGX_SSL_PASS_PHRASE_DEFAULT_VAL "builtin" ++#define NGX_SSL_SERVER_NULL "undefined" + + typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; + ++typedef struct ngx_ssl_ppdialog_conf_s ngx_ssl_ppdialog_conf_t; ++ ++struct ngx_ssl_ppdialog_conf_s { ++ ngx_str_t *data; ++ ngx_str_t *server; ++ ngx_int_t cryptosystem; ++}; + + struct ngx_ssl_s { + SSL_CTX *ctx; +@@ -84,7 +94,6 @@ struct ngx_ssl_s { + size_t buffer_size; + }; + +- + struct ngx_ssl_connection_s { + ngx_ssl_conn_t *connection; + SSL_CTX *session_ctx; +@@ -184,9 +193,9 @@ 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_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg); + 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_str_t *cert, ngx_str_t *key, ngx_array_t *passwords, ngx_ssl_ppdialog_conf_t *dlg); + 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); + +diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c +index dfe49c5..904263d 100644 +--- a/src/http/modules/ngx_http_grpc_module.c ++++ b/src/http/modules/ngx_http_grpc_module.c +@@ -4983,7 +4983,7 @@ ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) + if (ngx_ssl_certificate(cf, glcf->upstream.ssl, + &glcf->upstream.ssl_certificate->value, + &glcf->upstream.ssl_certificate_key->value, +- glcf->upstream.ssl_passwords) ++ glcf->upstream.ssl_passwords, NULL) + != NGX_OK) + { + return NGX_ERROR; +diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c +index 9cc202c..2c938d7 100644 +--- a/src/http/modules/ngx_http_proxy_module.c ++++ b/src/http/modules/ngx_http_proxy_module.c +@@ -5032,7 +5032,7 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) + if (ngx_ssl_certificate(cf, plcf->upstream.ssl, + &plcf->upstream.ssl_certificate->value, + &plcf->upstream.ssl_certificate_key->value, +- plcf->upstream.ssl_passwords) ++ plcf->upstream.ssl_passwords, NULL) + != NGX_OK) + { + return NGX_ERROR; +diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c +index 4c4a598..a147054 100644 +--- a/src/http/modules/ngx_http_ssl_module.c ++++ b/src/http/modules/ngx_http_ssl_module.c +@@ -17,8 +17,9 @@ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, + #define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" + #define NGX_DEFAULT_ECDH_CURVE "auto" + +-#define NGX_HTTP_ALPN_PROTOS "\x08http/1.1\x08http/1.0\x08http/0.9" ++static ngx_str_t ngx_ssl_server_null = ngx_string(NGX_SSL_SERVER_NULL); + ++#define NGX_HTTP_ALPN_PROTOS "\x08http/1.1\x08http/1.0\x08http/0.9" + + #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + static int ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, +@@ -53,6 +54,9 @@ static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, + + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); + ++static char *ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); ++ + + static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { + { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, +@@ -296,6 +300,13 @@ static ngx_command_t ngx_http_ssl_commands[] = { + offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), + NULL }, + ++ { ngx_string("ssl_pass_phrase_dialog"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_pass_phrase_dialog, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, pass_phrase_dialog), ++ NULL }, ++ + ngx_null_command + }; + +@@ -555,7 +566,7 @@ ngx_http_ssl_add_variables(ngx_conf_t *cf) + static void * + ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) + { +- ngx_http_ssl_srv_conf_t *sscf; ++ ngx_http_ssl_srv_conf_t *sscf; + + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t)); + if (sscf == NULL) { +@@ -577,6 +588,8 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) + * sscf->ocsp_responder = { 0, NULL }; + * sscf->stapling_file = { 0, NULL }; + * sscf->stapling_responder = { 0, NULL }; ++ * sscf->pass_phrase_dialog = NULL; ++ * + */ + + sscf->enable = NGX_CONF_UNSET; +@@ -608,6 +621,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) + { + ngx_http_ssl_srv_conf_t *prev = parent; + ngx_http_ssl_srv_conf_t *conf = child; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_ssl_ppdialog_conf_t dlg; + + ngx_pool_cleanup_t *cln; + +@@ -674,6 +689,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) + ngx_conf_merge_str_value(conf->stapling_responder, + prev->stapling_responder, ""); + ++ ngx_conf_merge_str_value(conf->pass_phrase_dialog, ++ prev->pass_phrase_dialog, NGX_SSL_PASS_PHRASE_DEFAULT_VAL); ++ + conf->ssl.log = cf->log; + + if (conf->enable) { +@@ -736,6 +754,30 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = &conf->ssl; + ++ /** directive format: ssl_pass_phrase_dialog buildin|exec:filepath */ ++ if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)"exec:", 5) == 0){ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive SET: %s ", conf->pass_phrase_dialog.data); ++ } else if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)NGX_SSL_PASS_PHRASE_DEFAULT_VAL, ++ sizeof(NGX_SSL_PASS_PHRASE_DEFAULT_VAL)) != 0){ ++ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive accepts only the following " ++ "values: %s | exec:filepath", NGX_SSL_PASS_PHRASE_DEFAULT_VAL); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); ++ ++ dlg.data = &conf->pass_phrase_dialog; ++ if (cscf->server_name.len != 0) { ++ dlg.server = &cscf->server_name; ++ } else { ++ dlg.server = &ngx_ssl_server_null; ++ } ++ ++ + #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + + if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, +@@ -786,7 +828,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) + /* configure certificates */ + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, +- conf->certificate_keys, conf->passwords) ++ conf->certificate_keys, conf->passwords, &dlg) + != NGX_OK) + { + return NGX_CONF_ERROR; +@@ -1335,3 +1377,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) + + return NGX_OK; + } ++ ++static char * ++ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_http_ssl_srv_conf_t *sscf = conf; ++ ngx_str_t *value; ++ ++ if (sscf->pass_phrase_dialog.data){ ++ return "is duplicate"; ++ } ++ ++ value = cf->args->elts; ++ ++ sscf->pass_phrase_dialog = value[1]; ++ ++ if (sscf->pass_phrase_dialog.len == 0) { ++ return NGX_CONF_OK; ++ } else if (sscf->pass_phrase_dialog.len > NGX_SSL_PASS_PHRASE_ARG_MAX_LEN) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog argument length exceeded maximum possible length: %d", ++ NGX_SSL_PASS_PHRASE_ARG_MAX_LEN); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} ++ +diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h +index 7ab0f7e..2f83d75 100644 +--- a/src/http/modules/ngx_http_ssl_module.h ++++ b/src/http/modules/ngx_http_ssl_module.h +@@ -67,6 +67,8 @@ typedef struct { + + u_char *file; + ngx_uint_t line; ++ ++ ngx_str_t pass_phrase_dialog; + } ngx_http_ssl_srv_conf_t; + + +diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c +index e4f721b..61efa99 100644 +--- a/src/http/modules/ngx_http_uwsgi_module.c ++++ b/src/http/modules/ngx_http_uwsgi_module.c +@@ -2564,7 +2564,7 @@ ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) + if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, + &uwcf->upstream.ssl_certificate->value, + &uwcf->upstream.ssl_certificate_key->value, +- uwcf->upstream.ssl_passwords) ++ uwcf->upstream.ssl_passwords, NULL) + != NGX_OK) + { + return NGX_ERROR; +diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c +index 28737ac..728181d 100644 +--- a/src/mail/ngx_mail_ssl_module.c ++++ b/src/mail/ngx_mail_ssl_module.c +@@ -13,6 +13,7 @@ + #define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" + #define NGX_DEFAULT_ECDH_CURVE "auto" + ++static ngx_str_t ngx_ssl_server_null = ngx_string(NGX_SSL_SERVER_NULL); + + #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + static int ngx_mail_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, +@@ -35,6 +36,8 @@ static char *ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, + static char *ngx_mail_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); + ++static char *ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); + + static ngx_conf_enum_t ngx_mail_starttls_state[] = { + { ngx_string("off"), NGX_MAIL_STARTTLS_OFF }, +@@ -216,6 +219,13 @@ static ngx_command_t ngx_mail_ssl_commands[] = { + offsetof(ngx_mail_ssl_conf_t, conf_commands), + &ngx_mail_ssl_conf_command_post }, + ++ { ngx_string("ssl_pass_phrase_dialog"), ++ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_pass_phrase_dialog, ++ NGX_MAIL_SRV_CONF_OFFSET, ++ offsetof(ngx_mail_ssl_conf_t, pass_phrase_dialog), ++ NULL }, ++ + ngx_null_command + }; + +@@ -345,6 +355,8 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + { + ngx_mail_ssl_conf_t *prev = parent; + ngx_mail_ssl_conf_t *conf = child; ++ ngx_mail_core_srv_conf_t *cscf; ++ ngx_ssl_ppdialog_conf_t dlg; + + char *mode; + ngx_pool_cleanup_t *cln; +@@ -388,6 +400,8 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + + ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + ++ ngx_conf_merge_str_value(conf->pass_phrase_dialog, ++ prev->pass_phrase_dialog, NGX_SSL_PASS_PHRASE_DEFAULT_VAL); + + conf->ssl.log = cf->log; + +@@ -449,6 +463,29 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = &conf->ssl; + ++ /** directive format: ssl_pass_phrase_dialog buildin|exec:filepath */ ++ if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)"exec:", 5) == 0){ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive SET: %s ", conf->pass_phrase_dialog.data); ++ } else if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)NGX_SSL_PASS_PHRASE_DEFAULT_VAL, ++ sizeof(NGX_SSL_PASS_PHRASE_DEFAULT_VAL)) != 0){ ++ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive accepts only the following " ++ "values: %s | exec:filepath", NGX_SSL_PASS_PHRASE_DEFAULT_VAL); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ cscf = ngx_mail_conf_get_module_srv_conf(cf, ngx_mail_core_module); ++ ++ dlg.data = &conf->pass_phrase_dialog; ++ if (cscf->server_name.len != 0) { ++ dlg.server = &cscf->server_name; ++ } else { ++ dlg.server = &ngx_ssl_server_null; ++ } ++ + #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_mail_ssl_alpn_select, NULL); + #endif +@@ -461,7 +498,7 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + } + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, +- conf->certificate_keys, conf->passwords) ++ conf->certificate_keys, conf->passwords, &dlg) + != NGX_OK) + { + return NGX_CONF_ERROR; +@@ -745,3 +782,32 @@ ngx_mail_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) + return NGX_CONF_OK; + #endif + } ++ ++static char * ++ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_mail_ssl_conf_t *sscf = conf; ++ ngx_str_t *value; ++ ++ if (sscf->pass_phrase_dialog.data){ ++ return "is duplicate"; ++ } ++ ++ value = cf->args->elts; ++ ++ sscf->pass_phrase_dialog = value[1]; ++ ++ if (sscf->pass_phrase_dialog.len == 0) { ++ return NGX_CONF_OK; ++ } else if (sscf->pass_phrase_dialog.len > NGX_SSL_PASS_PHRASE_ARG_MAX_LEN) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog argument length exceeded maximum possible length: %d", ++ NGX_SSL_PASS_PHRASE_ARG_MAX_LEN); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} ++ ++ +diff --git a/src/mail/ngx_mail_ssl_module.h b/src/mail/ngx_mail_ssl_module.h +index a0a6113..3d87d50 100644 +--- a/src/mail/ngx_mail_ssl_module.h ++++ b/src/mail/ngx_mail_ssl_module.h +@@ -57,6 +57,8 @@ typedef struct { + + u_char *file; + ngx_uint_t line; ++ ++ ngx_str_t pass_phrase_dialog; + } ngx_mail_ssl_conf_t; + + +diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c +index ed275c0..1747aed 100644 +--- a/src/stream/ngx_stream_proxy_module.c ++++ b/src/stream/ngx_stream_proxy_module.c +@@ -2305,7 +2305,7 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) + if (ngx_ssl_certificate(cf, pscf->ssl, + &pscf->ssl_certificate->value, + &pscf->ssl_certificate_key->value, +- pscf->ssl_passwords) ++ pscf->ssl_passwords, NULL) + != NGX_OK) + { + return NGX_ERROR; +diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c +index 1ba1825..ba70547 100644 +--- a/src/stream/ngx_stream_ssl_module.c ++++ b/src/stream/ngx_stream_ssl_module.c +@@ -17,6 +17,8 @@ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, + #define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" + #define NGX_DEFAULT_ECDH_CURVE "auto" + ++#define NGX_SSL_STREAM_NAME "NGX_STREAM_SSL_MODULE" ++static ngx_str_t ngx_ssl_stream_default_name = ngx_string(NGX_SSL_STREAM_NAME); + + 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, +@@ -57,6 +59,9 @@ static char *ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, + static char *ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); + ++static char *ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); ++ + static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf); + + +@@ -226,6 +231,13 @@ static ngx_command_t ngx_stream_ssl_commands[] = { + 0, + NULL }, + ++ { ngx_string("ssl_pass_phrase_dialog"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_pass_phrase_dialog, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_stream_ssl_conf_t, pass_phrase_dialog), ++ NULL }, ++ + ngx_null_command + }; + +@@ -690,6 +702,7 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + { + ngx_stream_ssl_conf_t *prev = parent; + ngx_stream_ssl_conf_t *conf = child; ++ ngx_ssl_ppdialog_conf_t dlg; + + ngx_pool_cleanup_t *cln; + +@@ -732,6 +745,8 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + + ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + ++ ngx_conf_merge_str_value(conf->pass_phrase_dialog, ++ prev->pass_phrase_dialog, NGX_SSL_PASS_PHRASE_DEFAULT_VAL); + + conf->ssl.log = cf->log; + +@@ -779,6 +794,23 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = &conf->ssl; + ++ /** directive format: ssl_pass_phrase_dialog buildin|exec:filepath */ ++ if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)"exec:", 5) == 0){ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive SET: %s ", conf->pass_phrase_dialog.data); ++ } else if (ngx_strncasecmp(conf->pass_phrase_dialog.data, (u_char *)NGX_SSL_PASS_PHRASE_DEFAULT_VAL, ++ sizeof(NGX_SSL_PASS_PHRASE_DEFAULT_VAL)) != 0){ ++ ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog config directive accepts only the following " ++ "values: %s | exec:filepath", NGX_SSL_PASS_PHRASE_DEFAULT_VAL); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ dlg.data = &conf->pass_phrase_dialog; ++ dlg.server = &ngx_ssl_stream_default_name; ++ + #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, + ngx_stream_ssl_servername); +@@ -823,7 +855,7 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) + /* configure certificates */ + + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, +- conf->certificate_keys, conf->passwords) ++ conf->certificate_keys, conf->passwords, &dlg) + != NGX_OK) + { + return NGX_CONF_ERROR; +@@ -1209,3 +1241,31 @@ ngx_stream_ssl_init(ngx_conf_t *cf) + + return NGX_OK; + } ++ ++static char * ++ngx_conf_set_pass_phrase_dialog(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_stream_ssl_conf_t *sscf = conf; ++ ngx_str_t *value; ++ ++ if (sscf->pass_phrase_dialog.data){ ++ return "is duplicate"; ++ } ++ ++ value = cf->args->elts; ++ ++ sscf->pass_phrase_dialog = value[1]; ++ ++ if (sscf->pass_phrase_dialog.len == 0) { ++ return NGX_CONF_OK; ++ } else if (sscf->pass_phrase_dialog.len > NGX_SSL_PASS_PHRASE_ARG_MAX_LEN) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "ssl_pass_phrase_dialog argument length exceeded maximum possible length: %d", ++ NGX_SSL_PASS_PHRASE_ARG_MAX_LEN); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} ++ +diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h +index e7c825e..d80daa4 100644 +--- a/src/stream/ngx_stream_ssl_module.h ++++ b/src/stream/ngx_stream_ssl_module.h +@@ -56,6 +56,8 @@ typedef struct { + + u_char *file; + ngx_uint_t line; ++ ++ ngx_str_t pass_phrase_dialog; + } ngx_stream_ssl_conf_t; + + diff --git a/nginx-ssl-pass-dialog b/nginx-ssl-pass-dialog new file mode 100755 index 0000000..79318a6 --- /dev/null +++ b/nginx-ssl-pass-dialog @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /bin/systemd-ask-password "Enter TLS private key passphrase for $1 ($2) : " diff --git a/nginx.spec b/nginx.spec index ee36d61..9313c9b 100644 --- a/nginx.spec +++ b/nginx.spec @@ -78,6 +78,7 @@ Source13: nginx-upgrade Source14: nginx-upgrade.8 Source15: macros.nginxmods.in Source16: nginxmods.attr +Source17: nginx-ssl-pass-dialog Source102: nginx-logo.png Source103: 404.html Source104: 50x.html @@ -92,6 +93,10 @@ Patch0: 0001-remove-Werror-in-upstream-build-scripts.patch # rejected upstream: https://trac.nginx.org/nginx/ticket/1897 Patch1: 0002-fix-PIDFile-handling.patch +# downstream patch - Add ssl-pass-phrase-dialog helper script for +# encrypted private keys with pass phrase decryption +Patch2: 0003-add-ssl-pass-phrase-dialog.patch + BuildRequires: make BuildRequires: gcc BuildRequires: gnupg2 @@ -452,6 +457,11 @@ sed -e "s|@@NGINX_ABIVERSION@@|%{nginx_abiversion}|g" \ ## Install dependency generator install -Dpm0644 %{SOURCE16} %{buildroot}%{_fileattrsdir}/nginxmods.attr +# install http-ssl-pass-dialog +mkdir -p $RPM_BUILD_ROOT%{_libexecdir} +install -m755 $RPM_SOURCE_DIR/nginx-ssl-pass-dialog \ + $RPM_BUILD_ROOT%{_libexecdir}/nginx-ssl-pass-dialog + %pre filesystem getent group %{nginx_user} > /dev/null || groupadd -r %{nginx_user} @@ -523,6 +533,7 @@ fi %license LICENSE %doc CHANGES README README.dynamic %{_sbindir}/nginx +%{_libexecdir}/nginx-ssl-pass-dialog %config(noreplace) %{_sysconfdir}/nginx/fastcgi.conf %config(noreplace) %{_sysconfdir}/nginx/fastcgi.conf.default %config(noreplace) %{_sysconfdir}/nginx/fastcgi_params