Skip to content

Commit

Permalink
Move certificate verification to handshake and allow disabling.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisstaite committed Sep 17, 2021
1 parent 4bfbc10 commit d38e165
Show file tree
Hide file tree
Showing 21 changed files with 538 additions and 201 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ set(CommonSources
src/openssl/ssl_connection.cpp
include/openssl/hostname_verifier.h
src/openssl/hostname_verifier.cpp
include/openssl/spki_verifier.h
src/openssl/spki_verifier.cpp
include/openssl/certificate_utilities.h
src/openssl/certificate_utilities.cpp
include/openssl/base64.h
src/openssl/base64.cpp
include/socket.h
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/chrisstaite/DoTe.svg?branch=master)](https://travis-ci.org/chrisstaite/DoTe)
[![Build Status](https://app.travis-ci.com/chrisstaite/DoTe.svg?branch=master)](https://app.travis-ci.com/chrisstaite/DoTe)

DoTe
====
Expand Down Expand Up @@ -97,7 +97,7 @@ Run

By default DoTe will listen on localhost port 53 and
forward queries to 1.1.1.1 port 853 with hostname
verification and certificate pinned.
verification and PKI enabled.

~~~~
./dote
Expand Down
5 changes: 5 additions & 0 deletions include/config_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class ConfigParser
{
/// The IP and port to connect to and forward to
sockaddr_storage remote;
/// Set to true to disable certificate verification
bool disablePki;
/// The host to verify the certificate common name against
std::string host;
/// The base64 encoded SHA-256 hash of the certificate
Expand Down Expand Up @@ -119,6 +121,9 @@ class ConfigParser
/// \param pin The Base64 encoded public key pin
void addPin(const char* pin);

/// \brief Disable certificate verification for the current forwarder
void disableVerification();

/// \brief The hostname to add to the current m_partialForwarder
///
/// \param hostname The hostname expected for the certificate
Expand Down
8 changes: 3 additions & 5 deletions include/forwarder_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,9 @@ class ForwarderConnection
CLOSED
};

/// \brief Verify the certificate of the connection matches
/// the configuration pinning
///
/// \return True if the hostname and certificate are valid
bool verifyConnection();
/// \brief Configure the verification routines for the connection to
/// the given forwarder
void configureVerifier();

/// \brief Perform the initial connection
///
Expand Down
46 changes: 46 additions & 0 deletions include/openssl/certificate_utilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

#include <openssl/ssl.h>

#include <vector>
#include <string>

namespace dote {
namespace openssl {

class CertificateUtilities
{
public:
/// \brief Create a utilty instance for the given certificate
///
/// \param certificate A certificate to work on, must last the lifetime of the instance
explicit CertificateUtilities(X509* certificate);

/// \brief Get the SHA-256 hash of the public key of the given certificate
///
/// \return The SHA-256 hash of the certificate's public key or empty vector on error
std::vector<unsigned char> getPublicKeyHash();

/// \brief Get the common name of the certificate
///
/// \return The common name of the certificate or empty string on error
std::string getCommonName();

private:
/// The hash function for getPeerCertificateHash
using HashFunction = int(*)(
const X509*, const EVP_MD*, unsigned char*, unsigned int*
);

/// \brief Get the SHA-256 hash of the certificate
///
/// \param function The hash function to use for the certificate
///
/// \return The SHA-256 hash
std::vector<unsigned char> getCertificateHash(HashFunction function);

/// The certificate to perform operations on
X509* m_certificate;
};

} // namespace openssl
} // namespace dote
35 changes: 27 additions & 8 deletions include/openssl/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ typedef struct ssl_session_st SSL_SESSION;
namespace dote {
namespace openssl {

class SslConnection;

/// \brief A wrapper around an OpenSSL context which automatically
/// cleans it up on destruction. Will also initialise the
/// library on construction if context creation fails.
Expand All @@ -34,10 +36,17 @@ class Context
/// \brief Clean up the context
~Context();

/// \brief Set the verifier for the connections
/// \brief Set the chain verifier for all incoming connections
///
/// \prarm verifier The verifier to set
void setChainVerifier(Verifier verifier);

/// \brief Set the SSLConnection instance for the SSL object, this
/// causes the verify function to be fired
///
/// \prarm handle The verifier to set
void setVerifier(Verifier verifier);
/// \param ssl The SSL object to get the connection for
/// \param connection The connection to set on the SSL object
void setSslConnection(SSL* ssl, SslConnection* connection);

protected:
/// \brief Get the raw context
Expand Down Expand Up @@ -66,19 +75,29 @@ class Context
/// \brief A C-style trampoline to get back the C++ instance to
/// perform the certificate verification
///
/// \param store The context to verify
/// \param context The Context instance to forward to
/// \param preverify Set to 0 if the chain failed verification built-in, 1 otherwise
/// \param store The context to verify
///
/// \return The result of the context->m_verifier function or
/// 0 if any of the checks fail
static int verifyTrampoline(X509_STORE_CTX* store, void* context);
static int verifyTrampoline(int preverify, X509_STORE_CTX* store);

/// \brief A C-style trampoline to get back the C++ instance to
/// perform the certificate verification
///
/// \param store The context to verify
/// \param context The Context instance to forward to
///
/// \return The result of the context->m_chainVerifier function or
/// 0 if any of the checks fail
static int chainVerifyTrampoline(X509_STORE_CTX* store, void* context);

/// The wrapped context
SSL_CTX* m_context;
/// The client session that we could re-use
SSL_SESSION* m_session;
/// The verifier to use for the connection if not the default
Verifier m_verifier;
/// The chain verifier to use for the connection if not the default
Verifier m_chainVerifier;
};

} // namespace openssl
Expand Down
26 changes: 14 additions & 12 deletions include/openssl/i_ssl_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@

#include <vector>
#include <string>
#include <functional>

typedef struct x509_store_ctx_st X509_STORE_CTX;

namespace dote {
namespace openssl {

class ISslConnection
{
public:
/// The type of verifier to forward on to, returns 2 if pin and hostname pass, 1 if hostname only, 0 if not valid
using Verifier = std::function<int(X509_STORE_CTX*)>;

/// \brief The result types from communications
enum Result
{
Expand All @@ -35,11 +41,15 @@ class ISslConnection
/// \param handle The underlying socket to set on this connection
virtual void setSocket(int handle) = 0;

/// \brief Get the SHA-256 hash of the peer certificate after
/// connect has completed
/// \brief Disable certificate verification, should be used
/// for testing only, it kind of defeats the point
virtual void disableVerification() = 0;

/// \brief Set the verifier for the connections, by default
/// connections are verified by PKI, this allows SPKI
///
/// \return The SHA-256 hash of the certificate
virtual std::vector<unsigned char> getPeerCertificateHash() = 0;
/// \param verifier The verifier to set
virtual void setVerifier(Verifier verifier) = 0;

/// \brief Get the SHA-256 hash of the public key of the attached
/// peer certificate after connect has completed
Expand All @@ -52,14 +62,6 @@ class ISslConnection
/// \return The peer certificate common name
virtual std::string getCommonName() = 0;

/// \brief Check the connected peer certificate is valid for the
/// given hostname after connect has completed
///
/// \param hostname The hostname to verify
///
/// \return True if the hostname is valid for the connected peer
virtual bool verifyHostname(const std::string& hostname) = 0;

/// \brief Connect the underlying connection
///
/// \return The status of the function
Expand Down
49 changes: 49 additions & 0 deletions include/openssl/spki_verifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

#include "config_parser.h"

#include <vector>
#include <string>

#include <openssl/ssl.h>

namespace dote {
namespace openssl {

class SpkiVerifier
{
public:
/// \brief Create a verifier for a given forwarder
///
/// \param config The forwarder to create the verifier for, lifetime must outlive this instance
explicit SpkiVerifier(ConfigParser::Forwarder& config);

/// \brief Verify a certificate matches the SPKI requirements
///
/// \param store The store to get the certificates to check
///
/// \return 2 if pin and hostname pass, 1 if hostname only, 0 if not valid
int verify(X509_STORE_CTX* store) const;

private:
/// \brief Check the given peer certificate is valid for the
/// configured hostname
///
/// \param certificate The certificate to verify against the hostname
///
/// \return True if the hostname is valid for the connected peer
bool verifyHostname(X509* certificate) const;

/// \brief Check the given peer certificate is valid for the
/// configured public key hash
///
/// \param certificate The certificate to verify against the hash
///
/// \return True if the SHA-256 hash of the public key matches the configured hash
bool verifyHash(X509* certificate) const;

/// The forwarder configuration to validate against
ConfigParser::Forwarder& m_config;
};

} // namespace openssl
} // namespace dote
46 changes: 20 additions & 26 deletions include/openssl/ssl_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace dote {
namespace openssl {

class Context;
class CertificateUtilities;

/// \brief A wrapper around an OpenSSL SSL connection
class SslConnection : public ISslConnection
Expand All @@ -28,17 +29,15 @@ class SslConnection : public ISslConnection
/// \brief Clean up the wrapped connection
~SslConnection() noexcept;

/// \brief Disable certificate verification, should be used
/// for testing only, it kind of defeats the point
void disableVerification() override;

/// \brief Set the underlying socket for this connection
///
/// \param handle The underlying socket to set on this connection
void setSocket(int handle) override;

/// \brief Get the SHA-256 hash of the peer certificate after
/// connect has completed
///
/// \return The SHA-256 hash of the certificate
std::vector<unsigned char> getPeerCertificateHash() override;

/// \brief Get the SHA-256 hash of the public key of the attached
/// peer certificate after connect has completed
///
Expand All @@ -50,14 +49,6 @@ class SslConnection : public ISslConnection
/// \return The peer certificate common name
std::string getCommonName() override;

/// \brief Check the connected peer certificate is valid for the
/// given hostname after connect has completed
///
/// \param hostname The hostname to verify
///
/// \return True if the hostname is valid for the connected peer
bool verifyHostname(const std::string& hostname) override;

/// \brief Connect the underlying connection
///
/// \return The status of the function
Expand All @@ -82,21 +73,22 @@ class SslConnection : public ISslConnection
/// \return The status of the function
Result read(std::vector<char>& buffer) override;

private:
/// The maximum size of the read
static constexpr std::size_t MAX_FRAME = 16 * 1024;

/// The hash function for getPeerCertificateHash
using HashFunction = int(*)(
const X509*, const EVP_MD*, unsigned char*, unsigned int*
);
/// \brief Set the verifier for the connections, by default
/// connections are verified by PKI, this allows SPKI
///
/// \param verifier The verifier to set
void setVerifier(Verifier verifier) override;

/// \brief Get the SHA-256 hash of the peer certificate
/// \brief Verify a connection based on the given certificate store
///
/// \param function The hash function to use for the certificate
/// \param store The certificate store to verify
///
/// \return The SHA-256 hash
std::vector<unsigned char> getPeerCertificateHash(HashFunction function);
/// \return 2 if pin and hostname pass, 1 if hostname only, 0 if not valid
int verify(X509_STORE_CTX* store);

private:
/// The maximum size of the read
static constexpr std::size_t MAX_FRAME = 16 * 1024;

/// \brief Perform a function on the underlying SSL handling the
/// non-blocking errors
Expand All @@ -110,6 +102,8 @@ class SslConnection : public ISslConnection
std::shared_ptr<Context> m_context;
/// The wrapped SSL connection
SSL* m_ssl;
/// The verifier to use for the connection if not the default
Verifier m_verifier;
};

} // namespace openssl
Expand Down
Loading

0 comments on commit d38e165

Please sign in to comment.