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/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);