#include "zypp/KeyRing.h"
#include "zypp/ExternalProgram.h"
#include "zypp/TmpPath.h"
+#include "zypp/ZYppCallbacks.h" // JobReport::instance
+#include "zypp/KeyManager.h"
using std::endl;
#undef ZYPP_BASE_LOGGER_LOGGROUP
#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::KeyRing"
-/** \todo Fix duplicate define in PublicKey/KeyRing */
-#define GPG_BINARY "/usr/bin/gpg2"
-
///////////////////////////////////////////////////////////////////
namespace zypp
{ /////////////////////////////////////////////////////////////////
/// const std::list<PublicKeyData> & cachedPublicKeyData( const Pathname & keyring );
/// \endcode
///////////////////////////////////////////////////////////////////
- struct CachedPublicKeyData // : private base::NonCopyable - but KeyRing uses RWCOW though also NonCopyable :(
+ struct CachedPublicKeyData : private base::NonCopyable
{
const std::list<PublicKeyData> & operator()( const Pathname & keyring_r ) const
{ return getData( keyring_r ); }
+ void setDirty( const Pathname & keyring_r )
+ { _cacheMap[keyring_r].setDirty(); }
+
private:
struct Cache
{
- // Empty copy ctor to allow insert into std::map as
- // scoped_ptr is noncopyable.
Cache() {}
- Cache( const Cache & rhs ) {}
+
+ void setDirty()
+ {
+ _keyringK.reset();
+ _keyringP.reset();
+ }
void assertCache( const Pathname & keyring_r )
{
const std::list<PublicKeyData> & getData( const Pathname & keyring_r, Cache & cache_r ) const
{
- if ( cache_r.hasChanged() )
- {
- const char* argv[] =
- {
- GPG_BINARY,
- "--list-public-keys",
- "--homedir", keyring_r.c_str(),
- "--no-default-keyring",
- "--quiet",
- "--with-colons",
- "--fixed-list-mode",
- "--with-fingerprint",
- "--with-sig-list",
- "--no-tty",
- "--no-greeting",
- "--batch",
- "--status-fd", "1",
- NULL
- };
-
- PublicKeyScanner scanner;
- ExternalProgram prog( argv ,ExternalProgram::Discard_Stderr, false, -1, true );
- for( std::string line = prog.receiveLine(); !line.empty(); line = prog.receiveLine() )
- {
- scanner.scan( line );
- }
- prog.close();
-
- cache_r._data.swap( scanner._keys );
- MIL << "Found keys: " << cache_r._data << endl;
- }
- return cache_r._data;
+ if ( cache_r.hasChanged() ) {
+ shared_ptr<KeyManagerCtx> ctx = KeyManagerCtx::createForOpenPGP();
+ if (ctx) {
+ if (ctx->setHomedir(keyring_r)) {
+ std::list<PublicKeyData> foundKeys = ctx->listKeys();
+ cache_r._data.swap(foundKeys);
+ }
+ }
+ MIL << "Found keys: " << cache_r._data << endl;
+ }
+ return cache_r._data;
}
mutable CacheMap _cacheMap;
PublicKey exportKey( const std::string & id, const Pathname & keyring );
PublicKey exportKey( const PublicKeyData & keyData, const Pathname & keyring );
+ PublicKey exportKey( const PublicKey & key, const Pathname & keyring )
+ { return exportKey( key.keyData(), keyring ); }
void dumpPublicKey( const std::string & id, const Pathname & keyring, std::ostream & stream );
filesystem::TmpFile dumpPublicKeyToTmp( const std::string & id, const Pathname & keyring );
};
///////////////////////////////////////////////////////////////////
+ namespace
+ {
+ /// Handle signal emission from within KeyRing::Impl::importKey
+ struct ImportKeyCBHelper
+ {
+ void operator()( const PublicKey & key_r )
+ {
+ try {
+ _rpmdbEmitSignal->trustedKeyAdded( key_r );
+ _emitSignal->trustedKeyAdded( key_r );
+ }
+ catch ( const Exception & excp )
+ {
+ ERR << "Could not import key into rpmdb: " << excp << endl;
+ // TODO: JobReport as hotfix for bsc#1057188; should bubble up and go through some callback
+ JobReport::error( excp.asUserHistory() );
+ }
+ }
+
+ private:
+ callback::SendReport<target::rpm::KeyRingSignals> _rpmdbEmitSignal;
+ callback::SendReport<KeyRingSignals> _emitSignal;
+ };
+ } // namespace
+
void KeyRing::Impl::importKey( const PublicKey & key, bool trusted )
{
importKey( key.path(), trusted ? trustedKeyRing() : generalKeyRing() );
+ MIL << "Imported key " << key << " to " << (trusted ? "trustedKeyRing" : "generalKeyRing" ) << endl;
if ( trusted )
{
- callback::SendReport<target::rpm::KeyRingSignals> rpmdbEmitSignal;
- callback::SendReport<KeyRingSignals> emitSignal;
-
- rpmdbEmitSignal->trustedKeyAdded( key );
- emitSignal->trustedKeyAdded( key );
+ ImportKeyCBHelper emitSignal;
+ if ( key.hiddenKeys().empty() )
+ {
+ emitSignal( key );
+ }
+ else
+ {
+ // multiple keys: Export individual keys ascii armored to import in rpmdb
+ emitSignal( exportKey( key, trustedKeyRing() ) );
+ for ( const PublicKeyData & hkey : key.hiddenKeys() )
+ emitSignal( exportKey( hkey, trustedKeyRing() ) );
+ }
}
}
void KeyRing::Impl::deleteKey( const std::string & id, bool trusted )
{
- PublicKey key;
-
- if ( trusted )
+ PublicKeyData keyDataToDel( publicKeyExists( id, trusted ? trustedKeyRing() : generalKeyRing() ) );
+ if ( ! keyDataToDel )
{
- key = exportKey( id, trustedKeyRing() );
+ WAR << "Key to delete [" << id << "] is not in " << (trusted ? "trustedKeyRing" : "generalKeyRing" ) << endl;
+ return;
}
-
deleteKey( id, trusted ? trustedKeyRing() : generalKeyRing() );
+ MIL << "Deleted key [" << id << "] from " << (trusted ? "trustedKeyRing" : "generalKeyRing" ) << endl;
if ( trusted )
- {
- callback::SendReport<target::rpm::KeyRingSignals> rpmdbEmitSignal;
- callback::SendReport<KeyRingSignals> emitSignal;
+ try {
+ PublicKey key( keyDataToDel );
+ callback::SendReport<target::rpm::KeyRingSignals> rpmdbEmitSignal;
rpmdbEmitSignal->trustedKeyRemoved( key );
+
+ callback::SendReport<KeyRingSignals> emitSignal;
emitSignal->trustedKeyRemoved( key );
}
+ catch ( const Exception & excp )
+ {
+ ERR << "Could not delete key from rpmmdb: " << excp << endl;
+ // TODO: JobReport as hotfix for bsc#1057188; should bubble up and go through some callback
+ JobReport::error( excp.asUserHistory() );
+ }
}
PublicKeyData KeyRing::Impl::publicKeyExists( const std::string & id, const Pathname & keyring )
{
- MIL << "Searching key [" << id << "] in keyring " << keyring << endl;
- const std::list<PublicKeyData> & keys( publicKeyData( keyring ) );
- for_( it, keys.begin(), keys.end() )
+ PublicKeyData ret;
+ for ( const PublicKeyData & key : publicKeyData( keyring ) )
{
- if ( id == (*it).id() )
+ if ( key.providesKey( id ) )
{
- return *it;
+ ret = key;
+ break;
}
}
- return PublicKeyData();
+ MIL << (ret ? "Found" : "No") << " key [" << id << "] in keyring " << keyring << endl;
+ return ret;
}
PublicKey KeyRing::Impl::exportKey( const PublicKeyData & keyData, const Pathname & keyring )
return PublicKey( dumpPublicKeyToTmp( keyData.id(), keyring ), keyData );
// Here: key not found
- WAR << "No key " << id << " to export from " << keyring << endl;
+ WAR << "No key [" << id << "] to export from " << keyring << endl;
return PublicKey();
}
void KeyRing::Impl::dumpPublicKey( const std::string & id, const Pathname & keyring, std::ostream & stream )
{
- const char* argv[] =
- {
- GPG_BINARY,
- "-a",
- "--export",
- "--homedir", keyring.asString().c_str(),
- "--no-default-keyring",
- "--quiet",
- "--no-tty",
- "--no-greeting",
- "--no-permission-warning",
- "--batch",
- id.c_str(),
- NULL
- };
- ExternalProgram prog( argv,ExternalProgram::Discard_Stderr, false, -1, true );
- for ( std::string line = prog.receiveLine(); !line.empty(); line = prog.receiveLine() )
- {
- stream << line;
- }
- prog.close();
+ KeyManagerCtx::Ptr ctx = KeyManagerCtx::createForOpenPGP();
+ if (!ctx || !ctx->setHomedir(keyring))
+ return;
+ ctx->exportKey(id, stream);
}
filesystem::TmpFile KeyRing::Impl::dumpPublicKeyToTmp( const std::string & id, const Pathname & keyring )
{
filesystem::TmpFile tmpFile( _base_dir, "pubkey-"+id+"-" );
- MIL << "Going to export key " << id << " from " << keyring << " to " << tmpFile.path() << endl;
+ MIL << "Going to export key [" << id << "] from " << keyring << " to " << tmpFile.path() << endl;
std::ofstream os( tmpFile.path().c_str() );
dumpPublicKey( id, keyring, os );
if( signature.empty() || (!PathInfo( signature ).isExist()) )
{
bool res = report->askUserToAcceptUnsignedFile( filedesc, context );
- MIL << "User decision on unsigned file: " << res << endl;
+ MIL << "askUserToAcceptUnsignedFile: " << res << endl;
return res;
}
- // get the id of the signature
+ // get the id of the signature (it might be a subkey id!)
std::string id = readSignatureKeyId( signature );
- // doeskey exists in trusted keyring
+ // does key exists in trusted keyring
PublicKeyData trustedKeyData( publicKeyExists( id, trustedKeyRing() ) );
if ( trustedKeyData )
{
{
// bnc #393160: Comment #30: Compare at least the fingerprint
// in case an attacker created a key the the same id.
+ //
+ // FIXME: bsc#1008325: For keys using subkeys, we'd actually need
+ // to compare the subkey sets, to tell whether a key was updated.
+ // because created() remains unchanged if the primary key is not touched.
+ // For now we wait until a new subkey signs the data and treat it as a
+ // new key (else part below).
if ( trustedKeyData.fingerprint() == generalKeyData.fingerprint()
&& trustedKeyData.created() < generalKeyData.created() )
{
MIL << "Key was updated. Saving new version into trusted keyring: " << generalKeyData << endl;
importKey( exportKey( generalKeyData, generalKeyRing() ), true );
- trustedKeyData = generalKeyData = PublicKeyData(); // invalidated by import.
+ trustedKeyData = publicKeyExists( id, trustedKeyRing() ); // re-read: invalidated by import?
}
}
- if ( ! trustedKeyData ) // invalidated by previous import
- trustedKeyData = publicKeyExists( id, trustedKeyRing() );
+ // it exists, is trusted, does it validate?
report->infoVerify( filedesc, trustedKeyData, context );
-
- // it exists, is trusted, does it validates?
if ( verifyFile( file, signature, trustedKeyRing() ) )
{
return (sigValid_r=true); // signature is actually successfully validated!
}
else
{
- return report->askUserToAcceptVerificationFailed( filedesc, exportKey( trustedKeyData, trustedKeyRing() ), context );
+ bool res = report->askUserToAcceptVerificationFailed( filedesc, exportKey( trustedKeyData, trustedKeyRing() ), context );
+ MIL << "askUserToAcceptVerificationFailed: " << res << endl;
+ return res;
}
}
else
if ( generalKeyData )
{
PublicKey key( exportKey( generalKeyData, generalKeyRing() ) );
- MIL << "Exported key " << id << " to " << key.path() << endl;
- MIL << "Key " << id << " " << key.name() << " is not trusted" << endl;
+ MIL << "Key [" << id << "] " << key.name() << " is not trusted" << endl;
// ok the key is not trusted, ask the user to trust it or not
KeyRingReport::KeyTrust reply = report->askUserToAcceptKey( key, context );
if ( reply == KeyRingReport::KEY_TRUST_TEMPORARILY ||
reply == KeyRingReport::KEY_TRUST_AND_IMPORT )
{
- MIL << "User wants to trust key " << id << " " << key.name() << endl;
- //dumpFile( unKey.path() );
+ MIL << "User wants to trust key [" << id << "] " << key.name() << endl;
Pathname whichKeyring;
if ( reply == KeyRingReport::KEY_TRUST_AND_IMPORT )
{
- MIL << "User wants to import key " << id << " " << key.name() << endl;
+ MIL << "User wants to import key [" << id << "] " << key.name() << endl;
importKey( key, true );
whichKeyring = trustedKeyRing();
}
else
whichKeyring = generalKeyRing();
- // emit key added
+ // does it validate?
+ report->infoVerify( filedesc, generalKeyData, context );
if ( verifyFile( file, signature, whichKeyring ) )
{
- MIL << "File signature is verified" << endl;
return (sigValid_r=true); // signature is actually successfully validated!
}
else
{
- MIL << "File signature check fails" << endl;
- if ( report->askUserToAcceptVerificationFailed( filedesc, key, context ) )
- {
- MIL << "User continues anyway." << endl;
- return true;
- }
- else
- {
- MIL << "User does not want to continue" << endl;
- return false;
- }
+ bool res = report->askUserToAcceptVerificationFailed( filedesc, key, context );
+ MIL << "askUserToAcceptVerificationFailed: " << res << endl;
+ return res;
}
}
else
{
- MIL << "User does not want to trust key " << id << " " << key.name() << endl;
+ MIL << "User does not want to trust key [" << id << "] " << key.name() << endl;
return false;
}
}
else
{
- // unknown key...
+ // signed with an unknown key...
MIL << "File [" << file << "] ( " << filedesc << " ) signed with unknown key [" << id << "]" << endl;
- if ( report->askUserToAcceptUnknownKey( filedesc, id, context ) )
- {
- MIL << "User wants to accept unknown key " << id << endl;
- return true;
- }
- else
- {
- MIL << "User does not want to accept unknown key " << id << endl;
- return false;
- }
+ bool res = report->askUserToAcceptUnknownKey( filedesc, id, context );
+ MIL << "askUserToAcceptUnknownKey: " << res << endl;
+ return res;
}
}
return false;
% keyfile.asString()
% keyring.asString() ));
- const char* argv[] =
- {
- GPG_BINARY,
- "--import",
- "--homedir", keyring.asString().c_str(),
- "--no-default-keyring",
- "--quiet",
- "--no-tty",
- "--no-greeting",
- "--no-permission-warning",
- "--status-fd", "1",
- keyfile.asString().c_str(),
- NULL
- };
+ KeyManagerCtx::Ptr ctx = KeyManagerCtx::createForOpenPGP();
+ if(!ctx || !ctx->setHomedir(keyring))
+ ZYPP_THROW(KeyRingException(_("Failed to import key.")));
- ExternalProgram prog( argv,ExternalProgram::Discard_Stderr, false, -1, true );
- prog.close();
+ cachedPublicKeyData.setDirty( keyring );
+ if(!ctx->importKey(keyfile))
+ ZYPP_THROW(KeyRingException(_("Failed to import key.")));
}
void KeyRing::Impl::deleteKey( const std::string & id, const Pathname & keyring )
{
- const char* argv[] =
- {
- GPG_BINARY,
- "--delete-keys",
- "--homedir", keyring.asString().c_str(),
- "--no-default-keyring",
- "--yes",
- "--quiet",
- "--no-tty",
- "--batch",
- "--status-fd", "1",
- id.c_str(),
- NULL
- };
+ KeyManagerCtx::Ptr ctx = KeyManagerCtx::createForOpenPGP();
+ if(!ctx) {
+ ZYPP_THROW(KeyRingException(_("Failed to delete key.")));
+ }
+
+ if(!ctx->setHomedir(keyring)) {
+ ZYPP_THROW(KeyRingException(_("Failed to delete key.")));
+ }
- ExternalProgram prog( argv,ExternalProgram::Discard_Stderr, false, -1, true );
+ if(!ctx->deleteKey(id)){
+ ZYPP_THROW(KeyRingException(_("Failed to delete key.")));
+ }
- int code = prog.close();
- if ( code )
- ZYPP_THROW(Exception(_("Failed to delete key.")));
- else
- MIL << "Deleted key " << id << " from keyring " << keyring << endl;
+ cachedPublicKeyData.setDirty( keyring );
}
-
std::string KeyRing::Impl::readSignatureKeyId( const Pathname & signature )
{
if ( ! PathInfo( signature ).isFile() )
- ZYPP_THROW(Exception( str::Format(_("Signature file %s not found")) % signature.asString() ));
+ ZYPP_THROW(KeyRingException( str::Format(_("Signature file %s not found")) % signature.asString() ));
- MIL << "Determining key id if signature " << signature << endl;
- // HACK create a tmp keyring with no keys
- filesystem::TmpDir dir( _base_dir, "fake-keyring" );
- std::string tmppath( dir.path().asString() );
+ MIL << "Determining key id of signature " << signature << endl;
- const char* argv[] =
- {
- GPG_BINARY,
- "--homedir", tmppath.c_str(),
- "--no-default-keyring",
- "--quiet",
- "--no-tty",
- "--no-greeting",
- "--batch",
- "--status-fd", "1",
- signature.asString().c_str(),
- NULL
- };
-
- ExternalProgram prog( argv,ExternalProgram::Discard_Stderr, false, -1, true );
-
- std::string line;
- int count = 0;
-
- str::regex rxNoKey( "^\\[GNUPG:\\] NO_PUBKEY (.+)\n$" );
- std::string id;
- for( line = prog.receiveLine(), count=0; !line.empty(); line = prog.receiveLine(), count++ )
- {
- //MIL << "[" << line << "]" << endl;
- str::smatch what;
- if( str::regex_match( line, what, rxNoKey ) )
- {
- if ( what.size() >= 1 )
- {
- id = what[1];
- break;
- }
- //dumpRegexpResults( what );
- }
+ KeyManagerCtx::Ptr ctx = KeyManagerCtx::createForOpenPGP();
+ if(!ctx) {
+ return std::string();
}
- if ( count == 0 )
- {
- MIL << "no output" << endl;
+ std::list<std::string> fprs = ctx->readSignatureFingerprints(signature);
+ if (fprs.size()) {
+ std::string &id = fprs.back();
+ MIL << "Determined key id [" << id << "] for signature " << signature << endl;
+ return id;
}
-
- MIL << "Determined key id [" << id << "] for signature " << signature << endl;
- prog.close();
- return id;
+ return std::string();
}
bool KeyRing::Impl::verifyFile( const Pathname & file, const Pathname & signature, const Pathname & keyring )
{
- const char* argv[] =
- {
- GPG_BINARY,
- "--verify",
- "--homedir", keyring.asString().c_str(),
- "--no-default-keyring",
- "--quiet",
- "--no-tty",
- "--batch",
- "--no-greeting",
- "--status-fd", "1",
- signature.asString().c_str(),
- file.asString().c_str(),
- NULL
- };
-
- // no need to parse output for now
- // [GNUPG:] SIG_ID yCc4u223XRJnLnVAIllvYbUd8mQ 2006-03-29 1143618744
- // [GNUPG:] GOODSIG A84EDAE89C800ACA SuSE Package Signing Key <build@suse.de>
- // gpg: Good signature from "SuSE Package Signing Key <build@suse.de>"
- // [GNUPG:] VALIDSIG 79C179B2E1C820C1890F9994A84EDAE89C800ACA 2006-03-29 1143618744 0 3 0 17 2 00 79C179B2E1C820C1890F9994A84EDAE89C800ACA
- // [GNUPG:] TRUST_UNDEFINED
-
- // [GNUPG:] ERRSIG A84EDAE89C800ACA 17 2 00 1143618744 9
- // [GNUPG:] NO_PUBKEY A84EDAE89C800ACA
-
- ExternalProgram prog( argv,ExternalProgram::Discard_Stderr, false, -1, true );
+ KeyManagerCtx::Ptr ctx = KeyManagerCtx::createForOpenPGP();
+ if (!ctx || !ctx->setHomedir(keyring))
+ return false;
- return ( prog.close() == 0 ) ? true : false;
+ return ctx->verify(file, signature);
}
///////////////////////////////////////////////////////////////////