certificatewizardimpl.cpp
00001 /* 00002 certificatewizardimpl.cpp 00003 00004 This file is part of Kleopatra, the KDE keymanager 00005 Copyright (c) 2001,2002,2004 Klar�vdalens Datakonsult AB 00006 00007 Kleopatra is free software; you can redistribute it and/or modify 00008 it under the terms of the GNU General Public License as published by 00009 the Free Software Foundation; either version 2 of the License, or 00010 (at your option) any later version. 00011 00012 Kleopatra is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 General Public License for more details. 00016 00017 You should have received a copy of the GNU General Public License 00018 along with this program; if not, write to the Free Software 00019 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 00021 In addition, as a special exception, the copyright holders give 00022 permission to link the code of this program with any edition of 00023 the TQt library by Trolltech AS, Norway (or with modified versions 00024 of TQt that use the same license as TQt), and distribute linked 00025 combinations including the two. You must obey the GNU General 00026 Public License in all respects for all of the code used other than 00027 TQt. If you modify this file, you may extend this exception to 00028 your version of the file, but you are not obligated to do so. If 00029 you do not wish to do so, delete this exception statement from 00030 your version. 00031 */ 00032 00033 #ifdef HAVE_CONFIG_H 00034 #include <config.h> 00035 #endif 00036 00037 #include "certificatewizardimpl.h" 00038 #include "storedtransferjob.h" 00039 00040 // libkleopatra 00041 #include <kleo/oidmap.h> 00042 #include <kleo/keygenerationjob.h> 00043 #include <kleo/dn.h> 00044 #include <kleo/cryptobackendfactory.h> 00045 00046 #include <ui/progressdialog.h> 00047 00048 // gpgme++ 00049 #include <gpgmepp/keygenerationresult.h> 00050 00051 // KDE 00052 #include <kabc/stdaddressbook.h> 00053 #include <kabc/addressee.h> 00054 00055 #include <kmessagebox.h> 00056 #include <klocale.h> 00057 #include <kapplication.h> 00058 #include <kdebug.h> 00059 #include <kdialog.h> 00060 #include <kurlrequester.h> 00061 #include <kdcopservicestarter.h> 00062 #include <dcopclient.h> 00063 #include <kio/job.h> 00064 #include <kio/netaccess.h> 00065 00066 // TQt 00067 #include <tqlineedit.h> 00068 #include <tqtextedit.h> 00069 #include <tqpushbutton.h> 00070 #include <tqcheckbox.h> 00071 #include <tqradiobutton.h> 00072 #include <tqlayout.h> 00073 #include <tqlabel.h> 00074 #include <tqcombobox.h> 00075 00076 #include <assert.h> 00077 #include <dcopref.h> 00078 00079 static const unsigned int keyLengths[] = { 00080 1024, 1532, 2048, 3072, 4096 00081 }; 00082 static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths; 00083 00084 static TQString attributeLabel( const TQString & attr, bool required ) { 00085 if ( attr.isEmpty() ) 00086 return TQString(); 00087 const TQString label = Kleo::DNAttributeMapper::instance()->name2label( attr ); 00088 if ( !label.isEmpty() ) 00089 if ( required ) 00090 return i18n("Format string for the labels in the \"Your Personal Data\" page - required field", 00091 "*%1 (%2):").arg( label, attr ); 00092 else 00093 return i18n("Format string for the labels in the \"Your Personal Data\" page", 00094 "%1 (%2):").arg( label, attr ); 00095 00096 else if ( required ) 00097 return '*' + attr + ':'; 00098 else 00099 return attr + ':'; 00100 } 00101 00102 static TQString attributeFromKey( TQString key ) { 00103 return key.remove( '!' ); 00104 } 00105 00106 static bool availForMod( const TQLineEdit * le ) { 00107 return le && le->isEnabled(); 00108 } 00109 00110 /* 00111 * Constructs a CertificateWizardImpl which is a child of 'parent', with the 00112 * name 'name' and widget flags set to 'f' 00113 * 00114 * The wizard will by default be modeless, unless you set 'modal' to 00115 * TRUE to construct a modal wizard. 00116 */ 00117 CertificateWizardImpl::CertificateWizardImpl( TQWidget* parent, const char* name, bool modal, WFlags fl ) 00118 : CertificateWizard( parent, name, modal, fl ) 00119 { 00120 // don't allow to go to last page until a key has been generated 00121 setNextEnabled( generatePage, false ); 00122 // setNextEnabled( personalDataPage, false ); // ## disable again once we have a criteria when to enable again 00123 00124 createPersonalDataPage(); 00125 00126 // Allow to select remote URLs 00127 storeUR->setMode( KFile::File ); 00128 storeUR->setFilter( "application/pkcs10" ); 00129 connect( storeUR, TQT_SIGNAL( urlSelected( const TQString& ) ), 00130 this, TQT_SLOT( slotURLSelected( const TQString& ) ) ); 00131 00132 const KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" ); 00133 caEmailED->setText( config.readEntry( "CAEmailAddress" ) ); 00134 00135 connect( this, TQT_SIGNAL( helpClicked() ), 00136 this, TQT_SLOT( slotHelpClicked() ) ); 00137 connect( insertAddressButton, TQT_SIGNAL( clicked() ), 00138 this, TQT_SLOT( slotSetValuesFromWhoAmI() ) ); 00139 00140 for ( unsigned int i = 0 ; i < numKeyLengths ; ++i ) 00141 keyLengthCB->insertItem( i18n("%n bit", "%n bits", keyLengths[i] ) ); 00142 } 00143 00144 static bool requirementsAreMet( const CertificateWizardImpl::AttrPairList & list ) { 00145 for ( CertificateWizardImpl::AttrPairList::const_iterator it = list.begin() ; 00146 it != list.end() ; ++it ) { 00147 const TQLineEdit * le = (*it).second; 00148 if ( !le ) 00149 continue; 00150 const TQString key = (*it).first; 00151 #ifndef NDEBUG 00152 kdbgstream s = kdDebug(); 00153 #else 00154 kndbgstream s = kdDebug(); 00155 #endif 00156 s << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\": "; 00157 if ( key.endsWith("!") && le->text().stripWhiteSpace().isEmpty() ) { 00158 s << "required field is empty!" << endl; 00159 return false; 00160 } 00161 s << "ok" << endl; 00162 } 00163 return true; 00164 } 00165 00166 /* 00167 This slot is called when the user changes the text. 00168 */ 00169 void CertificateWizardImpl::slotEnablePersonalDataPageExit() { 00170 setNextEnabled( personalDataPage, requirementsAreMet( _attrPairList ) ); 00171 } 00172 00173 00174 /* 00175 * Destroys the object and frees any allocated resources 00176 */ 00177 CertificateWizardImpl::~CertificateWizardImpl() 00178 { 00179 // no need to delete child widgets, TQt does it all for us 00180 } 00181 00182 static const char * oidForAttributeName( const TQString & attr ) { 00183 TQCString attrUtf8 = attr.utf8(); 00184 for ( unsigned int i = 0 ; i < numOidMaps ; ++i ) 00185 if ( qstricmp( attrUtf8, oidmap[i].name ) == 0 ) 00186 return oidmap[i].oid; 00187 return 0; 00188 } 00189 00190 /* 00191 * protected slot 00192 */ 00193 void CertificateWizardImpl::slotGenerateCertificate() 00194 { 00195 // Ask gpgme to generate a key and return it 00196 TQString certParms; 00197 certParms += "<GnupgKeyParms format=\"internal\">\n"; 00198 certParms += "Key-Type: RSA\n"; 00199 certParms += TQString( "Key-Length: %1\n" ).arg( keyLengths[keyLengthCB->currentItem()] ); 00200 certParms += "Key-Usage: "; 00201 if ( signOnlyCB->isChecked() ) 00202 certParms += "Sign"; 00203 else if ( encryptOnlyCB->isChecked() ) 00204 certParms += "Encrypt"; 00205 else 00206 certParms += "Sign, Encrypt"; 00207 certParms += "\n"; 00208 certParms += "name-dn: "; 00209 00210 TQString email; 00211 TQStringList rdns; 00212 for( AttrPairList::const_iterator it = _attrPairList.begin(); it != _attrPairList.end(); ++it ) { 00213 const TQString attr = attributeFromKey( (*it).first.upper() ); 00214 const TQLineEdit * le = (*it).second; 00215 if ( !le ) 00216 continue; 00217 00218 const TQString value = le->text().stripWhiteSpace(); 00219 if ( value.isEmpty() ) 00220 continue; 00221 00222 if ( attr == "EMAIL" ) { 00223 // EMAIL is special, since it shouldn't be part of the DN, 00224 // except for non-RFC-conformant CAs that require it to be 00225 // there. 00226 email = value; 00227 if ( !brokenCA->isChecked() ) 00228 continue; 00229 } 00230 00231 if ( const char * oid = oidForAttributeName( attr ) ) { 00232 // we need to translate the attribute name for the backend: 00233 rdns.push_back( TQString::fromUtf8( oid ) + '=' + Kleo::DN::escape( value ) ); 00234 } else { 00235 rdns.push_back( attr + '=' + Kleo::DN::escape( value ) ); 00236 } 00237 } 00238 certParms += rdns.join(","); 00239 if( !email.isEmpty() ) 00240 certParms += "\nname-email: " + email; 00241 certParms += "\n</GnupgKeyParms>\n"; 00242 00243 kdDebug() << certParms << endl; 00244 00245 Kleo::KeyGenerationJob * job = 00246 Kleo::CryptoBackendFactory::instance()->smime()->keyGenerationJob(); 00247 assert( job ); 00248 00249 connect( job, TQT_SIGNAL(result(const GpgME::KeyGenerationResult&,const TQByteArray&)), 00250 TQT_SLOT(slotResult(const GpgME::KeyGenerationResult&,const TQByteArray&)) ); 00251 00252 certificateTE->setText( certParms ); 00253 00254 const GpgME::Error err = job->start( certParms ); 00255 if ( err ) 00256 KMessageBox::error( this, 00257 i18n( "Could not start certificate generation: %1" ) 00258 .arg( TQString::fromLocal8Bit( err.asString() ) ), 00259 i18n( "Certificate Manager Error" ) ); 00260 else { 00261 generatePB->setEnabled( false ); 00262 setBackEnabled( generatePage, false ); 00263 (void)new Kleo::ProgressDialog( job, i18n("Generating key"), this ); 00264 } 00265 } 00266 00267 00268 void CertificateWizardImpl::slotResult( const GpgME::KeyGenerationResult & res, 00269 const TQByteArray & keyData ) { 00270 //kdDebug() << "keyData.size(): " << keyData.size() << endl; 00271 _keyData = keyData; 00272 00273 if ( res.error().isCanceled() || res.error() ) { 00274 setNextEnabled( generatePage, false ); 00275 setBackEnabled( generatePage, true ); 00276 setFinishEnabled( finishPage, false ); 00277 generatePB->setEnabled( true ); 00278 if ( !res.error().isCanceled() ) 00279 KMessageBox::error( this, 00280 i18n( "Could not generate certificate: %1" ) 00281 .arg( TQString::fromLatin1( res.error().asString() ) ), 00282 i18n( "Certificate Manager Error" ) ); 00283 } else { 00284 // next will stay enabled until the user clicks Generate 00285 // Certificate again 00286 setNextEnabled( generatePage, true ); 00287 setFinishEnabled( finishPage, true ); 00288 } 00289 } 00290 00291 void CertificateWizardImpl::slotHelpClicked() 00292 { 00293 kapp->invokeHelp( "newcert" ); 00294 } 00295 00296 void CertificateWizardImpl::slotSetValuesFromWhoAmI() 00297 { 00298 const KABC::Addressee a = KABC::StdAddressBook::self( true )->whoAmI(); 00299 if ( a.isEmpty() ) 00300 return; 00301 const KABC::Address adr = a.address(KABC::Address::Work); 00302 00303 for ( AttrPairList::const_iterator it = _attrPairList.begin() ; 00304 it != _attrPairList.end() ; ++it ) { 00305 TQLineEdit * le = (*it).second; 00306 if ( !availForMod( le ) ) 00307 continue; 00308 00309 const TQString attr = attributeFromKey( (*it).first.upper() ); 00310 if ( attr == "CN" ) 00311 le->setText( a.formattedName() ); 00312 else if ( attr == "EMAIL" ) 00313 le->setText( a.preferredEmail() ); 00314 else if ( attr == "O" ) 00315 le->setText( a.organization() ); 00316 else if ( attr == "OU" ) 00317 le->setText( a.custom( "KADDRESSBOOK", "X-Department" ) ); 00318 else if ( attr == "L" ) 00319 le->setText( adr.locality() ); 00320 else if ( attr == "SP" ) 00321 le->setText( adr.region() ); 00322 else if ( attr == "PC" ) 00323 le->setText( adr.postalCode() ); 00324 else if ( attr == "SN" ) 00325 le->setText( a.familyName() ); 00326 else if ( attr == "GN" ) 00327 le->setText( a.givenName() ); 00328 else if ( attr == "T" ) 00329 le->setText( a.title() ); 00330 else if ( attr == "BC" ) 00331 le->setText( a.role() ); // correct mapping? 00332 } 00333 } 00334 00335 void CertificateWizardImpl::createPersonalDataPage() 00336 { 00337 TQGridLayout* grid = new TQGridLayout( edContainer, 2, 1, 00338 KDialog::marginHint(), KDialog::spacingHint() ); 00339 00340 KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" ); 00341 TQStringList attrOrder = config.readListEntry( "DNAttributeOrder" ); 00342 if ( attrOrder.empty() ) 00343 attrOrder << "CN!" << "L" << "OU" << "O!" << "C!" << "EMAIL!"; 00344 int row = 0; 00345 00346 for ( TQStringList::const_iterator it = attrOrder.begin() ; it != attrOrder.end() ; ++it, ++row ) { 00347 const TQString key = (*it).stripWhiteSpace().upper(); 00348 const TQString attr = attributeFromKey( key ); 00349 if ( attr.isEmpty() ) { 00350 --row; 00351 continue; 00352 } 00353 const TQString preset = config.readEntry( attr ); 00354 const TQString label = config.readEntry( attr + "_label", 00355 attributeLabel( attr, key.endsWith("!") ) ); 00356 00357 TQLineEdit * le = new TQLineEdit( edContainer ); 00358 grid->addWidget( le, row, 1 ); 00359 grid->addWidget( new TQLabel( le, label.isEmpty() ? attr : label, edContainer ), row, 0 ); 00360 00361 le->setText( preset ); 00362 if ( config.entryIsImmutable( attr ) ) 00363 le->setEnabled( false ); 00364 00365 _attrPairList.append(tqMakePair(key, le)); 00366 00367 connect( le, TQT_SIGNAL(textChanged(const TQString&)), 00368 TQT_SLOT(slotEnablePersonalDataPageExit()) ); 00369 } 00370 00371 // enable button only if administrator wants to allow it 00372 if (KABC::StdAddressBook::self( true )->whoAmI().isEmpty() || 00373 !config.readBoolEntry("ShowSetWhoAmI", true)) 00374 insertAddressButton->setEnabled( false ); 00375 00376 slotEnablePersonalDataPageExit(); 00377 } 00378 00379 bool CertificateWizardImpl::sendToCA() const { 00380 return sendToCARB->isChecked(); 00381 } 00382 00383 TQString CertificateWizardImpl::caEMailAddress() const { 00384 return caEmailED->text().stripWhiteSpace(); 00385 } 00386 00387 void CertificateWizardImpl::slotURLSelected( const TQString& _url ) 00388 { 00389 KURL url = KURL::fromPathOrURL( _url.stripWhiteSpace() ); 00390 #if ! KDE_IS_VERSION(3,2,90) 00391 // The application/pkcs10 mimetype didn't have a native extension, 00392 // so the filedialog didn't have the checkbox for auto-adding it. 00393 TQString fileName = url.fileName(); 00394 int pos = fileName.findRev( '.' ); 00395 if ( pos < 0 ) // no extension 00396 url.setFileName( fileName + ".p10" ); 00397 #endif 00398 storeUR->setURL( url.prettyURL() ); 00399 } 00400 00401 KURL CertificateWizardImpl::saveFileUrl() const { 00402 return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() ); 00403 } 00404 00405 void CertificateWizardImpl::showPage( TQWidget * page ) 00406 { 00407 CertificateWizard::showPage( page ); 00408 if ( page == generatePage ) { 00409 // Initial settings for the generation page: focus the correct lineedit 00410 // and disable the other one 00411 if ( storeInFileRB->isChecked() ) { 00412 storeUR->setEnabled( true ); 00413 caEmailED->setEnabled( false ); 00414 storeUR->setFocus(); 00415 } else { 00416 storeUR->setEnabled( false ); 00417 caEmailED->setEnabled( true ); 00418 caEmailED->setFocus(); 00419 } 00420 } 00421 } 00422 00423 static const char* const dcopObjectId = "KMailIface"; 00427 void CertificateWizardImpl::sendCertificate( const TQString& email, const TQByteArray& certificateData ) 00428 { 00429 TQString error; 00430 TQCString dcopService; 00431 int result = KDCOPServiceStarter::self()-> 00432 findServiceFor( "DCOP/Mailer", TQString(), 00433 TQString(), &error, &dcopService ); 00434 if ( result != 0 ) { 00435 kdDebug() << "Couldn't connect to KMail\n"; 00436 KMessageBox::error( this, 00437 i18n( "DCOP Communication Error, unable to send certificate using KMail.\n%1" ).arg( error ) ); 00438 return; 00439 } 00440 00441 TQCString dummy; 00442 // OK, so kmail (or kontact) is running. Now ensure the object we want is available. 00443 // [that's not the case when kontact was already running, but kmail not loaded into it... in theory.] 00444 if ( !kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) ) { 00445 DCOPRef ref( dcopService, dcopService ); // talk to the KUniqueApplication or its kontact wrapper 00446 DCOPReply reply = ref.call( "load()" ); 00447 if ( reply.isValid() && (bool)reply ) { 00448 Q_ASSERT( kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", TQByteArray(), dummy, dummy ) ); 00449 } else 00450 kdWarning() << "Error loading " << dcopService << endl; 00451 } 00452 00453 DCOPClient* dcopClient = kapp->dcopClient(); 00454 TQByteArray data; 00455 TQDataStream arg( data, IO_WriteOnly ); 00456 arg << email; 00457 arg << certificateData; 00458 if( !dcopClient->send( dcopService, dcopObjectId, 00459 "sendCertificate(TQString,TQByteArray)", data ) ) { 00460 KMessageBox::error( this, 00461 i18n( "DCOP Communication Error, unable to send certificate using KMail." ) ); 00462 return; 00463 } 00464 // All good, close dialog 00465 CertificateWizard::accept(); 00466 } 00467 00468 // Called when pressing Finish 00469 // We want to do the emailing/uploading first, before closing the dialog, 00470 // in case of errors during the upload. 00471 void CertificateWizardImpl::accept() 00472 { 00473 if( sendToCA() ) { 00474 // Ask KMail to send this key to the CA. 00475 sendCertificate( caEMailAddress(), _keyData ); 00476 } else { 00477 // Save in file/URL 00478 KURL url = saveFileUrl(); 00479 bool overwrite = false; 00480 if ( KIO::NetAccess::exists( url, false /*dest*/, this ) ) { 00481 if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( 00482 this, 00483 i18n( "A file named \"%1\" already exists. " 00484 "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ), 00485 i18n( "Overwrite File?" ), 00486 i18n( "&Overwrite" ) ) ) 00487 return; 00488 overwrite = true; 00489 } 00490 00491 KIO::Job* uploadJob = KIOext::put( _keyData, url, -1, overwrite, false /*resume*/ ); 00492 uploadJob->setWindow( this ); 00493 connect( uploadJob, TQT_SIGNAL( result( KIO::Job* ) ), 00494 this, TQT_SLOT( slotUploadResult( KIO::Job* ) ) ); 00495 // Can't press finish again during the upload 00496 setFinishEnabled( finishPage, false ); 00497 } 00498 } 00499 00504 void CertificateWizardImpl::slotUploadResult( KIO::Job* job ) 00505 { 00506 if ( job->error() ) { 00507 job->showErrorDialog(); 00508 setFinishEnabled( finishPage, true ); 00509 } else { 00510 // All good, close dialog 00511 CertificateWizard::accept(); 00512 } 00513 } 00514 00515 #include "certificatewizardimpl.moc"