certmanager

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 Qt library by Trolltech AS, Norway (or with modified versions
00024     of Qt that use the same license as Qt), 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     Qt.  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 // Qt
00067 #include <qlineedit.h>
00068 #include <qtextedit.h>
00069 #include <qpushbutton.h>
00070 #include <qcheckbox.h>
00071 #include <qradiobutton.h>
00072 #include <qlayout.h>
00073 #include <qlabel.h>
00074 #include <qcombobox.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 #define DEFAULT_KEY_LENGTH 2048
00083 
00084 static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths;
00085 
00086 static QString attributeLabel( const QString & attr, bool required ) {
00087   if ( attr.isEmpty() )
00088     return QString::null;
00089   const QString label = Kleo::DNAttributeMapper::instance()->name2label( attr );
00090   if ( !label.isEmpty() )
00091     if ( required )
00092       return i18n("Format string for the labels in the \"Your Personal Data\" page - required field",
00093           "*%1 (%2):").arg( label, attr );
00094     else
00095       return i18n("Format string for the labels in the \"Your Personal Data\" page",
00096           "%1 (%2):").arg( label, attr );
00097 
00098   else if ( required )
00099     return '*' + attr + ':';
00100   else
00101     return attr + ':';
00102 }
00103 
00104 static QString attributeFromKey( QString key ) {
00105   return key.remove( '!' );
00106 }
00107 
00108 static bool availForMod( const QLineEdit * le ) {
00109   return le && le->isEnabled();
00110 }
00111 
00112 /*
00113  *  Constructs a CertificateWizardImpl which is a child of 'parent', with the
00114  *  name 'name' and widget flags set to 'f'
00115  *
00116  *  The wizard will by default be modeless, unless you set 'modal' to
00117  *  TRUE to construct a modal wizard.
00118  */
00119 CertificateWizardImpl::CertificateWizardImpl( QWidget* parent,  const char* name, bool modal, WFlags fl )
00120     : CertificateWizard( parent, name, modal, fl )
00121 {
00122     // don't allow to go to last page until a key has been generated
00123     setNextEnabled( generatePage, false );
00124     // setNextEnabled( personalDataPage, false ); // ## disable again once we have a criteria when to enable again
00125 
00126     createPersonalDataPage();
00127 
00128     // Allow to select remote URLs
00129     storeUR->setMode( KFile::File );
00130     storeUR->setFilter( "application/pkcs10" );
00131     connect( storeUR, SIGNAL( urlSelected( const QString& ) ),
00132              this, SLOT( slotURLSelected( const QString& ) ) );
00133 
00134     const KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" );
00135     caEmailED->setText( config.readEntry( "CAEmailAddress" ) );
00136 
00137     connect( this, SIGNAL( helpClicked() ),
00138          this, SLOT( slotHelpClicked() ) );
00139     connect( insertAddressButton, SIGNAL( clicked() ),
00140          this, SLOT( slotSetValuesFromWhoAmI() ) );
00141 
00142     for ( unsigned int i = 0 ; i < numKeyLengths ; ++i ) {
00143       keyLengthCB->insertItem( i18n("%n bit", "%n bits", keyLengths[i] ) );
00144       if ( keyLengths[i] == DEFAULT_KEY_LENGTH ) {
00145         keyLengthCB->setCurrentItem( i );
00146       }
00147     }
00148 }
00149 
00150 static bool requirementsAreMet( const CertificateWizardImpl::AttrPairList & list ) {
00151   for ( CertificateWizardImpl::AttrPairList::const_iterator it = list.begin() ;
00152     it != list.end() ; ++it ) {
00153     const QLineEdit * le = (*it).second;
00154     if ( !le )
00155       continue;
00156     const QString key = (*it).first;
00157 #ifndef NDEBUG
00158     kdbgstream s = kdDebug();
00159 #else
00160     kndbgstream s = kdDebug();
00161 #endif
00162     s << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\": ";
00163     if ( key.endsWith("!") && le->text().stripWhiteSpace().isEmpty() ) {
00164       s << "required field is empty!" << endl;
00165       return false;
00166     }
00167     s << "ok" << endl;
00168   }
00169   return true;
00170 }
00171 
00172 /*
00173   This slot is called when the user changes the text.
00174  */
00175 void CertificateWizardImpl::slotEnablePersonalDataPageExit() {
00176   setNextEnabled( personalDataPage, requirementsAreMet( _attrPairList ) );
00177 }
00178 
00179 
00180 /*
00181  *  Destroys the object and frees any allocated resources
00182  */
00183 CertificateWizardImpl::~CertificateWizardImpl()
00184 {
00185     // no need to delete child widgets, Qt does it all for us
00186 }
00187 
00188 static const char * oidForAttributeName( const QString & attr ) {
00189   QCString attrUtf8 = attr.utf8();
00190   for ( unsigned int i = 0 ; i < numOidMaps ; ++i )
00191     if ( qstricmp( attrUtf8, oidmap[i].name ) == 0 )
00192       return oidmap[i].oid;
00193   return 0;
00194 }
00195 
00196 /*
00197  * protected slot
00198  */
00199 void CertificateWizardImpl::slotGenerateCertificate()
00200 {
00201     // Ask gpgme to generate a key and return it
00202     QString certParms;
00203     certParms += "<GnupgKeyParms format=\"internal\">\n";
00204     certParms += "Key-Type: RSA\n";
00205     certParms += QString( "Key-Length: %1\n" ).arg( keyLengths[keyLengthCB->currentItem()] );
00206     certParms += "Key-Usage: ";
00207     if ( signOnlyCB->isChecked() )
00208       certParms += "Sign";
00209     else if ( encryptOnlyCB->isChecked() )
00210       certParms += "Encrypt";
00211     else
00212       certParms += "Sign, Encrypt";
00213     certParms += "\n";
00214     certParms += "name-dn: ";
00215 
00216     QString email;
00217     QStringList rdns;
00218     for( AttrPairList::const_iterator it = _attrPairList.begin(); it != _attrPairList.end(); ++it ) {
00219       const QString attr = attributeFromKey( (*it).first.upper() );
00220       const QLineEdit * le = (*it).second;
00221       if ( !le )
00222         continue;
00223 
00224       const QString value = le->text().stripWhiteSpace();
00225       if ( value.isEmpty() )
00226         continue;
00227 
00228       if ( attr == "EMAIL" ) {
00229         // EMAIL is special, since it shouldn't be part of the DN,
00230         // except for non-RFC-conformant CAs that require it to be
00231         // there.
00232         email = value;
00233         if ( !brokenCA->isChecked() )
00234           continue;
00235       }
00236 
00237       if ( const char * oid = oidForAttributeName( attr ) ) {
00238         // we need to translate the attribute name for the backend:
00239               rdns.push_back( QString::fromUtf8( oid ) + '=' + Kleo::DN::escape( value ) );
00240       } else {
00241               rdns.push_back( attr + '=' + Kleo::DN::escape( value ) );
00242       }
00243     }
00244     certParms += rdns.join(",");
00245     if( !email.isEmpty() )
00246       certParms += "\nname-email: " + email;
00247     certParms += "\n</GnupgKeyParms>\n";
00248 
00249     kdDebug() << certParms << endl;
00250 
00251     Kleo::KeyGenerationJob * job =
00252       Kleo::CryptoBackendFactory::instance()->smime()->keyGenerationJob();
00253     assert( job );
00254 
00255     connect( job, SIGNAL(result(const GpgME::KeyGenerationResult&,const QByteArray&)),
00256          SLOT(slotResult(const GpgME::KeyGenerationResult&,const QByteArray&)) );
00257 
00258     certificateTE->setText( certParms );
00259 
00260     const GpgME::Error err = job->start( certParms );
00261     if ( err )
00262       KMessageBox::error( this,
00263               i18n( "Could not start certificate generation: %1" )
00264               .arg( QString::fromLocal8Bit( err.asString() ) ),
00265               i18n( "Certificate Manager Error" ) );
00266     else {
00267       generatePB->setEnabled( false );
00268       setBackEnabled( generatePage, false );
00269       (void)new Kleo::ProgressDialog( job, i18n("Generating key"), this );
00270     }
00271 }
00272 
00273 
00274 void CertificateWizardImpl::slotResult( const GpgME::KeyGenerationResult & res,
00275                     const QByteArray & keyData ) {
00276     //kdDebug() << "keyData.size(): " << keyData.size() << endl;
00277     _keyData = keyData;
00278 
00279     if ( res.error().isCanceled() || res.error() ) {
00280           setNextEnabled( generatePage, false );
00281       setBackEnabled( generatePage, true );
00282           setFinishEnabled( finishPage, false );
00283       generatePB->setEnabled( true );
00284       if ( !res.error().isCanceled() )
00285         KMessageBox::error( this,
00286                 i18n( "Could not generate certificate: %1" )
00287                 .arg( QString::fromLatin1( res.error().asString() ) ),
00288                 i18n( "Certificate Manager Error" ) );
00289     } else {
00290         // next will stay enabled until the user clicks Generate
00291         // Certificate again
00292         setNextEnabled( generatePage, true );
00293         setFinishEnabled( finishPage, true );
00294     }
00295 }
00296 
00297 void CertificateWizardImpl::slotHelpClicked()
00298 {
00299   kapp->invokeHelp( "newcert" );
00300 }
00301 
00302 void CertificateWizardImpl::slotSetValuesFromWhoAmI()
00303 {
00304   const KABC::Addressee a = KABC::StdAddressBook::self( true )->whoAmI();
00305   if ( a.isEmpty() )
00306     return;
00307   const KABC::Address adr = a.address(KABC::Address::Work);
00308 
00309   for ( AttrPairList::const_iterator it = _attrPairList.begin() ;
00310     it != _attrPairList.end() ; ++it ) {
00311     QLineEdit * le = (*it).second;
00312     if ( !availForMod( le ) )
00313       continue;
00314 
00315     const QString attr = attributeFromKey( (*it).first.upper() );
00316     if ( attr == "CN" )
00317       le->setText( a.formattedName() );
00318     else if ( attr == "EMAIL" )
00319       le->setText( a.preferredEmail() );
00320     else if ( attr == "O" )
00321       le->setText( a.organization() );
00322     else if ( attr == "OU" )
00323       le->setText( a.custom( "KADDRESSBOOK", "X-Department" ) );
00324     else if ( attr == "L" )
00325       le->setText( adr.locality() );
00326     else if ( attr == "SP" )
00327       le->setText( adr.region() );
00328     else if ( attr == "PC" )
00329       le->setText( adr.postalCode() );
00330     else if ( attr == "SN" )
00331       le->setText( a.familyName() );
00332     else if ( attr == "GN" )
00333       le->setText( a.givenName() );
00334     else if ( attr == "T" )
00335       le->setText( a.title() );
00336     else if ( attr == "BC" )
00337       le->setText( a.role() ); // correct mapping?
00338   }
00339 }
00340 
00341 void CertificateWizardImpl::createPersonalDataPage()
00342 {
00343   QGridLayout* grid = new QGridLayout( edContainer, 2, 1,
00344                        KDialog::marginHint(), KDialog::spacingHint() );
00345 
00346   KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" );
00347   QStringList attrOrder = config.readListEntry( "DNAttributeOrder" );
00348   if ( attrOrder.empty() )
00349     attrOrder << "CN!" << "L" << "OU" << "O!" << "C!" << "EMAIL!";
00350   int row = 0;
00351 
00352   for ( QStringList::const_iterator it = attrOrder.begin() ; it != attrOrder.end() ; ++it, ++row ) {
00353     const QString key = (*it).stripWhiteSpace().upper();
00354     const QString attr = attributeFromKey( key );
00355     if ( attr.isEmpty() ) {
00356       --row;
00357       continue;
00358     }
00359     const QString preset = config.readEntry( attr );
00360     const QString label = config.readEntry( attr + "_label",
00361                         attributeLabel( attr, key.endsWith("!") ) );
00362 
00363     QLineEdit * le = new QLineEdit( edContainer );
00364     grid->addWidget( le, row, 1 );
00365     grid->addWidget( new QLabel( le, label.isEmpty() ? attr : label, edContainer ), row, 0 );
00366 
00367     le->setText( preset );
00368     if ( config.entryIsImmutable( attr ) )
00369       le->setEnabled( false );
00370 
00371     _attrPairList.append(qMakePair(key, le));
00372 
00373     connect( le, SIGNAL(textChanged(const QString&)),
00374          SLOT(slotEnablePersonalDataPageExit()) );
00375   }
00376 
00377   // enable button only if administrator wants to allow it
00378   if (KABC::StdAddressBook::self( true )->whoAmI().isEmpty() ||
00379       !config.readBoolEntry("ShowSetWhoAmI", true))
00380     insertAddressButton->setEnabled( false );
00381 
00382   slotEnablePersonalDataPageExit();
00383 }
00384 
00385 bool CertificateWizardImpl::sendToCA() const {
00386   return sendToCARB->isChecked();
00387 }
00388 
00389 QString CertificateWizardImpl::caEMailAddress() const {
00390   return caEmailED->text().stripWhiteSpace();
00391 }
00392 
00393 void CertificateWizardImpl::slotURLSelected( const QString& _url )
00394 {
00395   KURL url = KURL::fromPathOrURL( _url.stripWhiteSpace() );
00396 #if ! KDE_IS_VERSION(3,2,90)
00397   // The application/pkcs10 mimetype didn't have a native extension,
00398   // so the filedialog didn't have the checkbox for auto-adding it.
00399   QString fileName = url.fileName();
00400   int pos = fileName.findRev( '.' );
00401   if ( pos < 0 ) // no extension
00402     url.setFileName( fileName + ".p10" );
00403 #endif
00404   storeUR->setURL( url.prettyURL() );
00405 }
00406 
00407 KURL CertificateWizardImpl::saveFileUrl() const {
00408   return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() );
00409 }
00410 
00411 void CertificateWizardImpl::showPage( QWidget * page )
00412 {
00413   CertificateWizard::showPage( page );
00414   if ( page == generatePage ) {
00415     // Initial settings for the generation page: focus the correct lineedit
00416     // and disable the other one
00417     if ( storeInFileRB->isChecked() ) {
00418       storeUR->setEnabled( true );
00419       caEmailED->setEnabled( false );
00420       storeUR->setFocus();
00421     } else {
00422       storeUR->setEnabled( false );
00423       caEmailED->setEnabled( true );
00424       caEmailED->setFocus();
00425     }
00426   }
00427 }
00428 
00429 static const char* const dcopObjectId = "KMailIface";
00433 void CertificateWizardImpl::sendCertificate( const QString& email, const QByteArray& certificateData )
00434 {
00435   QString error;
00436   QCString dcopService;
00437   int result = KDCOPServiceStarter::self()->
00438     findServiceFor( "DCOP/Mailer", QString::null,
00439                     QString::null, &error, &dcopService );
00440   if ( result != 0 ) {
00441     kdDebug() << "Couldn't connect to KMail\n";
00442     KMessageBox::error( this,
00443                         i18n( "DCOP Communication Error, unable to send certificate using KMail.\n%1" ).arg( error ) );
00444     return;
00445   }
00446 
00447   QCString dummy;
00448   // OK, so kmail (or kontact) is running. Now ensure the object we want is available.
00449   // [that's not the case when kontact was already running, but kmail not loaded into it... in theory.]
00450   if ( !kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ) {
00451     DCOPRef ref( dcopService, dcopService ); // talk to the KUniqueApplication or its kontact wrapper
00452     DCOPReply reply = ref.call( "load()" );
00453     if ( reply.isValid() && (bool)reply ) {
00454       Q_ASSERT( kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) );
00455     } else
00456       kdWarning() << "Error loading " << dcopService << endl;
00457   }
00458 
00459   DCOPClient* dcopClient = kapp->dcopClient();
00460   QByteArray data;
00461   QDataStream arg( data, IO_WriteOnly );
00462   arg << email;
00463   arg << certificateData;
00464   if( !dcopClient->send( dcopService, dcopObjectId,
00465                          "sendCertificate(QString,QByteArray)", data ) ) {
00466     KMessageBox::error( this,
00467                         i18n( "DCOP Communication Error, unable to send certificate using KMail." ) );
00468     return;
00469   }
00470   // All good, close dialog
00471   CertificateWizard::accept();
00472 }
00473 
00474 // Called when pressing Finish
00475 // We want to do the emailing/uploading first, before closing the dialog,
00476 // in case of errors during the upload.
00477 void CertificateWizardImpl::accept()
00478 {
00479   if( sendToCA() ) {
00480     // Ask KMail to send this key to the CA.
00481     sendCertificate( caEMailAddress(), _keyData );
00482   } else {
00483     // Save in file/URL
00484     KURL url = saveFileUrl();
00485     bool overwrite = false;
00486     if ( KIO::NetAccess::exists( url, false /*dest*/, this ) ) {
00487       if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
00488                                                                      this,
00489                                                                      i18n( "A file named \"%1\" already exists. "
00490                                                                            "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
00491                                                                      i18n( "Overwrite File?" ),
00492                                                                      i18n( "&Overwrite" ) ) )
00493         return;
00494       overwrite = true;
00495     }
00496 
00497     KIO::Job* uploadJob = KIOext::put( _keyData, url, -1, overwrite, false /*resume*/ );
00498     uploadJob->setWindow( this );
00499     connect( uploadJob, SIGNAL( result( KIO::Job* ) ),
00500              this, SLOT( slotUploadResult( KIO::Job* ) ) );
00501     // Can't press finish again during the upload
00502     setFinishEnabled( finishPage, false );
00503   }
00504 }
00505 
00510 void CertificateWizardImpl::slotUploadResult( KIO::Job* job )
00511 {
00512   if ( job->error() ) {
00513     job->showErrorDialog();
00514     setFinishEnabled( finishPage, true );
00515   } else {
00516     // All good, close dialog
00517     CertificateWizard::accept();
00518   }
00519 }
00520 
00521 #include "certificatewizardimpl.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys