Add a test for the ticket callback.

Change-Id: I7b2a4f617bd8d49c86fdaaf45bf67e0170bbd44f
Reviewed-on: https://boringssl-review.googlesource.com/5230
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index e8575a5..f324d39 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -38,7 +38,10 @@
 #include <openssl/bio.h>
 #include <openssl/buf.h>
 #include <openssl/bytestring.h>
+#include <openssl/cipher.h>
 #include <openssl/err.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
 #include <openssl/ssl.h>
 
 #include <memory>
@@ -443,6 +446,30 @@
   return 1;
 }
 
+static int TicketKeyCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
+                             EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
+                             int encrypt) {
+  // This is just test code, so use the all-zeros key.
+  static const uint8_t kZeros[16] = {0};
+
+  if (encrypt) {
+    memcpy(key_name, kZeros, sizeof(kZeros));
+    RAND_bytes(iv, 16);
+  } else if (memcmp(key_name, kZeros, 16) != 0) {
+    return 0;
+  }
+
+  if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
+      !EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
+    return -1;
+  }
+
+  if (!encrypt) {
+    return GetConfigPtr(ssl)->renew_ticket ? 2 : 1;
+  }
+  return 1;
+}
+
 // Connect returns a new socket connected to localhost on |port| or -1 on
 // error.
 static int Connect(uint16_t port) {
@@ -548,6 +575,10 @@
   SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback);
   SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
 
+  if (config->use_ticket_callback) {
+    SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback);
+  }
+
   return ssl_ctx;
 }
 
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 928c2b2..c7ccf80 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -720,6 +720,10 @@
 	// EmptyCertificateList, if true, causes the server to send an empty
 	// certificate list in the Certificate message.
 	EmptyCertificateList bool
+
+	// ExpectNewTicket, if true, causes the client to abort if it does not
+	// receive a new ticket.
+	ExpectNewTicket bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index a950313..00bff0e 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -786,6 +786,9 @@
 	}
 
 	if !hs.serverHello.ticketSupported {
+		if c.config.Bugs.ExpectNewTicket {
+			return errors.New("tls: expected new ticket")
+		}
 		if hs.session == nil && len(hs.serverHello.sessionId) > 0 {
 			session.sessionId = hs.serverHello.sessionId
 			hs.session = session
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 3506e05..5c66741 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2841,6 +2841,24 @@
 		resumeSession:        true,
 		expectResumeRejected: true,
 	})
+	// Test the ticket callback, with and without renewal.
+	testCases = append(testCases, testCase{
+		testType:      serverTest,
+		name:          "TicketCallback",
+		resumeSession: true,
+		flags:         []string{"-use-ticket-callback"},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TicketCallback-Renew",
+		config: Config{
+			Bugs: ProtocolBugs{
+				ExpectNewTicket: true,
+			},
+		},
+		flags:         []string{"-use-ticket-callback", "-renew-ticket"},
+		resumeSession: true,
+	})
 	// Resume with an oversized session id.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 00dec32..b4da5ce 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -85,6 +85,8 @@
   { "-use-async-private-key", &TestConfig::use_async_private_key },
   { "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal },
   { "-expect-no-session", &TestConfig::expect_no_session },
+  { "-use-ticket-callback", &TestConfig::use_ticket_callback },
+  { "-renew-ticket", &TestConfig::renew_ticket },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index e7230a7..6e0b2e4 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -82,6 +82,8 @@
   bool use_async_private_key = false;
   bool expect_ticket_renewal = false;
   bool expect_no_session = false;
+  bool use_ticket_callback = false;
+  bool renew_ticket = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);