From 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 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/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- certmanager/certificateinfowidgetimpl.cpp | 440 ++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 certmanager/certificateinfowidgetimpl.cpp (limited to 'certmanager/certificateinfowidgetimpl.cpp') diff --git a/certmanager/certificateinfowidgetimpl.cpp b/certmanager/certificateinfowidgetimpl.cpp new file mode 100644 index 000000000..c6e810109 --- /dev/null +++ b/certmanager/certificateinfowidgetimpl.cpp @@ -0,0 +1,440 @@ +/* + certificateinfowidgetimpl.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2001,2002,2004 Klarälvdalens Datakonsult AB + + Kleopatra 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. + + Kleopatra 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 + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "certificateinfowidgetimpl.h" + +// libkleopatra +#include +#include +#include + +#include + +// gpgme++ +#include + +// KDE +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// other +#include + +CertificateInfoWidgetImpl::CertificateInfoWidgetImpl( const GpgME::Key & key, bool external, + QWidget * parent, const char * name ) + : CertificateInfoWidget( parent, name ), + mExternal( external ), + mFoundIssuer( true ), + mHaveKeyLocally( false ) +{ + importButton->setEnabled( false ); + + listView->setColumnWidthMode( 1, QListView::Maximum ); + QFontMetrics fm = fontMetrics(); + listView->setColumnWidth( 1, fm.width( i18n("Information") ) * 5 ); + + listView->header()->setClickEnabled( false ); + listView->setSorting( -1 ); + + connect( listView, SIGNAL( selectionChanged( QListViewItem* ) ), + this, SLOT( slotShowInfo( QListViewItem* ) ) ); + pathView->setColumnWidthMode( 0, QListView::Maximum ); + pathView->header()->hide(); + + connect( pathView, SIGNAL( doubleClicked( QListViewItem* ) ), + this, SLOT( slotShowCertPathDetails( QListViewItem* ) ) ); + connect( pathView, SIGNAL( returnPressed( QListViewItem* ) ), + this, SLOT( slotShowCertPathDetails( QListViewItem* ) ) ); + connect( importButton, SIGNAL( clicked() ), + this, SLOT( slotImportCertificate() ) ); + + dumpView->setFont( KGlobalSettings::fixedFont() ); + + if ( !key.isNull() ) + setKey( key ); +} + +static QString time_t2string( time_t t ) { + QDateTime dt; + dt.setTime_t( t ); + return dt.toString(); +} + +void CertificateInfoWidgetImpl::setKey( const GpgME::Key & key ) { + mChain.clear(); + mFoundIssuer = true; + mHaveKeyLocally = false; + + listView->clear(); + pathView->clear(); + importButton->setEnabled( false ); + + if ( key.isNull() ) + return; + + mChain.push_front( key ); + startKeyExistanceCheck(); // starts a local keylisting to enable the + // importButton if needed + + QListViewItem * item = 0; + item = new QListViewItem( listView, item, i18n("Valid"), QString("From %1 to %2") + .arg( time_t2string( key.subkey(0).creationTime() ), + time_t2string( key.subkey(0).expirationTime() ) ) ); + item = new QListViewItem( listView, item, i18n("Can be used for signing"), + key.canSign() ? i18n("Yes") : i18n("No") ); + item = new QListViewItem( listView, item, i18n("Can be used for encryption"), + key.canEncrypt() ? i18n("Yes") : i18n("No") ); + item = new QListViewItem( listView, item, i18n("Can be used for certification"), + key.canCertify() ? i18n("Yes") : i18n("No") ); + item = new QListViewItem( listView, item, i18n("Can be used for authentication"), + key.canAuthenticate() ? i18n("Yes") : i18n("No" ) ); + item = new QListViewItem( listView, item, i18n("Fingerprint"), key.primaryFingerprint() ); + item = new QListViewItem( listView, item, i18n("Issuer"), Kleo::DN( key.issuerName() ).prettyDN() ); + item = new QListViewItem( listView, item, i18n("Serial Number"), key.issuerSerial() ); + + const Kleo::DN dn = key.userID(0).id(); + + // FIXME: use the attributeLabelMap from certificatewizardimpl.cpp: + static QMap dnComponentNames; + if ( dnComponentNames.isEmpty() ) { + dnComponentNames["C"] = i18n("Country"); + dnComponentNames["OU"] = i18n("Organizational Unit"); + dnComponentNames["O"] = i18n("Organization"); + dnComponentNames["L"] = i18n("Location"); + dnComponentNames["CN"] = i18n("Common Name"); + dnComponentNames["EMAIL"] = i18n("Email"); + } + + for ( Kleo::DN::const_iterator dnit = dn.begin() ; dnit != dn.end() ; ++dnit ) { + QString displayName = (*dnit).name(); + if( dnComponentNames.contains(displayName) ) displayName = dnComponentNames[displayName]; + item = new QListViewItem( listView, item, displayName, (*dnit).value() ); + } + + const std::vector uids = key.userIDs(); + if ( !uids.empty() ) { + item = new QListViewItem( listView, item, i18n("Subject"), + Kleo::DN( uids.front().id() ).prettyDN() ); + for ( std::vector::const_iterator it = uids.begin() + 1 ; it != uids.end() ; ++it ) { + if ( !(*it).id() ) + continue; + const QString email = QString::fromUtf8( (*it).id() ).stripWhiteSpace(); + if ( email.isEmpty() ) + continue; + if ( email.startsWith( "<" ) ) + item = new QListViewItem( listView, item, i18n("Email"), + email.mid( 1, email.length()-2 ) ); + else + item = new QListViewItem( listView, item, i18n("A.k.a."), email ); + } + } + + updateChainView(); + startCertificateChainListing(); + startCertificateDump(); +} + +static void showChainListError( QWidget * parent, const GpgME::Error & err, const char * subject ) { + assert( err ); + const QString msg = i18n("

An error occurred while fetching " + "the certificate %1 from the backend:

" + "

%2

") + .arg( subject ? QString::fromUtf8( subject ) : QString::null, + QString::fromLocal8Bit( err.asString() ) ); + KMessageBox::error( parent, msg, i18n("Certificate Listing Failed" ) ); +} + +void CertificateInfoWidgetImpl::startCertificateChainListing() { + kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing()" << endl; + + if ( mChain.empty() ) { + // we need a seed... + kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): mChain is empty!" << endl; + return; + } + const char * chainID = mChain.front().chainID(); + if ( !chainID || !*chainID ) { + // cert not found: + kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): empty chain ID - root not found" << endl; + return; + } + const char * fpr = mChain.front().primaryFingerprint(); + if ( qstricmp( fpr, chainID ) == 0 ) { + kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): chain_id equals fingerprint -> found root" << endl; + return; + } + if ( mChain.size() > 100 ) { + // safe guard against certificate loops (paranoia factor 8 out of 10)... + kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): maximum chain length of 100 exceeded!" << endl; + return; + } + if ( !mFoundIssuer ) { + // key listing failed. Don't end up in endless loop + kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): issuer not found - giving up" << endl; + return; + } + + mFoundIssuer = false; + + // gpgsm / dirmngr / LDAP / whoever doesn't support looking up + // external keys by fingerprint. Furthermore, since we actually got + // a chain-id set on the key, we know that we have the issuer's cert + // in the local keyring, so just use local keylisting. + Kleo::KeyListJob * job = + Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false ); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::KeyListResult&)), + SLOT(slotCertificateChainListingResult(const GpgME::KeyListResult&)) ); + connect( job, SIGNAL(nextKey(const GpgME::Key&)), + SLOT(slotNextKey(const GpgME::Key&)) ); + + kdDebug() << "Going to fetch" << endl + << " issuer : \"" << mChain.front().issuerName() << "\"" << endl + << " chain id: " << mChain.front().chainID() << endl + << "for" << endl + << " subject : \"" << mChain.front().userID(0).id() << "\"" << endl + << " subj.fpr: " << mChain.front().primaryFingerprint() << endl; + + const GpgME::Error err = job->start( mChain.front().chainID() ); + + if ( err ) + showChainListError( this, err, mChain.front().issuerName() ); + else + (void)new Kleo::ProgressDialog( job, i18n("Fetching Certificate Chain"), this ); +} + +void CertificateInfoWidgetImpl::startCertificateDump() { + KProcess* proc = new KProcess( this ); + (*proc) << "gpgsm"; // must be in the PATH + (*proc) << "--dump-keys"; + (*proc) << mChain.front().primaryFingerprint(); + + QObject::connect( proc, SIGNAL( receivedStdout(KProcess *, char *, int) ), + this, SLOT( slotCollectStdout(KProcess *, char *, int) ) ); + QObject::connect( proc, SIGNAL( receivedStderr(KProcess *, char *, int) ), + this, SLOT( slotCollectStderr(KProcess *, char *, int) ) ); + QObject::connect( proc, SIGNAL( processExited(KProcess*) ), + this, SLOT( slotDumpProcessExited(KProcess*) ) ); + + if ( !proc->start( KProcess::NotifyOnExit, (KProcess::Communication)(KProcess::Stdout | KProcess::Stderr) ) ) { + QString wmsg = i18n("Failed to execute gpgsm:\n%1").arg( i18n( "program not found" ) ); + dumpView->setText( QStyleSheet::escape( wmsg ) ); + delete proc; + } +} + +void CertificateInfoWidgetImpl::slotCollectStdout(KProcess *, char *buffer, int buflen) +{ + mDumpOutput += QCString(buffer, buflen+1); // like KProcIO does +} + +void CertificateInfoWidgetImpl::slotCollectStderr(KProcess *, char *buffer, int buflen) +{ + mDumpError += QCString(buffer, buflen+1); // like KProcIO does +} + +void CertificateInfoWidgetImpl::slotDumpProcessExited(KProcess* proc) { + int rc = ( proc->normalExit() ) ? proc->exitStatus() : -1 ; + + if ( rc == 0 ) { + dumpView->setText( QStyleSheet::escape( QString::fromUtf8( mDumpOutput ) ) ); + } else { + if ( !mDumpError.isEmpty() ) { + dumpView->setText( QStyleSheet::escape( QString::fromUtf8( mDumpError ) ) ); + } else + { + QString wmsg = i18n("Failed to execute gpgsm:\n%1"); + if ( rc == -1 ) + wmsg = wmsg.arg( i18n( "program cannot be executed" ) ); + else + wmsg = wmsg.arg( strerror(rc) ); + dumpView->setText( QStyleSheet::escape( wmsg ) ); + } + } + + proc->deleteLater(); +} + +void CertificateInfoWidgetImpl::slotNextKey( const GpgME::Key & key ) { + kdDebug() << "CertificateInfoWidgetImpl::slotNextKey( \"" + << key.userID(0).id() << "\" )" << endl; + if ( key.isNull() ) + return; + + mFoundIssuer = true; + mChain.push_front( key ); + updateChainView(); + // FIXME: cancel the keylisting. We're only interested in _one_ key. +} + +void CertificateInfoWidgetImpl::updateChainView() { + pathView->clear(); + if ( mChain.empty() ) + return; + QListViewItem * item = 0; + + QValueList::const_iterator it = mChain.begin(); + // root item: + if ( (*it).chainID() && qstrcmp( (*it).chainID(), (*it).primaryFingerprint() ) == 0 ) + item = new QListViewItem( pathView, Kleo::DN( (*it++).userID(0).id() ).prettyDN() ); + else { + item = new QListViewItem( pathView, i18n("Issuer certificate not found ( %1)") + .arg( Kleo::DN( (*it).issuerName() ).prettyDN() ) ); + item->setOpen( true ); // Qt bug: doesn't open after setEnabled( false ) :/ + item->setEnabled( false ); + } + item->setOpen( true ); + + // subsequent items: + while ( it != mChain.end() ) { + item = new QListViewItem( item, Kleo::DN( (*it++).userID(0).id() ).prettyDN() ); + item->setOpen( true ); + } +} + +void CertificateInfoWidgetImpl::slotCertificateChainListingResult( const GpgME::KeyListResult & res ) { + if ( res.error() ) + return showChainListError( this, res.error(), mChain.front().issuerName() ); + else + startCertificateChainListing(); +} + +void CertificateInfoWidgetImpl::slotShowInfo( QListViewItem * item ) { + textView->setText( item->text(1) ); +} + +void CertificateInfoWidgetImpl::slotShowCertPathDetails( QListViewItem * item ) { + if ( !item ) + return; + + // find the key corresponding to "item". This hack would not be + // necessary if pathView was a Kleo::KeyListView, but it's + // Qt-Designer-generated and I don't feel like creating a custom + // widget spec for Kleo::KeyListView. + unsigned int totalCount = 0; + int itemIndex = -1; + for ( const QListViewItem * i = pathView->firstChild() ; i ; i = i->firstChild() ) { + if ( i == item ) + itemIndex = totalCount; + ++totalCount; + } + + assert( totalCount == mChain.size() || totalCount == mChain.size() + 1 ); + + // skip pseudo root item with "not found message": + if ( totalCount == mChain.size() + 1 ) + --itemIndex; + + assert( itemIndex >= 0 ); + + KDialogBase * dialog = + new KDialogBase( this, "dialog", false, i18n("Additional Information for Key"), + KDialogBase::Close, KDialogBase::Close ); + CertificateInfoWidgetImpl * top = + new CertificateInfoWidgetImpl( mChain[itemIndex], mExternal, dialog ); + dialog->setMainWidget( top ); + // proxy the signal to our receiver: + connect( top, SIGNAL(requestCertificateDownload(const QString&, const QString&)), + SIGNAL(requestCertificateDownload(const QString&, const QString&)) ); + dialog->show(); +} + + +void CertificateInfoWidgetImpl::slotImportCertificate() +{ + if ( mChain.empty() || mChain.back().isNull() ) + return; + const Kleo::DN dn = mChain.back().userID( 0 ).id(); + emit requestCertificateDownload( mChain.back().primaryFingerprint(), dn.prettyDN() ); + importButton->setEnabled( false ); +} + +void CertificateInfoWidgetImpl::startKeyExistanceCheck() { + if ( !mExternal ) + // we already have it if it's from a local keylisting :) + return; + if ( mChain.empty() || mChain.back().isNull() ) + // need a key to look for + return; + const QString fingerprint = mChain.back().primaryFingerprint(); + if ( fingerprint.isEmpty() ) + // empty pattern means list all keys. We don't want that + return; + + // start _local_ keylistjob (no progressdialog needed here): + Kleo::KeyListJob * job = + Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false ); + assert( job ); + + connect( job, SIGNAL(nextKey(const GpgME::Key&)), + SLOT(slotKeyExistanceCheckNextCandidate(const GpgME::Key&)) ); + connect( job, SIGNAL(result(const GpgME::KeyListResult&)), + SLOT(slotKeyExistanceCheckFinished()) ); + // nor to check for errors: + job->start( fingerprint ); +} + +void CertificateInfoWidgetImpl::slotKeyExistanceCheckNextCandidate( const GpgME::Key & key ) { + if ( key.isNull() || mChain.empty() || !key.primaryFingerprint() ) + return; + + if ( qstrcmp( key.primaryFingerprint(), + mChain.back().primaryFingerprint() ) == 0 ) + mHaveKeyLocally = true; +} + +void CertificateInfoWidgetImpl::slotKeyExistanceCheckFinished() { + importButton->setEnabled( !mHaveKeyLocally ); +} + + +#include "certificateinfowidgetimpl.moc" -- cgit v1.2.1