/* ksslsocket.cpp - KDE SSL Socket Copyright (c) 2004 by Jason Keirstead Kopete (c) 2002-2003 by the Kopete developers stolen from kopete, but this is a modified version. ************************************************************************* * * * 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. * * * ************************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include "ksslsocket.h" #include "stdlib.h" struct KSSLSocketPrivate { mutable KSSL *kssl; KSSLCertificateCache *cc; DCOPClient *dcc; TQMap metaData; TQSocketNotifier *socketNotifier; }; KSSLSocket::KSSLSocket() : KExtendedSocket() { // kdDebug() << "KSSLSocket() " << (void*)this << endl; d = new KSSLSocketPrivate; d->kssl = 0L; d->dcc = 0L; d->cc = new KSSLCertificateCache; d->cc->reload(); //No blocking setBlockingMode(false); //Connect internal slots TQObject::connect( this, TQT_SIGNAL(connectionSuccess()), this, TQT_SLOT(slotConnected()) ); TQObject::connect( this, TQT_SIGNAL(closed(int)), this, TQT_SLOT(slotDisconnected()) ); TQObject::connect( this, TQT_SIGNAL(connectionFailed(int)), this, TQT_SLOT(slotDisconnected())); } KSSLSocket::~KSSLSocket() { // kdDebug() << "KSSLSocket()::~KSSLSocket() " << (void*)this << endl; //Close connection closeNow(); if( d->kssl ) { d->kssl->close(); delete d->kssl; } if ( d->dcc ) d->dcc->detach(); delete d->dcc; delete d->cc; delete d; } TQ_LONG KSSLSocket::readBlock( char* data, TQ_ULONG maxLen ) { return d->kssl->read( data, maxLen ); } TQ_LONG KSSLSocket::writeBlock( const char* data, TQ_ULONG len ) { // kdDebug() << "KSSLSocket::writeBlock() " << (void*)this << endl; // kdDebug() << " d->kssl: " << (void*)d->kssl << endl; return d->kssl->write( data, len ); } void KSSLSocket::slotConnected() { // kdDebug() << "KSSLSocket::slotConnected() " << (void*)this << endl; if( KSSL::doesSSLWork() ) { // kdDebug(0) << k_funcinfo << "Trying SSL connection..." << endl; if( !d->kssl ) { d->kssl = new KSSL(); } else { d->kssl->reInitialize(); } d->kssl->setPeerHost(host()); // kdDebug() << "SOCKET STATUS: " << socketqStatus() << endl; int rc = d->kssl->connect( sockfd ); if ( rc <= 0 ) { kdError() << "Error connecting KSSL: " << rc << endl; kdDebug() << "SYSTEM ERROR: " << systemError() << endl; emit sslFailure(); closeNow(); } else { readNotifier()->setEnabled(true); if( verifyCertificate() != 1 ) { closeNow(); } } } else { kdError(0) << k_funcinfo << "SSL not functional!" << endl; d->kssl = 0L; emit sslFailure(); closeNow(); } } void KSSLSocket::slotDisconnected() { // kdDebug() << "KSSLSocket::slotDisconnected() " << (void*)this << endl; if( readNotifier() ) readNotifier()->setEnabled(false); } void KSSLSocket::setMetaData( const TQString &key, const TQVariant &data ) { TQVariant v = data; d->metaData[key] = v.asString(); } bool KSSLSocket::hasMetaData( const TQString &key ) { return d->metaData.tqcontains(key); } TQString KSSLSocket::metaData( const TQString &key ) { if( d->metaData.tqcontains(key) ) return d->metaData[key]; return TQString(); } /* I basically copied the below from tcpKIO::SlaveBase.hpp, with some modificaions and formatting. * Copyright (C) 2000 Alex Zepeda * Copyright (C) 2001 Dawit Alemayehu */ int KSSLSocket::messageBox( KIO::SlaveBase::MessageBoxType type, const TQString &text, const TQString &caption, const TQString &buttonYes, const TQString &buttonNo ) { kdDebug(0) << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo << endl; TQByteArray data, result; TQCString returnType; TQDataStream arg(data, IO_WriteOnly); arg << (int)1 << (int)type << text << caption << buttonYes << buttonNo; if ( ! d->dcc ){ d->dcc = new DCOPClient(); d->dcc->attach(); } if (!d->dcc->isApplicationRegistered("kio_uiserver")) { KApplication::startServiceByDesktopPath("kio_uiserver.desktop",TQStringList()); } d->dcc->call("kio_uiserver", "UIServer", "messageBox(int,int,TQString,TQString,TQString,TQString)", data, returnType, result); if( returnType == "int" ) { int res; TQDataStream r(result, IO_ReadOnly); r >> res; return res; } else return 0; // communication failure } // Returns 0 for failed verification, -1 for rejected cert and 1 for ok int KSSLSocket::verifyCertificate() { int rc = 0; bool permacache = false; bool _IPmatchesCN = false; int result; bool doAddHost = false; TQString ourHost = host(); TQString ourIp = peerAddress()->pretty(); TQString theurl = "https://" + ourHost + ":" + port(); if (!d->cc) d->cc = new KSSLCertificateCache; KSSLCertificate& pc = d->kssl->peerInfo().getPeerCertificate(); KSSLCertificate::KSSLValidationList ksvl = pc.validateVerbose(KSSLCertificate::SSLServer); if ( ksvl.count() == 1 && ksvl.first() == KSSLCertificate::Unknown ) { kdDebug() << "Unknown validation error" << endl; return 0; } _IPmatchesCN = d->kssl->peerInfo().certMatchesAddress(); if (!_IPmatchesCN && (metaData("ssl_militant") == "TRUE") ) { ksvl << KSSLCertificate::InvalidHost; } KSSLCertificate::KSSLValidation ksv = KSSLCertificate::Ok; if (!ksvl.isEmpty()) ksv = ksvl.first(); /* Setting the various bits of meta-info that will be needed. */ setMetaData("ssl_cipher", d->kssl->connectionInfo().getCipher()); setMetaData("ssl_cipher_desc", d->kssl->connectionInfo().getCipherDescription()); setMetaData("ssl_cipher_version", d->kssl->connectionInfo().getCipherVersion()); setMetaData("ssl_cipher_used_bits", TQString::number(d->kssl->connectionInfo().getCipherUsedBits())); setMetaData("ssl_cipher_bits", TQString::number(d->kssl->connectionInfo().getCipherBits())); setMetaData("ssl_peer_ip", ourIp ); TQString errorStr; for(KSSLCertificate::KSSLValidationList::ConstIterator it = ksvl.begin(); it != ksvl.end(); ++it) { errorStr += TQString::number(*it)+":"; } setMetaData("ssl_cert_errors", errorStr); setMetaData("ssl_peer_certificate", pc.toString()); if (pc.chain().isValid() && pc.chain().depth() > 1) { TQString theChain; TQPtrList chain = pc.chain().getChain(); for (KSSLCertificate *c = chain.first(); c; c = chain.next()) { theChain += c->toString(); theChain += "\n"; } setMetaData("ssl_peer_chain", theChain); } else { setMetaData("ssl_peer_chain", ""); } setMetaData("ssl_cert_state", TQString::number(ksv)); if (ksv == KSSLCertificate::Ok) { rc = 1; setMetaData("ssl_action", "accept"); } // Since we're the tqparent, we need to teach the child. setMetaData("ssl_parent_ip", ourIp ); setMetaData("ssl_parent_cert", pc.toString()); // - Read from cache and see if there is a policy for this KSSLCertificateCache::KSSLCertificatePolicy cp = d->cc->getPolicyByCertificate(pc); // - validation code if (ksv != KSSLCertificate::Ok) { if( cp == KSSLCertificateCache::Unknown || cp == KSSLCertificateCache::Ambiguous) { cp = KSSLCertificateCache::Prompt; } else { // A policy was already set so let's honor that. permacache = d->cc->isPermanent(pc); } if (!_IPmatchesCN && (metaData("ssl_militant") == "TRUE") && cp == KSSLCertificateCache::Accept) { cp = KSSLCertificateCache::Prompt; } // Precondition: cp is one of Reject, Accept or Prompt switch (cp) { case KSSLCertificateCache::Accept: rc = 1; break; case KSSLCertificateCache::Reject: rc = -1; break; case KSSLCertificateCache::Prompt: { do { if (ksv == KSSLCertificate::InvalidHost) { TQString msg = i18n("The IP address of the host %1 " "does not match the one the " "certificate was issued to."); result = messageBox( KIO::SlaveBase::WarningYesNoCancel, msg.tqarg(ourHost), i18n("Server Authentication"), i18n("&Details"), KStdGuiItem::cont().text() ); } else { TQString msg = i18n("The server certificate failed the " "authenticity test (%1)."); result = messageBox( KIO::SlaveBase::WarningYesNoCancel, msg.tqarg(ourHost), i18n("Server Authentication"), i18n("&Details"), KStdGuiItem::cont().text() ); } } while (result == KMessageBox::Yes); if (result == KMessageBox::No) { rc = 1; cp = KSSLCertificateCache::Accept; doAddHost = true; result = messageBox( KIO::SlaveBase::WarningYesNo, i18n("Would you like to accept this " "certificate forever without " "being prompted?"), i18n("Server Authentication"), i18n("&Forever"), i18n("&Current Sessions Only")); if (result == KMessageBox::Yes) permacache = true; else permacache = false; } else { rc = -1; cp = KSSLCertificateCache::Prompt; } break; } default: kdDebug(0) << "SSL error in cert code." << endl; break; } } // - cache the results d->cc->addCertificate(pc, cp, permacache); if (doAddHost) d->cc->addHost(pc, ourHost); if (rc == -1) return rc; if ( getenv("DEBUG_GW_RESOURCE") ) { kdDebug(0) << "SSL connection information follows:" << endl << "+-----------------------------------------------" << endl << "| Cipher: " << d->kssl->connectionInfo().getCipher() << endl << "| Description: " << d->kssl->connectionInfo().getCipherDescription() << endl << "| Version: " << d->kssl->connectionInfo().getCipherVersion() << endl << "| Strength: " << d->kssl->connectionInfo().getCipherUsedBits() << " of " << d->kssl->connectionInfo().getCipherBits() << " bits used." << endl << "| PEER:" << endl << "| Subject: " << d->kssl->peerInfo().getPeerCertificate().getSubject() << endl << "| Issuer: " << d->kssl->peerInfo().getPeerCertificate().getIssuer() << endl << "| Validation: " << (int)ksv << endl << "| Certificate matches IP: " << _IPmatchesCN << endl << "+-----------------------------------------------" << endl; } return rc; } #include "ksslsocket.moc"