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 2019/05/30 14:02:18 UTC

[trafficserver] branch master updated: Added new API TSSslClientContextsNamesGet and TSSslClientContextFindByName. Added an example plugin that records all loaded context information into a log file. Added an autest for the example plugin. Added documentation for the APIs.

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 896f0e2  Added new API TSSslClientContextsNamesGet and TSSslClientContextFindByName. Added an example plugin that records all loaded context information into a log file. Added an autest for the example plugin. Added documentation for the APIs.
896f0e2 is described below

commit 896f0e24bc169b1cfb270e5864963d81f59d9329
Author: dyrock <ze...@gmail.com>
AuthorDate: Wed May 15 11:04:41 2019 -0500

    Added new API TSSslClientContextsNamesGet and
    TSSslClientContextFindByName. Added an example plugin that records all
    loaded context information into a log file. Added an autest for the
    example plugin. Added documentation for the APIs.
---
 .../api/functions/TSSslClientContext.en.rst        |  59 +++++++
 example/Makefile.am                                |   2 +
 example/client_context_dump/client_context_dump.cc | 189 +++++++++++++++++++++
 include/ts/ts.h                                    |   4 +
 src/traffic_server/InkAPI.cc                       |  73 ++++++++
 tests/gold_tests/autest-site/setup.cli.ext         |   1 +
 .../client_context_dump.test.py                    |  78 +++++++++
 .../gold/client_context_dump.gold                  |   2 +
 .../pluginTest/client_context_dump/ssl/one.com.pem |  84 +++++++++
 .../pluginTest/client_context_dump/ssl/two.com.pem |  83 +++++++++
 10 files changed, 575 insertions(+)

diff --git a/doc/developer-guide/api/functions/TSSslClientContext.en.rst b/doc/developer-guide/api/functions/TSSslClientContext.en.rst
new file mode 100644
index 0000000..f6aa13b
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSSslClientContext.en.rst
@@ -0,0 +1,59 @@
+.. 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
+
+TSSslClientContext
+******************
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSReturnCode TSSslClientContextsNamesGet(int n, const char **result, int *actual)
+
+.. function:: TSSslContext TSSslClientContextFindByName(const char *ca_paths, const char *ck_paths)
+ 
+Description
+===========
+
+These functions are used to explore the client contexts that |TS| uses to connect to upstreams.
+
+:func:`TSSslClientContextsNamesGet` can be used to retrieve the entire client context mappings. Note 
+that in traffic server, client contexts are stored in a 2-level mapping with ca paths and cert/key 
+paths as keys. Hence every 2 null-terminated string in :arg:`result` can be used to lookup one context.
+:arg:`result` points to an user allocated array that will hold pointers to lookup key strings and 
+:arg:`n` is the size for :arg:`result` array. :arg:`actual`, if valid, will be filled with actual number
+of lookup keys (2 for each context).
+
+:func:`TSSslClientContextFindByName` can be used to retrieve the client context pointed by the lookup 
+key pairs. User should call :func:`TSSslClientContextsNamesGet` first to determine which lookup keys are 
+present before quering for the context. :arg:`ca_paths` should be the first key and :arg:`ck_paths` 
+should be the second. This function returns NULL if the client context mapping are changed and no valid 
+context exists for the key pair.
+
+Examples
+========
+
+The example below is excerpted from `example/client_context_dump/client_context_dump.cc` in the Traffic 
+Server source distribution. It demonstrates how to use :func:`TSSslClientContextsNamesGet` and 
+:func:`TSSslClientContextFindByName` to retreive all contextxs.
+
+.. literalinclude:: ../../../../example/client_context_dump/client_context_dump.cc
+  :language: c
+  :lines: 137-145
diff --git a/example/Makefile.am b/example/Makefile.am
index 56fa287..062b749 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -34,6 +34,7 @@ example_Plugins = \
 	bnull_transform.la \
 	request_buffer.la \
 	cache_scan.la \
+	client_context_dump.la \
 	file_1.la \
 	hello.la \
 	intercept.la \
@@ -103,6 +104,7 @@ blacklist_1_la_SOURCES = blacklist_1/blacklist_1.c
 bnull_transform_la_SOURCES = bnull_transform/bnull_transform.c
 request_buffer_la_SOURCES = request_buffer/request_buffer.c
 cache_scan_la_SOURCES = cache_scan/cache_scan.cc
+client_context_dump_la_SOURCES = client_context_dump/client_context_dump.cc
 file_1_la_SOURCES = file_1/file_1.c
 hello_la_SOURCES = hello/hello.c
 intercept_la_SOURCES = intercept/intercept.cc
diff --git a/example/client_context_dump/client_context_dump.cc b/example/client_context_dump/client_context_dump.cc
new file mode 100644
index 0000000..9f83fd0
--- /dev/null
+++ b/example/client_context_dump/client_context_dump.cc
@@ -0,0 +1,189 @@
+/** @file
+   an example client context dump plugin
+   @section license License
+   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 <stdio.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#ifdef OPENSSL_NO_SSL_INTERN
+#undef OPENSSL_NO_SSL_INTERN
+#endif
+
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "ts/ts.h"
+#include "tscpp/util/TextView.h"
+
+#define PLUGIN_NAME "client_context_dump"
+TSTextLogObject context_dump_log;
+
+char *
+asn1_string_extract(ASN1_STRING *s)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x010100000
+  return reinterpret_cast<char *>(const_cast<unsigned char *>(ASN1_STRING_get0_data(s)));
+#else
+  return reinterpret_cast<char *>(ASN1_STRING_data(s));
+#endif
+}
+
+// For 1.0.2, needs access to internal structure
+// For 1.1.0 and 1.1.1, use API
+void
+dump_context(const char *ca_path, const char *ck_path)
+{
+  SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(TSSslClientContextFindByName(ca_path, ck_path));
+  if (ctx) {
+    SSL *s = SSL_new(ctx);
+    if (s) {
+      char *data  = nullptr;
+      long length = 0;
+      std::string subject_s, san_s, serial_s, time_s;
+      X509 *cert = SSL_get_certificate(s);
+      if (cert) {
+        // Retrieve state info and write to log object
+        // expiration date, serial number, common name, and subject alternative names
+        const ASN1_TIME *not_after = X509_get_notAfter(cert);
+        const ASN1_INTEGER *serial = X509_get_serialNumber(cert);
+        X509_NAME *subject_name    = X509_get_subject_name(cert);
+
+        // Subject name
+        BIO *subject_bio = BIO_new(BIO_s_mem());
+        X509_NAME_print_ex(subject_bio, subject_name, 0, XN_FLAG_RFC2253);
+        length = BIO_get_mem_data(subject_bio, &data);
+        if (length > 0 && data) {
+          subject_s = std::string(data, length);
+        }
+        length = 0;
+        data   = nullptr;
+        BIO_free(subject_bio);
+
+        // Subject Alternative Name
+        GENERAL_NAMES *names = static_cast<GENERAL_NAMES *>(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+        if (names) {
+          unsigned count = sk_GENERAL_NAME_num(names);
+          for (unsigned i = 0; i < count; ++i) {
+            GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
+            data               = nullptr;
+            length             = 0;
+            switch (name->type) {
+            case (GEN_EMAIL): {
+              data   = asn1_string_extract(name->d.rfc822Name);
+              length = ASN1_STRING_length(name->d.rfc822Name);
+              break;
+            }
+            case (GEN_DNS): {
+              data   = asn1_string_extract(name->d.dNSName);
+              length = ASN1_STRING_length(name->d.dNSName);
+              break;
+            }
+            case (GEN_URI): {
+              data   = asn1_string_extract(name->d.uniformResourceIdentifier);
+              length = ASN1_STRING_length(name->d.uniformResourceIdentifier);
+              break;
+            }
+            default:
+              break;
+            }
+            if (data) {
+              san_s.append(data, length);
+              san_s.push_back(',');
+            }
+          }
+          if (san_s.back() == ',') {
+            san_s.pop_back();
+          }
+        }
+
+        // Serial number
+        long sn = 0;
+#if OPENSSL_VERSION_NUMBER >= 0x010100000
+        ASN1_INTEGER_get_int64(&sn, serial);
+#else
+        sn = ASN1_INTEGER_get(serial);
+#endif
+        if (sn != 0 && sn != -1) {
+          serial_s = std::to_string(sn);
+        }
+
+        // Expiration
+        BIO *time_bio = BIO_new(BIO_s_mem());
+        ASN1_TIME_print(time_bio, not_after);
+        length = BIO_get_mem_data(time_bio, &data);
+        time_s = std::string(data, length);
+        BIO_free(time_bio);
+        TSDebug(PLUGIN_NAME, "LookupName: %s:%s, Subject: %s. SAN: %s. Serial: %s. NotAfter: %s.", ca_path, ck_path,
+                subject_s.c_str(), san_s.c_str(), serial_s.c_str(), time_s.c_str());
+        TSTextLogObjectWrite(context_dump_log, "LookupName: %s:%s, Subject: %s. SAN: %s. Serial: %s. NotAfter: %s.", ca_path,
+                             ck_path, subject_s.c_str(), san_s.c_str(), serial_s.c_str(), time_s.c_str());
+      }
+    }
+    SSL_free(s);
+  }
+}
+
+// Plugin Message Continuation
+int
+CB_context_dump(TSCont, TSEvent, void *edata)
+{
+  TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
+  static constexpr std::string_view PLUGIN_PREFIX("client_context_dump."_sv);
+
+  std::string_view tag(msg->tag, strlen(msg->tag));
+
+  if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
+    tag.remove_prefix(PLUGIN_PREFIX.size());
+    // Grab all keys by API and dump to log file according to arg passed in
+    int count = 0;
+    TSSslClientContextsNamesGet(0, nullptr, &count);
+    if (count > 0) {
+      char const **results = static_cast<char const **>(malloc(sizeof(const char *) * count));
+      TSSslClientContextsNamesGet(count, results, nullptr);
+      for (int i = 0; i < count; i += 2) {
+        dump_context(results[i], results[i + 1]);
+      }
+    }
+  }
+  TSTextLogObjectFlush(context_dump_log);
+  return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PLUGIN_NAME;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+    return;
+  }
+  if (TSTextLogObjectCreate(PLUGIN_NAME, TS_LOG_MODE_ADD_TIMESTAMP, &context_dump_log) != TS_SUCCESS || !context_dump_log) {
+    TSError("[%s] Failed to create log file", PLUGIN_NAME);
+    return;
+  }
+  TSDebug(PLUGIN_NAME, "Initialized.");
+  TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, TSContCreate(CB_context_dump, nullptr));
+}
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 6552969..250d258 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1235,6 +1235,10 @@ tsapi TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn sslp);
 /*  Fetch a SSL context from the global lookup table */
 tsapi TSSslContext TSSslContextFindByName(const char *name);
 tsapi TSSslContext TSSslContextFindByAddr(struct sockaddr const *);
+/* Fetch SSL client contexts from the global lookup table */
+tsapi TSReturnCode TSSslClientContextsNamesGet(int n, const char **result, int *actual);
+tsapi TSSslContext TSSslClientContextFindByName(const char *ca_paths, const char *ck_paths);
+
 /*  Create a new SSL context based on the settings in records.config */
 tsapi TSSslContext TSSslServerContextCreate(TSSslX509 cert, const char *certname, const char *rsp_file);
 tsapi void TSSslContextDestroy(TSSslContext ctx);
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index e313b75..bb211a2 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -9004,6 +9004,79 @@ TSSslContextFindByAddr(struct sockaddr const *addr)
   return ret;
 }
 
+/**
+ * This function retrieves an array of lookup keys for client contexts loaded in
+ * traffic server. Given a 2-level mapping for client contexts, every 2 lookup keys
+ * can be used to locate and identify 1 context.
+ * @param n Allocated size for result array.
+ * @param result Const char pointer arrays to be filled with lookup keys.
+ * @param actual Total number of lookup keys.
+ */
+tsapi TSReturnCode
+TSSslClientContextsNamesGet(int n, const char **result, int *actual)
+{
+  sdk_assert(n == 0 || result != nullptr);
+  int idx = 0, count = 0;
+  SSLConfigParams *params = SSLConfig::acquire();
+
+  if (params) {
+    auto &ctx_map_lock = params->ctxMapLock;
+    auto &ca_map       = params->top_level_ctx_map;
+    auto mem           = static_cast<std::string_view *>(alloca(sizeof(std::string_view) * n));
+    ink_mutex_acquire(&ctx_map_lock);
+    for (auto &ca_pair : ca_map) {
+      // Populate mem array with 2 strings each time
+      for (auto &ctx_pair : *ca_pair.second) {
+        if (idx + 1 < n) {
+          mem[idx++] = ca_pair.first;
+          mem[idx++] = ctx_pair.first;
+        }
+        count += 2;
+      }
+    }
+    ink_mutex_release(&ctx_map_lock);
+    for (int i = 0; i < idx; i++) {
+      result[i] = mem[i].data();
+    }
+  }
+  if (actual) {
+    *actual = count;
+  }
+  SSLConfig::release(params);
+  return TS_SUCCESS;
+}
+
+/**
+ * This function returns the client context corresponding to the lookup keys provided.
+ * User should call TSSslClientContextsGet() first to determine which lookup keys are
+ * present before querying for them.
+ * Returns valid TSSslContext on success and nullptr on failure.
+ * @param first_key Key string for the top level.
+ * @param second_key Key string for the second level.
+ */
+tsapi TSSslContext
+TSSslClientContextFindByName(const char *ca_paths, const char *ck_paths)
+{
+  if (!ca_paths || !ck_paths || ca_paths[0] == '\0' || ck_paths[0] == '\0') {
+    return nullptr;
+  }
+  SSLConfigParams *params = SSLConfig::acquire();
+  TSSslContext retval     = nullptr;
+  if (params) {
+    ink_mutex_acquire(&params->ctxMapLock);
+    auto ca_iter = params->top_level_ctx_map.find(ca_paths);
+    if (ca_iter != params->top_level_ctx_map.end()) {
+      auto ctx_iter = ca_iter->second->find(ck_paths);
+      if (ctx_iter != ca_iter->second->end()) {
+        retval = reinterpret_cast<TSSslContext>(ctx_iter->second);
+      }
+    }
+    ink_mutex_release(&params->ctxMapLock);
+  }
+  SSLConfig::release(params);
+  return retval;
+}
+
 tsapi TSSslContext
 TSSslServerContextCreate(TSSslX509 cert, const char *certname, const char *rsp_file)
 {
diff --git a/tests/gold_tests/autest-site/setup.cli.ext b/tests/gold_tests/autest-site/setup.cli.ext
index 3f8ca69..d0f1e64 100644
--- a/tests/gold_tests/autest-site/setup.cli.ext
+++ b/tests/gold_tests/autest-site/setup.cli.ext
@@ -78,6 +78,7 @@ if ENV['ATS_BIN'] is not None:
                 host.WriteError("tsxs is broken. Aborting tests", show_stack=False)
     host.WriteVerbose(['ats'], "Traffic server build flags:\n", pprint.pformat(out))
     Variables.update(out)
+Variables.AtsExampleDir = os.path.join(AutestSitePath, '../../../example')
 Variables.AtsTestToolsDir = os.path.join(AutestSitePath, '../../tools')
 
 # modify delay times as we always have to kill Trafficserver
diff --git a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
new file mode 100644
index 0000000..ce1a48e
--- /dev/null
+++ b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
@@ -0,0 +1,78 @@
+'''
+Test the client_context_dump plugin.
+'''
+#  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.
+
+import os
+import subprocess
+Test.Summary = '''
+Test client_context_dump plugin
+'''
+
+# Set up ATS
+ts = Test.MakeATSProcess("ts", command="traffic_manager", select_ports=False)
+
+# Set up ssl files
+ts.addSSLfile("ssl/one.com.pem")
+ts.addSSLfile("ssl/two.com.pem")
+
+ts.Variables.ssl_port = 4443
+
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'client_context_dump',
+    'proxy.config.ssl.server.cert.path': '{}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.client.cert.path': '{}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.client.private_key.path': '{}'.format(ts.Variables.SSLDir),
+    'proxy.config.http.server_ports': (
+        '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port))
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=one.com.pem ssl_key_name=one.com.pem'
+)
+
+ts.Disk.ssl_server_name_yaml.AddLines([
+    '- fqdn: "*one.com"',
+    '  client_cert: "one.com.pem"',
+    '- fqdn: "*two.com"',
+    '  client_cert: "two.com.pem"'
+])
+
+# Set up plugin
+Test.PreparePlugin(Test.Variables.AtsExampleDir + '/client_context_dump/client_context_dump.cc', ts)
+
+# custom log comparison
+Test.Disk.File(ts.Variables.LOGDIR + '/client_context_dump.log', exists=True, content='gold/client_context_dump.gold')
+
+# traffic server test
+t = Test.AddTestRun("Test traffic server started properly")
+t.StillRunningAfter = Test.Processes.ts
+
+p = t.Processes.Default
+p.Command = "curl http://127.0.0.1:8080"
+p.ReturnCode = 0
+p.StartBefore(Test.Processes.ts, ready=When.PortOpen(8080))
+
+# Client contexts test
+tr = Test.AddTestRun()
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = (
+    '{0}/traffic_ctl plugin msg client_context_dump.t 1'.format(ts.Variables.BINDIR)
+)
+tr.Processes.Default.ReturnCode = 0
diff --git a/tests/gold_tests/pluginTest/client_context_dump/gold/client_context_dump.gold b/tests/gold_tests/pluginTest/client_context_dump/gold/client_context_dump.gold
new file mode 100644
index 0000000..21527d9
--- /dev/null
+++ b/tests/gold_tests/pluginTest/client_context_dump/gold/client_context_dump.gold
@@ -0,0 +1,2 @@
+``LookupName:``two.com.pem:, Subject: C=US,ST=Illinois,L=Champaign,OU=Two,O=Apache,emailAddress=ssl@two.com,CN=two.com. SAN: . Serial: . NotAfter: Jun 21 20:39:43 2019 GMT.
+``LookupName:``one.com.pem:, Subject: CN=one.com,O=Apache,L=Champaign,ST=Illinois,C=US. SAN: . Serial: . NotAfter: May 22 18:17:04 2020 GMT.
diff --git a/tests/gold_tests/pluginTest/client_context_dump/ssl/one.com.pem b/tests/gold_tests/pluginTest/client_context_dump/ssl/one.com.pem
new file mode 100644
index 0000000..9835695
--- /dev/null
+++ b/tests/gold_tests/pluginTest/client_context_dump/ssl/one.com.pem
@@ -0,0 +1,84 @@
+-----BEGIN CERTIFICATE-----
+MIIFjzCCA3egAwIBAgIUL4Tdbs9LkVpOyO1zSKphXYcXNfwwDQYJKoZIhvcNAQEL
+BQAwVzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRIwEAYDVQQHDAlD
+aGFtcGFpZ24xDzANBgNVBAoMBkFwYWNoZTEQMA4GA1UEAwwHb25lLmNvbTAeFw0x
+OTA1MjMxODE3MDRaFw0yMDA1MjIxODE3MDRaMFcxCzAJBgNVBAYTAlVTMREwDwYD
+VQQIDAhJbGxpbm9pczESMBAGA1UEBwwJQ2hhbXBhaWduMQ8wDQYDVQQKDAZBcGFj
+aGUxEDAOBgNVBAMMB29uZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQD0YSNv4UxBFFWT5SOXWtqzfNQI8RsMSQFh2qEHKyQ4oDoBv3mO1iwM143J
+pCE4t/960A/XdbXsVYdHh1YRcBZu+EKmj6i0od+JRit/gBat+brNBWH11oAf9hhq
+SBgl0GMD5HgcYIkRYGkhLUNllTFEGh79uPPBLLrCmdnZG9G8sNVeaPp74iyXsY57
+mw9IoD5b3t21IwMIBzTvAcsRKWWGi/s0ZSN4srERRjA3CWqF8srv28QciXXlpe2a
+MRH1TC/Ec/UuT6SUtAC/G7J6oQ8KS8TwyGafZoz28z4BlfVRw9WkNv+Atxy1zvZU
+3JQCPecVV3Y3mD5zyXSQjp54ukcEd3pBE7kqLVrs4IddS/bifJl564Z6WlFNblbi
+8g46dQyVkcQyLb/CaKbtNVI2jT6h01LtoaXLtL8XdbKWxXIfDf03pAcAnGOUTwuP
+pIMWnWypd6C6OKYJc2MO/QdP+xnyrFXPn1YlKEudEVCPKuc2Ymtd75OPit6OgZul
+JPUehOSM//UxEHsCAMZ+0AAzAnEkRdqSTtVz2L8uLDSYNndoCm49+p0GCdGFSmkO
+SHED1sw5Qbq0AcGmIynp+c56z0Yk7QPmLKWhmnXQPt1+TjGZQFsjoty26zlYdA7O
+g9PkszUz1AiNk/qPMq2fF7pOEOtnPJbVVLdDqTOoNJsjw/RVNwIDAQABo1MwUTAd
+BgNVHQ4EFgQUrFYvh5JA7/TTRZWrXqFqZZdRJuYwHwYDVR0jBBgwFoAUrFYvh5JA
+7/TTRZWrXqFqZZdRJuYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AgEANXx7/cpj3ybrTq7WqPFoklnuppCZc6SYYrfGNAay6vYopYVscxwsGemkAnQY
+K9ezCvMlvGw2wAMB+Ple0SiejnIXFtfv49Exti6HCnLEI+uRkuDWYR8dn3TeIj5u
+OziJIMBWhzgRaM2uuQeXh/tF0RDindeKIec5gCRaUpQvyi9zVwS9hSejnnREFnbK
+oDjLqsNVzXB3nORxGTDWYWSM7EvIvYpBIeUFmHZ0cdcWyVFc/umlPauCcKEsyHEm
+mjmTNF3oxpsoQysjaPVsXtG0cpAw+1u8IcIoSf9R1ncXIHNAYjC4TQySofs0Pgn9
+BdKhmrFLvxhhG9tklin7z+mlhofl+kIrylGsXEkJgxY7PuHRJnHxjUVM9PdGH3Ce
+6IwbaDA7OPfdQgJEWQtzO5Y3hAhLp07RXAhabxCUc8T9pZRXMed6poZFqw+YTGQ4
+ZasqYpCVx8oxuucRV8Cz9nbW0l0yyfbvWB0yCAn+Jq5gBudkxx4gFtQQHsSDQ+77
+QxrY1acflfH3E8bhs05caYLWV21YxJdNek5eHM3IPWJxi308w0oekcSLEcvP6gke
+jwnbewzAwTLFft+zprt7JV7A7BxK1dleZUzk6CqNAo+D91E5NjYKwSXB/7r5JBpr
+y6RRX1nxu9BL7bADWENbcLppUbPST01sgkm9lQKt7o+vSbI=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQD0YSNv4UxBFFWT
+5SOXWtqzfNQI8RsMSQFh2qEHKyQ4oDoBv3mO1iwM143JpCE4t/960A/XdbXsVYdH
+h1YRcBZu+EKmj6i0od+JRit/gBat+brNBWH11oAf9hhqSBgl0GMD5HgcYIkRYGkh
+LUNllTFEGh79uPPBLLrCmdnZG9G8sNVeaPp74iyXsY57mw9IoD5b3t21IwMIBzTv
+AcsRKWWGi/s0ZSN4srERRjA3CWqF8srv28QciXXlpe2aMRH1TC/Ec/UuT6SUtAC/
+G7J6oQ8KS8TwyGafZoz28z4BlfVRw9WkNv+Atxy1zvZU3JQCPecVV3Y3mD5zyXSQ
+jp54ukcEd3pBE7kqLVrs4IddS/bifJl564Z6WlFNblbi8g46dQyVkcQyLb/CaKbt
+NVI2jT6h01LtoaXLtL8XdbKWxXIfDf03pAcAnGOUTwuPpIMWnWypd6C6OKYJc2MO
+/QdP+xnyrFXPn1YlKEudEVCPKuc2Ymtd75OPit6OgZulJPUehOSM//UxEHsCAMZ+
+0AAzAnEkRdqSTtVz2L8uLDSYNndoCm49+p0GCdGFSmkOSHED1sw5Qbq0AcGmIynp
++c56z0Yk7QPmLKWhmnXQPt1+TjGZQFsjoty26zlYdA7Og9PkszUz1AiNk/qPMq2f
+F7pOEOtnPJbVVLdDqTOoNJsjw/RVNwIDAQABAoICAFQWEHFwniJOctu+ni54DoRv
+0S16eIoTnKwk4/9pcr8hMpRjY9ooC2Qnzxuwo9W2SeviJ3FFiWFWVIPiJ1U8shtg
+xN421B/DCv1a7aCjXCpkoUfmMDy8n71fmisHv3dTap5uQH3TIZafC81km6oaWadL
+LZqzxvuS/nfzJCg2EEbSubgQew+hVSKk75fMVMfyi0JlPvgSofpjMG2EunCfQ9W1
+2KvAmSHwqkh9VB272bFZR0ac4a/IBI8ONuE1PS6gFleMZrKWqNSZ0x/u0cysGla6
+l3saGWWDjcFzFMTK6mxF6/7jBasuVmygV9X/R9Y1oouPWpfqhKKmkLskt5Zm0gQl
+R/pcpI8N40vtStYppX6kXFzeTt+XA46SHsGzrD+CLReH3GEH3u6Q9mNc7/A95rOg
+uk6VekNNPOZ3FVXnJJeqrT+NixUlmi0bnpN1NH9G3LhJ4rzkXykT/nadu2KmsViW
+iRZ+T79Vm2X4vL8g9BXGPhXE2uwV5HVkkNuzp29/HX66t8xxBwDASGFPLn19EuAi
+MA8YlKuKpDLCb6eflkEVXkBY+TZJT76yF5IE3n8h2n/F6uO8qCfvP0d37WVI/Rx6
+izX/7ftFnjbshWX5davNyGu2gicPcIvHB0f8SFLK6DflWeSr2aC884nrG59YT/0f
+3qGOKwlULLKimNC3D28hAoIBAQD+1LGPfPkpprRfJ1EGzMV/gBHgwW0cWdFNikVg
+VRTgUQoxdN0eOoLtGbyOe+O7Q/HntiC8vj8ntezi4sYbJTerFmkV/Gu8VwYUJza1
+koz8yAgmmaJHHO/zKWkqD+q4jDYUpzE0I8PhH6nuwuWX86S+J4/fS3l6iWKWefcj
+9yOmCim16NW72hdhlbshkl8Om4IVe8i5CbeZ56QI/ExO2CGOz4sZXyUYf7IMZHdE
+4H7q8X8gQ+ZSiKyaj8/9mPq6teW3EwJchR1rjNdkOZVdn55huU9QuMIbf8z4thrb
+JYV98CTZA/M6uh2fMGngvil+LrJVCkKvrJ5E9kciVF7RO0FfAoIBAQD1gCtbeBF4
+1erNRQj48oci+47WZFoT0CRDV2Qe1gOnnc3Yjp+8IAYQCDPcXG3dI7KslXl0kp+D
+7cO+BZef5VYyDXLFl++HZLBQDoVwFjMse9yGSD+S8gyAHer7Smaqs7UxJkklK+m+
+NMlslifXKp7aMOoGjlM42umnQ177++xmr7oLA06VaZwAj1Aj3LB4J9uTS1CJeDl+
+n5+tcaBO6Lf9brnhvwigDsbkfRt4ccyY6rc4QKIM568LgXsmOe/OyUP1wda4zyCr
+3K62q/bwZIKPwIhLK+hxGCp3vz/Mu7umN1XkuVXT2xqniNiApT7MXsg0lx1RgLfh
+6YUilb7XhUMpAoIBAQCKla8uwp9aeG+VY/NbyFcL3OFcIrUs+uepzK2oEv00dL4f
+YVezTczQFvQFZPjXab8P7WtmWexMs1JtnThxoM7ie2CQ9WK93XHP2feVzWphOoO8
+QkcPd3xC+F1Z46gZzx7GIprOqTiooKiw0Us9VOJeC3Ph0tDww/Bat1+hLpEzhkli
+xYofDB81EdHgExMhBY88EcJ6Zv9zcpcxz8vMARxW5yXVmXm6FhAFT0nRqmk7ajRh
+nquObQe5UsahOuX4Tl3sLylUmYwDZmfo+Kvza5Adw0KQOrpNbDZTd+2pCoHLmKLh
+ZpWLdZYQcarS73fvSIPxXZAgq7ay+GB9GfcqwJfvAoIBACvFl3VumgbmdT/2MBxa
++bdGDPiy2dCwitaq7UIGPI6VN0+GVnqvZwVSwRRoMnp8U+4rlIUxY1mdegoWaytq
+M40nErCiX2XPkRQlEquieatTxkT2+sbTe2EYdH4rjNSgyAykW+RRyRJNzSAcQaw+
+gCY9FGzo0XPQrFpTS8s35rWEXXJ7O3auZs8+vjY2sgwqZx8DDbAFDJNEGK9PFBsd
+qTh5lpDmg74uBE5W9B/sgmM9bj+MXphYcsBlbLSrHdPL1N8rmYJIA/ZAmbIeRSAl
+e5Xv6R0mDgKkIWZKZjC1xEZllV37oY7tgPogDyIY1HKR77ZYvzR5889G2KMK+gTp
+UMkCggEAHIUUQ3lh2tMvLajJQPiDZmHSoNh/EVwLP/y35CjEwgrHRv3pk4j7/ZG2
+sIN4WWK2hftks4WQdk5Jc0y9++4uaWvgQQzu3Jb4sN6dx0r/C2A4sG96pgT835fQ
+UwtMVZm9l/eolB7XVdcvJogJk9khmvyIlq7cZx53YMlOw0b/hG3rNdOHPYgL0GTT
+v0X5v38X7GRBJQvsl2RLoolWAn8cInk7dsYG5c5Lvd7sJWJI/g/+JUZ633G7MYDK
+8Ez2S4PbV9KrqB/Zyh8ZjS95HyQm4xUhbcOJRQc8qe0kZsx0JbaR3K0D8jmgnmGj
+qFO5h9/cDU3/Kzwsv+Ki3hXJKN2BeA==
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/pluginTest/client_context_dump/ssl/two.com.pem b/tests/gold_tests/pluginTest/client_context_dump/ssl/two.com.pem
new file mode 100644
index 0000000..1d6a917
--- /dev/null
+++ b/tests/gold_tests/pluginTest/client_context_dump/ssl/two.com.pem
@@ -0,0 +1,83 @@
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0ICFDcEKGSZ6+X7UeFhcJSF3MaAdSFaMA0GCSqGSIb3DQEBCwUAMFEx
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJJTDESMBAGA1UEBwwJQ2hhbXBhaWduMQ8w
+DQYDVQQKDAZBcGFjaGUxEDAOBgNVBAMMB2Zvby5jb20wHhcNMTkwNTIyMjAzOTQz
+WhcNMTkwNjIxMjAzOTQzWjCBgTEQMA4GA1UEAwwHdHdvLmNvbTEaMBgGCSqGSIb3
+DQEJARYLc3NsQHR3by5jb20xDzANBgNVBAoMBkFwYWNoZTEMMAoGA1UECwwDVHdv
+MRIwEAYDVQQHDAlDaGFtcGFpZ24xETAPBgNVBAgMCElsbGlub2lzMQswCQYDVQQG
+EwJVUzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKsQcVF2TbO+VtT+
+YbBjfwrO7+5X5hs2UY4tV0NBR3LlTmM1/qlql0ayjja2Te87ULZZ1gAyL1InnEvI
+HKLPIWaUknKM2LG9y6j+ZToGpw6JdSXuDVgd7UC1SY//XIRVW0FNTWkP/aV7ScUE
+4SvPuCjBCnEvddUXfc2DlskJGBDnHQ4P07yc+GnFY2chNHSngQ7ctoazvi0dEi56
+ViXgg53IUXib6veGtNfn//rk9CpQY7AM3qKJsqoKUHhBKPFXlA5z25EkBuPVMbhB
+yUyrHGy377nE7FZFKSziHEA5XUS1F+ITbXxTgaPchsoU02spvlHHKSDNEFvc1GDl
+n5pPQXJI34lQryjtvrneSt7mz05qKf1DNcN70ZMKkBDMmsn3wt/+TXi2w3vKkyxD
+pJHT4P6P7d1kWnp3nOworl53ybcc/DGcBftu2KiMKQ0aF1zjRwwM1uCizg41IxNw
+nZYsQPL47lE93kI9OZ0NjhorQI9n7Cs6AS/sRDZSJrlEFQXcidSkfGOaLj0Dhrd0
+UrahIk3r5vhtLw1BL0NQSPeJkwJQ6zkbavhKDvN4Odol2YUWdQAsPl5BJypts/CT
+UOQ20FVyQQgXj9GYyJrwwrSm8YF48KrItdOGF9iHyGdeVn3BTMWaokCyWfcAeK1k
+CwhCyAT/HNTorSHDSNL1TL45LsdXAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAKiy
+DMDfNxNmn5EnAnzwLem/nqNiPKzsjnkYChBJXVTy5uuOetjigbi7VUPPiFUHZ3+9
+kyXqRBRaGjRf4IhgepFZmpuvuxDQUuVK5XycWAme1NOerWX+y/tAxQM+exWEGAaR
+KTqLmiz0fSF5Plcg71a7GS2S4drmnn6vGkDX5aJiCWJsFE694zv8F7u/VRJSF+bj
+TY48tSvsGOmGzK6T9BriCaBRIC4kX/6l0zGM6CWd6pdGwT8JflK4jWoW+m8nBFqN
+1QuOC3l+F4eBGCDqxyh8lZkssf2ZRxmX2W6nBFEZ7NUR+XnAUZSgpvZwULm/fMJP
+gr5I8OXc8oLIIPSWRagm7O3Plyw4EeVEmrcBNoRMs/7wfQz4H+Hfct/cle8O0cWT
+censViZ2JRs9BM9JHz0LnW8QhcHD8+Qu9QAJQDw3ILrhjuiMjEnT0a10ILXZftQv
+QQOxsKlwxO5dcUN31JZYCx3sr83ApGEGDksIRiUDjlwO5k1jBD2Co7K4oVFgC6T1
+9nOPC36R3XcAZ7ed3XXyq5WDFC6cdjs5a+V9ScSVzaAwziHFKIeVn7Hy4pncPd5/
+yafdgQzE7o3DO94chOzb6lJn7jyERuJAUWXsRX2esT9fsoNQDJhZ9tdFNq+JVVwO
+jypGgJ0QTy7rVlu+HEgMOt9WGOjYNb0BRN0at2+d
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+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
+k7+f38jMk3EvPFZ5xgE1uAtgf8IFEeU=
+-----END PRIVATE KEY-----