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/t1_lib.c b/ssl/t1_lib.c
index 34478ed..e3d0a9e 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2017,10 +2017,18 @@
   /* TLS 1.3 session tickets are renewed separately as part of the
    * NewSessionTicket. */
   int unused_renew;
-  if (!tls_process_ticket(ssl, out_session, &unused_renew, CBS_data(&ticket),
-                          CBS_len(&ticket), NULL, 0)) {
-    *out_alert = SSL_AD_INTERNAL_ERROR;
-    return 0;
+  switch (ssl_process_ticket(ssl, out_session, &unused_renew, CBS_data(&ticket),
+                             CBS_len(&ticket), NULL, 0)) {
+    case ssl_ticket_aead_success:
+      break;
+    case ssl_ticket_aead_ignore_ticket:
+      assert(*out_session == NULL);
+      break;
+    case ssl_ticket_aead_retry:
+    /* TODO: async tickets for TLS 1.3. */
+    case ssl_ticket_aead_error:
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return 0;
   }
 
   return 1;
@@ -3043,12 +3051,12 @@
   return 1;
 }
 
-int tls_process_ticket(SSL *ssl, SSL_SESSION **out_session,
-                       int *out_renew_ticket, const uint8_t *ticket,
-                       size_t ticket_len, const uint8_t *session_id,
-                       size_t session_id_len) {
-  int ret = 1; /* Most errors are non-fatal. */
-  SSL_CTX *ssl_ctx = ssl->session_ctx;
+static enum ssl_ticket_aead_result_t
+ssl_decrypt_ticket_with_cipher_ctx(SSL *ssl, uint8_t **out, size_t *out_len,
+                                   int *out_renew_ticket, const uint8_t *ticket,
+                                   size_t ticket_len) {
+  enum ssl_ticket_aead_result_t ret = ssl_ticket_aead_ignore_ticket;
+  const SSL_CTX *const ssl_ctx = ssl->session_ctx;
   uint8_t *plaintext = NULL;
 
   HMAC_CTX hmac_ctx;
@@ -3056,23 +3064,12 @@
   EVP_CIPHER_CTX cipher_ctx;
   EVP_CIPHER_CTX_init(&cipher_ctx);
 
-  *out_renew_ticket = 0;
-  *out_session = NULL;
-
-  if (SSL_get_options(ssl) & SSL_OP_NO_TICKET) {
-    goto done;
-  }
-
-  if (session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
-    goto done;
-  }
-
   /* Ensure there is room for the key name and the largest IV
    * |tlsext_ticket_key_cb| may try to consume. The real limit may be lower, but
    * the maximum IV length should be well under the minimum size for the
    * session material and HMAC. */
   if (ticket_len < SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH) {
-    goto done;
+    goto out;
   }
   const uint8_t *iv = ticket + SSL_TICKET_KEY_NAME_LEN;
 
@@ -3081,28 +3078,26 @@
         ssl, (uint8_t *)ticket /* name */, (uint8_t *)iv, &cipher_ctx,
         &hmac_ctx, 0 /* decrypt */);
     if (cb_ret < 0) {
-      ret = 0;
-      goto done;
-    }
-    if (cb_ret == 0) {
-      goto done;
-    }
-    if (cb_ret == 2) {
+      ret = ssl_ticket_aead_error;
+      goto out;
+    } else if (cb_ret == 0) {
+      goto out;
+    } else if (cb_ret == 2) {
       *out_renew_ticket = 1;
     }
   } else {
     /* Check the key name matches. */
     if (OPENSSL_memcmp(ticket, ssl_ctx->tlsext_tick_key_name,
                        SSL_TICKET_KEY_NAME_LEN) != 0) {
-      goto done;
+      goto out;
     }
     if (!HMAC_Init_ex(&hmac_ctx, ssl_ctx->tlsext_tick_hmac_key,
                       sizeof(ssl_ctx->tlsext_tick_hmac_key), tlsext_tick_md(),
                       NULL) ||
         !EVP_DecryptInit_ex(&cipher_ctx, EVP_aes_128_cbc(), NULL,
                             ssl_ctx->tlsext_tick_aes_key, iv)) {
-      ret = 0;
-      goto done;
+      ret = ssl_ticket_aead_error;
+      goto out;
     }
   }
   size_t iv_len = EVP_CIPHER_CTX_iv_length(&cipher_ctx);
@@ -3112,7 +3107,7 @@
   size_t mac_len = HMAC_size(&hmac_ctx);
   if (ticket_len < SSL_TICKET_KEY_NAME_LEN + iv_len + 1 + mac_len) {
     /* The ticket must be large enough for key name, IV, data, and MAC. */
-    goto done;
+    goto out;
   }
   HMAC_Update(&hmac_ctx, ticket, ticket_len - mac_len);
   HMAC_Final(&hmac_ctx, mac, NULL);
@@ -3122,7 +3117,7 @@
   mac_ok = 1;
 #endif
   if (!mac_ok) {
-    goto done;
+    goto out;
   }
 
   /* Decrypt the session data. */
@@ -3131,8 +3126,8 @@
                           mac_len;
   plaintext = OPENSSL_malloc(ciphertext_len);
   if (plaintext == NULL) {
-    ret = 0;
-    goto done;
+    ret = ssl_ticket_aead_error;
+    goto out;
   }
   size_t plaintext_len;
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
@@ -3140,24 +3135,89 @@
   plaintext_len = ciphertext_len;
 #else
   if (ciphertext_len >= INT_MAX) {
-    goto done;
+    goto out;
   }
   int len1, len2;
   if (!EVP_DecryptUpdate(&cipher_ctx, plaintext, &len1, ciphertext,
                          (int)ciphertext_len) ||
       !EVP_DecryptFinal_ex(&cipher_ctx, plaintext + len1, &len2)) {
-    ERR_clear_error(); /* Don't leave an error on the queue. */
-    goto done;
+    ERR_clear_error();
+    goto out;
   }
-  plaintext_len = (size_t)(len1 + len2);
+  plaintext_len = (size_t)(len1) + len2;
 #endif
 
+  *out = plaintext;
+  plaintext = NULL;
+  *out_len = plaintext_len;
+  ret = ssl_ticket_aead_success;
+
+out:
+  OPENSSL_free(plaintext);
+  HMAC_CTX_cleanup(&hmac_ctx);
+  EVP_CIPHER_CTX_cleanup(&cipher_ctx);
+  return ret;
+}
+
+static enum ssl_ticket_aead_result_t ssl_decrypt_ticket_with_method(
+    SSL *ssl, uint8_t **out, size_t *out_len, int *out_renew_ticket,
+    const uint8_t *ticket, size_t ticket_len) {
+  uint8_t *plaintext = OPENSSL_malloc(ticket_len);
+  if (plaintext == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return ssl_ticket_aead_error;
+  }
+
+  size_t plaintext_len;
+  const enum ssl_ticket_aead_result_t result =
+      ssl->session_ctx->ticket_aead_method->open(
+          ssl, plaintext, &plaintext_len, ticket_len, ticket, ticket_len);
+
+  if (result == ssl_ticket_aead_success) {
+    *out = plaintext;
+    plaintext = NULL;
+    *out_len = plaintext_len;
+  }
+
+  OPENSSL_free(plaintext);
+  return result;
+}
+
+enum ssl_ticket_aead_result_t ssl_process_ticket(
+    SSL *ssl, SSL_SESSION **out_session, int *out_renew_ticket,
+    const uint8_t *ticket, size_t ticket_len, const uint8_t *session_id,
+    size_t session_id_len) {
+  *out_renew_ticket = 0;
+  *out_session = NULL;
+
+  if ((SSL_get_options(ssl) & SSL_OP_NO_TICKET) ||
+      session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  uint8_t *plaintext = NULL;
+  size_t plaintext_len;
+  enum ssl_ticket_aead_result_t result;
+  if (ssl->session_ctx->ticket_aead_method != NULL) {
+    result = ssl_decrypt_ticket_with_method(
+        ssl, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
+  } else {
+    result = ssl_decrypt_ticket_with_cipher_ctx(
+        ssl, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
+  }
+
+  if (result != ssl_ticket_aead_success) {
+    return result;
+  }
+
   /* Decode the session. */
   SSL_SESSION *session =
       SSL_SESSION_from_bytes(plaintext, plaintext_len, ssl->ctx);
+  OPENSSL_free(plaintext);
+
   if (session == NULL) {
     ERR_clear_error(); /* Don't leave an error on the queue. */
-    goto done;
+    return ssl_ticket_aead_ignore_ticket;
   }
 
   /* Copy the client's session ID into the new session, to denote the ticket has
@@ -3166,12 +3226,7 @@
   session->session_id_length = session_id_len;
 
   *out_session = session;
-
-done:
-  OPENSSL_free(plaintext);
-  HMAC_CTX_cleanup(&hmac_ctx);
-  EVP_CIPHER_CTX_cleanup(&cipher_ctx);
-  return ret;
+  return ssl_ticket_aead_success;
 }
 
 int tls1_parse_peer_sigalgs(SSL_HANDSHAKE *hs, const CBS *in_sigalgs) {