libzypp 17.35.9
KeyManager.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
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>
16
17#include <boost/thread/once.hpp>
18#include <boost/interprocess/smart_ptr/scoped_ptr.hpp>
19#include <gpgme.h>
20
21#include <stdio.h>
22using std::endl;
23
24#undef ZYPP_BASE_LOGGER_LOGGROUP
25#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::gpg"
26
28namespace zypp
29{
31 namespace
32 {
33 // @TODO [threading]
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;
38
39 void initGpgme ()
40 {
41 const char *version = gpgme_check_version(NULL);
42 if ( version )
43 {
44 MIL << "Initialized libgpgme version: " << version << endl;
45 }
46 else
47 {
48 MIL << "Initialized libgpgme with unknown version" << endl;
49 }
50 }
51
52 //using boost::interprocess pointer because it allows a custom deleter
53 using GpgmeDataPtr = boost::interprocess::scoped_ptr<gpgme_data, boost::function<void (gpgme_data_t)>>;
54 using GpgmeKeyPtr = boost::interprocess::scoped_ptr<_gpgme_key, boost::function<void (gpgme_key_t)>>;
55 using FILEPtr = boost::interprocess::scoped_ptr<FILE, boost::function<int (FILE *)>>;
56
57 struct GpgmeErr
58 {
59 GpgmeErr( gpgme_error_t err_r = GPG_ERR_NO_ERROR )
60 : _err( err_r )
61 {}
62 operator gpgme_error_t() const { return _err; }
63 private:
64 gpgme_error_t _err;
65 };
66
67 std::ostream & operator<<( std::ostream & str, const GpgmeErr & obj )
68 { return str << "<" << gpgme_strsource(obj) << "> " << gpgme_strerror(obj); }
69
71 std::ostream & operator<<( std::ostream & str, const _gpgme_op_import_result & obj )
72 {
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 )
88 {
89 str << " - " << p->fpr << ": " << p->result << endl;
90 }
91 // In V.1.11: str << " " << obj.skipped_v3_keys << " skipped v3 keys." << endl;
92 return str << "}";
93 }
94 } // namespace
96
98 {
99 GpgmeException( const std::string & in_r, const GpgmeErr & err_r )
100 : KeyRingException( str::Format( "libgpgme error in '%1%': %2%" ) % in_r % err_r )
101 {}
102 };
103
105 {
106 public:
108 { boost::call_once( gpgme_init_once, initGpgme ); }
109
110 Impl(const Impl &) = delete;
111 Impl(Impl &&) = delete;
112 Impl &operator=(const Impl &) = delete;
113 Impl &operator=(Impl &&) = delete;
114
116 if (_ctx)
118 }
119
121 std::list<std::string> readSignaturesFprs( const Pathname & signature_r )
123
125 std::list<std::string> readSignaturesFprs( const ByteArray & signature_r )
127
130 {
131 bool verify = false;
133 return verify;
134 }
135
136 template< typename Callback >
137 bool importKey(GpgmeDataPtr &data, Callback &&calcDataSize );
138
139 gpgme_ctx_t _ctx { nullptr };
140 bool _volatile { false };
141
142 private:
148 std::list<std::string> readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
149 std::list<std::string> readSignaturesFprsOptVerify( const ByteArray& keyData_r, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
150 std::list<std::string> readSignaturesFprsOptVerify( GpgmeDataPtr &sigData, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
151 };
152
154{
155 //lets be pessimistic
156 if ( verify_r )
157 *verify_r = false;
158
159 if (!PathInfo( signature_r ).isExist())
160 return std::list<std::string>();
161
162 FILEPtr sigFile(fopen(signature_r.c_str(), "rb"), fclose);
163 if (!sigFile) {
164 ERR << "Unable to open signature file '" << signature_r << "'" <<endl;
165 return std::list<std::string>();
166 }
167
169 GpgmeErr err = gpgme_data_new_from_stream (&sigData.get(), sigFile.get());
170 if (err) {
171 ERR << err << endl;
172 return std::list<std::string>();
173 }
174
176}
177
179{
180 //lets be pessimistic
181 if ( verify_r )
182 *verify_r = false;
183
185 GpgmeErr err = gpgme_data_new_from_mem(&sigData.get(), keyData_r.data(), keyData_r.size(), 1 );
186 if (err) {
187 ERR << err << endl;
188 return std::list<std::string>();
189 }
190
191 return readSignaturesFprsOptVerify( sigData, file_r, verify_r );
192}
193
195{
196 //lets be pessimistic
197 if ( verify_r )
198 *verify_r = false;
199
200 FILEPtr dataFile(fopen(file_r.c_str(), "rb"), fclose);
201 if (!dataFile)
202 return std::list<std::string>();
203
205 GpgmeErr err = gpgme_data_new_from_stream (&fileData.get(), dataFile.get());
206 if (err) {
207 ERR << err << endl;
208 return std::list<std::string>();
209 }
210
211 err = gpgme_op_verify(_ctx, sigData.get(), fileData.get(), NULL);
212 if (err != GPG_ERR_NO_ERROR) {
213 ERR << err << endl;
214 return std::list<std::string>();
215 }
216
218 if (!res || !res->signatures) {
219 ERR << "Unable to read signature fingerprints" <<endl;
220 return std::list<std::string>();
221 }
222
223 bool foundBadSignature = false;
224 bool foundGoodSignature = false;
225 std::list<std::string> signatures;
226 for ( gpgme_signature_t sig = res->signatures; sig; sig = sig->next ) {
227
228 if ( sig->fpr )
229 {
230 // bsc#1100427: With libgpgme11-1.11.0 and if a recent gpg version was used
231 // to create the signature, the field may contain the full fingerprint, but
232 // we're expected to return the ID.
233 // [https://github.com/gpg/gpgme/commit/478d1650bbef84958ccce439fac982ef57b16cd0]
234 std::string id( sig->fpr );
235 if ( id.size() > 16 )
236 id = id.substr( id.size()-16 );
237
238 DBG << "Found signature with ID: " << id << " in " << file_r << std::endl;
239 signatures.push_back( std::move(id) );
240 }
241
242 if ( verify_r && sig->status != GPG_ERR_NO_ERROR ) {
243 const auto status = gpgme_err_code(sig->status);
244
245 // bsc#1180721: libgpgme started to return signatures of unknown keys, which breaks
246 // our workflow when verifying files that have multiple signatures, including some that are
247 // not in the trusted keyring. We should not fail if we have unknown or expired keys and at least a good one.
248 // We will however keep the behaviour of failing if we find a bad signatures even if others are good.
249 if ( status != GPG_ERR_KEY_EXPIRED && status != GPG_ERR_NO_PUBKEY )
250 {
251 WAR << "Failed signature check: " << file_r << " " << GpgmeErr(sig->status) << endl;
252 if ( !foundBadSignature )
253 foundBadSignature = true;
254 }
255 else
256 {
257 WAR << "Legacy: Ignore expired or unknown key: " << file_r << " " << GpgmeErr(sig->status) << endl;
258 // for now treat expired keys as good signature
259 if ( status == GPG_ERR_KEY_EXPIRED )
260 foundGoodSignature = true;
261 }
262 } else {
263 foundGoodSignature = true;
264 }
265 }
266
267 if ( verify_r )
269 return signatures;
270}
271
275
277{
278 static Pathname tmppath( zypp::myTmpDir() / "PublicKey" );
280
282 ret._pimpl->_volatile = true; // readKeyFromFile workaround bsc#1140670
283 return ret;
284}
285
287{
288 DBG << "createForOpenPGP(" << keyring_r << ")" << endl;
289
291 gpgme_ctx_t & ctx { ret._pimpl->_ctx };
292
293 // create the context
294 GpgmeErr err = gpgme_new( &ctx );
295 if ( err != GPG_ERR_NO_ERROR )
296 ZYPP_THROW( GpgmeException( "gpgme_new", err ) );
297
298 // use OpenPGP
300 if ( err != GPG_ERR_NO_ERROR )
301 ZYPP_THROW( GpgmeException( "gpgme_set_protocol", err ) );
302
303 if ( !keyring_r.empty() ) {
304 // get engine information to read current state
306 if ( !enginfo )
307 ZYPP_THROW( GpgmeException( "gpgme_ctx_get_engine_info", err ) );
308
310 if ( err != GPG_ERR_NO_ERROR )
311 ZYPP_THROW( GpgmeException( "gpgme_ctx_set_engine_info", err ) );
312 }
313
314 return ret;
315}
316
318{
321 ret = enginfo->home_dir;
322 return ret;
323}
324
325std::list<PublicKeyData> KeyManagerCtx::listKeys()
326{
327 std::list<PublicKeyData> ret;
328 GpgmeErr err = GPG_ERR_NO_ERROR;
329
330 // Reset gpgme_keylist_mode on return!
332 // Let listed keys include signatures (required if PublicKeyData are created from the key)
334 ERR << "gpgme_set_keylist_mode: " << err << endl;
335 return ret;
336 }
337
338 if ( (err = gpgme_op_keylist_start( _pimpl->_ctx, NULL, 0 )) != GPG_ERR_NO_ERROR ) {
339 ERR << "gpgme_op_keylist_start: " << err << endl;
340 return ret;
341 }
342 // Close list operation on return!
344
346 for ( ; gpgme_op_keylist_next( _pimpl->_ctx, &(*key) ) == GPG_ERR_NO_ERROR; key.getDispose()( key ) ) {
348 if ( data )
349 ret.push_back( data );
350 }
351
352 return ret;
353}
354
355std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile( const Pathname & keyfile_r )
356{
357 // bsc#1140670: GPGME does not support reading keys from a keyfile using
358 // gpgme_data_t and gpgme_op_keylist_from_data_start. Despite GPGME_KEYLIST_MODE_SIGS
359 // the signatures are missing, but we need them to create proper PublicKeyData objects.
360 // While this is not resolved, we read into a temp. keyring. Impl::_volatile helps
361 // to detect whether we can clear and import into the current context or need to
362 // create a temp. one.
363 std::list<PublicKeyData> ret;
364
365 if ( _pimpl->_volatile ) {
366 // in a volatile context we can simple clear the keyring...
368 if ( importKey( keyfile_r ) )
369 ret = listKeys();
370 } else {
371 // read in a volatile context
372 ret = createForOpenPGP().readKeyFromFile( keyfile_r );
373 }
374
375 return ret;
376}
377
378bool KeyManagerCtx::verify(const Pathname &file, const Pathname &signature)
379{
380 return _pimpl->verifySignaturesFprs(file, signature);
381}
382
383bool KeyManagerCtx::exportKey(const std::string &id, std::ostream &stream)
384{
385 GpgmeErr err = GPG_ERR_NO_ERROR;
386
388
389 //search for requested key id
390 gpgme_key_t key = nullptr;
392 while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
393 if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
395 break;
396 }
398 }
400
401 if (!foundKey) {
402 WAR << "Key " << id << "not found" << endl;
403 return false;
404 }
405
406 //function needs a array of keys to export
408 keyarray[0] = foundKey.get();
409 keyarray[1] = NULL;
410
411 GpgmeDataPtr out(nullptr, gpgme_data_release);
412 err = gpgme_data_new (&out.get());
413 if (err) {
414 ERR << err << endl;
415 return false;
416 }
417
418 //format as ascii armored
419 gpgme_set_armor (_pimpl->_ctx, 1);
420 // bsc#1179222: Remove outdated self signatures when exporting the key.
421 // The keyring does not order the signatures when multiple versions of the
422 // same key are imported. Rpm however uses the 1st to compute the -release
423 // of the gpg-pubkey. So we export only the latest to get a proper-release.
425 if (!err) {
426 int ret = gpgme_data_seek (out.get(), 0, SEEK_SET);
427 if (ret) {
428 ERR << "Unable to seek in exported key data" << endl;
429 return false;
430 }
431
432 const int bufsize = 512;
433 char buf[bufsize + 1];
434 while ((ret = gpgme_data_read(out.get(), buf, bufsize)) > 0) {
435 stream.write(buf, ret);
436 }
437
438 //failed to read from buffer
439 if (ret < 0) {
440 ERR << "Unable to read exported key data" << endl;
441 return false;
442 }
443 } else {
444 ERR << "Error exporting key: "<< err << endl;
445 return false;
446 }
447
448 //if we reach this point export was successful
449 return true;
450}
451
453{
454 if ( !PathInfo( keyfile ).isExist() ) {
455 ERR << "Keyfile '" << keyfile << "' does not exist.";
456 return false;
457 }
458
459 GpgmeDataPtr data(nullptr, gpgme_data_release);
460 GpgmeErr err;
461
462 err = gpgme_data_new_from_file(&data.get(), keyfile.c_str(), 1);
463 if (err) {
464 ERR << "Error importing key: "<< err << endl;
465 return false;
466 }
467
468 return _pimpl->importKey( data, [&](){ return PathInfo(keyfile).size(); } );
469}
470
472{
473 GpgmeDataPtr data(nullptr, gpgme_data_release);
474 GpgmeErr err;
475
476 err = gpgme_data_new_from_mem( &data.get(), keydata.data(), keydata.size(), 1);
477 if (err) {
478 ERR << "Error importing key: "<< err << endl;
479 return false;
480 }
481
482 return _pimpl->importKey( data, [&](){ return keydata.size(); } );
483}
484
485template<typename Callback>
487{
488 GpgmeErr err;
489 err = gpgme_op_import( _ctx, data.get() );
490 if (err) {
491 ERR << "Error importing key: "<< err << endl;
492 return false;
493 }
494
495 // Work around bsc#1127220 [libgpgme] no error upon incomplete import due to signal received.
496 // We need this error, otherwise RpmDb will report the missing keys as 'probably v3'.
498 {
499 if ( ! res->considered && std::forward<Callback>(calcDataSize)() )
500 {
501 DBG << *res << endl;
502 ERR << "Error importing key: No keys considered (bsc#1127220, [libgpgme] signal received?)" << endl;
503 return false;
504 }
505 }
506
507 return (err == GPG_ERR_NO_ERROR);
508}
509
510bool KeyManagerCtx::deleteKey(const std::string &id)
511{
512 gpgme_key_t key = nullptr;
513 GpgmeErr err = GPG_ERR_NO_ERROR;
514
516
517 while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
518 if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
519 err = gpgme_op_delete(_pimpl->_ctx, key, 0);
520
523
524 if (err) {
525 ERR << "Error deleting key: "<< err << endl;
526 return false;
527 }
528 return true;
529 }
531 }
532
534 WAR << "Key: '"<< id << "' not found." << endl;
535 return false;
536}
537
538std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const Pathname &signature)
539{ return _pimpl->readSignaturesFprs(signature); }
540
541std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const ByteArray &keyData)
542{ return _pimpl->readSignaturesFprs(keyData); }
543
544} // namespace zypp
gpgme_error_t _err
Definition KeyManager.cc:64
Reference counted access to a Tp object calling a custom Dispose function when the last AutoDispose h...
Definition AutoDispose.h:95
void swap(AutoDispose &rhs) noexcept
Exchange the contents of two AutoDispose objects.
const Dispose & getDispose() const
Return the current dispose function.
shared_ptr< Impl > _pimpl
Impl & operator=(const Impl &)=delete
std::list< std::string > readSignaturesFprs(const Pathname &signature_r)
Return all fingerprints found in signature_r.
Impl(const Impl &)=delete
bool _volatile
readKeyFromFile workaround bsc#1140670
Impl & operator=(Impl &&)=delete
std::list< std::string > readSignaturesFprs(const ByteArray &signature_r)
Return all fingerprints found in signature_r.
std::list< std::string > readSignaturesFprsOptVerify(const Pathname &signature_r, const Pathname &file_r="/dev/null", bool *verify_r=nullptr)
Return all fingerprints found in signature_r and optionally verify the file_r on the fly.
bool verifySignaturesFprs(const Pathname &file_r, const Pathname &signature_r)
Tries to verify the file_r using signature_r.
bool importKey(GpgmeDataPtr &data, Callback &&calcDataSize)
bool exportKey(const std::string &id, std::ostream &stream)
Exports the key with id into the given stream, returns true on success.
std::list< PublicKeyData > listKeys()
Returns a list of all public keys found in the current keyring.
bool verify(const Pathname &file, const Pathname &signature)
Tries to verify file using signature, returns true on success.
static KeyManagerCtx createForOpenPGP()
Creates a new KeyManagerCtx for PGP using a volatile temp.
std::list< std::string > readSignatureFingerprints(const Pathname &signature)
Reads all fingerprints from the signature file , returns a list of all found fingerprints.
std::list< PublicKeyData > readKeyFromFile(const Pathname &file)
Returns a list of all PublicKeyData found in file.
RW_pointer< Impl > _pimpl
Pointer to implementation.
Definition KeyManager.h:90
bool deleteKey(const std::string &id)
Tries to delete a key specified by id, returns true on success.
Pathname homedir() const
Return the homedir/keyring.
bool importKey(const Pathname &keyfile)
Tries to import a key from keyfile, returns true on success.
Class representing one GPG Public Keys data.
Definition PublicKey.h:208
static PublicKeyData fromGpgmeKey(_gpgme_key *data)
Definition PublicKey.cc:412
Wrapper class for stat/lstat.
Definition PathInfo.h:222
String related utilities and Regular expression matching.
int clean_dir(const Pathname &path)
Like 'rm -r DIR/ *'.
Definition PathInfo.cc:447
int assert_dir(const Pathname &path, unsigned mode)
Like 'mkdir -p'.
Definition PathInfo.cc:324
const std::string & asString(const std::string &t)
Global asString() that works with std::string too.
Definition String.h:139
Easy-to use interface to the ZYPP dependency resolver.
Pathname myTmpDir()
Global access to the zypp.TMPDIR (created on demand, deleted when libzypp is unloaded)
Definition ZYppImpl.cc:343
std::ostream & operator<<(std::ostream &str, const SerialNumber &obj)
DlContextRefType _ctx
Definition rpmmd.cc:69
GpgmeException(const std::string &in_r, const GpgmeErr &err_r)
Definition KeyManager.cc:99
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:424
#define DBG
Definition Logger.h:97
#define MIL
Definition Logger.h:98
#define ERR
Definition Logger.h:100
#define WAR
Definition Logger.h:99