You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by sh...@apache.org on 2017/11/17 14:59:24 UTC

[trafficserver] branch master updated: TS API and hooks to manipulate the ATS specific session cache and session ticket keys.

This is an automated email from the ASF dual-hosted git repository.

shinrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 329c021  TS API and hooks to manipulate the ATS specific session cache and session ticket keys.
329c021 is described below

commit 329c0216dd37a99dcb88e0688170d9402fb24957
Author: Susan Hinrichs <sh...@ieee.org>
AuthorDate: Wed Sep 27 13:21:01 2017 +0000

    TS API and hooks to manipulate the ATS specific session cache and session ticket keys.
---
 .../api/functions/TSSslSession.en.rst              | 69 ++++++++++++++++++
 doc/developer-guide/api/types/TSSslSession.en.rst  | 47 +++++++++++++
 .../plugins/hooks-and-transactions/index.en.rst    |  1 +
 .../hooks-and-transactions/ssl-session-api.en.rst  | 78 +++++++++++++++++++++
 iocore/net/P_SSLConfig.h                           | 10 ++-
 iocore/net/SSLCertLookup.cc                        |  1 +
 iocore/net/SSLConfig.cc                            | 50 +++++++++++--
 iocore/net/SSLSessionCache.cc                      | 81 ++++++++++++++++++----
 iocore/net/SSLSessionCache.h                       | 23 +++---
 iocore/net/SSLUtils.cc                             | 24 ++++++-
 lib/ts/apidefs.h.in                                | 17 ++++-
 mgmt/ProxyConfig.cc                                |  1 -
 proxy/InkAPI.cc                                    | 63 +++++++++++++++++
 proxy/InkAPIInternal.h                             |  1 +
 proxy/InkAPITest.cc                                |  3 +-
 proxy/api/ts/ts.h                                  |  5 ++
 proxy/http/HttpDebugNames.cc                       |  2 +
 17 files changed, 442 insertions(+), 34 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSSslSession.en.rst b/doc/developer-guide/api/functions/TSSslSession.en.rst
new file mode 100644
index 0000000..b1bbab9
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSSslSession.en.rst
@@ -0,0 +1,69 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../../common.defs
+.. default-domain:: c
+
+TSSslSession
+************
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSSslSession TSSslSessionGet(const TSSslSessionID * sessionid) 
+.. function:: int TSSslSessionGetBuffer(const TSSslSessionID * sessionid, char * buffer, int * len_ptr)
+.. function:: TSReturnCode TSSslSessionInsert(const TSSslSessionID * sessionid, TSSslSession addSession)
+.. function:: TSReturnCode TSSslSessionRemove(const TSSslSessionID * sessionid)
+.. function:: void TSSslTicketKeyUpdate(char * ticketData, int ticketDataLength)
+
+Description
+===========
+
+These functions work with the internal ATS session cache.  These functions are only useful if the ATS internal
+session cache is enabled by setting :ts:cv:`proxy.config.ssl.session_cache` has been set to 2.
+
+These functions tend to be used with the :macro:`TS_SSL_SESSION_HOOK`. 
+
+The functions work with the :type:`TSSslSessionID` object to identify sessions to retrieve, insert, or delete.
+
+The functions also work with the :type:`TSSSlSession` object which can be cast to a pointer to the openssl SSL_SESSION object.
+
+These functions perform the appropriate locking on the session cache to avoid errors.
+
+The :func:`TSSslSessionGet` and :func:`TSSslSessionGetBuffer` functions retreive the :type:`TSSslSession` object that is identifed by the
+:type:`TSSslSessionID` object.  If there is no matching sesion object, :func:`TSSslSessionGet` returns NULL and :func:`TSSslSessionGetBuffer`
+returns 0.  
+
+:func:`TSSslSessionGetBuffer` returns the session information serialized in a buffer that can be shared between processes.  
+When the function is called len_ptr should point to the amount of space
+available in the buffer parameter.  The function returns the amount of data really needed to encode the session.  len_ptr is updated with the amount of data actually stored in the buffer.
+
+:func:`TSSslSessionInsert` inserts the session specified by the addSession parameter into the ATS session cache under the sessionid key.
+If there is already an entry in the cache for the session id key, it is first removed before the new entry is added.
+
+:func:`TSSslSessionRemove` removes the session entry from the session cache that is keyed by sessionid.
+
+:func:`TSsslTicketKeyUpdate` updates the running ATS process to use a new set of Session Ticket Encryption keys.  This behaves the same way as 
+updating the session ticket encrypt key file with new data and reloading the current ATS process.  However, this API does not
+require writing session ticket encryption keys to disk.  
+
+If both the ticket key files and :func:`TSSslTicketKeyUpdate` are used to update session ticket encryption keys, ATS will use the most recent update
+reguarless if whether it was made by file and configuration reload or API.
+
+
diff --git a/doc/developer-guide/api/types/TSSslSession.en.rst b/doc/developer-guide/api/types/TSSslSession.en.rst
new file mode 100644
index 0000000..c6c9401
--- /dev/null
+++ b/doc/developer-guide/api/types/TSSslSession.en.rst
@@ -0,0 +1,47 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../../common.defs
+.. default-domain:: c
+
+TSSslSession
+**************
+
+Synopsis
+========
+
+`#include <ts/apidefs.h>`
+
+.. type:: TSSslSessionID
+
+   .. member:: size_t len
+
+   .. member:: char bytes[TS_SSL_MAX_SSL_SESSION_ID_LENGTH]
+
+.. type:: TSSslSession
+
+Description
+===========
+
+:type:`TSSslSessionID` represents the SSL session ID as a buffer and length.  The ``TS_SSL_MAX_SSL_SESSION_ID_LENGTH`` is the same value
+as the openssl constant ``SSL_MAX_SSL_SESSION_ID_LENGTH``. The plugin has direct access to this object since creating and 
+manipulating session IDs seems like a fairly common operation (rather than providing an API to access the data via an 
+opaque TS object type).
+
+
+:type:`TSSslSession` references the SSL session object.  It can be cast to the openssl type ``SSL_SESSION``.
+
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/index.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/index.en.rst
index 301dfbb..f68036f 100644
--- a/doc/developer-guide/plugins/hooks-and-transactions/index.en.rst
+++ b/doc/developer-guide/plugins/hooks-and-transactions/index.en.rst
@@ -40,6 +40,7 @@ This chapter contains the following sections:
    initiate-http-connection.en
    http-alternate-selection.en
    ssl-hooks.en
+   ssl-session-api.en
 
 .. _developer-plugins-hooks:
 
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst
new file mode 100644
index 0000000..f4f7618
--- /dev/null
+++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst
@@ -0,0 +1,78 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../../common.defs
+
+.. _developer-plugins-ssl-session-hooks:
+
+.. default-domain:: c
+
+TLS Session Plugin API
+**********************
+
+These interfaces enable a plugin to hook into operations on the ATS TLS session cache.  ATS also provides API's 
+to enable the plugin to update the session cache based on outside information, e.g. peer servers.
+
+.. macro:: TS_SSL_SESSION_HOOK
+
+This hook is invoked when a change has been made to the ATS session cache or a session has been accessed
+from ATS via openssl.  These hooks are only activated if the ATS implementation of the session cache is in
+use.  This means that the setting :ts:cv:`proxy.config.ssl.session_cache` has been set to 2.
+
+The hook callback has the following signature
+
+.. function:: int SSL_session_callback(TSCont contp, TSEvent event, void * edata)
+
+The edata parameter is a pointer to a :type:`TSSslSessionID`.
+
+This callback in synchronous since the underlying openssl callback is unable to pause processing.
+
+The following events can be sent to this callback
+
+.. macro:: TS_EVENT_SSL_SESSION_NEW  
+   Sent after a new session has been inserted into the SSL session cache.  The plugin can call :func:`TSSslSessionGet` to retrieve the actual session object.  The plugin could communicate information about the new session to other processes or update additional logging or statistics.
+.. macro:: TS_EVENT_SSL_SESSION_GET  
+   Sent after a session has been fetched from the SSL session cache by a client request.  The plugin could update additional logginc and statistics.
+.. macro:: TS_EVENT_SSL_SESSION_REMOVE 
+   Sent after a session has been removed from the SSL session cache.  The plugin could communication information about the session removal to other processes or update additional logging and statistics.
+
+Utilitiy Functions
+******************
+
+A number of API functions will likely be used with this hook.
+
+* :func:`TSSslSessionGet`
+* :func:`TSSslSessionGetBuffer`
+* :func:`TSSslSessionInsert`
+* :func:`TSSslSessionRemove`
+* :func:`TSSslTicketKeyUpdate`
+
+Example Use Case
+****************
+
+Consider deploying a set of ATS servers as a farm behind a layer 4 load balancer.  The load balancer does not 
+guarantee that all the requests from a single client are directed to the same ATS box.  Therefore, to maximize TLS session
+reuse, the servers should share session state via some external communication library like redis or rabbitmq.
+
+To do this, they write a plugin that sets the :macro:`TS_SSL_SESSION_HOOK`.  When the hook is triggered, the plugin function sends the 
+updated session state to the other ATS servers via the communication library.
+
+The plugin also has thread that listens for updates and calls :func:`TSSslSessionInsert` and :func:`TSSslSessionRemove` to update the local session cache accordingly.
+
+The plugin can also engage in a protocol to periodically update the session ticket encryption key and communicate the new key to its
+peers.  The plugin calls :func:`TSSslTicketKeyUpdate` to update the local ATS process with the newest keys and the last N keys.
+
diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h
index c16af27..6fa438f 100644
--- a/iocore/net/P_SSLConfig.h
+++ b/iocore/net/P_SSLConfig.h
@@ -69,7 +69,6 @@ struct SSLConfigParams : public ConfigInfo {
   char *dhparamsFile;
   char *cipherSuite;
   char *client_cipherSuite;
-  char *ticket_key_filename;
   int configExitOnLoadError;
   int clientCertLevel;
   int verify_depth;
@@ -161,9 +160,11 @@ private:
 };
 
 struct SSLTicketParams : public ConfigInfo {
-  ssl_ticket_key_block *default_global_keyblock;
+  ssl_ticket_key_block *default_global_keyblock = nullptr;
+  time_t load_time                              = 0;
   char *ticket_key_filename;
   bool LoadTicket();
+  void LoadTicketData(char *ticket_data, int ticket_data_len);
   void cleanup();
 
   ~SSLTicketParams() { cleanup(); }
@@ -172,6 +173,7 @@ struct SSLTicketParams : public ConfigInfo {
 struct SSLTicketKeyConfig {
   static void startup();
   static bool reconfigure();
+  static bool reconfigure_data(char *ticket_data, int ticket_data_len);
 
   static SSLTicketParams *
   acquire()
@@ -182,7 +184,9 @@ struct SSLTicketKeyConfig {
   static void
   release(SSLTicketParams *params)
   {
-    configProcessor.release(configid, params);
+    if (configid > 0) {
+      configProcessor.release(configid, params);
+    }
   }
 
   typedef ConfigProcessor::scoped_config<SSLTicketKeyConfig, SSLTicketParams> scoped_config;
diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc
index 6d39a2d..25bde8e 100644
--- a/iocore/net/SSLCertLookup.cc
+++ b/iocore/net/SSLCertLookup.cc
@@ -178,6 +178,7 @@ ticket_block_create(char *ticket_key_data, int ticket_key_len)
     Error("SSL session ticket key is too short (>= 48 bytes are required)");
     goto fail;
   }
+  Debug("ssl", "Create %d ticket key blocks", num_ticket_keys);
 
   keyblock = ticket_block_alloc(num_ticket_keys);
 
diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc
index a0da4fc..7af5c9e 100644
--- a/iocore/net/SSLConfig.cc
+++ b/iocore/net/SSLConfig.cc
@@ -91,8 +91,8 @@ SSLConfigParams::reset()
 {
   serverCertPathOnly = serverCertChainFilename = configFilePath = serverCACertFilename = serverCACertPath = clientCertPath =
     clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly =
-      ticket_key_filename                                                                                     = nullptr;
-  client_ctx                                                                                                  = nullptr;
+      nullptr;
+  client_ctx      = nullptr;
   clientCertLevel = client_verify_depth = verify_depth = clientVerify = 0;
   ssl_ctx_options                                                     = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
   ssl_client_ctx_protocols                                            = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
@@ -122,7 +122,6 @@ SSLConfigParams::cleanup()
   client_cipherSuite      = (char *)ats_free_null(client_cipherSuite);
   dhparamsFile            = (char *)ats_free_null(dhparamsFile);
   ssl_wire_trace_ip       = (IpAddr *)ats_free_null(ssl_wire_trace_ip);
-  ticket_key_filename     = (char *)ats_free_null(ticket_key_filename);
 
   freeCTXmap();
   SSLReleaseContext(client_ctx);
@@ -542,13 +541,32 @@ SSLTicketParams::LoadTicket()
   ssl_ticket_key_block *keyblock = nullptr;
 
   SSLConfig::scoped_config params;
+  time_t last_load_time    = 0;
+  bool no_default_keyblock = true;
+  SSLTicketKeyConfig::scoped_config ticket_params;
+  if (ticket_params) {
+    last_load_time      = ticket_params->load_time;
+    no_default_keyblock = ticket_params->default_global_keyblock != nullptr;
+  }
 
   if (REC_ReadConfigStringAlloc(ticket_key_filename, "proxy.config.ssl.server.ticket_key.filename") == REC_ERR_OKAY &&
       ticket_key_filename != nullptr) {
     ats_scoped_str ticket_key_path(Layout::relative_to(params->serverCertPathOnly, ticket_key_filename));
+    // See if the file changed since we last loaded
+    struct stat sdata;
+    if (stat(ticket_key_filename, &sdata) >= 0) {
+      if (sdata.st_mtim.tv_sec <= last_load_time) {
+        // No updates since last load
+        return false;
+      }
+    }
     keyblock = ssl_create_ticket_keyblock(ticket_key_path);
-  } else {
+    // Initialize if we don't have one yet
+  } else if (no_default_keyblock) {
     keyblock = ssl_create_ticket_keyblock(nullptr);
+  } else {
+    // No need to update.  Keep the previous ticket param
+    return false;
   }
   if (!keyblock) {
     Error("ticket key reloaded from %s", ticket_key_filename);
@@ -563,6 +581,20 @@ SSLTicketParams::LoadTicket()
 }
 
 void
+SSLTicketParams::LoadTicketData(char *ticket_data, int ticket_data_len)
+{
+  cleanup();
+#if HAVE_OPENSSL_SESSION_TICKETS
+  if (ticket_data != nullptr && ticket_data_len > 0) {
+    default_global_keyblock = ticket_block_create(ticket_data, ticket_data_len);
+  } else {
+    default_global_keyblock = ssl_create_ticket_keyblock(nullptr);
+  }
+  load_time = time(nullptr);
+#endif
+}
+
+void
 SSLTicketKeyConfig::startup()
 {
   auto sslTicketKey = new ConfigUpdateHandler<SSLTicketKeyConfig>();
@@ -585,7 +617,17 @@ SSLTicketKeyConfig::reconfigure()
       return false;
     }
   }
+  configid = configProcessor.set(configid, ticketKey);
+  return true;
+}
 
+bool
+SSLTicketKeyConfig::reconfigure_data(char *ticket_data, int ticket_data_len)
+{
+  SSLTicketParams *ticketKey = new SSLTicketParams();
+  if (ticketKey) {
+    ticketKey->LoadTicketData(ticket_data, ticket_data_len);
+  }
   configid = configProcessor.set(configid, ticketKey);
   return true;
 }
diff --git a/iocore/net/SSLSessionCache.cc b/iocore/net/SSLSessionCache.cc
index 6f72025..ba12185 100644
--- a/iocore/net/SSLSessionCache.cc
+++ b/iocore/net/SSLSessionCache.cc
@@ -49,6 +49,16 @@ SSLSessionCache::~SSLSessionCache()
   delete[] session_bucket;
 }
 
+int
+SSLSessionCache::getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const
+{
+  uint64_t hash            = sid.hash();
+  uint64_t target_bucket   = hash % nbuckets;
+  SSLSessionBucket *bucket = &session_bucket[target_bucket];
+
+  return bucket->getSessionBuffer(sid, buffer, len);
+}
+
 bool
 SSLSessionCache::getSession(const SSLSessionID &sid, SSL_SESSION **sess) const
 {
@@ -80,7 +90,9 @@ SSLSessionCache::removeSession(const SSLSessionID &sid)
           target_bucket, bucket, buf, hash);
   }
 
-  SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction);
+  if (ssl_rsb) {
+    SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction);
+  }
   bucket->removeSession(sid);
 }
 
@@ -117,21 +129,14 @@ SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess)
     Debug("ssl.session_cache", "Inserting session '%s' to bucket %p.", buf, this);
   }
 
-  Ptr<IOBufferData> buf;
-  buf = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
-  ink_release_assert(static_cast<size_t>(buf->block_size()) >= len);
-  unsigned char *loc = reinterpret_cast<unsigned char *>(buf->data());
-  i2d_SSL_SESSION(sess, &loc);
-
-  ats_scoped_obj<SSLSession> ssl_session(new SSLSession(id, buf, len));
-
   MUTEX_TRY_LOCK(lock, mutex, this_ethread());
   if (!lock.is_locked()) {
-    SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
+    if (ssl_rsb) {
+      SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
+    }
     if (SSLConfigParams::session_cache_skip_on_lock_contention) {
       return;
     }
-
     lock.acquire(this_ethread());
   }
 
@@ -140,12 +145,62 @@ SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess)
     removeOldestSession();
   }
 
+  // Don't insert if it is already there
+  SSLSession *node = queue.tail;
+  while (node) {
+    if (node->session_id == id) {
+      return;
+    }
+    node = node->link.prev;
+  }
+
+  Ptr<IOBufferData> buf;
+  buf = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
+  ink_release_assert(static_cast<size_t>(buf->block_size()) >= len);
+  unsigned char *loc = reinterpret_cast<unsigned char *>(buf->data());
+  i2d_SSL_SESSION(sess, &loc);
+
+  ats_scoped_obj<SSLSession> ssl_session(new SSLSession(id, buf, len));
+
   /* do the actual insert */
   queue.enqueue(ssl_session.release());
 
   PRINT_BUCKET("insertSession after")
 }
 
+int
+SSLSessionBucket::getSessionBuffer(const SSLSessionID &id, char *buffer, int &len)
+{
+  int true_len = 0;
+  MUTEX_TRY_LOCK(lock, mutex, this_ethread());
+  if (!lock.is_locked()) {
+    if (ssl_rsb) {
+      SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
+    }
+    if (SSLConfigParams::session_cache_skip_on_lock_contention)
+      return true_len;
+
+    lock.acquire(this_ethread());
+  }
+
+  // We work backwards because that's the most likely place we'll find our session...
+  SSLSession *node = queue.tail;
+  while (node) {
+    if (node->session_id == id) {
+      true_len = node->len_asn1_data;
+      if (buffer) {
+        const unsigned char *loc = reinterpret_cast<const unsigned char *>(node->asn1_data->data());
+        if (true_len < len)
+          len = true_len;
+        memcpy(buffer, loc, len);
+        return true_len;
+      }
+    }
+    node = node->link.prev;
+  }
+  return 0;
+}
+
 bool
 SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess)
 {
@@ -159,7 +214,9 @@ SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess)
 
   MUTEX_TRY_LOCK(lock, mutex, this_ethread());
   if (!lock.is_locked()) {
-    SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
+    if (ssl_rsb) {
+      SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
+    }
     if (SSLConfigParams::session_cache_skip_on_lock_contention) {
       return false;
     }
diff --git a/iocore/net/SSLSessionCache.h b/iocore/net/SSLSessionCache.h
index db80bc9..0ff2b66 100644
--- a/iocore/net/SSLSessionCache.h
+++ b/iocore/net/SSLSessionCache.h
@@ -31,16 +31,15 @@
 #include "ts/ink_platform.h"
 #include "P_SSLUtils.h"
 #include "ts/RbTree.h"
+#include "ts/apidefs.h"
 #include <openssl/ssl.h>
 
 #define SSL_MAX_SESSION_SIZE 256
 
-struct SSLSessionID {
-  char bytes[SSL_MAX_SSL_SESSION_ID_LENGTH];
-  size_t len;
-
-  SSLSessionID(const unsigned char *s, size_t l) : len(l)
+struct SSLSessionID : public TSSslSessionID {
+  SSLSessionID(const unsigned char *s, size_t l)
   {
+    len = l;
     ink_release_assert(l <= sizeof(bytes));
     memcpy(bytes, s, l);
   }
@@ -102,13 +101,15 @@ struct SSLSessionID {
   uint64_t
   hash() const
   {
-    // because the session ids should be uniformly random let's just use the upper 64 bits as the hash.
-    if (len >= sizeof(uint64_t))
-      return *reinterpret_cast<uint64_t *>(const_cast<char *>(bytes));
-    else if (len)
+    // because the session ids should be uniformly random let's just use the last 64 bits as the hash.
+    // The first bytes could be interpreted as a name, and so not random.
+    if (len >= sizeof(uint64_t)) {
+      return *reinterpret_cast<uint64_t *>(const_cast<char *>(bytes + len - sizeof(uint64_t)));
+    } else if (len) {
       return static_cast<uint64_t>(bytes[0]);
-    else
+    } else {
       return 0;
+    }
   }
 };
 
@@ -134,6 +135,7 @@ public:
   ~SSLSessionBucket();
   void insertSession(const SSLSessionID &, SSL_SESSION *ctx);
   bool getSession(const SSLSessionID &, SSL_SESSION **ctx);
+  int getSessionBuffer(const SSLSessionID &, char *buffer, int &len);
   void removeSession(const SSLSessionID &);
 
 private:
@@ -149,6 +151,7 @@ class SSLSessionCache
 {
 public:
   bool getSession(const SSLSessionID &sid, SSL_SESSION **sess) const;
+  int getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const;
   void insertSession(const SSLSessionID &sid, SSL_SESSION *sess);
   void removeSession(const SSLSessionID &sid);
   SSLSessionCache();
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 7c55cd5..ae964eb 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -28,6 +28,7 @@
 #include "ts/ink_mutex.h"
 #include "P_OCSPStapling.h"
 #include "SSLSessionCache.h"
+#include "InkAPIInternal.h"
 #include "SSLDynlock.h"
 
 #include <string>
@@ -236,8 +237,13 @@ ssl_get_cached_session(SSL *ssl, const unsigned char *id, int len, int *copy)
     Debug("ssl.session_cache.get", "ssl_get_cached_session cached session '%s' context %p", printable_buf, SSL_get_SSL_CTX(ssl));
   }
 
-  SSL_SESSION *session = nullptr;
+  APIHook *hook = ssl_hooks->get(TS_SSL_SESSION_INTERNAL_HOOK);
+  while (hook) {
+    hook->invoke(TS_EVENT_SSL_SESSION_GET, &sid);
+    hook = hook->m_link.next;
+  }
 
+  SSL_SESSION *session = nullptr;
   if (session_cache->getSession(sid, &session)) {
     ink_assert(session);
 
@@ -248,8 +254,8 @@ ssl_get_cached_session(SSL *ssl, const unsigned char *id, int len, int *copy)
 // from the openssl built-in hash table.  The external remove cb is not called
 #if 0 // This is currently eliminated, since it breaks things in odd ways (see TS-3710)
       ssl_rm_cached_session(SSL_get_SSL_CTX(ssl), session);
-      session = nullptr;
 #endif
+      session = nullptr;
     } else {
       SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
       SSL_INCREMENT_DYN_STAT(ssl_session_cache_hit);
@@ -278,6 +284,13 @@ ssl_new_cached_session(SSL *ssl, SSL_SESSION *sess)
   SSL_INCREMENT_DYN_STAT(ssl_session_cache_new_session);
   session_cache->insertSession(sid, sess);
 
+  // Call hook after new session is created
+  APIHook *hook = ssl_hooks->get(TS_SSL_SESSION_INTERNAL_HOOK);
+  while (hook) {
+    hook->invoke(TS_EVENT_SSL_SESSION_NEW, &sid);
+    hook = hook->m_link.next;
+  }
+
   return 0;
 }
 
@@ -288,6 +301,13 @@ ssl_rm_cached_session(SSL_CTX *ctx, SSL_SESSION *sess)
   const unsigned char *id = SSL_SESSION_get_id(sess, &len);
   SSLSessionID sid(id, len);
 
+  // Call hook before session is removed
+  APIHook *hook = ssl_hooks->get(TS_SSL_SESSION_INTERNAL_HOOK);
+  while (hook) {
+    hook->invoke(TS_EVENT_SSL_SESSION_REMOVE, &sid);
+    hook = hook->m_link.next;
+  }
+
   if (diags->tag_activated("ssl.session_cache")) {
     char printable_buf[(len * 2) + 1];
     sid.toString(printable_buf, sizeof(printable_buf));
diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in
index 5442299..ee480d2 100644
--- a/lib/ts/apidefs.h.in
+++ b/lib/ts/apidefs.h.in
@@ -291,7 +291,8 @@ typedef enum {
   TS_SSL_CERT_HOOK = TS_SSL_SNI_HOOK,
   TS_SSL_SERVERNAME_HOOK,
   TS_SSL_SERVER_VERIFY_HOOK,
-  TS_SSL_LAST_HOOK = TS_SSL_SERVER_VERIFY_HOOK,
+  TS_SSL_SESSION_HOOK,
+  TS_SSL_LAST_HOOK = TS_SSL_SESSION_HOOK,
   TS_HTTP_LAST_HOOK
 } TSHttpHookID;
 
@@ -421,6 +422,9 @@ typedef enum {
   TS_EVENT_CACHE_READ_READY                     = 1134,
   TS_EVENT_CACHE_READ_COMPLETE                  = 1135,
   TS_EVENT_INTERNAL_1200                        = 1200,
+  TS_EVENT_SSL_SESSION_GET                      = 2000,
+  TS_EVENT_SSL_SESSION_NEW                      = 2001,
+  TS_EVENT_SSL_SESSION_REMOVE                   = 2002,
   TS_AIO_EVENT_DONE                             = 3900,
   TS_EVENT_HTTP_CONTINUE                        = 60000,
   TS_EVENT_HTTP_ERROR                           = 60001,
@@ -831,6 +835,7 @@ typedef struct tsapi_mbuffer *TSMBuffer;
 typedef struct tsapi_httpssn *TSHttpSsn;
 typedef struct tsapi_httptxn *TSHttpTxn;
 typedef struct tsapi_ssl_obj *TSSslConnection;
+typedef struct tsapi_ssl_session *TSSslSession;
 typedef struct tsapi_httpaltinfo *TSHttpAltInfo;
 typedef struct tsapi_mimeparser *TSMimeParser;
 typedef struct tsapi_httpparser *TSHttpParser;
@@ -877,6 +882,16 @@ typedef struct TSFetchUrlParams {
   struct TSFetchUrlParams *next;
 } TSFetchUrlParams_t;
 
+// This is a duplicate of the SSL_MAX_SSL_SESSION_ID_LENGTH constant
+// Redefining here so we don't include the openssl/ssl.h file here
+#define TS_SSL_MAX_SSL_SESSION_ID_LENGTH        32
+
+// This mirrors the internal data structure SSLSessionID
+typedef struct TSSslSessionID_s {
+  size_t len;
+  char bytes[TS_SSL_MAX_SSL_SESSION_ID_LENGTH];
+} TSSslSessionID;
+
 /* --------------------------------------------------------------------------
    Init */
 
diff --git a/mgmt/ProxyConfig.cc b/mgmt/ProxyConfig.cc
index c90f950..3b403a4 100644
--- a/mgmt/ProxyConfig.cc
+++ b/mgmt/ProxyConfig.cc
@@ -168,7 +168,6 @@ ConfigProcessor::get(unsigned int id)
   ConfigInfo *info;
   int idx;
 
-  ink_assert(id != 0);
   ink_assert(id <= MAX_CONFIGS);
 
   if (id == 0 || id > MAX_CONFIGS) {
diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc
index 4a17cfe..f927095 100644
--- a/proxy/InkAPI.cc
+++ b/proxy/InkAPI.cc
@@ -43,6 +43,7 @@
 #include "P_HostDB.h"
 #include "P_Cache.h"
 #include "I_RecCore.h"
+#include "P_SSLConfig.h"
 #include "ProxyConfig.h"
 #include "Plugin.h"
 #include "LogObject.h"
@@ -9191,6 +9192,12 @@ TSSslContextDestroy(TSSslContext ctx)
   SSLReleaseContext(reinterpret_cast<SSL_CTX *>(ctx));
 }
 
+tsapi void
+TSSslTicketKeyUpdate(char *ticketData, int ticketDataLen)
+{
+  SSLTicketKeyConfig::reconfigure_data(ticketData, ticketDataLen);
+}
+
 void
 TSRegisterProtocolSet(TSVConn sslp, TSNextProtocolSet ps)
 {
@@ -9278,6 +9285,62 @@ TSVConnReenable(TSVConn vconn)
   }
 }
 
+extern SSLSessionCache *session_cache; // declared extern in P_SSLConfig.h
+
+TSSslSession
+TSSslSessionGet(const TSSslSessionID *session_id)
+{
+  SSL_SESSION *session = NULL;
+  if (session_id && session_cache) {
+    session_cache->getSession(reinterpret_cast<const SSLSessionID &>(*session_id), &session);
+  }
+  return reinterpret_cast<TSSslSession>(session);
+}
+
+int
+TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr)
+{
+  int true_len = 0;
+  // Don't get if there is no session id or the cache is not yet set up
+  if (session_id && session_cache && len_ptr) {
+    true_len = session_cache->getSessionBuffer(reinterpret_cast<const SSLSessionID &>(*session_id), buffer, *len_ptr);
+  }
+  return true_len;
+}
+
+TSReturnCode
+TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session)
+{
+  // Don't insert if there is no session id or the cache is not yet set up
+  if (session_id && session_cache) {
+    if (is_debug_tag_set("ssl.session_cache")) {
+      const SSLSessionID *sid = reinterpret_cast<const SSLSessionID *>(session_id);
+      char buf[sid->len * 2 + 1];
+      sid->toString(buf, sizeof(buf));
+      Debug("ssl.session_cache.insert", "TSSslSessionInsert: Inserting session '%s' ", buf);
+    }
+    SSL_SESSION *session = reinterpret_cast<SSL_SESSION *>(add_session);
+    session_cache->insertSession(reinterpret_cast<const SSLSessionID &>(*session_id), session);
+    // insertSession returns void, assume all went well
+    return TS_SUCCESS;
+  } else {
+    return TS_ERROR;
+  }
+}
+
+TSReturnCode
+TSSslSessionRemove(const TSSslSessionID *session_id)
+{
+  // Don't remove if there is no session id or the cache is not yet set up
+  if (session_id && session_cache) {
+    session_cache->removeSession(reinterpret_cast<const SSLSessionID &>(*session_id));
+    // removeSession returns void, assume all went well
+    return TS_SUCCESS;
+  } else {
+    return TS_ERROR;
+  }
+}
+
 // APIs for managing and using UUIDs.
 TSUuid
 TSUuidCreate(void)
diff --git a/proxy/InkAPIInternal.h b/proxy/InkAPIInternal.h
index 2616cf9..9207b68 100644
--- a/proxy/InkAPIInternal.h
+++ b/proxy/InkAPIInternal.h
@@ -282,6 +282,7 @@ typedef enum {
   TS_SSL_CERT_INTERNAL_HOOK,
   TS_SSL_SERVERNAME_INTERNAL_HOOK,
   TS_SSL_SERVER_VERIFY_INTERNAL_HOOK,
+  TS_SSL_SESSION_INTERNAL_HOOK,
   TS_SSL_INTERNAL_LAST_HOOK
 } TSSslHookInternalID;
 
diff --git a/proxy/InkAPITest.cc b/proxy/InkAPITest.cc
index 84d6f55..d9f4d97 100644
--- a/proxy/InkAPITest.cc
+++ b/proxy/InkAPITest.cc
@@ -5544,7 +5544,8 @@ typedef enum {
   ORIG_TS_SSL_SNI_HOOK,
   ORIG_TS_SSL_SERVERNAME_HOOK,
   ORIG_TS_SSL_SERVER_VERIFY_HOOK,
-  ORIG_TS_SSL_LAST_HOOK = ORIG_TS_SSL_SERVER_VERIFY_HOOK,
+  ORIG_TS_SSL_SESSION_HOOK,
+  ORIG_TS_SSL_LAST_HOOK = ORIG_TS_SSL_SESSION_HOOK,
   ORIG_TS_HTTP_LAST_HOOK
 } ORIG_TSHttpHookID;
 
diff --git a/proxy/api/ts/ts.h b/proxy/api/ts/ts.h
index 5a0818d..41ffd6a 100644
--- a/proxy/api/ts/ts.h
+++ b/proxy/api/ts/ts.h
@@ -1239,6 +1239,7 @@ tsapi TSSslContext TSSslContextFindByAddr(struct sockaddr const *);
 // Create a new SSL context based on the settings in records.config
 tsapi TSSslContext TSSslServerContextCreate(void);
 tsapi void TSSslContextDestroy(TSSslContext ctx);
+tsapi void TSSslTicketKeyUpdate(char *ticketData, int ticketDataLen);
 tsapi TSNextProtocolSet TSUnregisterProtocol(TSNextProtocolSet protoset, const char *protocol);
 TSAcceptor TSAcceptorGet(TSVConn sslp);
 TSNextProtocolSet TSGetcloneProtoSet(TSAcceptor tna);
@@ -1249,6 +1250,10 @@ int TSAcceptorIDGet(TSAcceptor acceptor);
 
 // Returns 1 if the sslp argument refers to a SSL connection
 tsapi int TSVConnIsSsl(TSVConn sslp);
+tsapi TSSslSession TSSslSessionGet(const TSSslSessionID *session_id);
+tsapi int TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr);
+tsapi TSReturnCode TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session);
+tsapi TSReturnCode TSSslSessionRemove(const TSSslSessionID *session_id);
 
 /* --------------------------------------------------------------------------
    HTTP transactions */
diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc
index 6715abb..a67d574 100644
--- a/proxy/http/HttpDebugNames.cc
+++ b/proxy/http/HttpDebugNames.cc
@@ -474,6 +474,8 @@ HttpDebugNames::get_api_hook_name(TSHttpHookID t)
     return "TS_SSL_SERVERNAME_HOOK";
   case TS_SSL_SERVER_VERIFY_HOOK:
     return "TS_SSL_SERVER_VERIFY_HOOK";
+  case TS_SSL_SESSION_HOOK:
+    return "TS_SSL_SESSION_HOOK";
   }
 
   return "unknown hook";

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>'].