Support asynchronous ticket decryption with TLS 1.0–1.2.

This change adds support for setting an |SSL_TICKET_AEAD_METHOD| which
allows a caller to control ticket encryption and decryption to a greater
extent than previously possible and also permits asynchronous ticket
decryption.

This change only includes partial support: TLS 1.3 work remains to be
done.

Change-Id: Ia2e10ebb3257e1a119630c463b6bf389cf20ef18
Reviewed-on: https://boringssl-review.googlesource.com/14144
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index e11238f..05ae059 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -581,16 +581,11 @@
   return 0;
 }
 
-int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
+static int ssl_encrypt_ticket_with_cipher_ctx(SSL *ssl, CBB *out,
+                                              const uint8_t *session_buf,
+                                              size_t session_len) {
   int ret = 0;
 
-  /* Serialize the SSL_SESSION to be encoded into the ticket. */
-  uint8_t *session_buf = NULL;
-  size_t session_len;
-  if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) {
-    return -1;
-  }
-
   EVP_CIPHER_CTX ctx;
   EVP_CIPHER_CTX_init(&ctx);
   HMAC_CTX hctx;
@@ -667,12 +662,60 @@
   ret = 1;
 
 err:
-  OPENSSL_free(session_buf);
   EVP_CIPHER_CTX_cleanup(&ctx);
   HMAC_CTX_cleanup(&hctx);
   return ret;
 }
 
+static int ssl_encrypt_ticket_with_method(SSL *ssl, CBB *out,
+                                          const uint8_t *session_buf,
+                                          size_t session_len) {
+  const SSL_TICKET_AEAD_METHOD *method = ssl->session_ctx->ticket_aead_method;
+  const size_t max_overhead = method->max_overhead(ssl);
+  const size_t max_out = session_len + max_overhead;
+  if (max_out < max_overhead) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  uint8_t *ptr;
+  if (!CBB_reserve(out, &ptr, max_out)) {
+    return 0;
+  }
+
+  size_t out_len;
+  if (!method->seal(ssl, ptr, &out_len, max_out, session_buf, session_len)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_TICKET_ENCRYPTION_FAILED);
+    return 0;
+  }
+
+  if (!CBB_did_write(out, out_len)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
+  /* Serialize the SSL_SESSION to be encoded into the ticket. */
+  uint8_t *session_buf = NULL;
+  size_t session_len;
+  if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) {
+    return -1;
+  }
+
+  int ret = 0;
+  if (ssl->session_ctx->ticket_aead_method) {
+    ret = ssl_encrypt_ticket_with_method(ssl, out, session_buf, session_len);
+  } else {
+    ret =
+        ssl_encrypt_ticket_with_cipher_ctx(ssl, out, session_buf, session_len);
+  }
+
+  OPENSSL_free(session_buf);
+  return ret;
+}
+
 int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session) {
   if (session == NULL) {
     return 0;
@@ -811,10 +854,18 @@
       SSL_early_callback_ctx_extension_get(
           client_hello, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len);
   if (tickets_supported && ticket_len > 0) {
-    if (!tls_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
-                            client_hello->session_id,
-                            client_hello->session_id_len)) {
-      return ssl_session_error;
+    switch (ssl_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
+                               client_hello->session_id,
+                               client_hello->session_id_len)) {
+      case ssl_ticket_aead_success:
+        break;
+      case ssl_ticket_aead_ignore_ticket:
+        assert(session == NULL);
+        break;
+      case ssl_ticket_aead_error:
+        return ssl_session_error;
+      case ssl_ticket_aead_retry:
+        return ssl_session_ticket_retry;
     }
   } else {
     /* The client didn't send a ticket, so the session ID is a real ID. */