/* 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 class KSSLPrivate { public: KSSLPrivate() { lastInitTLS = false; kossl = KOpenSSLProxy::self(); session = 0L; } ~KSSLPrivate() { delete session; session = 0L; } bool lastInitTLS; KSSLCertificate::KSSLValidation m_cert_vfy_res; TQString 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->TLS_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 TQString 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->TLS_client_method(); } else if (m_cfg->sslv3()) { d->m_meth = d->kossl->SSLv3_client_method(); } else if (m_cfg->sslv2()) { d->m_meth = d->kossl->SSLv2_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 TQString 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::takeSession(KSSLSession *session) { #ifdef KSSL_HAVE_SSL if (!session) { delete d->session; d->session = 0L; return true; } // Take session reference d->session = new KSSLSession; d->session->_session = session->_session; session->_session = 0L; 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 OPENSSL_VERSION_NUMBER < 0x10100000L 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 #endif 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; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME d->kossl->SSL_set_tlsext_host_name(d->m_ssl, d->proxyPeer.ascii()); #endif 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 OPENSSL_VERSION_NUMBER < 0x10100000L 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 #endif 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; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME d->kossl->SSL_set_tlsext_host_name(d->m_ssl, d->proxyPeer.ascii()); #endif 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 = reinterpret_cast(d->kossl->OPENSSL_sk_dup(xs)); // Leak? m_pi.m_cert.setChain((void *)xs); #endif } KSSLConnectionInfo& KSSL::connectionInfo() { return m_ci; } // KDE 4: Make it const TQString & void KSSL::setPeerHost(TQString realHost) { d->proxyPeer = realHost; } // deprecated void KSSL::setProxyUse(bool, TQString, int, TQString) { } 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 } 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 }