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

cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit fdf194ef809148c12b8a3509fba3cc0ebb3f89b1
Author: Leif Hedstrom <[email protected]>
AuthorDate: Wed Jul 9 15:23:17 2025 -0500

    Cripts: Adds some certificate introspection (#12320)
    
    * Cripts: Adds some certificate introspection
    
    * Use the Cripts mixin string_view type
    
    * Fixes autest for Cripts, and adds some more tests
    
    (cherry picked from commit 671f791ab513b9245430765520b22fffa826ac99)
    (cherry picked from commit 2d13cff2028a4e6bbf075662b218b925069a8e48)
---
 doc/developer-guide/cripts/cripts-certs.en.rst     | 186 ++++++
 .../cripts/cripts-connections.en.rst               |  25 +
 doc/developer-guide/cripts/index.en.rst            |   3 +-
 include/cripts/Certs.hpp                           | 633 +++++++++++++++++++++
 include/cripts/ConfigsBase.hpp                     |   1 +
 include/cripts/Connections.hpp                     | 102 +++-
 include/cripts/Matcher.hpp                         |   6 +-
 include/cripts/Preamble.hpp                        |   1 +
 src/cripts/CMakeLists.txt                          |   1 +
 src/cripts/Certs.cc                                | 269 +++++++++
 src/cripts/Connections.cc                          |  14 +
 tests/gold_tests/cripts/cripts.test.py             |  30 +-
 tests/gold_tests/cripts/files/basic.cript          |   8 +
 tests/gold_tests/cripts/gold/certs_cript.gold      |  17 +
 tools/cripts/compiler.sh                           |   6 +-
 15 files changed, 1281 insertions(+), 21 deletions(-)

diff --git a/doc/developer-guide/cripts/cripts-certs.en.rst 
b/doc/developer-guide/cripts/cripts-certs.en.rst
new file mode 100644
index 0000000000..7c9522e842
--- /dev/null
+++ b/doc/developer-guide/cripts/cripts-certs.en.rst
@@ -0,0 +1,186 @@
+.. 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
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _cripts-certs:
+
+Certificates
+************
+
+Cripts provides a set of convenient classes for introspection into the various
+TLS certificates that are used. These include both the server certificates used
+to establish a TLS connections, as well as any client certificates used for
+mutual TLS.
+
+In the current implementation, these objects only work on X509 certificates as
+associated with the ``client`` and ``server`` connections. Let's start off with
+a simple example of how to use these objects:
+
+.. code-block:: cpp
+
+   do_send_response()
+   {
+     if (client.connection.IsTLS()) {
+       const auto tls = cripts::Certs::Server(client.connection);
+
+       client.response["X-Subject"] = tls.subject;
+       client.response["X-NotBefore"] = tls.notBefore;
+       client.response["X-NotAfter"] = tls.notAfter;
+     }
+   }
+
+.. _cripts-certs-objects:
+
+Objects
+=======
+
+There are two types of objects for the certificates:
+
+=================================   
===============================================================
+Object                              Description
+=================================   
===============================================================
+``cripts::Certs::Server``           The certificate used on the connection for 
TLS handshakes.
+``cripts::Certs::Client``           The mutual TLS (mTLS) certificate used on 
the connection.
+=================================   
===============================================================
+
+This combined with the two kinds of connections, 
``cripts::Client::Connection`` and
+``cripts::Server::Connection`` yields a total of four possible certificate 
objects. For example, to
+access the client mTLS provided certificate on a client connection, you would 
use:
+
+.. code-block:: cpp
+
+   const auto tls = cripts::Certs::Client(cripts::Client::Connection::Get());
+
+Or if you are using the convenience wrappers:
+
+.. code-block:: cpp
+
+   const auto tls = cripts::Certs::Client(client.connection);
+
+.. _cripts-certs-x509:
+
+X509 Values
+===========
+
+As part of the certificate objects, there are a number of values that can be
+accessed. These values are all based on the X509 standard and can be used to
+introspect the certificate. The following values are available:
+
+=================================   
===============================================================
+Value                               Description
+=================================   
===============================================================
+``certificate``                     The raw X509 certificate in PEM format.
+``signature``                       The raw signature of the certificate.
+``subject``                         The subject of the certificate.
+``issuer``                          The issuer of the certificate.
+``serialNumber``                    The serial number of the certificate.
+``notBefore``                       The date and time when the certificate is 
valid from.
+``notAfter``                        The date and time when the certificate is 
valid until.
+``version``                         The version of the certificate.
+=================================   
===============================================================
+
+.. _cripts-certs-san:
+
+SAN Values
+==========
+
+We've made special provisions to access the Subject Alternative Name (SAN) 
values
+of the certificate. These values are often used to identify the hostnames or IP
+addresses that the certificate is valid for. Once you have the certificate 
object,
+you can access the SAN values as follows:
+
+====================   ===============   
===============================================================
+Field                  X509 field        Description
+====================   ===============   
===============================================================
+``.san``               na                An array of tuples with type and 
``string_view`` of  all SANs.
+``.san.email``         ``GEN_EMAIL``     An array of ``string_view`` of email 
addresses.
+``.san.dns``           ``GEN_DNS``       An array of ``string_view`` of DNS 
names.
+``.san.uri``           ``GEN_URI``       An array of ``string_view`` of URIs.
+``.san.ipadd``         ``GEN_IPADD``     An array of ``string_view`` of IP 
addresses.
+====================   ===============   
===============================================================
+
+.. note::
+
+   These arrays are empty if no SAN values are present in the certificate. We 
also populate these
+   arrays lazily, but they are kept for the lifetime of the certificate 
object. This means that
+   you can access these values multiple times without incurring additional 
overhead. Remember
+   that you can use the ``cripts::Net::IP`` class to convert the IP addresses 
into proper
+   IP address objects if needed.
+
+
+Odds are that you will want to use one of the specific array values, such as 
``.san.uri``, which is
+easily done in a simple loop:
+
+.. code-block:: cpp
+
+   do_remap()
+   {
+     if (client.connection.IsTLS()) {
+       const auto tls = cripts::Certs::Server(client.connection);
+
+       for (auto uri : tls.san.uri) {
+         // Check the URI string_view
+       }
+     }
+   }
+
+
+You can of course loop over all SAN values, which is where the type of the 
value would come in handy,
+and why this is an array of tuples. In this scenario, you would iterate over 
the tuples like this:
+
+.. code-block:: cpp
+
+   do_remap()
+   {
+     if (client.connection.IsTLS()) {
+       const auto tls = cripts::Certs::Server(client.connection);
+
+       for (const [type, san] : tls.san) {
+         if (type == cripts::Certs::SAN::URI) {
+           // Check the URI string here
+         } else if (type == cripts::Certs::SAN::DNS) {
+           // Check the DNS string here
+         }
+       }
+     }
+   }
+
+In addition to traditional C++ iterators, you can also access SAN values by 
index. Make sure
+you check the size of the array first, as accessing an out-of-bounds index 
will give you an
+empty tuple. Prefer the iterator above, unless you know you want to access a 
specific element.
+
+Example of an alternative way to loop over all SAN values:
+
+.. code-block:: cpp
+
+   do_remap()
+   {
+     if (client.connection.IsTLS()) {
+       const auto tls = cripts::Certs::Server(client.connection);
+
+       size_t san_count = tls.san.size();
+
+       for (size_t i = 0; i < san_count; ++i) {
+         const auto [type, san] = tls.san[i];
+         // Process the type and san as needed
+       }
+     }
+   }
diff --git a/doc/developer-guide/cripts/cripts-connections.en.rst 
b/doc/developer-guide/cripts/cripts-connections.en.rst
index 196335c8af..9137afe76f 100644
--- a/doc/developer-guide/cripts/cripts-connections.en.rst
+++ b/doc/developer-guide/cripts/cripts-connections.en.rst
@@ -71,6 +71,9 @@ Method                    Description
 ``LocalIP()``             The server (ATS) IP address of the connection.
 ``IsInternal()``          Returns ``true`` or ``false`` if the connection is 
internal to ATS.
 ``Socket()``              Returns the raw socket structure for the connection 
(use with care).
+``IsTLS()``               Returns ``true`` if the connection is a TLS 
connection.
+``ClientCert()``          Returns the client certificiate (mTLS) for the 
connection (if any).
+``ServerCert()``          Returns the server certificate for the connection, 
if it's a TLS connection.
 =======================   
=========================================================================
 
 The ``IP()`` and ``LocalIP()`` methods return the IP address as an object. In 
addition to the
@@ -102,6 +105,7 @@ Variable                   Description
 ``pacing``                Configure socket pacing for the connection.
 ``dscp``                  Manage the DSCP value for the connection socket.
 ``mark``                  Manage the Mark value for the connection socket.
+``tls``                   Access to the TLS object for the connection.
 =======================   
=========================================================================
 
 For other advanced features, a Cript has access to the socket file descriptor, 
via the ``FD()``
@@ -165,3 +169,24 @@ Method                    Description
 .. note::
    All methods return string values. These are methods and not fields, so they 
must be called as
    functions.
+
+.. _cripts-connections-tls:
+
+TLS
+===
+
+The ``tls`` variable provides access to the TLS object for the session. This 
object
+provides access to the TLS certificate and other TLS related information. The 
following methods
+are available:
+
+=======================   
=========================================================================
+Method                    Description
+=======================   
=========================================================================
+``Connection``            Returns the connection object for the TLS connection.
+``GetX509()``             Returns the X509 certificate for the connection, an 
OpenSSL object.
+=======================   
=========================================================================
+
+Both of these can return a null pointer, if the connection is not a TLS 
connection or
+if the certificate is not available. The ``GetX509()`` method can take an 
optional
+boolean argument, which indicates if the certificate should be for a mutual 
TLS connection. The
+default is ``false``, which means that the server certificate for the 
connection will be returned.
diff --git a/doc/developer-guide/cripts/index.en.rst 
b/doc/developer-guide/cripts/index.en.rst
index 03fa6da08f..2e1dbe8ee4 100644
--- a/doc/developer-guide/cripts/index.en.rst
+++ b/doc/developer-guide/cripts/index.en.rst
@@ -31,8 +31,9 @@ Cripts
    cripts-urls.en
    cripts-headers.en
    cripts-connections.en
-   cripts-matcher.en
+   cripts-certs.en
    cripts-crypto.en
+   cripts-matcher.en
    cripts-misc.en
    cripts-bundles.en
    cripts-convenience.en
diff --git a/include/cripts/Certs.hpp b/include/cripts/Certs.hpp
new file mode 100644
index 0000000000..f19fb8f41b
--- /dev/null
+++ b/include/cripts/Certs.hpp
@@ -0,0 +1,633 @@
+/*
+  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.
+*/
+#pragma once
+
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/bio.h>
+#include <vector>
+#include <algorithm>
+
+#include "cripts/Lulu.hpp"
+#include "cripts/Connections.hpp"
+
+namespace cripts::Certs
+{
+// These *must* match the values in x509v3.h.
+enum class SAN : std::uint8_t {
+  OTHER = GEN_OTHERNAME,
+  EMAIL = GEN_EMAIL,
+  DNS   = GEN_DNS,
+  URI   = GEN_URI,
+  IPADD = GEN_IPADD,
+};
+
+class String : public cripts::StringViewMixin<String>
+{
+  using super_type = cripts::StringViewMixin<String>;
+  using self_type  = String;
+
+public:
+  virtual ~String() = default;
+
+  operator cripts::string_view() const { return GetSV(); }
+
+  self_type &
+  operator=(const cripts::string_view str) override
+  {
+    _setSV(str);
+
+    return *this;
+  }
+
+  using super_type::StringViewMixin;
+
+}; // Class cripts::Certs::String
+
+} // namespace cripts::Certs
+
+namespace detail
+{
+class CertBase
+{
+  using self_type = CertBase;
+
+  using X509 = struct x509_st;
+  using BIO  = struct bio_st;
+
+public:
+  class X509Value
+  {
+    using self_type = X509Value;
+
+  public:
+    explicit X509Value(CertBase *owner) : _owner(owner) {}
+
+    virtual ~X509Value() = default;
+
+    X509Value()                  = delete;
+    X509Value(const self_type &) = delete;
+
+    self_type &operator=(const self_type &) = delete;
+    self_type &operator=(self_type &&)      = delete;
+
+    cripts::string_view
+    GetSV() const
+    {
+      _load();
+      return _value;
+    }
+
+    operator cripts::string_view() const { return GetSV(); }
+
+  protected:
+    void _update_value() const;
+
+    virtual void
+    _load() const
+    {
+      if (!_bio) {
+        _bio.reset(BIO_new(BIO_s_mem()));
+      }
+    }
+
+    void _load_name(X509_NAME *(*getter)(const X509 *)) const;
+    void _load_integer(ASN1_INTEGER *(*getter)(X509 *)) const;
+    void _load_long(long (*getter)(const X509 *)) const;
+    void _load_time(ASN1_TIME *(*getter)(const X509 *)) const;
+
+    CertBase                                         *_owner = nullptr;
+    mutable std::unique_ptr<BIO, decltype(&BIO_free)> _bio{nullptr, BIO_free};
+    mutable cripts::Certs::String                     _value;
+    mutable bool                                      _ready = false;
+  }; //  End class CertBase::X509Value
+
+  // Here comes all the various X509 value fields that we support.
+  class Certificate : public X509Value
+  {
+    using self_type  = Certificate;
+    using super_type = X509Value;
+
+  public:
+    explicit Certificate(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void _load() const override;
+
+  }; // End class CertBase::Certificate
+
+  class Signature : public X509Value
+  {
+    using self_type  = Signature;
+    using super_type = X509Value;
+
+  public:
+    explicit Signature(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void _load() const override;
+
+  }; // End class CertBase::Signature
+
+  class Subject : public X509Value
+  {
+    using self_type  = Subject;
+    using super_type = X509Value;
+
+  public:
+    explicit Subject(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_name(X509_get_subject_name);
+    }
+
+  }; // End class CertBase::Subject
+
+  class Issuer : public X509Value
+  {
+    using self_type  = Issuer;
+    using super_type = X509Value;
+
+  public:
+    explicit Issuer(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_name(X509_get_issuer_name);
+    }
+
+  }; // End class CertBase::Issuer
+
+  class SerialNumber : public X509Value
+  {
+    using self_type  = SerialNumber;
+    using super_type = X509Value;
+
+  public:
+    explicit SerialNumber(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_integer(X509_get_serialNumber);
+    }
+
+  }; // End class CertBase::SerialNumer
+
+  class NotBefore : public X509Value
+  {
+    using self_type  = NotBefore;
+    using super_type = X509Value;
+
+  public:
+    explicit NotBefore(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_time(X509_get_notBefore);
+    }
+
+  }; // End class CertBase::NotBefore
+
+  class NotAfter : public X509Value
+  {
+    using self_type  = NotAfter;
+    using super_type = X509Value;
+
+  public:
+    explicit NotAfter(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_time(X509_get_notAfter);
+    }
+
+  }; // End class CertBase::NotAfter
+
+  class Version : public X509Value
+  {
+    using self_type  = Version;
+    using super_type = X509Value;
+
+  public:
+    explicit Version(CertBase *owner) : super_type(owner) {}
+
+  protected:
+    void
+    _load() const override
+    {
+      _load_long(X509_get_version);
+    }
+
+  }; // End class CertBase::SerialNumer
+
+  class SAN
+  {
+    using self_type = SAN;
+
+    class SANBase
+    {
+      using self_type = SANBase;
+
+    public:
+      using Container = std::vector<std::string>;
+
+      explicit SANBase(SAN *owner, cripts::Certs::SAN san_id) : 
_san_id(san_id), _owner(owner) {}
+
+      virtual ~SANBase() = default;
+
+      SANBase()                = delete;
+      SANBase(const SANBase &) = delete;
+      SANBase(SANBase &&)      = delete;
+
+      self_type &operator=(const self_type &) = delete;
+      self_type &operator=(self_type &&)      = delete;
+
+      [[nodiscard]] cripts::Certs::SAN
+      sanType() const
+      {
+        return _san_id;
+      }
+
+      class Iterator
+      {
+        using self_type = Iterator;
+
+      public:
+        using iterator_category = std::forward_iterator_tag;
+        using base_iterator     = Container::const_iterator;
+        using value_type        = cripts::Certs::String;
+        using reference         = cripts::Certs::String;
+
+        explicit Iterator(base_iterator iter) : _iter(iter) {}
+
+        ~Iterator() = default;
+
+        Iterator(const Iterator &)            = default;
+        Iterator &operator=(const Iterator &) = default;
+        Iterator(Iterator &&)                 = default;
+        Iterator &operator=(Iterator &&)      = default;
+
+        [[nodiscard]] reference
+        operator*() const
+        {
+          return {*_iter};
+        }
+
+        self_type &
+        operator++()
+        {
+          ++_iter;
+          return *this;
+        }
+
+        [[nodiscard]] bool
+        operator!=(const self_type &other) const
+        {
+          return _iter != other._iter;
+        }
+
+        [[nodiscard]] base_iterator
+        base_iter() const
+        {
+          return _iter;
+        }
+
+      private:
+        base_iterator _iter;
+      }; // End class SAN::SANBase::Iterator
+
+      void
+      ensureLoaded() const
+      {
+        if (!_ready) {
+          _load();
+          _ready = true;
+        }
+      }
+
+      [[nodiscard]] Iterator
+      begin() const
+      {
+        ensureLoaded();
+        return Iterator(_data.begin());
+      }
+
+      [[nodiscard]] Iterator
+      end() const
+      {
+        return Iterator(_data.end());
+      }
+
+      [[nodiscard]] Container &
+      Data() const
+      {
+        ensureLoaded();
+        return _data;
+      }
+
+      [[nodiscard]] size_t
+      size() const
+      {
+        ensureLoaded();
+        return _data.size();
+      }
+
+      [[nodiscard]] size_t
+      Size() const
+      {
+        return size();
+      }
+      [[nodiscard]] cripts::Certs::String
+      operator[](size_t index) const
+      {
+        ensureLoaded();
+        if (index >= _data.size()) {
+          return {""};
+        }
+        return {_data[index]};
+      }
+
+      [[nodiscard]] cripts::string Join(const char *delim = ",") const;
+
+    protected:
+      void _load() const;
+
+      mutable Container  _data;
+      mutable bool       _ready  = false;
+      cripts::Certs::SAN _san_id = cripts::Certs::SAN::OTHER;
+      SAN               *_owner  = nullptr;
+
+    }; // End class SAN::SANBase
+
+  public:
+    template <cripts::Certs::SAN ID> class SANType : public SANBase
+    {
+      using super_type = SANBase;
+      using self_type  = SANType;
+
+    public:
+      explicit SANType(SAN *owner) : SANBase(owner, ID) {}
+
+      ~SANType() override = default;
+
+      SANType()                = delete;
+      SANType(const SANType &) = delete;
+      SANType(SANType &&)      = delete;
+
+      self_type &operator=(const self_type &) = delete;
+      self_type &operator=(self_type &&)      = delete;
+    }; // End class SAN::SANType
+
+    explicit SAN(CertBase *owner) : email(this), dns(this), uri(this), 
ipadd(this), _owner(owner) {}
+
+    ~SAN() = default;
+
+    SAN()            = delete;
+    SAN(const SAN &) = delete;
+    SAN(SAN &&)      = delete;
+
+    self_type &operator=(const self_type &) = delete;
+    self_type &operator=(self_type &&)      = delete;
+
+    SAN::SANType<cripts::Certs::SAN::EMAIL> email;
+    SAN::SANType<cripts::Certs::SAN::DNS>   dns;
+    SAN::SANType<cripts::Certs::SAN::URI>   uri;
+    SAN::SANType<cripts::Certs::SAN::IPADD> ipadd;
+
+    class Iterator
+    {
+    public:
+      using iterator_category = std::forward_iterator_tag;
+      using value_type        = std::tuple<cripts::Certs::SAN, 
cripts::Certs::String>;
+      using reference         = value_type; // Return by value instead of 
const reference
+      using base_iterator     = SANBase::Container::const_iterator;
+
+      explicit Iterator(std::nullptr_t) : _ended(true) {}
+      explicit Iterator(const SAN *san) : _san(san) { _advance(); }
+
+      Iterator()                            = default;
+      Iterator(const Iterator &)            = default;
+      Iterator &operator=(const Iterator &) = default;
+      Iterator(Iterator &&)                 = default;
+      Iterator &operator=(Iterator &&)      = default;
+
+      ~Iterator() = default;
+
+      [[nodiscard]] reference
+      operator*() const
+      {
+        return {_current};
+      }
+
+      Iterator &
+      operator++()
+      {
+        if (_ended) {
+          return *this;
+        }
+
+        ++_iter;
+        if (_iter == _san->_sans[_index - 1]->Data().end()) {
+          _advance();
+        } else {
+          _update_current();
+        }
+
+        return *this;
+      }
+
+      [[nodiscard]] bool
+      operator!=(const Iterator &other) const
+      {
+        if (_ended && other._ended) {
+          return false;
+        }
+
+        return _san != other._san || _index != other._index || _iter != 
other._iter;
+      }
+
+      [[nodiscard]] bool
+      operator==(const Iterator &other) const
+      {
+        if (_ended && other._ended) {
+          return true;
+        }
+        return _san == other._san && _index == other._index && _iter == 
other._iter;
+      }
+
+    private:
+      void
+      _update_current() const
+      {
+        if (_san && !_ended) {
+          _current = std::make_tuple(_san->_sans[_index - 1]->sanType(), 
cripts::Certs::String(*_iter));
+        }
+      }
+
+      void _advance();
+
+      mutable value_type _current; // The current value of the iterator
+      base_iterator      _iter;    // The current iterator within the SAN types
+      bool               _ended = false;
+      const SAN         *_san   = nullptr;
+      size_t             _index = 0;
+
+    }; // End class CertBase::SAN::Iterator
+
+    [[nodiscard]] Iterator
+    begin() const
+    {
+      auto it = Iterator(this);
+
+      return it;
+    }
+
+    [[nodiscard]] Iterator
+    end() const
+    {
+      Iterator it{nullptr};
+
+      return it;
+    }
+
+    [[nodiscard]] size_t
+    size() const
+    {
+      size_t total = 0;
+
+      for (const auto *san : _sans) {
+        total += san->Size();
+      }
+      return total;
+    }
+
+    [[nodiscard]] size_t
+    Size() const
+    {
+      return size();
+    }
+
+    [[nodiscard]] Iterator::value_type operator[](size_t index) const;
+
+  private:
+    const std::array<const SANBase *, 4> _sans = std::to_array<const SANBase 
*>({&email, &dns, &uri, &ipadd});
+    CertBase                            *_owner;
+
+  }; // End class CertBase::SAN
+
+public:
+  CertBase(detail::ConnBase &conn)
+    : certificate(this),
+      signature(this),
+      subject(this),
+      issuer(this),
+      serialNumber(this),
+      notBefore(this),
+      notAfter(this),
+      version(this),
+      san(this),
+      _conn(&conn)
+  {
+  }
+
+  CertBase()                  = delete;
+  CertBase(const self_type &) = delete;
+
+  self_type &operator=(const self_type &) = delete;
+  self_type &operator=(self_type &&)      = delete;
+
+  Certificate  certificate;
+  Signature    signature;
+  Subject      subject;
+  Issuer       issuer;
+  SerialNumber serialNumber;
+  NotBefore    notBefore;
+  NotAfter     notAfter;
+  Version      version;
+  SAN          san;
+
+protected:
+  X509             *_x509 = nullptr;
+  detail::ConnBase *_conn = nullptr;
+
+}; // End class detail::CertBase
+
+template <bool IsMutualTLS> class Cert : public detail::CertBase
+{
+  using self_type  = Cert<IsMutualTLS>;
+  using super_type = detail::CertBase;
+
+public:
+  explicit Cert(detail::ConnBase &conn) : super_type(conn) { _x509 = 
conn.tls.GetX509(IsMutualTLS); }
+}; // End class Cert
+
+} // namespace detail
+
+namespace cripts::Certs
+{
+using Client = detail::Cert<true>;
+using Server = detail::Cert<false>;
+
+} // namespace cripts::Certs
+
+namespace fmt
+{
+
+template <> struct formatter<cripts::Certs::SAN> {
+  constexpr auto
+  parse(format_parse_context &ctx) -> decltype(ctx.begin())
+  {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto
+  format(cripts::Certs::SAN san, FormatContext &ctx) const -> 
decltype(ctx.out())
+  {
+    return fmt::format_to(ctx.out(), "{}", static_cast<int>(san));
+  }
+};
+
+template <> struct formatter<cripts::Certs::String> {
+  constexpr auto
+  parse(format_parse_context &ctx) -> decltype(ctx.begin())
+  {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto
+  format(const cripts::Certs::String &str, FormatContext &ctx) const -> 
decltype(ctx.out())
+  {
+    return fmt::format_to(ctx.out(), "{}", str.GetSV());
+  }
+};
+
+} // namespace fmt
diff --git a/include/cripts/ConfigsBase.hpp b/include/cripts/ConfigsBase.hpp
index 74174ef428..6eba3628dc 100644
--- a/include/cripts/ConfigsBase.hpp
+++ b/include/cripts/ConfigsBase.hpp
@@ -22,6 +22,7 @@
 #include <unordered_map>
 
 #include "ts/ts.h"
+
 #include "cripts/Lulu.hpp"
 #include "cripts/Context.hpp"
 
diff --git a/include/cripts/Connections.hpp b/include/cripts/Connections.hpp
index 1671b5db7e..3e035105a6 100644
--- a/include/cripts/Connections.hpp
+++ b/include/cripts/Connections.hpp
@@ -17,10 +17,7 @@
 */
 #pragma once
 
-namespace cripts
-{
-class Context;
-}
+#include <openssl/ssl.h>
 
 #include "ts/apidefs.h"
 #include "ts/ts.h"
@@ -28,6 +25,18 @@ class Context;
 #include "cripts/Lulu.hpp"
 #include "cripts/Matcher.hpp"
 
+namespace detail
+{
+class ConnBase;
+template <bool IsMutualTLS> class Cert;
+} // namespace detail
+
+namespace cripts::Certs
+{
+using Client = detail::Cert<true>;
+using Server = detail::Cert<false>;
+} // namespace cripts::Certs
+
 // This is figured out in this way because
 // this header has to be available to include
 // from cripts scripts that won't have access
@@ -305,8 +314,67 @@ class ConnBase
 
   }; // End class ConnBase::TcpInfo
 
+  class TLS
+  {
+    using self_type = TLS;
+
+  public:
+    friend class ConnBase;
+
+    TLS()                             = default;
+    void operator=(const self_type &) = delete;
+
+    operator bool()
+    {
+      auto conn = Connection();
+      return conn != nullptr;
+    }
+
+    [[nodiscard]] TSSslConnection
+    Connection()
+    {
+      if (_not_tls) [[unlikely]] {
+        return nullptr; // Avoid repeated attempts
+      }
+
+      _ensure_initialized(_owner);
+      if (!_tls) {
+        _tls = TSVConnSslConnectionGet(_owner->_vc);
+        if (!_tls) [[unlikely]] {
+          _not_tls = true;
+        }
+      }
+
+      return _tls;
+    }
+
+    [[nodiscard]] X509 *
+    GetX509(bool mTLS = false)
+    {
+      auto conn = Connection();
+
+      if (mTLS) {
+#ifdef OPENSSL_IS_OPENSSL3
+        return SSL_get1_peer_certificate(reinterpret_cast<::SSL *>(conn));
+#else
+        return SSL_get_peer_certificate(reinterpret_cast<::SSL *>(conn));
+#endif
+      } else {
+        return SSL_get_certificate(reinterpret_cast<::SSL *>(conn));
+      }
+    }
+
+  private:
+    ConnBase       *_owner   = nullptr;
+    TSSslConnection _tls     = nullptr;
+    bool            _not_tls = false;
+
+  }; // End class ConnBase::SSL
+
 public:
-  ConnBase() { dscp._owner = congestion._owner = tcpinfo._owner = geo._owner = 
pacing._owner = mark._owner = this; }
+  ConnBase() { dscp._owner = congestion._owner = tcpinfo._owner = geo._owner = 
pacing._owner = mark._owner = tls._owner = this; }
+
+  virtual ~ConnBase() = default;
 
   ConnBase(const self_type &)       = delete;
   void operator=(const self_type &) = delete;
@@ -339,6 +407,13 @@ public:
     return TSHttpTxnIsInternal(_state->txnp);
   }
 
+  [[nodiscard]] bool
+  IsTLS()
+  {
+    _ensure_initialized(this);
+    return TSVConnIsSsl(_vc);
+  }
+
   [[nodiscard]] virtual cripts::IP LocalIP() const        = 0;
   [[nodiscard]] virtual int        Count() const          = 0;
   virtual void                     SetDscp(int val) const = 0;
@@ -351,15 +426,19 @@ public:
     _state = state;
   }
 
-  Dscp       dscp;
-  Congestion congestion;
-  TcpInfo    tcpinfo;
-  Geo        geo;
-  Pacing     pacing;
-  Mark       mark;
+  Dscp        dscp;
+  Congestion  congestion;
+  TcpInfo     tcpinfo;
+  Geo         geo;
+  Pacing      pacing;
+  Mark        mark;
+  mutable TLS tls;
 
   cripts::string_view string(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 
128);
 
+  cripts::Certs::Client ClientCert();
+  cripts::Certs::Server ServerCert();
+
 protected:
   static void
   _ensure_initialized(self_type *ptr)
@@ -418,7 +497,6 @@ namespace Client
     {
       return cripts::IP{TSHttpTxnIncomingAddrGet(_state->txnp)};
     }
-
   }; // End class Client::Connection
 
 } // namespace Client
diff --git a/include/cripts/Matcher.hpp b/include/cripts/Matcher.hpp
index 4cb46ee8f0..30574b67ac 100644
--- a/include/cripts/Matcher.hpp
+++ b/include/cripts/Matcher.hpp
@@ -17,17 +17,15 @@
 */
 #pragma once
 
-#include "cripts/Headers.hpp"
-#include "cripts/Lulu.hpp"
-#include "swoc/IPRange.h"
 // Setup for PCRE2
 #define PCRE2_CODE_UNIT_WIDTH 8
 #include <pcre2.h>
 #include <vector>
 #include <tuple>
 
-#include "ts/ts.h"
+#include "cripts/Headers.hpp"
 #include "cripts/Lulu.hpp"
+#include "swoc/IPRange.h"
 
 namespace cripts::Matcher
 {
diff --git a/include/cripts/Preamble.hpp b/include/cripts/Preamble.hpp
index 5c61d469d9..b484826f18 100644
--- a/include/cripts/Preamble.hpp
+++ b/include/cripts/Preamble.hpp
@@ -92,6 +92,7 @@
 #include "cripts/Matcher.hpp"
 #include "cripts/Time.hpp"
 #include "cripts/Crypto.hpp"
+#include "cripts/Certs.hpp"
 #include "cripts/Files.hpp"
 #include "cripts/Metrics.hpp"
 #include "cripts/Plugins.hpp"
diff --git a/src/cripts/CMakeLists.txt b/src/cripts/CMakeLists.txt
index 2b32b89d5c..6e151f46f3 100644
--- a/src/cripts/CMakeLists.txt
+++ b/src/cripts/CMakeLists.txt
@@ -28,6 +28,7 @@ list(REMOVE_ITEM CPP_FILES ${TEST_CPP_FILES})
 
 set(CRIPTS_PUBLIC_HEADERS
     ${PROJECT_SOURCE_DIR}/include/cripts/Bundle.hpp
+    ${PROJECT_SOURCE_DIR}/include/cripts/Certs.hpp
     ${PROJECT_SOURCE_DIR}/include/cripts/Configs.hpp
     ${PROJECT_SOURCE_DIR}/include/cripts/ConfigsBase.hpp
     ${PROJECT_SOURCE_DIR}/include/cripts/Connections.hpp
diff --git a/src/cripts/Certs.cc b/src/cripts/Certs.cc
new file mode 100644
index 0000000000..abb85c9a5d
--- /dev/null
+++ b/src/cripts/Certs.cc
@@ -0,0 +1,269 @@
+/*
+  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 <arpa/inet.h>
+
+#include "ts/ts.h"
+
+#include "cripts/Certs.hpp"
+
+namespace
+{
+} // namespace
+
+namespace detail
+{
+// The first two are essentially taken from our sslheaders plugin, for 
compatiblity.
+void
+CertBase::Certificate::_load() const
+{
+  if (!_ready && _owner->_x509) {
+    long  remain;
+    char *ptr;
+
+    super_type::_load();
+    PEM_write_bio_X509(_bio.get(), _owner->_x509);
+
+    // The PEM format has newlines in it. mod_ssl replaces those with spaces.
+    remain = BIO_get_mem_data(_bio.get(), &ptr);
+    for (char *nl; (nl = static_cast<char *>(memchr(ptr, '\n', remain))); ptr 
= nl) {
+      *nl     = ' ';
+      remain -= nl - ptr;
+    }
+    _update_value();
+  }
+}
+
+void
+CertBase::Signature::_load() const
+{
+  if (!_ready && _owner->_x509) {
+    const ASN1_BIT_STRING *sig;
+    X509_get0_signature(&sig, nullptr, _owner->_x509);
+    const char *ptr = reinterpret_cast<const char *>(sig->data);
+    const char *end = ptr + sig->length;
+
+    super_type::_load();
+    for (; ptr < end; ++ptr) {
+      BIO_printf(_bio.get(), "%02X", static_cast<unsigned char>(*ptr));
+    }
+    _update_value();
+  }
+}
+
+void
+CertBase::X509Value::_update_value() const
+{
+  if (BIO_pending(_bio.get())) {
+    char *data = nullptr;
+    long  len  = BIO_get_mem_data(_bio.get(), &data);
+
+    _value = cripts::Certs::String(data, static_cast<size_t>(len));
+    _ready = true;
+  }
+}
+
+void
+CertBase::X509Value::_load_name(X509_NAME *(*getter)(const X509 *)) const
+{
+  if (!_ready && _owner->_x509) {
+    auto *name = getter(_owner->_x509);
+
+    if (name) {
+      X509Value::_load();
+      X509_NAME_print_ex(_bio.get(), name, 0, XN_FLAG_ONELINE);
+      _update_value();
+    } else [[unlikely]] {
+      _value = cripts::Certs::String();
+      _ready = true;
+    }
+  }
+}
+
+void
+CertBase::X509Value::_load_integer(ASN1_INTEGER *(*getter)(X509 *)) const
+{
+  if (!_ready && _owner->_x509) {
+    auto *value = getter(_owner->_x509);
+
+    X509Value::_load();
+    i2a_ASN1_INTEGER(_bio.get(), value);
+    X509Value::_update_value();
+  }
+}
+
+void
+CertBase::X509Value::_load_long(long (*getter)(const X509 *)) const
+{
+  if (!_ready && _owner->_x509) {
+    auto value = getter(_owner->_x509);
+
+    X509Value::_load();
+    BIO_printf(_bio.get(), "%ld", value);
+    X509Value::_update_value();
+  }
+}
+
+void
+CertBase::X509Value::_load_time(ASN1_TIME *(*getter)(const X509 *)) const
+{
+  if (!_ready && _owner->_x509) {
+    auto *time = getter(_owner->_x509);
+
+    if (time) {
+      X509Value::_load();
+      ASN1_TIME_print(_bio.get(), time);
+      X509Value::_update_value();
+    } else [[unlikely]] {
+      _value = cripts::Certs::String();
+      _ready = true;
+    }
+  }
+}
+
+namespace
+{
+
+  using GenWriterFunc = void (*)(const GENERAL_NAME *, BIO *);
+
+  void
+  _write_asn1_string(const ASN1_IA5STRING *str, BIO *_bio)
+  {
+    const char *data = reinterpret_cast<const char 
*>(ASN1_STRING_get0_data(str));
+    int         len  = ASN1_STRING_length(str);
+
+    BIO_write(_bio, data, len);
+  }
+
+  void
+  _write_ip_address(const ASN1_OCTET_STRING *ip, BIO *_bio)
+  {
+    char                 buffer[INET6_ADDRSTRLEN];
+    const unsigned char *raw = ip->data;
+    int                  len = ip->length;
+
+    if (inet_ntop(len == 4 ? AF_INET : AF_INET6, raw, buffer, sizeof(buffer))) 
{
+      BIO_printf(_bio, "%s", buffer);
+    }
+  }
+
+  static constexpr GenWriterFunc _gen_writers[] = {
+    /* 0 */ nullptr,
+    /* 1 GEN_EMAIL */ [](const GENERAL_NAME *n, BIO *b) { 
_write_asn1_string(n->d.rfc822Name, b); },
+    /* 2 GEN_DNS   */ [](const GENERAL_NAME *n, BIO *b) { 
_write_asn1_string(n->d.dNSName, b); },
+    /* 3 GEN_X400  */ nullptr,
+    /* 4 GEN_DIRNAME */ nullptr,
+    /* 5 GEN_EDIPARTY */ nullptr,
+    /* 6 GEN_URI   */ [](const GENERAL_NAME *n, BIO *b) { 
_write_asn1_string(n->d.uniformResourceIdentifier, b); },
+    /* 7 GEN_IPADD */ [](const GENERAL_NAME *n, BIO *b) { 
_write_ip_address(n->d.iPAddress, b); },
+    /* 8 GEN_RID   */ nullptr};
+} // end anonymous namespace
+
+void
+CertBase::SAN::SANBase::_load() const
+{
+  if (_ready || !_owner->_owner->_x509) {
+    return;
+  }
+
+  auto *san_names =
+    static_cast<STACK_OF(GENERAL_NAME) 
*>(X509_get_ext_d2i(_owner->_owner->_x509, NID_subject_alt_name, nullptr, 
nullptr));
+
+  if (!san_names) {
+    return;
+  }
+
+  auto bio = BIO_new(BIO_s_mem());
+
+  if (bio) {
+    for (int i = 0; i < sk_GENERAL_NAME_num(san_names); ++i) {
+      const GENERAL_NAME *name = sk_GENERAL_NAME_value(san_names, i);
+
+      if (static_cast<cripts::Certs::SAN>(name->type) == _san_id) {
+        GenWriterFunc fn = (name->type < 
static_cast<int>(std::size(_gen_writers))) ? _gen_writers[name->type] : nullptr;
+        CAssert(fn != nullptr);
+
+        BIO_reset(bio);
+        fn(name, bio);
+
+        char *ptr = nullptr;
+        long  len = BIO_get_mem_data(bio, &ptr);
+
+        if (ptr && len > 0) {
+          _data.emplace_back(ptr, static_cast<size_t>(len));
+        }
+      }
+    }
+    BIO_free(bio);
+  }
+  sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
+}
+
+cripts::string
+CertBase::SAN::SANBase::Join(const char *delim) const
+{
+  cripts::string str;
+
+  ensureLoaded();
+  for (const auto &s : _data) {
+    if (!str.empty()) {
+      str += delim;
+    }
+    str += s;
+  }
+
+  return str; // RVO
+}
+
+void
+CertBase::SAN::Iterator::_advance()
+{
+  if (!_san || _ended) {
+    return;
+  }
+
+  auto it = std::find_if(_san->_sans.begin() + _index, _san->_sans.end(), 
[](const SANBase *entry) { return entry->Size() > 0; });
+
+  if (it != _san->_sans.end()) {
+    _index = std::distance(_san->_sans.begin(), it) + 1;
+    _iter  = (*it)->Data().begin();
+    _update_current();
+  } else {
+    _ended = true;
+  }
+}
+
+CertBase::SAN::Iterator::value_type
+CertBase::SAN::operator[](size_t index) const
+{
+  if (index < Size()) {
+    size_t cur = 0;
+
+    for (auto *san : _sans) {
+      size_t type_size = san->Size();
+
+      if (index < cur + type_size) {
+        // Found the SAN type that contains this index
+        return std::make_tuple(san->sanType(), 
cripts::Certs::String((*san)[index - cur]));
+      }
+      cur += type_size;
+    }
+  }
+
+  return std::make_tuple(cripts::Certs::SAN::OTHER, cripts::Certs::String());
+}
+} // namespace detail
diff --git a/src/cripts/Connections.cc b/src/cripts/Connections.cc
index eac1c223f0..d456a961df 100644
--- a/src/cripts/Connections.cc
+++ b/src/cripts/Connections.cc
@@ -89,6 +89,20 @@ detail::ConnBase::TcpInfo::Log()
   return _logging;
 }
 
+cripts::Certs::Client
+detail::ConnBase::ClientCert()
+{
+  _ensure_initialized(this);
+  return cripts::Certs::Client{*this};
+}
+
+cripts::Certs::Server
+detail::ConnBase::ServerCert()
+{
+  _ensure_initialized(this);
+  return cripts::Certs::Server{*this};
+}
+
 namespace cripts
 {
 
diff --git a/tests/gold_tests/cripts/cripts.test.py 
b/tests/gold_tests/cripts/cripts.test.py
index 32d9cc8702..c72f8c2ccc 100644
--- a/tests/gold_tests/cripts/cripts.test.py
+++ b/tests/gold_tests/cripts/cripts.test.py
@@ -19,6 +19,9 @@ Test basic cripts functionality
 
 import os
 
+# Needed if we want to use sed -i '' on macOS, but autest doesn't like that ...
+# import platform
+
 Test.testName = "cripts: basic functions"
 Test.Summary = '''
 Simple cripts test that sets a response header back to the client
@@ -45,19 +48,26 @@ class CriptsBasicTest:
         self.server.addResponse("sessionfile.log", request_header, 
response_header)
 
     def setUpTS(self):
-        self.ts = Test.MakeATSProcess("ts")
+        self.ts = Test.MakeATSProcess("ts_in", enable_tls=True, 
enable_cache=False)
+
+        self.ts.addDefaultSSLFiles()
+        self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* 
ssl_cert_name=server.pem ssl_key_name=server.key")
 
         self.ts.Setup.Copy('files/basic.cript', self.ts.Variables.CONFIGDIR)
 
         self.ts.Disk.records_config.update(
             {
-                'proxy.config.diags.debug.enabled': 1,
                 'proxy.config.plugin.dynamic_reload_mode': 1,
                 'proxy.config.plugin.compiler_path': self._compiler_location,
+                "proxy.config.ssl.server.cert.path": 
f"{self.ts.Variables.SSLDir}",
+                "proxy.config.ssl.server.private_key.path": 
f"{self.ts.Variables.SSLDir}",
             })
 
         self.ts.Disk.remap_config.AddLine(
             f'map http://www.example.com 
http://127.0.0.1:{self.server.Variables.Port} @plugin=basic.cript')
+        self.ts.Disk.remap_config.AddLine(
+            f'map https://www.example.com:{self.ts.Variables.ssl_port} 
http://127.0.0.1:{self.server.Variables.Port} @plugin=basic.cript'
+        )
 
     def updateCompilerForTest(self):
         '''Update the compiler script for the install location of the ATS 
process.'''
@@ -66,7 +76,11 @@ class CriptsBasicTest:
         compiler_source = os.path.join(p.Variables.RepoDir, 'tools', 'cripts', 
'compiler.sh')
         p.Setup.Copy(compiler_source, self._compiler_location)
         install_dir = os.path.split(p.Variables.BINDIR)[0]
-        p.Command = f'sed -i "s|\"/tmp/ats\"|{install_dir}|g" 
{self._compiler_location}'
+        # autest doesn't like the -i '' that's necessary on Darwin/macOS
+        # sed_in_place = "-i ''" if platform.system() == 'Darwin' else "-i"
+        # p.Command = f"sed -i '' 's|\"/tmp/ats\"|\"{install_dir}\"|' 
{self._compiler_location}"
+        p.Command = (f'perl -pi -e \'s|\\"/tmp/ats\\"|\\"{install_dir}\\"|g\' 
{self._compiler_location}')
+
         p.ReturnCode = 0
 
     def runHeaderTest(self):
@@ -78,9 +92,19 @@ class CriptsBasicTest:
         tr.Processes.Default.Streams.stderr = "gold/basic_cript.gold"
         tr.StillRunningAfter = self.server
 
+    def runCertsTest(self):
+        tr = Test.AddTestRun('Exercise Cripts certificate introspection.')
+        tr.MakeCurlCommand(
+            f'-v --http1.1 -k -H "Host: 
www.example.com:{self.ts.Variables.ssl_port}" 
https://127.0.0.1:{self.ts.Variables.ssl_port}'
+        )
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Streams.stderr = "gold/certs_cript.gold"
+        tr.StillRunningAfter = self.server
+
     def run(self):
         self.updateCompilerForTest()
         self.runHeaderTest()
+        self.runCertsTest()
 
 
 CriptsBasicTest().run()
diff --git a/tests/gold_tests/cripts/files/basic.cript 
b/tests/gold_tests/cripts/files/basic.cript
index b8c136668a..9c9343c092 100644
--- a/tests/gold_tests/cripts/files/basic.cript
+++ b/tests/gold_tests/cripts/files/basic.cript
@@ -27,7 +27,15 @@ do_read_response()
 do_send_response()
 {
   borrow resp = cripts::Client::Response::Get();
+  borrow conn =cripts::Client::Connection::Get();
   resp["criptsResponseHeader"] = "response";
+
+  if (conn.IsTLS()) {
+    const auto tls = cripts::Certs::Server(conn);
+    resp["X-Subject"] = tls.subject;
+    resp["X-NotBefore"] = tls.notBefore;
+    resp["X-NotAfter"] = tls.notAfter;
+  }
 }
 
 #include <cripts/Epilogue.hpp>
diff --git a/tests/gold_tests/cripts/gold/certs_cript.gold 
b/tests/gold_tests/cripts/gold/certs_cript.gold
new file mode 100644
index 0000000000..bfc31a9e71
--- /dev/null
+++ b/tests/gold_tests/cripts/gold/certs_cript.gold
@@ -0,0 +1,17 @@
+``
+> GET / HTTP/1.1
+> Host: www.example.com``
+> User-Agent: curl/``
+> Accept: */*
+``
+< HTTP/1.1 200 OK
+< responseHeader: changed
+< Date: ``
+< Age: ``
+< Connection: keep-alive
+< Server: ATS/``
+< criptsResponseHeader: response
+< X-Subject: C = IE, ST = Dublin, L = Dublin, O = example.com, OU = 
example.com, CN = example.com
+< X-NotBefore: Jan 26 14:56:08 2021 GMT
+< X-NotAfter: Aug 21 14:56:08 2119 GMT
+``
diff --git a/tools/cripts/compiler.sh b/tools/cripts/compiler.sh
index e6a035f70a..cce575fde4 100755
--- a/tools/cripts/compiler.sh
+++ b/tools/cripts/compiler.sh
@@ -26,7 +26,11 @@
 # Configurable parts
 : ${ATS_ROOT:="/tmp/ats"}
 : ${CXX:="clang++"}
-: ${CXXFLAGS:="-x c++ -std=c++20 -I/opt/homebrew/include"}
+if [[ "$(uname)" == "Darwin" ]]; then
+    : ${CXXFLAGS:="-x c++ -std=c++20 -I/opt/homebrew/include -undefined 
dynamic_lookup"}
+else
+    : ${CXXFLAGS:="-x c++ -std=c++20"}
+fi
 
 # Probably don't need to change these ?
 STDFLAGS="-shared -fPIC -Wall -Werror -I${ATS_ROOT}/include -L${ATS_ROOT}/lib 
-lcripts"


Reply via email to