diff options
Diffstat (limited to 'src/lib/asiolink/openssl_tls.h')
-rw-r--r-- | src/lib/asiolink/openssl_tls.h | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/lib/asiolink/openssl_tls.h b/src/lib/asiolink/openssl_tls.h new file mode 100644 index 0000000..e5a5a21 --- /dev/null +++ b/src/lib/asiolink/openssl_tls.h @@ -0,0 +1,242 @@ +// Copyright (C) 2021-2022 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/. + +// Do not include this header directly: use crypto_tls.h instead. + +#ifndef OPENSSL_TLS_H +#define OPENSSL_TLS_H + +/// @file openssl_tls.h OpenSSL implementation of the TLS API. + +#ifdef WITH_OPENSSL + +#include <asiolink/asio_wrapper.h> +#include <asiolink/io_asio_socket.h> +#include <asiolink/io_service.h> +#include <asiolink/common_tls.h> + +#include <boost/asio/ssl.hpp> + +namespace isc { +namespace asiolink { + +/// @brief Translate TLS role into implementation. +inline boost::asio::ssl::stream_base::handshake_type roleToImpl(TlsRole role) { + if (role == TlsRole::SERVER) { + return (boost::asio::ssl::stream_base::server); + } else { + return (boost::asio::ssl::stream_base::client); + } +} + +/// @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 Get the peer certificate requirement mode. + /// + /// @return True if peer certificates are required, false if they + /// are optional. + virtual bool getCertRequired() const; + + /// @brief Get the error message. + /// + /// @note Wrapper against OpenSSL 3.x not returning error messages + /// from system errors. + /// + /// @param ec The Boost error code. + /// @return The error message. + static std::string getErrMsg(boost::system::error_code ec); + +protected: + /// @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 Load the trust anchor aka certification authority. + /// + /// @param ca_file The certificate file name. + virtual void loadCaFile(const std::string& ca_file); + + /// @brief Load the trust anchor aka certification 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 from a file. + /// + /// @param key_file The private key file name. + virtual void loadKeyFile(const std::string& key_file); + + /// @brief Cached cert_required value. + bool cert_required_; + + /// @brief Boost ASIO SSL object. + boost::asio::ssl::context context_; + + /// @brief Allow access to protected methods by the base class. + friend class TlsContextBase; +}; + +/// @brief The type of underlying TLS streams. +typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> TlsStreamImpl; + +/// @brief TlsStreamBase constructor. +/// +/// @tparam Callback The type of callbacks. +/// @tparam TlsStreamImpl The type of underlying TLS streams. +/// @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. +template <typename Callback, typename TlsStreamImpl> +TlsStreamBase<Callback, TlsStreamImpl>:: +TlsStreamBase(IOService& service, TlsContextPtr context) + : TlsStreamImpl(service.get_io_service(), context->getContext()), + role_(context->getRole()) { +} + +/// @brief OpenSSL TLS stream. +/// +/// @tparam callback The callback. +template <typename Callback> +class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl> { +public: + + /// @brief Type of the base. + typedef TlsStreamBase<Callback, TlsStreamImpl> Base; + + /// @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) + : Base(service, context) { + } + + /// @brief Destructor. + virtual ~TlsStream() { } + + /// @brief TLS Handshake. + /// + /// @param callback Callback object. + virtual void handshake(Callback& callback) { + Base::async_handshake(roleToImpl(Base::getRole()), callback); + } + + /// @brief TLS shutdown. + /// + /// @param callback Callback object. + virtual void shutdown(Callback& callback) { + Base::async_shutdown(callback); + } + + /// @brief Return the commonName part of the subjectName of + /// the peer certificate. + /// + /// First commonName when there are more than one, in UTF-8. + /// RFC 3280 provides as a commonName example "Susan Housley", + /// to idea to give access to this come from the Role Based + /// Access Control experiment. + /// + /// + /// @return The commonName part of the subjectName or the empty string. + virtual std::string getSubject() { + ::X509* cert = ::SSL_get_peer_certificate(this->native_handle()); + 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); + } + + /// @brief Return the commonName part of the issuerName of + /// the peer certificate. + /// + /// First commonName when there are more than one, in UTF-8. + /// The issuerName is the subjectName of the signing certificate + /// (the issue in PKIX terms). The idea is to encode a group as + /// members of an intermediate certification authority. + /// + /// + /// @return The commonName part of the issuerName or the empty string. + virtual std::string getIssuer() { + ::X509* cert = ::SSL_get_peer_certificate(this->native_handle()); + 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); + } +}; + +// Stream truncated error code. +#ifdef HAVE_STREAM_TRUNCATED_ERROR +const int STREAM_TRUNCATED = boost::asio::ssl::error::stream_truncated; +#else +const int STREAM_TRUNCATED = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ); +#endif + +} // namespace asiolink +} // namespace isc + +#endif // WITH_OPENSSL + +#endif // OPENSSL_TLS_H |