diff options
Diffstat (limited to '')
-rw-r--r-- | src/net/tls/TLSSession.cpp | 342 | ||||
-rw-r--r-- | src/net/tls/TLSSocket.cpp | 391 | ||||
-rw-r--r-- | src/net/tls/X509Certificate.cpp | 273 | ||||
-rw-r--r-- | src/net/tls/certificateChain.cpp (renamed from vmime/net/authHelper.hpp) | 27 | ||||
-rw-r--r-- | src/net/tls/defaultCertificateVerifier.cpp | 164 |
5 files changed, 1189 insertions, 8 deletions
diff --git a/src/net/tls/TLSSession.cpp b/src/net/tls/TLSSession.cpp new file mode 100644 index 00000000..fb84714c --- /dev/null +++ b/src/net/tls/TLSSession.cpp @@ -0,0 +1,342 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include <gnutls/gnutls.h> +#include <gnutls/extra.h> + +#include "vmime/net/tls/TLSSession.hpp" + +#include "vmime/exception.hpp" + + +// Enable GnuTLS debugging by defining GNUTLS_DEBUG +//#define GNUTLS_DEBUG 1 + + +#if VMIME_DEBUG && GNUTLS_DEBUG + #include <iostream> +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + +namespace vmime { +namespace net { +namespace tls { + + +#ifndef VMIME_BUILDING_DOC + +// Initialize GNU TLS library +struct TLSGlobal +{ + TLSGlobal() + { + gnutls_global_init(); + //gnutls_global_init_extra(); + +#if VMIME_DEBUG && GNUTLS_DEBUG + gnutls_global_set_log_function(TLSLogFunc); + gnutls_global_set_log_level(10); +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + gnutls_anon_allocate_client_credentials(&anonCred); + gnutls_certificate_allocate_credentials(&certCred); + } + + ~TLSGlobal() + { + gnutls_anon_free_client_credentials(anonCred); + gnutls_certificate_free_credentials(certCred); + + gnutls_global_deinit(); + } + +#if VMIME_DEBUG && GNUTLS_DEBUG + + static void TLSLogFunc(int level, const char *str) + { + std::cerr << "GNUTLS: [" << level << "] " << str << std::endl; + } + +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + + gnutls_anon_client_credentials anonCred; + gnutls_certificate_credentials certCred; +}; + +static TLSGlobal g_gnutlsGlobal; + + +#endif // VMIME_BUILDING_DOC + + + +TLSSession::TLSSession(ref <certificateVerifier> cv) + : m_certVerifier(cv) +{ + int res; + + m_gnutlsSession = new gnutls_session; + + if (gnutls_init(m_gnutlsSession, GNUTLS_CLIENT) != 0) + throw std::bad_alloc(); + + // Sets some default priority on the ciphers, key exchange methods, + // macs and compression methods. + gnutls_set_default_priority(*m_gnutlsSession); + + // Sets the priority on the certificate types supported by gnutls. + // Priority is higher for types specified before others. After + // specifying the types you want, you must append a 0. + const int certTypePriority[] = { GNUTLS_CRT_X509, 0 }; + + res = gnutls_certificate_type_set_priority + (*m_gnutlsSession, certTypePriority); + + if (res < 0) + { + throwTLSException + ("gnutls_certificate_type_set_priority", res); + } + + // Sets the priority on the protocol types + const int protoPriority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + + res = gnutls_protocol_set_priority(*m_gnutlsSession, protoPriority); + + if (res < 0) + { + throwTLSException + ("gnutls_certificate_type_set_priority", res); + } + + // Priority on the ciphers + const int cipherPriority[] = + { + GNUTLS_CIPHER_ARCFOUR_128, + GNUTLS_CIPHER_3DES_CBC, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_256_CBC, + GNUTLS_CIPHER_ARCFOUR_40, + GNUTLS_CIPHER_RC2_40_CBC, + GNUTLS_CIPHER_DES_CBC, + 0 + }; + + gnutls_cipher_set_priority(*m_gnutlsSession, cipherPriority); + + // Priority on MACs + const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0}; + + gnutls_mac_set_priority(*m_gnutlsSession, macPriority); + + // Priority on key exchange methods + const int kxPriority[] = + { + GNUTLS_KX_RSA, + GNUTLS_KX_DHE_DSS, + GNUTLS_KX_DHE_RSA, + GNUTLS_KX_ANON_DH, + GNUTLS_KX_SRP, + GNUTLS_KX_RSA_EXPORT, + GNUTLS_KX_SRP_RSA, + GNUTLS_KX_SRP_DSS, + 0 + }; + + gnutls_kx_set_priority(*m_gnutlsSession, kxPriority); + + // Priority on compression methods + const int compressionPriority[] = + { + GNUTLS_COMP_ZLIB, + //GNUTLS_COMP_LZO, + GNUTLS_COMP_NULL, + 0 + }; + + gnutls_compression_set_priority(*m_gnutlsSession, compressionPriority); + + // Initialize credentials + gnutls_credentials_set(*m_gnutlsSession, + GNUTLS_CRD_ANON, &g_gnutlsGlobal.anonCred); + + gnutls_credentials_set(*m_gnutlsSession, + GNUTLS_CRD_CERTIFICATE, &g_gnutlsGlobal.certCred); +} + + +TLSSession::TLSSession(const TLSSession&) + : object() +{ + // Not used +} + + +TLSSession::~TLSSession() +{ + if (m_gnutlsSession) + { + gnutls_deinit(*m_gnutlsSession); + + delete m_gnutlsSession; + m_gnutlsSession = NULL; + } +} + + +ref <TLSSocket> TLSSession::getSocket(ref <socket> sok) +{ + return vmime::create <TLSSocket> + (thisRef().dynamicCast <TLSSession>(), sok); +} + + +ref <tls::certificateVerifier> TLSSession::getCertificateVerifier() +{ + return m_certVerifier; +} + + +void TLSSession::throwTLSException(const string& fname, const int code) +{ + string msg = fname + "() returned "; + +#define ERROR(x) \ + case x: msg += #x; break; + + switch (code) + { + ERROR(GNUTLS_E_SUCCESS) + ERROR(GNUTLS_E_UNKNOWN_COMPRESSION_ALGORITHM) + ERROR(GNUTLS_E_UNKNOWN_CIPHER_TYPE) + ERROR(GNUTLS_E_LARGE_PACKET) + ERROR(GNUTLS_E_UNSUPPORTED_VERSION_PACKET) + ERROR(GNUTLS_E_UNEXPECTED_PACKET_LENGTH) + ERROR(GNUTLS_E_INVALID_SESSION) + ERROR(GNUTLS_E_FATAL_ALERT_RECEIVED) + ERROR(GNUTLS_E_UNEXPECTED_PACKET) + ERROR(GNUTLS_E_WARNING_ALERT_RECEIVED) + ERROR(GNUTLS_E_ERROR_IN_FINISHED_PACKET) + ERROR(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) + ERROR(GNUTLS_E_UNKNOWN_CIPHER_SUITE) + ERROR(GNUTLS_E_UNWANTED_ALGORITHM) + ERROR(GNUTLS_E_MPI_SCAN_FAILED) + ERROR(GNUTLS_E_DECRYPTION_FAILED) + ERROR(GNUTLS_E_MEMORY_ERROR) + ERROR(GNUTLS_E_DECOMPRESSION_FAILED) + ERROR(GNUTLS_E_COMPRESSION_FAILED) + ERROR(GNUTLS_E_AGAIN) + ERROR(GNUTLS_E_EXPIRED) + ERROR(GNUTLS_E_DB_ERROR) + ERROR(GNUTLS_E_SRP_PWD_ERROR) + ERROR(GNUTLS_E_INSUFFICIENT_CREDENTIALS) + ERROR(GNUTLS_E_HASH_FAILED) + ERROR(GNUTLS_E_BASE64_DECODING_ERROR) + ERROR(GNUTLS_E_MPI_PRINT_FAILED) + ERROR(GNUTLS_E_REHANDSHAKE) + ERROR(GNUTLS_E_GOT_APPLICATION_DATA) + ERROR(GNUTLS_E_RECORD_LIMIT_REACHED) + ERROR(GNUTLS_E_ENCRYPTION_FAILED) + ERROR(GNUTLS_E_PK_ENCRYPTION_FAILED) + ERROR(GNUTLS_E_PK_DECRYPTION_FAILED) + ERROR(GNUTLS_E_PK_SIGN_FAILED) + ERROR(GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION) + ERROR(GNUTLS_E_KEY_USAGE_VIOLATION) + ERROR(GNUTLS_E_NO_CERTIFICATE_FOUND) + ERROR(GNUTLS_E_INVALID_REQUEST) + ERROR(GNUTLS_E_SHORT_MEMORY_BUFFER) + ERROR(GNUTLS_E_INTERRUPTED) + ERROR(GNUTLS_E_PUSH_ERROR) + ERROR(GNUTLS_E_PULL_ERROR) + ERROR(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER) + ERROR(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + ERROR(GNUTLS_E_PKCS1_WRONG_PAD) + ERROR(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION) + ERROR(GNUTLS_E_INTERNAL_ERROR) + ERROR(GNUTLS_E_DH_PRIME_UNACCEPTABLE) + ERROR(GNUTLS_E_FILE_ERROR) + ERROR(GNUTLS_E_TOO_MANY_EMPTY_PACKETS) + ERROR(GNUTLS_E_UNKNOWN_PK_ALGORITHM) + ERROR(GNUTLS_E_INIT_LIBEXTRA) + ERROR(GNUTLS_E_LIBRARY_VERSION_MISMATCH) + ERROR(GNUTLS_E_NO_TEMPORARY_RSA_PARAMS) + ERROR(GNUTLS_E_LZO_INIT_FAILED) + ERROR(GNUTLS_E_NO_COMPRESSION_ALGORITHMS) + ERROR(GNUTLS_E_NO_CIPHER_SUITES) + ERROR(GNUTLS_E_OPENPGP_GETKEY_FAILED) + ERROR(GNUTLS_E_PK_SIG_VERIFY_FAILED) + ERROR(GNUTLS_E_ILLEGAL_SRP_USERNAME) + ERROR(GNUTLS_E_SRP_PWD_PARSING_ERROR) + ERROR(GNUTLS_E_NO_TEMPORARY_DH_PARAMS) + ERROR(GNUTLS_E_ASN1_ELEMENT_NOT_FOUND) + ERROR(GNUTLS_E_ASN1_IDENTIFIER_NOT_FOUND) + ERROR(GNUTLS_E_ASN1_DER_ERROR) + ERROR(GNUTLS_E_ASN1_VALUE_NOT_FOUND) + ERROR(GNUTLS_E_ASN1_GENERIC_ERROR) + ERROR(GNUTLS_E_ASN1_VALUE_NOT_VALID) + ERROR(GNUTLS_E_ASN1_TAG_ERROR) + ERROR(GNUTLS_E_ASN1_TAG_IMPLICIT) + ERROR(GNUTLS_E_ASN1_TYPE_ANY_ERROR) + ERROR(GNUTLS_E_ASN1_SYNTAX_ERROR) + ERROR(GNUTLS_E_ASN1_DER_OVERFLOW) + ERROR(GNUTLS_E_OPENPGP_TRUSTDB_VERSION_UNSUPPORTED) + ERROR(GNUTLS_E_OPENPGP_UID_REVOKED) + ERROR(GNUTLS_E_CERTIFICATE_ERROR) + //ERROR(GNUTLS_E_X509_CERTIFICATE_ERROR) + ERROR(GNUTLS_E_CERTIFICATE_KEY_MISMATCH) + ERROR(GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE) + ERROR(GNUTLS_E_X509_UNKNOWN_SAN) + ERROR(GNUTLS_E_OPENPGP_FINGERPRINT_UNSUPPORTED) + ERROR(GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE) + ERROR(GNUTLS_E_UNKNOWN_HASH_ALGORITHM) + ERROR(GNUTLS_E_UNKNOWN_PKCS_CONTENT_TYPE) + ERROR(GNUTLS_E_UNKNOWN_PKCS_BAG_TYPE) + ERROR(GNUTLS_E_INVALID_PASSWORD) + ERROR(GNUTLS_E_MAC_VERIFY_FAILED) + ERROR(GNUTLS_E_CONSTRAINT_ERROR) + ERROR(GNUTLS_E_BASE64_ENCODING_ERROR) + ERROR(GNUTLS_E_INCOMPATIBLE_GCRYPT_LIBRARY) + //ERROR(GNUTLS_E_INCOMPATIBLE_CRYPTO_LIBRARY) + ERROR(GNUTLS_E_INCOMPATIBLE_LIBTASN1_LIBRARY) + ERROR(GNUTLS_E_OPENPGP_KEYRING_ERROR) + ERROR(GNUTLS_E_X509_UNSUPPORTED_OID) + //ERROR(GNUTLS_E_RANDOM_FAILED) + ERROR(GNUTLS_E_UNIMPLEMENTED_FEATURE) + + default: + + msg += "unknown error"; + break; + } + +#undef ERROR + + throw exceptions::tls_exception(msg); +} + + +} // tls +} // net +} // vmime + diff --git a/src/net/tls/TLSSocket.cpp b/src/net/tls/TLSSocket.cpp new file mode 100644 index 00000000..ebf3214b --- /dev/null +++ b/src/net/tls/TLSSocket.cpp @@ -0,0 +1,391 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "vmime/net/tls/TLSSocket.hpp" +#include "vmime/net/tls/TLSSession.hpp" + +#include "vmime/platformDependant.hpp" + +#include "vmime/net/tls/X509Certificate.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +TLSSocket::TLSSocket(ref <TLSSession> session, ref <socket> sok) + : m_session(session), m_wrapped(sok), m_connected(false), + m_handshaking(false), m_ex(NULL) +{ + gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this); + + gnutls_transport_set_push_function(*m_session->m_gnutlsSession, gnutlsPushFunc); + gnutls_transport_set_pull_function(*m_session->m_gnutlsSession, gnutlsPullFunc); +} + + +TLSSocket::~TLSSocket() +{ + try + { + disconnect(); + } + catch (...) + { + // Don't throw exception in destructor + } +} + + +void TLSSocket::connect(const string& address, const port_t port) +{ + m_wrapped->connect(address, port); + + handshake(NULL); + + m_connected = true; +} + + +void TLSSocket::disconnect() +{ + if (m_connected) + { + gnutls_bye(*m_session->m_gnutlsSession, GNUTLS_SHUT_RDWR); + + m_wrapped->disconnect(); + + m_connected = false; + } +} + + +const bool TLSSocket::isConnected() const +{ + return m_wrapped->isConnected() && m_connected; +} + + +void TLSSocket::receive(string& buffer) +{ + const int size = receiveRaw(m_buffer, sizeof(m_buffer)); + buffer = vmime::string(m_buffer, size); +} + + +void TLSSocket::send(const string& buffer) +{ + sendRaw(buffer.data(), buffer.length()); +} + + +const int TLSSocket::receiveRaw(char* buffer, const int count) +{ + const ssize_t ret = gnutls_record_recv + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + return 0; + + TLSSession::throwTLSException("gnutls_record_recv", ret); + } + + return static_cast <int>(ret); +} + + +void TLSSocket::sendRaw(const char* buffer, const int count) +{ + gnutls_record_send + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); +} + + +void TLSSocket::handshake(ref <timeoutHandler> toHandler) +{ + if (toHandler) + toHandler->resetTimeOut(); + + // Start handshaking process + m_handshaking = true; + m_toHandler = toHandler; + + try + { + while (true) + { + const int ret = gnutls_handshake(*m_session->m_gnutlsSession); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || + ret == GNUTLS_E_INTERRUPTED) + { + // Non-fatal error + platformDependant::getHandler()->wait(); + } + else + { + TLSSession::throwTLSException("gnutls_handshake", ret); + } + } + else + { + // Successful handshake + break; + } + } + } + catch (...) + { + m_handshaking = false; + m_toHandler = NULL; + + throw; + } + + m_handshaking = false; + m_toHandler = NULL; + + // Verify server's certificate(s) + ref <certificateChain> certs = getPeerCertificates(); + + if (certs == NULL) + throw exceptions::tls_exception("No peer certificate."); + + m_session->getCertificateVerifier()->verify(certs); + + m_connected = true; +} + + +ssize_t TLSSocket::gnutlsPushFunc + (gnutls_transport_ptr trspt, const void* data, size_t len) +{ + TLSSocket* sok = reinterpret_cast <TLSSocket*>(trspt); + + try + { + sok->m_wrapped->sendRaw + (reinterpret_cast <const char*>(data), static_cast <int>(len)); + } + catch (exception& e) + { + // Workaround for bad behaviour when throwing C++ exceptions + // from C functions (GNU TLS) + sok->m_ex = e.clone(); + return -1; + } + + return len; +} + + +ssize_t TLSSocket::gnutlsPullFunc + (gnutls_transport_ptr trspt, void* data, size_t len) +{ + TLSSocket* sok = reinterpret_cast <TLSSocket*>(trspt); + + try + { + // Workaround for cross-platform asynchronous handshaking: + // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv() + // returns -1 and errno is set to EGAIN... + if (sok->m_handshaking) + { + while (true) + { + const ssize_t ret = static_cast <ssize_t> + (sok->m_wrapped->receiveRaw + (reinterpret_cast <char*>(data), + static_cast <int>(len))); + + if (ret == 0) + { + // No data available yet + platformDependant::getHandler()->wait(); + } + else + { + return ret; + } + + // Check whether the time-out delay is elapsed + if (sok->m_toHandler && sok->m_toHandler->isTimeOut()) + { + if (!sok->m_toHandler->handleTimeOut()) + throw exceptions::operation_timed_out(); + + sok->m_toHandler->resetTimeOut(); + } + } + } + else + { + const ssize_t n = static_cast <ssize_t> + (sok->m_wrapped->receiveRaw + (reinterpret_cast <char*>(data), + static_cast <int>(len))); + + if (n == 0) + return GNUTLS_E_AGAIN; // This seems like a hack, really... + + return n; + } + } + catch (exception& e) + { + // Workaround for bad behaviour when throwing C++ exceptions + // from C functions (GNU TLS) + sok->m_ex = e.clone(); + return -1; + } +} + + +ref <certificateChain> TLSSocket::getPeerCertificates() +{ + unsigned int certCount = 0; + const gnutls_datum* rawData = gnutls_certificate_get_peers + (*m_session->m_gnutlsSession, &certCount); + + // Try X.509 + gnutls_x509_crt* x509Certs = new gnutls_x509_crt[certCount]; + + unsigned int count = certCount; + + int res = gnutls_x509_crt_list_import + (x509Certs, &count, rawData, GNUTLS_X509_FMT_PEM, 0); + + if (res <= 0) + { + count = certCount; + + res = gnutls_x509_crt_list_import + (x509Certs, &count, rawData, GNUTLS_X509_FMT_DER, 0); + } + + if (res >= 1) + { + std::vector <ref <certificate> > certs; + bool error = false; + + count = static_cast <unsigned int>(res); + + for (unsigned int i = 0 ; i < count ; ++i) + { + size_t dataSize = 0; + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, NULL, &dataSize); + + byte* data = new byte[dataSize]; + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, data, &dataSize); + + ref <X509Certificate> cert = + X509Certificate::import(data, dataSize); + + if (cert != NULL) + certs.push_back(cert); + else + error = true; + + delete [] data; + + gnutls_x509_crt_deinit(x509Certs[i]); + } + + delete [] x509Certs; + + if (error) + return NULL; + + return vmime::create <certificateChain>(certs); + } + + delete [] x509Certs; + + return NULL; +} + + +// Following is a workaround for C++ exceptions to pass correctly between +// C and C++ calls. +// +// gnutls_record_recv() calls TLSSocket::gnutlsPullFunc, and exceptions +// thrown by the socket can not not catched. + +#ifndef VMIME_BUILDING_DOC + +class TLSSocket_DeleteExWrapper : public object +{ +public: + + TLSSocket_DeleteExWrapper(exception* ex) : m_ex(ex) { } + ~TLSSocket_DeleteExWrapper() { delete m_ex; } + +private: + + exception* m_ex; +}; + +#endif // VMIME_BUILDING_DOC + + +void TLSSocket::internalThrow() +{ + static std::vector <ref <TLSSocket_DeleteExWrapper> > exToDelete; + + if (m_ex) + { + // To avoid memory leaks + exToDelete.push_back(vmime::create <TLSSocket_DeleteExWrapper>(m_ex)); + + throw *m_ex; + } +} + + +} // tls +} // net +} // vmime + diff --git a/src/net/tls/X509Certificate.cpp b/src/net/tls/X509Certificate.cpp new file mode 100644 index 00000000..cfb52a1d --- /dev/null +++ b/src/net/tls/X509Certificate.cpp @@ -0,0 +1,273 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include <ctime> + +#include "vmime/net/tls/X509Certificate.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +#ifndef VMIME_BUILDING_DOC + +struct X509CertificateInternalData +{ + X509CertificateInternalData() + { + gnutls_x509_crt_init(&cert); + } + + ~X509CertificateInternalData() + { + gnutls_x509_crt_deinit(cert); + } + + + gnutls_x509_crt cert; +}; + +#endif // VMIME_BUILDING_DOC + + +X509Certificate::X509Certificate() + : m_data(new X509CertificateInternalData) +{ +} + + +X509Certificate::X509Certificate(const X509Certificate&) + : certificate(), m_data(NULL) +{ + // Not used +} + + +X509Certificate::~X509Certificate() +{ + delete m_data; +} + + +// static +ref <X509Certificate> X509Certificate::import(utility::inputStream& is) +{ + byteArray bytes; + utility::stream::value_type chunk[4096]; + + while (!is.eof()) + { + const int len = is.read(chunk, sizeof(chunk)); + bytes.insert(bytes.end(), chunk, chunk + len); + } + + return import(&bytes[0], bytes.size()); +} + + +// static +ref <X509Certificate> X509Certificate::import + (const byte* data, const unsigned int length) +{ + ref <X509Certificate> cert = vmime::create <X509Certificate>(); + + gnutls_datum buffer; + buffer.data = const_cast <byte*>(data); + buffer.size = length; + + // Try DER format + if (gnutls_x509_crt_import(cert->m_data->cert, &buffer, GNUTLS_X509_FMT_DER) >= 0) + return cert; + + // Try PEM format + if (gnutls_x509_crt_import(cert->m_data->cert, &buffer, GNUTLS_X509_FMT_PEM) >= 0) + return cert; + + return NULL; +} + + +void X509Certificate::write + (utility::outputStream& os, const Format format) const +{ + size_t dataSize = 0; + gnutls_x509_crt_fmt fmt = GNUTLS_X509_FMT_DER; + + switch (format) + { + case FORMAT_DER: fmt = GNUTLS_X509_FMT_DER; break; + case FORMAT_PEM: fmt = GNUTLS_X509_FMT_PEM; break; + } + + gnutls_x509_crt_export(m_data->cert, fmt, NULL, &dataSize); + + byte* data = new byte[dataSize]; + + gnutls_x509_crt_export(m_data->cert, fmt, data, &dataSize); + + try + { + os.write(reinterpret_cast <utility::stream::value_type*>(data), dataSize); + } + catch (...) + { + delete [] data; + throw; + } +} + + +const byteArray X509Certificate::getSerialNumber() const +{ + char serial[64]; + size_t serialSize = sizeof(serial); + + gnutls_x509_crt_get_serial(m_data->cert, serial, &serialSize); + + return byteArray(serial, serial + serialSize); +} + + +const bool X509Certificate::checkIssuer + (ref <const X509Certificate> issuer) const +{ + return (gnutls_x509_crt_check_issuer + (m_data->cert, issuer->m_data->cert) >= 1); +} + + +const bool X509Certificate::verify(ref <const X509Certificate> caCert) const +{ + unsigned int verify = 0; + + const int res = gnutls_x509_crt_verify + (m_data->cert, &(caCert->m_data->cert), 1, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, + &verify); + + return (res == 0 && verify == 0); +} + + +const datetime X509Certificate::getActivationDate() const +{ + const time_t t = gnutls_x509_crt_get_activation_time(m_data->cert); + return datetime(t); +} + + +const datetime X509Certificate::getExpirationDate() const +{ + const time_t t = gnutls_x509_crt_get_expiration_time(m_data->cert); + return datetime(t); +} + + +const byteArray X509Certificate::getFingerprint(const DigestAlgorithm algo) const +{ + gnutls_digest_algorithm galgo; + + switch (algo) + { + case DIGEST_MD5: + + galgo = GNUTLS_DIG_MD5; + break; + + default: + case DIGEST_SHA1: + + galgo = GNUTLS_DIG_SHA; + break; + } + + size_t bufferSize = 0; + gnutls_x509_crt_get_fingerprint + (m_data->cert, galgo, NULL, &bufferSize); + + byte* buffer = new byte[bufferSize]; + + if (gnutls_x509_crt_get_fingerprint + (m_data->cert, galgo, buffer, &bufferSize) == 0) + { + byteArray res; + res.insert(res.end(), buffer, buffer + bufferSize); + + delete [] buffer; + + return res; + } + + delete [] buffer; + + return byteArray(); +} + + +const byteArray X509Certificate::getEncoded() const +{ + byteArray bytes; + utility::outputStreamByteArrayAdapter os(bytes); + + write(os, FORMAT_DER); + + return bytes; +} + + +const string X509Certificate::getType() const +{ + return "X.509"; +} + + +const int X509Certificate::getVersion() const +{ + return gnutls_x509_crt_get_version(m_data->cert); +} + + +const bool X509Certificate::equals(ref <const certificate> other) const +{ + ref <const X509Certificate> otherX509 = + other.dynamicCast <const X509Certificate>(); + + if (!otherX509) + return false; + + const byteArray fp1 = getFingerprint(DIGEST_MD5); + const byteArray fp2 = otherX509->getFingerprint(DIGEST_MD5); + + return fp1 == fp2; +} + + +} // tls +} // net +} // vmime + diff --git a/vmime/net/authHelper.hpp b/src/net/tls/certificateChain.cpp index 54487fbe..52855cc2 100644 --- a/vmime/net/authHelper.hpp +++ b/src/net/tls/certificateChain.cpp @@ -21,22 +21,33 @@ // the GNU General Public License cover the whole combination. // -#ifndef VMIME_NET_AUTHHELPER_HPP_INCLUDED -#define VMIME_NET_AUTHHELPER_HPP_INCLUDED - - -#include "vmime/types.hpp" +#include "vmime/net/tls/certificateChain.hpp" namespace vmime { namespace net { +namespace tls { + + +certificateChain::certificateChain(const std::vector <ref <certificate> >& certs) + : m_certs(certs) +{ +} -void hmac_md5(const string& text, const string& key, string& hexDigest); +const unsigned int certificateChain::getCount() const +{ + return static_cast <unsigned int>(m_certs.size()); +} +ref <certificate> certificateChain::getAt(const unsigned int index) +{ + return m_certs[index]; +} + + +} // tls } // net } // vmime - -#endif // VMIME_NET_AUTHHELPER_HPP_INCLUDED diff --git a/src/net/tls/defaultCertificateVerifier.cpp b/src/net/tls/defaultCertificateVerifier.cpp new file mode 100644 index 00000000..de0c6e45 --- /dev/null +++ b/src/net/tls/defaultCertificateVerifier.cpp @@ -0,0 +1,164 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/net/tls/defaultCertificateVerifier.hpp" + +#include "vmime/net/tls/X509Certificate.hpp" + +#include "vmime/exception.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +defaultCertificateVerifier::defaultCertificateVerifier() +{ +} + + +defaultCertificateVerifier::~defaultCertificateVerifier() +{ +} + + +defaultCertificateVerifier::defaultCertificateVerifier(const defaultCertificateVerifier&) + : certificateVerifier() +{ + // Not used +} + + +void defaultCertificateVerifier::verify(ref <certificateChain> chain) +{ + if (chain->getCount() == 0) + return; + + const string type = chain->getAt(0)->getType(); + + if (type == "X.509") + verifyX509(chain); + else + throw exceptions::unsupported_certificate_type(type); +} + + +void defaultCertificateVerifier::verifyX509(ref <certificateChain> chain) +{ + // For every certificate in the chain, verify that the certificate + // has been issued by the next certificate in the chain + if (chain->getCount() >= 2) + { + for (unsigned int i = 0 ; i < chain->getCount() - 1 ; ++i) + { + ref <X509Certificate> cert = + chain->getAt(i).dynamicCast <X509Certificate>(); + + ref <X509Certificate> next = + chain->getAt(i + 1).dynamicCast <X509Certificate>(); + + if (!cert->checkIssuer(next)) + { + throw exceptions::certificate_verification_exception + ("Subject/issuer verification failed."); + } + } + } + + // For every certificate in the chain, verify that the certificate + // is valid at the current time + const datetime now = datetime::now(); + + for (unsigned int i = 0 ; i < chain->getCount() ; ++i) + { + ref <X509Certificate> cert = + chain->getAt(i).dynamicCast <X509Certificate>(); + + const datetime begin = cert->getActivationDate(); + const datetime end = cert->getExpirationDate(); + + if (now < begin || now > end) + { + throw exceptions::certificate_verification_exception + ("Validity date check failed."); + } + } + + // Check whether the certificate can be trusted + + // -- First, verify that the the last certificate in the chain was + // -- issued by a third-party that we trust + ref <X509Certificate> lastCert = + chain->getAt(chain->getCount() - 1).dynamicCast <X509Certificate>(); + + bool trusted = false; + + for (unsigned int i = 0 ; !trusted && i < m_x509RootCAs.size() ; ++i) + { + ref <X509Certificate> rootCa = m_x509RootCAs[i]; + + if (lastCert->verify(rootCa)) + trusted = true; + } + + // -- Next, if the issuer certificate cannot be verified against + // -- root CAs, compare the subject's certificate against the + // -- trusted certificates + ref <X509Certificate> firstCert = + chain->getAt(0).dynamicCast <X509Certificate>(); + + for (unsigned int i = 0 ; !trusted && i < m_x509TrustedCerts.size() ; ++i) + { + ref <X509Certificate> cert = m_x509TrustedCerts[i]; + + if (firstCert->equals(cert)) + trusted = true; + } + + if (!trusted) + { + throw exceptions::certificate_verification_exception + ("Cannot verify certificate against trusted certificates."); + } +} + + +void defaultCertificateVerifier::setX509RootCAs + (const std::vector <ref <X509Certificate> >& caCerts) +{ + m_x509RootCAs = caCerts; +} + + +void defaultCertificateVerifier::setX509TrustedCerts + (const std::vector <ref <X509Certificate> >& trustedCerts) +{ + m_x509TrustedCerts = trustedCerts; +} + + +} // tls +} // net +} // vmime + |