From: Pekka Helenius Date: Sun, 02 Aug 2020 14:12:40 +0300 Subject: Implement OpenSSL support, update manual, update ChangeLog --- a/configure.ac 2020-07-31 23:00:40.000000000 +0300 +++ b/configure.ac 2020-08-02 01:23:30.696950640 +0300 @@ -59,35 +59,100 @@ AM_CONDITIONAL([HAVE_ADJFREQ], [test "x$ AM_CONDITIONAL([HAVE_CLOCK_GETRES], [test "x$ac_cv_func_clock_getres" = xyes]) AM_CONDITIONAL([HAVE_CLOCK_GETTIME], [test "x$ac_cv_func_clock_gettime" = xyes]) -# check for libtls -AC_SEARCH_LIBS([tls_config_set_ca_mem],[tls], - [LIBS="$LIBS -ltls -lssl -lcrypto"],,[-lssl -lcrypto]) -AC_CHECK_FUNCS([tls_config_set_ca_mem]) - -# check if libtls uses 3-argument tls_write -AC_CACHE_CHECK([if tls_write takes 3 arguments], ac_cv_have_tls_write_3_arg, [ - AC_LINK_IFELSE([AC_LANG_PROGRAM([[ -#include -size_t outlen; - ]], [[ tls_write(NULL, NULL, 0); ]])], - [ ac_cv_have_tls_write_3_arg="yes" ], - [ ac_cv_have_tls_write_3_arg="no" +# NOTE: hard-set AC_CHECK_HEADER or friends can't really be checked since +# libressl/openssl include header files must be determined by OS basis +# during compilation. Use the following approach instead. + +AM_CONDITIONAL(HAVE_SSL, false) +AM_CONDITIONAL([HAVE_LIBRESSL], false) +AM_CONDITIONAL([HAVE_OPENSSL], false) + +# check for libressl +AC_ARG_WITH([libressl], + AS_HELP_STRING([--without-libressl], + [Disable LibreSSL support for constraints])) + +AS_IF([test "x$with_libressl" != "xno" ], + [ have_libressl="yes" ], + [ have_libressl="no" ] +) + +if test "x$have_libressl" == "xyes"; then + + AC_SEARCH_LIBS([tls_config_set_ca_mem],[tls], + [LIBS="$LIBS -ltls -lssl -lcrypto"],,[-lssl -lcrypto]) + + AC_CHECK_FUNCS([tls_config_set_ca_mem]) + + # check if libressl uses 3-argument tls_write + AC_CACHE_CHECK( + [if LibreSSL tls_write takes 3 arguments], + ac_cv_have_libressl_write_3_arg, + [AC_LINK_IFELSE([AC_LANG_PROGRAM( + [[ #include ]], + [[ size_t outlen; ]], + [[ tls_write(NULL, NULL, 0); ]] + )], + [ ac_cv_have_libressl_write_3_arg="yes" ], + [ ac_cv_have_libressl_write_3_arg="no" ] + ) + ]) +fi + +# check for openssl +AC_ARG_WITH([openssl], + AS_HELP_STRING([--without-openssl], + [Disable OpenSSL support for constraints])) + +AS_IF([test "x$with_openssl" != "xno" ], + [ have_openssl="yes" ], + [ have_openssl="no" ] +) + +if test "x$have_openssl" == "xyes"; then + + AC_SEARCH_LIBS([X509_STORE_load_locations],[ssl], + [LIBS="$LIBS -lssl -lcrypto"],,[-lssl -lcrypto]) + + AC_CHECK_FUNCS([X509_STORE_load_locations]) + + # check if openssl uses 3-argument SSL_write + AC_CACHE_CHECK( + [if OpenSSL SSL_write takes 3 arguments], + ac_cv_have_openssl_write_3_arg, + [AC_LINK_IFELSE([AC_LANG_PROGRAM( + [[ #include ]], + [[ SSL *a; SSL_CTX *ff; ]], + [[ ff = SSL_CTX_new(TLS_method()); ]], + [[ a = SSL_new(ff); ]], + [[ SSL_write(a, NULL, 0); ]] + )], + [ ac_cv_have_openssl_write_3_arg="yes" ], + [ ac_cv_have_openssl_write_3_arg="no" ] + ) ]) -]) +fi -AC_ARG_ENABLE([https-constraint], - AS_HELP_STRING([--disable-https-constraint], - [Disable HTTPS Constraint Functionality])) - -AM_CONDITIONAL([HAVE_LIBTLS], - [test "x$ac_cv_func_tls_config_set_ca_mem" = xyes \ - -a "x$ac_cv_have_tls_write_3_arg" = xyes \ - -a "x$enable_https_constraint" != xno]) - -if test "x$ac_cv_func_tls_config_set_ca_mem" = xyes \ - -a "x$ac_cv_have_tls_write_3_arg" = xyes \ - -a "x$enable_https_constraint" != xno; then - AC_DEFINE([HAVE_LIBTLS], [yes]) +if test "x$with_libressl" != xno \ + -a "x$ac_cv_func_tls_config_set_ca_mem" = xyes \ + -a "x$ac_cv_have_libressl_write_3_arg" = xyes; then + AM_CONDITIONAL([HAVE_LIBRESSL], true) + AM_CONDITIONAL([HAVE_SSL], true) + AC_DEFINE([HAVE_LIBRESSL], [yes]) + AC_DEFINE([HAVE_SSL], [yes]) +else + AC_MSG_WARN([LibreSSL support disabled]) +fi + +if test "x$with_openssl" != xno \ + -a "x$ac_cv_func_X509_STORE_load_locations" = xyes \ + -a "x$ac_cv_have_openssl_write_3_arg" = xyes; then + AM_CONDITIONAL([HAVE_OPENSSL], true) + AM_CONDITIONAL([HAVE_SSL], true) + AC_DEFINE([HAVE_OPENSSL], [yes]) + AC_DEFINE([HAVE_SSL], [yes]) +else + AC_MSG_WARN([OpenSSL support disabled]) fi # Share test results with automake @@ -144,14 +209,6 @@ AC_ARG_WITH([privsep-path], ) AC_SUBST(PRIVSEP_PATH) -AC_ARG_WITH([cacert], - AS_HELP_STRING([--with-cacert=path], - [CA certificate location for HTTPS constraint validation]), - CONSTRAINT_CA="$withval", - CONSTRAINT_CA="/etc/ssl/cert.pem" -) -AC_DEFINE_UNQUOTED(CONSTRAINT_CA, "$CONSTRAINT_CA", [CA certificate path]) - AC_CONFIG_FILES([ Makefile include/Makefile --- a/include/tls.h 2020-07-31 23:00:40.000000000 +0300 +++ b/include/tls.h 2020-08-01 19:24:29.153594762 +0300 @@ -1,8 +1,22 @@ /* * Public domain * tls.h compatibility shim + * + * __linux__ + * __sun + * __FreeBSD__ + * __NetBSD__ + * __OpenBSD__ + * __APPLE__ */ -#ifdef HAVE_LIBTLS +#if defined(HAVE_LIBRESSL) && __linux__ +#include_next +#elif HAVE_LIBRESSL #include_next #endif + +#ifdef HAVE_OPENSSL +#include +#include +#endif --- a/src/constraint.c 2020-08-02 01:57:57.020286149 +0300 +++ b/src/constraint.c 2020-08-02 01:58:28.366952848 +0300 @@ -39,7 +39,6 @@ #include #include #include -#include #include #include @@ -65,33 +64,11 @@ void priv_constraint_close(int, int); void priv_constraint_readquery(struct constraint *, struct ntp_addr_msg *, uint8_t **); -struct httpsdate * - httpsdate_init(const char *, const int *, const char *, - const char *, const u_int8_t *, size_t); -void httpsdate_free(void *); -int httpsdate_request(struct httpsdate *, struct timeval *); -void *httpsdate_query(const char *, const int *, const char *, - const char *, const u_int8_t *, size_t, - struct timeval *, struct timeval *); - -char *tls_readline(struct tls *, size_t *, size_t *, struct timeval *); - u_int constraint_cnt; extern u_int peer_cnt; extern struct imsgbuf *ibuf; /* priv */ extern struct imsgbuf *ibuf_main; /* chld */ -struct httpsdate { - char *tls_addr; - char *tls_port; - char *tls_hostname; - char *tls_path; - char *tls_request; - struct tls_config *tls_config; - struct tls *tls_ctx; - struct tm tls_tm; -}; - int constraint_init(struct constraint *cstr) { @@ -155,7 +132,7 @@ constraint_query(struct constraint *cstr { time_t now; struct ntp_addr_msg am; - struct iovec iov[3]; + struct iovec iov[4]; int iov_cnt = 0; now = getmonotime(); @@ -252,7 +229,7 @@ priv_constraint_msg(u_int32_t id, u_int8 return; } memcpy(&am, data, sizeof(am)); - if (len != (sizeof(am) + am.namelen + am.pathlen + am.portlen)) { + if (len != (sizeof(am) + am.namelen + am.pathlen + am.portlen)) { log_warnx("constraint id %d: invalid query received", id); return; } @@ -343,13 +320,13 @@ priv_constraint_readquery(struct constra memcpy(h, &am->a, sizeof(*h)); memcpy(&port, &am->port, sizeof(port)); h->next = NULL; - + cstr->id = imsg.hdr.peerid; cstr->addr = h; cstr->addr_head.a = h; cstr->port = port; cstr->addr_head.port = port; - + dptr = imsg.data; memcpy(*data, dptr + sizeof(*am), mlen - sizeof(*am)); imsg_free(&imsg); @@ -364,20 +341,46 @@ priv_constraint_child(const char *pw_dir static char addr[NI_MAXHOST]; struct timeval rectv, xmttv; struct sigaction sa; - void *ctx; + void *ctx = NULL; struct iovec iov[2]; int i, rv; +#ifdef HAVE_OPENSSL + X509_STORE *o_store = NULL; +#endif if (setpriority(PRIO_PROCESS, 0, 0) == -1) log_warn("constraint: can't set priority for subprocess"); -#ifdef HAVE_LIBTLS - /* Init TLS and load CA certs before chroot() */ - if (tls_init() == -1) - fatalx("constraint: can't initialize LibreSSL engine"); - if ((conf->ca = tls_load_file(tls_default_ca_cert_file(), - &conf->ca_len, NULL)) == NULL) - log_warnx("constraint: failed to load CA certificate bundle file"); +/* Init TLS and load CA certs before chroot() */ +#ifdef HAVE_LIBRESSL + if (strcmp("libressl", conf->constraint_engine) == 0) { + if (tls_init() == -1) + fatalx("constraint: can't initialize LibreSSL engine"); + if (conf->constraint_ca_validation == 1) { + if ((conf->ca = tls_load_file(conf->constraint_ca, + &conf->ca_len, NULL)) == NULL) + log_warnx("constraint: failed to load CA certificate bundle file"); + } + } +#endif + +#ifdef HAVE_OPENSSL + if (strcmp("openssl", conf->constraint_engine) == 0) { + if (OPENSSL_init_ssl(0, NULL) == 0) + fatalx("constraint: can't initialize OpenSSL engine"); + //SSL_library_init(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_digests(); + SSL_load_error_strings(); + o_store = X509_STORE_new(); + + if (conf->constraint_ca_validation == 1) { + if ((conf->o_ca = X509_STORE_load_locations(o_store, conf->constraint_ca, NULL)) != 1) { + log_warnx("constraint: failed to load CA certificate bundle file"); + openssl_lasterr(); + } + } + } #endif if (chroot(pw_dir) == -1) @@ -420,7 +423,13 @@ priv_constraint_child(const char *pw_dir log_debug("constraint %s: setting HTTPS request", addr); setproctitle("constraint %s: new HTTPS request", addr); - (void)closefrom(CONSTRAINT_PASSFD + 1); + + /* + * OpenSSL requires new file descriptors which must not be deleted. + * This restriction does not apply to LibreSSL implementation. + */ + if (strcmp("libressl", conf->constraint_engine) == 0) + (void)closefrom(CONSTRAINT_PASSFD + 1); /* * Set the close-on-exec flag to prevent leaking the communication @@ -449,14 +458,32 @@ priv_constraint_child(const char *pw_dir fatalx("constraint %s: invalid port", addr); } - /* Run! */ - if ((ctx = httpsdate_query(addr, - &cstr.addr_head.port, cstr.addr_head.name, cstr.addr_head.path, - conf->ca, conf->ca_len, &rectv, &xmttv)) == NULL) { - log_debug("constraint %s: failed to get proper time results", addr); - /* Abort with failure but without warning */ - exit(1); +#ifdef HAVE_LIBRESSL + if (strcmp("libressl", conf->constraint_engine) == 0) { + /* Run! */ + log_debug("constraint %s: initializing HTTPS request", addr); + if ((ctx = httpsdate_query(addr, + &cstr.addr_head.port, cstr.addr_head.name, cstr.addr_head.path, + conf->ca, conf->ca_len, &rectv, &xmttv)) == NULL) { + log_debug("constraint %s: failed to get proper time results", addr); + /* Abort with failure but without warning */ + exit(1); + } } +#endif + +#ifdef HAVE_OPENSSL + if (strcmp("openssl", conf->constraint_engine) == 0) { + /* Run! */ + log_debug("constraint %s: initializing HTTPS request", addr); + if ((ctx = o_httpsdate_query(&cstr, + &conf->o_ca, &rectv, &xmttv)) == NULL) { + log_debug("constraint %s: failed to get proper time results", addr); + /* Abort with failure but without warning */ + exit(1); + } + } +#endif iov[0].iov_base = &rectv; iov[0].iov_len = sizeof(rectv); @@ -468,8 +495,18 @@ priv_constraint_child(const char *pw_dir rv = imsg_flush(&cstr.ibuf); } while (rv == -1 && errno == EAGAIN); - /* Tear down the TLS connection after sending the result */ - httpsdate_free(ctx); +/* Tear down the TLS connection after sending the result */ +#ifdef HAVE_LIBRESSL + if (strcmp("libressl", conf->constraint_engine) == 0) { + httpsdate_free(ctx); + } +#endif + +#ifdef HAVE_OPENSSL + if (strcmp("openssl", conf->constraint_engine) == 0) { + o_httpsdate_free(ctx); + } +#endif exit(0); } @@ -932,270 +969,6 @@ constraint_check(double val) return (0); } -struct httpsdate * -httpsdate_init(const char *addr, const int *port, const char *hostname, - const char *path, const u_int8_t *ca, size_t ca_len) -{ - struct httpsdate *httpsdate = NULL; - char port_s[sizeof(port)]; - - if ((httpsdate = calloc(1, sizeof(*httpsdate))) == NULL) - goto fail; - - if (hostname == NULL) - hostname = addr; - - sprintf(port_s, "%d", *port); - - if ((httpsdate->tls_addr = strdup(addr)) == NULL || - (httpsdate->tls_port = strdup(port_s)) == NULL || - (httpsdate->tls_hostname = strdup(hostname)) == NULL || - (httpsdate->tls_path = strdup(path)) == NULL) - goto fail; - - if (asprintf(&httpsdate->tls_request, - "HEAD %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", - httpsdate->tls_path, httpsdate->tls_hostname) == -1) - goto fail; - - if ((httpsdate->tls_config = tls_config_new()) == NULL) - goto fail; - if (tls_config_set_ca_mem(httpsdate->tls_config, ca, ca_len) == -1) - goto fail; - - /* - * Due to the fact that we're trying to determine a constraint for time - * we do our own certificate validity checking, since the automatic - * version is based on our wallclock, which may well be inaccurate... - */ - tls_config_insecure_noverifytime(httpsdate->tls_config); - - return (httpsdate); - - fail: - httpsdate_free(httpsdate); - return (NULL); -} - -void -httpsdate_free(void *arg) -{ - struct httpsdate *httpsdate = arg; - if (httpsdate == NULL) - return; - if (httpsdate->tls_ctx) - tls_close(httpsdate->tls_ctx); - tls_free(httpsdate->tls_ctx); - tls_config_free(httpsdate->tls_config); - free(httpsdate->tls_addr); - free(httpsdate->tls_port); - free(httpsdate->tls_hostname); - free(httpsdate->tls_path); - free(httpsdate->tls_request); - free(httpsdate); -} - -int -httpsdate_request(struct httpsdate *httpsdate, struct timeval *when) -{ - char timebuf1[32], timebuf2[32]; - size_t outlen = 0, maxlength = conf->constraint_maxheaderlength, len; - char *line, *p, *buf; - time_t httptime, notbefore, notafter; - struct tm *tm; - ssize_t ret; - - if ((httpsdate->tls_ctx = tls_client()) == NULL) - goto fail; - - if (tls_configure(httpsdate->tls_ctx, httpsdate->tls_config) == -1) - goto fail; - - /* - * libtls expects an address string, which can also be a DNS name, - * but we pass a pre-resolved IP address string in tls_addr so it - * does not trigger any DNS operation and is safe to be called - * without the dns pledge. - */ - log_debug("constraint %s: establishing connection", httpsdate->tls_addr); - if (tls_connect_servername(httpsdate->tls_ctx, httpsdate->tls_addr, - httpsdate->tls_port, httpsdate->tls_hostname) == -1) { - log_debug("constraint %s: TLS connection failed (%s): %s", - httpsdate->tls_addr, - httpsdate->tls_hostname, - tls_error(httpsdate->tls_ctx) - ); - goto fail; - } - - buf = httpsdate->tls_request; - len = strlen(httpsdate->tls_request); - while (len > 0) { - ret = tls_write(httpsdate->tls_ctx, buf, len); - if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) - continue; - if (ret == -1) { - log_warnx("constraint %s: TLS write operation failed (%s): %s", - httpsdate->tls_addr, - httpsdate->tls_hostname, - tls_error(httpsdate->tls_ctx) - ); - goto fail; - } - buf += ret; - len -= ret; - } - - while ((line = tls_readline(httpsdate->tls_ctx, &outlen, - &maxlength, when)) != NULL) { - line[strcspn(line, "\r\n")] = '\0'; - - if ((p = strchr(line, ' ')) == NULL || *p == '\0') - goto next; - *p++ = '\0'; - if (strcasecmp("Date:", line) != 0) - goto next; - - /* - * Expect the date/time format as IMF-fixdate which is - * mandated by HTTP/1.1 in the new RFC 7231 and was - * preferred by RFC 2616. Other formats would be RFC 850 - * or ANSI C's asctime() - the latter doesn't include - * the timezone which is required here. - */ - if (strptime(p, IMF_FIXDATE, - &httpsdate->tls_tm) == NULL) { - log_warnx("constraint %s: unsupported date format", - httpsdate->tls_addr - ); - free(line); - return (-1); - } - - free(line); - break; - next: - free(line); - } - - /* - * Now manually check the validity of the certificate presented in the - * TLS handshake, based on the time specified by the server's HTTP Date: - * header. - */ - notbefore = tls_peer_cert_notbefore(httpsdate->tls_ctx); - notafter = tls_peer_cert_notafter(httpsdate->tls_ctx); - if ((httptime = timegm(&httpsdate->tls_tm)) == -1) - goto fail; - if (httptime <= notbefore) { - if ((tm = gmtime(¬before)) == NULL) - goto fail; - if (strftime(timebuf1, sizeof(timebuf1), X509_DATE, tm) == 0) - goto fail; - if (strftime(timebuf2, sizeof(timebuf2), X509_DATE, - &httpsdate->tls_tm) == 0) - goto fail; - log_warnx("constraint %s: TLS certificate not yet valid (%s): " - "not before %s, now is %s", httpsdate->tls_addr, - httpsdate->tls_hostname, timebuf1, timebuf2); - goto fail; - } - if (httptime >= notafter) { - if ((tm = gmtime(¬after)) == NULL) - goto fail; - if (strftime(timebuf1, sizeof(timebuf1), X509_DATE, tm) == 0) - goto fail; - if (strftime(timebuf2, sizeof(timebuf2), X509_DATE, - &httpsdate->tls_tm) == 0) - goto fail; - log_warnx("constraint %s: TLS certificate has been expired (%s): " - "not after %s, now is %s", httpsdate->tls_addr, - httpsdate->tls_hostname, timebuf1, timebuf2); - goto fail; - } - - return (0); - - fail: - httpsdate_free(httpsdate); - return (-1); -} - -void * -httpsdate_query(const char *addr, const int *port, const char *hostname, - const char *path, const u_int8_t *ca, size_t ca_len, - struct timeval *rectv, struct timeval *xmttv) -{ - struct httpsdate *httpsdate; - struct timeval when; - time_t t; - - if ((httpsdate = httpsdate_init(addr, port, hostname, path, - ca, ca_len)) == NULL) - return (NULL); - - if (httpsdate_request(httpsdate, &when) == -1) - return (NULL); - - /* Return parsed date as local time */ - t = timegm(&httpsdate->tls_tm); - - /* Report parsed Date: as "received time" */ - rectv->tv_sec = t; - rectv->tv_usec = 0; - - /* And add delay as "transmit time" */ - xmttv->tv_sec = when.tv_sec; - xmttv->tv_usec = when.tv_usec; - - return (httpsdate); -} - -/* Based on SSL_readline in ftp/fetch.c */ -char * -tls_readline(struct tls *tls, size_t *lenp, size_t *maxlength, - struct timeval *when) -{ - size_t i, len; - char *buf, *q, c; - ssize_t ret; - - len = 128; - if ((buf = malloc(len)) == NULL) - fatal("constraint: can't allocate memory for TLS transfer buffer"); - for (i = 0; ; i++) { - if (i >= len - 1) { - if ((q = reallocarray(buf, len, 2)) == NULL) - fatal("constraint: can't expand TLS transfer buffer"); - buf = q; - len *= 2; - } - again: - ret = tls_read(tls, &c, 1); - if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) - goto again; - if (ret == -1) { - /* SSL read error, ignore */ - free(buf); - return (NULL); - } - - if (maxlength != NULL && (*maxlength)-- == 0) { - log_warnx("constraint: maximum HTTP header length exceeded"); - free(buf); - return (NULL); - } - - buf[i] = c; - if (c == '\n') - break; - } - *lenp = i; - if (gettimeofday(when, NULL) == -1) - fatal("constraint: can't get a valid time stamp"); - return (buf); -} - char * get_string(u_int8_t *ptr, size_t len) { --- /dev/null 2020-07-26 15:23:52.401078754 +0300 +++ b/src/constraint-openssl.c 2020-08-01 19:56:30.010263450 +0300 @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2015 Reyk Floeter + * Copyright (c) 2020 Pekka Helenius + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +struct o_httpsdate * +o_httpsdate_init(struct constraint *cstr, const int *ca) +{ + struct o_httpsdate *httpsdate = NULL; + + if ((httpsdate = calloc(1, sizeof(*httpsdate))) == NULL) + goto fail; + + if ((httpsdate->cstr = cstr) == NULL) + goto fail; + + if (asprintf(&httpsdate->tls_request, + "HEAD %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", + httpsdate->cstr->addr_head.path, + httpsdate->cstr->addr_head.name) == -1) + goto fail; + + if ((httpsdate->tls_method = TLS_method()) == NULL) + goto fai