1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 #include <zypp/KeyManager.h>
10 #include <zypp/KeyRing.h>
11 #include <zypp/PathInfo.h>
12 #include <zypp/base/Logger.h>
13 #include <zypp/TmpPath.h>
14 #include <zypp/base/String.h>
15 #include <zypp/AutoDispose.h>
17 #include <boost/thread/once.hpp>
18 #include <boost/interprocess/smart_ptr/scoped_ptr.hpp>
24 #undef ZYPP_BASE_LOGGER_LOGGROUP
25 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::gpg"
27 ///////////////////////////////////////////////////////////////////
30 ///////////////////////////////////////////////////////////////////
34 // make sure to call the init code of gpgme only once
35 // this might need to be moved to a different location when
36 // threads are introduced into libzypp
37 boost::once_flag gpgme_init_once = BOOST_ONCE_INIT;
41 const char *version = gpgme_check_version(NULL);
44 MIL << "Initialized libgpgme version: " << version << endl;
48 MIL << "Initialized libgpgme with unknown version" << endl;
52 //using boost::interprocess pointer because it allows a custom deleter
53 typedef boost::interprocess::scoped_ptr<gpgme_data, boost::function<void (gpgme_data_t)>> GpgmeDataPtr;
54 typedef boost::interprocess::scoped_ptr<_gpgme_key, boost::function<void (gpgme_key_t)>> GpgmeKeyPtr;
55 typedef boost::interprocess::scoped_ptr<FILE, boost::function<int (FILE *)>> FILEPtr;
59 GpgmeErr( gpgme_error_t err_r = GPG_ERR_NO_ERROR )
62 operator gpgme_error_t() const { return _err; }
67 std::ostream & operator<<( std::ostream & str, const GpgmeErr & obj )
68 { return str << "<" << gpgme_strsource(obj) << "> " << gpgme_strerror(obj); }
70 /** \relates gpgme_import_result_t Stream output. */
71 std::ostream & operator<<( std::ostream & str, const _gpgme_op_import_result & obj )
73 str << "gpgme_op_import_result {" << endl;
74 str << " " << obj.considered << " The total number of considered keys." << endl;
75 str << " " << obj.no_user_id << " The number of keys without user ID." << endl;
76 str << " " << obj.imported << " The total number of imported keys." << endl;
77 str << " " << obj.imported_rsa << " imported RSA keys." << endl;
78 str << " " << obj.unchanged << " unchanged keys." << endl;
79 str << " " << obj.new_user_ids << " new user IDs." << endl;
80 str << " " << obj.new_sub_keys << " new sub keys." << endl;
81 str << " " << obj.new_signatures << " new signatures." << endl;
82 str << " " << obj.new_revocations << " new revocations." << endl;
83 str << " " << obj.secret_read << " secret keys read." << endl;
84 str << " " << obj.secret_imported << " imported secret keys." << endl;
85 str << " " << obj.secret_unchanged << " unchanged secret keys." << endl;
86 str << " " << obj.not_imported << " keys not imported." << endl;
87 for ( gpgme_import_status_t p = obj.imports; p; p = p->next )
89 str << " - " << p->fpr << ": " << p->result << endl;
91 // In V.1.11: str << " " << obj.skipped_v3_keys << " skipped v3 keys." << endl;
95 ///////////////////////////////////////////////////////////////////
97 struct GpgmeException : public KeyRingException
99 GpgmeException( const std::string & in_r, const GpgmeErr & err_r )
100 : KeyRingException( str::Format( "libgpgme error in '%1%': %2%" ) % in_r % err_r )
104 class KeyManagerCtx::Impl
108 { boost::call_once( gpgme_init_once, initGpgme ); }
111 { if ( _ctx ) gpgme_release( _ctx ); }
114 /** Return all fingerprints found in \a signature_r. */
115 std::list<std::string> readSignaturesFprs( const Pathname & signature_r )
116 { return readSignaturesFprsOptVerify( signature_r ); }
118 /** Tries to verify the \a file_r using \a signature_r. */
119 bool verifySignaturesFprs( const Pathname & file_r, const Pathname & signature_r )
122 readSignaturesFprsOptVerify( signature_r, file_r, &verify );
126 gpgme_ctx_t _ctx { nullptr };
127 bool _volatile { false }; ///< readKeyFromFile workaround bsc#1140670
130 /** Return all fingerprints found in \a signature_r and optionally verify the \a file_r on the fly.
132 * If \a verify_r is not a \c nullptr, log verification errors and return
133 * whether all signatures are good.
135 std::list<std::string> readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
138 std::list<std::string> KeyManagerCtx::Impl::readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r, bool * verify_r )
140 //lets be pessimistic
145 if (!PathInfo( signature_r ).isExist())
146 return std::list<std::string>();
148 FILEPtr dataFile(fopen(file_r.c_str(), "rb"), fclose);
150 return std::list<std::string>();
152 GpgmeDataPtr fileData(nullptr, gpgme_data_release);
153 GpgmeErr err = gpgme_data_new_from_stream (&fileData.get(), dataFile.get());
156 return std::list<std::string>();
159 FILEPtr sigFile(fopen(signature_r.c_str(), "rb"), fclose);
161 ERR << "Unable to open signature file '" << signature_r << "'" <<endl;
162 return std::list<std::string>();
165 GpgmeDataPtr sigData(nullptr, gpgme_data_release);
166 err = gpgme_data_new_from_stream (&sigData.get(), sigFile.get());
169 return std::list<std::string>();
172 err = gpgme_op_verify(_ctx, sigData.get(), fileData.get(), NULL);
173 if (err != GPG_ERR_NO_ERROR) {
175 return std::list<std::string>();
178 gpgme_verify_result_t res = gpgme_op_verify_result(_ctx);
179 if (!res || !res->signatures) {
180 ERR << "Unable to read signature fingerprints" <<endl;
181 return std::list<std::string>();
184 bool foundBadSignature = false;
185 std::list<std::string> signatures;
186 for ( gpgme_signature_t sig = res->signatures; sig; sig = sig->next ) {
190 // bsc#1100427: With ibgpgme11-1.11.0 and if a recent gpg version was used
191 // to create the signature, the field may contain the full fingerprint, but
192 // we're expected to return the ID.
193 // [https://github.com/gpg/gpgme/commit/478d1650bbef84958ccce439fac982ef57b16cd0]
194 std::string id( sig->fpr );
195 if ( id.size() > 16 )
196 id = id.substr( id.size()-16 );
197 signatures.push_back( std::move(id) );
200 if ( sig->status != GPG_ERR_NO_ERROR )
202 if ( gpgme_err_code(sig->status) != GPG_ERR_KEY_EXPIRED )
204 if ( !foundBadSignature )
205 foundBadSignature = true;
207 WAR << "Failed signature check: " << file_r << " " << GpgmeErr(sig->status) << endl;
212 WAR << "Legacy: Ignore expired key: " << file_r << " " << GpgmeErr(sig->status) << endl;
218 *verify_r = (!foundBadSignature);
223 KeyManagerCtx::KeyManagerCtx()
227 KeyManagerCtx KeyManagerCtx::createForOpenPGP()
229 static Pathname tmppath( zypp::myTmpDir() / "PublicKey" );
230 filesystem::assert_dir( tmppath );
232 KeyManagerCtx ret { createForOpenPGP( tmppath ) };
233 ret._pimpl->_volatile = true; // readKeyFromFile workaround bsc#1140670
237 KeyManagerCtx KeyManagerCtx::createForOpenPGP( const Pathname & keyring_r )
239 DBG << "createForOpenPGP(" << keyring_r << ")" << endl;
242 gpgme_ctx_t & ctx { ret._pimpl->_ctx };
244 // create the context
245 GpgmeErr err = gpgme_new( &ctx );
246 if ( err != GPG_ERR_NO_ERROR )
247 ZYPP_THROW( GpgmeException( "gpgme_new", err ) );
250 err = gpgme_set_protocol( ctx, GPGME_PROTOCOL_OpenPGP );
251 if ( err != GPG_ERR_NO_ERROR )
252 ZYPP_THROW( GpgmeException( "gpgme_set_protocol", err ) );
254 if ( !keyring_r.empty() ) {
255 // get engine information to read current state
256 gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info( ctx );
258 ZYPP_THROW( GpgmeException( "gpgme_ctx_get_engine_info", err ) );
260 err = gpgme_ctx_set_engine_info( ctx, GPGME_PROTOCOL_OpenPGP, enginfo->file_name, keyring_r.c_str() );
261 if ( err != GPG_ERR_NO_ERROR )
262 ZYPP_THROW( GpgmeException( "gpgme_ctx_set_engine_info", err ) );
268 Pathname KeyManagerCtx::homedir() const
271 if ( gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info( _pimpl->_ctx ) )
272 ret = enginfo->home_dir;
276 std::list<PublicKeyData> KeyManagerCtx::listKeys()
278 std::list<PublicKeyData> ret;
279 GpgmeErr err = GPG_ERR_NO_ERROR;
281 // Reset gpgme_keylist_mode on return!
282 AutoDispose<gpgme_keylist_mode_t> guard { gpgme_get_keylist_mode( _pimpl->_ctx ), bind( &gpgme_set_keylist_mode, _pimpl->_ctx, _1 ) };
283 // Let listed keys include signatures (required if PublicKeyData are created from the key)
284 if ( (err = gpgme_set_keylist_mode( _pimpl->_ctx, GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_SIGS )) != GPG_ERR_NO_ERROR ) {
285 ERR << "gpgme_set_keylist_mode: " << err << endl;
289 if ( (err = gpgme_op_keylist_start( _pimpl->_ctx, NULL, 0 )) != GPG_ERR_NO_ERROR ) {
290 ERR << "gpgme_op_keylist_start: " << err << endl;
293 // Close list operation on return!
294 AutoDispose<gpgme_ctx_t> guard2 { _pimpl->_ctx, &gpgme_op_keylist_end };
296 AutoDispose<gpgme_key_t> key { nullptr, &gpgme_key_release };
297 for ( ; gpgme_op_keylist_next( _pimpl->_ctx, &(*key) ) == GPG_ERR_NO_ERROR; key.getDispose()( key ) ) {
298 PublicKeyData data { PublicKeyData::fromGpgmeKey( key ) };
300 ret.push_back( data );
307 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile( const Pathname & keyfile_r )
309 // bsc#1140670: GPGME does not support reading keys from a keyfile using
310 // gpgme_data_t and gpgme_op_keylist_from_data_start. Despite GPGME_KEYLIST_MODE_SIGS
311 // the signatures are missing, but we need them to create proper PublicKeyData objects.
312 // While this is not resolved, we read into a temp. keyring. Impl::_volatile helps
313 // to detect whether we can clear and import into the current context or need to
314 // create a temp. one.
315 std::list<PublicKeyData> ret;
317 if ( _pimpl->_volatile ) {
318 // in a volatile context we can simple clear the keyring...
319 filesystem::clean_dir( homedir() );
320 if ( importKey( keyfile_r ) )
323 // read in a volatile context
324 ret = createForOpenPGP().readKeyFromFile( keyfile_r );
330 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile( const Pathname & file_r )
332 std::list<PublicKeyData> ret;
333 GpgmeErr err = GPG_ERR_NO_ERROR;
335 AutoDispose<gpgme_data_t> data { nullptr, &gpgme_data_release };
336 if ( (err = gpgme_data_new_from_file( &(*data), file_r.c_str(), 1 )) != GPG_ERR_NO_ERROR ) {
337 ERR << "gpgme_data_new_from_file " << file_r << ": " << err << endl;
341 // Reset gpgme_keylist_mode on return!
342 AutoDispose<gpgme_keylist_mode_t> guard { gpgme_get_keylist_mode( _pimpl->_ctx ), bind( &gpgme_set_keylist_mode, _pimpl->_ctx, _1 ) };
343 // Let listed keys include signatures (required if PublicKeyData are created from the key)
344 if ( (err = gpgme_set_keylist_mode( _pimpl->_ctx, GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_SIGS )) != GPG_ERR_NO_ERROR ) {
345 ERR << "gpgme_set_keylist_mode: " << err << endl;
349 if ( (err = gpgme_op_keylist_from_data_start( _pimpl->_ctx, data, 0 )) != GPG_ERR_NO_ERROR ) {
350 ERR << "gpgme_op_keylist_from_data_start " << file_r << ": " << err << endl;
353 // Close list operation on return!
354 AutoDispose<gpgme_ctx_t> guard2 { _pimpl->_ctx, &gpgme_op_keylist_end };
356 AutoDispose<gpgme_key_t> key { nullptr, &gpgme_key_release };
357 for ( ; gpgme_op_keylist_next( _pimpl->_ctx, &(*key) ) == GPG_ERR_NO_ERROR; key.getDispose()( key ) ) {
358 PublicKeyData data { PublicKeyData::fromGpgmeKey( key ) };
360 ret.push_back( data );
367 bool KeyManagerCtx::verify(const Pathname &file, const Pathname &signature)
369 return _pimpl->verifySignaturesFprs(file, signature);
372 bool KeyManagerCtx::exportKey(const std::string &id, std::ostream &stream)
374 GpgmeErr err = GPG_ERR_NO_ERROR;
376 GpgmeKeyPtr foundKey;
378 //search for requested key id
380 gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
381 while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
382 if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
383 GpgmeKeyPtr(key, gpgme_key_release).swap(foundKey);
386 gpgme_key_release(key);
388 gpgme_op_keylist_end(_pimpl->_ctx);
391 WAR << "Key " << id << "not found" << endl;
395 //function needs a array of keys to export
396 gpgme_key_t keyarray[2];
397 keyarray[0] = foundKey.get();
400 GpgmeDataPtr out(nullptr, gpgme_data_release);
401 err = gpgme_data_new (&out.get());
407 //format as ascii armored
408 gpgme_set_armor (_pimpl->_ctx, 1);
409 err = gpgme_op_export_keys (_pimpl->_ctx, keyarray, 0, out.get());
411 int ret = gpgme_data_seek (out.get(), 0, SEEK_SET);
413 ERR << "Unable to seek in exported key data" << endl;
417 const int bufsize = 512;
418 char buf[bufsize + 1];
419 while ((ret = gpgme_data_read(out.get(), buf, bufsize)) > 0) {
420 stream.write(buf, ret);
423 //failed to read from buffer
425 ERR << "Unable to read exported key data" << endl;
429 ERR << "Error exporting key: "<< err << endl;
433 //if we reach this point export was successful
437 bool KeyManagerCtx::importKey(const Pathname &keyfile)
439 if ( !PathInfo( keyfile ).isExist() ) {
440 ERR << "Keyfile '" << keyfile << "' does not exist.";
444 GpgmeDataPtr data(nullptr, gpgme_data_release);
447 err = gpgme_data_new_from_file(&data.get(), keyfile.c_str(), 1);
449 ERR << "Error importing key: "<< err << endl;
453 err = gpgme_op_import(_pimpl->_ctx, data.get());
455 ERR << "Error importing key: "<< err << endl;
459 // Work around bsc#1127220 [libgpgme] no error upon incomplete import due to signal received.
460 // We need this error, otherwise RpmDb will report the missing keys as 'probably v3'.
461 if ( gpgme_import_result_t res = gpgme_op_import_result(_pimpl->_ctx) )
463 if ( ! res->considered && PathInfo(keyfile).size() )
466 ERR << "Error importing key: No keys considered (bsc#1127220, [libgpgme] signal received?)" << endl;
471 return (err == GPG_ERR_NO_ERROR);
474 bool KeyManagerCtx::deleteKey(const std::string &id)
477 GpgmeErr err = GPG_ERR_NO_ERROR;
479 gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
481 while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
482 if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
483 err = gpgme_op_delete(_pimpl->_ctx, key, 0);
485 gpgme_key_release(key);
486 gpgme_op_keylist_end(_pimpl->_ctx);
489 ERR << "Error deleting key: "<< err << endl;
494 gpgme_key_release(key);
497 gpgme_op_keylist_end(_pimpl->_ctx);
498 WAR << "Key: '"<< id << "' not found." << endl;
502 std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const Pathname &signature)
503 { return _pimpl->readSignaturesFprs(signature); }
506 ///////////////////////////////////////////////////////////////////