diff options
Diffstat (limited to 'tcpd')
| -rw-r--r-- | tcpd/configure.ac | 10 | ||||
| -rw-r--r-- | tcpd/libcouriergnutls.c | 184 | ||||
| -rw-r--r-- | tcpd/libcouriertls.c | 228 | 
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); | 
