├── CHANGELOG.markdown ├── LICENSE ├── README.markdown ├── config ├── ngx_http_ssl_ct_module.c ├── ngx_mail_ssl_ct_module.c ├── ngx_ssl_ct_module.c ├── ngx_ssl_ct_module.h └── ngx_stream_ssl_ct_module.c /CHANGELOG.markdown: -------------------------------------------------------------------------------- 1 | # UNRELEASED 2 | 3 | * Add TLS 1.3 support. 4 | * Build CT modules if the mail/stream modules are dynamically linked. 5 | * Add module name macros. 6 | 7 | # 1.3.2 (30 November 2016) 8 | 9 | * Don't send invalid zero-length SCT extensions. 10 | 11 | # 1.3.1 (20 September 2016) 12 | 13 | * Use exported functions to get the negotiated SSL certificate. 14 | 15 | # 1.3.0 (10 July 2016) 16 | 17 | * Fix compatibility with nginx 1.11.2 when the stream module is enabled. 18 | * Add support for multiple certificates when using nginx 1.11.0 or above. 19 | 20 | # 1.2.0 (14 February 2016) 21 | 22 | * Add dynamic module support. 23 | 24 | # 1.1.0 (5 February 2016) 25 | 26 | * Add BoringSSL support. 27 | * Add support for the mail and stream modules. 28 | 29 | # 1.0.0 (11 November 2015) 30 | 31 | * Initial stable release. 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 Graham Edgecombe 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | nginx Certificate Transparency module 2 | ===================================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | This module adds support for the TLS `signed_certificate_timestamp` extension to 8 | nginx, which is one of the mechanisms supported by Google's 9 | [Certificate Transparency][ct] project to deliver Signed Certificate Timestamps 10 | to TLS clients. 11 | 12 | Building 13 | -------- 14 | 15 | Add `--add-module=/path/to/nginx-ct` to the nginx `./configure` invocation. 16 | 17 | If you are using nginx 1.9.11 or above, you can use 18 | `--add-dynamic-module=/path/to/nginx-ct` to build as a dynamic module. 19 | 20 | The following versions of OpenSSL are supported: 21 | 22 | * OpenSSL 1.0.2 or above. 23 | * BoringSSL [4fac72e][boringssl] or above. 24 | 25 | LibreSSL is **not** supported as it doesn't provide either of the functions used 26 | to add the `signed_certificate_timestamp` extension to the response 27 | (`SSL_CTX_add_server_custom_ext` and `SSL_CTX_set_signed_cert_timestamp_list`). 28 | 29 | OpenSSL versions between 1.1.0 and 1.1.0e inclusive contain a [bug][openssl-bug] 30 | that prevents this module from working with non-`default_server` `server` 31 | blocks. The bug is fixed in OpenSSL 1.1.0f. 32 | 33 | Configuration 34 | ------------- 35 | 36 | If built as a dynamic module, add the following directives to the top level of 37 | your configuration file: 38 | 39 | load_module modules/ngx_ssl_ct_module.so; 40 | load_module modules/ngx_http_ssl_ct_module.so; 41 | 42 | You can also load `ngx_mail_ssl_ct_module.so` and `ngx_stream_ssl_ct_module.so` 43 | if you need `mail` or `stream` support. 44 | 45 | Add the following directives, which are valid in `http`, `mail`, `stream` and 46 | `server` blocks, to your configuration file: 47 | 48 | ssl_ct on; 49 | ssl_ct_static_scts /path/to/sct/dir; 50 | 51 | The module will read all `*.sct` files in the given directory, which are 52 | expected to be encoded in binary (see the definition of 53 | `SignedCertificateTimestamp` struct in [section 3.2 of RFC 6962][rfc]). This is 54 | the same format used by Apache's [mod\_ssl\_ct][apache] module. 55 | 56 | The module is compatible with nginx's multiple certificate support if you are 57 | using nginx 1.11.0 or above and are not using BoringSSL. Exactly one 58 | `ssl_ct_static_scts` directive must be specified for each `ssl_certificate` 59 | directive: 60 | 61 | ssl_ct on; 62 | 63 | ssl_certificate /path/to/rsa.pem; 64 | ssl_certificate_key /path/to/rsa.key; 65 | ssl_ct_static_scts /path/to/rsa/scts; 66 | 67 | ssl_certificate /path/to/ecdsa.pem; 68 | ssl_certificate_key /path/to/ecdsa.key; 69 | ssl_ct_static_scts /path/to/ecdsa/scts; 70 | 71 | [ct-submit][ct-submit] can be used to submit certificates to log servers and 72 | encode the `SignedCertificateTimestamp` struct in the appropriate format for use 73 | with this module. 74 | 75 | License 76 | ------- 77 | 78 | This project is available under the terms of the ISC license, which is similar 79 | to the 2-clause BSD license. See the `LICENSE` file for the copyright 80 | information and licensing terms. 81 | 82 | [ct]: http://www.certificate-transparency.org/ 83 | [rfc]: https://tools.ietf.org/html/rfc6962#section-3.2 84 | [apache]: https://httpd.apache.org/docs/trunk/mod/mod_ssl_ct.html 85 | [ct-submit]: https://github.com/grahamedgecombe/ct-submit 86 | [boringssl]: https://boringssl.googlesource.com/boringssl/+/4fac72e638c896c9fa30f5c6cd2fd7246f28f49e%5E!/ 87 | [openssl-bug]: https://github.com/openssl/openssl/issues/2180 88 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2017 Graham Edgecombe 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | ngx_addon_name=ngx_ssl_ct_module 16 | 17 | found_any=no 18 | 19 | if test -n "$ngx_module_link"; then 20 | ngx_module_type=CORE 21 | ngx_module_name=ngx_ssl_ct_module 22 | ngx_module_deps="$ngx_addon_dir/ngx_ssl_ct_module.h" 23 | ngx_module_srcs="$ngx_addon_dir/ngx_ssl_ct_module.c" 24 | ngx_module_libs=OPENSSL 25 | . auto/module 26 | have=NGX_SSL_CT . auto/have 27 | 28 | if [ $HTTP_SSL = YES ]; then 29 | ngx_module_type=HTTP 30 | ngx_module_name=ngx_http_ssl_ct_module 31 | ngx_module_deps="$ngx_addon_dir/ngx_ssl_ct_module.h" 32 | ngx_module_srcs="$ngx_addon_dir/ngx_http_ssl_ct_module.c" 33 | ngx_module_libs=OPENSSL 34 | ngx_module_order="ngx_http_ssl_module ngx_ssl_ct_module $ngx_module_name" 35 | . auto/module 36 | have=NGX_HTTP_SSL_CT . auto/have 37 | 38 | found_any=yes 39 | fi 40 | 41 | if [ $MAIL_SSL = YES ]; then 42 | ngx_module_type=MAIL 43 | ngx_module_name=ngx_mail_ssl_ct_module 44 | ngx_module_deps="$ngx_addon_dir/ngx_ssl_ct_module.h" 45 | ngx_module_srcs="$ngx_addon_dir/ngx_mail_ssl_ct_module.c" 46 | ngx_module_libs=OPENSSL 47 | ngx_module_order="ngx_mail_ssl_module ngx_ssl_ct_module $ngx_module_name" 48 | . auto/module 49 | have=NGX_MAIL_SSL_CT . auto/have 50 | 51 | found_any=yes 52 | fi 53 | 54 | if [ $STREAM_SSL = YES ]; then 55 | ngx_module_type=STREAM 56 | ngx_module_name=ngx_stream_ssl_ct_module 57 | ngx_module_deps="$ngx_addon_dir/ngx_ssl_ct_module.h" 58 | ngx_module_srcs="$ngx_addon_dir/ngx_stream_ssl_ct_module.c" 59 | ngx_module_libs=OPENSSL 60 | ngx_module_order="ngx_stream_ssl_module ngx_ssl_ct_module $ngx_module_name" 61 | . auto/module 62 | have=NGX_STREAM_SSL_CT . auto/have 63 | 64 | found_any=yes 65 | fi 66 | else 67 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_ssl_ct_module.h" 68 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_ssl_ct_module.c" 69 | CORE_MODULES="$CORE_MODULES ngx_ssl_ct_module" 70 | have=NGX_SSL_CT . auto/have 71 | 72 | if [ $HTTP_SSL = YES ]; then 73 | HTTP_MODULES="$HTTP_MODULES ngx_http_ssl_ct_module" 74 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_ssl_ct_module.c" 75 | found_any=yes 76 | have=NGX_HTTP_SSL_CT . auto/have 77 | fi 78 | 79 | if [ $MAIL_SSL = YES ]; then 80 | # XXX: we have to use MAIL_SSL_MODULE instead of MAIL_MODULES here such that 81 | # ngx_mail_ssl_ct_module is started *after* ngx_mail_ssl_module 82 | MAIL_SSL_MODULE="$MAIL_SSL_MODULE ngx_mail_ssl_ct_module" 83 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_mail_ssl_ct_module.c" 84 | found_any=yes 85 | have=NGX_MAIL_SSL_CT . auto/have 86 | fi 87 | 88 | if [ $STREAM_SSL = YES ]; then 89 | # XXX: we have to use STREAM_SSL_MODULE instead of STREAM_MODULES here such 90 | # that ngx_stream_ssl_ct_module is started *after* ngx_stream_ssl_module 91 | STREAM_SSL_MODULE="$STREAM_SSL_MODULE ngx_stream_ssl_ct_module" 92 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_stream_ssl_ct_module.c" 93 | found_any=yes 94 | have=NGX_STREAM_SSL_CT . auto/have 95 | fi 96 | fi 97 | 98 | if [ $found_any = no ]; then 99 | echo 100 | echo "$0: error: nginx-ct requires at least one of the following sets of flags:" 101 | echo " --with-http_ssl_module" 102 | echo " --with-mail --with-mail_ssl_module" 103 | echo " --with-stream --with-stream_ssl_module" 104 | exit 1 105 | fi 106 | -------------------------------------------------------------------------------- /ngx_http_ssl_ct_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2017 Graham Edgecombe 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include "ngx_ssl_ct_module.h" 19 | 20 | static char *ngx_http_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 21 | void *child); 22 | 23 | static ngx_http_module_t ngx_http_ssl_ct_module_ctx = { 24 | NULL, /* preconfiguration */ 25 | NULL, /* postconfiguration */ 26 | 27 | NULL, /* create main configuration */ 28 | NULL, /* init main configuration */ 29 | 30 | &ngx_ssl_ct_create_srv_conf, /* create server configuration */ 31 | &ngx_http_ssl_ct_merge_srv_conf, /* merge server configuration */ 32 | 33 | NULL, /* create location configuration */ 34 | NULL /* merge location configuration */ 35 | }; 36 | 37 | static ngx_command_t ngx_http_ssl_ct_commands[] = { 38 | { 39 | ngx_string("ssl_ct"), 40 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_FLAG, 41 | &ngx_conf_set_flag_slot, 42 | NGX_HTTP_SRV_CONF_OFFSET, 43 | offsetof(ngx_ssl_ct_srv_conf_t, enable), 44 | NULL 45 | }, 46 | { 47 | ngx_string("ssl_ct_static_scts"), 48 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, 49 | &ngx_conf_set_str_array_slot, 50 | NGX_HTTP_SRV_CONF_OFFSET, 51 | offsetof(ngx_ssl_ct_srv_conf_t, sct_dirs), 52 | NULL 53 | }, 54 | ngx_null_command 55 | }; 56 | 57 | ngx_module_t ngx_http_ssl_ct_module = { 58 | NGX_MODULE_V1, 59 | &ngx_http_ssl_ct_module_ctx, /* module context */ 60 | ngx_http_ssl_ct_commands, /* module directives */ 61 | NGX_HTTP_MODULE, /* module type */ 62 | NULL, /* init master */ 63 | NULL, /* init module */ 64 | NULL, /* init process */ 65 | NULL, /* init thread */ 66 | NULL, /* exit thread */ 67 | NULL, /* exit process */ 68 | NULL, /* exit master */ 69 | NGX_MODULE_V1_PADDING 70 | }; 71 | 72 | static char *ngx_http_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 73 | void *child) { 74 | ngx_http_ssl_srv_conf_t *ssl_conf = ngx_http_conf_get_module_srv_conf(cf, 75 | ngx_http_ssl_module); 76 | 77 | ngx_array_t *certificates; 78 | 79 | #if nginx_version >= 1011000 80 | certificates = ssl_conf->certificates; 81 | #else 82 | certificates = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); 83 | 84 | ngx_str_t *certificate = ngx_array_push(certificates); 85 | *certificate = ssl_conf->certificate; 86 | #endif 87 | 88 | return ngx_ssl_ct_merge_srv_conf(cf, parent, child, ssl_conf->ssl.ctx, 89 | certificates); 90 | } 91 | -------------------------------------------------------------------------------- /ngx_mail_ssl_ct_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2017 Graham Edgecombe 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include "ngx_ssl_ct_module.h" 19 | 20 | static char *ngx_mail_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 21 | void *child); 22 | 23 | static ngx_mail_module_t ngx_mail_ssl_ct_module_ctx = { 24 | NULL, /* protocol */ 25 | 26 | NULL, /* create main configuration */ 27 | NULL, /* init main configuration */ 28 | 29 | &ngx_ssl_ct_create_srv_conf, /* create server configuration */ 30 | &ngx_mail_ssl_ct_merge_srv_conf /* merge server configuration */ 31 | }; 32 | 33 | static ngx_command_t ngx_mail_ssl_ct_commands[] = { 34 | { 35 | ngx_string("ssl_ct"), 36 | NGX_MAIL_MAIN_CONF | NGX_MAIL_SRV_CONF | NGX_CONF_FLAG, 37 | &ngx_conf_set_flag_slot, 38 | NGX_MAIL_SRV_CONF_OFFSET, 39 | offsetof(ngx_ssl_ct_srv_conf_t, enable), 40 | NULL 41 | }, 42 | { 43 | ngx_string("ssl_ct_static_scts"), 44 | NGX_MAIL_MAIN_CONF | NGX_MAIL_SRV_CONF | NGX_CONF_TAKE1, 45 | &ngx_conf_set_str_array_slot, 46 | NGX_MAIL_SRV_CONF_OFFSET, 47 | offsetof(ngx_ssl_ct_srv_conf_t, sct_dirs), 48 | NULL 49 | }, 50 | ngx_null_command 51 | }; 52 | 53 | ngx_module_t ngx_mail_ssl_ct_module = { 54 | NGX_MODULE_V1, 55 | &ngx_mail_ssl_ct_module_ctx, /* module context */ 56 | ngx_mail_ssl_ct_commands, /* module directives */ 57 | NGX_MAIL_MODULE, /* module type */ 58 | NULL, /* init master */ 59 | NULL, /* init module */ 60 | NULL, /* init process */ 61 | NULL, /* init thread */ 62 | NULL, /* exit thread */ 63 | NULL, /* exit process */ 64 | NULL, /* exit master */ 65 | NGX_MODULE_V1_PADDING 66 | }; 67 | 68 | static char *ngx_mail_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 69 | void *child) { 70 | ngx_mail_ssl_conf_t *ssl_conf = ngx_mail_conf_get_module_srv_conf(cf, 71 | ngx_mail_ssl_module); 72 | 73 | ngx_array_t *certificates; 74 | 75 | #if nginx_version >= 1011000 76 | certificates = ssl_conf->certificates; 77 | #else 78 | certificates = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); 79 | 80 | ngx_str_t *certificate = ngx_array_push(certificates); 81 | *certificate = ssl_conf->certificate; 82 | #endif 83 | 84 | return ngx_ssl_ct_merge_srv_conf(cf, parent, child, ssl_conf->ssl.ctx, 85 | certificates); 86 | } 87 | -------------------------------------------------------------------------------- /ngx_ssl_ct_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2017 Graham Edgecombe 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "ngx_ssl_ct_module.h" 18 | 19 | static int ngx_ssl_ct_sct_list_index; 20 | 21 | static void *ngx_ssl_ct_create_conf(ngx_cycle_t *cycle); 22 | static ngx_ssl_ct_ext *ngx_ssl_ct_read_static_sct(ngx_conf_t *cf, 23 | ngx_str_t *dir, u_char *file, size_t file_len, 24 | ngx_ssl_ct_ext *sct_list); 25 | 26 | static ngx_core_module_t ngx_ssl_ct_module_ctx = { 27 | ngx_string("ssl_ct"), 28 | 29 | &ngx_ssl_ct_create_conf, /* create main configuration */ 30 | NULL /* init main configuration */ 31 | }; 32 | 33 | ngx_module_t ngx_ssl_ct_module = { 34 | NGX_MODULE_V1, 35 | &ngx_ssl_ct_module_ctx, /* module context */ 36 | NULL, /* module directives */ 37 | NGX_CORE_MODULE, /* module type */ 38 | NULL, /* init master */ 39 | NULL, /* init module */ 40 | NULL, /* init process */ 41 | NULL, /* init thread */ 42 | NULL, /* exit thread */ 43 | NULL, /* exit process */ 44 | NULL, /* exit master */ 45 | NGX_MODULE_V1_PADDING 46 | }; 47 | 48 | static void *ngx_ssl_ct_create_conf(ngx_cycle_t *cycle) { 49 | ngx_ssl_ct_sct_list_index = X509_get_ex_new_index(0, NULL, NULL, NULL, 50 | NULL); 51 | 52 | if (ngx_ssl_ct_sct_list_index == -1) { 53 | ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, 54 | "X509_get_ex_new_index failed"); 55 | return NULL; 56 | } 57 | 58 | return ngx_palloc(cycle->pool, 0); 59 | } 60 | 61 | void *ngx_ssl_ct_create_srv_conf(ngx_conf_t *cf) { 62 | ngx_ssl_ct_srv_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(*conf)); 63 | if (conf == NULL) { 64 | return NULL; 65 | } 66 | 67 | conf->enable = NGX_CONF_UNSET; 68 | conf->sct_dirs = NGX_CONF_UNSET_PTR; 69 | 70 | return conf; 71 | } 72 | 73 | char *ngx_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child, 74 | SSL_CTX *ssl_ctx, ngx_array_t *certificates) { 75 | /* merge config */ 76 | ngx_ssl_ct_srv_conf_t *prev = parent; 77 | ngx_ssl_ct_srv_conf_t *conf = child; 78 | 79 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 80 | ngx_conf_merge_ptr_value(conf->sct_dirs, prev->sct_dirs, NULL); 81 | 82 | /* validate config */ 83 | if (conf->enable) { 84 | if (!conf->sct_dirs) { 85 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 86 | "no \"ssl_ct_static_scts\" is defined for the \"ssl_ct\"" 87 | "directive"); 88 | return NGX_CONF_ERROR; 89 | } 90 | } else { 91 | return NGX_CONF_OK; 92 | } 93 | 94 | /* check if SSL is enabled */ 95 | if (!ssl_ctx) { 96 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 97 | "\"ssl_ct\" can only be enabled if ssl is enabled"); 98 | return NGX_CONF_ERROR; 99 | } 100 | 101 | /* check we have one SCT dir for each certificate */ 102 | ngx_uint_t sct_dir_count = conf->sct_dirs->nelts; 103 | if (sct_dir_count != certificates->nelts) { 104 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 105 | "there must be exactly one \"ssl_ct_static_scts\" directive for " 106 | "each \"ssl_certificate\" directive"); 107 | return NGX_CONF_ERROR; 108 | } 109 | 110 | /* loop through all the certs/SCT dirs */ 111 | ngx_str_t *sct_dirs = conf->sct_dirs->elts; 112 | X509 *cert = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_certificate_index); 113 | 114 | ngx_uint_t i; 115 | for (i = 0; i < sct_dir_count; i++) { 116 | /* the certificate linked list is stored in reverse order */ 117 | ngx_str_t *sct_dir = &sct_dirs[sct_dir_count - i - 1]; 118 | 119 | /* read the .sct files for this cert */ 120 | ngx_ssl_ct_ext *sct_list = ngx_ssl_ct_read_static_scts(cf, sct_dir); 121 | if (!sct_list) { 122 | /* ngx_ssl_ct_read_static_scts calls ngx_log_error */ 123 | return NGX_CONF_ERROR; 124 | } 125 | 126 | if (sct_list->len == 0) { 127 | ngx_pfree(cf->pool, sct_list); 128 | goto next; 129 | } 130 | 131 | #ifndef OPENSSL_IS_BORINGSSL 132 | /* associate the sct_list with the cert */ 133 | X509_set_ex_data(cert, ngx_ssl_ct_sct_list_index, sct_list); 134 | #else 135 | if (SSL_CTX_set_signed_cert_timestamp_list(ssl_ctx, sct_list->buf, 136 | sct_list->len) == 0) { 137 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 138 | "SSL_CTX_set_signed_cert_timestamp_list failed"); 139 | ngx_pfree(cf->pool, sct_list); 140 | return NGX_CONF_ERROR; 141 | } 142 | 143 | if (conf->sct_dirs->nelts > 1) { 144 | ngx_log_error(NGX_LOG_WARN, cf->log, 0, 145 | "BoringSSL does not support using SCTs with multiple " 146 | "certificates, the last non-empty \"ssl_ct_static_scts\" " 147 | "directory will be used for all certificates"); 148 | } 149 | 150 | break; 151 | #endif 152 | 153 | next: 154 | #if nginx_version >= 1011000 155 | cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index); 156 | #else 157 | break; 158 | #endif 159 | } 160 | 161 | #ifndef OPENSSL_IS_BORINGSSL 162 | /* add OpenSSL TLS extension */ 163 | # if OPENSSL_VERSION_NUMBER >= 0x10101000L 164 | int context = SSL_EXT_CLIENT_HELLO 165 | | SSL_EXT_TLS1_2_SERVER_HELLO 166 | | SSL_EXT_TLS1_3_CERTIFICATE; 167 | if (SSL_CTX_add_custom_ext(ssl_ctx, NGX_SSL_CT_EXT, context, 168 | &ngx_ssl_ct_ext_cb, NULL, NULL, NULL, NULL) == 0) { 169 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 170 | "SSL_CTX_add_custom_ext failed"); 171 | return NGX_CONF_ERROR; 172 | } 173 | # else 174 | if (SSL_CTX_add_server_custom_ext(ssl_ctx, NGX_SSL_CT_EXT, 175 | &ngx_ssl_ct_ext_cb, NULL, NULL, NULL, NULL) == 0) { 176 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 177 | "SSL_CTX_add_server_custom_ext failed"); 178 | return NGX_CONF_ERROR; 179 | } 180 | # endif 181 | #endif 182 | 183 | return NGX_CONF_OK; 184 | } 185 | 186 | #ifndef OPENSSL_IS_BORINGSSL 187 | # if OPENSSL_VERSION_NUMBER >= 0x10101000L 188 | int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, unsigned int context, 189 | const unsigned char **out, size_t *outlen, X509 *x, size_t chainidx, 190 | int *al, void *add_arg) { 191 | /* only include SCTs in the end-entity certificate */ 192 | if (context == SSL_EXT_TLS1_3_CERTIFICATE && chainidx != 0) { 193 | return 0; 194 | } 195 | # else 196 | int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, const unsigned char **out, 197 | size_t *outlen, int *al, void *add_arg) { 198 | X509 *x = NULL; 199 | # endif 200 | 201 | if (!x) { 202 | /* get the cert OpenSSL chose to use for this connection */ 203 | int result = SSL_set_current_cert(s, SSL_CERT_SET_SERVER); 204 | if (result == 2) { 205 | /* 206 | * Anonymous/PSK cipher suites don't use certificates, so don't attempt 207 | * to add the SCT extension to the ServerHello. 208 | */ 209 | return 0; 210 | } else if (result != 1) { 211 | ngx_connection_t *c = ngx_ssl_get_connection(s); 212 | ngx_log_error(NGX_LOG_WARN, c->log, 0, "SSL_set_current_cert failed"); 213 | return -1; 214 | } 215 | 216 | x = SSL_get_certificate(s); 217 | if (!x) { 218 | /* as above */ 219 | return 0; 220 | } 221 | } 222 | 223 | /* get sct_list for the cert OpenSSL chose to use for this connection */ 224 | ngx_ssl_ct_ext *sct_list = X509_get_ex_data(x, ngx_ssl_ct_sct_list_index); 225 | 226 | if (sct_list) { 227 | *out = sct_list->buf; 228 | *outlen = sct_list->len; 229 | return 1; 230 | } else { 231 | return 0; 232 | } 233 | } 234 | #endif 235 | 236 | static ngx_ssl_ct_ext *ngx_ssl_ct_read_static_sct(ngx_conf_t *cf, 237 | ngx_str_t *dir, u_char *file, size_t file_len, 238 | ngx_ssl_ct_ext *sct_list) { 239 | int ok = 0; 240 | 241 | /* join dir and file name */ 242 | size_t path_len = dir->len + file_len + 2; 243 | u_char *path = ngx_pcalloc(cf->pool, path_len); 244 | if (path == NULL) { 245 | return NULL; 246 | } 247 | 248 | u_char *path_end = ngx_cpystrn(path, dir->data, dir->len + 1); 249 | *path_end++ = '/'; 250 | ngx_cpystrn(path_end, file, file_len + 1); 251 | 252 | /* open file */ 253 | ngx_fd_t fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); 254 | if (fd == NGX_FILE_ERROR) { 255 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 256 | ngx_open_file_n " \"%s\" failed", path); 257 | ngx_pfree(cf->pool, path); 258 | return NULL; 259 | } 260 | 261 | /* get file size */ 262 | ngx_file_info_t stat; 263 | if (ngx_fd_info(fd, &stat) == NGX_FILE_ERROR) { 264 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 265 | ngx_fd_info_n " \"%s\" failed", path); 266 | goto out; 267 | } 268 | 269 | const size_t sct_len = ngx_file_size(&stat); 270 | if (sct_len == 0) { 271 | ok = 1; 272 | goto out; 273 | } 274 | 275 | const size_t len_pos = sct_list->len; 276 | size_t sct_pos = len_pos + 2; 277 | 278 | /* reserve two bytes for the length and sct_len bytes for the SCT. */ 279 | const size_t sct_and_len_size = sct_len + 2; 280 | 281 | if (sct_and_len_size < sct_len || 282 | sct_list->len + sct_and_len_size < sct_list->len || 283 | sct_list->len + sct_and_len_size > NGX_SSL_CT_EXT_MAX_LEN) { 284 | ngx_log_error(NGX_LOG_EMERG, cf->log, 0, 285 | "sct_list structure exceeds maximum length"); 286 | goto out; 287 | } 288 | sct_list->len += sct_and_len_size; 289 | 290 | /* read the SCT from disk */ 291 | size_t to_read = sct_len; 292 | while (to_read > 0) { 293 | ssize_t n = ngx_read_fd(fd, sct_list->buf + sct_pos, to_read); 294 | if (n == NGX_FILE_ERROR) { 295 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 296 | ngx_read_fd_n " \"%s\" failed", path); 297 | goto out; 298 | } 299 | 300 | to_read -= n; 301 | sct_pos += n; 302 | } 303 | 304 | /* fill in the length bytes and return */ 305 | sct_list->buf[len_pos] = sct_len >> 8; 306 | sct_list->buf[len_pos + 1] = sct_len; 307 | ok = 1; 308 | 309 | out: 310 | if (ngx_close_file(fd) != NGX_OK) { 311 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 312 | ngx_close_file_n " \"%s\" failed", path); 313 | } 314 | ngx_pfree(cf->pool, path); 315 | 316 | if (!ok) { 317 | return NULL; 318 | } 319 | return sct_list; 320 | } 321 | 322 | ngx_ssl_ct_ext *ngx_ssl_ct_read_static_scts(ngx_conf_t *cf, ngx_str_t *path) { 323 | /* resolve relative paths */ 324 | if (ngx_conf_full_name(cf->cycle, path, 1) != NGX_OK) { 325 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 326 | "ngx_conf_full_name \"%V\" failed"); 327 | return NULL; 328 | } 329 | 330 | /* allocate sct_list structure */ 331 | ngx_ssl_ct_ext *sct_list = ngx_pcalloc(cf->pool, sizeof(*sct_list)); 332 | if (!sct_list) { 333 | return NULL; 334 | } 335 | 336 | /* reserve the first two bytes for the length */ 337 | sct_list->len += 2; 338 | 339 | /* open directory */ 340 | ngx_dir_t dir; 341 | if (ngx_open_dir(path, &dir) != NGX_OK) { 342 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 343 | ngx_open_dir_n " \"%V\" failed", path); 344 | ngx_pfree(cf->pool, sct_list); 345 | return NULL; 346 | } 347 | 348 | /* iterate through all files */ 349 | for (;;) { 350 | ngx_set_errno(NGX_ENOMOREFILES); 351 | 352 | if (ngx_read_dir(&dir) != NGX_OK) { 353 | ngx_err_t err = ngx_errno; 354 | 355 | if (err == NGX_ENOMOREFILES) { 356 | break; 357 | } else { 358 | ngx_log_error(NGX_LOG_EMERG, cf->log, err, 359 | ngx_read_dir_n " \"%V\" failed", path); 360 | ngx_pfree(cf->pool, sct_list); 361 | return NULL; 362 | } 363 | } 364 | 365 | /* skip dotfiles */ 366 | size_t file_len = ngx_de_namelen(&dir); 367 | u_char *file = ngx_de_name(&dir); 368 | if (file[0] == '.') { 369 | continue; 370 | } 371 | 372 | /* skip files unless the extension is .sct */ 373 | u_char *file_ext = (u_char *) ngx_strrchr(file, '.'); 374 | if (!file_ext || ngx_strcmp(file_ext, ".sct")) { 375 | continue; 376 | } 377 | 378 | /* add the .sct file to the sct_list */ 379 | if (!ngx_ssl_ct_read_static_sct(cf, path, file, file_len, sct_list)) { 380 | /* ngx_ssl_ct_read_static_sct calls ngx_log_error */ 381 | ngx_pfree(cf->pool, sct_list); 382 | return NULL; 383 | } 384 | } 385 | 386 | /* close directory */ 387 | if (ngx_close_dir(&dir) != NGX_OK) { 388 | ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 389 | ngx_close_dir_n " \"%V\" failed", path); 390 | ngx_pfree(cf->pool, sct_list); 391 | return NULL; 392 | } 393 | 394 | /* fill in the length bytes and return */ 395 | size_t sct_list_len = sct_list->len - 2; 396 | if (sct_list_len > 0) { 397 | sct_list->buf[0] = sct_list_len >> 8; 398 | sct_list->buf[1] = sct_list_len; 399 | } else { 400 | sct_list->len = 0; 401 | } 402 | return sct_list; 403 | } 404 | -------------------------------------------------------------------------------- /ngx_ssl_ct_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2017 Graham Edgecombe 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef _NGX_SSL_CT_H_INCLUDED_ 18 | #define _NGX_SSL_CT_H_INCLUDED_ 19 | 20 | #include 21 | #include 22 | 23 | #define NGX_SSL_CT_EXT 18 /* from RFC 6962 */ 24 | #define NGX_SSL_CT_EXT_MAX_LEN 0xFFFF 25 | #define ngx_strrchr(s1, c) strrchr((const char *) s1, (int) c) 26 | 27 | typedef struct { 28 | ngx_flag_t enable; 29 | ngx_array_t *sct_dirs; 30 | } ngx_ssl_ct_srv_conf_t; 31 | 32 | typedef struct { 33 | u_char buf[NGX_SSL_CT_EXT_MAX_LEN]; 34 | size_t len; 35 | } ngx_ssl_ct_ext; 36 | 37 | ngx_int_t ngx_ssl_ct_init(ngx_log_t *log); 38 | #ifndef OPENSSL_IS_BORINGSSL 39 | # if OPENSSL_VERSION_NUMBER >= 0x10101000L 40 | int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, unsigned int context, 41 | const unsigned char **out, size_t *outlen, X509 *x, size_t chainidx, 42 | int *al, void *add_arg); 43 | # else 44 | int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, const unsigned char **out, 45 | size_t *outlen, int *al, void *add_arg); 46 | # endif 47 | #endif 48 | ngx_ssl_ct_ext *ngx_ssl_ct_read_static_scts(ngx_conf_t *cf, ngx_str_t *path); 49 | void *ngx_ssl_ct_create_srv_conf(ngx_conf_t *cf); 50 | char *ngx_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child, 51 | SSL_CTX *ssl_ctx, ngx_array_t *certificates); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /ngx_stream_ssl_ct_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2017 Graham Edgecombe 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include "ngx_ssl_ct_module.h" 19 | 20 | static char *ngx_stream_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 21 | void *child); 22 | 23 | static ngx_stream_module_t ngx_stream_ssl_ct_module_ctx = { 24 | #if nginx_version >= 1011002 25 | NULL, /* preconfiguration */ 26 | #endif 27 | NULL, /* postconfiguration */ 28 | 29 | NULL, /* create main configuration */ 30 | NULL, /* init main configuration */ 31 | 32 | &ngx_ssl_ct_create_srv_conf, /* create server configuration */ 33 | &ngx_stream_ssl_ct_merge_srv_conf /* merge server configuration */ 34 | }; 35 | 36 | static ngx_command_t ngx_stream_ssl_ct_commands[] = { 37 | { 38 | ngx_string("ssl_ct"), 39 | NGX_STREAM_MAIN_CONF | NGX_STREAM_SRV_CONF | NGX_CONF_FLAG, 40 | &ngx_conf_set_flag_slot, 41 | NGX_STREAM_SRV_CONF_OFFSET, 42 | offsetof(ngx_ssl_ct_srv_conf_t, enable), 43 | NULL 44 | }, 45 | { 46 | ngx_string("ssl_ct_static_scts"), 47 | NGX_STREAM_MAIN_CONF | NGX_STREAM_SRV_CONF | NGX_CONF_TAKE1, 48 | &ngx_conf_set_str_array_slot, 49 | NGX_STREAM_SRV_CONF_OFFSET, 50 | offsetof(ngx_ssl_ct_srv_conf_t, sct_dirs), 51 | NULL 52 | }, 53 | ngx_null_command 54 | }; 55 | 56 | ngx_module_t ngx_stream_ssl_ct_module = { 57 | NGX_MODULE_V1, 58 | &ngx_stream_ssl_ct_module_ctx, /* module context */ 59 | ngx_stream_ssl_ct_commands, /* module directives */ 60 | NGX_STREAM_MODULE, /* module type */ 61 | NULL, /* init master */ 62 | NULL, /* init module */ 63 | NULL, /* init process */ 64 | NULL, /* init thread */ 65 | NULL, /* exit thread */ 66 | NULL, /* exit process */ 67 | NULL, /* exit master */ 68 | NGX_MODULE_V1_PADDING 69 | }; 70 | 71 | static char *ngx_stream_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, 72 | void *child) { 73 | ngx_stream_ssl_conf_t *ssl_conf = ngx_stream_conf_get_module_srv_conf(cf, 74 | ngx_stream_ssl_module); 75 | 76 | ngx_array_t *certificates; 77 | 78 | #if nginx_version >= 1011000 79 | certificates = ssl_conf->certificates; 80 | #else 81 | certificates = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); 82 | 83 | ngx_str_t *certificate = ngx_array_push(certificates); 84 | *certificate = ssl_conf->certificate; 85 | #endif 86 | 87 | return ngx_ssl_ct_merge_srv_conf(cf, parent, child, ssl_conf->ssl.ctx, 88 | certificates); 89 | } 90 | --------------------------------------------------------------------------------