]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: ssl: Certificate Transparency support
authorJanusz Dziemidowicz <rraptorr@nails.eu.org>
Sat, 7 Mar 2015 22:03:59 +0000 (23:03 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 11 Mar 2015 22:27:05 +0000 (23:27 +0100)
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
src/ssl_sock.c

index b814de90523544aa4ab5288daabab286ee2e3fe1..3d08b4616381b9d8dd5747cc638d48ead8e98a26 100644 (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
index 69f754c808471d03bd7c6efb2a7cd4cdd4c0cc4a..bc209089079e54914106d338a7dcd3f9b9e6c45c 100644 (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);