]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1661] Added files
authorFrancis Dupont <fdupont@isc.org>
Wed, 24 Feb 2021 13:45:54 +0000 (14:45 +0100)
committerFrancis Dupont <fdupont@isc.org>
Fri, 12 Mar 2021 08:56:46 +0000 (09:56 +0100)
39 files changed:
src/lib/asiolink/botan_tls.h [new file with mode: 0644]
src/lib/asiolink/crypto_tls.h [new file with mode: 0644]
src/lib/asiolink/openssl_tls.cc [new file with mode: 0644]
src/lib/asiolink/openssl_tls.h [new file with mode: 0644]
src/lib/asiolink/tests/tls_acceptor_unittest.cc [new file with mode: 0644]
src/lib/asiolink/tests/tls_socket_unittest.cc [new file with mode: 0644]
src/lib/asiolink/tests/tls_unittest.cc [new file with mode: 0644]
src/lib/asiolink/testutils/ca/0c7eedb9.0 [new symlink]
src/lib/asiolink/testutils/ca/26d052a5.0 [new symlink]
src/lib/asiolink/testutils/ca/3071e5ff.0 [new symlink]
src/lib/asiolink/testutils/ca/a465d731.0 [new symlink]
src/lib/asiolink/testutils/ca/ad950210.0 [new symlink]
src/lib/asiolink/testutils/ca/d3d11a5f.0 [new symlink]
src/lib/asiolink/testutils/ca/doc [new file with mode: 0644]
src/lib/asiolink/testutils/ca/ext-addr-conf.cnf [new file with mode: 0644]
src/lib/asiolink/testutils/ca/ext-conf.cnf [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-ca.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-ca.key [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-client.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-client.csr [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-client.key [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-client.p12 [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-other.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-other.key [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-self.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-self.key [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-server-addr.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-server-addr.csr [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-server.crt [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-server.csr [new file with mode: 0644]
src/lib/asiolink/testutils/ca/kea-server.key [new file with mode: 0644]
src/lib/asiolink/testutils/ca/server-addr-conf.cnf [new file with mode: 0644]
src/lib/asiolink/testutils/ca/server-conf.cnf [new file with mode: 0644]
src/lib/asiolink/testutils/openssl_sample_client.cpp [new file with mode: 0644]
src/lib/asiolink/testutils/openssl_sample_server.cpp [new file with mode: 0644]
src/lib/asiolink/testutils/test_tls.cc [new file with mode: 0644]
src/lib/asiolink/testutils/test_tls.h [new file with mode: 0644]
src/lib/asiolink/tls_acceptor.h [new file with mode: 0644]
src/lib/asiolink/tls_socket.h [new file with mode: 0644]

diff --git a/src/lib/asiolink/botan_tls.h b/src/lib/asiolink/botan_tls.h
new file mode 100644 (file)
index 0000000..6065ab3
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BOTAN_TLS_H
+#define BOTAN_TLS_H
+
+#ifdef WITH_BOTAN
+
+namespace isc {
+namespace asiolink {
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN
+
+#endif // BOTAN_TLS_H
diff --git a/src/lib/asiolink/crypto_tls.h b/src/lib/asiolink/crypto_tls.h
new file mode 100644 (file)
index 0000000..42309ef
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CRYPTO_TLS_H
+#define CRYPTO_TLS_H
+
+#include <cryptolink/cryptolink.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @file crypto_tls.h Common TLS API.
+
+/// @brief Client and server roles.
+enum TlsRole { CLIENT, SERVER };
+
+/// @brief TLS context base class.
+class TlsContextBase : private boost::noncopyable {
+public:
+    /// @brief Destructor.
+    virtual ~TlsContextBase() { }
+
+    /// @brief Create a fresh context.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TlsContextBase(TlsRole role) : role_(role) { }
+
+    /// @brief Returns the role.
+    TlsRole getRole() const {
+        return (role_);
+    }
+
+    /// @note No need for a role set method.
+
+    /// @brief Set the peer certificate requirement mode.
+    ///
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    virtual void setCertRequired(bool cert_required) = 0;
+
+    /// @brief Get the peer certificate requirement mode.
+    ///
+    /// @return True if peer certificates are required, false if they
+    /// are optional.
+    virtual bool getCertRequired() const = 0;
+
+    /// @brief Load the trust anchor aka certificate authority.
+    ///
+    /// @param ca_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaFile(const std::string& ca_file) = 0;
+
+    /// @brief Load the trust anchor aka certificate authority.
+    ///
+    /// @param ca_path The certificate directory name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCaPath(const std::string& ca_path) = 0;
+
+    /// @brief Load the certificate file.
+    ///
+    /// @param cert_file The certificate file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadCertFile(const std::string& cert_file) = 0;
+
+    /// @brief Load the private key file name.
+    ///
+    /// @param key_file The private key file name.
+    /// @throw isc::cryptolink::LibraryError on various errors as
+    /// file not found, bad format, etc.
+    virtual void loadKeyFile(const std::string& key_file) = 0;
+
+public:
+    /// @brief The role i.e. client or server.
+    TlsRole role_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // CRYPTO_TLS_H
diff --git a/src/lib/asiolink/openssl_tls.cc b/src/lib/asiolink/openssl_tls.cc
new file mode 100644 (file)
index 0000000..857ca4f
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+#include <asiolink/openssl_tls.h>
+
+#include <sys/stat.h>
+
+#include <openssl/opensslv.h>
+
+using namespace boost::asio;
+using namespace boost::asio::ssl;
+using namespace boost::system;
+using namespace isc::cryptolink;
+
+namespace isc {
+namespace asiolink {
+
+TlsContext::TlsContext(TlsRole role)
+    : TlsContextBase(role), cert_required_(true),
+      context_(context::method::tls) {
+    // Not leave the verify mode to OpenSSL default.
+    setCertRequired(true);
+}
+
+boost::asio::ssl::context&
+TlsContext::getContext() {
+    return (context_);
+}
+
+::SSL_CTX*
+TlsContext::getNativeContext() {
+    return (context_.native_handle());
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+    cert_required_ = cert_required;
+    error_code ec;
+    int mode = verify_peer | verify_fail_if_no_peer_cert;
+    if (!cert_required_) {
+        mode = verify_none;
+    }
+    context_.set_verify_mode(mode, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+bool
+TlsContext::getCertRequired() const {
+    return (cert_required_);
+}
+
+void
+TlsContext::loadCaFile(const std::string& ca_file) {
+    error_code ec;
+    context_.load_verify_file(ca_file, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadCaPath(const std::string& ca_path) {
+    error_code ec;
+    context_.add_verify_path(ca_path, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadCertFile(const std::string& cert_file) {
+    error_code ec;
+    context_.use_certificate_chain_file(cert_file, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+void
+TlsContext::loadKeyFile(const std::string& key_file) {
+    error_code ec;
+    context_.use_private_key_file(key_file, context::file_format::pem, ec);
+    if (ec) {
+        isc_throw(LibraryError, ec.message());
+    }
+}
+
+namespace { // anonymous namespace
+
+// C++17 has this function but Kea is still C++11 so provide it.
+bool
+isDir(const std::string& name) {
+    struct stat stats;
+    if (::stat(name.c_str(), &stats) < 0) {
+        return (false);
+    }
+    return ((stats.st_mode & S_IFMT) == S_IFDIR);
+}
+
+} // end of namespace
+
+void
+TlsContext::configure(TlsContextPtr& context,
+                      TlsRole role,
+                      const std::string& ca_file,
+                      const std::string& cert_file,
+                      const std::string& key_file,
+                      bool cert_required) {
+    try {
+        context.reset(new TlsContext(role));
+        if (isDir(ca_file)) {
+            context->loadCaPath(ca_file);
+        } else {
+            context->loadCaFile(ca_file);
+        }
+        context->loadCertFile(cert_file);
+        context->loadKeyFile(key_file);
+        context->setCertRequired(cert_required);
+    } catch (...) {
+        context.reset();
+        throw;
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
diff --git a/src/lib/asiolink/openssl_tls.h b/src/lib/asiolink/openssl_tls.h
new file mode 100644 (file)
index 0000000..7565fa1
--- /dev/null
@@ -0,0 +1,239 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef OPENSSL_TLS_H
+#define OPENSSL_TLS_H
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+
+#include <boost/asio/ssl.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Forward declaration of OpenSSL TLS context.
+class TlsContext;
+
+/// @brief The type of shared pointers to TlsContext objects.
+///
+/// @note Not clear we need shared pointers but they covers more use cases...
+typedef boost::shared_ptr<TlsContext> TlsContextPtr;
+
+/// @brief OpenSSL TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+    /// @brief Destructor.
+    virtual ~TlsContext() { }
+
+    /// @brief Create a fresh context.
+    ///
+    /// @param role The TLS role client or server.
+    explicit TlsContext(TlsRole role);
+
+    /// @brief Return a reference to the underlying context.
+    boost::asio::ssl::context& getContext();
+
+    /// @brief Return the pointer to the SSL_CTX object.
+    ///
+    /// Currently used only for tests. Please note that since OpenSSL 1.1
+    /// The SSL_CTX type is not fully publicly defined.
+    ::SSL_CTX* getNativeContext();
+
+    /// @brief Set the peer certificate requirement mode.
+    ///
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    virtual void setCertRequired(bool cert_required);
+
+    /// @brief Get the peer certificate requirement mode.
+    ///
+    /// @return True if peer certificates are required, false if they
+    /// are optional.
+    virtual bool getCertRequired() const;
+
+    /// @brief Load the trust anchor aka certificate authority.
+    ///
+    /// @param ca_file The certificate file name.
+    virtual void loadCaFile(const std::string& ca_file);
+
+    /// @brief Load the trust anchor aka certificate authority.
+    ///
+    /// @param ca_path The certificate directory name.
+    virtual void loadCaPath(const std::string& ca_path);
+
+    /// @brief Load the certificate file.
+    ///
+    /// @param cert_file The certificate file name.
+    virtual void loadCertFile(const std::string& cert_file);
+
+    /// @brief Load the private key file name.
+    ///
+    /// @param key_file The private key file name.
+    virtual void loadKeyFile(const std::string& key_file);
+
+    /// @brief Configure.
+    ///
+    /// @param context The TLS context to configure.
+    /// @param role The TLS role client or server.
+    /// @param ca_file The certificate file or directory name.
+    /// @param cert_file The certificate file name.
+    /// @param key_file The private key file name.
+    /// @param cert_required True if peer certificates are required,
+    /// false if they are optional.
+    static void configure(TlsContextPtr& context,
+                          TlsRole role,
+                          const std::string& ca_file,
+                          const std::string& cert_file,
+                          const std::string& key_file,
+                          bool cert_required);
+
+protected:
+    /// @brief Cached cert_required value.
+    bool cert_required_;
+
+    /// @brief Boost ASIO SSL object.
+    boost::asio::ssl::context context_;
+};
+
+/// @brief The type of underlying TLS streams.
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> TlsStreamImpl;
+
+/// @brief The type of X509 certificates.
+typedef ::X509 TlsCertificate;
+
+/// @brief OpenSSL TLS stream.
+///
+/// @param callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamImpl {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param service I/O Service object used to manage the stream.
+    /// @param context Pointer to the TLS context.
+    /// @note The caller must not provide a null pointer to the TLS context.
+    TlsStream(IOService& service, TlsContextPtr context)
+        : TlsStreamImpl(service.get_io_service(), context->getContext()),
+          role_(context->getRole()) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TlsStream() { }
+
+    /// @brief Returns the role.
+    TlsRole getRole() const {
+        return (role_);
+    }
+
+    /// @brief TLS Handshake.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(Callback& callback) {
+        using namespace boost::asio::ssl;
+        if (role_ == SERVER) {
+            async_handshake(stream_base::server, callback);
+        } else {
+            async_handshake(stream_base::client, callback);
+        }
+    }
+
+    /// @brief TLS shutdown.
+    ///
+    /// @param callback Callback object.
+    virtual void shutdown(Callback& callback) {
+        async_shutdown(callback);
+    }
+
+    /// @brief Clear the SSL object.
+    virtual void clear() {
+        static_cast<void>(::SSL_clear(this->native_handle()));
+    }
+
+    /// @brief Return the peer certificate.
+    ///
+    /// @note The native_handle() method is used so it can't be made const.
+    /// @note Do not forget to free it when no longer used.
+    virtual TlsCertificate* getPeerCert() {
+        return (::SSL_get_peer_certificate(this->native_handle()));
+    }
+
+    /// @brief The role i.e. client or server.
+    TlsRole role_;
+
+    /// @break Return the commonName part of the subjectName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    ///
+    /// @return The commonName part of the subjectName or the empty string.
+    std::string getSubject() {
+        TlsCertificate* cert = getPeerCert();
+        if (!cert) {
+            return ("");
+        }
+        ::X509_NAME *name = ::X509_get_subject_name(cert);
+        int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+        ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+        if (!ne) {
+            ::X509_free(cert);
+            return ("");
+        }
+        unsigned char* buf = 0;
+        int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+        if (len < 0) {
+            ::X509_free(cert);
+            return ("");
+        }
+        std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+        ::OPENSSL_free(buf);
+        ::X509_free(cert);
+        return (ret);
+    }
+
+    /// @break Return the commonName part of the issuerName of
+    /// the peer certificate.
+    ///
+    /// First commonName when there are more than one, in UTF-8.
+    ///
+    /// @return The commonName part of the issuerName or the empty string.
+    std::string getIssuer() {
+        TlsCertificate* cert = getPeerCert();
+        if (!cert) {
+            return ("");
+        }
+        ::X509_NAME *name = ::X509_get_issuer_name(cert);
+        int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+        ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+        if (!ne) {
+            ::X509_free(cert);
+            return ("");
+        }
+        unsigned char* buf = 0;
+        int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+        if (len < 0) {
+            ::X509_free(cert);
+            return ("");
+        }
+        std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+        ::OPENSSL_free(buf);
+        ::X509_free(cert);
+        return (ret);
+    }
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
+
+#endif // OPENSSL_TLS_H
diff --git a/src/lib/asiolink/tests/tls_acceptor_unittest.cc b/src/lib/asiolink/tests/tls_acceptor_unittest.cc
new file mode 100644 (file)
index 0000000..06f0631
--- /dev/null
@@ -0,0 +1,449 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_acceptor.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace boost::asio;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+    /// @brief Implements callback for the asynchronous operation on the socket.
+    ///
+    /// This callback merely checks if error has occurred and reports this
+    /// error. It does nothing in case of success.
+    ///
+    /// @param ec Error code.
+    /// @param length Length of received data.
+    void operator()(boost::system::error_code ec, size_t length = 0) {
+        if (ec) {
+            ADD_FAILURE() << "error occurred for a socket: " << ec.message();
+        }
+    }
+
+};
+
+/// @brief Entity which can connect to the TLS server endpoint and close the
+/// connection.
+class TLSClient : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new socket instance. It doesn't connect. Call
+    /// connect() to connect to the server.
+    ///
+    /// @param io_service IO service to be stopped on error.
+    explicit TLSClient(IOService& io_service)
+        : io_service_(io_service.get_io_service()), socket_(io_service_) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the underlying socket if it is open.
+    ~TLSClient() {
+        close();
+    }
+
+    /// @brief Connect to the test server address and port.
+    ///
+    /// This method asynchronously connects to the server endpoint and uses the
+    /// connectHandler as a callback function.
+    void connect() {
+        ip::tcp::endpoint
+            endpoint(ip::address::from_string(SERVER_ADDRESS),
+                     SERVER_PORT);
+        socket_.async_connect(endpoint,
+                              std::bind(&TLSClient::connectHandler, this,
+                                        ph::_1));
+    }
+
+    /// @brief Callback function for connect().
+    ///
+    /// This function stops the IO service upon error.
+    ///
+    /// @param ec Error code.
+    void connectHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            // One would expect that async_connect wouldn't return EINPROGRESS
+            // error code, but simply wait for the connection to get
+            // established before the handler is invoked. It turns out, however,
+            // that on some OSes the connect handler may receive this error code
+            // which doesn't necessarily indicate a problem. Making an attempt
+            // to write and read from this socket will typically succeed. So,
+            // we ignore this error.
+            if (ec.value() != error::in_progress) {
+                ADD_FAILURE() << "error occurred while connecting: "
+                              << ec.message();
+                io_service_.stop();
+            }
+        }
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Holds reference to the IO service.
+    io_service& io_service_;
+
+    /// @brief A socket used for the connection.
+    ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TLSClient.
+typedef boost::shared_ptr<TLSClient> TLSClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TLSAcceptor.
+typedef std::function<void(const boost::system::error_code&)> TLSAcceptorCallback;
+
+/// @brief TLSAcceptor using TLSAcceptorCallback.
+typedef TLSAcceptor<TLSAcceptorCallback> TestTLSAcceptor;
+
+/// @brief Implements asynchronous TLS acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    /// @param context Pointer to TLS context.
+    /// @param acceptor Reference to the TLS acceptor on which asyncAccept
+    /// will be called.
+    /// @param callback Callback function for the asyncAccept.
+    explicit Acceptor(IOService& io_service,
+                      TlsContextPtr context,
+                      TestTLSAcceptor& acceptor,
+                      const TLSAcceptorCallback& callback)
+        : socket_(io_service, context), acceptor_(acceptor),
+          callback_(callback) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes socket.
+    ~Acceptor() {
+        socket_.close();
+    }
+
+    /// @brief Asynchronous accept new connection.
+    void accept() {
+        acceptor_.asyncAccept(socket_, callback_);
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+private:
+
+    /// @brief Socket into which connection is accepted.
+    TLSSocket<SocketCallback> socket_;
+
+    /// @brief Reference to the TLSAcceptor on which asyncAccept is called.
+    TestTLSAcceptor& acceptor_;
+
+    /// @brief Instance of the callback used for asyncAccept.
+    TLSAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TLSAcceptor.
+///
+/// This class provides means for creating new TLS connections, i.e. simulates
+/// clients connecting to the servers via TLSAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TLSAcceptorTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Besides initializing class members it also sets the test timer to guard
+    /// against endlessly running IO service when TLS connections are
+    /// unsuccessful.
+    TLSAcceptorTest()
+        : io_service_(), acceptor_(io_service_),
+          asio_endpoint_(ip::address::from_string(SERVER_ADDRESS),
+                         SERVER_PORT),
+          endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+          clients_(), connections_num_(0), aborted_connections_num_(0),
+          max_connections_(1) {
+        test_timer_.setup(std::bind(&TLSAcceptorTest::timeoutHandler, this),
+                                    TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Destructor.
+    virtual ~TLSAcceptorTest() {
+    }
+
+    /// @brief Specifies how many new connections are expected before the IO
+    /// service is stopped.
+    ///
+    /// @param max_connections Connections limit.
+    void setMaxConnections(const unsigned int max_connections) {
+        max_connections_ = max_connections;
+    }
+
+    /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+    /// IP address and modifying the port.
+    ///
+    /// This convenience method is useful to create new endpoint from the
+    /// existing endpoint to test reusing IP address for multiple acceptors.
+    /// The returned endpoint has the same IP address but different port.
+    ///
+    /// @param endpoint Source endpoint.
+    ///
+    /// @return New endpoint with the port number increased by 1.
+    ip::tcp::endpoint
+    createSiblingEndpoint(const ip::tcp::endpoint& endpoint) const {
+        ip::tcp::endpoint endpoint_copy(endpoint);
+        endpoint_copy.port(endpoint.port() + 1);
+        return (endpoint_copy);
+    }
+
+    /// @brief Opens TLS acceptor and sets 'reuse address' option.
+    void acceptorOpen() {
+        acceptor_.open(endpoint_);
+        acceptor_.setOption(TestTLSAcceptor::ReuseAddress(true));
+    }
+
+    /// @brief Starts accepting TLS connections.
+    ///
+    /// This method creates new Acceptor instance and calls accept() to start
+    /// accepting new connections. The instance of the Acceptor object is
+    /// retained in the connections_ list.
+    void accept() {
+        TLSAcceptorCallback cb = std::bind(&TLSAcceptorTest::acceptHandler,
+                                           this, ph::_1);
+        TlsContextPtr ctx(new TlsContext(SERVER));
+        AcceptorPtr conn(new Acceptor(io_service_, ctx, acceptor_, cb));
+        connections_.push_back(conn);
+        connections_.back()->accept();
+    }
+
+    /// @brief Connect to the endpoint.
+    ///
+    /// This method creates TLSClient instance and retains it in the clients_
+    /// list.
+    void connect() {
+        TLSClientPtr client(new TLSClient(io_service_));
+        clients_.push_back(client);
+        clients_.back()->connect();
+    }
+
+    /// @brief Callback function for asynchronous accept calls.
+    ///
+    /// It stops the IO service upon error or when the number of accepted
+    /// connections reaches the max_connections_ value. Otherwise it calls
+    /// accept() to start accepting next connections.
+    ///
+    /// @param ec Error code.
+    void acceptHandler(const boost::system::error_code& ec) {
+        if (ec) {
+            if (ec.value() != error::operation_aborted) {
+                ADD_FAILURE() << "error occurred while accepting connection: "
+                              << ec.message();
+            } else {
+                ++aborted_connections_num_;
+            }
+            io_service_.stop();
+        }
+
+        // We have reached the maximum number of connections - end the test.
+        if (++connections_num_ >= max_connections_) {
+            io_service_.stop();
+            return;
+        }
+
+        accept();
+    }
+
+    /// @brief Callback function invoke upon test timeout.
+    ///
+    /// It stops the IO service and reports test timeout.
+    void timeoutHandler() {
+        ADD_FAILURE() << "Timeout occurred while running the test!";
+        io_service_.stop();
+    }
+
+    /// @brief IO service.
+    IOService io_service_;
+
+    /// @brief TLSAcceptor under test.
+    TestTLSAcceptor acceptor_;
+
+    /// @brief Server endpoint.
+    ip::tcp::endpoint asio_endpoint_;
+
+    /// @brief asiolink server endpoint (uses asio_endpoint_).
+    TCPEndpoint endpoint_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief List of connections on the server side.
+    std::list<AcceptorPtr> connections_;
+
+    /// @brief List of client connections.
+    std::list<TLSClientPtr> clients_;
+
+    /// @brief Current number of established connections.
+    unsigned int connections_num_;
+
+    /// @brief Current number of aborted connections.
+    unsigned int aborted_connections_num_;
+
+    /// @brief Connections limit.
+    unsigned int max_connections_;
+};
+
+// Test TLSAcceptor::asyncAccept.
+TEST_F(TLSAcceptorTest, asyncAccept) {
+    // Establish up to 10 connections.
+    setMaxConnections(10);
+
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TLS connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Run the IO service until we have accepted 10 connections, an error
+    // or test timeout occurred.
+    io_service_.run();
+
+    // Make sure that all accepted connections have been recorded.
+    EXPECT_EQ(10, connections_num_);
+    EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TLSAcceptor.
+TEST_F(TLSAcceptorTest, reuseAddress) {
+    // We need at least two acceptors using common address. Let's create the
+    // second endpoint which has the same address but different port.
+    ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+    TCPEndpoint endpoint2(asio_endpoint2);
+
+    // Create and open two acceptors.
+    TestTLSAcceptor acceptor1(io_service_);
+    TestTLSAcceptor acceptor2(io_service_);
+    ASSERT_NO_THROW(acceptor1.open(endpoint_));
+    ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+    // Set SO_REUSEADDR socket option so as acceptors can bind to the
+    /// same address.
+    ASSERT_NO_THROW(
+        acceptor1.setOption(TestTLSAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(
+        acceptor2.setOption(TestTLSAcceptor::ReuseAddress(true))
+    );
+    ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+    ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+    // Create third acceptor, but don't set the SO_REUSEADDR. It should
+    // refuse to bind.
+    TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+    TestTLSAcceptor acceptor3(io_service_);
+    ASSERT_NO_THROW(acceptor3.open(endpoint3));
+    EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TLSAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TLSAcceptorTest, getProtocol) {
+    EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TLSAcceptor::getNative returns valid socket descriptor.
+TEST_F(TLSAcceptorTest, getNative) {
+    // Initially the descriptor should be invalid (negative).
+    ASSERT_LT(acceptor_.getNative(), 0);
+    // Now open the socket and make sure the returned descriptor is now valid.
+    ASSERT_NO_THROW(acceptorOpen());
+    EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TLSAcceptor::close works properly.
+TEST_F(TLSAcceptorTest, close) {
+    // Initialize acceptor.
+    acceptorOpen();
+    acceptor_.bind(endpoint_);
+    acceptor_.listen();
+
+    // Start accepting new connections.
+    accept();
+
+    // Create 10 new TLS connections (client side).
+    for (unsigned int i = 0; i < 10; ++i) {
+        connect();
+    }
+
+    // Close the acceptor before connections are accepted.
+    acceptor_.close();
+
+    // Run the IO service.
+    io_service_.run();
+
+    // The connections should have been aborted.
+    EXPECT_EQ(1, connections_num_);
+    EXPECT_EQ(1, aborted_connections_num_);
+    EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/tests/tls_socket_unittest.cc b/src/lib/asiolink/tests/tls_socket_unittest.cc
new file mode 100644 (file)
index 0000000..a996fad
--- /dev/null
@@ -0,0 +1,560 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// \brief Test of TCPSocket
+///
+/// Tests the functionality of a TCPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_socket.h>
+#include <asiolink/testutils/test_tls.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string>
+#include <vector>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TLSCallback {
+public:
+    /// \brief Operations the server is doing
+    enum Operation {
+        ACCEPT = 0,     ///< accept() was issued
+        OPEN = 1,       ///< Client connected to server
+        HANDSHAKE = 2,  ///< TLS handshake completed
+        READ = 3,       ///< Asynchronous read completed
+        WRITE = 4,      ///< Asynchronous write completed
+        NONE = 5        ///< "Not set" state
+    };
+
+    /// \brief Minimum size of buffers
+    enum {
+        MIN_SIZE = (64 * 1024 + 2)          ///< 64kB + two bytes for a count
+    };
+
+    struct PrivateData {
+        PrivateData() :
+            error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+            name_(""), queued_(NONE), called_(NONE), data_(MIN_SIZE, 0)
+        {}
+
+        boost::system::error_code  error_code_;    ///< Completion error code
+        size_t                     length_;        ///< Bytes transferred in this I/O
+        size_t                     cumulative_;    ///< Cumulative bytes transferred
+        size_t                     expected_;      ///< Expected amount of data
+        size_t                     offset_;        ///< Where to put data in buffer
+        std::string                name_;          ///< Which of the objects this is
+        Operation                  queued_;        ///< Queued operation
+        Operation                  called_;        ///< Which callback called
+        std::vector<uint8_t>       data_;          ///< Receive buffer
+    };
+
+    /// \brief Constructor
+    ///
+    /// Constructs the object.  It also creates the data member pointed to by
+    /// a shared pointer.  When used as a callback object, this is copied as it
+    /// is passed into the asynchronous function.  This means that there are two
+    /// objects and inspecting the one we passed in does not tell us anything.
+    ///
+    /// Therefore we use a boost::shared_ptr.  When the object is copied, the
+    /// shared pointer is copied, which leaves both objects pointing to the same
+    /// data.
+    ///
+    /// \param which Which of the two callback objects this is
+    TLSCallback(std::string which) : ptr_(new PrivateData())
+    {
+        ptr_->name_ = which;
+    }
+
+    /// \brief Destructor
+    ///
+    /// No code needed, destroying the shared pointer destroys the private data.
+    virtual ~TLSCallback()
+    {}
+
+    /// \brief Client Callback Function
+    ///
+    /// Called when an asynchronous operation is completed by the client, this
+    /// stores the origin of the operation in the client_called_ data member.
+    ///
+    /// \param ec I/O completion error code passed to callback function.
+    /// \param length Number of bytes transferred
+    void operator()(boost::system::error_code ec = boost::system::error_code(),
+                            size_t length = 0)
+    {
+        setCode(ec.value());
+        ptr_->called_ = ptr_->queued_;
+        ptr_->length_ = length;
+    }
+
+    /// \brief Get I/O completion error code
+    int getCode() {
+        return (ptr_->error_code_.value());
+    }
+
+    /// \brief Set I/O completion code
+    ///
+    /// \param code New value of completion code
+    void setCode(int code) {
+        ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+    }
+
+    /// \brief Get number of bytes transferred in I/O
+    size_t& length() {
+        return (ptr_->length_);
+    }
+
+    /// \brief Get cumulative number of bytes transferred in I/O
+    size_t& cumulative() {
+        return (ptr_->cumulative_);
+    }
+
+    /// \brief Get expected amount of data
+    size_t& expected() {
+        return (ptr_->expected_);
+    }
+
+    /// \brief Get offset into data
+    size_t& offset() {
+        return (ptr_->offset_);
+    }
+
+    /// \brief Get data member
+    uint8_t* data() {
+        return (&ptr_->data_[0]);
+    }
+
+    /// \brief Get flag to say what was queued
+    Operation& queued() {
+        return (ptr_->queued_);
+    }
+
+    /// \brief Get flag to say when callback was called
+    Operation& called() {
+        return (ptr_->called_);
+    }
+
+    /// \brief Return instance of callback name
+    std::string& name() {
+        return (ptr_->name_);
+    }
+
+private:
+    boost::shared_ptr<PrivateData>  ptr_;   ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// \param stream Stream on which the server is reading data
+// \param server_cb Structure in which server data is held.
+void
+serverRead(TlsStreamImpl& stream, TLSCallback& server_cb) {
+
+    // As we may need to read multiple times, keep a count of the cumulative
+    // amount of data read and do successive reads into the appropriate part
+    // of the buffer.
+    //
+    // Note that there are no checks for buffer overflow - this is a test
+    // program and we have sized the buffer to be large enough for the test.
+    server_cb.cumulative() = 0;
+
+    bool complete = false;
+    while (!complete) {
+
+        // Read block of data and update cumulative amount of data received.
+        server_cb.length() = stream.read_some(
+            boost::asio::buffer(server_cb.data() + server_cb.cumulative(),
+                                TLSCallback::MIN_SIZE - server_cb.cumulative()));
+        server_cb.cumulative() += server_cb.length();
+
+        // If we have read at least two bytes, we can work out how much we
+        // should be reading.
+        if (server_cb.cumulative() >= 2) {
+            server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
+            if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+                // Amount of data read from stream equals the size of the
+                // message (as indicated in the first two bytes of the message)
+                // plus the size of the count field.  Therefore we have received
+                // all the data.
+                complete = true;
+            }
+        }
+    }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TLSSocket, processReceivedData) {
+    // Amount of "real" data in the buffer
+    const uint16_t PACKET_SIZE = 16382;
+
+    // Used to instantiate socket
+    IOService service;
+    TlsContextPtr context(new TlsContext(CLIENT));
+    // Socket under test
+    TLSSocket<TLSCallback> test(service, context);
+    // Buffer to check
+    uint8_t inbuff[PACKET_SIZE + 2];
+    // Where data is put
+    OutputBufferPtr outbuff(new OutputBuffer(16));
+    // Expected amount of data
+    size_t expected;
+    // Where to put next data
+    size_t offset;
+    // Cumulative data received
+    size_t cumulative;
+
+    // Set some dummy values in the buffer to check
+    for (size_t i = 0; i < sizeof(inbuff); ++i) {
+        inbuff[i] = i % 256;
+    }
+
+    // Check that the method will handle various receive sizes.
+    writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
+
+    cumulative = 0;
+    offset = 0;
+    expected = 0;
+    outbuff->clear();
+    bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+                                             expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(1, cumulative);
+    EXPECT_EQ(1, offset);
+    EXPECT_EQ(0, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Now pretend that we've received one more byte.
+    complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+                                        outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(0, outbuff->getLength());
+
+    // Add another two bytes.  However, this time note that we have to offset
+    // in the input buffer because it is expected that the next chunk of data
+    // from the connection will be read into the start of the buffer.
+    complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+                                        offset, expected, outbuff);
+    EXPECT_FALSE(complete);
+    EXPECT_EQ(4, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(2, outbuff->getLength());
+
+    const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+    // And add the remaining data.  Remember that "inbuff" is "PACKET_SIZE + 2"
+    // long.
+    complete = test.processReceivedData(inbuff + cumulative,
+                                        PACKET_SIZE + 2 - cumulative,
+                                        cumulative, offset, expected, outbuff);
+    EXPECT_TRUE(complete);
+    EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+    EXPECT_EQ(0, offset);
+    EXPECT_EQ(PACKET_SIZE, expected);
+    EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+    dataptr = static_cast<const uint8_t*>(outbuff->getData());
+    EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a TLSSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TLSSocket, sequenceTest) {
+
+    // Common objects.
+    // Service object for async control
+    IOService service;
+
+    // The client - the TLSSocket being tested
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    // Socket under test
+    TLSSocket<TLSCallback> client(service, client_ctx);
+    // Async I/O callback function
+    TLSCallback client_cb("Client");
+    // Where client receives message from
+    TCPEndpoint client_remote_endpoint;
+    // Received data is put here
+    OutputBufferPtr client_buffer(new OutputBuffer(128));
+    // The server - with which the client communicates.
+    // Address of target server
+    IOAddress server_address(SERVER_ADDRESS);
+    // Server callback
+    TLSCallback server_cb("Server");
+    // Endpoint describing server
+    TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+    // Address where server received message from
+    TCPEndpoint server_remote_endpoint;
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    // Stream used for server.
+    TlsStreamImpl server(service.get_io_service(), server_ctx->getContext());
+
+    // Step 1.  Create the connection between the client and the server.  Set
+    // up the server to accept incoming connections and have the client open
+    // a channel to it.
+
+    // Set up server - open socket and queue an accept.
+    server_cb.queued() = TLSCallback::ACCEPT;
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.setCode(42);  // Some error
+    tcp::acceptor acceptor(service.get_io_service(),
+                           tcp::endpoint(tcp::v4(), SERVER_PORT));
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    acceptor.async_accept(server.lowest_layer(), server_cb);
+
+    // Set up client - connect to the server.
+    client_cb.queued() = TLSCallback::OPEN;
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.setCode(43);  // Some error
+    EXPECT_FALSE(client.isOpenSynchronous());
+    client.open(&server_endpoint, client_cb);
+
+    // Run the open and the accept callback and check that they ran.
+    while ((server_cb.called() == TLSCallback::NONE) ||
+           (client_cb.called() == TLSCallback::NONE)) {
+        service.run_one();
+    }
+    EXPECT_EQ(TLSCallback::ACCEPT, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+
+    EXPECT_EQ(TLSCallback::OPEN, client_cb.called());
+
+    // On some operating system the async_connect may return EINPROGRESS.
+    // This doesn't necessarily indicate an error. In most cases trying
+    // to asynchronously write and read from the socket would work just
+    // fine.
+    if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+        ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+            << " as a result of async_connect, got " << client_cb.getCode();
+    }
+
+    // Perform handshake.
+    client_cb.queued() = TLSCallback::HANDSHAKE;
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.setCode(43);  // Some error
+    client.handshake(client_cb);
+
+    server_cb.queued() = TLSCallback::HANDSHAKE;
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.setCode(42);  // Some error
+    server.async_handshake(ssl::stream_base::server, server_cb);
+
+    while ((server_cb.called() == TLSCallback::NONE) ||
+           (client_cb.called() == TLSCallback::NONE)) {
+        service.run_one();
+    }
+    EXPECT_EQ(TLSCallback::HANDSHAKE, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+
+    EXPECT_EQ(TLSCallback::HANDSHAKE, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+
+    // Step 2.  Get the client to write to the server asynchronously.  The
+    // server will loop reading the data synchronously.
+
+    // Write asynchronously to the server.
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.queued() = TLSCallback::WRITE;
+    client_cb.setCode(143);  // Arbitrary number
+    client_cb.length() = 0;
+    client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+    // Wait for the client callback to complete. (Must do this first on
+    // Solaris: if we do the synchronous read first, the test hangs.)
+    while (client_cb.called() == TLSCallback::NONE) {
+        service.run_one();
+    }
+
+    // Synchronously read the data from the server.;
+    serverRead(server, server_cb);
+
+    // Check the client state
+    EXPECT_EQ(TLSCallback::WRITE, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+    // ... and check what the server received.
+    EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+    EXPECT_TRUE(equal(OUTBOUND_DATA,
+                (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+                (server_cb.data() + 2)));
+
+    // Step 3.  Get the server to write all the data asynchronously and have the
+    // client loop (asynchronously) reading the data.  Note that we copy the
+    // data into the server's internal buffer in order to precede it with a two-
+    // byte count field.
+
+    // Have the server write asynchronously to the client.
+    server_cb.called() = TLSCallback::NONE;
+    server_cb.queued() = TLSCallback::WRITE;
+    server_cb.length() = 0;
+    server_cb.cumulative() = 0;
+
+    writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TLSCallback::MIN_SIZE);
+    copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+        (server_cb.data() + 2));
+    boost::asio::async_write(server,
+                             boost::asio::buffer(server_cb.data(),
+                                                 (sizeof(INBOUND_DATA) + 2)),
+                             server_cb);
+
+    // Have the client read asynchronously.
+    client_cb.called() = TLSCallback::NONE;
+    client_cb.queued() = TLSCallback::READ;
+    client_cb.length() = 0;
+    client_cb.cumulative() = 0;
+    client_cb.expected() = 0;
+    client_cb.offset() = 0;
+
+    client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE,
+                        client_cb.offset(), &client_remote_endpoint,
+                        client_cb);
+
+    // Run the callbacks. Several options are possible depending on how ASIO
+    // is implemented and whether the message gets fragmented:
+    //
+    // 1) The send handler may complete immediately, regardless of whether the
+    // data has been read by the client.  (This is the most likely.)
+    // 2) The send handler may only run after all the data has been read by
+    // the client. (This could happen if the client's TCP buffers were too
+    // small so the data was not transferred to the "remote" system until the
+    // remote buffer has been emptied one or more times.)
+    // 3) The client handler may be run a number of times to handle the message
+    // fragments and the server handler may run between calls of the client
+    // handler.
+    //
+    // So loop, running one handler at a time until we are certain that all the
+    // handlers have run.
+
+    bool server_complete = false;
+    bool client_complete = false;
+    while (!server_complete || !client_complete) {
+        service.run_one();
+
+        // Has the server run?
+        if (!server_complete) {
+            if (server_cb.called() != TLSCallback::NONE) {
+
+                // Yes.  Check that the send completed successfully and that
+                // all the data that was expected to have been sent was in fact
+                // sent.
+                EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+                EXPECT_EQ(0, server_cb.getCode());
+                EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+                server_complete = true;
+            }
+        }
+
+        // Has the client run?
+        if (!client_complete) {
+
+            if (client_cb.called() == TLSCallback::NONE) {
+                // No. Run the service another time.
+                continue;
+            }
+
+            // Client callback must have run.  Check that it ran OK.
+            EXPECT_EQ(TLSCallback::READ, client_cb.called());
+            EXPECT_EQ(0, client_cb.getCode());
+
+            // Check if we need to queue another read, copying the data into
+            // the output buffer as we do so.
+            client_complete = client.processReceivedData(client_cb.data(),
+                                                         client_cb.length(),
+                                                         client_cb.cumulative(),
+                                                         client_cb.offset(),
+                                                         client_cb.expected(),
+                                                         client_buffer);
+
+            // If the data is not complete, queue another read.
+            if (!client_complete) {
+                client_cb.called() = TLSCallback::NONE;
+                client_cb.queued() = TLSCallback::READ;
+                client_cb.length() = 0;
+                client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE ,
+                                    client_cb.offset(), &client_remote_endpoint,
+                                    client_cb);
+            }
+        }
+    }
+
+    // Both the send and the receive have completed.  Check that the received
+    // is what was sent.
+
+    // Check the client state
+    EXPECT_EQ(TLSCallback::READ, client_cb.called());
+    EXPECT_EQ(0, client_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+    EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+    // ... and check what the server sent.
+    EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+    EXPECT_EQ(0, server_cb.getCode());
+    EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+    // ... and that what was sent is what was received.
+    const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+    EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+                      received));
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
diff --git a/src/lib/asiolink/tests/tls_unittest.cc b/src/lib/asiolink/tests/tls_unittest.cc
new file mode 100644 (file)
index 0000000..78bb35b
--- /dev/null
@@ -0,0 +1,809 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/crypto_tls.h>
+#include <asiolink/botan_tls.h>
+#include <asiolink/openssl_tls.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/testutils/test_tls.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#ifdef WITH_OPENSSL
+#include <openssl/opensslv.h>
+#endif
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::cryptolink;
+using namespace std;
+
+// Test if we can get a client context.
+TEST(TLSTest, clientContext) {
+    TlsContextPtr ctx;
+    ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT)));
+}
+
+// Test if we can get a server context.
+TEST(TLSTest, serverContext) {
+    TlsContextPtr ctx;
+    ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::SERVER)));
+}
+
+// Test if the cert required flag is handled as expected.
+TEST(TLSTest, certRequired) {
+    auto check = [] (TlsContext& ctx) -> bool {
+#ifdef WITH_BOTAN
+        // Implement it.
+        return (ctx.getCertRequired());
+#else // WITH_OPENSSL
+        ::SSL_CTX* ssl_ctx = ctx.getNativeContext();
+        if (!ssl_ctx) {
+            ADD_FAILURE() << "null SSL_CTX";
+            return (false);
+        }
+        int mode = SSL_CTX_get_verify_mode(ssl_ctx);
+        switch (mode) {
+        case SSL_VERIFY_NONE:
+            return (false);
+        case (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT):
+            return (true);
+        default:
+            ADD_FAILURE() << "unknown ssl_verify_mode: " << mode;
+            return (false);
+        }
+#endif
+    };
+
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_TRUE(ctx.getCertRequired());
+    EXPECT_TRUE(check(ctx));
+    ASSERT_NO_THROW(ctx.setCertRequired(false));
+    EXPECT_FALSE(ctx.getCertRequired());
+    EXPECT_FALSE(check(ctx));
+    ASSERT_NO_THROW(ctx.setCertRequired(true));
+    EXPECT_TRUE(ctx.getCertRequired());
+    EXPECT_TRUE(check(ctx));
+}
+
+// Test if the certificate authority can be loaded.
+TEST(TLSTest, loadCAFile) {
+    string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+    TlsContext ctx(TlsRole::CLIENT);
+    ASSERT_NO_THROW(ctx.loadCaFile(ca));
+}
+
+// Test that no certificate authority gives an error.
+TEST(TLSTest, loadNoCAFile) {
+    string ca("/no-such-file");
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW_MSG(ctx.loadCaFile(ca), LibraryError,
+                     "No such file or directory");
+}
+
+#ifdef WITH_BOTAH
+// Test that a directory can't be loaded with Botan.
+TEST(TLSTest, loadCAPath) {
+    string ca(TEST_CA_DIR);
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW(ctx.loadCaPath(ca), NotImplemented);
+}
+#else // WITH_OPENSSL
+// Test that a directory can be loaded.
+TEST(TLSTest, loadCAPath) {
+    string ca(TEST_CA_DIR);
+    TlsContext ctx(TlsRole::CLIENT);
+    ASSERT_NO_THROW(ctx.loadCaPath(ca));
+}
+#endif
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadKeyCA) {
+    string ca(string(TEST_CA_DIR) + "/kea-ca.key");
+    TlsContext ctx(TlsRole::CLIENT);
+#ifdef WITH_OPENSSL
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    EXPECT_THROW_MSG(ctx.loadCaFile(ca), LibraryError,
+                     "no certificate or crl found");
+#endif
+#endif
+}
+
+// Test if the end entity certificate can be loaded.
+TEST(TLSTest, loadCertFile) {
+    string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+    TlsContext ctx(TlsRole::CLIENT);
+    ASSERT_NO_THROW(ctx.loadCertFile(cert));
+}
+
+// Test that no end entity certificate gives an error.
+TEST(TLSTest, loadNoCertFile) {
+    string cert("/no-such-file");
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW_MSG(ctx.loadCertFile(cert), LibraryError,
+                     "No such file or directory");
+}
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadCsrCertFile) {
+    string cert(string(TEST_CA_DIR) + "/kea-client.csr");
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW_MSG(ctx.loadCertFile(cert), LibraryError,
+                     "no start line");
+}
+
+// Test if the private key can be loaded.
+TEST(TLSTest, loadKeyFile) {
+    string key(string(TEST_CA_DIR) + "/kea-client.key");
+    TlsContext ctx(TlsRole::CLIENT);
+    ASSERT_NO_THROW(ctx.loadKeyFile(key));
+}
+
+// Test that no private key gives an error.
+TEST(TLSTest, loadNoKeyFile) {
+    string key("/no-such-file");
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError,
+                     "No such file or directory");
+}
+
+// Test that a private key is wanted.
+TEST(TLSTest, loadCertKeyFile) {
+    string key(string(TEST_CA_DIR) + "/kea-client.crt");
+    TlsContext ctx(TlsRole::CLIENT);
+    EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError,
+                     "no start line");
+}
+
+// Test that the certificate and private key must match.
+TEST(TLSTest, loadMismatch) {
+    string cert(string(TEST_CA_DIR) + "/kea-server.crt");
+    TlsContext ctx(TlsRole::SERVER);
+    EXPECT_NO_THROW(ctx.loadCertFile(cert));
+    string key(string(TEST_CA_DIR) + "/kea-client.key");
+    // In fact OpenSSL checks only RSA key values...
+    // The explicit check function is SSL_CTX_check_private_key.
+    EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError,
+                     "key values mismatch");
+}
+
+// Test the configure class method.
+TEST(TLSTest, configure) {
+    TlsContextPtr ctx;
+    string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+    string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+    string key(string(TEST_CA_DIR) + "/kea-client.key");
+    EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::CLIENT,
+                                          ca, cert, key, true));
+    ASSERT_TRUE(ctx);
+    EXPECT_EQ(TlsRole::CLIENT, ctx->getRole());
+    EXPECT_TRUE(ctx->getCertRequired());
+
+#ifdef WITH_OPENSSL
+    // Retry using the directory and the server.
+    ca = TEST_CA_DIR;
+    cert = string(TEST_CA_DIR) + "/kea-server.crt";
+    key = string(TEST_CA_DIR) + "/kea-server.key";
+    EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::SERVER,
+                                          ca, cert, key, false));
+    ASSERT_TRUE(ctx);
+    EXPECT_EQ(TlsRole::SERVER, ctx->getRole());
+    EXPECT_FALSE(ctx->getCertRequired());
+
+#endif
+
+    // The error case.
+    cert = "/no-such-file";
+    key = string(TEST_CA_DIR) + "/kea-client.key";
+    EXPECT_THROW_MSG(TlsContext::configure(ctx, TlsRole::CLIENT,
+                                           ca, cert, key, true),
+                     LibraryError,
+                     "No such file or directory");
+    // The context is reseted on errors.
+    EXPECT_FALSE(ctx);
+}
+
+// Define a callback class.
+namespace { // anonymous namespace.
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Class of test callbacks.
+class Callback {
+public:
+    /// @brief State part.
+    class State {
+    public:
+        /// @brief Constructor.
+        State() : called_(false), error_code_() {
+        }
+
+        /// @brief Destructor.
+        virtual ~State() {
+        }
+
+        /// @brief Called flag.
+        ///
+        /// Initialized to false, set to true when the callback is called.
+        bool called_;
+
+        /// @brief Last error code.
+        boost::system::error_code error_code_;
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Used to shared pointer to state to allow the callback object to
+    /// be copied keeping the state member values.
+    Callback() : state_(new State()) {
+    }
+
+    /// @brief Destructor.
+    virtual ~Callback() {
+    }
+
+    /// @brief Callback function (one argument).
+    ///
+    /// @parame ec Boost completion code.
+    void operator()(const boost::system::error_code& ec) {
+        state_->called_ = true;
+        state_->error_code_ = ec;
+    }
+
+    /// @brief Callback function (two arguments).
+    ///
+    /// @parame ec Boost completion code.
+    void operator()(const boost::system::error_code& ec, size_t) {
+        state_->called_ = true;
+        state_->error_code_ = ec;
+    }
+
+    /// @brief Get called value.
+    inline bool getCalled() const {
+        return (state_->called_);
+    }
+
+    /// @brief Get error code.
+    inline const boost::system::error_code& getCode() const {
+        return (state_->error_code_);
+    }
+
+protected:
+    /// @brief Pointer to state.
+    boost::shared_ptr<State> state_;
+};
+
+} // end of anonymous namespace.
+
+// Test if we can get a stream.
+TEST(TLSTest, stream) {
+    IOService service;
+    TlsContextPtr ctx;
+    ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT)));
+    boost::scoped_ptr<TlsStream<Callback> > st;
+    ASSERT_NO_THROW(st.reset(new TlsStream<Callback>(service, ctx)));
+}
+
+namespace { // anonymous namespace.
+} // end of anonymous namespace.
+
+// Test what happens when handshake is forgotten.
+TEST(TLSTest, noHandshake) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    TlsStream<Callback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    Callback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Send on the client.
+    char send_buf[] = "some text...";
+    Callback send_cb;
+    async_write(client, boost::asio::buffer(send_buf), send_cb);
+    while (!send_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(send_cb.getCode());
+    EXPECT_EQ("uninitialized", send_cb.getCode().message());
+
+    // Receive on the server.
+    vector<char> receive_buf(64);
+    Callback receive_cb;
+    server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+    while (!receive_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(receive_cb.getCode());
+    EXPECT_EQ("uninitialized", receive_cb.getCode().message());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the server was not configured.
+TEST(TLSTest, serverNotConfigured) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+    // Skip config.
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx;
+    test::configClient(client_ctx);
+    TlsStream<Callback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    Callback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform TLS handshakes.
+    Callback server_cb;
+    server.handshake(server_cb);
+    Callback client_cb;
+    client.handshake(client_cb);
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+#ifndef LIBRESSL_VERSION_NUMBER
+    string server_expected("no shared cipher");
+#else
+    string server_expected("sslv3 alert handshake failure");
+#endif
+    EXPECT_EQ(server_expected, server_cb.getCode().message());
+    EXPECT_TRUE(client_cb.getCode());
+    EXPECT_EQ("sslv3 alert handshake failure", client_cb.getCode().message());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client was not configured.
+TEST(TLSTest, clientNotConfigured) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT));
+    // Skip config.
+    TlsStream<Callback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    Callback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform TLS handshakes.
+    Callback server_cb;
+    server.async_handshake(ssl::stream_base::server, server_cb);
+    Callback client_cb;
+    client.async_handshake(ssl::stream_base::client, client_cb);
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+    EXPECT_EQ("tlsv1 alert unknown ca", server_cb.getCode().message());
+    EXPECT_TRUE(client_cb.getCode());
+#ifndef LIBRESSL_VERSION_NUMBER
+    string client_expected("certificate verify failed");
+#else
+    string client_expected("tlsv1 alert unknown ca");
+#endif
+    EXPECT_EQ(client_expected, client_cb.getCode().message());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client is HTTP (vs HTTPS).
+TEST(TLSTest, clientHTTPnoS) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    tcp::socket client(service.get_io_service());
+
+    // Connect to.
+    client.open(tcp::v4());
+    Callback connect_cb;
+    client.async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform server TLS handshake.
+    Callback server_cb;
+    server.async_handshake(ssl::stream_base::server, server_cb);
+
+    // Client sending a HTTP GET.
+    char send_buf[] = "GET / HTTP/1.1\r\n";
+    Callback client_cb;
+    client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+#ifndef LIBRESSL_VERSION_NUMBER
+    string server_expected("http request");
+#else
+    string server_expected("tlsv1 alert protocol version");
+#endif
+    EXPECT_EQ(server_expected, server_cb.getCode().message());
+    EXPECT_FALSE(client_cb.getCode());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client does not use HTTP nor HTTP.
+TEST(TLSTest, unknownClient) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part.
+    tcp::socket client(service.get_io_service());
+
+    // Connect to.
+    client.open(tcp::v4());
+    Callback connect_cb;
+    client.async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform server TLS handshake.
+    Callback server_cb;
+    server.async_handshake(ssl::stream_base::server, server_cb);
+
+    // Client sending something which is not a TLS ClientHello.
+    char send_buf[] = "hello my server...";
+    Callback client_cb;
+    client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+#ifdef WITH_OPENSSL
+#ifndef LIBRESSL_VERSION_NUMBER
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    string server_expected("wrong version number");
+#else
+    string server_expected("unknown protocol");
+#endif
+#else
+    string server_expected("tlsv1 alert protocol version");
+#endif
+#endif
+    EXPECT_EQ(server_expected, server_cb.getCode().message());
+    EXPECT_FALSE(client_cb.getCode());
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a certificate from another CA.
+TEST(TLSTest, anotherClient) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part using a certificate signed by another CA.
+    TlsContextPtr client_ctx;
+    test::configOther(client_ctx);
+    TlsStream<Callback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    Callback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform TLS handshakes.
+    Callback server_cb;
+    server.async_handshake(ssl::stream_base::server, server_cb);
+    Callback client_cb;
+    client.async_handshake(ssl::stream_base::client, client_cb);
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+    // Full error is:
+    // error 20 at 0 depth lookup:unable to get local issuer certificate
+#ifdef WITH_OPENSSL
+#ifndef LIBRESSL_VERSION_NUMBER
+    string server_expected("certificate verify failed");
+#else
+    string server_expected("tlsv1 alert unknown ca");
+#endif
+    EXPECT_EQ(server_expected, server_cb.getCode().message());
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    EXPECT_FALSE(client_cb.getCode());
+#endif
+#endif
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a self-signed certificate.
+TEST(TLSTest, selfSigned) {
+    IOService service;
+
+    // Server part.
+    TlsContextPtr server_ctx;
+    test::configServer(server_ctx);
+    TlsStream<Callback> server(service, server_ctx);
+
+    // Accept a client.
+    tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+                                          SERVER_PORT));
+    tcp::acceptor acceptor(service.get_io_service(), server_ep);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+    Callback accept_cb;
+    acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+    // Client part using a self-signed certificate.
+    TlsContextPtr client_ctx;
+    test::configSelf(client_ctx);
+    TlsStream<Callback> client(service, client_ctx);
+
+    // Connect to.
+    client.lowest_layer().open(tcp::v4());
+    Callback connect_cb;
+    client.lowest_layer().async_connect(server_ep, connect_cb);
+
+    // Run accept and connect.
+    while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+        service.run_one();
+    }
+
+    // Verify the error codes.
+    if (accept_cb.getCode()) {
+        FAIL() << "accept error " << accept_cb.getCode().value()
+               << " '" << accept_cb.getCode().message() << "'";
+    }
+    // Possible EINPROGRESS for the client.
+    if (connect_cb.getCode() &&
+        (connect_cb.getCode().value() != EINPROGRESS)) {
+        FAIL() << "connect error " << connect_cb.getCode().value()
+               << " '" << connect_cb.getCode().message() << "'";
+    }
+
+    // Perform TLS handshakes.
+    Callback server_cb;
+    server.async_handshake(ssl::stream_base::server, server_cb);
+    Callback client_cb;
+    client.async_handshake(ssl::stream_base::client, client_cb);
+    while (!server_cb.getCalled() || !client_cb.getCalled()) {
+        service.run_one();
+    }
+    EXPECT_TRUE(server_cb.getCode());
+    // Full error is:
+    // error 18 at 0 depth lookup:self signed certificate
+#ifdef WITH_OPENSSL
+#ifndef LIBRESSL_VERSION_NUMBER
+    string server_expected("certificate verify failed");
+#else
+    string server_expected("tlsv1 alert unknown ca");
+#endif
+    EXPECT_EQ(server_expected, server_cb.getCode().message());
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    EXPECT_FALSE(client_cb.getCode());
+#endif
+#endif
+
+    // Used when adding other error cases.
+#if 0
+    cerr << "server: '" << server_cb.getCode().message() << "'\n";
+    cerr << "client: '" << client_cb.getCode().message() << "'\n";
+#endif
+
+    // Close client and server.
+    EXPECT_NO_THROW(client.lowest_layer().close());
+    EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/asiolink/testutils/ca/0c7eedb9.0 b/src/lib/asiolink/testutils/ca/0c7eedb9.0
new file mode 120000 (symlink)
index 0000000..bedcce5
--- /dev/null
@@ -0,0 +1 @@
+kea-server.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/26d052a5.0 b/src/lib/asiolink/testutils/ca/26d052a5.0
new file mode 120000 (symlink)
index 0000000..6b786a1
--- /dev/null
@@ -0,0 +1 @@
+kea-self.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/3071e5ff.0 b/src/lib/asiolink/testutils/ca/3071e5ff.0
new file mode 120000 (symlink)
index 0000000..25804e5
--- /dev/null
@@ -0,0 +1 @@
+kea-client.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/a465d731.0 b/src/lib/asiolink/testutils/ca/a465d731.0
new file mode 120000 (symlink)
index 0000000..15909dd
--- /dev/null
@@ -0,0 +1 @@
+kea-other.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/ad950210.0 b/src/lib/asiolink/testutils/ca/ad950210.0
new file mode 120000 (symlink)
index 0000000..fbadecc
--- /dev/null
@@ -0,0 +1 @@
+kea-server-addr.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/d3d11a5f.0 b/src/lib/asiolink/testutils/ca/d3d11a5f.0
new file mode 120000 (symlink)
index 0000000..7ceeb6f
--- /dev/null
@@ -0,0 +1 @@
+kea-ca.crt
\ No newline at end of file
diff --git a/src/lib/asiolink/testutils/ca/doc b/src/lib/asiolink/testutils/ca/doc
new file mode 100644 (file)
index 0000000..280dc7f
--- /dev/null
@@ -0,0 +1,81 @@
+Similar to doc/examples/https/nginx/kea-nginx.conf
+ password is keatest
+ Country Name is US
+ Organization Name is ISC Inc.
+ Common Name is the key name.
+
+1 - create a CA self signed certificate (password is keatest)
+ openssl genrsa -aes128 -out kea-ca.key 4096
+ openssl req -new -x509 -days 3650 -key kea-ca.key -out kea-ca.crt
+
+2 - create a key for the client and decipher it
+ openssl genrsa -aes128 -out kea-client-aes.key 2048
+ openssl rsa -in kea-client-aes.key -out kea-client.key
+ rm kea-client-aes.key
+
+3 - create a certificate for the client
+ openssl req -new -key kea-client.key -out kea-client.csr
+ openssl x509 -req -days 3650 -in kea-client.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 10 -out kea-client.crt -sha256
+
+4 - create a PKCS#12 bundle on macOS (password is keatest)
+ openssl pkcs12 -in kea-client.crt -inkey kea-client.key -export \
+  -out kea-client.p12
+
+5 - create a key for the server and decipher it (same than 2)
+ openssl genrsa -aes128 -out kea-server-aes.key 2048
+ openssl rsa -in kea-server-aes.key -out kea-server.key
+ rm kea-server-aes.key
+
+6 - create a certificate with a subject alternate name set to localhost
+ for the server
+ openssl req -new -key kea-server.key -out kea-server.csr \
+  -config server-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 20 -out kea-server.crt \
+  -extfile ext-conf.cnf -sha256
+
+7 - create a certificate with a subject alternate name set to 127.0.0.1
+ and ::1 for the server
+ openssl req -new -key kea-server.key -out kea-server-addr.csr \
+  -config server-addr-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server-addr.csr -CA kea-ca.crt \
+  -CAkey kea-ca.key -set_serial 30 -out kea-server-addr.crt \
+  -extfile ext-addr-conf.cnf -sha256
+
+8 - use c_rehash or openssl rehash to create hashes
+
+Setup the control agent: kea-ctrl-agent.json sample.
+
+Using curl.
+Note the localhost is important: using 127.0.0.1 instead can make the
+subjectAltName check to fail. curl is also picky about http vs https.
+
+to send a command (e.g. list-commands) directly to the control agent
+listening at port 8000:
+
+curl -D - -X POST -H Content-Type:application/json \
+ -d '{ "command": "list-commands" }' http://localhost:8000
+
+With the CA only (so authenticating the server only):
+curl -D - -X POST -H Content-Type:application/json --cacert kea-ca.crt \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+With mutual authentication using OpenSSL:
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.crt --key kea-client.key \
+
+With the mutual authentication on macOS (when the OpenSSL one fails):
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.p12:keatest --cert-type P12 \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+To the control agent:
+echo | kea-shell
+
+With server authentication only:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost
+
+With the mutual authentication:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost \
+ --cert kea-client.crt --key kea-client.key
diff --git a/src/lib/asiolink/testutils/ca/ext-addr-conf.cnf b/src/lib/asiolink/testutils/ca/ext-addr-conf.cnf
new file mode 100644 (file)
index 0000000..a6b78c1
--- /dev/null
@@ -0,0 +1 @@
+subjectAltName=IP:127.0.0.1,IP:::1
diff --git a/src/lib/asiolink/testutils/ca/ext-conf.cnf b/src/lib/asiolink/testutils/ca/ext-conf.cnf
new file mode 100644 (file)
index 0000000..aafe5bd
--- /dev/null
@@ -0,0 +1 @@
+subjectAltName=DNS:localhost
diff --git a/src/lib/asiolink/testutils/ca/kea-ca.crt b/src/lib/asiolink/testutils/ca/kea-ca.crt
new file mode 100644 (file)
index 0000000..cfb8c53
--- /dev/null
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIE3jCCAsYCCQDVzhmZelXOXDANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJV
+UzERMA8GA1UECgwISVNDIEluYy4xDzANBgNVBAMMBmtlYS1jYTAeFw0yMDA2MTEx
+MzU3MzhaFw0zMDA2MDkxMzU3MzhaMDExCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhJ
+U0MgSW5jLjEPMA0GA1UEAwwGa2VhLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAvKQ/vJpJnXjZ+/LxZNfPc/QYSChSEQ8qoxh8prBYvPXyDu9ORHOa
+Dtd5AWusQLCI3iNYMDaJwrazj0g91jPKcxfvFZbnzFHTAZrDnmJwcTw96UfrP4b7
+PyXpUSF1/YfDf+/M3C7Wm9IJ/e704XHln/vFCw2dR/N5VOrXXJRcCd5NOES/ICXe
+xe62Mv7OjUQS8u6ovejtaaMkvoV2hGSG2LXdgVOCv0U8ybRs03Xl8BVM4lFYVO9H
+jnQ7O9AeGMqebvuyNAyGK9Dv+ERu65M9hB+pW//d+tVv3Dkfou+d5cOXPFXjf6vI
+K+2ClxkBH4A5dhsRJ7vPI41mwXA+H0g+MzxJ8Lg0pzJuLher03RZq3pBHvEc/jek
+P4u6mPrc+5J84jQ0hFwH4XIpxaKJsUiE/r1nFDiWRV27PgXMQgEbjdotxFX4IDBN
+KPtQNrybxiQHsYoZPdKcEfh8XyVT4NHrcbqN1SNf2ZIfDkm09aeDYXDdINAD+0yZ
+E+3YMeH4oWPpOIfW4OVzEDyfBGHyo2klTZfI5zdd54Kp4dKkzSlmIPC7OubdZZGo
+SlZfUlWVcRkqMbUAsZ8H2sdz0l+4k8+VmyiA4EWAiO6SV5xmYSncPQIN5dE2PbIx
+jKosl9JGhajs2gxCqlK+ZA3zgoFHhG1mKGWW7ucMic8Jy4oEq1XsoI0CAwEAATAN
+BgkqhkiG9w0BAQsFAAOCAgEAert/+ovFSWtRWKbFZNXs/o9ElWtVp+dxbOtgUNIS
+hdfLSHt/9nXw2FuBrvonDnTtl0kPhci1Qcwd5uAErlgddE6k27kcjOesMuXtwUke
+LLb7UQG7TQy3KmB+ARnG+toNTm2d8I5420+VDLqU1oh++x9l9KpWsDENSNeTDulT
+lVTJ7fVOTK7++NTCmqrp+Ublri3a2aoTK4pkt1ymcdIGD/kyCNeZro3/CKooV4yU
+xyTMBV0Huyu9V6OldtKtfbP2sWrQn5McRY1/18wJVTMq+OV5EI73R4bn/frfwl8o
+k8x8PH/ulozK+Pk4wz3z2NdT+ckSIfFs0RtVLW2MF1+8kJyt/9u8yUNfcw7MvNor
+94Zr86Mg0ZsHlXgeFfLm7h9dB/lQ5mtotrXfH4C4zltjPz17xouBSuZjZhgbkLaJ
+s4nPxWwxM/tN1mSYuVkiwq+qOz8ooePZh7zzEwpDiHr1tgzXxKojDcNC2uvVRTw/
+DKq8htcEb9kFyvDzxTq6zbvuNIyvzmpseEnpYxuzHFqCQtbN54Q88HuyebJlbxEI
+0BNb74yxvAQj3P+KS8xY9iqPExBeMiQu85eGmpTtKSnNjP1i09bg+xOVlESOeUPE
+cEe2ZsdEBwVaoCvjl5vbt2eJfpdt3UEwg6zfyncxjMZka/315B7d7k5qIEqsD5KO
+HXQ=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-ca.key b/src/lib/asiolink/testutils/ca/kea-ca.key
new file mode 100644 (file)
index 0000000..4ac82d3
--- /dev/null
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,1E4500430B45CC59A1AFA62E20D0632E
+
+L1O4pVdZnk9nHSyH4fkoEehRNfhE4xbYt28YMtVctjeOQQWCf6m89k/rtOlSb9c6
+82WMHWiACuWNGxcd3RLZl0dWTPZYE4xk6T3TzTk/GwkDbQRf/6hfzGcRnObVRGYq
+kzBq6zXtoqFbq2jAACqCSoRlZgpLOv8hUdUcnto707iT0ebmwbNgPsxCBXjvxOYO
+Pvkihpfd7QY5GD8fn14y/y/im/9sqZgpNfhEVeO//Dpo1Nvo6DasU1gTnEoOkRRK
+/IBl12N4FxdiAjg16SfDw/M3/uka6ftekdr4PwD616qiUsBdKsuslp9aN82k+5RK
+X3iuODmMc/42SUoSskbL5mkuroOZxihwbiKsejcmGOfVygYXuZ9a9tLHLsdKLoWO
+1mmTMU4fzNpwXPor4h0yEDaortX2KwBVPnSWOMCJtwreukgt0GHfePfbd08Ojf6M
+pyZZ7gVv/q573RSgQL6nipU+4Il6T+cK4Iwdui9WSFahiOKgALuhTX0eY7CmlfcR
+hgNqmJhXEuXbEiQONcDA7iEAggdha4W3bm8blCj7QEBpr45fAyDSZxP/dNrIoZWC
+BxbrTq+YqzLyhUOOE7THdR5qpCha5Tsoyv8n7K91v77wZjmL1poyqHbXqvWDIJni
++LAPJDd6/Z0lqXLyTV3U9FcE6cAz6kkl5J1aeWFzfWSPtdiSzMPFkaz1MUPPllHF
+nyoA1R8PAD1yPj2accSIi8nBMYpOUrwMZcS+MbSW4GsbPEOqkluLgLLas/H9eohp
+SdyPsSnNBmWaCAwNHGWRAyRRefeMsrjtlF2AfVMsrCIzUNiSiw0MHsZQV6zlI23i
+/xyYxMn3fDmMxqJCJ8FkEHxVx5SeyzbysYmCfBsquKnfzE8JAyjmRQzdqfXHt5H9
+MEctsLiTQ+WPwWMN/6zHjuJMpJFZTfK/y0RUgTUyf02t0C4Bobx30DOx0SM4B7Rx
+QQ7uwMlarE8Pg7tCDA0kC2aGCSaHo2u0qssmLVGhNKNkBVKkr7SpS4CM7dcIh+Yk
+30Q4UQfCzRbS17RD1LfdUg+SPCeDFoKdh4f4FVoHXrbeEOhPJVeCjPli78nnPuZ0
+kGvndf/v+4DH40Wvt5aZj90mes6q+2Hy4GlgciELEWhMcj2QSiRISNi5UFNYRsSL
+RsEhuksONQVrFnRS3n3WvQrZ8X4OLAfatlFewpR9UVvgfWXLuWLy6etDWa056wDa
+4OW715YaEedSsF8WrfhRXmU/IDJ19oiQzsQiyeiKoFW3OVRyf2ngb8psUOwLbgA6
+kjcrzt77RsYKlP7TYC2hvycqnvvDhKCe6yQmd6vS1lOdBm8VZWzJCGFfoeucx4i2
+DS5ryWhU9d4VoCxFYEEsNhC8GKkrcATikhLnB8riJgt5PrJenYMBd9EsuwAo3Xaa
++95SeiAdka2XIN2dBDOJ4qAJYKhHyZF/fJpJP/1s3zGsdBN3mkY3C1C3/dYR1fan
+7fK9Qx2fcZjeMTkdm91Ito7ui2LQDVjJoTEaZ0LyMh3Gz7hALuDfPeS3Eft3QXMB
+Do3Tki68lvtc9DadlDQfTm84WvS4BVyOhQVQqhS2Ttq+ICGrNekPg1zyMUI2N0bo
+8ulenrCKStFBqgyWq1aczcLNEDth0GWOFjLdgWUwI2pcN3tuouLHXpfKKARxxdis
+Un3Dj5nhg6G2vGhTTTRdxMQeiT0Dr6Q2tD9VUNojVZwJ1c50dgZ6hlhzU5pv+1vU
+krBjlx9szF2ikx2pUp8RHDAziKkv17zXDjvEJpE/pvYWHBfBPoQr5NPaPGYnbFIX
+qaLYtWOAFlL3BI1XSO/32nYee0+WjnKMr4IOvXJfnaa94S+wU6pJEbTGHP+1aGNS
+wsslmcfRDmmeblGd40Bo4ENCc93KxBf3V7g7/JnSUZO39TyfvMnyy1E3JC6fu/A4
+VvnlnFM+6ZjdhkiZ4RJqd2rc2AhA6HhOslJSa0kPRc6UQQqAci+7YHZBc/PELhpD
+LpFbBXbqyi1jNQNodhhJtkD8VkvYHOisqzHFTITZp5epK8mjLkBhIW2VUVZ+dDK+
+3kFrKB+CaEvE1OBAlDYeVxMAvT1rmyjT04mqPRnp0G57+5VQQFYrKfVevDddLIt2
+tQphIcgZYAHTU+2otlPAOXqgPJWRoKNTw6Rtc6dELrAOE/kDFqZ4VKRnXRNFmxj3
+NSC8zapuNmkGQTo8CHzJuRI8sfNHjcDrMELHV1Fe8XSoqdovV2X+Xa/fesCaYfrp
+6506uFGZSR7SrMdT5MoXGri1IEvGXkGI30UDq5QTEzHiyyYgC7kZFn3E/zREbA0y
+/WahS8zICLsEK2ZknSv3q6e9aONokNbYu7PqvQtW5IPGrjdZxuQDtRXEYafiDLKT
+c3h9eE8OKk5Si49TRjsYbuR4+BBw9N0R0RIfs5TIDkkGeCu0M4yFPKQVhCN98OAk
+h0L+ZhQJZfbDE7QNBuvmRBNcpJYe7JTXl2/p6JjoxeyZTgShk81BiOmMCaWavKB+
+gIqy4X39y+J+AiYMiKy/+B5gtNaZaE9hka7RH2tV5nkiTBilZ6v5N1A4V4Q0PRFT
+HZAXgnUwI0HcIRfkqxlF3gXMzhG1+K2wxS9uVn5K0E27xNeswr+ksfLJsyWz+gdT
+/ZFgGyErUY6CLmYzmW+WfQox+qd9pd1TMISNuBWXrdoKkX8iFjj8SWyPcZvqMUkx
+lo8RVzb/6ugSTcbCQGpf+6H8ZuOe9hZwD9tKBh6XZbC5KtBQ8TtSnrmsk9ufIzn8
+ACrJFTVOG4u/g/xn1j3MY4NIaLA77YSCed+TzOXBPmG+LrJM67n1tMtGWEPoOnGi
+6pzJpF5cxsF4i0QoqdYFThqMb6mHtaVPsjjIpdzEXmYyQENLQECERE6lYlz9ZVkS
+NsOR3KMOxXZQ+iWmqCptazz0hVVmEBFisg6K6WuQR3BpXcf8N9UP7xUnStlUUaQ7
+G5nf6BZl3AIxZPay/NoM87n4I4lplPaQwyK/ReMztu78OQFyx9mC1BGOHxVtF6hO
+W+POZqc7ugCXiY8A08vSv5yt8paWDnU+hHXnEo04Hw0ex2KNOOZeL0Eg+idJTZe0
+/0yl0olct0HUgSyhU3wm0uWiHwulreoa3tNL+a4Xt7k5L2e5XcvAh3T2mgxzDq5q
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.crt b/src/lib/asiolink/testutils/ca/kea-client.crt
new file mode 100644 (file)
index 0000000..1a0565c
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID2jCCAcICARQwDQYJKoZIhvcNAQELBQAwMTELMAkGA1UEBhMCVVMxETAPBgNV
+BAoMCElTQyBJbmMuMQ8wDQYDVQQDDAZrZWEtY2EwHhcNMjEwMjIxMTg0MDQzWhcN
+MzEwMjE5MTg0MDQzWjA1MQswCQYDVQQGEwJVUzERMA8GA1UECgwISVNDIEluYy4x
+EzARBgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfzG1k1
+6qMB5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PEl47e
+4Ae3Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyGJKXS
+uqCsaXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJSRKaG
+9hVRrAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zqqTgT
+p1MvttSv8JEN6OMhM+bpCbaiWp4DAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAElh
+o7srSKP+6qiuzXR1eWDgTXcZa13Zj1z78Ipr3GnnoKJLLa+X69nHkA8fC2nP+Y6h
+2COdlPn/JVJ20ZKMkmC+VnnBklAe7zGnd83cmiOm12kj8lGUwQ/muDW3GU402WBT
+3CZubevUGgVIZv3fYcw1l3t1Q6eNASRr/xY40a9QvGAilKQSvZKdbIuYbAoMEbpX
+yCErSCVPxHcIjVDghIx/Jsn2RXg+yehpRgtCO/DM9E7/6q7yhb6jMrUqujCE40cc
+5TuBexXZsXH1x/Ic7mcwVDgAfGMm9j5a5isyIh7+uCItNpGlTOQIIx80wZbVHVyx
+9IpUA+IInq2rK5LGp4otXGODAN9wbMBrMX0VTQlY2DZ24Vr5L6sykmHkOSELaWvW
+2M0bNU16NUPxRUoWDkG99AwqT2ZnKMnsYqwayWMiQu/s1ek9zs01Pf4YFf5w659I
+YHgAVhd5gSmxcJ3VTilgUaYE9DRAKY3GVFkliTlGYM55khyJYdASWGijHI14hs4W
+TZQWebbaoaKNtEq+5omj7HsNLrWfKe6EQrn9z7PY+96ZbSZsbt34/tmsVpmTrOFB
+BV/iU3uEJGvAucI0VXgguKN3jmw2hWstHzEWEMHm107Vp3QPWmrHzvcosAxLsKpg
+WyHjO3AiUQOsP9NPOy9Owr/XJCcSbf5k4MuFDLXi
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.csr b/src/lib/asiolink/testutils/ca/kea-client.csr
new file mode 100644 (file)
index 0000000..89c3d02
--- /dev/null
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICejCCAWICAQAwNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRMw
+EQYDVQQDDAprZWEtY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAym7A5BJaI7xYVbdt+BkDhFVbnRKeLPCmJw0E2abHXonaz98mPqln8xtZNeqj
+AeY1xe3UYc6uBYj22TLOXM9wX8GQ5cY1jMYIsT2gq1aYWCxoSPADp3vDxJeO3uAH
+twpfenVlH0CkJhiM8x303CbwsVDgqG+c2Dgg77Y5OPCMXbx4tfAG/4V8hiSl0rqg
+rGlwUSuwJp/P48TIHYXbPdrTcZtAYgCbbJpEJvUFvM7XKIhBIDuFYg0SUkSmhvYV
+UawHuBj4rDo1M3qwA5D1/6XIRyKpq0yfWCoZ4Dnw3YgGWNZZGXNXGzOs6qk4E6dT
+L7bUr/CRDejjITPm6Qm2olqeAwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAMgA
+4PiiHLAdo5tjjEWyPOsVCaKORAB8PqELc9XJHfZeyyCEDRptQfH//XKe7WRZmbZI
+baq1cqjZFVb8yrMjBr1mXUOuBzmofexaXwFEMOufirUawenqGeivkIW23j+Jq6vX
+xs2jlXdqE7H6ApXo5De0NhnpeNQS+88xDfQvcaqPYw5TmOrAtPrGt42vSa0x0vf6
+OnnFnOFEFh6AFfj6Sg6SWeNOn61RgUR5iqPkQsH33o/viTqKL4qITroFUHmau7Ec
+BimeigqvKOMS785BxmXeYl2qEg9Vu4zaFePAHPPpjIA7LELfXdM/B6TOP9/aCMEd
+NhQVPAUOXFxCnBHWo84=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.key b/src/lib/asiolink/testutils/ca/kea-client.key
new file mode 100644 (file)
index 0000000..8a5bf94
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAym7A5BJaI7xYVbdt+BkDhFVbnRKeLPCmJw0E2abHXonaz98m
+Pqln8xtZNeqjAeY1xe3UYc6uBYj22TLOXM9wX8GQ5cY1jMYIsT2gq1aYWCxoSPAD
+p3vDxJeO3uAHtwpfenVlH0CkJhiM8x303CbwsVDgqG+c2Dgg77Y5OPCMXbx4tfAG
+/4V8hiSl0rqgrGlwUSuwJp/P48TIHYXbPdrTcZtAYgCbbJpEJvUFvM7XKIhBIDuF
+Yg0SUkSmhvYVUawHuBj4rDo1M3qwA5D1/6XIRyKpq0yfWCoZ4Dnw3YgGWNZZGXNX
+GzOs6qk4E6dTL7bUr/CRDejjITPm6Qm2olqeAwIDAQABAoIBAQCiT9OSC7UC2k3p
+zL38I/JR9S8T7o2tcveGcEXnTnalMtujuUUtESAsKU2KkaxKJZAQN/YGxnV6Mqva
+04XrnNh5rvbDhf4B9feaRdPCDhjw+gpUET6c1/vMcck4o2EMPuD3i5GdUXNMqHq7
+pNVkgESVqEe6RmP4amjeS7nuEdI6hSqFQa7EelC7C7HIIxz/jpiHRYk2pp5o3wx3
+EEyyhR+Ip3+U9EOlGZyqvasaGf6PYgBC2pTjitVP7+ArxokBTx1/VfNmVgOT+A+2
+kkqg4Ee3sgmBGjy0aUatr/QOSEELnJw9cHZLIapklDo+cS/ypSWiGASGUvCyjmBm
+VDg/DDDBAoGBAPFXAR1NwmVNs5mU5LA9kgs8Pz/d4LAOa3CrUEFjBSMrfAkB3Je8
+0x0Xmht2QIRVPQ2NFklM07aqToWMtxPSoLoGlovgTEAtcyLWnRrANlhd0VwG0m62
+YlRkIrRcS5m1yS+EKETCEWnsGoCrdYbBdpKJVoNd4pxAHXYgjlzKLPH5AoGBANa6
+tz0RiP6GHU7yONR0yXEYmLhniWnE9A+5UEKjEt4ZOo4rDxocBZSENPwMf7576Vv3
+kTuL4aSkBPA5DxBsjOq/CT3o3Fng2aRLNL37glYrVLAsNIPs/YVWuLJZ2fXJZMbG
+PbR2SVhXU8YvQaY3s6OlzfQ07Zd4T5TUnoMpDA7bAoGAJ5638R6d5lGeRX1bGc/R
+1QRcAdkkFOMZIlMNht6BrmdqInRqyYJXSjRguVdtegwgTMQ3v2rcauWEpIoYWCnA
+9ykzt9znx7VubG69NfIOOe2U/D2meER62g3iYKeyRZbBY4qXrcoKX9BB/ZOoZKoI
+FEB1snVMSYiBDa6EkJkkTckCgYBeU3UtAWfxjw6O4H6wbYEUCl8EGo9VhCxGP/yO
+2T3vjJuZWjbvHEIjRJRV6FOxZJNVUAJfawo7HcYBlL8WUujwMe0oYgNyBAD3WAYa
+MsLFgZFZNoH3NgMEMN0/k5LYkpiPbQQsIw4DHZFybM3k63EhQTOgxCNet34V/fSH
+318powKBgH/QdL/jSMUV4DGnPtayzTEszjgNsqt7SPkWvKtA+K+EoX2rlpZf65RI
+Mei9BawHU2H4rfCN3QTqimHt2/xNKyCowF+a4fRLPz8bDqOqiWMPZeD+PscWSrKq
+r3TDUNfttWQvdE5x1nct20T4dQ9FY1w8MgcsouBbmhFoWYDQOfuO
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.p12 b/src/lib/asiolink/testutils/ca/kea-client.p12
new file mode 100644 (file)
index 0000000..008252b
Binary files /dev/null and b/src/lib/asiolink/testutils/ca/kea-client.p12 differ
diff --git a/src/lib/asiolink/testutils/ca/kea-other.crt b/src/lib/asiolink/testutils/ca/kea-other.crt
new file mode 100644 (file)
index 0000000..dbb6366
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID1zCCAb8CARQwDQYJKoZIhvcNAQELBQAwMzELMAkGA1UEBhMCVVMxETAPBgNV
+BAoMCElTQyBJbmMuMREwDwYDVQQDDAhvdGhlci1jYTAeFw0yMTAyMjEyMTE4NDda
+Fw0zMTAyMTkyMTE4NDdaMDAxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhJU0MgSW5j
+LjEOMAwGA1UEAwwFb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xmleGD+bwyHZFE
+sHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG+R08p3giURrJ
+zUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k2nlH3EuqvB3G
+BEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbBCAVrQsgcADNq
+jR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcUCUNfjRv5E2lW
++mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJKoZIhvcNAQELBQADggIBACK3Dl0s
+NmwPSNQuVH9d/fgL954ephn+GAsNamOLL9gFwZmHxVzHJ93GnbrVsTtvWFa2w+Tc
+jDGzRvbhMMh4bnKOJ4OUzn2ISQOyWdhBWnSKVt9kiunP7Jn8ufH4WOpkeP1FKXRg
+xMgGcK/3oOn2OV2Nj9BT5Wm8MPytVdsY1e3XCbBcRfYB1acNE0Q6Fx9/u0OxGNQ2
+ITRUQb5T1yoI6Isq2bhdW1hdl3O5DPcjkzDBQlqxXOUoZeLKuXeQlPxV+rELO52o
+z48CL/Y1jOhmplM7aUCNNxdObZZ0ym1OCEGo6yNCkGil6ErEgmVE7IrGaLMwbAHj
+eylbIcAieaAuc1w858nRbB+ryND9BWj5G0+B3xhuX3Xs4bfDKxPayytf9ixfshrn
+9TR0g3GIV+lIVOuCmQtf5H2eZ2wTxd09f6Sglh7WMm/RNEd+E53n3dnksjt9X/Lc
+27Q513x6IudXTreJMQdAstna/Aftjm4KN+zMc1JEYXPyiwN3UEFk8gfBstbg3Tb9
+ioA/yX4E1Jim7HHqB4eoTcmv4nz0kYrNtFxp4xAoy1qYc8afFOyborWdCqZFbERO
+JQJop0aVSm7tR95nhcd1O3ZgxMjzT6jOIw7P/DxCBQqz5xwXThpcYboK5z4cdWe7
+QAsCwmY3m4pbVvxtY9xl+LtEqNt7/eFXqstV
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-other.key b/src/lib/asiolink/testutils/ca/kea-other.key
new file mode 100644 (file)
index 0000000..6c1714f
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwKBBDVkOrZemmkTJ/smjVCkwSCFqVaMufrhrAXWVvvnxL8OM
+ZpXhg/m8Mh2RRLBz6LP7+81lDRWl9mlg9B/h5OJdlkxYoFjt7rT6Fw8wzo30JoJS
+hvkdPKd4IlEayc1ClsHv0USQbO6l3XA/cjV1Tm9iVqeAsZNYj4+800vyxrBa+xeP
+5Np5R9xLqrwdxgRFx2v7AOecYoDMr6lZ1W+KzYfj4xi/JeUmc0p623aCBLSsPy1W
+wQgFa0LIHAAzao0eyC26niKoNU3ddUL0Mwe3/1OXUKED86t4+KMNczomkqJGRO1H
+FAlDX40b+RNpVvppsjO55IMhPS6lCf97nSIdbwIDAQABAoIBAGFKT7D7MzOwbeBr
+MA71Lv5aE13LKtb4JYqxgLJq/mhH+26heO9zckTjRQ+W6YFlEhYNmg8TLZ/POFTd
+4q9KzyB4yMYZ54WNhRdU5x/wdzlMb21n02ECv6ab+0xx5HLarnBQodzzoXKzkqjm
+gdUgdRBZp7SWw6RtBIT2F2wtZwC8v9TbLVjgYanK+sU1NlVJ86kg8WuPaST7kmaR
+I+BtFXapq5SP/MI5dJki3WNlalzJUIUiRtmqkSointJIEL1Jx1RGonPWDTQtmq8V
+/86eXOFEoqMXpK5gpKACl6amxbMBks3BViUHq/p7wpCF/c4a/t2xZ+P3ZAyuBg+P
+QLcNJCkCgYEA3sP4hAyoENqG0BVVttloXk0rhN7A7AXZ6Hd2EWYCJ+1X+0Mmjas6
+DnnPEIBN6DLthHSisiw8jfP9yAQlas1CHliSzdwgregzSt+PIRCXNxXNoYm7/xGu
+E0OK2cjcYmCX1fOp3WLyjEEjJh8/ZQys64wf8dS2gQbjuKi1M4Cu78MCgYEA3V0s
+CaPTMwng76kLVMTWzFyiTnxKhbrGlvzYnW0vTJ6YSJnLEKGzH3Df4e+K9mh/sRhw
+ZOaT3nH1P6iuPHcxFp3K1qaUU3yfVXlOVWJZ5LyP5hwyiaqBGRMnEapVAkb5bvAx
+qhDh3pVu2Qo3Bg+A3JEQ5mQmng+/DsSpIsrtjOUCgYAYthiNXXIPXI5z6sn1XKyt
+OVZIiQVRqVyA4y+fwncewr9tygcu0/2+uVh09iauSWf7t4yMw0d8X8KZO4yDCn35
+K84tM+wUHpwCBEa2XkbH/40uDD9kjiuHS4jNm/CGoTx2qW8Adgd984PYqMK5jOxp
+vMOWaghMy9zbESv4qJ+/TQKBgGBLcKW841n3eScoNSqp+fqqbVyRCuYDqvHxidVp
+ssK01u/2HvTcMoyB0JJ7Xsr0CGAkjngGKdsBXbtUiH77Jpp9B8i6bBmpQ7Kt81qH
+Ty2GrV5fc8iZKFGdGEjB/Prhnw4YZLJjZ64o+TBnFiqHwfmxDVX7ySTHGsi02hKt
+jgrVAoGBAKF0Z+KdT95HbS12J0wLJ4LAyFRMxQJ2+A17ryt6MwN5lICMBh0IosHD
+fpgh5pd4ZDJ5pA0seG2pGwFLkPhCM5EuumikU713SGboxkjS8ozfUkJGiXyg2C1t
+9lpsU6MKC4eTMg6WlTjBXoFc3seXP83mNKjy7Rn/qqIDHKH7WXke
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-self.crt b/src/lib/asiolink/testutils/ca/kea-self.crt
new file mode 100644 (file)
index 0000000..a062c4c
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAdACCQC3T0mkbypFVTANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJV
+UzERMA8GA1UECgwISVNDIEluYy4xFDASBgNVBAMMC3NlbGYtc2lnbmVkMB4XDTIx
+MDIxODE4MzA0MloXDTMxMDIxNjE4MzA0MlowNjELMAkGA1UEBhMCVVMxETAPBgNV
+BAoMCElTQyBJbmMuMRQwEgYDVQQDDAtzZWxmLXNpZ25lZDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMCgQQ1ZDq2XpppEyf7Jo1QpMEghalWjLn64awF1
+lb758S/DjGaV4YP5vDIdkUSwc+iz+/vNZQ0VpfZpYPQf4eTiXZZMWKBY7e60+hcP
+MM6N9CaCUob5HTyneCJRGsnNQpbB79FEkGzupd1wP3I1dU5vYlangLGTWI+PvNNL
+8sawWvsXj+TaeUfcS6q8HcYERcdr+wDnnGKAzK+pWdVvis2H4+MYvyXlJnNKett2
+ggS0rD8tVsEIBWtCyBwAM2qNHsgtup4iqDVN3XVC9DMHt/9Tl1ChA/OrePijDXM6
+JpKiRkTtRxQJQ1+NG/kTaVb6abIzueSDIT0upQn/e50iHW8CAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEAnhmEeDZv9IJL5Vv8K9Ltb8WzCaH7faSd/wKW5qqh+odeUJHk
+mZN8gwBaL8VSrXiCGKgTexn5Uc4PgxAbK887t3Q0BUIleOHG5mvQ7/0+uBtGEp72
+PSSsIHL7osiSMTi142ppY2/LpUfP7I65Z1lpaThdJu2YgxjVeoFZI+L3ubzVM6M0
+V/yBrK/vZMVVQv4tkCgte3jX/XH7aQ/+OK1xB9oyOqe7yShMrPS6oFLmvGjWMqQO
+/NMPxqsGWH/EZeTVmPP8+zw7/s2mnHrdqMLkHO6/sEPAdgyxrjcDLqtIdNgoMdBz
+2sH8t4L5qGTKQjDIJ8Zam6O9lJJhZ18D6Rqtwg==
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-self.key b/src/lib/asiolink/testutils/ca/kea-self.key
new file mode 100644 (file)
index 0000000..6c1714f
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwKBBDVkOrZemmkTJ/smjVCkwSCFqVaMufrhrAXWVvvnxL8OM
+ZpXhg/m8Mh2RRLBz6LP7+81lDRWl9mlg9B/h5OJdlkxYoFjt7rT6Fw8wzo30JoJS
+hvkdPKd4IlEayc1ClsHv0USQbO6l3XA/cjV1Tm9iVqeAsZNYj4+800vyxrBa+xeP
+5Np5R9xLqrwdxgRFx2v7AOecYoDMr6lZ1W+KzYfj4xi/JeUmc0p623aCBLSsPy1W
+wQgFa0LIHAAzao0eyC26niKoNU3ddUL0Mwe3/1OXUKED86t4+KMNczomkqJGRO1H
+FAlDX40b+RNpVvppsjO55IMhPS6lCf97nSIdbwIDAQABAoIBAGFKT7D7MzOwbeBr
+MA71Lv5aE13LKtb4JYqxgLJq/mhH+26heO9zckTjRQ+W6YFlEhYNmg8TLZ/POFTd
+4q9KzyB4yMYZ54WNhRdU5x/wdzlMb21n02ECv6ab+0xx5HLarnBQodzzoXKzkqjm
+gdUgdRBZp7SWw6RtBIT2F2wtZwC8v9TbLVjgYanK+sU1NlVJ86kg8WuPaST7kmaR
+I+BtFXapq5SP/MI5dJki3WNlalzJUIUiRtmqkSointJIEL1Jx1RGonPWDTQtmq8V
+/86eXOFEoqMXpK5gpKACl6amxbMBks3BViUHq/p7wpCF/c4a/t2xZ+P3ZAyuBg+P
+QLcNJCkCgYEA3sP4hAyoENqG0BVVttloXk0rhN7A7AXZ6Hd2EWYCJ+1X+0Mmjas6
+DnnPEIBN6DLthHSisiw8jfP9yAQlas1CHliSzdwgregzSt+PIRCXNxXNoYm7/xGu
+E0OK2cjcYmCX1fOp3WLyjEEjJh8/ZQys64wf8dS2gQbjuKi1M4Cu78MCgYEA3V0s
+CaPTMwng76kLVMTWzFyiTnxKhbrGlvzYnW0vTJ6YSJnLEKGzH3Df4e+K9mh/sRhw
+ZOaT3nH1P6iuPHcxFp3K1qaUU3yfVXlOVWJZ5LyP5hwyiaqBGRMnEapVAkb5bvAx
+qhDh3pVu2Qo3Bg+A3JEQ5mQmng+/DsSpIsrtjOUCgYAYthiNXXIPXI5z6sn1XKyt
+OVZIiQVRqVyA4y+fwncewr9tygcu0/2+uVh09iauSWf7t4yMw0d8X8KZO4yDCn35
+K84tM+wUHpwCBEa2XkbH/40uDD9kjiuHS4jNm/CGoTx2qW8Adgd984PYqMK5jOxp
+vMOWaghMy9zbESv4qJ+/TQKBgGBLcKW841n3eScoNSqp+fqqbVyRCuYDqvHxidVp
+ssK01u/2HvTcMoyB0JJ7Xsr0CGAkjngGKdsBXbtUiH77Jpp9B8i6bBmpQ7Kt81qH
+Ty2GrV5fc8iZKFGdGEjB/Prhnw4YZLJjZ64o+TBnFiqHwfmxDVX7ySTHGsi02hKt
+jgrVAoGBAKF0Z+KdT95HbS12J0wLJ4LAyFRMxQJ2+A17ryt6MwN5lICMBh0IosHD
+fpgh5pd4ZDJ5pA0seG2pGwFLkPhCM5EuumikU713SGboxkjS8ozfUkJGiXyg2C1t
+9lpsU6MKC4eTMg6WlTjBXoFc3seXP83mNKjy7Rn/qqIDHKH7WXke
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-addr.crt b/src/lib/asiolink/testutils/ca/kea-server-addr.crt
new file mode 100644 (file)
index 0000000..f0efe82
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAfOgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xDzANBgNVBAMMBmtlYS1jYTAeFw0yMTAyMjEyMTE1
+MTdaFw0zMTAyMTkyMTE1MTdaMDoxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhJU0Mg
+SW5jLjEYMBYGA1UEAwwPa2VhLXNlcnZlci1hZGRyMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzD
+LIMNzlabxu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0
+g0oyCiWFpJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxB
+H8r5GJtQiJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmA
+vfKCj+ILMS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rB
+Xh9dNZB52U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABoyUwIzAhBgNVHREE
+GjAYhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4ICAQAz
+qHfXZGJ7fBZPEvOhAHrPyNTrf66tqNavGG+xkSIZ8R58SaS2JHQO/YvmX1vZ+kHI
+AM1DiqEFiT2/yeuh1OrzSN58L5n6mHxfkaCDFW3y7dmm8P34G36CqRY2YxiXH20Z
+pr+rAz2rVoopWxpsIY0hzuXytBF/ZDGLNv6NsrJzK6joM+WM23ugQ9krOy79whf5
+rrvVERygbnaTP+gXOrc1KePaw5YFEdGAdbOHxR2/1j+xvyNtdO5eqZr78SX81dr3
+YE6O3NHz+aHf3zJ5f0cXABSe9ZDqCnBaBvF3vj5Hd0BGdZ5TuM+tKLEQDCivdC2K
+89cthsPpCqTzednw3Q/mEuIqyJg6F6grQR1/qapMf8OlVa6LQXhTL9RVe6M6sg3u
+DuAnrdrFDcmxP2dGL4oycxA2t82lnJ3vjfl329PQmZ3iEVQJDuitQGsymv0nnqos
+S9YCqPpTnk6ADnv9RLqHuCyKJLgERy5RO/pMM9rjB0mqtNtSQgVW55Or2byFL1qR
+IKQIQWneP7VLAXaNKUQNbyDzTCTo7QZTHivDgC03E/36vKz2wWbcl4u/oBxkEa+H
+LORk7RNnXQJRXQms7rydJQk9osBWio0hsuZddkwuetUzkverWOSdofqAtN+xZUBt
+lZYE+em29aBd6/wektxqJriVZxpaQxyrSMKF+jQtmA==
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-addr.csr b/src/lib/asiolink/testutils/ca/kea-server-addr.csr
new file mode 100644 (file)
index 0000000..d6ba063
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICyzCCAbMCAQAwOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRgw
+FgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMsgw3OVpvG
+7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SDSjIKJYWk
+ksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEfyvkYm1CI
+kZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC98oKP4gsx
+LjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFeH101kHnZ
+T1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGgTDBKBgkqhkiG9w0BCQ4xPTA7
+MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCEGA1UdEQQaMBiHBH8AAAGHEAAAAAAA
+AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBADlAkM7Vt3acIbgx9uz/nzEU
+biTUETzQnCU/mJZU+F8nuZtIlH9TAej4oT0J1uBuneGdkgGSm3lONUNxYJ7Uz8dm
+wyudv4cpvtacAzPqZNb0aapX3qD9/lUbXfReoOUmt+asdmF2ncmn3l465ercxtUg
+zhbU5uQUEk7C7f4OZQ3b08yG+tblFhpO7Xm4JD6nJk9iQ6gB4WBUDSr7mdm7PMmV
+T8xesD7lDZVjSdXql9p/6YxJJR3360jycLXeTQbom6gfvsfQcs91yfGHRel2yoDx
+ZBcmjfkYK7mwagpB/QCsZDuC4cxZyFM7lV/ukIysviW7WzrtT9mvfTEcTqmPsPU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.crt b/src/lib/asiolink/testutils/ca/kea-server.crt
new file mode 100644 (file)
index 0000000..d5314bb
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+TCCAeGgAwIBAgIBFDANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xDzANBgNVBAMMBmtlYS1jYTAeFw0yMTAyMjEyMTEz
+MzZaFw0zMTAyMTkyMTEzMzZaMDUxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhJU0Mg
+SW5jLjETMBEGA1UEAwwKa2VhLXNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL3kUYCDdEvKHgV1WiBJ7BtQyrg8yiqi6C3QUDX6GayswyyDDc5W
+m8bttIfNmPzlNNhHRM2q/fDrLc4JSFopihVSNDpotALop1JL/euNLY6DtINKMgol
+haSSyfFjheI+75mVFrkkt1bYZQfjC1pTmecwAsj8lj6OGdp6B2mI34kMQR/K+Rib
+UIiRmqYFkW3siPpvN+244pqc+ldQBnmx73/s3dS4/swznM6d/D6iBlG5gL3ygo/i
+CzEuOsGI4x07whuVkghv0pJHq9hwCTNnDXBD5kKgDqQJJhUNhuOm1weKwV4fXTWQ
+edlPUJMjxRyq5zZ+NNAf8QRjSqMk0mGy0BMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJ
+bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4ICAQBXVoyYSkLjFHKwAutjN8E0SWdX
+tBuEPtYwB9lZrGx+B36x4dufxl5NBeUvNLvmJrJlZC4tnrdXzWQBTNX1wKjaNcbM
+nNMvcczFUo/H7mJJys3vVxQx2kCkMwjgOjA/VsFjcgGF5zK630o2+0fdLYEmoe2J
+OgOz304HccOS8j888p0Sfh4y3v4ZvvZ2uwrWXjVtehGa1Yy6iBNrrczmiVRBcOrh
+GSw4kw+p+BuLZ2VK98DWD5FzW1+9+a7pEJA5Zt/ru88wm+/FK5SpysIykq/2CY6G
+pHyBh361es+4gRobg0ApSkldqmd3TubWyQj9zXV98qghhyT0DuQN3KIAF/RMloq2
+dQHVK6a6h7hPk876/FSGILwKw3yxaXFYmkoUpv2bnEBtMvNYgrkoxp6zpxFforKa
+VVz6NhpkKg1iG4wEc7rot60IlRUfqPFX68sQfkcOXezuj6Qdkl41sfBSQIyUcPkH
+OOQ9Mi1Rn/pPMm/kHXwJthcuVcLP006eS5zFaU5ejicx+nT2L0YS/eyNygq6jubJ
+4Xm2QcX2be3LNyWwiGWPw1CqOCxpGFIgY1Z9cyORGL12KyZ5sPxFdwUWI2RTXLOn
+mjDYzyR8cByql0QZSO7neH/QSrQyfVeDxawbWJCK9VimAKqxUXWuAqjqtfF42XGM
+xbwIHFtwsd+04XA8xQ==
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.csr b/src/lib/asiolink/testutils/ca/kea-server.csr
new file mode 100644 (file)
index 0000000..458b369
--- /dev/null
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICuTCCAaECAQAwNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRMw
+EQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlabxu20h82Y
+/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWFpJLJ8WOF
+4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQiJGapgWR
+beyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+ILMS46wYjj
+HTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB52U9QkyPF
+HKrnNn400B/xBGNKoyTSYbLQEwIDAQABoD8wPQYJKoZIhvcNAQkOMTAwLjAJBgNV
+HRMEAjAAMAsGA1UdDwQEAwIF4DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI
+hvcNAQELBQADggEBAECqICoEZb0XeGwoBedtG2Exb4RUeoTAfL24q5a8cOtv0+Mw
+i7y9LNihtRqP2kzhoZ7IhzSUZGVuh4BIUywpJHuWfM9b+fe+hxSGdqCeULKS3InK
+4RWRh9jr12L7hEKfAG7VtL03/+Lm5DHLr47X6RkeZ5GwP29qqLwJcrK9qeFi26Bs
+TrEafPInhF7PgyFjH2YVZVotNaOFMRvwEQwAMtuF7SAqRHr+8VHXP3yi9UjHvxRs
+BpbVD6fEWNkLLJhoSqERgjWnsFlU3O+kj9R+iKA+6arxr4d+HS+dyYitFtVJaR6C
+0+De9msTbJmn+2mu4zQ09Sdf0pN5lb/I3pgcbLU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.key b/src/lib/asiolink/testutils/ca/kea-server.key
new file mode 100644 (file)
index 0000000..9f0a30e
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMN
+zlabxu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oy
+CiWFpJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5
+GJtQiJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKC
+j+ILMS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9d
+NZB52U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABAoIBAHYZ4nbDSzk346QR
+cJRUZXw3q10ascICv9R+kuR/QCic6mZsu9FxHEGE5ZrwzA29oDhDWw9vIbENE9fo
+/g7VdwP7tG//XrXQbQBKMwqlK4hPcZ2WL7kPzSamBOMUutgTvIEQsKlFpKFmxK87
+DnyHMlkPCyxlrCwLxRiUfLeuYRiiau/3mAHUneJiYTL+pqQVkeznSkWq1cYp9A7n
+Gy4+3lxzyj3ru///S28V4UShh53PgPD5ghWKPqLJYbS6Zc1T8c0mtg/cbwZBUyuX
+W8AkT/ifYuJEBn3oQWluI2acwV7Yy+onRgFOsfZwUiX1oQ7keNh7D5+5XJ6CpZxK
+xtIpw0ECgYEA8wW6O07AQfA0dNNyMbHijfVabpeG1FKfqZeIhAnbYLwU1DJiipHl
+fyNDo7wM0AM27puohMAjo16ZfacUseruIiUSvv8bcWQE9g3XN7RGqpfJHHPlRI2V
+WT0iHEkjkMOkq4viufWcxGz/nw4BZERBnYmAOANmbwXX7ZnVbCnrjEUCgYEAyAhD
+PPNUTQGTcME98lVPohHvtekuaqA415otCtHA330I4mG8I3XGYbYymXYnmbIzy0KH
++ZjMZdTXiWmqPYIh0P7ZOeXKXNe8ZTedCwfX+1wGpjk01KCIzpdoS8X5WeN23/1t
+hoF+HTKdhRBQte68WFD36Dtb0r1Hwe+IKC8h7HcCgYB0i0mdSY3v0UcGw6Re6qTw
+WTqOEMLLLfh9tzrzv1pikLAYdzVEqOT7TKkSa4tlcjU0xpdRWmd84FARrz/Adx7O
+ZyMPT34UqderPEX648yD4RjEOVw4vQFjc2rZT8XrlbdxwTrw3TXaCT+pQmkucYFa
+EGfZ9N953L6Jpp1wKsZYVQKBgEwaiKpZ0YryvIu7mbvnJUL+G/tT2isLBlVQ/S4O
+m5jr00N997xuBKoMTbgBMhPRrs74Yw9dSPa9QbuwDesU5ZTEQRU8Df/AvJatz/vw
+YgXp/0Wioiz7XtFq3W1mxvWiCwoxO0hfYAHvzepgSLTPPa1EMO2UF91X0kNAxMa1
+F+0FAoGAAIcCoN3PazFWSsNMO4EfZf4VUgnTg9Dh3mMH8M3hEGybISSVKz5NILBC
+OKRKNLPLuj4TwTcurelNjMOUvkd/+yQgu1B9ImNuHdSvJjS9TzWCgZ26Q16woMzv
+yKeky514sst/1LtWuwiitmGS0rpKf3vIlkqcUE9WcLd3Hy/PxKg=
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/server-addr-conf.cnf b/src/lib/asiolink/testutils/ca/server-addr-conf.cnf
new file mode 100644 (file)
index 0000000..12a34f4
--- /dev/null
@@ -0,0 +1,355 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME                   = .
+RANDFILE               = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file              = $ENV::HOME/.oid
+oid_section            = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions           = 
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir            = ./demoCA              # Where everything is kept
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+#unique_subject        = no                    # Set to 'no' to allow creation of
+                                       # several ctificates with same subject.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+                                       # must be commented out to leave a V1 CRL
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem# The private key
+RANDFILE       = $dir/private/.rand    # private random number file
+
+x509_extensions        = usr_cert              # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions       = crl_ext
+
+default_days   = 365                   # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = sha256                # use SHA-256 by default
+preserve       = no                    # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy         = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName            = match
+stateOrProvinceName    = match
+organizationName       = match
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+####################################################################
+[ req ]
+default_bits           = 1024
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options. 
+# default: PrintableString, T61String, BMPString.
+# pkix  : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_default            = AU
+countryName_min                        = 2
+countryName_max                        = 2
+
+stateOrProvinceName            = State or Province Name (full name)
+#stateOrProvinceName_default   = Some-State
+
+localityName                   = Locality Name (eg, city)
+
+0.organizationName             = Organization Name (eg, company)
+0.organizationName_default     = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName            = Second Organization Name (eg, company)
+#1.organizationName_default    = World Wide Web Pty Ltd
+
+organizationalUnitName         = Organizational Unit Name (eg, section)
+#organizationalUnitName_default        =
+
+commonName                     = Common Name (e.g. server FQDN or YOUR name)
+commonName_max                 = 64
+
+emailAddress                   = Email Address
+emailAddress_max               = 64
+
+# SET-ex3                      = SET extension number 3
+
+[ req_attributes ]
+challengePassword              = A challenge password
+challengePassword_min          = 4
+challengePassword_max          = 20
+
+unstructuredName               = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1      # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir            = ./demoCA              # TSA root directory
+serial         = $dir/tsaserial        # The current serial number (mandatory)
+crypto_device  = builtin               # OpenSSL engine to use for signing
+signer_cert    = $dir/tsacert.pem      # The TSA signing certificate
+                                       # (optional)
+certs          = $dir/cacert.pem       # Certificate chain to include in reply
+                                       # (optional)
+signer_key     = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1           # Policy if request did not specify it
+                                       # (optional)
+other_policies = tsa_policy2, tsa_policy3      # acceptable policies (optional)
+digests                = md5, sha1             # Acceptable message digests (mandatory)
+accuracy       = secs:1, millisecs:500, microsecs:100  # (optional)
+clock_precision_digits  = 0    # number of digits after dot. (optional)
+ordering               = yes   # Is ordering defined for timestamps?
+                               # (optional, default: no)
+tsa_name               = yes   # Must the TSA name be included in the reply?
+                               # (optional, default: no)
+ess_cert_id_chain      = no    # Must the ESS cert id chain be included?
+                               # (optional, default: no)
+
+[ alt_name ]
+IP.1 = 127.0.0.1
+IP.2 = ::1
diff --git a/src/lib/asiolink/testutils/ca/server-conf.cnf b/src/lib/asiolink/testutils/ca/server-conf.cnf
new file mode 100644 (file)
index 0000000..843b641
--- /dev/null
@@ -0,0 +1,354 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME                   = .
+RANDFILE               = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file              = $ENV::HOME/.oid
+oid_section            = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions           = 
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir            = ./demoCA              # Where everything is kept
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+#unique_subject        = no                    # Set to 'no' to allow creation of
+                                       # several ctificates with same subject.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+                                       # must be commented out to leave a V1 CRL
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem# The private key
+RANDFILE       = $dir/private/.rand    # private random number file
+
+x509_extensions        = usr_cert              # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions       = crl_ext
+
+default_days   = 365                   # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = sha256                # use SHA-256 by default
+preserve       = no                    # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy         = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName            = match
+stateOrProvinceName    = match
+organizationName       = match
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = supplied
+emailAddress           = optional
+
+####################################################################
+[ req ]
+default_bits           = 1024
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options. 
+# default: PrintableString, T61String, BMPString.
+# pkix  : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_default            = AU
+countryName_min                        = 2
+countryName_max                        = 2
+
+stateOrProvinceName            = State or Province Name (full name)
+#stateOrProvinceName_default   = Some-State
+
+localityName                   = Locality Name (eg, city)
+
+0.organizationName             = Organization Name (eg, company)
+0.organizationName_default     = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName            = Second Organization Name (eg, company)
+#1.organizationName_default    = World Wide Web Pty Ltd
+
+organizationalUnitName         = Organizational Unit Name (eg, section)
+#organizationalUnitName_default        =
+
+commonName                     = Common Name (e.g. server FQDN or YOUR name)
+commonName_max                 = 64
+
+emailAddress                   = Email Address
+emailAddress_max               = 64
+
+# SET-ex3                      = SET extension number 3
+
+[ req_attributes ]
+challengePassword              = A challenge password
+challengePassword_min          = 4
+challengePassword_max          = 20
+
+unstructuredName               = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType                   = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment                      = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl             = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1      # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir            = ./demoCA              # TSA root directory
+serial         = $dir/tsaserial        # The current serial number (mandatory)
+crypto_device  = builtin               # OpenSSL engine to use for signing
+signer_cert    = $dir/tsacert.pem      # The TSA signing certificate
+                                       # (optional)
+certs          = $dir/cacert.pem       # Certificate chain to include in reply
+                                       # (optional)
+signer_key     = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1           # Policy if request did not specify it
+                                       # (optional)
+other_policies = tsa_policy2, tsa_policy3      # acceptable policies (optional)
+digests                = md5, sha1             # Acceptable message digests (mandatory)
+accuracy       = secs:1, millisecs:500, microsecs:100  # (optional)
+clock_precision_digits  = 0    # number of digits after dot. (optional)
+ordering               = yes   # Is ordering defined for timestamps?
+                               # (optional, default: no)
+tsa_name               = yes   # Must the TSA name be included in the reply?
+                               # (optional, default: no)
+ess_cert_id_chain      = no    # Must the ESS cert id chain be included?
+                               # (optional, default: no)
+
+[ alt_name ]
+DNS.1 = localhost
diff --git a/src/lib/asiolink/testutils/openssl_sample_client.cpp b/src/lib/asiolink/testutils/openssl_sample_client.cpp
new file mode 100644 (file)
index 0000000..e989ee2
--- /dev/null
@@ -0,0 +1,173 @@
+//
+// client.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+using boost::asio::ip::tcp;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+inline std::string CA_(const std::string& filename) {
+  return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+enum { max_length = 1024 };
+
+class client
+{
+public:
+  client(boost::asio::io_service& io_context,
+      boost::asio::ssl::context& context,
+      const tcp::resolver::results_type& endpoints)
+    : socket_(io_context, context)
+  {
+    socket_.set_verify_mode(boost::asio::ssl::verify_peer |
+                            boost::asio::ssl::verify_fail_if_no_peer_cert);
+    socket_.set_verify_callback(
+        std::bind(&client::verify_certificate, this, _1, _2));
+
+    connect(endpoints);
+  }
+
+private:
+  bool verify_certificate(bool preverified,
+      boost::asio::ssl::verify_context& ctx)
+  {
+    // The verify callback can be used to check whether the certificate that is
+    // being presented is valid for the peer. For example, RFC 2818 describes
+    // the steps involved in doing this for HTTPS. Consult the OpenSSL
+    // documentation for more details. Note that the callback is called once
+    // for each certificate in the certificate chain, starting from the root
+    // certificate authority.
+
+    // In this example we will simply print the certificate's subject name.
+    char subject_name[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
+    X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
+    std::cout << "Verifying " << subject_name << "\n";
+
+    return preverified;
+  }
+
+  void connect(const tcp::resolver::results_type& endpoints)
+  {
+    boost::asio::async_connect(socket_.lowest_layer(), endpoints,
+        [this](const boost::system::error_code& error,
+          const tcp::endpoint& /*endpoint*/)
+        {
+          if (!error)
+          {
+            handshake();
+          }
+          else
+          {
+            std::cout << "Connect failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void handshake()
+  {
+    socket_.async_handshake(boost::asio::ssl::stream_base::client,
+        [this](const boost::system::error_code& error)
+        {
+          if (!error)
+          {
+            send_request();
+          }
+          else
+          {
+            std::cout << "Handshake failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void send_request()
+  {
+    std::cout << "Enter message: ";
+    std::cin.getline(request_, max_length);
+    size_t request_length = std::strlen(request_);
+
+    boost::asio::async_write(socket_,
+        boost::asio::buffer(request_, request_length),
+        [this](const boost::system::error_code& error, std::size_t length)
+        {
+          if (!error)
+          {
+            receive_response(length);
+          }
+          else
+          {
+            std::cout << "Write failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  void receive_response(std::size_t length)
+  {
+    boost::asio::async_read(socket_,
+        boost::asio::buffer(reply_, length),
+        [this](const boost::system::error_code& error, std::size_t length)
+        {
+          if (!error)
+          {
+            std::cout << "Reply: ";
+            std::cout.write(reply_, length);
+            std::cout << "\n";
+          }
+          else
+          {
+            std::cout << "Read failed: " << error.message() << "\n";
+          }
+        });
+  }
+
+  boost::asio::ssl::stream<tcp::socket> socket_;
+  char request_[max_length];
+  char reply_[max_length];
+};
+
+int main(int argc, char* argv[])
+{
+  try
+  {
+    if (argc != 3)
+    {
+      std::cerr << "Usage: client <host> <port>\n";
+      return 1;
+    }
+
+    boost::asio::io_service io_context;
+
+    tcp::resolver resolver(io_context);
+    auto endpoints = resolver.resolve(argv[1], argv[2]);
+
+    boost::asio::ssl::context ctx(boost::asio::ssl::context::tls);
+    ctx.load_verify_file(CA_("kea-ca.crt"));
+    ctx.use_certificate_chain_file(CA_("kea-client.crt"));
+    ctx.use_private_key_file(CA_("kea-client.key"),
+                             boost::asio::ssl::context::pem);
+
+    client c(io_context, ctx, endpoints);
+
+    io_context.run();
+  }
+  catch (std::exception& e)
+  {
+    std::cerr << "Exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
diff --git a/src/lib/asiolink/testutils/openssl_sample_server.cpp b/src/lib/asiolink/testutils/openssl_sample_server.cpp
new file mode 100644 (file)
index 0000000..fa907f9
--- /dev/null
@@ -0,0 +1,177 @@
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+// Use the cpp03 version because the cpp11 version does not compile with
+// some g++ e.g. on Fedora 33.
+
+#include <cstdlib>
+#include <iostream>
+#include <boost/bind/bind.hpp>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+inline std::string CA_(const std::string& filename) {
+  return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
+
+class session
+{
+public:
+  session(boost::asio::io_service& io_context,
+      boost::asio::ssl::context& context)
+    : socket_(io_context, context)
+  {
+  }
+
+  ssl_socket::lowest_layer_type& socket()
+  {
+    return socket_.lowest_layer();
+  }
+
+  void start()
+  {
+    socket_.async_handshake(boost::asio::ssl::stream_base::server,
+        boost::bind(&session::handle_handshake, this,
+          boost::asio::placeholders::error));
+  }
+
+  void handle_handshake(const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      socket_.async_read_some(boost::asio::buffer(data_, max_length),
+          boost::bind(&session::handle_read, this,
+            boost::asio::placeholders::error,
+            boost::asio::placeholders::bytes_transferred));
+    }
+    else
+    {
+      std::cerr << "handshake error '" << error.message() << "'\n";
+      delete this;
+    }
+  }
+
+  void handle_read(const boost::system::error_code& error,
+      size_t bytes_transferred)
+  {
+    if (!error)
+    {
+      boost::asio::async_write(socket_,
+          boost::asio::buffer(data_, bytes_transferred),
+          boost::bind(&session::handle_write, this,
+            boost::asio::placeholders::error));
+    }
+    else
+    {
+      delete this;
+    }
+  }
+
+  void handle_write(const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      socket_.async_read_some(boost::asio::buffer(data_, max_length),
+          boost::bind(&session::handle_read, this,
+            boost::asio::placeholders::error,
+            boost::asio::placeholders::bytes_transferred));
+    }
+    else
+    {
+      delete this;
+    }
+  }
+
+private:
+  ssl_socket socket_;
+  enum { max_length = 1024 };
+  char data_[max_length];
+};
+
+class server
+{
+public:
+  server(boost::asio::io_service& io_context, unsigned short port)
+    : io_context_(io_context),
+      acceptor_(io_context,
+          boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
+      context_(boost::asio::ssl::context::tls)
+  {
+    //context_.set_options(
+    //    boost::asio::ssl::context::default_workarounds
+    //    | boost::asio::ssl::context::no_sslv2
+    //    | boost::asio::ssl::context::single_dh_use);
+    //context_.set_password_callback(boost::bind(&server::get_password, this));
+    context_.set_verify_mode(boost::asio::ssl::verify_peer |
+                             boost::asio::ssl::verify_fail_if_no_peer_cert);
+    context_.load_verify_file(CA_("kea-ca.crt"));
+    context_.use_certificate_chain_file(CA_("kea-server.crt"));
+    context_.use_private_key_file(CA_("kea-server.key"),
+                                  boost::asio::ssl::context::pem);
+    //context_.use_tmp_dh_file("dh2048.pem");
+
+    start_accept();
+  }
+
+  void start_accept()
+  {
+    session* new_session = new session(io_context_, context_);
+    acceptor_.async_accept(new_session->socket(),
+        boost::bind(&server::handle_accept, this, new_session,
+          boost::asio::placeholders::error));
+  }
+
+  void handle_accept(session* new_session,
+      const boost::system::error_code& error)
+  {
+    if (!error)
+    {
+      new_session->start();
+    }
+    else
+    {
+      delete new_session;
+    }
+
+    start_accept();
+  }
+
+private:
+  boost::asio::io_service& io_context_;
+  boost::asio::ip::tcp::acceptor acceptor_;
+  boost::asio::ssl::context context_;
+};
+
+int main(int argc, char* argv[])
+{
+  try
+  {
+    if (argc != 2)
+    {
+      std::cerr << "Usage: server <port>\n";
+      return 1;
+    }
+
+    boost::asio::io_service io_context;
+
+    using namespace std; // For atoi.
+    server s(io_context, atoi(argv[1]));
+
+    io_context.run();
+  }
+  catch (std::exception& e)
+  {
+    std::cerr << "Exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
diff --git a/src/lib/asiolink/testutils/test_tls.cc b/src/lib/asiolink/testutils/test_tls.cc
new file mode 100644 (file)
index 0000000..0f4d7a5
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_tls.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-server.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+    TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, true);
+}
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-client.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-client.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-other.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-other.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx) {
+    std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+    std::string cert(std::string(TEST_CA_DIR) + "/kea-self.crt");
+    std::string key(std::string(TEST_CA_DIR) + "/kea-self.key");
+    TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/testutils/test_tls.h b/src/lib/asiolink/testutils/test_tls.h
new file mode 100644 (file)
index 0000000..d761b29
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_TLS_H
+#define TEST_TLS_H
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_tls.h
+#endif
+
+#include <asiolink/crypto_tls.h>
+#include <asiolink/botan_tls.h>
+#include <asiolink/openssl_tls.h>
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx);
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx);
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx);
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx);
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_TLS_H
diff --git a/src/lib/asiolink/tls_acceptor.h b/src/lib/asiolink/tls_acceptor.h
new file mode 100644 (file)
index 0000000..c575559
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TLS_ACCEPTOR_H
+#define TLS_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tls_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TLS connections.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TLSAcceptor : public TCPAcceptor<C> {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service.
+    explicit TLSAcceptor(IOService& io_service) : TCPAcceptor<C>(io_service) {
+    }
+
+    /// @brief Destructor.
+    virtual ~TLSAcceptor() { }
+
+    /// @brief Asynchronously accept new connection.
+    ///
+    /// This method accepts new connection into the specified socket. When the
+    /// new connection arrives or an error occurs the specified callback function
+    /// is invoked.
+    ///
+    /// @param socket Socket into which connection should be accepted.
+    /// @param callback Callback function to be invoked when the new connection
+    /// arrives.
+    /// @tparam SocketCallback Type of the callback for the @ref TLSSocket.
+    template<typename SocketCallback>
+    void asyncAccept(const TLSSocket<SocketCallback>& socket, C& callback) {
+        TCPAcceptor<C>::acceptor_->async_accept(socket.getASIOSocket(), callback);
+    }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/tls_socket.h b/src/lib/asiolink/tls_socket.h
new file mode 100644 (file)
index 0000000..c9f1410
--- /dev/null
@@ -0,0 +1,525 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TLS_SOCKET_H
+#define TLS_SOCKET_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/crypto_tls.h>
+#include <asiolink/botan_tls.h>
+#include <asiolink/openssl_tls.h>
+#include <asiolink/tcp_socket.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief The @c TLSSocket class is a concrete derived class of @c IOAsioSocket
+/// that represents a TLS socket.
+///
+/// @tparam C Callback type.
+template <typename C>
+class TLSSocket : public IOAsioSocket<C>, private boost::noncopyable {
+public:
+
+    /// @brief Constructor from a TLS stream.
+    ///
+    /// It is assumed that the caller will open and close the stream,
+    /// so these operations are a no-op for that stream.
+    ///
+    /// @param stream The TLS stream.
+    TLSSocket(TlsStream<C>& stream);
+
+    /// @brief Constructor.
+    ///
+    /// Used when the TLSSocket is being asked to manage its own internal
+    /// socket.  In this case, the open() and close() methods are used.
+    ///
+    /// @param service I/O Service object used to manage the socket.
+    /// @param context Pointer to TLS context.
+    TLSSocket(IOService& service, TlsContextPtr context);
+
+    /// @brief Destructor.
+    virtual ~TLSSocket() { }
+
+    /// @brief Return file descriptor of underlying socket.
+    virtual int getNative() const {
+#if BOOST_VERSION < 106600
+        return (socket_.native());
+#else
+        return (socket_.native_handle());
+#endif
+    }
+
+    /// @brief Return protocol of socket.
+    virtual int getProtocol() const {
+        return (IPPROTO_TCP);
+    }
+
+    /// @brief Is "open()" synchronous predicate.
+    ///
+    /// Indicates that the opening of a TLS socket is asynchronous.
+    virtual bool isOpenSynchronous() const {
+        return (false);
+    }
+
+    /// @brief Checks if the connection is usable.
+    ///
+    /// The connection is usable if the socket is open and the peer has not
+    /// closed its connection.
+    ///
+    /// @return true if the connection is usable.
+    bool isUsable() const {
+        // If the socket is open it doesn't mean that it is still
+        // usable. The connection could have been closed on the other
+        // end. We have to check if we can still use this socket.
+        if (socket_.is_open()) {
+            // Remember the current non blocking setting.
+            const bool non_blocking_orig = socket_.non_blocking();
+
+            // Set the socket to non blocking mode. We're going to
+            // test if the socket returns would_block status on the
+            // attempt to read from it.
+            socket_.non_blocking(true);
+
+            // Use receive with message peek flag to avoid removing
+            // the data awaiting to be read.
+            char data[2];
+            int err = 0;
+            int cc = recv(getNative(), data, sizeof(data), MSG_PEEK);
+            if (cc < 0) {
+                // Error case.
+                err = errno;
+            } else if (cc == 0) {
+                // End of file.
+                err = -1;
+            }
+
+            // Revert the original non_blocking flag on the socket.
+            socket_.non_blocking(non_blocking_orig);
+
+            // If the connection is alive we'd typically get
+            // would_block status code.  If there are any data that
+            // haven't been read we may also get success status. We're
+            // guessing that try_again may also be returned by some
+            // implementations in some situations. Any other error
+            // code indicates a problem with the connection so we
+            // assume that the connection has been closed.
+            return ((err == 0) || (err == EAGAIN) || (err == EWOULDBLOCK));
+        }
+
+        return (false);
+    }
+
+    /// @brief Open Socket.
+    ///
+    /// Opens the TLS socket.  This is an asynchronous operation, completion of
+    /// which will be signalled via a call to the callback function.
+    ///
+    /// @param endpoint Endpoint to which the socket will connect.
+    /// @param callback Callback object.
+    virtual void open(const IOEndpoint* endpoint, C& callback);
+
+    /// @brief Perform Handshake.
+    ///
+    /// Perform the TLS handshake. This is an asynchronous operation,
+    /// completion of which will be signalled via a call to the callback
+    /// function.
+    ///
+    /// @param callback Callback object.
+    virtual void handshake(C& callback);
+
+    /// @brief Send Asynchronously.
+    ///
+    /// Calls the underlying socket's async_send() method to send a
+    /// packet of data asynchronously to the remote endpoint.  The
+    /// callback will be called on completion.
+    ///
+    /// @param data Data to send.
+    /// @param length Length of data to send.
+    /// @param endpoint Target of the send. (Unused for a TLS socket because
+    /// that was determined when the connection was opened.)
+    /// @param callback Callback object.
+    /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    virtual void asyncSend(const void* data, size_t length,
+                           const IOEndpoint* endpoint, C& callback);
+
+    /// @brief Send Asynchronously without count.
+    ///
+    /// This variant of the method sends data over the TLS socket without
+    /// preceding the data with a data count. Eventually, we should migrate
+    /// the virtual method to not insert the count but there are existing
+    /// classes using the count. Once this migration is done, the existing
+    /// virtual method should be replaced by this method.
+    ///
+    /// @param data Data to send.
+    /// @param length Length of data to send.
+    /// @param callback Callback object.
+    /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    void asyncSend(const void* data, size_t length, C& callback);
+
+    /// @brief Receive Asynchronously.
+    ///
+    /// Calls the underlying socket's async_receive() method to read a packet
+    /// of data from a remote endpoint.  Arrival of the data is signalled via a
+    /// call to the callback function.
+    ///
+    /// @param data Buffer to receive incoming message.
+    /// @param length Length of the data buffer.
+    /// @param offset Offset into buffer where data is to be put.
+    /// @param endpoint Source of the communication.
+    /// @param callback Callback object.
+    virtual void asyncReceive(void* data, size_t length, size_t offset,
+                              IOEndpoint* endpoint, C& callback);
+
+    /// @brief Process received data packet.
+    ///
+    /// See the description of IOAsioSocket::receiveComplete for a complete
+    /// description of this method.
+    ///
+    /// @param staging Pointer to the start of the staging buffer.
+    /// @param length Amount of data in the staging buffer.
+    /// @param cumulative Amount of data received before the staging buffer is
+    /// processed.
+    /// @param offset Unused.
+    /// @param expected unused.
+    /// @param outbuff Output buffer.  Data in the staging buffer is be copied
+    ///        to this output buffer in the call.
+    ///
+    /// @return Always true.
+    virtual bool processReceivedData(const void* staging, size_t length,
+                                     size_t& cumulative, size_t& offset,
+                                     size_t& expected,
+                                     isc::util::OutputBufferPtr& outbuff);
+
+    /// @brief Cancel I/O On Socket.
+    virtual void cancel();
+
+    /// @brief Close socket.
+    virtual void close();
+
+    /// @brief TLS shutdown.
+    virtual void shutdown(C& callback);
+
+    /// @brief Returns reference to the underlying ASIO socket.
+    ///
+    /// @return Reference to underlying ASIO socket.
+    virtual typename TlsStream<C>::lowest_layer_type& getASIOSocket() const {
+        return (socket_);
+    }
+
+    /// @brief Returns reference to the underlying TLS stream.
+    ///
+    /// @return Reference to underlying TLS stream.
+    virtual TlsStream<C>& getTlsStream() const {
+        return (stream_);
+    }
+
+private:
+    /// Two variables to hold the stream - a stream and a pointer to it.  This
+    /// handles the case where a stream is passed to the TLSSocket on
+    /// construction, or where it is asked to manage its own stream.
+
+    /// @brief Pointer to own stream.
+    std::unique_ptr<TlsStream<C>> stream_ptr_;
+
+    /// @brief TLS stream.
+    TlsStream<C>& stream_;
+
+    /// @brief Underlying TCP socket.
+    typename TlsStream<C>::lowest_layer_type& socket_;
+
+    /// TODO: Remove temporary buffer
+    /// The current implementation copies the buffer passed to asyncSend() into
+    /// a temporary buffer and precedes it with a two-byte count field.  As
+    /// ASIO should really be just about sending and receiving data, the TCP
+    /// code should not do this.  If the protocol using this requires a two-byte
+    /// count, it should add it before calling this code.  (This may be best
+    /// achieved by altering isc::dns::buffer to have pairs of methods:
+    /// getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+    /// methods taking into account a two-byte count field.)
+    ///
+    /// The option of sending the data in two operations, the count followed by
+    /// the data was discounted as that would lead to two callbacks which would
+    /// cause problems with the stackless coroutine code.
+
+    /// @brief Send buffer.
+    isc::util::OutputBufferPtr send_buffer_;
+};
+
+// Constructor - caller manages socket.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(TlsStream<C>& stream) :
+    stream_ptr_(), stream_(stream),
+    socket_(stream_.lowest_layer()), send_buffer_() {
+}
+
+// Constructor - create socket on the fly.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(IOService& service, TlsContextPtr context) :
+    stream_ptr_(new TlsStream<C>(service, context)),
+    stream_(*stream_ptr_), socket_(stream_.lowest_layer()), send_buffer_()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+    // If socket is open on this end but has been closed by the peer,
+    // we need to reconnect.
+    if (socket_.is_open() && !isUsable()) {
+        socket_.close();
+    }
+
+    // Ignore opens on already-open socket.  Don't throw a failure because
+    // of uncertainties as to what precedes whan when using asynchronous I/O.
+    // At also allows us a treat a passed-in socket as a self-managed socket.
+    if (!socket_.is_open()) {
+        if (endpoint->getFamily() == AF_INET) {
+            socket_.open(boost::asio::ip::tcp::v4());
+        } else {
+            socket_.open(boost::asio::ip::tcp::v6());
+        }
+
+        // Set options on the socket:
+
+        // Reuse address - allow the socket to bind to a port even if the port
+        // is in the TIMED_WAIT state.
+        socket_.set_option(boost::asio::socket_base::reuse_address(true));
+    }
+
+    // Upconvert to a TCPEndpoint.  We need to do this because although
+    // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+    // contain a method for getting at the underlying endpoint type - that is in
+    /// the derived class and the two classes differ on return type.
+    isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+    const TCPEndpoint* tcp_endpoint =
+        static_cast<const TCPEndpoint*>(endpoint);
+
+    // Connect to the remote endpoint.  On success, the handler will be
+    // called (with one argument - the length argument will default to
+    // zero).
+    socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Perform the handshake.
+
+template <typename C> void
+TLSSocket<C>::handshake(C& callback) {
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen, "attempt to perform handshake on "
+                  "a TLS socket that is not open");
+    }
+    stream_.handshake(callback);
+}
+
+// Send a message.  Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to send on a TLS socket that is not open");
+    }
+
+    try {
+        send_buffer_.reset(new isc::util::OutputBuffer(length));
+        send_buffer_->writeData(data, length);
+
+        // Send the data.
+        boost::asio::async_write(stream_,
+                                 boost::asio::buffer(send_buffer_->getData(),
+                                                     send_buffer_->getLength()),
+                                 callback);
+    } catch (const boost::numeric::bad_numeric_cast&) {
+        isc_throw(BufferTooLarge,
+                  "attempt to send buffer larger than 64kB");
+    }
+}
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length,
+                        const IOEndpoint*, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to send on a TLS socket that is not open");
+    }
+
+    // Need to copy the data into a temporary buffer and precede it with
+    // a two-byte count field.
+    // TODO: arrange for the buffer passed to be preceded by the count
+    try {
+        // Ensure it fits into 16 bits
+        uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+        // Copy data into a buffer preceded by the count field.
+        send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
+        send_buffer_->writeUint16(count);
+        send_buffer_->writeData(data, length);
+
+        // ... and send it
+        boost::asio::async_write(stream_,
+                                 boost::asio::buffer(send_buffer_->getData(),
+                                                     send_buffer_->getLength()),
+                                 callback);
+    } catch (const boost::numeric::bad_numeric_cast&) {
+        isc_throw(BufferTooLarge,
+                  "attempt to send buffer larger than 64kB");
+    }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data.  It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+                           IOEndpoint* endpoint, C& callback)
+{
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen,
+                  "attempt to receive from a TLS socket that is not open");
+    }
+
+    // Upconvert to a TCPEndpoint.  We need to do this because although
+    // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+    // does not contain a method for getting at the underlying endpoint
+    // type - that is in the derived class and the two classes differ on
+    // return type.
+    isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+    TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+    // Write the endpoint details from the communications link.  Ideally
+    // we should make IOEndpoint assignable, but this runs in to all sorts
+    // of problems concerning the management of the underlying Boost
+    // endpoint (e.g. if it is not self-managed, is the copied one
+    // self-managed?) The most pragmatic solution is to let Boost take care
+    // of everything and copy details of the underlying endpoint.
+    tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+    // Ensure we can write into the buffer and if so, set the pointer to
+    // where the data will be written.
+    if (offset >= length) {
+        isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+                  "TCP receive buffer");
+    }
+    void* buffer_start =
+        static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+    // ... and kick off the read.
+    stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
+                            callback);
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TLSSocket<C>::processReceivedData(const void* staging, size_t length,
+                                  size_t& cumulative, size_t& offset,
+                                  size_t& expected,
+                                  isc::util::OutputBufferPtr& outbuff)
+{
+    // Point to the data in the staging buffer and note how much there is.
+    const uint8_t* data = static_cast<const uint8_t*>(staging);
+    size_t data_length = length;
+
+    // Is the number is "expected" valid?  It won't be unless we have received
+    // at least two bytes of data in total for this set of receives.
+    if (cumulative < 2) {
+
+        // "expected" is not valid.  Did this read give us enough data to
+        // work it out?
+        cumulative += length;
+        if (cumulative < 2) {
+
+            // Nope, still not valid.  This must have been the first packet and
+            // was only one byte long.  Tell the fetch code to read the next
+            // packet into the staging buffer beyond the data that is already
+            // there so that the next time we are called we have a complete
+            // TCP count.
+            offset = cumulative;
+            return (false);
+        }
+
+        // Have enough data to interpret the packet count, so do so now.
+        expected = isc::util::readUint16(data, cumulative);
+
+        // We have two bytes less of data to process.  Point to the start of the
+        // data and adjust the packet size.  Note that at this point,
+        // "cumulative" is the true amount of data in the staging buffer, not
+        // "length".
+        data += 2;
+        data_length = cumulative - 2;
+    } else {
+
+        // Update total amount of data received.
+        cumulative += length;
+    }
+
+    // Regardless of anything else, the next read goes into the start of the
+    // staging buffer.
+    offset = 0;
+
+    // Work out how much data we still have to put in the output buffer. (This
+    // could be zero if we have just interpreted the TCP count and that was
+    // set to zero.)
+    if (expected >= outbuff->getLength()) {
+
+        // Still need data in the output packet.  Copy what we can from the
+        // staging buffer to the output buffer.
+        size_t copy_amount = std::min(expected - outbuff->getLength(),
+                                      data_length);
+        outbuff->writeData(data, copy_amount);
+    }
+
+    // We can now say if we have all the data.
+    return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket.  No-op if the socket is not open.
+
+template <typename C> void
+TLSSocket<C>::cancel() {
+    if (socket_.is_open()) {
+        socket_.cancel();
+    }
+}
+
+// TLS shutdown.  Can be used for orderly close.
+
+template <typename C> void
+TLSSocket<C>::shutdown(C& callback) {
+    if (!socket_.is_open()) {
+        isc_throw(SocketNotOpen, "attempt to perform shutdown on "
+                  "a TLS socket that is not open");
+    }
+    stream_.shutdown(callback);
+}
+
+// Close the socket down.  Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TLSSocket<C>::close() {
+    stream_.clear();
+    if (socket_.is_open() && stream_ptr_) {
+        socket_.close();
+    }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TLS_SOCKET_H