certmanager

certificatewizardimpl.cpp
1 /*
2  certificatewizardimpl.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 "certificatewizardimpl.h"
38 #include "storedtransferjob.h"
39 
40 // libkleopatra
41 #include <kleo/oidmap.h>
42 #include <kleo/keygenerationjob.h>
43 #include <kleo/dn.h>
44 #include <kleo/cryptobackendfactory.h>
45 
46 #include <ui/progressdialog.h>
47 
48 // gpgme++
49 #include <gpgmepp/keygenerationresult.h>
50 
51 // KDE
52 #include <tdeabc/stdaddressbook.h>
53 #include <tdeabc/addressee.h>
54 
55 #include <tdemessagebox.h>
56 #include <tdelocale.h>
57 #include <tdeapplication.h>
58 #include <kdebug.h>
59 #include <kdialog.h>
60 #include <kurlrequester.h>
61 #include <kdcopservicestarter.h>
62 #include <dcopclient.h>
63 #include <tdeio/job.h>
64 #include <tdeio/netaccess.h>
65 
66 // TQt
67 #include <tqlineedit.h>
68 #include <tqtextedit.h>
69 #include <tqpushbutton.h>
70 #include <tqcheckbox.h>
71 #include <tqradiobutton.h>
72 #include <tqlayout.h>
73 #include <tqlabel.h>
74 #include <tqcombobox.h>
75 
76 #include <assert.h>
77 #include <dcopref.h>
78 
79 static const unsigned int keyLengths[] = {
80  1024, 1532, 2048, 3072, 4096
81 };
82 static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths;
83 
84 static TQString attributeLabel( const TQString & attr, bool required ) {
85  if ( attr.isEmpty() )
86  return TQString();
87  const TQString label = Kleo::DNAttributeMapper::instance()->name2label( attr );
88  if ( !label.isEmpty() )
89  if ( required )
90  return i18n("Format string for the labels in the \"Your Personal Data\" page - required field",
91  "*%1 (%2):").arg( label, attr );
92  else
93  return i18n("Format string for the labels in the \"Your Personal Data\" page",
94  "%1 (%2):").arg( label, attr );
95 
96  else if ( required )
97  return '*' + attr + ':';
98  else
99  return attr + ':';
100 }
101 
102 static TQString attributeFromKey( TQString key ) {
103  return key.remove( '!' );
104 }
105 
106 static bool availForMod( const TQLineEdit * le ) {
107  return le && le->isEnabled();
108 }
109 
110 /*
111  * Constructs a CertificateWizardImpl which is a child of 'parent', with the
112  * name 'name' and widget flags set to 'f'
113  *
114  * The wizard will by default be modeless, unless you set 'modal' to
115  * TRUE to construct a modal wizard.
116  */
117 CertificateWizardImpl::CertificateWizardImpl( TQWidget* parent, const char* name, bool modal, WFlags fl )
118  : CertificateWizard( parent, name, modal, fl )
119 {
120  // don't allow to go to last page until a key has been generated
121  setNextEnabled( generatePage, false );
122  // setNextEnabled( personalDataPage, false ); // ## disable again once we have a criteria when to enable again
123 
124  createPersonalDataPage();
125 
126  // Allow to select remote URLs
127  storeUR->setMode( KFile::File );
128  storeUR->setFilter( "application/pkcs10" );
129  connect( storeUR, TQ_SIGNAL( urlSelected( const TQString& ) ),
130  this, TQ_SLOT( slotURLSelected( const TQString& ) ) );
131 
132  const TDEConfigGroup config( TDEGlobal::config(), "CertificateCreationWizard" );
133  caEmailED->setText( config.readEntry( "CAEmailAddress" ) );
134 
135  connect( this, TQ_SIGNAL( helpClicked() ),
136  this, TQ_SLOT( slotHelpClicked() ) );
137  connect( insertAddressButton, TQ_SIGNAL( clicked() ),
138  this, TQ_SLOT( slotSetValuesFromWhoAmI() ) );
139 
140  for ( unsigned int i = 0 ; i < numKeyLengths ; ++i )
141  keyLengthCB->insertItem( i18n("%n bit", "%n bits", keyLengths[i] ) );
142 }
143 
144 static bool requirementsAreMet( const CertificateWizardImpl::AttrPairList & list ) {
145  for ( CertificateWizardImpl::AttrPairList::const_iterator it = list.begin() ;
146  it != list.end() ; ++it ) {
147  const TQLineEdit * le = (*it).second;
148  if ( !le )
149  continue;
150  const TQString key = (*it).first;
151 #ifndef NDEBUG
152  kdbgstream s = kdDebug();
153 #else
154  kndbgstream s = kdDebug();
155 #endif
156  s << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\": ";
157  if ( key.endsWith("!") && le->text().stripWhiteSpace().isEmpty() ) {
158  s << "required field is empty!" << endl;
159  return false;
160  }
161  s << "ok" << endl;
162  }
163  return true;
164 }
165 
166 /*
167  This slot is called when the user changes the text.
168  */
169 void CertificateWizardImpl::slotEnablePersonalDataPageExit() {
170  setNextEnabled( personalDataPage, requirementsAreMet( _attrPairList ) );
171 }
172 
173 
174 /*
175  * Destroys the object and frees any allocated resources
176  */
177 CertificateWizardImpl::~CertificateWizardImpl()
178 {
179  // no need to delete child widgets, TQt does it all for us
180 }
181 
182 static const char * oidForAttributeName( const TQString & attr ) {
183  TQCString attrUtf8 = attr.utf8();
184  for ( unsigned int i = 0 ; i < numOidMaps ; ++i )
185  if ( tqstricmp( attrUtf8, oidmap[i].name ) == 0 )
186  return oidmap[i].oid;
187  return 0;
188 }
189 
190 /*
191  * protected slot
192  */
193 void CertificateWizardImpl::slotGenerateCertificate()
194 {
195  // Ask gpgme to generate a key and return it
196  TQString certParms;
197  certParms += "<GnupgKeyParms format=\"internal\">\n";
198  certParms += "Key-Type: RSA\n";
199  certParms += TQString( "Key-Length: %1\n" ).arg( keyLengths[keyLengthCB->currentItem()] );
200  certParms += "Key-Usage: ";
201  if ( signOnlyCB->isChecked() )
202  certParms += "Sign";
203  else if ( encryptOnlyCB->isChecked() )
204  certParms += "Encrypt";
205  else
206  certParms += "Sign, Encrypt";
207  certParms += "\n";
208  certParms += "name-dn: ";
209 
210  TQString email;
211  TQStringList rdns;
212  for( AttrPairList::const_iterator it = _attrPairList.begin(); it != _attrPairList.end(); ++it ) {
213  const TQString attr = attributeFromKey( (*it).first.upper() );
214  const TQLineEdit * le = (*it).second;
215  if ( !le )
216  continue;
217 
218  const TQString value = le->text().stripWhiteSpace();
219  if ( value.isEmpty() )
220  continue;
221 
222  if ( attr == "EMAIL" ) {
223  // EMAIL is special, since it shouldn't be part of the DN,
224  // except for non-RFC-conformant CAs that require it to be
225  // there.
226  email = value;
227  if ( !brokenCA->isChecked() )
228  continue;
229  }
230 
231  if ( const char * oid = oidForAttributeName( attr ) ) {
232  // we need to translate the attribute name for the backend:
233  rdns.push_back( TQString::fromUtf8( oid ) + '=' + Kleo::DN::escape( value ) );
234  } else {
235  rdns.push_back( attr + '=' + Kleo::DN::escape( value ) );
236  }
237  }
238  certParms += rdns.join(",");
239  if( !email.isEmpty() )
240  certParms += "\nname-email: " + email;
241  certParms += "\n</GnupgKeyParms>\n";
242 
243  kdDebug() << certParms << endl;
244 
245  Kleo::KeyGenerationJob * job =
246  Kleo::CryptoBackendFactory::instance()->smime()->keyGenerationJob();
247  assert( job );
248 
249  connect( job, TQ_SIGNAL(result(const GpgME::KeyGenerationResult&,const TQByteArray&)),
250  TQ_SLOT(slotResult(const GpgME::KeyGenerationResult&,const TQByteArray&)) );
251 
252  certificateTE->setText( certParms );
253 
254  const GpgME::Error err = job->start( certParms );
255  if ( err )
256  KMessageBox::error( this,
257  i18n( "Could not start certificate generation: %1" )
258  .arg( TQString::fromLocal8Bit( err.asString() ) ),
259  i18n( "Certificate Manager Error" ) );
260  else {
261  generatePB->setEnabled( false );
262  setBackEnabled( generatePage, false );
263  (void)new Kleo::ProgressDialog( job, i18n("Generating key"), this );
264  }
265 }
266 
267 
268 void CertificateWizardImpl::slotResult( const GpgME::KeyGenerationResult & res,
269  const TQByteArray & keyData ) {
270  //kdDebug() << "keyData.size(): " << keyData.size() << endl;
271  _keyData = keyData;
272 
273  if ( res.error().isCanceled() || res.error() ) {
274  setNextEnabled( generatePage, false );
275  setBackEnabled( generatePage, true );
276  setFinishEnabled( finishPage, false );
277  generatePB->setEnabled( true );
278  if ( !res.error().isCanceled() )
279  KMessageBox::error( this,
280  i18n( "Could not generate certificate: %1" )
281  .arg( TQString::fromLatin1( res.error().asString() ) ),
282  i18n( "Certificate Manager Error" ) );
283  } else {
284  // next will stay enabled until the user clicks Generate
285  // Certificate again
286  setNextEnabled( generatePage, true );
287  setFinishEnabled( finishPage, true );
288  }
289 }
290 
291 void CertificateWizardImpl::slotHelpClicked()
292 {
293  kapp->invokeHelp( "newcert" );
294 }
295 
296 void CertificateWizardImpl::slotSetValuesFromWhoAmI()
297 {
298  const TDEABC::Addressee a = TDEABC::StdAddressBook::self( true )->whoAmI();
299  if ( a.isEmpty() )
300  return;
301  const TDEABC::Address adr = a.address(TDEABC::Address::Work);
302 
303  for ( AttrPairList::const_iterator it = _attrPairList.begin() ;
304  it != _attrPairList.end() ; ++it ) {
305  TQLineEdit * le = (*it).second;
306  if ( !availForMod( le ) )
307  continue;
308 
309  const TQString attr = attributeFromKey( (*it).first.upper() );
310  if ( attr == "CN" )
311  le->setText( a.formattedName() );
312  else if ( attr == "EMAIL" )
313  le->setText( a.preferredEmail() );
314  else if ( attr == "O" )
315  le->setText( a.organization() );
316  else if ( attr == "OU" )
317  le->setText( a.custom( "KADDRESSBOOK", "X-Department" ) );
318  else if ( attr == "L" )
319  le->setText( adr.locality() );
320  else if ( attr == "SP" )
321  le->setText( adr.region() );
322  else if ( attr == "PC" )
323  le->setText( adr.postalCode() );
324  else if ( attr == "SN" )
325  le->setText( a.familyName() );
326  else if ( attr == "GN" )
327  le->setText( a.givenName() );
328  else if ( attr == "T" )
329  le->setText( a.title() );
330  else if ( attr == "BC" )
331  le->setText( a.role() ); // correct mapping?
332  }
333 }
334 
335 void CertificateWizardImpl::createPersonalDataPage()
336 {
337  TQGridLayout* grid = new TQGridLayout( edContainer, 2, 1,
338  KDialog::marginHint(), KDialog::spacingHint() );
339 
340  TDEConfigGroup config( TDEGlobal::config(), "CertificateCreationWizard" );
341  TQStringList attrOrder = config.readListEntry( "DNAttributeOrder" );
342  if ( attrOrder.empty() )
343  attrOrder << "CN!" << "L" << "OU" << "O!" << "C!" << "EMAIL!";
344  int row = 0;
345 
346  for ( TQStringList::const_iterator it = attrOrder.begin() ; it != attrOrder.end() ; ++it, ++row ) {
347  const TQString key = (*it).stripWhiteSpace().upper();
348  const TQString attr = attributeFromKey( key );
349  if ( attr.isEmpty() ) {
350  --row;
351  continue;
352  }
353  const TQString preset = config.readEntry( attr );
354  const TQString label = config.readEntry( attr + "_label",
355  attributeLabel( attr, key.endsWith("!") ) );
356 
357  TQLineEdit * le = new TQLineEdit( edContainer );
358  grid->addWidget( le, row, 1 );
359  grid->addWidget( new TQLabel( le, label.isEmpty() ? attr : label, edContainer ), row, 0 );
360 
361  le->setText( preset );
362  if ( config.entryIsImmutable( attr ) )
363  le->setEnabled( false );
364 
365  _attrPairList.append(qMakePair(key, le));
366 
367  connect( le, TQ_SIGNAL(textChanged(const TQString&)),
368  TQ_SLOT(slotEnablePersonalDataPageExit()) );
369  }
370 
371  // enable button only if administrator wants to allow it
372  if (TDEABC::StdAddressBook::self( true )->whoAmI().isEmpty() ||
373  !config.readBoolEntry("ShowSetWhoAmI", true))
374  insertAddressButton->setEnabled( false );
375 
376  slotEnablePersonalDataPageExit();
377 }
378 
379 bool CertificateWizardImpl::sendToCA() const {
380  return sendToCARB->isChecked();
381 }
382 
383 TQString CertificateWizardImpl::caEMailAddress() const {
384  return caEmailED->text().stripWhiteSpace();
385 }
386 
387 void CertificateWizardImpl::slotURLSelected( const TQString& _url )
388 {
389  KURL url = KURL::fromPathOrURL( _url.stripWhiteSpace() );
390 #if ! KDE_IS_VERSION(3,2,90)
391  // The application/pkcs10 mimetype didn't have a native extension,
392  // so the filedialog didn't have the checkbox for auto-adding it.
393  TQString fileName = url.fileName();
394  int pos = fileName.findRev( '.' );
395  if ( pos < 0 ) // no extension
396  url.setFileName( fileName + ".p10" );
397 #endif
398  storeUR->setURL( url.prettyURL() );
399 }
400 
401 KURL CertificateWizardImpl::saveFileUrl() const {
402  return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() );
403 }
404 
405 void CertificateWizardImpl::showPage( TQWidget * page )
406 {
407  CertificateWizard::showPage( page );
408  if ( page == generatePage ) {
409  // Initial settings for the generation page: focus the correct lineedit
410  // and disable the other one
411  if ( storeInFileRB->isChecked() ) {
412  storeUR->setEnabled( true );
413  caEmailED->setEnabled( false );
414  storeUR->setFocus();
415  } else {
416  storeUR->setEnabled( false );
417  caEmailED->setEnabled( true );
418  caEmailED->setFocus();
419  }
420  }
421 }
422 
423 static const char* const dcopObjectId = "KMailIface";
427 void CertificateWizardImpl::sendCertificate( const TQString& email, const TQByteArray& certificateData )
428 {
429  TQString error;
430  TQCString dcopService;
431  int result = KDCOPServiceStarter::self()->
432  findServiceFor( "DCOP/Mailer", TQString(),
433  TQString(), &error, &dcopService );
434  if ( result != 0 ) {
435  kdDebug() << "Couldn't connect to KMail\n";
436  KMessageBox::error( this,
437  i18n( "DCOP Communication Error, unable to send certificate using KMail.\n%1" ).arg( error ) );
438  return;
439  }
440 
441  TQCString dummy;
442  // OK, so kmail (or kontact) is running. Now ensure the object we want is available.
443  // [that's not the case when kontact was already running, but kmail not loaded into it... in theory.]
444  if ( !kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) ) {
445  DCOPRef ref( dcopService, dcopService ); // talk to the TDEUniqueApplication or its kontact wrapper
446  DCOPReply reply = ref.call( "load()" );
447  if ( reply.isValid() && (bool)reply ) {
448  Q_ASSERT( kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) );
449  } else
450  kdWarning() << "Error loading " << dcopService << endl;
451  }
452 
453  DCOPClient* dcopClient = kapp->dcopClient();
454  TQByteArray data;
455  TQDataStream arg( data, IO_WriteOnly );
456  arg << email;
457  arg << certificateData;
458  if( !dcopClient->send( dcopService, dcopObjectId,
459  "sendCertificate(TQString,TQByteArray)", data ) ) {
460  KMessageBox::error( this,
461  i18n( "DCOP Communication Error, unable to send certificate using KMail." ) );
462  return;
463  }
464  // All good, close dialog
465  CertificateWizard::accept();
466 }
467 
468 // Called when pressing Finish
469 // We want to do the emailing/uploading first, before closing the dialog,
470 // in case of errors during the upload.
471 void CertificateWizardImpl::accept()
472 {
473  if( sendToCA() ) {
474  // Ask KMail to send this key to the CA.
475  sendCertificate( caEMailAddress(), _keyData );
476  } else {
477  // Save in file/URL
478  KURL url = saveFileUrl();
479  bool overwrite = false;
480  if ( TDEIO::NetAccess::exists( url, false /*dest*/, this ) ) {
481  if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
482  this,
483  i18n( "A file named \"%1\" already exists. "
484  "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
485  i18n( "Overwrite File?" ),
486  i18n( "&Overwrite" ) ) )
487  return;
488  overwrite = true;
489  }
490 
491  TDEIO::Job* uploadJob = TDEIOext::put( _keyData, url, -1, overwrite, false /*resume*/ );
492  uploadJob->setWindow( this );
493  connect( uploadJob, TQ_SIGNAL( result( TDEIO::Job* ) ),
494  this, TQ_SLOT( slotUploadResult( TDEIO::Job* ) ) );
495  // Can't press finish again during the upload
496  setFinishEnabled( finishPage, false );
497  }
498 }
499 
504 void CertificateWizardImpl::slotUploadResult( TDEIO::Job* job )
505 {
506  if ( job->error() ) {
507  job->showErrorDialog();
508  setFinishEnabled( finishPage, true );
509  } else {
510  // All good, close dialog
511  CertificateWizard::accept();
512  }
513 }
514 
515 #include "certificatewizardimpl.moc"