1 /*
2 * Copyright (c) 2015-2016 Graham Edgecombe <gpe@grahamedgecombe.com>
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
ngx_ssl_ct_create_conf(ngx_cycle_t * cycle)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
ngx_ssl_ct_create_srv_conf(ngx_conf_t * cf)61 void *ngx_ssl_ct_create_srv_conf(ngx_conf_t *cf)
62 {
63 ngx_ssl_ct_srv_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(*conf));
64 if (conf == NULL)
65 {
66 return NULL;
67 }
68
69 conf->enable = NGX_CONF_UNSET;
70 conf->sct_dirs = NGX_CONF_UNSET_PTR;
71
72 return conf;
73 }
74
ngx_ssl_ct_merge_srv_conf(ngx_conf_t * cf,void * parent,void * child,SSL_CTX * ssl_ctx,ngx_array_t * certificates)75 char *ngx_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child,
76 SSL_CTX *ssl_ctx, ngx_array_t *certificates)
77 {
78 /* merge config */
79 ngx_ssl_ct_srv_conf_t *prev = parent;
80 ngx_ssl_ct_srv_conf_t *conf = child;
81
82 ngx_conf_merge_value(conf->enable, prev->enable, 0);
83 ngx_conf_merge_ptr_value(conf->sct_dirs, prev->sct_dirs, NULL);
84
85 /* validate config */
86 if (conf->enable)
87 {
88 if (!conf->sct_dirs)
89 {
90 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
91 "no \"ssl_ct_static_scts\" is defined for the \"ssl_ct\""
92 "directive");
93 return NGX_CONF_ERROR;
94 }
95 }
96 else
97 {
98 return NGX_CONF_OK;
99 }
100
101 /* check if SSL is enabled */
102 if (!ssl_ctx)
103 {
104 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
105 "\"ssl_ct\" can only be enabled if ssl is enabled");
106 return NGX_CONF_ERROR;
107 }
108
109 /* check we have one SCT dir for each certificate */
110 ngx_uint_t sct_dir_count = conf->sct_dirs->nelts;
111 if (sct_dir_count != certificates->nelts)
112 {
113 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
114 "there must be exactly one \"ssl_ct_static_scts\" directive for "
115 "each \"ssl_certificate\" directive");
116 return NGX_CONF_ERROR;
117 }
118
119 /* loop through all the certs/SCT dirs */
120 ngx_str_t *sct_dirs = conf->sct_dirs->elts;
121 X509 *cert = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_certificate_index);
122
123 ngx_uint_t i;
124 for (i = 0; i < sct_dir_count; i++)
125 {
126 /* the certificate linked list is stored in reverse order */
127 ngx_str_t *sct_dir = &sct_dirs[sct_dir_count - i - 1];
128
129 /* read the .sct files for this cert */
130 ngx_ssl_ct_ext *sct_list = ngx_ssl_ct_read_static_scts(cf, sct_dir);
131 if (!sct_list)
132 {
133 /* ngx_ssl_ct_read_static_scts calls ngx_log_error */
134 return NGX_CONF_ERROR;
135 }
136
137 if (sct_list->len == 0) {
138 ngx_pfree(cf->pool, sct_list);
139 goto next;
140 }
141
142 #ifndef OPENSSL_IS_BORINGSSL
143 /* associate the sct_list with the cert */
144 X509_set_ex_data(cert, ngx_ssl_ct_sct_list_index, sct_list);
145 #else
146 if (SSL_CTX_set_signed_cert_timestamp_list(ssl_ctx, sct_list->buf,
147 sct_list->len) == 0)
148 {
149 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
150 "SSL_CTX_set_signed_cert_timestamp_list failed");
151 ngx_pfree(cf->pool, sct_list);
152 return NGX_CONF_ERROR;
153 }
154
155 if (conf->sct_dirs->nelts > 1) {
156 ngx_log_error(NGX_LOG_WARN, cf->log, 0,
157 "BoringSSL does not support using SCTs with multiple "
158 "certificates, the last non-empty \"ssl_ct_static_scts\" "
159 "directory will be used for all certificates");
160 }
161
162 break;
163 #endif
164
165 next:
166 #if nginx_version >= 1011000
167 cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index);
168 #else
169 break;
170 #endif
171 }
172
173 #if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER)
174 /* add OpenSSL TLS extension */
175 if (SSL_CTX_add_server_custom_ext(ssl_ctx, NGX_SSL_CT_EXT,
176 &ngx_ssl_ct_ext_cb, NULL, NULL, NULL, NULL) == 0)
177 {
178 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
179 "SSL_CTX_add_server_custom_ext failed");
180 return NGX_CONF_ERROR;
181 }
182 #endif
183
184 return NGX_CONF_OK;
185 }
186
187 #if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER)
ngx_ssl_ct_ext_cb(SSL * s,unsigned int ext_type,const unsigned char ** out,size_t * outlen,int * al,void * add_arg)188 int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, const unsigned char **out,
189 size_t *outlen, int *al, void *add_arg)
190 {
191 /* get the cert OpenSSL chose to use for this connection */
192 int result = SSL_set_current_cert(s, SSL_CERT_SET_SERVER);
193 if (result == 2) {
194 /*
195 * Anonymous/PSK cipher suites don't use certificates, so don't attempt
196 * to add the SCT extension to the ServerHello.
197 */
198 return 0;
199 } else if (result != 1) {
200 ngx_connection_t *c = ngx_ssl_get_connection(s);
201 ngx_log_error(NGX_LOG_WARN, c->log, 0, "SSL_set_current_cert failed");
202 return -1;
203 }
204
205 X509 *x509 = SSL_get_certificate(s);
206 if (!x509) {
207 /* as above */
208 return 0;
209 }
210
211 /* get sct_list for the cert OpenSSL chose to use for this connection */
212 ngx_ssl_ct_ext *sct_list = X509_get_ex_data(x509,
213 ngx_ssl_ct_sct_list_index);
214
215 if (sct_list) {
216 *out = sct_list->buf;
217 *outlen = sct_list->len;
218 return 1;
219 } else {
220 return 0;
221 }
222 }
223 #endif
224
ngx_ssl_ct_read_static_sct(ngx_conf_t * cf,ngx_str_t * dir,u_char * file,size_t file_len,ngx_ssl_ct_ext * sct_list)225 static ngx_ssl_ct_ext *ngx_ssl_ct_read_static_sct(ngx_conf_t *cf,
226 ngx_str_t *dir, u_char *file, size_t file_len,
227 ngx_ssl_ct_ext *sct_list)
228 {
229 int ok = 0;
230
231 /* join dir and file name */
232 size_t path_len = dir->len + file_len + 2;
233 u_char *path = ngx_pcalloc(cf->pool, path_len);
234 if (path == NULL)
235 {
236 return NULL;
237 }
238
239 u_char *path_end = ngx_cpystrn(path, dir->data, dir->len + 1);
240 *path_end++ = '/';
241 ngx_cpystrn(path_end, file, file_len + 1);
242
243 /* open file */
244 ngx_fd_t fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
245 if (fd == NGX_FILE_ERROR)
246 {
247 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
248 ngx_open_file_n " \"%s\" failed", path);
249 ngx_pfree(cf->pool, path);
250 return NULL;
251 }
252
253 /* get file size */
254 ngx_file_info_t stat;
255 if (ngx_fd_info(fd, &stat) == NGX_FILE_ERROR)
256 {
257 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
258 ngx_fd_info_n " \"%s\" failed", path);
259 goto out;
260 }
261
262 const size_t sct_len = ngx_file_size(&stat);
263 if (sct_len == 0) {
264 ok = 1;
265 goto out;
266 }
267
268 const size_t len_pos = sct_list->len;
269 size_t sct_pos = len_pos + 2;
270
271 /* reserve two bytes for the length and sct_len bytes for the SCT. */
272 const size_t sct_and_len_size = sct_len + 2;
273
274 if (sct_and_len_size < sct_len ||
275 sct_list->len + sct_and_len_size < sct_list->len ||
276 sct_list->len + sct_and_len_size > NGX_SSL_CT_EXT_MAX_LEN)
277 {
278 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
279 "sct_list structure exceeds maximum length");
280 goto out;
281 }
282 sct_list->len += sct_and_len_size;
283
284 /* read the SCT from disk */
285 size_t to_read = sct_len;
286 while (to_read > 0)
287 {
288 ssize_t n = ngx_read_fd(fd, sct_list->buf + sct_pos, to_read);
289 if (n == NGX_FILE_ERROR)
290 {
291 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
292 ngx_read_fd_n " \"%s\" failed", path);
293 goto out;
294 }
295
296 to_read -= n;
297 sct_pos += n;
298 }
299
300 /* fill in the length bytes and return */
301 sct_list->buf[len_pos] = sct_len >> 8;
302 sct_list->buf[len_pos + 1] = sct_len;
303 ok = 1;
304
305 out:
306 if (ngx_close_file(fd) != NGX_OK)
307 {
308 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
309 ngx_close_file_n " \"%s\" failed", path);
310 }
311 ngx_pfree(cf->pool, path);
312
313 if (!ok) {
314 return NULL;
315 }
316 return sct_list;
317 }
318
ngx_ssl_ct_read_static_scts(ngx_conf_t * cf,ngx_str_t * path)319 ngx_ssl_ct_ext *ngx_ssl_ct_read_static_scts(ngx_conf_t *cf, ngx_str_t *path)
320 {
321 /* resolve relative paths */
322 if (ngx_conf_full_name(cf->cycle, path, 1) != NGX_OK)
323 {
324 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
325 "ngx_conf_full_name \"%V\" failed");
326 return NULL;
327 }
328
329 /* allocate sct_list structure */
330 ngx_ssl_ct_ext *sct_list = ngx_pcalloc(cf->pool, sizeof(*sct_list));
331 if (!sct_list)
332 {
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 {
343 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
344 ngx_open_dir_n " \"%V\" failed", path);
345 ngx_pfree(cf->pool, sct_list);
346 return NULL;
347 }
348
349 /* iterate through all files */
350 for (;;)
351 {
352 ngx_set_errno(NGX_ENOMOREFILES);
353
354 if (ngx_read_dir(&dir) != NGX_OK)
355 {
356 ngx_err_t err = ngx_errno;
357
358 if (err == NGX_ENOMOREFILES)
359 {
360 break;
361 }
362 else
363 {
364 ngx_log_error(NGX_LOG_EMERG, cf->log, err,
365 ngx_read_dir_n " \"%V\" failed", path);
366 ngx_pfree(cf->pool, sct_list);
367 return NULL;
368 }
369 }
370
371 /* skip dotfiles */
372 size_t file_len = ngx_de_namelen(&dir);
373 u_char *file = ngx_de_name(&dir);
374 if (file[0] == '.')
375 {
376 continue;
377 }
378
379 /* skip files unless the extension is .sct */
380 u_char *file_ext = (u_char *) ngx_strrchr(file, '.');
381 if (!file_ext || ngx_strcmp(file_ext, ".sct"))
382 {
383 continue;
384 }
385
386 /* add the .sct file to the sct_list */
387 if (!ngx_ssl_ct_read_static_sct(cf, path, file, file_len, sct_list))
388 {
389 /* ngx_ssl_ct_read_static_sct calls ngx_log_error */
390 ngx_pfree(cf->pool, sct_list);
391 return NULL;
392 }
393 }
394
395 /* close directory */
396 if (ngx_close_dir(&dir) != NGX_OK)
397 {
398 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
399 ngx_close_dir_n " \"%V\" failed", path);
400 ngx_pfree(cf->pool, sct_list);
401 return NULL;
402 }
403
404 /* fill in the length bytes and return */
405 size_t sct_list_len = sct_list->len - 2;
406 if (sct_list_len > 0) {
407 sct_list->buf[0] = sct_list_len >> 8;
408 sct_list->buf[1] = sct_list_len;
409 } else {
410 sct_list->len = 0;
411 }
412 return sct_list;
413 }
414