From 87259631e4f9baf4cafb55a75db16ca9cc20d40e Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Mon, 10 Dec 2012 22:59:19 +0100 Subject: [PATCH] SSL server identity check. --- CMakeLists.txt | 1 + SConstruct | 1 + examples/example6.cpp | 5 +- src/net/tls/gnutls/TLSSocket_GnuTLS.cpp | 14 ++- src/net/tls/openssl/TLSSocket_OpenSSL.cpp | 14 ++- src/platforms/posix/posixSocket.cpp | 95 +++++++++++++++++++ src/platforms/windows/windowsSocket.cpp | 67 +++++++++++++ .../cert/defaultCertificateVerifier.cpp | 15 ++- .../cert/gnutls/X509Certificate_GnuTLS.cpp | 6 ++ .../cert/openssl/X509Certificate_OpenSSL.cpp | 68 +++++++++++++ src/security/sasl/SASLSocket.cpp | 12 +++ vmime/net/socket.hpp | 12 +++ vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp | 3 + vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp | 3 + vmime/platforms/posix/posixSocket.hpp | 5 + vmime/platforms/windows/windowsSocket.hpp | 5 + vmime/security/cert/X509Certificate.hpp | 7 ++ vmime/security/cert/certificateVerifier.hpp | 6 +- .../cert/defaultCertificateVerifier.hpp | 4 +- .../cert/gnutls/X509Certificate_GnuTLS.hpp | 2 + .../cert/openssl/X509Certificate_OpenSSL.hpp | 2 + vmime/security/sasl/SASLSocket.hpp | 3 + 22 files changed, 340 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5be9ca32..79a8ee03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -561,6 +561,7 @@ INCLUDE(CheckFunctionExists) INCLUDE(CheckSymbolExists) CHECK_FUNCTION_EXISTS(getaddrinfo VMIME_HAVE_GETADDRINFO) +CHECK_FUNCTION_EXISTS(getnameinfo VMIME_HAVE_GETNAMEINFO) CHECK_FUNCTION_EXISTS(gettid VMIME_HAVE_GETTID) CHECK_FUNCTION_EXISTS(syscall VMIME_HAVE_SYSCALL) diff --git a/SConstruct b/SConstruct index d1bb23c5..3fe9588d 100644 --- a/SConstruct +++ b/SConstruct @@ -860,6 +860,7 @@ for platform in libvmime_platforms_sources: config_hpp.write(""" #define VMIME_HAVE_GETADDRINFO 1 +#define VMIME_HAVE_GETNAMEINFO 1 #define VMIME_HAVE_PTHREAD 1 #define VMIME_HAVE_GETTID 0 #define VMIME_HAVE_SYSCALL 1 diff --git a/examples/example6.cpp b/examples/example6.cpp index bcb2df9a..53ff978a 100644 --- a/examples/example6.cpp +++ b/examples/example6.cpp @@ -147,7 +147,7 @@ class interactiveCertificateVerifier : public vmime::security::cert::defaultCert { public: - void verify(vmime::ref chain) + void verify(vmime::ref chain, const vmime::string& hostname) { try { @@ -176,6 +176,9 @@ public: { m_trustedCerts.push_back(cert.dynamicCast ()); + + setX509TrustedCerts(m_trustedCerts); + defaultCertificateVerifier::verify(chain, hostname); } return; diff --git a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp index 477f655b..37381881 100644 --- a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp +++ b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp @@ -116,6 +116,18 @@ TLSSocket::size_type TLSSocket_GnuTLS::getBlockSize() const } +const string TLSSocket_GnuTLS::getPeerName() const +{ + return m_wrapped->getPeerName(); +} + + +const string TLSSocket_OpenSSL::getPeerAddress() const +{ + return m_wrapped->getPeerAddress(); +} + + void TLSSocket_GnuTLS::receive(string& buffer) { const int size = receiveRaw(m_buffer, sizeof(m_buffer)); @@ -262,7 +274,7 @@ void TLSSocket_GnuTLS::handshake(ref toHandler) if (certs == NULL) throw exceptions::tls_exception("No peer certificate."); - m_session->getCertificateVerifier()->verify(certs); + m_session->getCertificateVerifier()->verify(certs, getPeerName()); m_connected = true; } diff --git a/src/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/net/tls/openssl/TLSSocket_OpenSSL.cpp index 25937e32..f37d9a33 100755 --- a/src/net/tls/openssl/TLSSocket_OpenSSL.cpp +++ b/src/net/tls/openssl/TLSSocket_OpenSSL.cpp @@ -160,6 +160,18 @@ TLSSocket::size_type TLSSocket_OpenSSL::getBlockSize() const } +const string TLSSocket_OpenSSL::getPeerName() const +{ + return m_wrapped->getPeerName(); +} + + +const string TLSSocket_OpenSSL::getPeerAddress() const +{ + return m_wrapped->getPeerAddress(); +} + + void TLSSocket_OpenSSL::receive(string& buffer) { const size_type size = receiveRaw(m_buffer, sizeof(m_buffer)); @@ -239,7 +251,7 @@ void TLSSocket_OpenSSL::handshake(ref toHandler) if (certs == NULL) throw exceptions::tls_exception("No peer certificate."); - m_session->getCertificateVerifier()->verify(certs); + m_session->getCertificateVerifier()->verify(certs, getPeerName()); m_connected = true; } diff --git a/src/platforms/posix/posixSocket.cpp b/src/platforms/posix/posixSocket.cpp index b3e33f49..1a7fb7a3 100644 --- a/src/platforms/posix/posixSocket.cpp +++ b/src/platforms/posix/posixSocket.cpp @@ -104,6 +104,8 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port throw vmime::exceptions::connection_error("Cannot resolve address."); } + m_serverAddress = address; + // Connect to host int sock = -1; struct ::addrinfo* res = res0; @@ -268,6 +270,8 @@ void posixSocket::connect(const vmime::string& address, const vmime::port_t port ::memcpy(reinterpret_cast (&addr.sin_addr), hostInfo->h_addr, hostInfo->h_length); } + m_serverAddress = address; + // Get a new socket m_desc = ::socket(AF_INET, SOCK_STREAM, 0); @@ -331,6 +335,97 @@ void posixSocket::disconnect() } +static bool isNumericAddress(const char* address) +{ + +#if VMIME_HAVE_GETADDRINFO + + struct addrinfo hint, *info = NULL; + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_NUMERICHOST; + + if (getaddrinfo(address, 0, &hint, &info) == 0) + { + freeaddrinfo(info); + return true; + } + else + { + return false; + } + +#else + + return inet_addr(address) != INADDR_NONE; + +#endif + +} + + +const string posixSocket::getPeerAddress() const +{ + // Get address of connected peer + sockaddr peer; + socklen_t peerLen = sizeof(peer); + + getpeername(m_desc, reinterpret_cast (&peer), &peerLen); + + // Convert to numerical presentation format + char numericAddress[1024]; + + if (inet_ntop(peer.sa_family, &peer, numericAddress, sizeof(numericAddress)) != NULL) + return string(numericAddress); + + return ""; // should not happen +} + + +const string posixSocket::getPeerName() const +{ + // Get address of connected peer + sockaddr peer; + socklen_t peerLen = sizeof(peer); + + getpeername(m_desc, reinterpret_cast (&peer), &peerLen); + + // If server address as specified when connecting is a numeric + // address, try to get a host name for it + if (isNumericAddress(m_serverAddress.c_str())) + { + +#if VMIME_HAVE_GETNAMEINFO + + char host[NI_MAXHOST + 1]; + char service[NI_MAXSERV + 1]; + + if (getnameinfo(reinterpret_cast (&peer), peerLen, + host, sizeof(host), service, sizeof(service), + /* flags */ NI_NAMEREQD) == 0) + { + return string(host); + } + +#else + + struct hostent *hp; + + if ((hp = gethostbyaddr(reinterpret_cast (&peer), + sizeof(peer), peer.sa_family)) != NULL) + { + return string(hp->h_name); + } + +#endif + + } + + return m_serverAddress; +} + + posixSocket::size_type posixSocket::getBlockSize() const { return 16384; // 16 KB diff --git a/src/platforms/windows/windowsSocket.cpp b/src/platforms/windows/windowsSocket.cpp index dc6f1b47..abc16d70 100644 --- a/src/platforms/windows/windowsSocket.cpp +++ b/src/platforms/windows/windowsSocket.cpp @@ -91,6 +91,8 @@ void windowsSocket::connect(const vmime::string& address, const vmime::port_t po memcpy(reinterpret_cast (&addr.sin_addr), hostInfo->h_addr, hostInfo->h_length); } + m_serverAddress = address; + // Get a new socket m_desc = ::socket(AF_INET, SOCK_STREAM, 0); @@ -156,6 +158,71 @@ void windowsSocket::disconnect() } +static bool isNumericAddress(const char* address) +{ + struct addrinfo hint, *info = NULL; + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_NUMERICHOST; + + if (getaddrinfo(address, 0, &hint, &info) == 0) + { + freeaddrinfo(info); + return true; + } + else + { + return false; + } +} + + +const string windowsSocket::getPeerAddress() const +{ + // Get address of connected peer + sockaddr peer; + socklen_t peerLen = sizeof(peer); + + getpeername(m_desc, reinterpret_cast (&peer), &peerLen); + + // Convert to numerical presentation format + char numericAddress[1024]; + + if (inet_ntop(peer.sa_family, &peer, numericAddress, sizeof(numericAddress)) != NULL) + return string(numericAddress); + + return ""; // should not happen +} + + +const string windowsSocket::getPeerName() const +{ + // Get address of connected peer + sockaddr peer; + socklen_t peerLen = sizeof(peer); + + getpeername(m_desc, reinterpret_cast (&peer), &peerLen); + + // If server address as specified when connecting is a numeric + // address, try to get a host name for it + if (isNumericAddress(m_serverAddress.c_str())) + { + char host[NI_MAXHOST + 1]; + char service[NI_MAXSERV + 1]; + + if (getnameinfo(reinterpret_cast (&peer), peerLen, + host, sizeof(host), service, sizeof(service), + /* flags */ NI_NAMEREQD) == 0) + { + return string(host); + } + } + + return m_serverAddress; +} + + windowsSocket::size_type windowsSocket::getBlockSize() const { return 16384; // 16 KB diff --git a/src/security/cert/defaultCertificateVerifier.cpp b/src/security/cert/defaultCertificateVerifier.cpp index 6fde5519..65f5f476 100644 --- a/src/security/cert/defaultCertificateVerifier.cpp +++ b/src/security/cert/defaultCertificateVerifier.cpp @@ -50,7 +50,8 @@ defaultCertificateVerifier::defaultCertificateVerifier(const defaultCertificateV } -void defaultCertificateVerifier::verify(ref chain) +void defaultCertificateVerifier::verify + (ref chain, const string& hostname) { if (chain->getCount() == 0) return; @@ -58,13 +59,14 @@ void defaultCertificateVerifier::verify(ref chain) const string type = chain->getAt(0)->getType(); if (type == "X.509") - verifyX509(chain); + verifyX509(chain, hostname); else throw exceptions::unsupported_certificate_type(type); } -void defaultCertificateVerifier::verifyX509(ref chain) +void defaultCertificateVerifier::verifyX509 + (ref chain, const string& hostname) { // For every certificate in the chain, verify that the certificate // has been issued by the next certificate in the chain @@ -141,6 +143,13 @@ void defaultCertificateVerifier::verifyX509(ref chain) throw exceptions::certificate_verification_exception ("Cannot verify certificate against trusted certificates."); } + + // Ensure the first certificate's subject name matches server hostname + if (!firstCert->verifyHostName(hostname)) + { + throw exceptions::certificate_verification_exception + ("Server identity cannot be verified."); + } } diff --git a/src/security/cert/gnutls/X509Certificate_GnuTLS.cpp b/src/security/cert/gnutls/X509Certificate_GnuTLS.cpp index 633004ff..b3f939ec 100644 --- a/src/security/cert/gnutls/X509Certificate_GnuTLS.cpp +++ b/src/security/cert/gnutls/X509Certificate_GnuTLS.cpp @@ -181,6 +181,12 @@ bool X509Certificate_GnuTLS::verify(ref caCert_) const } +bool X509Certificate_GnuTLS::verifyHostName(const string& hostname) const +{ + return gnutls_x509_crt_check_hostname(m_data->cert, hostname.c_str()) != 0; +} + + const datetime X509Certificate_GnuTLS::getActivationDate() const { const time_t t = gnutls_x509_crt_get_activation_time(m_data->cert); diff --git a/src/security/cert/openssl/X509Certificate_OpenSSL.cpp b/src/security/cert/openssl/X509Certificate_OpenSSL.cpp index e47a19a4..3f171a4f 100755 --- a/src/security/cert/openssl/X509Certificate_OpenSSL.cpp +++ b/src/security/cert/openssl/X509Certificate_OpenSSL.cpp @@ -41,6 +41,8 @@ #include "vmime/exception.hpp" #include +#include +#include #include #include #include @@ -330,6 +332,72 @@ bool X509Certificate_OpenSSL::verify(ref caCert_) const } +bool X509Certificate_OpenSSL::verifyHostName(const string& hostname) const +{ + // First, check subject common name against hostname + char CNBuffer[1024]; + CNBuffer[sizeof(CNBuffer - 1)] = '\0'; + + X509_NAME* xname = X509_get_subject_name(m_data->cert); + + if (X509_NAME_get_text_by_NID(xname, NID_commonName, CNBuffer, sizeof(CNBuffer)) != -1) + { + if (strcasecmp(CNBuffer, hostname.c_str()) == 0) + return true; + } + + // Now, look in subject alternative names + for (int i = 0, extCount = X509_get_ext_count(m_data->cert) ; i < extCount ; ++i) + { + X509_EXTENSION* ext = X509_get_ext(m_data->cert, i); + const char* extStr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext))); + + if (strcmp(extStr, "subjectAltName") == 0) + { + const X509V3_EXT_METHOD* method; + + if ((method = X509V3_EXT_get(ext)) != NULL) + { + const unsigned char* extVal = ext->value->data; + void *extValStr; + + if (method->it) + { + extValStr = ASN1_item_d2i + (NULL, &extVal, ext->value->length, ASN1_ITEM_ptr(method->it)); + } + else + { + extValStr = method->d2i + (NULL, &extVal, ext->value->length); + } + + if (extValStr && method->i2v) + { + STACK_OF(CONF_VALUE)* val = method->i2v(method, extValStr, NULL); + + for (int j = 0 ; j < sk_CONF_VALUE_num(val) ; ++j) + { + CONF_VALUE* cnf = sk_CONF_VALUE_value(val, j); + + if ((strcasecmp(cnf->name, "DNS") == 0 && + strcasecmp(cnf->value, hostname.c_str()) == 0) + || + (strncasecmp(cnf->name, "IP", 2) == 0 && + strcasecmp(cnf->value, hostname.c_str()) == 0)) + { + return true; + } + } + } + } + } + } + + return false; +} + + const datetime X509Certificate_OpenSSL::convertX509Date(void* time) const { char* buffer; diff --git a/src/security/sasl/SASLSocket.cpp b/src/security/sasl/SASLSocket.cpp index c3498d22..a4b0ea56 100644 --- a/src/security/sasl/SASLSocket.cpp +++ b/src/security/sasl/SASLSocket.cpp @@ -81,6 +81,18 @@ SASLSocket::size_type SASLSocket::getBlockSize() const } +const string SASLSocket::getPeerName() const +{ + return m_wrapped->getPeerName(); +} + + +const string SASLSocket::getPeerAddress() const +{ + return m_wrapped->getPeerAddress(); +} + + void SASLSocket::receive(string& buffer) { const size_type n = receiveRaw(m_recvBuffer, sizeof(m_recvBuffer)); diff --git a/vmime/net/socket.hpp b/vmime/net/socket.hpp index 4551e3e2..7a14b3d8 100644 --- a/vmime/net/socket.hpp +++ b/vmime/net/socket.hpp @@ -127,6 +127,18 @@ public: */ virtual unsigned int getStatus() const = 0; + /** Return the hostname of peer this socket is connected to. + * + * @return name of the peer, or numeric address if it cannot be found + */ + virtual const string getPeerName() const = 0; + + /** Return the address of peer this socket is connected to. + * + * @return numeric address of the peer + */ + virtual const string getPeerAddress() const = 0; + protected: socket() { } diff --git a/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp b/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp index ca113f17..ba7456d5 100644 --- a/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp +++ b/vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp @@ -76,6 +76,9 @@ public: unsigned int getStatus() const; + const string getPeerName() const; + const string getPeerAddress() const; + private: void internalThrow(); diff --git a/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp b/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp index ab4093f7..6f7bc3d1 100755 --- a/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp +++ b/vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp @@ -80,6 +80,9 @@ public: unsigned int getStatus() const; + const string getPeerName() const; + const string getPeerAddress() const; + private: static int bio_write(BIO* bio, const char* buf, int len); diff --git a/vmime/platforms/posix/posixSocket.hpp b/vmime/platforms/posix/posixSocket.hpp index 78b1c0aa..4c5bc9b0 100644 --- a/vmime/platforms/posix/posixSocket.hpp +++ b/vmime/platforms/posix/posixSocket.hpp @@ -61,6 +61,9 @@ public: unsigned int getStatus() const; + const string getPeerName() const; + const string getPeerAddress() const; + protected: static void throwSocketError(const int err); @@ -73,6 +76,8 @@ private: int m_desc; unsigned int m_status; + + string m_serverAddress; }; diff --git a/vmime/platforms/windows/windowsSocket.hpp b/vmime/platforms/windows/windowsSocket.hpp index ca007a06..8fe65133 100644 --- a/vmime/platforms/windows/windowsSocket.hpp +++ b/vmime/platforms/windows/windowsSocket.hpp @@ -65,6 +65,9 @@ public: unsigned int getStatus() const; + const string getPeerName() const; + const string getPeerAddress() const; + protected: void throwSocketError(const int err); @@ -86,6 +89,8 @@ private: SOCKET m_desc; unsigned int m_status; + + string m_serverAddress; }; diff --git a/vmime/security/cert/X509Certificate.hpp b/vmime/security/cert/X509Certificate.hpp index b7f0b946..a993a91c 100644 --- a/vmime/security/cert/X509Certificate.hpp +++ b/vmime/security/cert/X509Certificate.hpp @@ -115,6 +115,13 @@ public: */ virtual bool verify(ref caCert) const = 0; + /** Verify certificate's subject name against the given hostname. + * + * @param hostname DNS name of the server + * @return true if the match is successful, false otherwise + */ + virtual bool verifyHostName(const string& hostname) const = 0; + /** Gets the expiration date of this certificate. This is the date * at which this certificate will not be valid anymore. * diff --git a/vmime/security/cert/certificateVerifier.hpp b/vmime/security/cert/certificateVerifier.hpp index cf038262..05a66154 100644 --- a/vmime/security/cert/certificateVerifier.hpp +++ b/vmime/security/cert/certificateVerifier.hpp @@ -44,10 +44,12 @@ public: /** Verify that the specified certificate chain is trusted. * * @param chain certificate chain + * @param server hostname * @throw exceptions::certificate_verification_exception if one - * or more certificates can not be trusted + * or more certificates can not be trusted, or the server identity + * cannot be verified */ - virtual void verify(ref chain) = 0; + virtual void verify(ref chain, const string& hostname) = 0; }; diff --git a/vmime/security/cert/defaultCertificateVerifier.hpp b/vmime/security/cert/defaultCertificateVerifier.hpp index 6f650f39..81262b8b 100644 --- a/vmime/security/cert/defaultCertificateVerifier.hpp +++ b/vmime/security/cert/defaultCertificateVerifier.hpp @@ -63,7 +63,7 @@ public: // Implementation of 'certificateVerifier' - void verify(ref chain); + void verify(ref chain, const string& hostname); private: @@ -71,7 +71,7 @@ private: * * @param chain list of X.509 certificates */ - void verifyX509(ref chain); + void verifyX509(ref chain, const string& hostname); std::vector > m_x509RootCAs; diff --git a/vmime/security/cert/gnutls/X509Certificate_GnuTLS.hpp b/vmime/security/cert/gnutls/X509Certificate_GnuTLS.hpp index c720c1fb..b06b712f 100644 --- a/vmime/security/cert/gnutls/X509Certificate_GnuTLS.hpp +++ b/vmime/security/cert/gnutls/X509Certificate_GnuTLS.hpp @@ -62,6 +62,8 @@ public: bool verify(ref caCert) const; + bool verifyHostName(const string& hostname) const; + const datetime getExpirationDate() const; const datetime getActivationDate() const; diff --git a/vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp b/vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp index d9083b06..ef92b35f 100644 --- a/vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp +++ b/vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp @@ -65,6 +65,8 @@ public: bool verify(ref caCert) const; + bool verifyHostName(const string& hostname) const; + const datetime getExpirationDate() const; const datetime getActivationDate() const; diff --git a/vmime/security/sasl/SASLSocket.hpp b/vmime/security/sasl/SASLSocket.hpp index 0e7d209f..c450b998 100644 --- a/vmime/security/sasl/SASLSocket.hpp +++ b/vmime/security/sasl/SASLSocket.hpp @@ -69,6 +69,9 @@ public: unsigned int getStatus() const; + const string getPeerName() const; + const string getPeerAddress() const; + private: ref m_session;