diff --git a/examples/tls/tlsconn.cpp b/examples/tls/tlsconn.cpp index 627e09c..d4bd43e 100644 --- a/examples/tls/tlsconn.cpp +++ b/examples/tls/tlsconn.cpp @@ -36,18 +36,18 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -------------------------------------------------------------------------- +#include + +#include +#include +#include + #include "sockpp/tcp_connector.h" #include "sockpp/tls/connector.h" #include "sockpp/tls/context.h" #include "sockpp/tls/error.h" #include "sockpp/version.h" -#include - -#include -#include -#include - using namespace std; int main(int argc, char* argv[]) { @@ -141,13 +141,27 @@ int main(int argc, char* argv[]) { cout << "Successful connection to " << addr << endl; - if (auto cert = conn.peer_certificate(); cert.empty()) { + if (auto opt_cert = conn.peer_certificate(); !opt_cert) { cout << "No peer certificate" << endl; } else { - ofstream fil("peer.cer", ios::binary); - fil.write(reinterpret_cast(cert.data()), cert.size()); - cout << "Wrote peer certificate to peer.cer" << endl; + const auto& cert = opt_cert.value(); + + cout << "\nCertificate info:\n" + << " Subject: " << cert.subject_name() << '\n' + << " Issuer: " << cert.issuer_name() << '\n' + << " Valid dates: " << cert.not_before_str() << " - " << cert.not_after_str() + << endl; + + ofstream derfil("peer.cer", ios::binary); + auto der = cert.to_der(); + derfil.write(reinterpret_cast(der.data()), der.size()); + cout << "\nWrote peer certificate to peer.cer" << endl; + + ofstream pemfil("peer.pem"); + auto pem = cert.to_pem(); + pemfil.write(pem.data(), pem.size()); + cout << "\nWrote peer certificate to peer.pem" << endl; } if (auto res = conn.write("HELO"); !res) { diff --git a/include/sockpp/tls/openssl_certificate.h b/include/sockpp/tls/openssl_certificate.h new file mode 100644 index 0000000..1cdf2e1 --- /dev/null +++ b/include/sockpp/tls/openssl_certificate.h @@ -0,0 +1,111 @@ +/** + * @file openssl_certificate.h + * + * Socket type for OpenSSL TLS/SSL sockets. + * + * @author Frank Pagliughi + * @date January 2025 + */ + +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2025 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#ifndef __sockpp_tls_openssl_certificate_h +#define __sockpp_tls_openssl_certificate_h + +#include + +#include "sockpp/types.h" + +namespace sockpp { + +// Forward declaration +class tls_socket; + +///////////////////////////////////////////////////////////////////////////// + +/** + * An X509 certificate implemented with OpenSSL. + */ +class tls_certificate +{ + /** The certificate library struct */ + X509* cert_; + + friend class tls_socket; + + /** Object takes ownership of the pointer */ + tls_certificate(X509* cert) : cert_{cert} {} + +public: + /** + * Destructor. + */ + ~tls_certificate() { ::X509_free(cert_); } + /** + * Gets the subject name for the certificate. + * @return The subject name for the certificate. + */ + string subject_name() const; + /** + * Gets the subject name for the certificate. + * @return The subject name for the certificate. + */ + string issuer_name() const; + /** + * Gets the certificate's "not before" date as a string. + * @return The certificate's "not before" date as a string. + */ + string not_before_str() const; + /** + * Gets the certificate's "not after" date as a string. + * @return The certificate's "not after" date as a string. + */ + string not_after_str() const; + /** + * Gets the certificate as a DER binary blob. + * @return The certificate as a DER binary blob. + */ + binary to_der() const; + /** + * Gets the certificate as a PEM string. + * @return The certificate as a PEM string. + */ + string to_pem() const; +}; + +///////////////////////////////////////////////////////////////////////////// +} // namespace sockpp + +#endif // __sockpp_tls_openssl_certificate_h diff --git a/include/sockpp/tls/openssl_socket.h b/include/sockpp/tls/openssl_socket.h index 5f3301f..f20b44d 100644 --- a/include/sockpp/tls/openssl_socket.h +++ b/include/sockpp/tls/openssl_socket.h @@ -46,7 +46,10 @@ #include +#include + #include "sockpp/stream_socket.h" +#include "sockpp/tls/openssl_certificate.h" #include "sockpp/tls/openssl_context.h" #include "sockpp/tls/openssl_error.h" #include "sockpp/types.h" @@ -121,10 +124,11 @@ public: tls_socket& operator=(tls_socket&& rhs); /** - * Returns the peer's X.509 certificate data, in binary DER format. + * Returns the peer's X.509 certificate. */ - binary peer_certificate(); + std::optional peer_certificate(); +#if 0 /** * */ @@ -134,6 +138,7 @@ public: * certificate. */ string peer_certificate_status_message(); +#endif // I/O primitives diff --git a/include/sockpp/types.h b/include/sockpp/types.h index 99dd58c..6bb116c 100644 --- a/include/sockpp/types.h +++ b/include/sockpp/types.h @@ -48,8 +48,8 @@ #define __sockpp_types_h #include -#include #include +#include namespace sockpp { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d6b447d..906b15c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,6 +68,7 @@ endif() if(SOCKPP_WITH_OPENSSL) list(APPEND SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/tls/openssl_certificate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tls/openssl_context.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tls/openssl_socket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tls/openssl_error.cpp diff --git a/src/tls/openssl_certificate.cpp b/src/tls/openssl_certificate.cpp new file mode 100644 index 0000000..2031847 --- /dev/null +++ b/src/tls/openssl_certificate.cpp @@ -0,0 +1,112 @@ +// openssl_certificate.cpp +// +// -------------------------------------------------------------------------- +// This file is part of the "sockpp" C++ socket library. +// +// Copyright (c) 2025 Frank Pagliughi +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// -------------------------------------------------------------------------- + +#include "sockpp/tls/openssl_certificate.h" + +#include +#include + +#include + +namespace sockpp { + +///////////////////////////////////////////////////////////////////////////// + +string tls_certificate::subject_name() const { + auto name = X509_get_subject_name(cert_); + if (!name) + return string{}; + const char* name_str = X509_NAME_oneline(name, NULL, 0); + return (name_str) ? string{name_str} : string{}; +} + +// int X509_set_subject_name(X509 *x, const X509_NAME *name); + +string tls_certificate::issuer_name() const { + auto name = X509_get_issuer_name(cert_); + if (!name) + return string{}; + const char* name_str = X509_NAME_oneline(name, NULL, 0); + return (name_str) ? string{name_str} : string{}; +} + +// int X509_set_issuer_name(X509 *x, const X509_NAME *name); + +string tls_certificate::not_before_str() const { + auto tm = X509_get0_notBefore(cert_); + return (tm && tm->data) ? string{(const char*)tm->data} : string{}; +} + +string tls_certificate::not_after_str() const { + auto tm = X509_get0_notAfter(cert_); + return (tm && tm->data) ? string{(const char*)tm->data} : string{}; +} + +binary tls_certificate::to_der() const { + if (!cert_) + return binary{}; + + uint8_t* buf = nullptr; + int len = i2d_X509(cert_, &buf); + + // TODO: Return an error result on <0? + if (len <= 0) + return binary{}; + + binary certBin{buf, size_t(len)}; + ::OPENSSL_free(buf); + + return certBin; +} + +string tls_certificate::to_pem() const { + BIO* bio = ::BIO_new(BIO_s_mem()); + if (!bio || !::PEM_write_bio_X509(bio, cert_)) { + ::BIO_vfree(bio); + return string{}; + } + + size_t keylen = BIO_pending(bio); + std::unique_ptr key(new char[keylen]); + + int len = ::BIO_read(bio, key.get(), (int)keylen); + ::BIO_vfree(bio); + + return (len > 0) ? string{key.get(), (size_t)len} : string{}; +} + +///////////////////////////////////////////////////////////////////////////// +} // namespace sockpp diff --git a/src/tls/openssl_socket.cpp b/src/tls/openssl_socket.cpp index 5850845..737b494 100644 --- a/src/tls/openssl_socket.cpp +++ b/src/tls/openssl_socket.cpp @@ -73,26 +73,17 @@ tls_socket& tls_socket::operator=(tls_socket&& rhs) { return *this; } -binary tls_socket::peer_certificate() { +std::optional tls_socket::peer_certificate() { // TODO: Implement this - X509* cert = SSL_get0_peer_certificate(ssl_); + X509* cert = SSL_get1_peer_certificate(ssl_); if (!cert) - return binary{}; + return std::nullopt; - uint8_t* buf = nullptr; - int len = i2d_X509(cert, &buf); - - // TODO: Return an error result on <0? - if (len <= 0) - return binary{}; - - binary certBin{buf, size_t(len)}; - OPENSSL_free(buf); - - return certBin; + return tls_certificate{cert}; } +#if 0 uint32_t tls_socket::peer_certificate_status() { // TODO: Implement this? return 0; @@ -104,7 +95,7 @@ string tls_socket::peer_certificate_status_message() { // TODO: Implement this? return string{}; } - +#endif result tls_socket::read(void* buf, size_t n) { size_t nx;