Add server side support for supported_versions extension
authorMatt Caswell <matt@openssl.org>
Sat, 22 Oct 2016 23:41:11 +0000 (00:41 +0100)
committerMatt Caswell <matt@openssl.org>
Wed, 9 Nov 2016 16:03:08 +0000 (16:03 +0000)
Reviewed-by: Rich Salz <rsalz@openssl.org>
ssl/ssl_locl.h
ssl/statem/statem_clnt.c
ssl/statem/statem_lib.c
ssl/t1_lib.c
test/recipes/70-test_sslvertol.t
test/ssl-tests/17-renegotiate.conf
test/ssl-tests/17-renegotiate.conf.in
util/TLSProxy/Record.pm

index 0c7aeedfebc1a49752e62a7ebb2bc4cb96dc77e9..31e1fd52ddf577b70eba7d1f708ea62c341f8896 100644 (file)
@@ -2077,6 +2077,9 @@ __owur int dtls1_process_heartbeat(SSL *s, unsigned char *p,
                                    size_t length);
 #  endif
 
+__owur RAW_EXTENSION *tls_get_extension_by_type(RAW_EXTENSION *exts,
+                                                size_t numexts,
+                                                unsigned int type);
 __owur int tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
                                       SSL_SESSION **ret);
 __owur int tls_check_client_ems_support(SSL *s, const CLIENTHELLO_MSG *hello);
index 6a05b9dd240466b04b3c45ebe296eab7fb45c36b..b8bca0e4dcbee143a4f5ff4e42b725717104acdc 100644 (file)
@@ -779,8 +779,13 @@ int tls_construct_client_hello(SSL *s, WPACKET *pkt)
      * TLS 1.0 and renegotiating with TLS 1.2. We do this by using
      * client_version in client hello and not resetting it to
      * the negotiated version.
+     *
+     * For TLS 1.3 we always set the ClientHello version to 1.2 and rely on the
+     * supported_versions extension for the reall supported versions.
      */
-    if (!WPACKET_put_bytes_u16(pkt, s->client_version)
+    if (!WPACKET_put_bytes_u16(pkt,
+                (!SSL_IS_DTLS(s) && s->client_version >= TLS1_3_VERSION)
+                ? TLS1_2_VERSION : s->client_version)
             || !WPACKET_memcpy(pkt, s->s3->client_random, SSL3_RANDOM_SIZE)) {
         SSLerr(SSL_F_TLS_CONSTRUCT_CLIENT_HELLO, ERR_R_INTERNAL_ERROR);
         return 0;
index a3a31bc74044b67306b2af1eb25289f547b2356e..a7f2a0f5d78cb1e1d805f51c3c199ac581927676 100644 (file)
@@ -996,6 +996,7 @@ int ssl_choose_server_version(SSL *s, CLIENTHELLO_MSG *hello)
     const version_info *vent;
     const version_info *table;
     int disabled = 0;
+    RAW_EXTENSION *suppversions;
 
     s->client_version = client_version;
 
@@ -1019,6 +1020,73 @@ int ssl_choose_server_version(SSL *s, CLIENTHELLO_MSG *hello)
         break;
     }
 
+    suppversions = tls_get_extension_by_type(hello->pre_proc_exts,
+                                             hello->num_extensions,
+                                             TLSEXT_TYPE_supported_versions);
+
+    /*
+     * TODO(TLS1.3): We only look at this if our max protocol version is TLS1.3
+     * or above. Should we allow it for lower versions too?
+     */
+    if (suppversions != NULL && !SSL_IS_DTLS(s)
+            && (s->max_proto_version == 0
+                || TLS1_3_VERSION <= s->max_proto_version)) {
+        unsigned int candidate_vers = 0;
+        unsigned int best_vers = 0;
+        const SSL_METHOD *best_method = NULL;
+        PACKET versionslist;
+
+        if (!PACKET_get_length_prefixed_1(&suppversions->data, &versionslist)
+                || PACKET_remaining(&suppversions->data) != 0) {
+            /* Trailing or invalid data? */
+            return SSL_R_LENGTH_MISMATCH;
+        }
+
+        while (PACKET_get_net_2(&versionslist, &candidate_vers)) {
+            /* TODO(TLS1.3): Remove this before release */
+            if (candidate_vers == TLS1_3_VERSION_DRAFT)
+                candidate_vers = TLS1_3_VERSION;
+            if ((int)candidate_vers > s->client_version)
+                s->client_version = candidate_vers;
+            if (version_cmp(s, candidate_vers, best_vers) <= 0)
+                continue;
+            for (vent = table;
+                 vent->version != 0 && vent->version != (int)candidate_vers;
+                 ++vent);
+            if (vent->version != 0) {
+                const SSL_METHOD *method;
+
+                method = vent->smeth();
+                if (ssl_method_error(s, method) == 0) {
+                    best_vers = candidate_vers;
+                    best_method = method;
+                }
+            }
+        }
+        if (PACKET_remaining(&versionslist) != 0) {
+            /* Trailing data? */
+            return SSL_R_LENGTH_MISMATCH;
+        }
+
+        if (best_vers > 0) {
+            s->version = best_vers;
+            s->method = best_method;
+            return 0;
+        }
+        return SSL_R_UNSUPPORTED_PROTOCOL;
+    }
+
+    /*
+     * If the supported versions extension isn't present, then the highest
+     * version we can negotiate is TLSv1.2
+     */
+    if (version_cmp(s, client_version, TLS1_3_VERSION) >= 0)
+        client_version = TLS1_2_VERSION;
+
+    /*
+     * No supported versions extension, so we just use the version supplied in
+     * the ClientHello.
+     */
     for (vent = table; vent->version != 0; ++vent) {
         const SSL_METHOD *method;
 
index 515b4e33af5c698789abe19fe64d501f5ab05a87..2122726213a66e15133b9103299cc9bf920b41cc 100644 (file)
@@ -2826,8 +2826,8 @@ int ssl_parse_serverhello_tlsext(SSL *s, PACKET *pkt)
  *
  * Returns a pointer to the found RAW_EXTENSION data, or NULL if not found.
  */
-static RAW_EXTENSION *get_extension_by_type(RAW_EXTENSION *exts, size_t numexts,
-                                            unsigned int type)
+RAW_EXTENSION *tls_get_extension_by_type(RAW_EXTENSION *exts, size_t numexts,
+                                         unsigned int type)
 {
     size_t loop;
 
@@ -2885,9 +2885,9 @@ int tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
     if (s->version <= SSL3_VERSION || !tls_use_ticket(s))
         return 0;
 
-    ticketext = get_extension_by_type(hello->pre_proc_exts,
-                                      hello->num_extensions,
-                                      TLSEXT_TYPE_session_ticket);
+    ticketext = tls_get_extension_by_type(hello->pre_proc_exts,
+                                          hello->num_extensions,
+                                          TLSEXT_TYPE_session_ticket);
     if (ticketext == NULL)
         return 0;
 
@@ -2948,8 +2948,9 @@ int tls_check_client_ems_support(SSL *s, const CLIENTHELLO_MSG *hello)
     if (s->version <= SSL3_VERSION)
         return 1;
 
-    emsext = get_extension_by_type(hello->pre_proc_exts, hello->num_extensions,
-                                   TLSEXT_TYPE_extended_master_secret);
+    emsext = tls_get_extension_by_type(hello->pre_proc_exts,
+                                       hello->num_extensions,
+                                       TLSEXT_TYPE_extended_master_secret);
 
     /*
      * No extensions is a success - we have successfully discovered that the
index f8c94e91dfa1fd2174aea73d8e577b6dcb175c81..46fc9b6cc7bd91f6f8706034b4d5da0a86d321ec 100755 (executable)
@@ -34,15 +34,18 @@ my $proxy = TLSProxy::Proxy->new(
     (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
 );
 
-#Test 1: Asking for TLS1.3 should pass
-my $client_version = TLSProxy::Record::VERS_TLS_1_3;
+#Test 1: Asking for TLS1.4 should pass
+my $client_version = TLSProxy::Record::VERS_TLS_1_4;
+#We don't want the supported versions extension for this test
+$proxy->clientflags("-no_tls1_3");
 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
 plan tests => 2;
-ok(TLSProxy::Message->success(), "Version tolerance test, TLS 1.3");
+ok(TLSProxy::Message->success(), "Version tolerance test, TLS 1.4");
 
 #Test 2: Testing something below SSLv3 should fail
 $client_version = TLSProxy::Record::VERS_SSL_3_0 - 1;
 $proxy->clear();
+$proxy->clientflags("-no_tls1_3");
 $proxy->start();
 ok(TLSProxy::Message->fail(), "Version tolerance test, SSL < 3.0");
 
index c47a9567dd853d12d5df31ddf46294bf6700ff6b..fffb572a47096553b05813907d0dd04513f6a0ff 100644 (file)
@@ -18,6 +18,7 @@ client = 0-renegotiate-client-no-resume-client
 [0-renegotiate-client-no-resume-server]
 Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
 CipherString = DEFAULT
+MaxProtocol = TLSv1.2
 Options = NoResumptionOnRenegotiation
 PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
 
@@ -45,6 +46,7 @@ client = 1-renegotiate-client-resume-client
 [1-renegotiate-client-resume-server]
 Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
 CipherString = DEFAULT
+MaxProtocol = TLSv1.2
 PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
 
 [1-renegotiate-client-resume-client]
@@ -71,6 +73,7 @@ client = 2-renegotiate-server-no-resume-client
 [2-renegotiate-server-no-resume-server]
 Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
 CipherString = DEFAULT
+MaxProtocol = TLSv1.2
 Options = NoResumptionOnRenegotiation
 PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
 
@@ -98,6 +101,7 @@ client = 3-renegotiate-server-resume-client
 [3-renegotiate-server-resume-server]
 Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
 CipherString = DEFAULT
+MaxProtocol = TLSv1.2
 PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
 
 [3-renegotiate-server-resume-client]
index a0816177241fd102d89992bb3cfb222ff9f1b2d8..ab581eca726c75912c1d7f23da8e3fd2b375be70 100644 (file)
@@ -19,7 +19,8 @@ our @tests = (
     {
         name => "renegotiate-client-no-resume",
         server => {
-            "Options" => "NoResumptionOnRenegotiation"
+            "Options" => "NoResumptionOnRenegotiation",
+            "MaxProtocol" => "TLSv1.2"
         },
         client => {},
         test => {
@@ -31,7 +32,9 @@ our @tests = (
     },
     {
         name => "renegotiate-client-resume",
-        server => {},
+        server => {
+            "MaxProtocol" => "TLSv1.2"
+        },
         client => {},
         test => {
             "Method" => "TLS",
@@ -43,7 +46,8 @@ our @tests = (
     {
         name => "renegotiate-server-no-resume",
         server => {
-            "Options" => "NoResumptionOnRenegotiation"
+            "Options" => "NoResumptionOnRenegotiation",
+            "MaxProtocol" => "TLSv1.2"
         },
         client => {},
         test => {
@@ -55,7 +59,9 @@ our @tests = (
     },
     {
         name => "renegotiate-server-resume",
-        server => {},
+        server => {
+            "MaxProtocol" => "TLSv1.2"
+        },
         client => {},
         test => {
             "Method" => "TLS",
index a4e7adcf8248698472a8b7d17f955145b532d8fa..bf6de439ad18e3caf84a035418f53e3e03541d6f 100644 (file)
@@ -35,6 +35,7 @@ my %record_type = (
 );
 
 use constant {
+    VERS_TLS_1_4 => 773,
     VERS_TLS_1_3 => 772,
     VERS_TLS_1_2 => 771,
     VERS_TLS_1_1 => 770,