summaryrefslogtreecommitdiffstats
path: root/tcpd
diff options
context:
space:
mode:
authorSam Varshavchik2021-02-20 21:07:50 -0500
committerSam Varshavchik2021-02-20 21:07:50 -0500
commit7e9140305a0652e86d18e8dcf71fc07748ce8e4d (patch)
tree6b31f077fc1f3dc8a557c841cedffc6c917e509f /tcpd
parent4f8680c108d2ca32611dd60345cb0553a2ebd82e (diff)
downloadcourier-libs-7e9140305a0652e86d18e8dcf71fc07748ce8e4d.tar.bz2
Add support for ALPN.
Diffstat (limited to 'tcpd')
-rw-r--r--tcpd/configure.ac10
-rw-r--r--tcpd/libcouriergnutls.c184
-rw-r--r--tcpd/libcouriertls.c228
3 files changed, 421 insertions, 1 deletions
diff --git a/tcpd/configure.ac b/tcpd/configure.ac
index 0aa6f8a..c3bb9cb 100644
--- a/tcpd/configure.ac
+++ b/tcpd/configure.ac
@@ -318,6 +318,11 @@ if test "$have_ssl" = "yes"
then
LIBCOURIERTLSOPENSSL=libcouriertlsopenssl.la
+ AC_CHECK_LIB(ssl, SSL_CTX_set_alpn_protos, [
+ AC_DEFINE_UNQUOTED(HAVE_OPENSSL_ALPN, 1,
+ [ Whether OpenSSL supports ALPN ])
+ ])
+
if test "$KRB5CONFIG" != "krb5-config"
then
AC_MSG_CHECKING(whether OpenSSL requires Kerberos)
@@ -394,6 +399,11 @@ then
gnutlsdep="`$PKGCONFIG --libs gnutls`"
AC_DEFINE([HAVE_GNUTLS3], [1], [Use GnuTLS3])
fi
+
+ AC_CHECK_LIB(gnutls, gnutls_alpn_set_protocols, [
+ AC_DEFINE_UNQUOTED(HAVE_GNUTLS_ALPN, 1,
+ [ Whether OpenSSL supports ALPN ])
+ ])
fi
AC_CHECK_SIZEOF(gnutls_transport_ptr_t,0, [
diff --git a/tcpd/libcouriergnutls.c b/tcpd/libcouriergnutls.c
index 1b4bfe2..18ea01c 100644
--- a/tcpd/libcouriergnutls.c
+++ b/tcpd/libcouriergnutls.c
@@ -161,6 +161,23 @@ static void nonsslerror(struct tls_info *info, const char *pfix)
(*info->tls_err_msg)(errmsg, info->app_data);
}
+#if HAVE_GNUTLS_ALPN
+
+static void sslerror(ssl_context ctx, int errcode, const char *pfix)
+{
+ char errmsg[512];
+ const char *err;
+
+ strcat(strcpy(errmsg, pfix), ": ");
+
+ err=gnutls_strerror(errcode);
+
+ strncat(errmsg, err, sizeof(errmsg)-1-strlen(errmsg));
+ errmsg[sizeof(errmsg)-1]=0;
+ (*ctx->info_cpy.tls_err_msg)(errmsg, ctx->info_cpy.app_data);
+}
+#endif
+
static const char *safe_getenv(ssl_context context, const char *n,
const char *def)
{
@@ -722,6 +739,154 @@ static int verify_client(ssl_handle ssl, int fd)
return 0;
}
+#if HAVE_GNUTLS_ALPN
+/*
+** Parse the TLS_ALPN setting, repeatedly invoking a callback with:
+** 1) size of the protocol string
+** 2) the protocol string
+** 3) pass-through parameter.
+*/
+
+static gnutls_alpn_flags_t alpn_parse(const char *alpn,
+ void (*cb)(unsigned char l,
+ const char *p,
+ void *arg),
+ void *arg)
+{
+ gnutls_alpn_flags_t flags=GNUTLS_ALPN_MANDATORY;
+
+ while (alpn && *alpn)
+ {
+ const char *p;
+ unsigned char l;
+
+ /* Commas and spaces are delimiters here */
+
+ if (*alpn==',' || isspace(*alpn))
+ {
+ ++alpn;
+ continue;
+ }
+
+ /* Look for the next comma, spaces, or end of string */
+ p=alpn;
+
+ while (*alpn && *alpn != ',' && !isspace(*alpn))
+ ++alpn;
+
+ /*
+ ** We now have the character count and the string.
+ ** Defend against a bad setting by checking for overflow.
+ */
+ l=alpn - p;
+
+ if (l != alpn - p)
+ continue;
+
+ if (*p != '@')
+ (*cb)(l, p, arg);
+ else
+ {
+ if (l > 1)
+ switch (p[1]) {
+ case 'o':
+ case 'O':
+ flags &= GNUTLS_ALPN_MANDATORY;
+ break;
+ case 's':
+ case 'S':
+ flags |= GNUTLS_ALPN_SERVER_PRECEDENCE;
+ break;
+ }
+ }
+ }
+
+ return flags;
+}
+
+static void alpn_count(unsigned char l,
+ const char *p,
+ void *arg)
+{
+ ++*(unsigned *)arg;
+}
+
+static void alpn_save(unsigned char l,
+ const char *name,
+ void *arg)
+{
+ gnutls_datum_t **p=(gnutls_datum_t **)arg;
+
+ (*p)->data=(unsigned char *)name;
+ (*p)->size=l;
+ ++*p;
+}
+
+static int alpn_set(ssl_context ctx,
+ gnutls_session_t session)
+{
+ const char *p=safe_getenv(ctx, "TLS_ALPN", "");
+ unsigned n=0;
+ gnutls_datum_t *datums;
+ gnutls_datum_t *datum_ptr;
+ gnutls_alpn_flags_t flags;
+ int ret;
+
+ alpn_parse(p, alpn_count, &n);
+
+ if (n == 0)
+ return GNUTLS_E_SUCCESS;
+
+ if ((datums=(gnutls_datum_t *)malloc(n * sizeof(gnutls_datum_t)))
+ == NULL)
+ {
+ return GNUTLS_E_SUCCESS;
+ }
+
+ datum_ptr=datums;
+
+ flags=alpn_parse(p, alpn_save, &datum_ptr);
+
+ ret=gnutls_alpn_set_protocols(session, datums, n, flags);
+ free((char *)datums);
+
+ return ret;
+}
+
+#if 0
+static int verify_alpn(ssl_handle ssl)
+{
+ const char *p=safe_getenv(ssl->ctx, "TLS_ALPN", "");
+ unsigned n=0;
+
+ gnutls_datum_t protocol;
+ int rc;
+
+ alpn_parse(p, alpn_count, &n);
+
+ rc=gnutls_alpn_get_selected_protocol(ssl->session, &protocol);
+ if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ {
+ fprintf(stderr, "Not negotiated\n");
+
+ return 0;
+ }
+ if (rc != GNUTLS_E_SUCCESS)
+ {
+ fprintf(stderr, "ERROR: %d %s\n",
+ rc, gnutls_strerror(rc));
+ return 0;
+ }
+
+ fprintf(stderr, "Negotiated ");
+ fwrite(protocol.data, protocol.size, 1, stderr);
+ fprintf(stderr, "\n");
+
+ return 0;
+}
+#endif
+#endif
+
static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w)
{
int rc;
@@ -734,10 +899,13 @@ static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w)
{
ssl->info_cpy.connect_interrupted=0;
-
if (verify_client(ssl, fd))
return -1;
+#if 0
+ if (verify_alpn(ssl))
+ return -1;
+#endif
if (ssl->info_cpy.connect_callback != NULL &&
!(*ssl->info_cpy.connect_callback)(ssl,
ssl->info_cpy.app_data))
@@ -1537,6 +1705,20 @@ RT |
return NULL;
}
+#if HAVE_GNUTLS_ALPN
+ {
+ int result_rc;
+
+ result_rc=alpn_set(ctx, ssl->session);
+
+ if (result_rc != GNUTLS_E_SUCCESS)
+ {
+ sslerror(ctx, result_rc, "gnutls_alpn_set_protocols");
+ tls_free_session(ssl);
+ return (NULL);
+ }
+ }
+#endif
if (ssl->dhparams_initialized)
{
gnutls_certificate_set_dh_params(ssl->xcred, ssl->dhparams);
diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c
index 874a0d1..39c7d49 100644
--- a/tcpd/libcouriertls.c
+++ b/tcpd/libcouriertls.c
@@ -732,6 +732,203 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg)
return SSL_TLSEXT_ERR_OK;
}
+#if HAVE_OPENSSL_ALPN
+
+/*
+** Parse the TLS_ALPN setting, repeatedly invoking a callback with:
+** 1) size of the protocol string
+** 2) the protocol string
+** 3) pass-through parameter.
+*/
+
+static void alpn_parse(const char *alpn,
+ void (*cb)(unsigned char l,
+ const char *p,
+ void *arg),
+ void *arg)
+{
+ while (alpn && *alpn)
+ {
+ const char *p;
+ unsigned char l;
+
+ /* Commas and spaces are delimiters here */
+
+ if (*alpn==',' || isspace(*alpn))
+ {
+ ++alpn;
+ continue;
+ }
+
+ /* Look for the next comma, spaces, or end of string */
+ p=alpn;
+
+ while (*alpn && *alpn != ',' && !isspace(*alpn))
+ ++alpn;
+
+ /*
+ ** We now have the character count and the string.
+ ** Defend against a bad setting by checking for overflow.
+ */
+ l=alpn - p;
+
+ if (l != alpn - p)
+ continue;
+
+ if (*p != '@') /* Used in the gnutls version */
+ (*cb)(l, p, arg);
+ }
+}
+
+struct tls_avpn_to_protocol_list_info {
+ void (*cb)(const char *, unsigned int, void *);
+ void *arg;
+};
+
+/*
+** Take the result of alpn_parse and construct a character stream, in ALPN
+** protocol-list format.
+*/
+
+static void tls_avpn_to_protocol_list_cb(unsigned char l,
+ const char *p,
+ void *arg)
+{
+ struct tls_avpn_to_protocol_list_info *ptr=
+ (struct tls_avpn_to_protocol_list_info *)arg;
+
+ (*ptr->cb)((char *)&l, 1, ptr->arg);
+ (*ptr->cb)(p, l, ptr->arg);
+}
+
+static void tls_avpn_to_protocol_list(const char *alpn,
+ void (*cb)(const char *, unsigned int,
+ void *),
+ void *arg)
+{
+ struct tls_avpn_to_protocol_list_info info;
+
+ info.cb=cb;
+ info.arg=arg;
+
+ alpn_parse(alpn, tls_avpn_to_protocol_list_cb, &info);
+}
+
+/*
+** Create a discrete unsigned char buffer with the ALPN protocol-list
+** string.
+**
+** First we use tls_avpn_to_protocol_list to count the size of the string,
+** then malloc it, then create it for real-sies.
+*/
+
+struct alpn_info {
+ unsigned char *p;
+ unsigned int l;
+};
+
+static void alpn_proto_count(const char *ptr, unsigned int n,
+ void *arg)
+{
+ struct alpn_info *info=(struct alpn_info *)arg;
+
+ info->l += n;
+}
+
+static void alpn_proto_save(const char *ptr, unsigned int n,
+ void *arg)
+{
+ struct alpn_info *info=(struct alpn_info *)arg;
+
+ memcpy(info->p, ptr, n);
+ info->p+=n;
+}
+
+static void parse_tls_alpn(const struct tls_info *info,
+ struct alpn_info *alpn)
+{
+ const char *s=safe_getenv(info, "TLS_ALPN");
+ unsigned char *buffer;
+
+ alpn->p=NULL;
+ alpn->l=0;
+
+ tls_avpn_to_protocol_list(s, alpn_proto_count, alpn);
+
+ if (alpn->l==0)
+ return;
+
+ buffer=(unsigned char *)malloc(alpn->l);
+
+ if (!buffer)
+ {
+ nonsslerror(info, "malloc");
+ exit(1);
+ }
+ alpn->p=buffer;
+ tls_avpn_to_protocol_list(s, alpn_proto_save, alpn);
+ alpn->p=buffer;
+}
+
+struct tls_alpn_server_info {
+ const unsigned char *in;
+ unsigned char inl;
+ int found;
+};
+
+static void alpn_proto_search(unsigned char l,
+ const char *p,
+ void *arg)
+{
+ struct tls_alpn_server_info *info=
+ (struct tls_alpn_server_info *)arg;
+
+ if (info->inl == l &&
+ memcmp(info->in, p, l) == 0)
+ {
+ info->found=1;
+ }
+}
+
+static int tls_alpn_server_cb(SSL *ssl,
+ const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg)
+{
+ struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl);
+ const char *s=safe_getenv(info, "TLS_ALPN");
+
+ while (inlen)
+ {
+ struct tls_alpn_server_info search_info;
+
+ if (*in >= inlen || *in == 0) /* Won't assume */
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+
+ search_info.in=in+1;
+ search_info.inl=*in;
+ search_info.found=0;
+
+ alpn_parse(s, alpn_proto_search, &search_info);
+ if (search_info.found)
+ {
+ *outlen=search_info.inl;
+ *out=search_info.in;
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ inlen -= *in;
+ --inlen;
+ in += *in;
+ ++in;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+
+#endif
+
SSL_CTX *tls_create(int isserver, const struct tls_info *info)
{
return tls_create_int(isserver, info, 0);
@@ -966,10 +1163,41 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info,
if (isserver)
{
+#if HAVE_OPENSSL_ALPN
+ struct alpn_info alpn;
+
+ parse_tls_alpn(info, &alpn);
+
+ if (alpn.p)
+ {
+ free((char *)alpn.p);
+
+ SSL_CTX_set_alpn_select_cb(ctx, tls_alpn_server_cb,
+ NULL);
+ }
+#endif
SSL_CTX_set_tlsext_servername_callback(ctx, server_cert_cb);
}
else
{
+#if HAVE_OPENSSL_ALPN
+ struct alpn_info alpn;
+
+ parse_tls_alpn(info, &alpn);
+
+ if (alpn.p)
+ {
+ int ret=SSL_CTX_set_alpn_protos(ctx, alpn.p, alpn.l);
+ free((char *)alpn.p);
+
+ if (ret)
+ {
+ sslerror(info, "SSL_CTX_set_alpn_protos", -1);
+ tls_destroy(ctx);
+ return (NULL);
+ }
+ }
+#endif
SSL_CTX_set_client_cert_cb(ctx, client_cert_cb);
}
return (ctx);