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 storeUR->setURL( url.prettyURL() );
391}
392
393KURL CertificateWizardImpl::saveFileUrl() const {
394 return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() );
395}
396
397void CertificateWizardImpl::showPage( TQWidget * page )
398{
399 CertificateWizard::showPage( page );
400 if ( page == generatePage ) {
401 // Initial settings for the generation page: focus the correct lineedit
402 // and disable the other one
403 if ( storeInFileRB->isChecked() ) {
404 storeUR->setEnabled( true );
405 caEmailED->setEnabled( false );
406 storeUR->setFocus();
407 } else {
408 storeUR->setEnabled( false );
409 caEmailED->setEnabled( true );
410 caEmailED->setFocus();
411 }
412 }
413}
414
415static const char* const dcopObjectId = "KMailIface";
419void CertificateWizardImpl::sendCertificate( const TQString& email, const TQByteArray& certificateData )
420{
421 TQString error;
422 TQCString dcopService;
423 int result = KDCOPServiceStarter::self()->
424 findServiceFor( "DCOP/Mailer", TQString(),
425 TQString(), &error, &dcopService );
426 if ( result != 0 ) {
427 kdDebug() << "Couldn't connect to KMail\n";
428 KMessageBox::error( this,
429 i18n( "DCOP Communication Error, unable to send certificate using KMail.\n%1" ).arg( error ) );
430 return;
431 }
432
433 TQCString dummy;
434 // OK, so kmail (or kontact) is running. Now ensure the object we want is available.
435 // [that's not the case when kontact was already running, but kmail not loaded into it... in theory.]
436 if ( !tdeApp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) ) {
437 DCOPRef ref( dcopService, dcopService ); // talk to the TDEUniqueApplication or its kontact wrapper
438 DCOPReply reply = ref.call( "load()" );
439 if ( reply.isValid() && (bool)reply ) {
440 Q_ASSERT( tdeApp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) );
441 } else
442 kdWarning() << "Error loading " << dcopService << endl;
443 }
444
445 DCOPClient* dcopClient = tdeApp->dcopClient();
446 TQByteArray data;
447 TQDataStream arg( data, IO_WriteOnly );
448 arg << email;
449 arg << certificateData;
450 if( !dcopClient->send( dcopService, dcopObjectId,
451 "sendCertificate(TQString,TQByteArray)", data ) ) {
452 KMessageBox::error( this,
453 i18n( "DCOP Communication Error, unable to send certificate using KMail." ) );
454 return;
455 }
456 // All good, close dialog
457 CertificateWizard::accept();
458}
459
460// Called when pressing Finish
461// We want to do the emailing/uploading first, before closing the dialog,
462// in case of errors during the upload.
463void CertificateWizardImpl::accept()
464{
465 if( sendToCA() ) {
466 // Ask KMail to send this key to the CA.
467 sendCertificate( caEMailAddress(), _keyData );
468 } else {
469 // Save in file/URL
470 KURL url = saveFileUrl();
471 bool overwrite = false;
472 if ( TDEIO::NetAccess::exists( url, false /*dest*/, this ) ) {
473 if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
474 this,
475 i18n( "A file named \"%1\" already exists. "
476 "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
477 i18n( "Overwrite File?" ),
478 i18n( "&Overwrite" ) ) )
479 return;
480 overwrite = true;
481 }
482
483 TDEIO::Job* uploadJob = TDEIOext::put( _keyData, url, -1, overwrite, false /*resume*/ );
484 uploadJob->setWindow( this );
485 connect( uploadJob, TQ_SIGNAL( result( TDEIO::Job* ) ),
486 this, TQ_SLOT( slotUploadResult( TDEIO::Job* ) ) );
487 // Can't press finish again during the upload
488 setFinishEnabled( finishPage, false );
489 }
490}
491
496void CertificateWizardImpl::slotUploadResult( TDEIO::Job* job )
497{
498 if ( job->error() ) {
499 job->showErrorDialog();
500 setFinishEnabled( finishPage, true );
501 } else {
502 // All good, close dialog
503 CertificateWizard::accept();
504 }
505}
506
507#include "certificatewizardimpl.moc"