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
79static const unsigned int keyLengths[] = {
80 1024, 1532, 2048, 3072, 4096
81};
82static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths;
83
84static 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
102static TQString attributeFromKey( TQString key ) {
103 return key.remove( '!' );
104}
105
106static 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 */
117CertificateWizardImpl::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
144static 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 */
169void CertificateWizardImpl::slotEnablePersonalDataPageExit() {
170 setNextEnabled( personalDataPage, requirementsAreMet( _attrPairList ) );
171}
172
173
174/*
175 * Destroys the object and frees any allocated resources
176 */
177CertificateWizardImpl::~CertificateWizardImpl()
178{
179 // no need to delete child widgets, TQt does it all for us
180}
181
182static 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 */
193void 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
268void 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
291void CertificateWizardImpl::slotHelpClicked()
292{
293 tdeApp->invokeHelp( "newcert" );
294}
295
296void 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
335void 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
379bool CertificateWizardImpl::sendToCA() const {
380 return sendToCARB->isChecked();
381}
382
383TQString CertificateWizardImpl::caEMailAddress() const {
384 return caEmailED->text().stripWhiteSpace();
385}
386
387void 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
401KURL CertificateWizardImpl::saveFileUrl() const {
402 return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() );
403}
404
405void 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
423static const char* const dcopObjectId = "KMailIface";
427void 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 ( !tdeApp->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( tdeApp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) );
449 } else
450 kdWarning() << "Error loading " << dcopService << endl;
451 }
452
453 DCOPClient* dcopClient = tdeApp->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.
471void 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
504void 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"