/** * cryptplugwrapper.cpp * * Copyright (c) 2001 Karl-Heinz Zimmer, Klaraelvdalens Datakonsult AB * * This CRYPTPLUG wrapper implementation is based on cryptplug.h by * Karl-Heinz Zimmer which is based on 'The Aegypten Plugin API' as * specified by Matthias Kalle Dalheimer, Klaraelvdalens Datakonsult AB, * see file mua-integration.sgml located on Aegypten CVS: * http://www.gnupg.org/aegypten/development.en.html * * purpose: Wrap up all Aegypten Plugin API functions in one C++ class * for usage by KDE programs, e.g. KMail (or KMime, resp.) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "cryptplugwrapper.h" #include "cryptplug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qgpgme #include // gpgme++ #include #include #include // kde #include #include #include #include #include // other #include #include #include #include /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This file's source comments - as well as those in interface file * * cryptplugwrapper.h - are optimized for processing by Doxygen. * * * * To obtain best results please get an updated version of Doxygen, * * for sources and binaries goto http://www.doxygen.org/index.html * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \file cryptplugwrapper.cpp \brief C++ wrapper for the CRYPTPLUG library API. This CRYPTPLUG wrapper implementation is based on cryptplug.h by Karl-Heinz Zimmer which is based on 'The Aegypten Plugin API' as specified by Matthias Kalle Dalheimer, Klaraelvdalens Datakonsult AB, see file mua-integration.sgml located on Aegypten CVS: http://www.gnupg.org/aegypten/development.en.html purpose: Wrap up all Aegypten Plugin API functions in one C++ class for usage by KDE programs, e.g. KMail (or KMime, resp.) CRYPTPLUG is an independent cryptography plug-in API developed for Sphinx-enabeling KMail and Mutt. CRYPTPLUG was designed for the Aegypten project, but it may be used by 3rd party developers as well to design pluggable crypto backends for the above mentioned MUAs. \note All string parameters appearing in this API are to be interpreted as UTF-8 encoded. \see cryptplugwrapper.h */ // a little helper class for reordering of DN attributes class DNBeautifier { public: enum UnknownAttrsHandling { unknownAttrsHide, unknownAttrsPrefix, unknownAttrsPostfix, unknownAttrsInfix }; // infix: at the position of "_X_", if any, else Postfix DNBeautifier() { // the attrOrder is defaulted to an empty string automatically _unknownAttrsHandling = unknownAttrsInfix; _unknownAttrsHandlingChar = "INFIX"; } DNBeautifier( KConfig* config, const TQString& cfgGroup, const TQString& cfgAttributeOrderEntry, const TQString& cfgUnknownAttrsEntry, const TQStringList& fallbackAttrOrder = TQStringList(), UnknownAttrsHandling fallbackUnknowAttrsHandling = unknownAttrsInfix ) { _unknownAttrsHandling = unknownAttrsInfix; _unknownAttrsHandlingChar = "INFIX"; if( config ){ const TQString oldGroup( config->group() ); config->setGroup( cfgGroup ); // e.g. "General" _attrOrder = config->readListEntry( cfgAttributeOrderEntry ); // e.g. "DNAttributeOrder" _unknownAttrsHandlingChar = config->readEntry( cfgUnknownAttrsEntry ).upper().latin1(); // e.g. "DNUnknownAttributes" config->setGroup( oldGroup ); if( _unknownAttrsHandlingChar == "HIDE" ) _unknownAttrsHandling = unknownAttrsHide; else if( _unknownAttrsHandlingChar == "PREFIX" ) _unknownAttrsHandling = unknownAttrsPrefix; else if( _unknownAttrsHandlingChar == "POSTFIX" ) _unknownAttrsHandling = unknownAttrsPostfix; else if( _unknownAttrsHandlingChar == "INFIX" ) _unknownAttrsHandling = unknownAttrsInfix; else _unknownAttrsHandlingChar = "INFIX"; } if( _attrOrder.isEmpty() && ! fallbackAttrOrder.isEmpty() ) _attrOrder = fallbackAttrOrder; if( _attrOrder.isEmpty() ){ _attrOrderChar = 0; }else{ _attrOrderChar = new char*[ _attrOrder.count()+1 ]; int i=0; for( TQStringList::ConstIterator itOrder = _attrOrder.begin(); itOrder != _attrOrder.end(); ++itOrder ){ _attrOrderChar[ i ] = (char*)malloc( ((*itOrder).length()+1)*sizeof(char) ); strcpy( _attrOrderChar[ i ], (*itOrder).latin1() ); ++i; } _attrOrderChar[ i ] = NULL; } } ~DNBeautifier() { int i=0; for( TQStringList::ConstIterator itOrder = _attrOrder.begin(); itOrder != _attrOrder.end(); ++itOrder ){ free( _attrOrderChar[ i ] ); ++i; } delete[] _attrOrderChar; } TQStringList attrOrder() const { return _attrOrder; } char** attrOrderChar() { return _attrOrderChar; } UnknownAttrsHandling unknownAttrsHandling() const { return _unknownAttrsHandling; } const char* unknownAttrsHandlingChar() const { return _unknownAttrsHandlingChar; } TQValueList< TQPair > reorder( const TQValueList< TQPair > & dn ) const { return reorder( dn, _attrOrder, _unknownAttrsHandling ); } static TQValueList< TQPair > reorder( const TQValueList< TQPair > & dn, TQStringList attrOrder, UnknownAttrsHandling unknownAttrsHandling ) { if( !attrOrder.isEmpty() ){ TQPtrList< TQPair > unknownEntries; TQValueList< TQPair > dnNew; TQPair* unknownEntry; TQStringList::ConstIterator itOrder; TQValueList< TQPair >::ConstIterator itDN; bool bFound; if( unknownAttrsHandling != unknownAttrsHide ){ // find all unknown entries in their order of appearance for( itDN = dn.begin(); itDN != dn.end(); ++itDN ){ bFound = false; for( itOrder = attrOrder.begin(); itOrder != attrOrder.end(); ++itOrder ){ if( (*itOrder) == (*itDN).first ){ bFound = true; break; } } if( !bFound ) unknownEntries.append( &(*itDN) ); } } // prepend the unknown attrs (if desired) if( unknownAttrsHandling == unknownAttrsPrefix ){ for( unknownEntry = unknownEntries.first(); unknownEntry; unknownEntry = unknownEntries.next() ){ dnNew << *unknownEntry; } } // process the known attrs in the desired order bool b_X_declared = false; for( itOrder = attrOrder.begin(); itOrder != attrOrder.end(); ++itOrder ){ if( (*itOrder) == "_X_" ){ b_X_declared = true; // insert the unknown attrs (if desired) if( unknownAttrsHandling == unknownAttrsInfix ){ for( unknownEntry = unknownEntries.first(); unknownEntry; unknownEntry = unknownEntries.next() ){ dnNew << *unknownEntry; } } }else{ for( itDN = dn.begin(); itDN != dn.end(); ++itDN ){ if( (*itOrder) == (*itDN).first ){ dnNew << *itDN; //kdDebug(5150) << TQString((*itDN).first) <<" = " << TQString((*itDN).second) << endl;; } } } } // append the unknown attrs (if desired) if( unknownAttrsHandling == unknownAttrsPostfix || ( unknownAttrsHandling == unknownAttrsInfix && ! b_X_declared ) ){ for( unknownEntry = unknownEntries.first(); unknownEntry; unknownEntry = unknownEntries.next() ){ dnNew << *unknownEntry; } } return dnNew; } return dn; } private: TQStringList _attrOrder; char** _attrOrderChar; UnknownAttrsHandling _unknownAttrsHandling; TQCString _unknownAttrsHandlingChar; }; /* special helper class to be used by signing/encrypting functions *******/ StructuringInfoWrapper::StructuringInfoWrapper( CryptPlugWrapper* wrapper ) : _initDone( false ), _wrapper( wrapper ) { initMe(); } StructuringInfoWrapper::~StructuringInfoWrapper() { freeMe(); } void StructuringInfoWrapper::reset() { freeMe(); initMe(); } void StructuringInfoWrapper::initMe() { if ( _wrapper && _wrapper->cryptPlug() ) { _wrapper->cryptPlug()->init_StructuringInfo( &data ); _initDone = true; } } void StructuringInfoWrapper::freeMe() { if( _wrapper && _wrapper->cryptPlug() && _initDone ) { _wrapper->cryptPlug()->free_StructuringInfo( &data ); _initDone = false; } } class CryptPlugWrapper::Config { public: Config( gpgme_protocol_t proto ); ~Config(); const char* signatureKeyCertificate; SignatureAlgorithm signatureAlgorithm; SignatureCompoundMode signatureCompoundMode; SendCertificates sendCertificates; bool saveSentSignatures; bool warnNoCertificate; bool signatureUseCRLs; EncryptionAlgorithm encryptionAlgorithm; EncryptEmail encryptEmail; bool saveMessagesEncrypted; bool encryptionUseCRLs; bool encryptionCRLExpiryNearWarning; int encryptionCRLNearExpiryInterval; CertificateSource certificateSource; bool warnSendUnsigned; bool signatureCertificateExpiryNearWarning; int signatureCertificateExpiryNearInterval; bool cACertificateExpiryNearWarning; int cACertificateExpiryNearInterval; bool rootCertificateExpiryNearWarning; int rootCertificateExpiryNearInterval; bool warnSendUnencrypted; bool checkCertificatePath; bool receiverCertificateExpiryNearWarning; int receiverCertificateExpiryNearWarningInterval; bool certificateInChainExpiryNearWarning; int certificateInChainExpiryNearWarningInterval; bool receiverEmailAddressNotInCertificateWarning; const char* libVersion; /* a statically allocated string with the GPGME Version used */ }; static const int NEAR_EXPIRY = 14; CryptPlugWrapper::Config::Config( gpgme_protocol_t proto ) { signatureAlgorithm = SignAlg_SHA1; if ( proto == GPGME_PROTOCOL_CMS ) signatureCompoundMode = SignatureCompoundMode_Opaque; else signatureCompoundMode = SignatureCompoundMode_Detached; sendCertificates = SendCert_SendChainWithRoot; saveSentSignatures = true; warnNoCertificate = true; signatureUseCRLs = true; encryptionAlgorithm = EncryptAlg_RSA; encryptEmail = EncryptEmail_Ask; saveMessagesEncrypted = true; encryptionUseCRLs = true; encryptionCRLExpiryNearWarning = false; encryptionCRLNearExpiryInterval = NEAR_EXPIRY; certificateSource = CertSrc_Server; warnSendUnsigned = true; signatureCertificateExpiryNearWarning = true; signatureCertificateExpiryNearInterval = NEAR_EXPIRY; cACertificateExpiryNearWarning = true; cACertificateExpiryNearInterval = NEAR_EXPIRY; rootCertificateExpiryNearWarning = true; rootCertificateExpiryNearInterval = NEAR_EXPIRY; warnSendUnencrypted = false; checkCertificatePath = true; receiverCertificateExpiryNearWarning = true; receiverCertificateExpiryNearWarningInterval = NEAR_EXPIRY; certificateInChainExpiryNearWarning = true; certificateInChainExpiryNearWarningInterval = NEAR_EXPIRY; receiverEmailAddressNotInCertificateWarning = true; libVersion = gpgme_check_version (NULL); } CryptPlugWrapper::Config::~Config() { } /* Some multi purpose functions ******************************************/ TQString CryptPlugWrapper::errorIdToText( int errId, bool & isPassphraseError ) { const GpgME::Error err( errId ); isPassphraseError = err.isCanceled() || gpgme_err_code( errId ) == GPG_ERR_NO_SECKEY ; // FIXME: more? return TQString::fromLocal8Bit( err.asString() ); } /* some special functions ************************************************/ CryptPlugWrapper::CryptPlugWrapper( const TQString& name, const TQString& libName, const TQString& update, bool active ) : Kleo::CryptoBackend::Protocol(), _name( name ), _libName( libName ), _updateURL( update ), _active( active ), _iniStatus( IniStatus_undef ), _cp( 0 ), _config( 0 ), _cryptoConfig( 0 ) { const bool ok = initialize( 0, 0 ); assert( ok ); } CryptPlugWrapper::~CryptPlugWrapper() { deinitialize(); } void CryptPlugWrapper::setActive( bool active ) { _active = active; } bool CryptPlugWrapper::active() const { return _active; } bool CryptPlugWrapper::setLibName( const TQString& libName ) { bool bOk = ! _cp; // Changing the lib name is only allowed if( bOk ) // when either no initialization took _libName = libName; // place or 'deinitialize()' has been return bOk; // called afterwards. } TQString CryptPlugWrapper::libName() const { return _libName; } TQString CryptPlugWrapper::protocol() const { if ( _libName.contains( "smime" ) ) return "SMIME"; if ( _libName.contains( "openpgp" ) ) return "OpenPGP"; return TQString(); } void CryptPlugWrapper::setDisplayName( const TQString& name ) { _name = name; } TQString CryptPlugWrapper::displayName() const { if ( !_name.isEmpty() ) return _name; if ( _libName.contains( "smime" ) ) return "gpgsm"; if ( _libName.contains( "openpgp" ) ) return "gpg"; return i18n("(Unknown Protocol)"); } bool CryptPlugWrapper::initialize( IniStatus* iniStatus, TQString* errorMsg ) { if ( _cp ) return true; _iniStatus = IniStatus_undef; /* make sure we have a lib name */ if ( _libName.isEmpty() ) { _iniStatus = IniStatus_NoLibName; kdDebug(5150) << "No library name was given.\n" << endl; } else { if ( _libName.contains( "smime" ) ) { _cp = new SMIMECryptPlug(); _config = new Config( GPGME_PROTOCOL_CMS ); } else if ( _libName.contains( "openpgp" ) ) { _cp = new OpenPGPCryptPlug(); _config = new Config( GPGME_PROTOCOL_OpenPGP ); } else { _cp = 0; _config = 0; } if ( !_cp ) { _iniStatus = IniStatus_LoadError; kdDebug(5150) << "Couldn't create '" << _libName.latin1() << "'" << endl; } else { /* now call the init function */ if( !_cp->initialize() ) { _iniStatus = IniStatus_InitError; kdDebug(5150) << "Error while executing function 'initialize' on plugin " << _libName << endl; _lastError = i18n("Error while initializing plugin \"%1\"").arg( _libName ); if ( errorMsg ) *errorMsg = _lastError; delete _cp; _cp = 0; delete _config; _config = 0; } else { _iniStatus = IniStatus_Ok; } } } if( iniStatus ) *iniStatus = _iniStatus; return _iniStatus == IniStatus_Ok; } void CryptPlugWrapper::deinitialize() { delete _cp; _cp = 0; delete _config; _config = 0; delete _cryptoConfig; _cryptoConfig = 0; } CryptPlugWrapper::IniStatus CryptPlugWrapper::iniStatus( TQString* errorMsg ) const { if( errorMsg ) *errorMsg = _lastError; return _iniStatus; } bool CryptPlugWrapper::hasFeature( Feature flag ) { return _cp && _cp->hasFeature( flag ); } /* normal functions ******************************************************/ bool CryptPlugWrapper::checkMessageSignature( char** cleartext, const char* signaturetext, bool signatureIsBinary, int signatureLen, CryptPlug::SignatureMetaData* sigmeta ) { DNBeautifier dnBeautifier( kapp->config(), "DN", "AttributeOrder", "UnknownAttributes" ); return _cp && _cp->checkMessageSignature( cleartext, signaturetext, signatureIsBinary, signatureLen, sigmeta, dnBeautifier.attrOrderChar(), dnBeautifier.unknownAttrsHandlingChar() ); } bool CryptPlugWrapper::decryptMessage( const char* ciphertext, bool cipherIsBinary, int cipherLen, char** cleartext, const char* certificate, int* errId, char** errTxt ) { return _cp && _cp->decryptMessage( ciphertext, cipherIsBinary, cipherLen, (const char**)cleartext, certificate, errId, errTxt ); } bool CryptPlugWrapper::decryptAndCheckMessage( const char* ciphertext, bool cipherIsBinary, int cipherLen, char** cleartext, const char* certificate, bool* signatureFound, CryptPlug::SignatureMetaData* sigmeta, int* errId, char** errTxt ) { DNBeautifier dnBeautifier( kapp->config(), "DN", "AttributeOrder", "UnknownAttributes" ); return _cp && _cp->decryptAndCheckMessage( ciphertext, cipherIsBinary, cipherLen, (const char**)cleartext, certificate, signatureFound, sigmeta, errId, errTxt, dnBeautifier.attrOrderChar(), dnBeautifier.unknownAttrsHandlingChar() ); } void CryptPlugWrapper::freeSignatureMetaData( CryptPlug::SignatureMetaData* sigmeta ) { if ( !sigmeta ) return; free( sigmeta->status ); for( int i = 0; i < sigmeta->extended_info_count; ++i ) { free( sigmeta->extended_info[i].creation_time ); free( (void*)sigmeta->extended_info[i].status_text ); free( (void*)sigmeta->extended_info[i].keyid ); free( (void*)sigmeta->extended_info[i].fingerprint ); free( (void*)sigmeta->extended_info[i].algo ); free( (void*)sigmeta->extended_info[i].userid ); free( (void*)sigmeta->extended_info[i].name ); free( (void*)sigmeta->extended_info[i].comment ); if( sigmeta->extended_info[i].emailCount ){ for( int j=0; j < sigmeta->extended_info[i].emailCount; ++j) if( sigmeta->extended_info[i].emailList[j] ) free( (void*)sigmeta->extended_info[i].emailList[j] ); free( (void*)sigmeta->extended_info[i].emailList ); } } free( sigmeta->extended_info ); } GpgME::ImportResult CryptPlugWrapper::importCertificate( const char* data, size_t length ) { if ( !_cp ) return GpgME::ImportResult(); return _cp->importCertificateFromMem( data, length ); } Kleo::KeyListJob * CryptPlugWrapper::keyListJob( bool remote, bool includeSigs, bool validate ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; unsigned int mode = context->keyListMode(); if ( remote ) { mode |= GpgME::Context::Extern; mode &= ~GpgME::Context::Local; } else { mode |= GpgME::Context::Local; mode &= ~GpgME::Context::Extern; } if ( includeSigs ) mode |= GpgME::Context::Signatures; if ( validate ) mode |= GpgME::Context::Validate; context->setKeyListMode( mode ); return new Kleo::QGpgMEKeyListJob( context ); } Kleo::EncryptJob * CryptPlugWrapper::encryptJob( bool armor, bool textmode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setArmor( armor ); context->setTextMode( textmode ); return new Kleo::QGpgMEEncryptJob( context ); } Kleo::DecryptJob * CryptPlugWrapper::decryptJob() const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; return new Kleo::QGpgMEDecryptJob( context ); } Kleo::SignJob * CryptPlugWrapper::signJob( bool armor, bool textMode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setArmor( armor ); context->setTextMode( textMode ); return new Kleo::QGpgMESignJob( context ); } Kleo::VerifyDetachedJob * CryptPlugWrapper::verifyDetachedJob( bool textMode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setTextMode( textMode ); return new Kleo::QGpgMEVerifyDetachedJob( context ); } Kleo::VerifyOpaqueJob * CryptPlugWrapper::verifyOpaqueJob( bool textMode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setTextMode( textMode ); return new Kleo::QGpgMEVerifyOpaqueJob( context ); } Kleo::KeyGenerationJob * CryptPlugWrapper::keyGenerationJob() const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; return new Kleo::QGpgMEKeyGenerationJob( context ); } Kleo::ImportJob * CryptPlugWrapper::importJob() const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; return new Kleo::QGpgMEImportJob( context ); } Kleo::ExportJob * CryptPlugWrapper::publicKeyExportJob( bool armor ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setArmor( armor ); return new Kleo::QGpgMEExportJob( context ); } Kleo::ExportJob * CryptPlugWrapper::secretKeyExportJob( bool armor, const TQString& charset ) const { if ( !_cp || _cp->mProtocol != GpgME::Context::CMS ) // fixme: add support for gpg, too return 0; // this operation is not supported by gpgme, so we have to call gpgsm ourselves: return new Kleo::QGpgMESecretKeyExportJob( armor, charset ); } Kleo::RefreshKeysJob * CryptPlugWrapper::refreshKeysJob() const { if ( !_cp || _cp->mProtocol != GpgME::Context::CMS ) // fixme: add support for gpg, too return 0; // this operation is not supported by gpgme, so we have to call gpgsm ourselves: return new Kleo::QGpgMERefreshKeysJob(); } Kleo::DownloadJob * CryptPlugWrapper::downloadJob( bool armor ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setArmor( armor ); // this is the hackish interface for downloading from keyserers currently: context->setKeyListMode( GpgME::Context::Extern ); return new Kleo::QGpgMEDownloadJob( context ); } Kleo::DeleteJob * CryptPlugWrapper::deleteJob() const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; return new Kleo::QGpgMEDeleteJob( context ); } Kleo::SignEncryptJob * CryptPlugWrapper::signEncryptJob( bool armor, bool textMode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setArmor( armor ); context->setTextMode( textMode ); return new Kleo::QGpgMESignEncryptJob( context ); } Kleo::DecryptVerifyJob * CryptPlugWrapper::decryptVerifyJob( bool textMode ) const { if ( !_cp ) return 0; GpgME::Context * context = GpgME::Context::createForProtocol( _cp->mProtocol ); if ( !context ) return 0; context->setTextMode( textMode ); return new Kleo::QGpgMEDecryptVerifyJob( context ); }