certmanager Library API Documentation

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