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 <kprocio.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 
71 CertificateInfoWidgetImpl::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 
105 static TQString time_t2string( time_t t ) {
106  TQDateTime dt;
107  dt.setTime_t( t );
108  return dt.toString();
109 }
110 
111 void 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 
185 static 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 
195 void 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 
255 void 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 
275 void CertificateInfoWidgetImpl::slotCollectStdout(TDEProcess *, char *buffer, int buflen)
276 {
277  mDumpOutput += TQCString(buffer, buflen+1); // like KProcIO does
278 }
279 
280 void CertificateInfoWidgetImpl::slotCollectStderr(TDEProcess *, char *buffer, int buflen)
281 {
282  mDumpError += TQCString(buffer, buflen+1); // like KProcIO does
283 }
284 
285 void 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 
307 void 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 
319 void 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 
344 void 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 
351 void CertificateInfoWidgetImpl::slotShowInfo( TQListViewItem * item ) {
352  textView->setText( item->text(1) );
353 }
354 
355 void 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 
392 void 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 
401 void 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 
426 void 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 
435 void CertificateInfoWidgetImpl::slotKeyExistanceCheckFinished() {
436  importButton->setEnabled( !mHaveKeyLocally );
437 }
438 
439 
440 #include "certificateinfowidgetimpl.moc"