certificateinfowidgetimpl.cpp
00001 /* 00002 certificateinfowidgetimpl.cpp 00003 00004 This file is part of Kleopatra, the KDE keymanager 00005 Copyright (c) 2001,2002,2004 Klarälvdalens Datakonsult AB 00006 00007 Kleopatra is free software; you can redistribute it and/or modify 00008 it under the terms of the GNU General Public License as published by 00009 the Free Software Foundation; either version 2 of the License, or 00010 (at your option) any later version. 00011 00012 Kleopatra is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 General Public License for more details. 00016 00017 You should have received a copy of the GNU General Public License 00018 along with this program; if not, write to the Free Software 00019 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 00021 In addition, as a special exception, the copyright holders give 00022 permission to link the code of this program with any edition of 00023 the TQt library by Trolltech AS, Norway (or with modified versions 00024 of TQt that use the same license as TQt), and distribute linked 00025 combinations including the two. You must obey the GNU General 00026 Public License in all respects for all of the code used other than 00027 TQt. If you modify this file, you may extend this exception to 00028 your version of the file, but you are not obligated to do so. If 00029 you do not wish to do so, delete this exception statement from 00030 your version. 00031 */ 00032 00033 #ifdef HAVE_CONFIG_H 00034 #include <config.h> 00035 #endif 00036 00037 #include "certificateinfowidgetimpl.h" 00038 00039 // libkleopatra 00040 #include <kleo/keylistjob.h> 00041 #include <kleo/dn.h> 00042 #include <kleo/cryptobackendfactory.h> 00043 00044 #include <ui/progressdialog.h> 00045 00046 // gpgme++ 00047 #include <gpgmepp/keylistresult.h> 00048 00049 // KDE 00050 #include <tdelocale.h> 00051 #include <kdialogbase.h> 00052 #include <tdemessagebox.h> 00053 #include <kdebug.h> 00054 #include <kprocio.h> 00055 #include <tdeglobalsettings.h> 00056 00057 // TQt 00058 #include <tqlistview.h> 00059 #include <tqtextedit.h> 00060 #include <tqheader.h> 00061 #include <tqpushbutton.h> 00062 #include <tqcursor.h> 00063 #include <tqapplication.h> 00064 #include <tqdatetime.h> 00065 #include <tqstylesheet.h> 00066 #include <tqtextcodec.h> 00067 00068 // other 00069 #include <assert.h> 00070 00071 CertificateInfoWidgetImpl::CertificateInfoWidgetImpl( const GpgME::Key & key, bool external, 00072 TQWidget * parent, const char * name ) 00073 : CertificateInfoWidget( parent, name ), 00074 mExternal( external ), 00075 mFoundIssuer( true ), 00076 mHaveKeyLocally( false ) 00077 { 00078 importButton->setEnabled( false ); 00079 00080 listView->setColumnWidthMode( 1, TQListView::Maximum ); 00081 TQFontMetrics fm = fontMetrics(); 00082 listView->setColumnWidth( 1, fm.width( i18n("Information") ) * 5 ); 00083 00084 listView->header()->setClickEnabled( false ); 00085 listView->setSorting( -1 ); 00086 00087 connect( listView, TQT_SIGNAL( selectionChanged( TQListViewItem* ) ), 00088 this, TQT_SLOT( slotShowInfo( TQListViewItem* ) ) ); 00089 pathView->setColumnWidthMode( 0, TQListView::Maximum ); 00090 pathView->header()->hide(); 00091 00092 connect( pathView, TQT_SIGNAL( doubleClicked( TQListViewItem* ) ), 00093 this, TQT_SLOT( slotShowCertPathDetails( TQListViewItem* ) ) ); 00094 connect( pathView, TQT_SIGNAL( returnPressed( TQListViewItem* ) ), 00095 this, TQT_SLOT( slotShowCertPathDetails( TQListViewItem* ) ) ); 00096 connect( importButton, TQT_SIGNAL( clicked() ), 00097 this, TQT_SLOT( slotImportCertificate() ) ); 00098 00099 dumpView->setFont( TDEGlobalSettings::fixedFont() ); 00100 00101 if ( !key.isNull() ) 00102 setKey( key ); 00103 } 00104 00105 static TQString time_t2string( time_t t ) { 00106 TQDateTime dt; 00107 dt.setTime_t( t ); 00108 return dt.toString(); 00109 } 00110 00111 void CertificateInfoWidgetImpl::setKey( const GpgME::Key & key ) { 00112 mChain.clear(); 00113 mFoundIssuer = true; 00114 mHaveKeyLocally = false; 00115 00116 listView->clear(); 00117 pathView->clear(); 00118 importButton->setEnabled( false ); 00119 00120 if ( key.isNull() ) 00121 return; 00122 00123 mChain.push_front( key ); 00124 startKeyExistanceCheck(); // starts a local keylisting to enable the 00125 // importButton if needed 00126 00127 TQListViewItem * item = 0; 00128 item = new TQListViewItem( listView, item, i18n("Valid"), TQString("From %1 to %2") 00129 .arg( time_t2string( key.subkey(0).creationTime() ), 00130 time_t2string( key.subkey(0).expirationTime() ) ) ); 00131 item = new TQListViewItem( listView, item, i18n("Can be used for signing"), 00132 key.canSign() ? i18n("Yes") : i18n("No") ); 00133 item = new TQListViewItem( listView, item, i18n("Can be used for encryption"), 00134 key.canEncrypt() ? i18n("Yes") : i18n("No") ); 00135 item = new TQListViewItem( listView, item, i18n("Can be used for certification"), 00136 key.canCertify() ? i18n("Yes") : i18n("No") ); 00137 item = new TQListViewItem( listView, item, i18n("Can be used for authentication"), 00138 key.canAuthenticate() ? i18n("Yes") : i18n("No" ) ); 00139 item = new TQListViewItem( listView, item, i18n("Fingerprint"), key.primaryFingerprint() ); 00140 item = new TQListViewItem( listView, item, i18n("Issuer"), Kleo::DN( key.issuerName() ).prettyDN() ); 00141 item = new TQListViewItem( listView, item, i18n("Serial Number"), key.issuerSerial() ); 00142 00143 const Kleo::DN dn = key.userID(0).id(); 00144 00145 // FIXME: use the attributeLabelMap from certificatewizardimpl.cpp: 00146 static TQMap<TQString,TQString> dnComponentNames; 00147 if ( dnComponentNames.isEmpty() ) { 00148 dnComponentNames["C"] = i18n("Country"); 00149 dnComponentNames["OU"] = i18n("Organizational Unit"); 00150 dnComponentNames["O"] = i18n("Organization"); 00151 dnComponentNames["L"] = i18n("Location"); 00152 dnComponentNames["CN"] = i18n("Common Name"); 00153 dnComponentNames["EMAIL"] = i18n("Email"); 00154 } 00155 00156 for ( Kleo::DN::const_iterator dnit = dn.begin() ; dnit != dn.end() ; ++dnit ) { 00157 TQString displayName = (*dnit).name(); 00158 if( dnComponentNames.contains(displayName) ) displayName = dnComponentNames[displayName]; 00159 item = new TQListViewItem( listView, item, displayName, (*dnit).value() ); 00160 } 00161 00162 const std::vector<GpgME::UserID> uids = key.userIDs(); 00163 if ( !uids.empty() ) { 00164 item = new TQListViewItem( listView, item, i18n("Subject"), 00165 Kleo::DN( uids.front().id() ).prettyDN() ); 00166 for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() + 1 ; it != uids.end() ; ++it ) { 00167 if ( !(*it).id() ) 00168 continue; 00169 const TQString email = TQString::fromUtf8( (*it).id() ).stripWhiteSpace(); 00170 if ( email.isEmpty() ) 00171 continue; 00172 if ( email.startsWith( "<" ) ) 00173 item = new TQListViewItem( listView, item, i18n("Email"), 00174 email.mid( 1, email.length()-2 ) ); 00175 else 00176 item = new TQListViewItem( listView, item, i18n("A.k.a."), email ); 00177 } 00178 } 00179 00180 updateChainView(); 00181 startCertificateChainListing(); 00182 startCertificateDump(); 00183 } 00184 00185 static void showChainListError( TQWidget * parent, const GpgME::Error & err, const char * subject ) { 00186 assert( err ); 00187 const TQString msg = i18n("<qt><p>An error occurred while fetching " 00188 "the certificate <b>%1</b> from the backend:</p>" 00189 "<p><b>%2</b></p></qt>") 00190 .arg( subject ? TQString::fromUtf8( subject ) : TQString(), 00191 TQString::fromLocal8Bit( err.asString() ) ); 00192 KMessageBox::error( parent, msg, i18n("Certificate Listing Failed" ) ); 00193 } 00194 00195 void CertificateInfoWidgetImpl::startCertificateChainListing() { 00196 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing()" << endl; 00197 00198 if ( mChain.empty() ) { 00199 // we need a seed... 00200 kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): mChain is empty!" << endl; 00201 return; 00202 } 00203 const char * chainID = mChain.front().chainID(); 00204 if ( !chainID || !*chainID ) { 00205 // cert not found: 00206 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): empty chain ID - root not found" << endl; 00207 return; 00208 } 00209 const char * fpr = mChain.front().primaryFingerprint(); 00210 if ( tqstricmp( fpr, chainID ) == 0 ) { 00211 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): chain_id equals fingerprint -> found root" << endl; 00212 return; 00213 } 00214 if ( mChain.size() > 100 ) { 00215 // safe guard against certificate loops (paranoia factor 8 out of 10)... 00216 kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): maximum chain length of 100 exceeded!" << endl; 00217 return; 00218 } 00219 if ( !mFoundIssuer ) { 00220 // key listing failed. Don't end up in endless loop 00221 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): issuer not found - giving up" << endl; 00222 return; 00223 } 00224 00225 mFoundIssuer = false; 00226 00227 // gpgsm / dirmngr / LDAP / whoever doesn't support looking up 00228 // external keys by fingerprint. Furthermore, since we actually got 00229 // a chain-id set on the key, we know that we have the issuer's cert 00230 // in the local keyring, so just use local keylisting. 00231 Kleo::KeyListJob * job = 00232 Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false ); 00233 assert( job ); 00234 00235 connect( job, TQT_SIGNAL(result(const GpgME::KeyListResult&)), 00236 TQT_SLOT(slotCertificateChainListingResult(const GpgME::KeyListResult&)) ); 00237 connect( job, TQT_SIGNAL(nextKey(const GpgME::Key&)), 00238 TQT_SLOT(slotNextKey(const GpgME::Key&)) ); 00239 00240 kdDebug() << "Going to fetch" << endl 00241 << " issuer : \"" << mChain.front().issuerName() << "\"" << endl 00242 << " chain id: " << mChain.front().chainID() << endl 00243 << "for" << endl 00244 << " subject : \"" << mChain.front().userID(0).id() << "\"" << endl 00245 << " subj.fpr: " << mChain.front().primaryFingerprint() << endl; 00246 00247 const GpgME::Error err = job->start( mChain.front().chainID() ); 00248 00249 if ( err ) 00250 showChainListError( this, err, mChain.front().issuerName() ); 00251 else 00252 (void)new Kleo::ProgressDialog( job, i18n("Fetching Certificate Chain"), this ); 00253 } 00254 00255 void CertificateInfoWidgetImpl::startCertificateDump() { 00256 TDEProcess* proc = new TDEProcess( TQT_TQOBJECT(this) ); 00257 (*proc) << "gpgsm"; // must be in the PATH 00258 (*proc) << "--dump-keys"; 00259 (*proc) << mChain.front().primaryFingerprint(); 00260 00261 TQObject::connect( proc, TQT_SIGNAL( receivedStdout(TDEProcess *, char *, int) ), 00262 this, TQT_SLOT( slotCollectStdout(TDEProcess *, char *, int) ) ); 00263 TQObject::connect( proc, TQT_SIGNAL( receivedStderr(TDEProcess *, char *, int) ), 00264 this, TQT_SLOT( slotCollectStderr(TDEProcess *, char *, int) ) ); 00265 TQObject::connect( proc, TQT_SIGNAL( processExited(TDEProcess*) ), 00266 this, TQT_SLOT( slotDumpProcessExited(TDEProcess*) ) ); 00267 00268 if ( !proc->start( TDEProcess::NotifyOnExit, (TDEProcess::Communication)(TDEProcess::Stdout | TDEProcess::Stderr) ) ) { 00269 TQString wmsg = i18n("Failed to execute gpgsm:\n%1").arg( i18n( "program not found" ) ); 00270 dumpView->setText( TQStyleSheet::escape( wmsg ) ); 00271 delete proc; 00272 } 00273 } 00274 00275 void CertificateInfoWidgetImpl::slotCollectStdout(TDEProcess *, char *buffer, int buflen) 00276 { 00277 mDumpOutput += TQCString(buffer, buflen+1); // like KProcIO does 00278 } 00279 00280 void CertificateInfoWidgetImpl::slotCollectStderr(TDEProcess *, char *buffer, int buflen) 00281 { 00282 mDumpError += TQCString(buffer, buflen+1); // like KProcIO does 00283 } 00284 00285 void CertificateInfoWidgetImpl::slotDumpProcessExited(TDEProcess* proc) { 00286 int rc = ( proc->normalExit() ) ? proc->exitStatus() : -1 ; 00287 00288 if ( rc == 0 ) { 00289 dumpView->setText( TQStyleSheet::escape( TQString::fromUtf8( mDumpOutput ) ) ); 00290 } else { 00291 if ( !mDumpError.isEmpty() ) { 00292 dumpView->setText( TQStyleSheet::escape( TQString::fromUtf8( mDumpError ) ) ); 00293 } else 00294 { 00295 TQString wmsg = i18n("Failed to execute gpgsm:\n%1"); 00296 if ( rc == -1 ) 00297 wmsg = wmsg.arg( i18n( "program cannot be executed" ) ); 00298 else 00299 wmsg = wmsg.arg( strerror(rc) ); 00300 dumpView->setText( TQStyleSheet::escape( wmsg ) ); 00301 } 00302 } 00303 00304 proc->deleteLater(); 00305 } 00306 00307 void CertificateInfoWidgetImpl::slotNextKey( const GpgME::Key & key ) { 00308 kdDebug() << "CertificateInfoWidgetImpl::slotNextKey( \"" 00309 << key.userID(0).id() << "\" )" << endl; 00310 if ( key.isNull() ) 00311 return; 00312 00313 mFoundIssuer = true; 00314 mChain.push_front( key ); 00315 updateChainView(); 00316 // FIXME: cancel the keylisting. We're only interested in _one_ key. 00317 } 00318 00319 void CertificateInfoWidgetImpl::updateChainView() { 00320 pathView->clear(); 00321 if ( mChain.empty() ) 00322 return; 00323 TQListViewItem * item = 0; 00324 00325 TQValueList<GpgME::Key>::const_iterator it = mChain.begin(); 00326 // root item: 00327 if ( (*it).chainID() && qstrcmp( (*it).chainID(), (*it).primaryFingerprint() ) == 0 ) 00328 item = new TQListViewItem( pathView, Kleo::DN( (*it++).userID(0).id() ).prettyDN() ); 00329 else { 00330 item = new TQListViewItem( pathView, i18n("Issuer certificate not found ( %1)") 00331 .arg( Kleo::DN( (*it).issuerName() ).prettyDN() ) ); 00332 item->setOpen( true ); // TQt bug: doesn't open after setEnabled( false ) :/ 00333 item->setEnabled( false ); 00334 } 00335 item->setOpen( true ); 00336 00337 // subsequent items: 00338 while ( it != mChain.end() ) { 00339 item = new TQListViewItem( item, Kleo::DN( (*it++).userID(0).id() ).prettyDN() ); 00340 item->setOpen( true ); 00341 } 00342 } 00343 00344 void CertificateInfoWidgetImpl::slotCertificateChainListingResult( const GpgME::KeyListResult & res ) { 00345 if ( res.error() ) 00346 return showChainListError( this, res.error(), mChain.front().issuerName() ); 00347 else 00348 startCertificateChainListing(); 00349 } 00350 00351 void CertificateInfoWidgetImpl::slotShowInfo( TQListViewItem * item ) { 00352 textView->setText( item->text(1) ); 00353 } 00354 00355 void CertificateInfoWidgetImpl::slotShowCertPathDetails( TQListViewItem * item ) { 00356 if ( !item ) 00357 return; 00358 00359 // find the key corresponding to "item". This hack would not be 00360 // necessary if pathView was a Kleo::KeyListView, but it's 00361 // TQt-Designer-generated and I don't feel like creating a custom 00362 // widget spec for Kleo::KeyListView. 00363 unsigned int totalCount = 0; 00364 int itemIndex = -1; 00365 for ( const TQListViewItem * i = pathView->firstChild() ; i ; i = i->firstChild() ) { 00366 if ( i == item ) 00367 itemIndex = totalCount; 00368 ++totalCount; 00369 } 00370 00371 assert( totalCount == mChain.size() || totalCount == mChain.size() + 1 ); 00372 00373 // skip pseudo root item with "not found message": 00374 if ( totalCount == mChain.size() + 1 ) 00375 --itemIndex; 00376 00377 assert( itemIndex >= 0 ); 00378 00379 KDialogBase * dialog = 00380 new KDialogBase( this, "dialog", false, i18n("Additional Information for Key"), 00381 KDialogBase::Close, KDialogBase::Close ); 00382 CertificateInfoWidgetImpl * top = 00383 new CertificateInfoWidgetImpl( mChain[itemIndex], mExternal, dialog ); 00384 dialog->setMainWidget( top ); 00385 // proxy the signal to our receiver: 00386 connect( top, TQT_SIGNAL(requestCertificateDownload(const TQString&, const TQString&)), 00387 TQT_SIGNAL(requestCertificateDownload(const TQString&, const TQString&)) ); 00388 dialog->show(); 00389 } 00390 00391 00392 void CertificateInfoWidgetImpl::slotImportCertificate() 00393 { 00394 if ( mChain.empty() || mChain.back().isNull() ) 00395 return; 00396 const Kleo::DN dn = mChain.back().userID( 0 ).id(); 00397 emit requestCertificateDownload( mChain.back().primaryFingerprint(), dn.prettyDN() ); 00398 importButton->setEnabled( false ); 00399 } 00400 00401 void CertificateInfoWidgetImpl::startKeyExistanceCheck() { 00402 if ( !mExternal ) 00403 // we already have it if it's from a local keylisting :) 00404 return; 00405 if ( mChain.empty() || mChain.back().isNull() ) 00406 // need a key to look for 00407 return; 00408 const TQString fingerprint = mChain.back().primaryFingerprint(); 00409 if ( fingerprint.isEmpty() ) 00410 // empty pattern means list all keys. We don't want that 00411 return; 00412 00413 // start _local_ keylistjob (no progressdialog needed here): 00414 Kleo::KeyListJob * job = 00415 Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false ); 00416 assert( job ); 00417 00418 connect( job, TQT_SIGNAL(nextKey(const GpgME::Key&)), 00419 TQT_SLOT(slotKeyExistanceCheckNextCandidate(const GpgME::Key&)) ); 00420 connect( job, TQT_SIGNAL(result(const GpgME::KeyListResult&)), 00421 TQT_SLOT(slotKeyExistanceCheckFinished()) ); 00422 // nor to check for errors: 00423 job->start( fingerprint ); 00424 } 00425 00426 void CertificateInfoWidgetImpl::slotKeyExistanceCheckNextCandidate( const GpgME::Key & key ) { 00427 if ( key.isNull() || mChain.empty() || !key.primaryFingerprint() ) 00428 return; 00429 00430 if ( qstrcmp( key.primaryFingerprint(), 00431 mChain.back().primaryFingerprint() ) == 0 ) 00432 mHaveKeyLocally = true; 00433 } 00434 00435 void CertificateInfoWidgetImpl::slotKeyExistanceCheckFinished() { 00436 importButton->setEnabled( !mHaveKeyLocally ); 00437 } 00438 00439 00440 #include "certificateinfowidgetimpl.moc"