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