certmanager

certificateinfowidgetimpl.cpp
1/*
2 certificateinfowidgetimpl.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2001,2002,2004 Klarälvdalens Datakonsult AB
6
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the TQt library by Trolltech AS, Norway (or with modified versions
24 of TQt that use the same license as TQt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 TQt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
30 your version.
31*/
32
33#ifdef HAVE_CONFIG_H
34#include <config.h>
35#endif
36
37#include "certificateinfowidgetimpl.h"
38
39// libkleopatra
40#include <kleo/keylistjob.h>
41#include <kleo/dn.h>
42#include <kleo/cryptobackendfactory.h>
43
44#include <ui/progressdialog.h>
45
46// gpgme++
47#include <gpgmepp/keylistresult.h>
48
49// KDE
50#include <tdelocale.h>
51#include <kdialogbase.h>
52#include <tdemessagebox.h>
53#include <kdebug.h>
54#include <tdeprocio.h>
55#include <tdeglobalsettings.h>
56
57// TQt
58#include <tqlistview.h>
59#include <tqtextedit.h>
60#include <tqheader.h>
61#include <tqpushbutton.h>
62#include <tqcursor.h>
63#include <tqapplication.h>
64#include <tqdatetime.h>
65#include <tqstylesheet.h>
66#include <tqtextcodec.h>
67
68// other
69#include <assert.h>
70
71CertificateInfoWidgetImpl::CertificateInfoWidgetImpl( const GpgME::Key & key, bool external,
72 TQWidget * parent, const char * name )
73 : CertificateInfoWidget( parent, name ),
74 mExternal( external ),
75 mFoundIssuer( true ),
76 mHaveKeyLocally( false )
77{
78 importButton->setEnabled( false );
79
80 listView->setColumnWidthMode( 1, TQListView::Maximum );
81 TQFontMetrics fm = fontMetrics();
82 listView->setColumnWidth( 1, fm.width( i18n("Information") ) * 5 );
83
84 listView->header()->setClickEnabled( false );
85 listView->setSorting( -1 );
86
87 connect( listView, TQ_SIGNAL( selectionChanged( TQListViewItem* ) ),
88 this, TQ_SLOT( slotShowInfo( TQListViewItem* ) ) );
89 pathView->setColumnWidthMode( 0, TQListView::Maximum );
90 pathView->header()->hide();
91
92 connect( pathView, TQ_SIGNAL( doubleClicked( TQListViewItem* ) ),
93 this, TQ_SLOT( slotShowCertPathDetails( TQListViewItem* ) ) );
94 connect( pathView, TQ_SIGNAL( returnPressed( TQListViewItem* ) ),
95 this, TQ_SLOT( slotShowCertPathDetails( TQListViewItem* ) ) );
96 connect( importButton, TQ_SIGNAL( clicked() ),
97 this, TQ_SLOT( slotImportCertificate() ) );
98
99 dumpView->setFont( TDEGlobalSettings::fixedFont() );
100
101 if ( !key.isNull() )
102 setKey( key );
103}
104
105static TQString time_t2string( time_t t ) {
106 TQDateTime dt;
107 dt.setTime_t( t );
108 return dt.toString();
109}
110
111void CertificateInfoWidgetImpl::setKey( const GpgME::Key & key ) {
112 mChain.clear();
113 mFoundIssuer = true;
114 mHaveKeyLocally = false;
115
116 listView->clear();
117 pathView->clear();
118 importButton->setEnabled( false );
119
120 if ( key.isNull() )
121 return;
122
123 mChain.push_front( key );
124 startKeyExistanceCheck(); // starts a local keylisting to enable the
125 // importButton if needed
126
127 TQListViewItem * item = 0;
128 item = new TQListViewItem( listView, item, i18n("Valid"), TQString("From %1 to %2")
129 .arg( time_t2string( key.subkey(0).creationTime() ),
130 time_t2string( key.subkey(0).expirationTime() ) ) );
131 item = new TQListViewItem( listView, item, i18n("Can be used for signing"),
132 key.canSign() ? i18n("Yes") : i18n("No") );
133 item = new TQListViewItem( listView, item, i18n("Can be used for encryption"),
134 key.canEncrypt() ? i18n("Yes") : i18n("No") );
135 item = new TQListViewItem( listView, item, i18n("Can be used for certification"),
136 key.canCertify() ? i18n("Yes") : i18n("No") );
137 item = new TQListViewItem( listView, item, i18n("Can be used for authentication"),
138 key.canAuthenticate() ? i18n("Yes") : i18n("No" ) );
139 item = new TQListViewItem( listView, item, i18n("Fingerprint"), key.primaryFingerprint() );
140 item = new TQListViewItem( listView, item, i18n("Issuer"), Kleo::DN( key.issuerName() ).prettyDN() );
141 item = new TQListViewItem( listView, item, i18n("Serial Number"), key.issuerSerial() );
142
143 const Kleo::DN dn = key.userID(0).id();
144
145 // FIXME: use the attributeLabelMap from certificatewizardimpl.cpp:
146 static TQMap<TQString,TQString> dnComponentNames;
147 if ( dnComponentNames.isEmpty() ) {
148 dnComponentNames["C"] = i18n("Country");
149 dnComponentNames["OU"] = i18n("Organizational Unit");
150 dnComponentNames["O"] = i18n("Organization");
151 dnComponentNames["L"] = i18n("Location");
152 dnComponentNames["CN"] = i18n("Common Name");
153 dnComponentNames["EMAIL"] = i18n("Email");
154 }
155
156 for ( Kleo::DN::const_iterator dnit = dn.begin() ; dnit != dn.end() ; ++dnit ) {
157 TQString displayName = (*dnit).name();
158 if( dnComponentNames.contains(displayName) ) displayName = dnComponentNames[displayName];
159 item = new TQListViewItem( listView, item, displayName, (*dnit).value() );
160 }
161
162 const std::vector<GpgME::UserID> uids = key.userIDs();
163 if ( !uids.empty() ) {
164 item = new TQListViewItem( listView, item, i18n("Subject"),
165 Kleo::DN( uids.front().id() ).prettyDN() );
166 for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() + 1 ; it != uids.end() ; ++it ) {
167 if ( !(*it).id() )
168 continue;
169 const TQString email = TQString::fromUtf8( (*it).id() ).stripWhiteSpace();
170 if ( email.isEmpty() )
171 continue;
172 if ( email.startsWith( "<" ) )
173 item = new TQListViewItem( listView, item, i18n("Email"),
174 email.mid( 1, email.length()-2 ) );
175 else
176 item = new TQListViewItem( listView, item, i18n("A.k.a."), email );
177 }
178 }
179
180 updateChainView();
181 startCertificateChainListing();
182 startCertificateDump();
183}
184
185static void showChainListError( TQWidget * parent, const GpgME::Error & err, const char * subject ) {
186 assert( err );
187 const TQString msg = i18n("<qt><p>An error occurred while fetching "
188 "the certificate <b>%1</b> from the backend:</p>"
189 "<p><b>%2</b></p></qt>")
190 .arg( subject ? TQString::fromUtf8( subject ) : TQString(),
191 TQString::fromLocal8Bit( err.asString() ) );
192 KMessageBox::error( parent, msg, i18n("Certificate Listing Failed" ) );
193}
194
195void CertificateInfoWidgetImpl::startCertificateChainListing() {
196 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing()" << endl;
197
198 if ( mChain.empty() ) {
199 // we need a seed...
200 kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): mChain is empty!" << endl;
201 return;
202 }
203 const char * chainID = mChain.front().chainID();
204 if ( !chainID || !*chainID ) {
205 // cert not found:
206 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): empty chain ID - root not found" << endl;
207 return;
208 }
209 const char * fpr = mChain.front().primaryFingerprint();
210 if ( tqstricmp( fpr, chainID ) == 0 ) {
211 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): chain_id equals fingerprint -> found root" << endl;
212 return;
213 }
214 if ( mChain.size() > 100 ) {
215 // safe guard against certificate loops (paranoia factor 8 out of 10)...
216 kdWarning() << "CertificateInfoWidgetImpl::startCertificateChainListing(): maximum chain length of 100 exceeded!" << endl;
217 return;
218 }
219 if ( !mFoundIssuer ) {
220 // key listing failed. Don't end up in endless loop
221 kdDebug() << "CertificateInfoWidgetImpl::startCertificateChainListing(): issuer not found - giving up" << endl;
222 return;
223 }
224
225 mFoundIssuer = false;
226
227 // gpgsm / dirmngr / LDAP / whoever doesn't support looking up
228 // external keys by fingerprint. Furthermore, since we actually got
229 // a chain-id set on the key, we know that we have the issuer's cert
230 // in the local keyring, so just use local keylisting.
231 Kleo::KeyListJob * job =
232 Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false );
233 assert( job );
234
235 connect( job, TQ_SIGNAL(result(const GpgME::KeyListResult&)),
236 TQ_SLOT(slotCertificateChainListingResult(const GpgME::KeyListResult&)) );
237 connect( job, TQ_SIGNAL(nextKey(const GpgME::Key&)),
238 TQ_SLOT(slotNextKey(const GpgME::Key&)) );
239
240 kdDebug() << "Going to fetch" << endl
241 << " issuer : \"" << mChain.front().issuerName() << "\"" << endl
242 << " chain id: " << mChain.front().chainID() << endl
243 << "for" << endl
244 << " subject : \"" << mChain.front().userID(0).id() << "\"" << endl
245 << " subj.fpr: " << mChain.front().primaryFingerprint() << endl;
246
247 const GpgME::Error err = job->start( mChain.front().chainID() );
248
249 if ( err )
250 showChainListError( this, err, mChain.front().issuerName() );
251 else
252 (void)new Kleo::ProgressDialog( job, i18n("Fetching Certificate Chain"), this );
253}
254
255void CertificateInfoWidgetImpl::startCertificateDump() {
256 TDEProcess* proc = new TDEProcess( this );
257 (*proc) << "gpgsm"; // must be in the PATH
258 (*proc) << "--dump-keys";
259 (*proc) << mChain.front().primaryFingerprint();
260
261 TQObject::connect( proc, TQ_SIGNAL( receivedStdout(TDEProcess *, char *, int) ),
262 this, TQ_SLOT( slotCollectStdout(TDEProcess *, char *, int) ) );
263 TQObject::connect( proc, TQ_SIGNAL( receivedStderr(TDEProcess *, char *, int) ),
264 this, TQ_SLOT( slotCollectStderr(TDEProcess *, char *, int) ) );
265 TQObject::connect( proc, TQ_SIGNAL( processExited(TDEProcess*) ),
266 this, TQ_SLOT( slotDumpProcessExited(TDEProcess*) ) );
267
268 if ( !proc->start( TDEProcess::NotifyOnExit, (TDEProcess::Communication)(TDEProcess::Stdout | TDEProcess::Stderr) ) ) {
269 TQString wmsg = i18n("Failed to execute gpgsm:\n%1").arg( i18n( "program not found" ) );
270 dumpView->setText( TQStyleSheet::escape( wmsg ) );
271 delete proc;
272 }
273}
274
275void CertificateInfoWidgetImpl::slotCollectStdout(TDEProcess *, char *buffer, int buflen)
276{
277 mDumpOutput += TQCString(buffer, buflen+1); // like TDEProcIO does
278}
279
280void CertificateInfoWidgetImpl::slotCollectStderr(TDEProcess *, char *buffer, int buflen)
281{
282 mDumpError += TQCString(buffer, buflen+1); // like TDEProcIO does
283}
284
285void CertificateInfoWidgetImpl::slotDumpProcessExited(TDEProcess* proc) {
286 int rc = ( proc->normalExit() ) ? proc->exitStatus() : -1 ;
287
288 if ( rc == 0 ) {
289 dumpView->setText( TQStyleSheet::escape( TQString::fromUtf8( mDumpOutput ) ) );
290 } else {
291 if ( !mDumpError.isEmpty() ) {
292 dumpView->setText( TQStyleSheet::escape( TQString::fromUtf8( mDumpError ) ) );
293 } else
294 {
295 TQString wmsg = i18n("Failed to execute gpgsm:\n%1");
296 if ( rc == -1 )
297 wmsg = wmsg.arg( i18n( "program cannot be executed" ) );
298 else
299 wmsg = wmsg.arg( strerror(rc) );
300 dumpView->setText( TQStyleSheet::escape( wmsg ) );
301 }
302 }
303
304 proc->deleteLater();
305}
306
307void CertificateInfoWidgetImpl::slotNextKey( const GpgME::Key & key ) {
308 kdDebug() << "CertificateInfoWidgetImpl::slotNextKey( \""
309 << key.userID(0).id() << "\" )" << endl;
310 if ( key.isNull() )
311 return;
312
313 mFoundIssuer = true;
314 mChain.push_front( key );
315 updateChainView();
316 // FIXME: cancel the keylisting. We're only interested in _one_ key.
317}
318
319void CertificateInfoWidgetImpl::updateChainView() {
320 pathView->clear();
321 if ( mChain.empty() )
322 return;
323 TQListViewItem * item = 0;
324
325 TQValueList<GpgME::Key>::const_iterator it = mChain.begin();
326 // root item:
327 if ( (*it).chainID() && qstrcmp( (*it).chainID(), (*it).primaryFingerprint() ) == 0 )
328 item = new TQListViewItem( pathView, Kleo::DN( (*it++).userID(0).id() ).prettyDN() );
329 else {
330 item = new TQListViewItem( pathView, i18n("Issuer certificate not found ( %1)")
331 .arg( Kleo::DN( (*it).issuerName() ).prettyDN() ) );
332 item->setOpen( true ); // TQt bug: doesn't open after setEnabled( false ) :/
333 item->setEnabled( false );
334 }
335 item->setOpen( true );
336
337 // subsequent items:
338 while ( it != mChain.end() ) {
339 item = new TQListViewItem( item, Kleo::DN( (*it++).userID(0).id() ).prettyDN() );
340 item->setOpen( true );
341 }
342}
343
344void CertificateInfoWidgetImpl::slotCertificateChainListingResult( const GpgME::KeyListResult & res ) {
345 if ( res.error() )
346 return showChainListError( this, res.error(), mChain.front().issuerName() );
347 else
348 startCertificateChainListing();
349}
350
351void CertificateInfoWidgetImpl::slotShowInfo( TQListViewItem * item ) {
352 textView->setText( item->text(1) );
353}
354
355void CertificateInfoWidgetImpl::slotShowCertPathDetails( TQListViewItem * item ) {
356 if ( !item )
357 return;
358
359 // find the key corresponding to "item". This hack would not be
360 // necessary if pathView was a Kleo::KeyListView, but it's
361 // TQt-Designer-generated and I don't feel like creating a custom
362 // widget spec for Kleo::KeyListView.
363 unsigned int totalCount = 0;
364 int itemIndex = -1;
365 for ( const TQListViewItem * i = pathView->firstChild() ; i ; i = i->firstChild() ) {
366 if ( i == item )
367 itemIndex = totalCount;
368 ++totalCount;
369 }
370
371 assert( totalCount == mChain.size() || totalCount == mChain.size() + 1 );
372
373 // skip pseudo root item with "not found message":
374 if ( totalCount == mChain.size() + 1 )
375 --itemIndex;
376
377 assert( itemIndex >= 0 );
378
379 KDialogBase * dialog =
380 new KDialogBase( this, "dialog", false, i18n("Additional Information for Key"),
381 KDialogBase::Close, KDialogBase::Close );
382 CertificateInfoWidgetImpl * top =
383 new CertificateInfoWidgetImpl( mChain[itemIndex], mExternal, dialog );
384 dialog->setMainWidget( top );
385 // proxy the signal to our receiver:
386 connect( top, TQ_SIGNAL(requestCertificateDownload(const TQString&, const TQString&)),
387 TQ_SIGNAL(requestCertificateDownload(const TQString&, const TQString&)) );
388 dialog->show();
389}
390
391
392void CertificateInfoWidgetImpl::slotImportCertificate()
393{
394 if ( mChain.empty() || mChain.back().isNull() )
395 return;
396 const Kleo::DN dn = mChain.back().userID( 0 ).id();
397 emit requestCertificateDownload( mChain.back().primaryFingerprint(), dn.prettyDN() );
398 importButton->setEnabled( false );
399}
400
401void CertificateInfoWidgetImpl::startKeyExistanceCheck() {
402 if ( !mExternal )
403 // we already have it if it's from a local keylisting :)
404 return;
405 if ( mChain.empty() || mChain.back().isNull() )
406 // need a key to look for
407 return;
408 const TQString fingerprint = mChain.back().primaryFingerprint();
409 if ( fingerprint.isEmpty() )
410 // empty pattern means list all keys. We don't want that
411 return;
412
413 // start _local_ keylistjob (no progressdialog needed here):
414 Kleo::KeyListJob * job =
415 Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( false );
416 assert( job );
417
418 connect( job, TQ_SIGNAL(nextKey(const GpgME::Key&)),
419 TQ_SLOT(slotKeyExistanceCheckNextCandidate(const GpgME::Key&)) );
420 connect( job, TQ_SIGNAL(result(const GpgME::KeyListResult&)),
421 TQ_SLOT(slotKeyExistanceCheckFinished()) );
422 // nor to check for errors:
423 job->start( fingerprint );
424}
425
426void CertificateInfoWidgetImpl::slotKeyExistanceCheckNextCandidate( const GpgME::Key & key ) {
427 if ( key.isNull() || mChain.empty() || !key.primaryFingerprint() )
428 return;
429
430 if ( qstrcmp( key.primaryFingerprint(),
431 mChain.back().primaryFingerprint() ) == 0 )
432 mHaveKeyLocally = true;
433}
434
435void CertificateInfoWidgetImpl::slotKeyExistanceCheckFinished() {
436 importButton->setEnabled( !mHaveKeyLocally );
437}
438
439
440#include "certificateinfowidgetimpl.moc"