2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

stream-ssl: Add support for TLS SNI (Server Name Indication).

This TLS extension, introduced in RFC 3546, allows the server to know what
host the client believes it is contacting, the TLS equivalent of the Host:
header in HTTP.

Tested-by: Yifeng Sun <pkusunyifeng@gmail.com>
Reviewed-by: Yifeng Sun <pkusunyifeng@gmail.com>
Requested-by: Shivaram Mysore <smysore@servicefractal.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Ben Pfaff 2019-03-20 17:38:53 -07:00
parent f72469405e
commit b291eb69d3
5 changed files with 102 additions and 8 deletions

2
NEWS
View File

@ -31,6 +31,8 @@ Post-v2.11.0
* Added the HA chassis group support.
* Added 'external' logical port support.
- New QoS type "linux-netem" on Linux.
- Added support for TLS Server Name Indication (SNI).
v2.11.0 - 19 Feb 2019
---------------------

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
* Copyright (c) 2008-2016, 2019 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -220,9 +220,19 @@ want_to_poll_events(int want)
}
}
/* Takes ownership of 'name'. */
/* Creates a new SSL connection based on socket 'fd', as either a client or a
* server according to 'type', initially in 'state'. On success, returns 0 and
* stores the new stream in '*streamp', otherwise returns an errno value and
* doesn't bother with '*streamp'.
*
* Takes ownership of 'name', which should be the name of the connection in the
* format that would be used to connect to it, e.g. "ssl:1.2.3.4:5".
*
* For client connections, 'server_name' should be the host name of the server
* being connected to, for use with SSL SNI (server name indication). Takes
* ownership of 'server_name'. */
static int
new_ssl_stream(char *name, int fd, enum session_type type,
new_ssl_stream(char *name, char *server_name, int fd, enum session_type type,
enum ssl_state state, struct stream **streamp)
{
struct ssl_stream *sslv;
@ -274,6 +284,14 @@ new_ssl_stream(char *name, int fd, enum session_type type,
if (!verify_peer_cert || (bootstrap_ca_cert && type == CLIENT)) {
SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
}
#if OPENSSL_SUPPORTS_SNI
if (server_name && !SSL_set_tlsext_host_name(ssl, server_name)) {
VLOG_ERR("%s: failed to set server name indication (%s)",
server_name, ERR_error_string(ERR_get_error(), NULL));
retval = ENOPROTOOPT;
goto error;
}
#endif
/* Create and return the ssl_stream. */
sslv = xmalloc(sizeof *sslv);
@ -293,6 +311,7 @@ new_ssl_stream(char *name, int fd, enum session_type type,
}
*streamp = &sslv->stream;
free(server_name);
return 0;
error:
@ -301,6 +320,7 @@ error:
}
closesocket(fd);
free(name);
free(server_name);
return retval;
}
@ -311,6 +331,30 @@ ssl_stream_cast(struct stream *stream)
return CONTAINER_OF(stream, struct ssl_stream, stream);
}
/* Extracts and returns the server name from 'suffix'. The caller must
* eventually free it.
*
* Returns NULL if there is no server name, and particularly if it is an IP
* address rather than a host name, since RFC 3546 is explicit that IP
* addresses are unsuitable as server name indication (SNI). */
static char *
get_server_name(const char *suffix_)
{
char *suffix = xstrdup(suffix_);
char *host, *port;
inet_parse_host_port_tokens(suffix, &host, &port);
ovs_be32 ipv4;
struct in6_addr ipv6;
char *server_name = (ip_parse(host, &ipv4) || ipv6_parse(host, &ipv6)
? NULL : xstrdup(host));
free(suffix);
return server_name;
}
static int
ssl_open(const char *name, char *suffix, struct stream **streamp, uint8_t dscp)
{
@ -325,7 +369,8 @@ ssl_open(const char *name, char *suffix, struct stream **streamp, uint8_t dscp)
dscp);
if (fd >= 0) {
int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
return new_ssl_stream(xstrdup(name), fd, CLIENT, state, streamp);
return new_ssl_stream(xstrdup(name), get_server_name(suffix),
fd, CLIENT, state, streamp);
} else {
VLOG_ERR("%s: connect: %s", name, ovs_strerror(error));
return error;
@ -514,6 +559,14 @@ ssl_connect(struct stream *stream)
VLOG_INFO("rejecting SSL connection during bootstrap race window");
return EPROTO;
} else {
#if OPENSSL_SUPPORTS_SNI
const char *servername = SSL_get_servername(
sslv->ssl, TLSEXT_NAMETYPE_host_name);
if (servername) {
VLOG_DBG("connection indicated server name %s", servername);
}
#endif
char *cn = get_peer_common_name(sslv);
if (cn) {
@ -899,7 +952,7 @@ pssl_accept(struct pstream *pstream, struct stream **new_streamp)
ds_put_cstr(&name, "ssl:");
ss_format_address(&ss, &name);
ds_put_format(&name, ":%"PRIu16, ss_get_port(&ss));
return new_ssl_stream(ds_steal_cstr(&name), new_fd, SERVER,
return new_ssl_stream(ds_steal_cstr(&name), NULL, new_fd, SERVER,
STATE_SSL_CONNECTING, new_streamp);
}

View File

@ -1,6 +1,6 @@
# -*- autoconf -*-
# Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
# Copyright (c) 2008-2016, 2019 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -285,7 +285,24 @@ OpenFlow connections over SSL will not be supported.
AM_CONDITIONAL([HAVE_OPENSSL], [test "$HAVE_OPENSSL" = yes])
if test "$HAVE_OPENSSL" = yes; then
AC_DEFINE([HAVE_OPENSSL], [1], [Define to 1 if OpenSSL is installed.])
fi])
fi
OPENSSL_SUPPORTS_SNI=no
if test $HAVE_OPENSSL = yes; then
save_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $SSL_INCLUDES"
AC_CHECK_DECL([SSL_set_tlsext_host_name], [OPENSSL_SUPPORTS_SNI=yes],
[], [#include <openssl/ssl.h>
])
if test $OPENSSL_SUPPORTS_SNI = yes; then
AC_DEFINE(
[OPENSSL_SUPPORTS_SNI], [1],
[Define to 1 if OpenSSL supports Server Name Indication (SNI).])
fi
CPPFLAGS=$save_CPPFLAGS
fi
AC_SUBST([OPENSSL_SUPPORTS_SNI])
])
dnl Checks for libraries needed by lib/socket-util.c.
AC_DEFUN([OVS_CHECK_SOCKET_LIBS],
@ -691,7 +708,7 @@ AC_DEFUN([OVS_CHECK_CXX],
dnl Checks for unbound library.
AC_DEFUN([OVS_CHECK_UNBOUND],
[AC_CHECK_LIB(unbound, ub_ctx_create, [HAVE_UNBOUND=yes])
[AC_CHECK_LIB(unbound, ub_ctx_create, [HAVE_UNBOUND=yes], [HAVE_UNBOUND=no])
if test "$HAVE_UNBOUND" = yes; then
AC_DEFINE([HAVE_UNBOUND], [1], [Define to 1 if unbound is detected.])
LIBS="$LIBS -lunbound"

View File

@ -1,8 +1,10 @@
# -*- shell-script -*-
HAVE_OPENSSL='@HAVE_OPENSSL@'
OPENSSL_SUPPORTS_SNI='@OPENSSL_SUPPORTS_SNI@'
HAVE_PYTHON='@HAVE_PYTHON@'
HAVE_PYTHON2='@HAVE_PYTHON2@'
HAVE_PYTHON3='@HAVE_PYTHON3@'
HAVE_UNBOUND='@HAVE_UNBOUND@'
EGREP='@EGREP@'
if test x"$PYTHON" = x; then

View File

@ -1422,3 +1422,23 @@ AT_CHECK([ovs-vsctl -t 5 --no-wait --db=ssl:127.0.0.1:$SSL_PORT --private-key=$P
OVS_VSCTL_CLEANUP
AT_CLEANUP
AT_SETUP([TLS server name indication (SNI)])
AT_KEYWORDS([ovsdb server positive ssl tls sni])
AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
AT_SKIP_IF([test "$OPENSSL_SUPPORTS_SNI" = no])
AT_SKIP_IF([test "$HAVE_UNBOUND" = no])
OVSDB_INIT([conf.db])
PKIDIR=$abs_top_builddir/tests
AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --private-key=$PKIDIR/testpki-privkey2.pem --certificate=$PKIDIR/testpki-cert2.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:0:127.0.0.1 -vPATTERN:file:%m -vstream_ssl conf.db], [0], [ignore], [ignore])
PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
AT_CHECK([ovs-vsctl -t 5 --no-wait --db=ssl:localhost:$SSL_PORT --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --bootstrap-ca-cert=$PKIDIR/testpki-cacert.pem add-br br0])
AT_CAPTURE_FILE([ovsdb-server.log])
AT_CHECK([grep "server name" ovsdb-server.log], [0],
[connection indicated server name localhost
])
OVS_VSCTL_CLEANUP
AT_CLEANUP