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.

Requested-by: Shivaram Mysore <[email protected]>
Signed-off-by: Ben Pfaff <[email protected]>
---
 NEWS               |  2 ++
 lib/stream-ssl.c   | 63 ++++++++++++++++++++++++++++++++++++++++++----
 m4/openvswitch.m4  | 23 ++++++++++++++---
 tests/atlocal.in   |  2 ++
 tests/ovs-vsctl.at | 20 +++++++++++++++
 5 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/NEWS b/NEWS
index 1e4744dbd244..0f8d02817e61 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,8 @@ Post-v2.11.0
    - OVN:
      * Select IPAM mac_prefix in a random manner if not provided by the user
    - New QoS type "linux-netem" on Linux.
+   - Added support for TLS Server Name Indication (SNI).
+
 
 v2.11.0 - 19 Feb 2019
 ---------------------
diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
index fed71801b823..81f2409965b2 100644
--- a/lib/stream-ssl.c
+++ b/lib/stream-ssl.c
@@ -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);
 }
 
diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
index 41042c98e286..b599f17d77a1 100644
--- a/m4/openvswitch.m4
+++ b/m4/openvswitch.m4
@@ -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"
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 5eff0a0aa216..f37f15430ccd 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -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
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
index 6f37c0da781a..77604c58a2bc 100644
--- a/tests/ovs-vsctl.at
+++ b/tests/ovs-vsctl.at
@@ -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
-- 
2.20.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to