From ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kio/kssl/kssl.cc | 688 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 688 insertions(+) create mode 100644 kio/kssl/kssl.cc (limited to 'kio/kssl/kssl.cc') diff --git a/kio/kssl/kssl.cc b/kio/kssl/kssl.cc new file mode 100644 index 000000000..3996aae20 --- /dev/null +++ b/kio/kssl/kssl.cc @@ -0,0 +1,688 @@ +/* This file is part of the KDE project + * + * Copyright (C) 2000-2003 George Staikos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +// this hack provided by Malte Starostik to avoid glibc/openssl bug +// on some systems +#ifdef KSSL_HAVE_SSL +#include +#include +#include +#define crypt _openssl_crypt +#include +#include +#include +#include +#include +#undef crypt +#endif + +#include "kssl.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define sk_dup d->kossl->sk_dup + +class KSSLPrivate { +public: + KSSLPrivate() { + lastInitTLS = false; + kossl = KOpenSSLProxy::self(); + session = 0L; + } + + ~KSSLPrivate() { + delete session; + session = 0L; + } + + bool lastInitTLS; + KSSLCertificate::KSSLValidation m_cert_vfy_res; + QString proxyPeer; + +#ifdef KSSL_HAVE_SSL + SSL *m_ssl; + SSL_CTX *m_ctx; + SSL_METHOD *m_meth; +#endif + KSSLSession *session; + KOSSL *kossl; +}; + + +KSSL::KSSL(bool init) { + d = new KSSLPrivate; + m_bInit = false; + m_bAutoReconfig = true; + m_cfg = new KSSLSettings(); +#ifdef KSSL_HAVE_SSL + d->m_ssl = 0L; +#endif + + if (init) + initialize(); +} + + +KSSL::~KSSL() { + close(); + delete m_cfg; + delete d; +} + + +int KSSL::seedWithEGD() { +int rc = 0; +#ifdef KSSL_HAVE_SSL + if (m_cfg->useEGD() && !m_cfg->getEGDPath().isEmpty()) { + rc = d->kossl->RAND_egd(m_cfg->getEGDPath().latin1()); + if (rc < 0) + kdDebug(7029) << "KSSL: Error seeding PRNG with the EGD." << endl; + else kdDebug(7029) << "KSSL: PRNG was seeded with " << rc + << " bytes from the EGD." << endl; + } else if (m_cfg->useEFile() && !m_cfg->getEGDPath().isEmpty()) { + rc = d->kossl->RAND_load_file(m_cfg->getEGDPath().latin1(), -1); + if (rc < 0) + kdDebug(7029) << "KSSL: Error seeding PRNG with the entropy file." << endl; + else kdDebug(7029) << "KSSL: PRNG was seeded with " << rc + << " bytes from the entropy file." << endl; + } +#endif +return rc; +} + + +bool KSSL::TLSInit() { +#ifdef KSSL_HAVE_SSL +// kdDebug(7029) << "KSSL TLS initialize" << endl; + if (m_bInit) + return false; + + if (m_bAutoReconfig) + m_cfg->load(); + + if (!m_cfg->tlsv1()) + return false; + + seedWithEGD(); + d->m_meth = d->kossl->TLSv1_client_method(); + d->lastInitTLS = true; + + m_pi.reset(); + + d->m_ctx = d->kossl->SSL_CTX_new(d->m_meth); + if (d->m_ctx == 0L) { + return false; + } + + // set cipher list + QString clist = m_cfg->getCipherList(); + //kdDebug(7029) << "Cipher list: " << clist << endl; + if (!clist.isEmpty()) + d->kossl->SSL_CTX_set_cipher_list(d->m_ctx, const_cast(clist.ascii())); + + m_bInit = true; +return true; +#else +return false; +#endif +} + + +bool KSSL::initialize() { +#ifdef KSSL_HAVE_SSL + kdDebug(7029) << "KSSL initialize" << endl; + if (m_bInit) + return false; + + if (m_bAutoReconfig) + m_cfg->load(); + + seedWithEGD(); + // FIXME: we should be able to force SSL off entirely. + d->lastInitTLS = false; + + m_pi.reset(); + + if (!m_cfg->tlsv1() && !m_cfg->sslv3() && m_cfg->sslv2()) + d->m_meth = d->kossl->SSLv2_client_method(); + else if (m_cfg->tlsv1() && !m_cfg->sslv3() && !m_cfg->sslv2()) + d->m_meth = d->kossl->TLSv1_client_method(); + else if (!m_cfg->tlsv1() && m_cfg->sslv3() && !m_cfg->sslv2()) + d->m_meth = d->kossl->SSLv3_client_method(); + else d->m_meth = d->kossl->SSLv23_client_method(); + +/* +if (m_cfg->sslv2() && m_cfg->sslv3()) kdDebug(7029) << "Double method" << endl; +else if (m_cfg->sslv2()) kdDebug(7029) << "SSL2 method" << endl; +else if (m_cfg->sslv3()) kdDebug(7029) << "SSL3 method" << endl; +*/ + + d->m_ctx = d->kossl->SSL_CTX_new(d->m_meth); + if (d->m_ctx == 0L) { + return false; + } + + // set cipher list + QString clist = m_cfg->getCipherList(); + kdDebug(7029) << "Cipher list: " << clist << endl; + if (!clist.isEmpty()) + d->kossl->SSL_CTX_set_cipher_list(d->m_ctx, const_cast(clist.ascii())); + + m_bInit = true; +return true; +#else +return false; +#endif +} + + +bool KSSL::setSession(const KSSLSession *session) { +#ifdef KSSL_HAVE_SSL + if (!session) { + delete d->session; + d->session = 0L; + return true; + } + + // Obtain a reference by incrementing the reference count. Yuck. + static_cast(session->_session)->references++; + + d->session = new KSSLSession; + d->session->_session = session->_session; + + return true; +#else + return false; +#endif +} + + +void KSSL::close() { +#ifdef KSSL_HAVE_SSL +//kdDebug(7029) << "KSSL close" << endl; + if (!m_bInit) + return; + + delete d->session; + d->session = 0L; + + if (d->m_ssl) { + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0L; + } + + d->kossl->SSL_CTX_free(d->m_ctx); + if (m_cfg->useEFile() && !m_cfg->getEGDPath().isEmpty()) { + d->kossl->RAND_write_file(m_cfg->getEGDPath().latin1()); + } + + m_bInit = false; +#endif +} + + +bool KSSL::reInitialize() { + close(); +return initialize(); +} + +// get the callback file - it's hidden away in here +//#include "ksslcallback.c" + + +bool KSSL::setVerificationLogic() { +#if 0 +#ifdef KSSL_HAVE_SSL + // SSL_set_verify_result(d->m_ssl, X509_V_OK); + // SSL_CTX_set_verify(d->m_ctx, SSL_VERIFY_PEER, X509Callback); +#endif +#endif +return true; +} + + +int KSSL::accept(int sock) { +#ifdef KSSL_HAVE_SSL +// kdDebug(7029) << "KSSL accept" << endl; +int rc; + if (!m_bInit) + return -1; + d->m_ssl = d->kossl->SSL_new(d->m_ctx); + if (!d->m_ssl) + return -1; + + if (d->session) { + if (static_cast(d->session->_session)->sess_cert == 0) + { + kdDebug(7029) << "Can't reuse session, no certificate." << endl; + delete d->session; + d->session = 0; + } else if (1 == d->kossl->SSL_set_session(d->m_ssl, + static_cast(d->session->_session))) { + kdDebug(7029) << "Session ID is being reused." << endl; + } else { + kdDebug(7029) << "Error attempting to reuse session." << endl; + delete d->session; + d->session = 0; + } + } + +/* + if (!setVerificationLogic()) { + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return -1; + } +*/ + + int off = SSL_OP_ALL; + if (!d->lastInitTLS && !m_cfg->tlsv1()) + off |= SSL_OP_NO_TLSv1; + if (!m_cfg->sslv3()) + off |= SSL_OP_NO_SSLv3; + if (!m_cfg->sslv2()) + off |= SSL_OP_NO_SSLv2; + + d->kossl->SSL_set_options(d->m_ssl, off); + + rc = d->kossl->SSL_set_fd(d->m_ssl, sock); + if (rc == 0) { + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return rc; + } + + rc = d->kossl->SSL_accept(d->m_ssl); + if (rc == 1) { + setConnectionInfo(); + setPeerInfo(); + kdDebug(7029) << "KSSL connected OK" << endl; + } else { + kdDebug(7029) << "KSSL accept failed - rc = " << rc << endl; + kdDebug(7029) << " ERROR = " + << d->kossl->SSL_get_error(d->m_ssl, rc) << endl; + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return -1; + } + + if (!d->kossl->SSL_session_reused(d->m_ssl)) { + if (d->session) { + kdDebug(7029) << "Session reuse failed. New session used instead." << endl; + delete d->session; + d->session = 0L; + } + } + + if (!d->session) { + SSL_SESSION *sess = d->kossl->SSL_get1_session(d->m_ssl); + if (sess) { + d->session = new KSSLSession; + d->session->_session = sess; + } + } + +return rc; +#else +return -1; +#endif +} + + +int KSSL::connect(int sock) { +#ifdef KSSL_HAVE_SSL +// kdDebug(7029) << "KSSL connect" << endl; +int rc; + if (!m_bInit) + return -1; + d->m_ssl = d->kossl->SSL_new(d->m_ctx); + if (!d->m_ssl) + return -1; + + if (d->session) { + if (static_cast(d->session->_session)->sess_cert == 0) + { + kdDebug(7029) << "Can't reuse session, no certificate." << endl; + delete d->session; + d->session = 0; + } else if (1 == d->kossl->SSL_set_session(d->m_ssl, + static_cast(d->session->_session))) { + kdDebug(7029) << "Session ID is being reused." << endl; + } else { + kdDebug(7029) << "Error attempting to reuse session." << endl; + delete d->session; + d->session = 0; + } + } + +/* + if (!setVerificationLogic()) { + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return -1; + } +*/ + + int off = SSL_OP_ALL; + if (!d->lastInitTLS && !m_cfg->tlsv1()) + off |= SSL_OP_NO_TLSv1; + if (!m_cfg->sslv3()) + off |= SSL_OP_NO_SSLv3; + if (!m_cfg->sslv2()) + off |= SSL_OP_NO_SSLv2; + + d->kossl->SSL_set_options(d->m_ssl, off); + + rc = d->kossl->SSL_set_fd(d->m_ssl, sock); + if (rc == 0) { + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return rc; + } + +connect_again: + rc = d->kossl->SSL_connect(d->m_ssl); + if (rc == 1) { + setConnectionInfo(); + setPeerInfo(); + kdDebug(7029) << "KSSL connected OK" << endl; + } else { + int err = d->kossl->SSL_get_error(d->m_ssl, rc); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + // nonblocking - but we block anyways in connect() :) + goto connect_again; + } else { + kdDebug(7029) << "KSSL connect failed - rc = " + << rc << endl; + kdDebug(7029) << " ERROR = " + << err << endl; + d->kossl->ERR_print_errors_fp(stderr); + d->kossl->SSL_shutdown(d->m_ssl); + d->kossl->SSL_free(d->m_ssl); + d->m_ssl = 0; + return -1; + } + } + + if (!d->kossl->SSL_session_reused(d->m_ssl)) { + if (d->session) { + kdDebug(7029) << "Session reuse failed. New session used instead." << endl; + delete d->session; + d->session = 0L; + } + } + + if (!d->session) { + SSL_SESSION *sess = d->kossl->SSL_get1_session(d->m_ssl); + if (sess) { + d->session = new KSSLSession; + d->session->_session = sess; + } + } + +return rc; +#else +return -1; +#endif +} + + +int KSSL::pending() { +#ifdef KSSL_HAVE_SSL + if (!m_bInit) + return -1; +return d->kossl->SSL_pending(d->m_ssl); +#else +return -1; +#endif +} + + +int KSSL::peek(void *buf, int len) { +#ifdef KSSL_HAVE_SSL + if (!m_bInit) + return -1; + // FIXME: enhance to work the way read() does below, handling errors +return d->kossl->SSL_peek(d->m_ssl, buf, len); +#else +return -1; +#endif +} + + +int KSSL::read(void *buf, int len) { +#ifdef KSSL_HAVE_SSL + int rc = 0; + int maxIters = 10; + + if (!m_bInit) + return -1; + +read_again: + rc = d->kossl->SSL_read(d->m_ssl, (char *)buf, len); + if (rc <= 0) { + int err = d->kossl->SSL_get_error(d->m_ssl, rc); + + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + kdDebug(7029) << "SSL read() returning 0: " << err << endl; + if (maxIters-- > 0) { + ::usleep(20000); // 20ms sleep + goto read_again; + } + return 0; + } + + kdDebug(7029) << "SSL READ ERROR: " << err << endl; + if (err != SSL_ERROR_NONE && + err != SSL_ERROR_ZERO_RETURN && err != SSL_ERROR_SYSCALL) { + rc = -1; // OpenSSL returns 0 on error too + d->kossl->ERR_print_errors_fp(stderr); + } + +// else if (err == SSL_ERROR_ZERO_RETURN) +// rc = 0; + } +return rc; +#else +return -1; +#endif +} + + +int KSSL::write(const void *buf, int len) { +#ifdef KSSL_HAVE_SSL + if (!m_bInit) + return -1; + +write_again: + int rc = d->kossl->SSL_write(d->m_ssl, (const char *)buf, len); + if (rc <= 0) { // OpenSSL returns 0 on error too + int err = d->kossl->SSL_get_error(d->m_ssl, rc); + + if (err == SSL_ERROR_WANT_WRITE) { + ::usleep(20000); // 20ms sleep + goto write_again; + } + + kdDebug(7029) << "SSL WRITE ERROR: " << err << endl; + if (err != SSL_ERROR_NONE && + err != SSL_ERROR_ZERO_RETURN && err != SSL_ERROR_SYSCALL) + rc = -1; + } + +return rc; +#else +return -1; +#endif +} + + +bool KSSL::reconfig() { + return reInitialize(); +} + + +void KSSL::setAutoReconfig(bool ar) { + m_bAutoReconfig = ar; +} + + +bool KSSL::setSettings(KSSLSettings *settings) { + delete m_cfg; + m_cfg = settings; + return reconfig(); +} + + +#ifdef KSSL_HAVE_SSL +bool KSSL::m_bSSLWorks = true; +#else +bool KSSL::m_bSSLWorks = false; +#endif + +bool KSSL::doesSSLWork() { + return m_bSSLWorks; +} + + +void KSSL::setConnectionInfo() { +#ifdef KSSL_HAVE_SSL +SSL_CIPHER *sc; +char buf[1024]; + + buf[0] = 0; // for safety. + sc = d->kossl->SSL_get_current_cipher(d->m_ssl); + if (!sc) { + kdDebug(7029) << "KSSL get current cipher failed - we're probably gonna crash!" << endl; + return; + } + + // set the number of bits, bits used + m_ci.m_iCipherUsedBits = d->kossl->SSL_CIPHER_get_bits(sc, &(m_ci.m_iCipherBits)); + // set the cipher version + m_ci.m_cipherVersion = d->kossl->SSL_CIPHER_get_version(sc); + // set the cipher name + m_ci.m_cipherName = d->kossl->SSL_CIPHER_get_name(sc); + // set the cipher description + m_ci.m_cipherDescription = d->kossl->SSL_CIPHER_description(sc, buf, 1023); + +#endif +} + + +void KSSL::setPeerInfo() { +#ifdef KSSL_HAVE_SSL + m_pi.setPeerHost(d->proxyPeer); + m_pi.m_cert.setCert(d->kossl->SSL_get_peer_certificate(d->m_ssl)); + STACK_OF(X509) *xs = d->kossl->SSL_get_peer_cert_chain(d->m_ssl); + if (xs) + xs = sk_X509_dup(xs); // Leak? + m_pi.m_cert.setChain((void *)xs); +#endif +} + + +KSSLConnectionInfo& KSSL::connectionInfo() { + return m_ci; +} + + +// KDE 4: Make it const QString & +void KSSL::setPeerHost(QString realHost) { + d->proxyPeer = realHost; +} + +// deprecated +void KSSL::setProxyUse(bool, QString, int, QString) { +} + + +KSSLPeerInfo& KSSL::peerInfo() { + return m_pi; +} + + +bool KSSL::setClientCertificate(KSSLPKCS12 *pkcs) { +#ifdef KSSL_HAVE_SSL + if (!pkcs || !pkcs->getCertificate()) + return false; + +int rc; +X509 *x = pkcs->getCertificate()->getCert(); +EVP_PKEY *k = pkcs->getPrivateKey(); + + if (!x || !k) return false; + + if (!pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient()) + return false; + + rc = d->kossl->SSL_CTX_use_certificate(d->m_ctx, x); + if (rc <= 0) { + kdDebug(7029) << "KSSL - SSL_CTX_use_certificate failed. rc = " << rc << endl; + return false; + } + + rc = d->kossl->SSL_CTX_use_PrivateKey(d->m_ctx, k); + if (rc <= 0) { + kdDebug(7029) << "KSSL - SSL_CTX_use_PrivateKey failed. rc = " << rc << endl; + return false; + } + + return true; +#else + return false; +#endif +} + +#undef sk_dup + +const KSSLSession* KSSL::session() const { + return d->session; +} + +bool KSSL::reusingSession() const { +#ifdef KSSL_HAVE_SSL + return (d->m_ssl && d->kossl->SSL_session_reused(d->m_ssl)); +#else + return false; +#endif +} + + -- cgit v1.2.1