diff options
| author | Vincent Richard <[email protected]> | 2012-11-03 09:27:12 +0100 |
|---|---|---|
| committer | Vincent Richard <[email protected]> | 2012-11-03 09:27:12 +0100 |
| commit | bc63892291ecf245dd210e236ab14f12e31baef8 (patch) | |
| tree | 031bc486b0cafc527f516e6890105521bc7f2afc /src/net/tls/gnutls | |
| parent | Migrated build system to CMake. Conditional file compilation. Automatic selec... (diff) | |
| download | vmime-bc63892291ecf245dd210e236ab14f12e31baef8.tar.gz vmime-bc63892291ecf245dd210e236ab14f12e31baef8.zip | |
OpenSSL support (thanks to Mehmet Bozkurt).
Diffstat (limited to 'src/net/tls/gnutls')
| -rw-r--r-- | src/net/tls/gnutls/TLSSession_GnuTLS.cpp | 304 | ||||
| -rw-r--r-- | src/net/tls/gnutls/TLSSocket_GnuTLS.cpp | 424 |
2 files changed, 728 insertions, 0 deletions
diff --git a/src/net/tls/gnutls/TLSSession_GnuTLS.cpp b/src/net/tls/gnutls/TLSSession_GnuTLS.cpp new file mode 100644 index 00000000..644a74ec --- /dev/null +++ b/src/net/tls/gnutls/TLSSession_GnuTLS.cpp @@ -0,0 +1,304 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2009 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 3 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/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include <gnutls/gnutls.h> +#if GNUTLS_VERSION_NUMBER < 0x030000 +#include <gnutls/extra.h> +#endif + + +// Dependency on gcrypt is not needed since GNU TLS version 2.12. +// See here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=638651 +#if GNUTLS_VERSION_NUMBER <= 0x020b00 +# define VMIME_GNUTLS_NEEDS_GCRYPT 1 +#endif + +#if VMIME_HAVE_PTHREAD +# include <pthread.h> +# if VMIME_GNUTLS_NEEDS_GCRYPT +# include <gcrypt.h> +# endif +# include <errno.h> +#endif // VMIME_HAVE_PTHREAD + +#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp" +#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp" + +#include "vmime/exception.hpp" + + +// Enable GnuTLS debugging by defining GNUTLS_DEBUG +//#define GNUTLS_DEBUG 1 + + +#include <sstream> +#include <iomanip> + +#if VMIME_DEBUG && GNUTLS_DEBUG + #include <iostream> +#endif // VMIME_DEBUG && GNUTLS_DEBUG + + +#if VMIME_HAVE_PTHREAD && VMIME_GNUTLS_NEEDS_GCRYPT && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) +extern "C" +{ + GCRY_THREAD_OPTION_PTHREAD_IMPL; +} +#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL + + +namespace vmime { +namespace net { +namespace tls { + + +#ifndef VMIME_BUILDING_DOC + +// Initialize GNU TLS library +struct TLSGlobal +{ + TLSGlobal() + { +#if VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) + #if VMIME_GNUTLS_NEEDS_GCRYPT + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + #endif // VMIME_GNUTLS_NEEDS_GCRYPT +#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL + + 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 + + + +// static +ref <TLSSession> TLSSession::create(ref <security::cert::certificateVerifier> cv) +{ + return vmime::create <TLSSession_GnuTLS>(cv); +} + + +TLSSession_GnuTLS::TLSSession_GnuTLS(ref <security::cert::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. +#if HAVE_GNUTLS_PRIORITY_FUNCS + gnutls_dh_set_prime_bits(*m_gnutlsSession, 128); + + if ((res = gnutls_priority_set_direct + (*m_gnutlsSession, "NORMAL:%SSL3_RECORD_VERSION", NULL)) != 0) + { + if ((res = gnutls_priority_set_direct + (*m_gnutlsSession, "NORMAL", NULL)) != 0) + { + throwTLSException + ("gnutls_priority_set_direct", res); + } + } + +#else // !HAVE_GNUTLS_PRIORITY_FUNCS + + 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); + +#endif // !HAVE_GNUTLS_PRIORITY_FUNCS + + // 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_GnuTLS::TLSSession_GnuTLS(const TLSSession_GnuTLS&) + : TLSSession() +{ + // Not used +} + + +TLSSession_GnuTLS::~TLSSession_GnuTLS() +{ + if (m_gnutlsSession) + { + gnutls_deinit(*m_gnutlsSession); + + delete m_gnutlsSession; + m_gnutlsSession = NULL; + } +} + + +ref <TLSSocket> TLSSession_GnuTLS::getSocket(ref <socket> sok) +{ + return TLSSocket::wrap(thisRef().dynamicCast <TLSSession>(), sok); +} + + +ref <security::cert::certificateVerifier> TLSSession_GnuTLS::getCertificateVerifier() +{ + return m_certVerifier; +} + + +void TLSSession_GnuTLS::throwTLSException(const string& fname, const int code) +{ + std::ostringstream msg; + + msg << fname + "() returned code "; + msg << std::hex << code; + msg << ": "; + msg << gnutls_strerror(code); + + throw exceptions::tls_exception(msg.str()); +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS diff --git a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp new file mode 100644 index 00000000..35e1d415 --- /dev/null +++ b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp @@ -0,0 +1,424 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2009 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 3 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/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS + + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp" +#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/security/cert/X509Certificate.hpp" + + +namespace vmime { +namespace net { +namespace tls { + + +// static +ref <TLSSocket> TLSSocket::wrap(ref <TLSSession> session, ref <socket> sok) +{ + return vmime::create <TLSSocket_GnuTLS> + (session.dynamicCast <TLSSession_GnuTLS>(), sok); +} + + +TLSSocket_GnuTLS::TLSSocket_GnuTLS(ref <TLSSession_GnuTLS> 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_GnuTLS::~TLSSocket_GnuTLS() +{ + if (m_ex) + { + delete m_ex; + m_ex = NULL; + } + + try + { + disconnect(); + } + catch (...) + { + // Don't throw exception in destructor + } +} + + +void TLSSocket_GnuTLS::connect(const string& address, const port_t port) +{ + m_wrapped->connect(address, port); + + handshake(NULL); + + m_connected = true; +} + + +void TLSSocket_GnuTLS::disconnect() +{ + if (m_connected) + { + gnutls_bye(*m_session->m_gnutlsSession, GNUTLS_SHUT_RDWR); + + m_wrapped->disconnect(); + + m_connected = false; + } +} + + +bool TLSSocket_GnuTLS::isConnected() const +{ + return m_wrapped->isConnected() && m_connected; +} + + +TLSSocket::size_type TLSSocket_GnuTLS::getBlockSize() const +{ + return 16384; // 16 KB +} + + +void TLSSocket_GnuTLS::receive(string& buffer) +{ + const int size = receiveRaw(m_buffer, sizeof(m_buffer)); + buffer = vmime::string(m_buffer, size); +} + + +void TLSSocket_GnuTLS::send(const string& buffer) +{ + sendRaw(buffer.data(), buffer.length()); +} + + +TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type 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_GnuTLS::throwTLSException("gnutls_record_recv", ret); + } + + return static_cast <int>(ret); +} + + +void TLSSocket_GnuTLS::sendRaw(const char* buffer, const size_type count) +{ + gnutls_record_send + (*m_session->m_gnutlsSession, + buffer, static_cast <size_t>(count)); + + if (m_ex) + internalThrow(); +} + + +void TLSSocket_GnuTLS::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 + platform::getHandler()->wait(); + } + else + { + TLSSession_GnuTLS::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 <security::cert::certificateChain> certs = getPeerCertificates(); + + if (certs == NULL) + throw exceptions::tls_exception("No peer certificate."); + + m_session->getCertificateVerifier()->verify(certs); + + m_connected = true; +} + + +ssize_t TLSSocket_GnuTLS::gnutlsPushFunc + (gnutls_transport_ptr trspt, const void* data, size_t len) +{ + TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(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_GnuTLS::gnutlsPullFunc + (gnutls_transport_ptr trspt, void* data, size_t len) +{ + TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(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 + platform::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 <security::cert::certificateChain> TLSSocket_GnuTLS::getPeerCertificates() const +{ + unsigned int certCount = 0; + const gnutls_datum* rawData = gnutls_certificate_get_peers + (*m_session->m_gnutlsSession, &certCount); + + if (rawData == NULL) + return NULL; + + // Try X.509 + gnutls_x509_crt* x509Certs = new gnutls_x509_crt[certCount]; + + for (unsigned int i = 0; i < certCount; ++i) + { + gnutls_x509_crt_init(x509Certs + i); + + int res = gnutls_x509_crt_import(x509Certs[i], rawData + i, + GNUTLS_X509_FMT_DER); + + if (res < 0) + { + // XXX more fine-grained error reporting? + delete [] x509Certs; + return NULL; + } + } + + { + std::vector <ref <security::cert::certificate> > certs; + bool error = false; + + for (unsigned int i = 0 ; i < certCount ; ++i) + { + size_t dataSize = 0; + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, NULL, &dataSize); + + std::vector <byte_t> data(dataSize); + + gnutls_x509_crt_export(x509Certs[i], + GNUTLS_X509_FMT_DER, &data[0], &dataSize); + + ref <security::cert::X509Certificate> cert = + security::cert::X509Certificate::import(&data[0], dataSize); + + if (cert != NULL) + certs.push_back(cert); + else + error = true; + + gnutls_x509_crt_deinit(x509Certs[i]); + } + + delete [] x509Certs; + + if (error) + return NULL; + + return vmime::create <security::cert::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 be caught. + +#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_GnuTLS::internalThrow() +{ + static std::vector <ref <TLSSocket_DeleteExWrapper> > exToDelete; + + if (m_ex) + { + // Reset the current exception pointer to prevent the same + // exception from being thrown again later + exception* ex = m_ex; + m_ex = NULL; + + // To avoid memory leaks + exToDelete.push_back(vmime::create <TLSSocket_DeleteExWrapper>(ex)); + + throw *ex; + } +} + + +} // tls +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS |
