From 2c701b5f3b1ad0c410000ddfe1930c9d656d17ae Mon Sep 17 00:00:00 2001 From: Janusz Dziemidowicz Date: Sat, 7 Mar 2015 23:03:59 +0100 Subject: [PATCH] MEDIUM: ssl: Certificate Transparency support Adds ability to include Signed Certificate Timestamp List in TLS extension. File containing SCTL must be present at the same path of the certificate file, suffixed with '.sctl'. This requires OpenSSL 1.0.2 or later. --- doc/configuration.txt | 20 ++++-- src/ssl_sock.c | 164 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 8 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index b814de905..3d08b4616 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -8735,13 +8735,13 @@ crt If a directory name is used instead of a PEM file, then all files found in that directory will be loaded in alphabetic order unless their name ends with - '.issuer' or '.ocsp' (reserved extensions). This directive may be specified - multiple times in order to load certificates from multiple files or - directories. The certificates will be presented to clients who provide a valid - TLS Server Name Indication field matching one of their CN or alt subjects. - Wildcards are supported, where a wildcard character '*' is used instead of the - first hostname component (eg: *.example.org matches www.example.org but not - www.sub.example.org). + '.issuer', '.ocsp' or '.sctl' (reserved extensions). This directive may be + specified multiple times in order to load certificates from multiple files or + directories. The certificates will be presented to clients who provide a + valid TLS Server Name Indication field matching one of their CN or alt + subjects. Wildcards are supported, where a wildcard character '*' is used + instead of the first hostname component (eg: *.example.org matches + www.example.org but not www.sub.example.org). If no SNI is provided by the client or if the SSL library does not support TLS extensions, or if the client provides an SNI hostname which does not @@ -8773,6 +8773,12 @@ crt be loaded from a file at the same path as the PEM file suffixed by ".issuer" if it exists otherwise it will fail with an error. + For each PEM file, haproxy also checks for the presence of file at the same + path suffixed by ".sctl". If such file is found, support for Certificate + Transparency (RFC6962) TLS extension is enabled. The file must contain a + valid Signed Certificate Timestamp List, as described in RFC. File is parsed + to check basic syntax, but no signatures are verified. + crt-ignore-err This setting is only available when support for OpenSSL was built in. Sets a comma separated list of errorIDs to ignore during verify at depth == 0. If diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 69f754c80..bc2090890 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -596,6 +596,141 @@ out: #endif +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + +#define CT_EXTENSION_TYPE 18 + +static int sctl_ex_index = -1; + +/* + * Try to parse Signed Certificate Timestamp List structure. This function + * makes only basic test if the data seems like SCTL. No signature validation + * is performed. + */ +static int ssl_sock_parse_sctl(struct chunk *sctl) +{ + int ret = 1; + int len, pos, sct_len; + unsigned char *data; + + if (sctl->len < 2) + goto out; + + data = (unsigned char *)sctl->str; + len = (data[0] << 8) | data[1]; + + if (len + 2 != sctl->len) + goto out; + + data = data + 2; + pos = 0; + while (pos < len) { + if (len - pos < 2) + goto out; + + sct_len = (data[pos] << 8) | data[pos + 1]; + if (pos + sct_len + 2 > len) + goto out; + + pos += sct_len + 2; + } + + ret = 0; + +out: + return ret; +} + +static int ssl_sock_load_sctl_from_file(const char *sctl_path, struct chunk **sctl) +{ + int fd = -1; + int r = 0; + int ret = 1; + + *sctl = NULL; + + fd = open(sctl_path, O_RDONLY); + if (fd == -1) + goto end; + + trash.len = 0; + while (trash.len < trash.size) { + r = read(fd, trash.str + trash.len, trash.size - trash.len); + if (r < 0) { + if (errno == EINTR) + continue; + + goto end; + } + else if (r == 0) { + break; + } + trash.len += r; + } + + ret = ssl_sock_parse_sctl(&trash); + if (ret) + goto end; + + *sctl = calloc(1, sizeof(struct chunk)); + if (!chunk_dup(*sctl, &trash)) { + free(*sctl); + *sctl = NULL; + goto end; + } + +end: + if (fd != -1) + close(fd); + + return ret; +} + +int ssl_sock_sctl_add_cbk(SSL *ssl, unsigned ext_type, const unsigned char **out, size_t *outlen, int *al, void *add_arg) +{ + struct chunk *sctl = (struct chunk *)add_arg; + + *out = (unsigned char *)sctl->str; + *outlen = sctl->len; + + return 1; +} + +int ssl_sock_sctl_parse_cbk(SSL *s, unsigned int ext_type, const unsigned char *in, size_t inlen, int *al, void *parse_arg) +{ + return 1; +} + +static int ssl_sock_load_sctl(SSL_CTX *ctx, const char *cert_path) +{ + char sctl_path[MAXPATHLEN+1]; + int ret = -1; + struct stat st; + struct chunk *sctl = NULL; + + snprintf(sctl_path, MAXPATHLEN+1, "%s.sctl", cert_path); + + if (stat(sctl_path, &st)) + return 1; + + if (ssl_sock_load_sctl_from_file(sctl_path, &sctl)) + goto out; + + if (!SSL_CTX_add_server_custom_ext(ctx, CT_EXTENSION_TYPE, ssl_sock_sctl_add_cbk, NULL, sctl, ssl_sock_sctl_parse_cbk, NULL)) { + free(sctl); + goto out; + } + + SSL_CTX_set_ex_data(ctx, sctl_ex_index, sctl); + + ret = 0; + +out: + return ret; +} + +#endif + void ssl_sock_infocbk(const SSL *ssl, int where, int ret) { struct connection *conn = (struct connection *)SSL_get_app_data(ssl); @@ -1342,6 +1477,18 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf } #endif +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + if (sctl_ex_index >= 0) { + ret = ssl_sock_load_sctl(ctx, path); + if (ret < 0) { + if (err) + memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n", + *err ? *err : "", path); + return 1; + } + } +#endif + #ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME if (bind_conf->default_ctx) { memprintf(err, "%sthis version of openssl cannot load multiple SSL certificates.\n", @@ -1383,7 +1530,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, struct proxy *cu struct dirent *de = de_list[i]; end = strrchr(de->d_name, '.'); - if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp"))) + if (end && (!strcmp(end, ".issuer") || !strcmp(end, ".ocsp") || !strcmp(end, ".sctl"))) goto ignore_entry; snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); @@ -4836,6 +4983,18 @@ struct xprt_ops ssl_sock = { .init = ssl_sock_init, }; +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + +static void ssl_sock_sctl_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) +{ + if (ptr) { + chunk_destroy(ptr); + free(ptr); + } +} + +#endif + __attribute__((constructor)) static void __ssl_sock_init(void) { @@ -4857,6 +5016,9 @@ static void __ssl_sock_init(void) SSL_library_init(); cm = SSL_COMP_get_compression_methods(); sk_SSL_COMP_zero(cm); +#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL) + sctl_ex_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_sctl_free_func); +#endif sample_register_fetches(&sample_fetch_keywords); acl_register_keywords(&acl_kws); bind_register_keywords(&bind_kws);