Cleanup ticket processing and session lookup.

Use more sensible variable names. Also move some work between the helpers and
s3_srvr.c a little; the session lookup functions now only return a new session.
Whether to send a ticket is now an additional output to avoid the enum
explosion around renewal. The actual SSL state is not modified.

This is somewhat cleaner as s3_srvr.c may still reject a session for other
reasons, so we avoid setting ssl->session and ssl->verify_result to a session
that wouldn't be used. (They get fixed up in ssl_get_new_session, so it didn't
actually matter.)

Change-Id: Ib52fabbe993b5e2b7408395a02cdea3dee66df7b
Reviewed-on: https://boringssl-review.googlesource.com/5235
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 5259793..84ce436 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -595,6 +595,10 @@
 OPENSSL_EXPORT int SSL_CTX_set_tlsext_ticket_keys(SSL_CTX *ctx, const void *in,
                                                   size_t len);
 
+/* SSL_TICKET_KEY_NAME_LEN is the length of the key name prefix of a session
+ * ticket. */
+#define SSL_TICKET_KEY_NAME_LEN 16
+
 /* SSL_CTX_set_tlsext_ticket_key_cb sets the ticket callback to |callback| and
  * returns one. |callback| will be called when encrypting a new ticket and when
  * decrypting a ticket from the client.
@@ -1113,7 +1117,7 @@
   int (*tlsext_servername_callback)(SSL *, int *, void *);
   void *tlsext_servername_arg;
   /* RFC 4507 session ticket keys */
-  uint8_t tlsext_tick_key_name[16];
+  uint8_t tlsext_tick_key_name[SSL_TICKET_KEY_NAME_LEN];
   uint8_t tlsext_tick_hmac_key[16];
   uint8_t tlsext_tick_aes_key[16];
   /* Callback to support customisation of ticket key setting */
diff --git a/ssl/internal.h b/ssl/internal.h
index c136167..c6f7fd9 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -507,8 +507,6 @@
  * SSL_aRSA <- RSA_ENC | RSA_SIGN
  * SSL_aDSS <- DSA_SIGN */
 
-#define PENDING_SESSION -10000
-
 /* From RFC4492, used in encoding the curve type in ECParameters */
 #define EXPLICIT_PRIME_CURVE_TYPE 1
 #define EXPLICIT_CHAR2_CURVE_TYPE 2
@@ -828,7 +826,23 @@
 SESS_CERT *ssl_sess_cert_dup(const SESS_CERT *sess_cert);
 void ssl_sess_cert_free(SESS_CERT *sess_cert);
 int ssl_get_new_session(SSL *s, int session);
-int ssl_get_prev_session(SSL *s, const struct ssl_early_callback_ctx *ctx);
+
+enum ssl_session_result_t {
+  ssl_session_success,
+  ssl_session_error,
+  ssl_session_retry,
+};
+
+/* ssl_get_prev_session looks up the previous session based on |ctx|. On
+ * success, it sets |*out_session| to the session or NULL if none was found. It
+ * sets |*out_send_ticket| to whether a ticket should be sent at the end of the
+ * handshake. If the session could not be looked up synchronously, it returns
+ * |ssl_session_retry| and should be called again. Otherwise, it returns
+ * |ssl_session_error|.  */
+enum ssl_session_result_t ssl_get_prev_session(
+    SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
+    const struct ssl_early_callback_ctx *ctx);
+
 STACK_OF(SSL_CIPHER) *ssl_bytes_to_cipher_list(SSL *s, const CBS *cbs);
 int ssl_cipher_list_to_bytes(SSL *s, STACK_OF(SSL_CIPHER) *sk, uint8_t *p);
 struct ssl_cipher_preference_list_st *ssl_cipher_preference_list_dup(
@@ -1094,8 +1108,16 @@
 int ssl_prepare_serverhello_tlsext(SSL *s);
 
 #define tlsext_tick_md EVP_sha256
-int tls1_process_ticket(SSL *s, const struct ssl_early_callback_ctx *ctx,
-                        SSL_SESSION **ret);
+
+/* tls_process_ticket processes the session ticket extension. On success, it
+ * sets |*out_session| to the decrypted session or NULL if the ticket was
+ * rejected. It sets |*out_send_ticket| to whether a new ticket should be sent
+ * at the end of the handshake. It returns one on success and zero on fatal
+ * error. */
+int tls_process_ticket(SSL *ssl, SSL_SESSION **out_session,
+                       int *out_send_ticket, const uint8_t *ticket,
+                       size_t ticket_len, const uint8_t *session_id,
+                       size_t session_id_len);
 
 int tls12_get_sigandhash(SSL *ssl, uint8_t *p, const EVP_PKEY *pk,
                          const EVP_MD *md);
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 3e6903d..0a26689 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -815,6 +815,7 @@
   CBS client_hello;
   uint16_t client_version;
   CBS client_random, session_id, cipher_suites, compression_methods;
+  SSL_SESSION *session = NULL;
 
   /* We do this so that we will respond with our native type. If we are TLSv1
    * and we get SSLv3, we will respond with TLSv1, This down switching should
@@ -935,13 +936,17 @@
   }
 
   s->hit = 0;
-  int session_ret = ssl_get_prev_session(s, &early_ctx);
-  if (session_ret == PENDING_SESSION) {
-    s->rwstate = SSL_PENDING_SESSION;
-    goto err;
-  } else if (session_ret == -1) {
-    goto err;
+  int send_new_ticket = 0;
+  switch (ssl_get_prev_session(s, &session, &send_new_ticket, &early_ctx)) {
+    case ssl_session_success:
+      break;
+    case ssl_session_error:
+      goto err;
+    case ssl_session_retry:
+      s->rwstate = SSL_PENDING_SESSION;
+      goto err;
   }
+  s->tlsext_ticket_expected = send_new_ticket;
 
   /* The EMS state is needed when making the resumption decision, but
    * extensions are not normally parsed until later. This detects the EMS
@@ -956,8 +961,8 @@
                                            &ems_data, &ems_len) &&
       ems_len == 0;
 
-  if (session_ret == 1) {
-    if (s->session->extended_master_secret &&
+  if (session != NULL) {
+    if (session->extended_master_secret &&
         !have_extended_master_secret) {
       /* A ClientHello without EMS that attempts to resume a session with EMS
        * is fatal to the connection. */
@@ -970,13 +975,20 @@
     s->hit =
         /* Only resume if the session's version matches the negotiated version:
          * most clients do not accept a mismatch. */
-        s->version == s->session->ssl_version &&
+        s->version == session->ssl_version &&
         /* If the client offers the EMS extension, but the previous session
          * didn't use it, then negotiate a new session. */
-        have_extended_master_secret == s->session->extended_master_secret;
+        have_extended_master_secret == session->extended_master_secret;
   }
 
-  if (!s->hit && !ssl_get_new_session(s, 1)) {
+  if (s->hit) {
+    /* Use the new session. */
+    SSL_SESSION_free(s->session);
+    s->session = session;
+    session = NULL;
+
+    s->verify_result = s->session->verify_result;
+  } else if (!ssl_get_new_session(s, 1)) {
     goto err;
   }
 
@@ -1132,6 +1144,7 @@
 
 err:
   sk_SSL_CIPHER_free(ciphers);
+  SSL_SESSION_free(session);
   return ret;
 }
 
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 765c9d9..f5c4249 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -133,6 +133,7 @@
  * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
  * OTHERWISE. */
 
+#include <assert.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -358,120 +359,110 @@
   return 1;
 }
 
-/* ssl_get_prev attempts to find an SSL_SESSION to be used to resume this
- * connection. It is only called by servers.
- *
- *   ctx: contains the early callback context, which is the result of a
- *       shallow parse of the ClientHello.
- *
- * Returns:
- *   -1: error
- *    0: a session may have been found.
- *
- * Side effects:
- *   - If a session is found then s->session is pointed at it (after freeing an
- *     existing session if need be) and s->verify_result is set from the session.
- *   - Both for new and resumed sessions, s->tlsext_ticket_expected is set to 1
- *     if the server should issue a new session ticket (to 0 otherwise). */
-int ssl_get_prev_session(SSL *s, const struct ssl_early_callback_ctx *ctx) {
-  /* This is used only by servers. */
-  SSL_SESSION *ret = NULL;
-  int fatal = 0;
-  int try_session_cache = 1;
-  int r;
+/* ssl_lookup_session looks up |session_id| in the session cache and sets
+ * |*out_session| to an |SSL_SESSION| object if found. The caller takes
+ * ownership of the result. */
+static enum ssl_session_result_t ssl_lookup_session(
+    SSL *ssl, SSL_SESSION **out_session, const uint8_t *session_id,
+    size_t session_id_len) {
+  *out_session = NULL;
 
-  if (ctx->session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
-    goto err;
+  if (session_id_len == 0 || session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+    return ssl_session_success;
   }
 
-  if (ctx->session_id_len == 0) {
-    try_session_cache = 0;
-  }
-
-  r = tls1_process_ticket(s, ctx, &ret); /* sets s->tlsext_ticket_expected */
-  switch (r) {
-    case -1: /* Error during processing */
-      fatal = 1;
-      goto err;
-
-    case 0:  /* No ticket found */
-    case 1:  /* Zero length ticket found */
-      break; /* Ok to carry on processing session id. */
-
-    case 2:  /* Ticket found but not decrypted. */
-    case 3:  /* Ticket decrypted, *ret has been set. */
-      try_session_cache = 0;
-      break;
-
-    default:
-      abort();
-  }
-
-  if (try_session_cache && ret == NULL &&
-      !(s->initial_ctx->session_cache_mode &
+  SSL_SESSION *session;
+  /* Try the internal cache, if it exists. */
+  if (!(ssl->initial_ctx->session_cache_mode &
         SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) {
     SSL_SESSION data;
-    data.ssl_version = s->version;
-    data.session_id_length = ctx->session_id_len;
-    if (ctx->session_id_len == 0) {
-      return 0;
+    data.ssl_version = ssl->version;
+    data.session_id_length = session_id_len;
+    memcpy(data.session_id, session_id, session_id_len);
+
+    CRYPTO_MUTEX_lock_read(&ssl->initial_ctx->lock);
+    session = lh_SSL_SESSION_retrieve(ssl->initial_ctx->sessions, &data);
+    if (session != NULL) {
+      SSL_SESSION_up_ref(session);
     }
-    memcpy(data.session_id, ctx->session_id, ctx->session_id_len);
+    CRYPTO_MUTEX_unlock(&ssl->initial_ctx->lock);
 
-    CRYPTO_MUTEX_lock_read(&s->initial_ctx->lock);
-    ret = lh_SSL_SESSION_retrieve(s->initial_ctx->sessions, &data);
-    if (ret != NULL) {
-      SSL_SESSION_up_ref(ret);
-    }
-    CRYPTO_MUTEX_unlock(&s->initial_ctx->lock);
-  }
-
-  if (try_session_cache && ret == NULL &&
-      s->initial_ctx->get_session_cb != NULL) {
-    int copy = 1;
-
-    ret = s->initial_ctx->get_session_cb(s, (uint8_t *)ctx->session_id,
-                                         ctx->session_id_len, &copy);
-    if (ret != NULL) {
-      if (ret == SSL_magic_pending_session_ptr()) {
-        /* This is a magic value which indicates that the callback needs to
-         * unwind the stack and figure out the session asynchronously. */
-        return PENDING_SESSION;
-      }
-
-      /* Increment reference count now if the session callback asks us to do so
-       * (note that if the session structures returned by the callback are
-       * shared between threads, it must handle the reference count itself
-       * [i.e. copy == 0], or things won't be thread-safe). */
-      if (copy) {
-        SSL_SESSION_up_ref(ret);
-      }
-
-      /* Add the externally cached session to the internal cache as well if and
-       * only if we are supposed to. */
-      if (!(s->initial_ctx->session_cache_mode &
-            SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
-        /* The following should not return 1, otherwise, things are very
-         * strange */
-        SSL_CTX_add_session(s->initial_ctx, ret);
-      }
+    if (session != NULL) {
+      *out_session = session;
+      return ssl_session_success;
     }
   }
 
-  if (ret == NULL) {
-    goto err;
+  /* Fall back to the external cache, if it exists. */
+  if (ssl->initial_ctx->get_session_cb == NULL) {
+    return ssl_session_success;
+  }
+  int copy = 1;
+  session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id,
+                                             session_id_len, &copy);
+  if (session == NULL) {
+    return ssl_session_success;
+  }
+  if (session == SSL_magic_pending_session_ptr()) {
+    return ssl_session_retry;
   }
 
-  /* Now ret is non-NULL and we own one of its reference counts. */
-
-  if (ret->sid_ctx_length != s->sid_ctx_length ||
-      memcmp(ret->sid_ctx, s->sid_ctx, ret->sid_ctx_length)) {
-    /* We have the session requested by the client, but we don't want to use it
-     * in this context. */
-    goto err; /* treat like cache miss */
+  /* Increment reference count now if the session callback asks us to do so
+   * (note that if the session structures returned by the callback are shared
+   * between threads, it must handle the reference count itself [i.e. copy ==
+   * 0], or things won't be thread-safe). */
+  if (copy) {
+    SSL_SESSION_up_ref(session);
   }
 
-  if ((s->verify_mode & SSL_VERIFY_PEER) && s->sid_ctx_length == 0) {
+  /* Add the externally cached session to the internal cache if necessary. */
+  if (!(ssl->initial_ctx->session_cache_mode &
+        SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
+    SSL_CTX_add_session(ssl->initial_ctx, session);
+  }
+
+  *out_session = session;
+  return ssl_session_success;
+}
+
+enum ssl_session_result_t ssl_get_prev_session(
+    SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
+    const struct ssl_early_callback_ctx *ctx) {
+  /* This is used only by servers. */
+  assert(ssl->server);
+  SSL_SESSION *session = NULL;
+  int send_ticket = 0;
+
+  /* If tickets are disabled, always behave as if no tickets are present. */
+  const uint8_t *ticket;
+  size_t ticket_len;
+  const int tickets_supported =
+      !(SSL_get_options(ssl) & SSL_OP_NO_TICKET) &&
+      (ssl->version > SSL3_VERSION || ctx->extensions != NULL) &&
+      SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_session_ticket,
+                                           &ticket, &ticket_len);
+  if (tickets_supported) {
+    if (!tls_process_ticket(ssl, &session, &send_ticket, ticket, ticket_len,
+                            ctx->session_id, ctx->session_id_len)) {
+      return ssl_session_error;
+    }
+  } else {
+    /* The client does not support session tickets, so the session ID should be
+     * used instead. */
+    enum ssl_session_result_t lookup_ret = ssl_lookup_session(
+        ssl, &session, ctx->session_id, ctx->session_id_len);
+    if (lookup_ret != ssl_session_success) {
+      return lookup_ret;
+    }
+  }
+
+  if (session == NULL ||
+      session->sid_ctx_length != ssl->sid_ctx_length ||
+      memcmp(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length) != 0) {
+    goto no_session;
+  }
+
+  if ((ssl->verify_mode & SSL_VERIFY_PEER) && ssl->sid_ctx_length == 0) {
     /* We can't be sure if this session is being used out of context, which is
      * especially important for SSL_VERIFY_PEER. The application should have
      * used SSL[_CTX]_set_session_id_context.
@@ -482,37 +473,30 @@
      * noticing). */
     OPENSSL_PUT_ERROR(SSL, ssl_get_prev_session,
                       SSL_R_SESSION_ID_CONTEXT_UNINITIALIZED);
-    fatal = 1;
-    goto err;
+    goto fatal_error;
   }
 
-  if (ret->timeout < (long)(time(NULL) - ret->time)) {
-    /* timeout */
-    if (try_session_cache) {
-      /* session was from the cache, so remove it */
-      SSL_CTX_remove_session(s->initial_ctx, ret);
+  if (session->timeout < (long)(time(NULL) - session->time)) {
+    if (!tickets_supported) {
+      /* The session was from the cache, so remove it. */
+      SSL_CTX_remove_session(ssl->initial_ctx, session);
     }
-    goto err;
+    goto no_session;
   }
 
-  SSL_SESSION_free(s->session);
-  s->session = ret;
-  s->verify_result = s->session->verify_result;
-  return 1;
+  *out_session = session;
+  *out_send_ticket = send_ticket;
+  return ssl_session_success;
 
-err:
-  if (ret != NULL) {
-    SSL_SESSION_free(ret);
-    if (!try_session_cache) {
-      /* The session was from a ticket, so we should
-       * issue a ticket for the new session */
-      s->tlsext_ticket_expected = 1;
-    }
-  }
-  if (fatal) {
-    return -1;
-  }
-  return 0;
+fatal_error:
+  SSL_SESSION_free(session);
+  return ssl_session_error;
+
+no_session:
+  *out_session = NULL;
+  *out_send_ticket = tickets_supported;
+  SSL_SESSION_free(session);
+  return ssl_session_success;
 }
 
 int SSL_CTX_add_session(SSL_CTX *ctx, SSL_SESSION *c) {
@@ -528,12 +512,13 @@
   CRYPTO_MUTEX_lock_write(&ctx->lock);
   if (!lh_SSL_SESSION_insert(ctx->sessions, &s, c)) {
     CRYPTO_MUTEX_unlock(&ctx->lock);
+    SSL_SESSION_free(c);
     return 0;
   }
 
-  /* s != NULL iff we already had a session with the given PID. In this case, s
-   * == c should hold (then we did not really modify ctx->sessions), or we're
-   * in trouble. */
+  /* s != NULL iff we already had a session with the given session ID. In this
+   * case, s == c should hold (then we did not really modify ctx->sessions), or
+   * we're in trouble. */
   if (s != NULL && s != c) {
     /* We *are* in trouble ... */
     SSL_SESSION_list_remove(ctx, s);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 654cf6a..d5aa8d5 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -107,6 +107,7 @@
  * Hudson (tjh@cryptsoft.com). */
 
 #include <assert.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -122,9 +123,6 @@
 #include "internal.h"
 
 
-static int tls_decrypt_ticket(SSL *s, const uint8_t *tick, int ticklen,
-                              const uint8_t *sess_id, int sesslen,
-                              SSL_SESSION **psess);
 static int ssl_check_clienthello_tlsext(SSL *s);
 static int ssl_check_serverhello_tlsext(SSL *s);
 
@@ -2288,195 +2286,124 @@
   return 1;
 }
 
-/* Since the server cache lookup is done early on in the processing of the
- * ClientHello, and other operations depend on the result, we need to handle
- * any TLS session ticket extension at the same time.
- *
- *   ctx: contains the early callback context, which is the result of a
- *       shallow parse of the ClientHello.
- *   ret: (output) on return, if a ticket was decrypted, then this is set to
- *       point to the resulting session.
- *
- * Returns:
- *   -1: fatal error, either from parsing or decrypting the ticket.
- *    0: no ticket was found (or was ignored, based on settings).
- *    1: a zero length extension was found, indicating that the client supports
- *       session tickets but doesn't currently have one to offer.
- *    2: a ticket was offered but couldn't be decrypted because of a non-fatal
- *       error.
- *    3: a ticket was successfully decrypted and *ret was set.
- *
- * Side effects:
- *   Sets s->tlsext_ticket_expected to 1 if the server will have to issue
- *   a new session ticket to the client because the client indicated support
- *   but the client either doesn't have a session ticket or we couldn't use
- *   the one it gave us, or if s->ctx->tlsext_ticket_key_cb asked to renew
- *   the client's ticket.  Otherwise, s->tlsext_ticket_expected is set to 0.
- */
-int tls1_process_ticket(SSL *s, const struct ssl_early_callback_ctx *ctx,
-                        SSL_SESSION **ret) {
-  *ret = NULL;
-  s->tlsext_ticket_expected = 0;
-  const uint8_t *data;
-  size_t len;
-  int r;
+int tls_process_ticket(SSL *ssl, SSL_SESSION **out_session,
+                       int *out_send_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->initial_ctx;
+  uint8_t *plaintext = NULL;
 
-  /* If tickets disabled behave as if no ticket present to permit stateful
-   * resumption. */
-  if ((SSL_get_options(s) & SSL_OP_NO_TICKET) ||
-      (s->version <= SSL3_VERSION && !ctx->extensions) ||
-      !SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_session_ticket,
-                                            &data, &len)) {
-    return 0;
+  HMAC_CTX hmac_ctx;
+  HMAC_CTX_init(&hmac_ctx);
+  EVP_CIPHER_CTX cipher_ctx;
+  EVP_CIPHER_CTX_init(&cipher_ctx);
+
+  *out_send_ticket = 0;
+  *out_session = NULL;
+
+  if (session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+    goto done;
   }
 
-  if (len == 0) {
+  if (ticket_len == 0) {
     /* The client will accept a ticket but doesn't currently have one. */
-    s->tlsext_ticket_expected = 1;
-    return 1;
+    *out_send_ticket = 1;
+    goto done;
   }
 
-  r = tls_decrypt_ticket(s, data, len, ctx->session_id, ctx->session_id_len,
-                         ret);
-  switch (r) {
-    case 2: /* ticket couldn't be decrypted */
-      s->tlsext_ticket_expected = 1;
-      return 2;
-
-    case 3: /* ticket was decrypted */
-      return r;
-
-    case 4: /* ticket decrypted but need to renew */
-      s->tlsext_ticket_expected = 1;
-      return 3;
-
-    default: /* fatal error */
-      return -1;
-  }
-}
-
-/* tls_decrypt_ticket attempts to decrypt a session ticket.
- *
- *   etick: points to the body of the session ticket extension.
- *   eticklen: the length of the session tickets extenion.
- *   sess_id: points at the session ID.
- *   sesslen: the length of the session ID.
- *   psess: (output) on return, if a ticket was decrypted, then this is set to
- *       point to the resulting session.
- *
- * Returns:
- *   -1: fatal error, either from parsing or decrypting the ticket.
- *    2: the ticket couldn't be decrypted.
- *    3: a ticket was successfully decrypted and *psess was set.
- *    4: same as 3, but the ticket needs to be renewed. */
-static int tls_decrypt_ticket(SSL *s, const uint8_t *etick, int eticklen,
-                              const uint8_t *sess_id, int sesslen,
-                              SSL_SESSION **psess) {
-  SSL_SESSION *sess;
-  uint8_t *sdec;
-  const uint8_t *p;
-  int slen, mlen, renew_ticket = 0;
-  uint8_t tick_hmac[EVP_MAX_MD_SIZE];
-  HMAC_CTX hctx;
-  EVP_CIPHER_CTX ctx;
-  SSL_CTX *tctx = s->initial_ctx;
-
   /* 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 (eticklen < 16 + EVP_MAX_IV_LENGTH) {
-    return 2;
+  if (ticket_len < SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH) {
+    goto done;
   }
+  const uint8_t *iv = ticket + SSL_TICKET_KEY_NAME_LEN;
 
-  /* Initialize session ticket encryption and HMAC contexts */
-  HMAC_CTX_init(&hctx);
-  EVP_CIPHER_CTX_init(&ctx);
-  if (tctx->tlsext_ticket_key_cb) {
-    uint8_t *nctick = (uint8_t *)etick;
-    int rv = tctx->tlsext_ticket_key_cb(s, nctick, nctick + 16, &ctx, &hctx,
-                                        0 /* decrypt */);
-    if (rv < 0) {
-      return -1;
+  if (ssl_ctx->tlsext_ticket_key_cb != NULL) {
+    int cb_ret = ssl_ctx->tlsext_ticket_key_cb(ssl, (uint8_t*)ticket /* name */,
+                                               (uint8_t*)iv, &cipher_ctx, &hmac_ctx,
+                                               0 /* decrypt */);
+    if (cb_ret < 0) {
+      ret = 0;
+      goto done;
     }
-    if (rv == 0) {
-      return 2;
+    if (cb_ret == 0) {
+      goto done;
     }
-    if (rv == 2) {
-      renew_ticket = 1;
+    if (cb_ret == 2) {
+      *out_send_ticket = 1;
     }
   } else {
-    /* Check key name matches */
-    if (memcmp(etick, tctx->tlsext_tick_key_name, 16)) {
-      return 2;
+    /* Check the key name matches. */
+    if (memcmp(ticket, ssl_ctx->tlsext_tick_key_name,
+               SSL_TICKET_KEY_NAME_LEN) != 0) {
+      goto done;
     }
-    if (!HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(),
+    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(&ctx, EVP_aes_128_cbc(), NULL,
-                            tctx->tlsext_tick_aes_key, etick + 16)) {
-      HMAC_CTX_cleanup(&hctx);
-      EVP_CIPHER_CTX_cleanup(&ctx);
-      return -1;
+        !EVP_DecryptInit_ex(&cipher_ctx, EVP_aes_128_cbc(), NULL,
+                            ssl_ctx->tlsext_tick_aes_key, iv)) {
+      ret = 0;
+      goto done;
     }
   }
+  size_t iv_len = EVP_CIPHER_CTX_iv_length(&cipher_ctx);
 
-  /* First, check the MAC. The MAC is at the end of the ticket. */
-  mlen = HMAC_size(&hctx);
-  if ((size_t) eticklen < 16 + EVP_CIPHER_CTX_iv_length(&ctx) + 1 + mlen) {
+  /* Check the MAC at the end of the ticket. */
+  uint8_t mac[EVP_MAX_MD_SIZE];
+  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. */
-    HMAC_CTX_cleanup(&hctx);
-    EVP_CIPHER_CTX_cleanup(&ctx);
-    return 2;
+    goto done;
   }
-  eticklen -= mlen;
-  /* Check HMAC of encrypted ticket */
-  HMAC_Update(&hctx, etick, eticklen);
-  HMAC_Final(&hctx, tick_hmac, NULL);
-  HMAC_CTX_cleanup(&hctx);
-  if (CRYPTO_memcmp(tick_hmac, etick + eticklen, mlen)) {
-    EVP_CIPHER_CTX_cleanup(&ctx);
-    return 2;
+  HMAC_Update(&hmac_ctx, ticket, ticket_len - mac_len);
+  HMAC_Final(&hmac_ctx, mac, NULL);
+  if (CRYPTO_memcmp(mac, ticket + (ticket_len - mac_len), mac_len) != 0) {
+    goto done;
   }
 
-  /* Attempt to decrypt session data */
-  /* Move p after IV to start of encrypted ticket, update length */
-  p = etick + 16 + EVP_CIPHER_CTX_iv_length(&ctx);
-  eticklen -= 16 + EVP_CIPHER_CTX_iv_length(&ctx);
-  sdec = OPENSSL_malloc(eticklen);
-  if (!sdec) {
-    EVP_CIPHER_CTX_cleanup(&ctx);
-    return -1;
+  /* Decrypt the session data. */
+  const uint8_t *ciphertext = ticket + SSL_TICKET_KEY_NAME_LEN + iv_len;
+  size_t ciphertext_len = ticket_len - SSL_TICKET_KEY_NAME_LEN - iv_len -
+                          mac_len;
+  plaintext = OPENSSL_malloc(ciphertext_len);
+  if (plaintext == NULL) {
+    ret = 0;
+    goto done;
   }
-  EVP_DecryptUpdate(&ctx, sdec, &slen, p, eticklen);
-  if (EVP_DecryptFinal_ex(&ctx, sdec + slen, &mlen) <= 0) {
-    EVP_CIPHER_CTX_cleanup(&ctx);
-    OPENSSL_free(sdec);
-    return 2;
+  if (ciphertext_len >= INT_MAX) {
+    goto done;
   }
-  slen += mlen;
-  EVP_CIPHER_CTX_cleanup(&ctx);
-  p = sdec;
-
-  sess = SSL_SESSION_from_bytes(sdec, slen);
-  OPENSSL_free(sdec);
-  if (sess) {
-    /* The session ID, if non-empty, is used by some clients to detect that the
-     * ticket has been accepted. So we copy it to the session structure. If it
-     * is empty set length to zero as required by standard. */
-    if (sesslen) {
-      memcpy(sess->session_id, sess_id, sesslen);
-    }
-    sess->session_id_length = sesslen;
-    *psess = sess;
-    if (renew_ticket) {
-      return 4;
-    }
-    return 3;
+  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();
-  /* For session parse failure, indicate that we need to send a new ticket. */
-  return 2;
+  /* Decode the session. */
+  SSL_SESSION *session = SSL_SESSION_from_bytes(plaintext, len1 + len2);
+  if (session == NULL) {
+    ERR_clear_error(); /* Don't leave an error on the queue. */
+    goto done;
+  }
+
+  /* Copy the client's session ID into the new session, to denote the ticket has
+   * been accepted. */
+  memcpy(session->session_id, session_id, session_id_len);
+  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;
 }
 
 /* Tables to translate from NIDs to TLS v1.2 ids */