Session resume broken switching contexts

When an SSL's context is swtiched from a ticket-enabled context to
a ticket-disabled context in the servername callback, no session-id
is generated, so the session can't be resumed.

If a servername callback changes the SSL_OP_NO_TICKET option, check
to see if it's changed to disable, and whether a session ticket is
expected (i.e. the client indicated ticket support and the SSL had
tickets enabled at the time), and whether we already have a previous
session (i.e. s->hit is set).

In this case, clear the ticket-expected flag, remove any ticket data
and generate a session-id in the session.

If the SSL hit (resumed) and switched to a ticket-disabled context,
assume that the resumption was via session-id, and don't bother to
update the session.

Before this fix, the updated unit-tests in 06-sni-ticket.conf would
fail test #4 (server1 = SNI, server2 = no SNI).

Reviewed-by: Rich Salz <rsalz@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
(Merged from https://github.com/openssl/openssl/pull/1529)
diff --git a/test/README.ssltest.md b/test/README.ssltest.md
index c4540b4..3b4bb56 100644
--- a/test/README.ssltest.md
+++ b/test/README.ssltest.md
@@ -81,6 +81,11 @@
   - Yes - a session ticket is expected
   - No - a session ticket is not expected
 
+* SessionIdExpected - whether or not a session id is expected
+  - Ignore - do not check for a session id (default)
+  - Yes - a session id is expected
+  - No - a session id is not expected
+
 * ResumptionExpected - whether or not resumption is expected (Resume mode only)
   - Yes - resumed handshake
   - No - full handshake (default)
diff --git a/test/handshake_helper.c b/test/handshake_helper.c
index 3d59abc..be96abe 100644
--- a/test/handshake_helper.c
+++ b/test/handshake_helper.c
@@ -1304,6 +1304,8 @@
     handshake_status_t status = HANDSHAKE_RETRY;
     const unsigned char* tick = NULL;
     size_t tick_len = 0;
+    const unsigned char* sess_id = NULL;
+    unsigned int sess_id_len = 0;
     SSL_SESSION* sess = NULL;
     const unsigned char *proto = NULL;
     /* API dictates unsigned int rather than size_t. */
@@ -1496,8 +1498,10 @@
     ret->server_protocol = SSL_version(server.ssl);
     ret->client_protocol = SSL_version(client.ssl);
     ret->servername = server_ex_data.servername;
-    if ((sess = SSL_get0_session(client.ssl)) != NULL)
+    if ((sess = SSL_get0_session(client.ssl)) != NULL) {
         SSL_SESSION_get0_ticket(sess, &tick, &tick_len);
+        sess_id = SSL_SESSION_get_id(sess, &sess_id_len);
+    }
     if (tick == NULL || tick_len == 0)
         ret->session_ticket = SSL_TEST_SESSION_TICKET_NO;
     else
@@ -1505,6 +1509,10 @@
     ret->compression = (SSL_get_current_compression(client.ssl) == NULL)
                        ? SSL_TEST_COMPRESSION_NO
                        : SSL_TEST_COMPRESSION_YES;
+    if (sess_id == NULL || sess_id_len == 0)
+        ret->session_id = SSL_TEST_SESSION_ID_NO;
+    else
+        ret->session_id = SSL_TEST_SESSION_ID_YES;
     ret->session_ticket_do_not_call = server_ex_data.session_ticket_do_not_call;
 
 #ifndef OPENSSL_NO_NEXTPROTONEG
diff --git a/test/handshake_helper.h b/test/handshake_helper.h
index 2736057..96c670e 100644
--- a/test/handshake_helper.h
+++ b/test/handshake_helper.h
@@ -62,6 +62,8 @@
     int client_sign_type;
     /* Client CA names */
     STACK_OF(X509_NAME) *client_ca_names;
+    /* Session id status */
+    ssl_session_id_t session_id;
 } HANDSHAKE_RESULT;
 
 HANDSHAKE_RESULT *HANDSHAKE_RESULT_new(void);
diff --git a/test/ssl-tests/06-sni-ticket.conf b/test/ssl-tests/06-sni-ticket.conf
index ce0f63b..a3a9c78 100644
--- a/test/ssl-tests/06-sni-ticket.conf
+++ b/test/ssl-tests/06-sni-ticket.conf
@@ -93,6 +93,7 @@
 [test-1]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = Yes
 server = 1-sni-session-ticket-server-extra
 client = 1-sni-session-ticket-client-extra
@@ -136,6 +137,7 @@
 [test-2]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = Yes
 server = 2-sni-session-ticket-server-extra
 client = 2-sni-session-ticket-client-extra
@@ -179,6 +181,7 @@
 [test-3]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = Yes
 server = 3-sni-session-ticket-server-extra
 client = 3-sni-session-ticket-client-extra
@@ -222,6 +225,7 @@
 [test-4]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 4-sni-session-ticket-server-extra
 client = 4-sni-session-ticket-client-extra
@@ -265,6 +269,7 @@
 [test-5]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 5-sni-session-ticket-server-extra
 client = 5-sni-session-ticket-client-extra
@@ -308,6 +313,7 @@
 [test-6]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 6-sni-session-ticket-server-extra
 client = 6-sni-session-ticket-client-extra
@@ -351,6 +357,7 @@
 [test-7]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 7-sni-session-ticket-server-extra
 client = 7-sni-session-ticket-client-extra
@@ -394,6 +401,7 @@
 [test-8]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 8-sni-session-ticket-server-extra
 client = 8-sni-session-ticket-client-extra
@@ -437,6 +445,7 @@
 [test-9]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 9-sni-session-ticket-server-extra
 client = 9-sni-session-ticket-client-extra
@@ -480,6 +489,7 @@
 [test-10]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 10-sni-session-ticket-server-extra
 client = 10-sni-session-ticket-client-extra
@@ -523,6 +533,7 @@
 [test-11]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 11-sni-session-ticket-server-extra
 client = 11-sni-session-ticket-client-extra
@@ -566,6 +577,7 @@
 [test-12]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 12-sni-session-ticket-server-extra
 client = 12-sni-session-ticket-client-extra
@@ -609,6 +621,7 @@
 [test-13]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 13-sni-session-ticket-server-extra
 client = 13-sni-session-ticket-client-extra
@@ -652,6 +665,7 @@
 [test-14]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 14-sni-session-ticket-server-extra
 client = 14-sni-session-ticket-client-extra
@@ -695,6 +709,7 @@
 [test-15]
 ExpectedResult = Success
 ExpectedServerName = server1
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 15-sni-session-ticket-server-extra
 client = 15-sni-session-ticket-client-extra
@@ -738,6 +753,7 @@
 [test-16]
 ExpectedResult = Success
 ExpectedServerName = server2
+SessionIdExpected = Yes
 SessionTicketExpected = No
 server = 16-sni-session-ticket-server-extra
 client = 16-sni-session-ticket-client-extra
diff --git a/test/ssl-tests/06-sni-ticket.conf.in b/test/ssl-tests/06-sni-ticket.conf.in
index 8725960..b4f4ffc 100644
--- a/test/ssl-tests/06-sni-ticket.conf.in
+++ b/test/ssl-tests/06-sni-ticket.conf.in
@@ -24,7 +24,8 @@
         foreach my $s1 ("SessionTicket", "-SessionTicket") {
             foreach my $s2 ("SessionTicket", "-SessionTicket") {
                 foreach my $n ("server1", "server2") {
-                    my $result = expected_result($c, $s1, $s2, $n);
+                    my $ticket_result = expected_result($c, $s1, $s2, $n);
+                    my $session_id_result = "Yes"; # always, even with a ticket
                     push @tests, {
                         "name" => "sni-session-ticket",
                         "client" => {
@@ -47,7 +48,8 @@
                         "test" => {
                             "ExpectedServerName" => $n,
                             "ExpectedResult" => "Success",
-                            "SessionTicketExpected" => $result,
+                            "SessionIdExpected" => $session_id_result,
+                            "SessionTicketExpected" => $ticket_result,
                         }
                     };
                 }
diff --git a/test/ssl_test.c b/test/ssl_test.c
index 44232db..dcdd867 100644
--- a/test/ssl_test.c
+++ b/test/ssl_test.c
@@ -143,6 +143,19 @@
     return 1;
 }
 
+static int check_session_id(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx)
+{
+    if (test_ctx->session_id_expected == SSL_TEST_SESSION_ID_IGNORE)
+        return 1;
+    if (!TEST_int_eq(result->session_id, test_ctx->session_id_expected)) {
+        TEST_info("Client SessionIdExpected mismatch, expected %s, got %s\n.",
+                ssl_session_id_name(test_ctx->session_id_expected),
+                ssl_session_id_name(result->session_id));
+        return 0;
+    }
+    return 1;
+}
+
 static int check_compression(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx)
 {
     if (!TEST_int_eq(result->compression, test_ctx->compression_expected))
@@ -320,6 +333,7 @@
         ret &= check_servername(result, test_ctx);
         ret &= check_session_ticket(result, test_ctx);
         ret &= check_compression(result, test_ctx);
+        ret &= check_session_id(result, test_ctx);
         ret &= (result->session_ticket_do_not_call == 0);
 #ifndef OPENSSL_NO_NEXTPROTONEG
         ret &= check_npn(result, test_ctx);
diff --git a/test/ssl_test_ctx.c b/test/ssl_test_ctx.c
index d669d0d..569aef0 100644
--- a/test/ssl_test_ctx.c
+++ b/test/ssl_test_ctx.c
@@ -293,6 +293,32 @@
 
 IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, compression_expected)
 
+/* SessionIdExpected */
+
+static const test_enum ssl_session_id[] = {
+    {"Ignore", SSL_TEST_SESSION_ID_IGNORE},
+    {"Yes", SSL_TEST_SESSION_ID_YES},
+    {"No", SSL_TEST_SESSION_ID_NO},
+};
+
+__owur static int parse_session_id(SSL_TEST_CTX *test_ctx, const char *value)
+{
+    int ret_value;
+    if (!parse_enum(ssl_session_id, OSSL_NELEM(ssl_session_id),
+                    &ret_value, value)) {
+        return 0;
+    }
+    test_ctx->session_id_expected = ret_value;
+    return 1;
+}
+
+const char *ssl_session_id_name(ssl_session_id_t server)
+{
+    return enum_name(ssl_session_id,
+                     OSSL_NELEM(ssl_session_id),
+                     server);
+}
+
 /* Method */
 
 static const test_enum ssl_test_methods[] = {
@@ -577,6 +603,7 @@
     { "ExpectedServerName", &parse_expected_servername },
     { "SessionTicketExpected", &parse_session_ticket },
     { "CompressionExpected", &parse_test_compression_expected },
+    { "SessionIdExpected", &parse_session_id },
     { "Method", &parse_test_method },
     { "ExpectedNPNProtocol", &parse_test_expected_npn_protocol },
     { "ExpectedALPNProtocol", &parse_test_expected_alpn_protocol },
diff --git a/test/ssl_test_ctx.h b/test/ssl_test_ctx.h
index 5eff75c..fea6527 100644
--- a/test/ssl_test_ctx.h
+++ b/test/ssl_test_ctx.h
@@ -57,6 +57,12 @@
 } ssl_compression_t;
 
 typedef enum {
+    SSL_TEST_SESSION_ID_IGNORE = 0, /* Default */
+    SSL_TEST_SESSION_ID_YES,
+    SSL_TEST_SESSION_ID_NO
+} ssl_session_id_t;
+
+typedef enum {
     SSL_TEST_METHOD_TLS = 0, /* Default */
     SSL_TEST_METHOD_DTLS
 } ssl_test_method_t;
@@ -200,6 +206,8 @@
     STACK_OF(X509_NAME) *expected_client_ca_names;
     /* Whether to use SCTP for the transport */
     int use_sctp;
+    /* Whether to expect a session id from the server */
+    ssl_session_id_t session_id_expected;
 } SSL_TEST_CTX;
 
 const char *ssl_test_result_name(ssl_test_result_t result);
@@ -210,6 +218,7 @@
 const char *ssl_servername_callback_name(ssl_servername_callback_t
                                          servername_callback);
 const char *ssl_session_ticket_name(ssl_session_ticket_t server);
+const char *ssl_session_id_name(ssl_session_id_t server);
 const char *ssl_test_method_name(ssl_test_method_t method);
 const char *ssl_handshake_mode_name(ssl_handshake_mode_t mode);
 const char *ssl_ct_validation_name(ssl_ct_validation_t mode);
diff --git a/test/ssl_test_ctx_test.c b/test/ssl_test_ctx_test.c
index 194919d..33a1842 100644
--- a/test/ssl_test_ctx_test.c
+++ b/test/ssl_test_ctx_test.c
@@ -92,7 +92,9 @@
             || !TEST_str_eq(ctx->expected_alpn_protocol,
                             ctx2->expected_alpn_protocol)
             || !TEST_int_eq(ctx->resumption_expected,
-                            ctx2->resumption_expected))
+                            ctx2->resumption_expected)
+            || !TEST_int_eq(ctx->session_id_expected,
+                            ctx2->session_id_expected))
         return 0;
     return 1;
 }
@@ -166,6 +168,7 @@
     fixture->expected_ctx->expected_servername = SSL_TEST_SERVERNAME_SERVER2;
     fixture->expected_ctx->session_ticket_expected = SSL_TEST_SESSION_TICKET_YES;
     fixture->expected_ctx->compression_expected = SSL_TEST_COMPRESSION_NO;
+    fixture->expected_ctx->session_id_expected = SSL_TEST_SESSION_ID_IGNORE;
     fixture->expected_ctx->resumption_expected = 1;
 
     fixture->expected_ctx->extra.client.verify_callback =
@@ -207,6 +210,7 @@
     "ssltest_unknown_servername_callback",
     "ssltest_unknown_session_ticket_expected",
     "ssltest_unknown_compression_expected",
+    "ssltest_unknown_session_id_expected",
     "ssltest_unknown_method",
     "ssltest_unknown_handshake_mode",
     "ssltest_unknown_resumption_expected",
diff --git a/test/ssl_test_ctx_test.conf b/test/ssl_test_ctx_test.conf
index 86d40e5..c85a4ba 100644
--- a/test/ssl_test_ctx_test.conf
+++ b/test/ssl_test_ctx_test.conf
@@ -75,6 +75,9 @@
 [ssltest_unknown_compression_expected]
 CompressionExpected = Foo
 
+[ssltest_unknown_session_id_expected]
+SessionIdExpected = Foo
+
 [ssltest_unknown_method]
 Method = TLS2