You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
409 lines
11 KiB
409 lines
11 KiB
/*
|
|
ksslsocket.cpp - KDE SSL Socket
|
|
|
|
Copyright (c) 2004 by Jason Keirstead <jason@keirstead.org>
|
|
|
|
Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org>
|
|
|
|
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 <qsocketnotifier.h>
|
|
|
|
#include <dcopclient.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kssl.h>
|
|
#include <ksslinfodlg.h>
|
|
#include <ksslpeerinfo.h>
|
|
#include <ksslcertchain.h>
|
|
#include <ksslcertificatecache.h>
|
|
#include <kapplication.h>
|
|
#include <kmessagebox.h>
|
|
|
|
#include "ksslsocket.h"
|
|
|
|
#include "stdlib.h"
|
|
|
|
struct KSSLSocketPrivate
|
|
{
|
|
mutable KSSL *kssl;
|
|
KSSLCertificateCache *cc;
|
|
DCOPClient *dcc;
|
|
QMap<QString,QString> metaData;
|
|
QSocketNotifier *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
|
|
QObject::connect( this, SIGNAL(connectionSuccess()), this, SLOT(slotConnected()) );
|
|
QObject::connect( this, SIGNAL(closed(int)), this, SLOT(slotDisconnected()) );
|
|
QObject::connect( this, SIGNAL(connectionFailed(int)), this, 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;
|
|
}
|
|
|
|
Q_LONG KSSLSocket::readBlock( char* data, Q_ULONG maxLen )
|
|
{
|
|
return d->kssl->read( data, maxLen );
|
|
}
|
|
|
|
Q_LONG KSSLSocket::writeBlock( const char* data, Q_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: " << socketStatus() << 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 QString &key, const QVariant &data )
|
|
{
|
|
QVariant v = data;
|
|
d->metaData[key] = v.asString();
|
|
}
|
|
|
|
bool KSSLSocket::hasMetaData( const QString &key )
|
|
{
|
|
return d->metaData.contains(key);
|
|
}
|
|
|
|
QString KSSLSocket::metaData( const QString &key )
|
|
{
|
|
if( d->metaData.contains(key) )
|
|
return d->metaData[key];
|
|
return QString::null;
|
|
}
|
|
|
|
/*
|
|
I basically copied the below from tcpKIO::SlaveBase.hpp, with some modificaions and formatting.
|
|
|
|
* Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net
|
|
* Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
|
|
* Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
|
|
*/
|
|
|
|
int KSSLSocket::messageBox( KIO::SlaveBase::MessageBoxType type, const QString &text, const QString &caption,
|
|
const QString &buttonYes, const QString &buttonNo )
|
|
{
|
|
kdDebug(0) << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo << endl;
|
|
QByteArray data, result;
|
|
QCString returnType;
|
|
QDataStream 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",QStringList());
|
|
}
|
|
|
|
d->dcc->call("kio_uiserver", "UIServer",
|
|
"messageBox(int,int,QString,QString,QString,QString)", data, returnType, result);
|
|
|
|
if( returnType == "int" )
|
|
{
|
|
int res;
|
|
QDataStream 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;
|
|
QString ourHost = host();
|
|
QString ourIp = peerAddress()->pretty();
|
|
|
|
QString 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", QString::number(d->kssl->connectionInfo().getCipherUsedBits()));
|
|
setMetaData("ssl_cipher_bits", QString::number(d->kssl->connectionInfo().getCipherBits()));
|
|
setMetaData("ssl_peer_ip", ourIp );
|
|
|
|
QString errorStr;
|
|
for(KSSLCertificate::KSSLValidationList::ConstIterator it = ksvl.begin();
|
|
it != ksvl.end(); ++it)
|
|
{
|
|
errorStr += QString::number(*it)+":";
|
|
}
|
|
|
|
setMetaData("ssl_cert_errors", errorStr);
|
|
setMetaData("ssl_peer_certificate", pc.toString());
|
|
|
|
if (pc.chain().isValid() && pc.chain().depth() > 1)
|
|
{
|
|
QString theChain;
|
|
QPtrList<KSSLCertificate> 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", QString::number(ksv));
|
|
|
|
if (ksv == KSSLCertificate::Ok)
|
|
{
|
|
rc = 1;
|
|
setMetaData("ssl_action", "accept");
|
|
}
|
|
|
|
// Since we're the parent, 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)
|
|
{
|
|
QString 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.arg(ourHost),
|
|
i18n("Server Authentication"),
|
|
i18n("&Details"),
|
|
KStdGuiItem::cont().text() );
|
|
}
|
|
else
|
|
{
|
|
QString msg = i18n("The server certificate failed the "
|
|
"authenticity test (%1).");
|
|
result = messageBox( KIO::SlaveBase::WarningYesNoCancel,
|
|
msg.arg(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"
|