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.
This commit is contained in:
Janusz Dziemidowicz 2015-03-07 23:03:59 +01:00 committed by Willy Tarreau
parent 2468d4e4f7
commit 2c701b5f3b
2 changed files with 176 additions and 8 deletions

View File

@ -8735,13 +8735,13 @@ crt <cert>
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 <cert>
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 <errors>
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

View File

@ -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);