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 static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths;
00083 
00084 static QString attributeLabel( const QString & attr, bool required ) {
00085   if ( attr.isEmpty() )
00086     return QString::null;
00087   const QString 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 QString attributeFromKey( QString key ) {
00103   return key.remove( '!' );
00104 }
00105 
00106 static bool availForMod( const QLineEdit * 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( QWidget* 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, SIGNAL( urlSelected( const QString& ) ),
00130              this, SLOT( slotURLSelected( const QString& ) ) );
00131 
00132     const KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" );
00133     caEmailED->setText( config.readEntry( "CAEmailAddress" ) );
00134 
00135     connect( this, SIGNAL( helpClicked() ),
00136          this, SLOT( slotHelpClicked() ) );
00137     connect( insertAddressButton, SIGNAL( clicked() ),
00138          this, 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 QLineEdit * le = (*it).second;
00148     if ( !le )
00149       continue;
00150     const QString 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, Qt does it all for us
00180 }
00181 
00182 static const char * oidForAttributeName( const QString & attr ) {
00183   QCString 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     QString certParms;
00197     certParms += "<GnupgKeyParms format=\"internal\">\n";
00198     certParms += "Key-Type: RSA\n";
00199     certParms += QString( "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     QString email;
00211     QStringList rdns;
00212     for( AttrPairList::const_iterator it = _attrPairList.begin(); it != _attrPairList.end(); ++it ) {
00213       const QString attr = attributeFromKey( (*it).first.upper() );
00214       const QLineEdit * le = (*it).second;
00215       if ( !le )
00216         continue;
00217 
00218       const QString 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( QString::fromUtf8( oid ) + '=' + value );
00234       } else {
00235         rdns.push_back( attr + '=' + 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, SIGNAL(result(const GpgME::KeyGenerationResult&,const QByteArray&)),
00250          SLOT(slotResult(const GpgME::KeyGenerationResult&,const QByteArray&)) );
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( QString::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 QByteArray & 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( QString::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     QLineEdit * le = (*it).second;
00306     if ( !availForMod( le ) )
00307       continue;
00308 
00309     const QString 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   QGridLayout* grid = new QGridLayout( edContainer, 2, 1,
00338                        KDialog::marginHint(), KDialog::spacingHint() );
00339 
00340   KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" );
00341   QStringList attrOrder = config.readListEntry( "DNAttributeOrder" );
00342   if ( attrOrder.empty() )
00343     attrOrder << "CN!" << "L" << "OU" << "O!" << "C!" << "EMAIL!";
00344   int row = 0;
00345 
00346   for ( QStringList::const_iterator it = attrOrder.begin() ; it != attrOrder.end() ; ++it, ++row ) {
00347     const QString key = (*it).stripWhiteSpace().upper();
00348     const QString attr = attributeFromKey( key );
00349     if ( attr.isEmpty() ) {
00350       --row;
00351       continue;
00352     }
00353     const QString preset = config.readEntry( attr );
00354     const QString label = config.readEntry( attr + "_label",
00355                         attributeLabel( attr, key.endsWith("!") ) );
00356 
00357     QLineEdit * le = new QLineEdit( edContainer );
00358     grid->addWidget( le, row, 1 );
00359     grid->addWidget( new QLabel( 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(qMakePair(key, le));
00366 
00367     connect( le, SIGNAL(textChanged(const QString&)),
00368          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 QString CertificateWizardImpl::caEMailAddress() const {
00384   return caEmailED->text().stripWhiteSpace();
00385 }
00386 
00387 void CertificateWizardImpl::slotURLSelected( const QString& _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   QString 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( QWidget * 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 QString& email, const QByteArray& certificateData )
00428 {
00429   QString error;
00430   QCString dcopService;
00431   int result = KDCOPServiceStarter::self()->
00432     findServiceFor( "DCOP/Mailer", QString::null,
00433                     QString::null, &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   QCString 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, "", QByteArray(), 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, "", QByteArray(), dummy, dummy ) );
00449     } else
00450       kdWarning() << "Error loading " << dcopService << endl;
00451   }
00452 
00453   DCOPClient* dcopClient = kapp->dcopClient();
00454   QByteArray data;
00455   QDataStream arg( data, IO_WriteOnly );
00456   arg << email;
00457   arg << certificateData;
00458   if( !dcopClient->send( dcopService, dcopObjectId,
00459                          "sendCertificate(QString,QByteArray)", 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, SIGNAL( result( KIO::Job* ) ),
00494              this, 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"
KDE Home | KDE Accessibility Home | Description of Access Keys