libzypp  17.14.0
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>
22 using std::endl;
23 
24 #undef ZYPP_BASE_LOGGER_LOGGROUP
25 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::gpg"
26 
28 namespace 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  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;
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 
111  { if ( _ctx ) gpgme_release( _ctx ); }
112 
113 
115  std::list<std::string> readSignaturesFprs( const Pathname & signature_r )
116  { return readSignaturesFprsOptVerify( signature_r ); }
117 
119  bool verifySignaturesFprs( const Pathname & file_r, const Pathname & signature_r )
120  {
121  bool verify = false;
122  readSignaturesFprsOptVerify( signature_r, file_r, &verify );
123  return verify;
124  }
125 
126  gpgme_ctx_t _ctx { nullptr };
127  bool _volatile { false };
128 
129  private:
135  std::list<std::string> readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
136  };
137 
138 std::list<std::string> KeyManagerCtx::Impl::readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r, bool * verify_r )
139 {
140  //lets be pessimistic
141  if ( verify_r )
142  *verify_r = false;
143 
144 
145  if (!PathInfo( signature_r ).isExist())
146  return std::list<std::string>();
147 
148  FILEPtr dataFile(fopen(file_r.c_str(), "rb"), fclose);
149  if (!dataFile)
150  return std::list<std::string>();
151 
152  GpgmeDataPtr fileData(nullptr, gpgme_data_release);
153  GpgmeErr err = gpgme_data_new_from_stream (&fileData.get(), dataFile.get());
154  if (err) {
155  ERR << err << endl;
156  return std::list<std::string>();
157  }
158 
159  FILEPtr sigFile(fopen(signature_r.c_str(), "rb"), fclose);
160  if (!sigFile) {
161  ERR << "Unable to open signature file '" << signature_r << "'" <<endl;
162  return std::list<std::string>();
163  }
164 
165  GpgmeDataPtr sigData(nullptr, gpgme_data_release);
166  err = gpgme_data_new_from_stream (&sigData.get(), sigFile.get());
167  if (err) {
168  ERR << err << endl;
169  return std::list<std::string>();
170  }
171 
172  err = gpgme_op_verify(_ctx, sigData.get(), fileData.get(), NULL);
173  if (err != GPG_ERR_NO_ERROR) {
174  ERR << err << endl;
175  return std::list<std::string>();
176  }
177 
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>();
182  }
183 
184  bool foundBadSignature = false;
185  std::list<std::string> signatures;
186  for ( gpgme_signature_t sig = res->signatures; sig; sig = sig->next ) {
187 
188  if ( sig->fpr )
189  {
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) );
198  }
199 
200  if ( sig->status != GPG_ERR_NO_ERROR )
201  {
202  if ( gpgme_err_code(sig->status) != GPG_ERR_KEY_EXPIRED )
203  {
204  if ( !foundBadSignature )
205  foundBadSignature = true;
206  if ( verify_r )
207  WAR << "Failed signature check: " << file_r << " " << GpgmeErr(sig->status) << endl;
208  }
209  else
210  {
211  if ( verify_r )
212  WAR << "Legacy: Ignore expired key: " << file_r << " " << GpgmeErr(sig->status) << endl;
213  }
214  }
215  }
216 
217  if ( verify_r )
218  *verify_r = (!foundBadSignature);
219  return signatures;
220 }
221 
222 
224 : _pimpl( new Impl )
225 {}
226 
228 {
229  static Pathname tmppath( zypp::myTmpDir() / "PublicKey" );
230  filesystem::assert_dir( tmppath );
231 
232  KeyManagerCtx ret { createForOpenPGP( tmppath ) };
233  ret._pimpl->_volatile = true; // readKeyFromFile workaround bsc#1140670
234  return ret;
235 }
236 
238 {
239  DBG << "createForOpenPGP(" << keyring_r << ")" << endl;
240 
241  KeyManagerCtx ret;
242  gpgme_ctx_t & ctx { ret._pimpl->_ctx };
243 
244  // create the context
245  GpgmeErr err = gpgme_new( &ctx );
246  if ( err != GPG_ERR_NO_ERROR )
247  ZYPP_THROW( GpgmeException( "gpgme_new", err ) );
248 
249  // use OpenPGP
250  err = gpgme_set_protocol( ctx, GPGME_PROTOCOL_OpenPGP );
251  if ( err != GPG_ERR_NO_ERROR )
252  ZYPP_THROW( GpgmeException( "gpgme_set_protocol", err ) );
253 
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 );
257  if ( !enginfo )
258  ZYPP_THROW( GpgmeException( "gpgme_ctx_get_engine_info", err ) );
259 
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 ) );
263  }
264 
265  return ret;
266 }
267 
269 {
270  Pathname ret;
271  if ( gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info( _pimpl->_ctx ) )
272  ret = enginfo->home_dir;
273  return ret;
274 }
275 
276 std::list<PublicKeyData> KeyManagerCtx::listKeys()
277 {
278  std::list<PublicKeyData> ret;
279  GpgmeErr err = GPG_ERR_NO_ERROR;
280 
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;
286  return ret;
287  }
288 
289  if ( (err = gpgme_op_keylist_start( _pimpl->_ctx, NULL, 0 )) != GPG_ERR_NO_ERROR ) {
290  ERR << "gpgme_op_keylist_start: " << err << endl;
291  return ret;
292  }
293  // Close list operation on return!
294  AutoDispose<gpgme_ctx_t> guard2 { _pimpl->_ctx, &gpgme_op_keylist_end };
295 
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 ) ) {
299  if ( data )
300  ret.push_back( data );
301  }
302 
303  return ret;
304 }
305 
306 #if 1
307 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile( const Pathname & keyfile_r )
308 {
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;
316 
317  if ( _pimpl->_volatile ) {
318  // in a volatile context we can simple clear the keyring...
320  if ( importKey( keyfile_r ) )
321  ret = listKeys();
322  } else {
323  // read in a volatile context
324  ret = createForOpenPGP().readKeyFromFile( keyfile_r );
325  }
326 
327  return ret;
328 }
329 #else
330 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile( const Pathname & file_r )
331 {
332  std::list<PublicKeyData> ret;
333  GpgmeErr err = GPG_ERR_NO_ERROR;
334 
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;
338  return ret;
339  }
340 
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;
346  return ret;
347  }
348 
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;
351  return ret;
352  }
353  // Close list operation on return!
354  AutoDispose<gpgme_ctx_t> guard2 { _pimpl->_ctx, &gpgme_op_keylist_end };
355 
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 ) };
359  if ( data )
360  ret.push_back( data );
361  }
362 
363  return ret;
364 }
365 #endif
366 
367 bool KeyManagerCtx::verify(const Pathname &file, const Pathname &signature)
368 {
369  return _pimpl->verifySignaturesFprs(file, signature);
370 }
371 
372 bool KeyManagerCtx::exportKey(const std::string &id, std::ostream &stream)
373 {
374  GpgmeErr err = GPG_ERR_NO_ERROR;
375 
376  GpgmeKeyPtr foundKey;
377 
378  //search for requested key id
379  gpgme_key_t key;
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);
384  break;
385  }
386  gpgme_key_release(key);
387  }
388  gpgme_op_keylist_end(_pimpl->_ctx);
389 
390  if (!foundKey) {
391  WAR << "Key " << id << "not found" << endl;
392  return false;
393  }
394 
395  //function needs a array of keys to export
396  gpgme_key_t keyarray[2];
397  keyarray[0] = foundKey.get();
398  keyarray[1] = NULL;
399 
400  GpgmeDataPtr out(nullptr, gpgme_data_release);
401  err = gpgme_data_new (&out.get());
402  if (err) {
403  ERR << err << endl;
404  return false;
405  }
406 
407  //format as ascii armored
408  gpgme_set_armor (_pimpl->_ctx, 1);
409  err = gpgme_op_export_keys (_pimpl->_ctx, keyarray, 0, out.get());
410  if (!err) {
411  int ret = gpgme_data_seek (out.get(), 0, SEEK_SET);
412  if (ret) {
413  ERR << "Unable to seek in exported key data" << endl;
414  return false;
415  }
416 
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);
421  }
422 
423  //failed to read from buffer
424  if (ret < 0) {
425  ERR << "Unable to read exported key data" << endl;
426  return false;
427  }
428  } else {
429  ERR << "Error exporting key: "<< err << endl;
430  return false;
431  }
432 
433  //if we reach this point export was successful
434  return true;
435 }
436 
437 bool KeyManagerCtx::importKey(const Pathname &keyfile)
438 {
439  if ( !PathInfo( keyfile ).isExist() ) {
440  ERR << "Keyfile '" << keyfile << "' does not exist.";
441  return false;
442  }
443 
444  GpgmeDataPtr data(nullptr, gpgme_data_release);
445  GpgmeErr err;
446 
447  err = gpgme_data_new_from_file(&data.get(), keyfile.c_str(), 1);
448  if (err) {
449  ERR << "Error importing key: "<< err << endl;
450  return false;
451  }
452 
453  err = gpgme_op_import(_pimpl->_ctx, data.get());
454  if (err) {
455  ERR << "Error importing key: "<< err << endl;
456  return false;
457  }
458 
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) )
462  {
463  if ( ! res->considered && PathInfo(keyfile).size() )
464  {
465  DBG << *res << endl;
466  ERR << "Error importing key: No keys considered (bsc#1127220, [libgpgme] signal received?)" << endl;
467  return false;
468  }
469  }
470 
471  return (err == GPG_ERR_NO_ERROR);
472 }
473 
474 bool KeyManagerCtx::deleteKey(const std::string &id)
475 {
476  gpgme_key_t key;
477  GpgmeErr err = GPG_ERR_NO_ERROR;
478 
479  gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
480 
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);
484 
485  gpgme_key_release(key);
486  gpgme_op_keylist_end(_pimpl->_ctx);
487 
488  if (err) {
489  ERR << "Error deleting key: "<< err << endl;
490  return false;
491  }
492  return true;
493  }
494  gpgme_key_release(key);
495  }
496 
497  gpgme_op_keylist_end(_pimpl->_ctx);
498  WAR << "Key: '"<< id << "' not found." << endl;
499  return false;
500 }
501 
502 std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const Pathname &signature)
503 { return _pimpl->readSignaturesFprs(signature); }
504 
505 } // namespace zypp
int assert_dir(const Pathname &path, unsigned mode)
Like 'mkdir -p'.
Definition: PathInfo.cc:320
#define MIL
Definition: Logger.h:79
std::list< PublicKeyData > readKeyFromFile(const Pathname &file)
Returns a list of all PublicKeyData found in file.
Definition: KeyManager.cc:307
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:392
std::list< std::string > readSignaturesFprs(const Pathname &signature_r)
Return all fingerprints found in signature_r.
Definition: KeyManager.cc:115
gpgme_error_t _err
Definition: KeyManager.cc:64
Class representing one GPG Public Keys data.
Definition: PublicKey.h:139
int clean_dir(const Pathname &path)
Like 'rm -r DIR/ *'.
Definition: PathInfo.cc:434
const char * c_str() const
String representation.
Definition: Pathname.h:109
String related utilities and Regular expression matching.
const std::string & asString(const std::string &t)
Global asString() that works with std::string too.
Definition: String.h:136
bool verify(const Pathname &file, const Pathname &signature)
Tries to verify file using signature, returns true on success.
Definition: KeyManager.cc:367
static KeyManagerCtx createForOpenPGP()
Creates a new KeyManagerCtx for PGP using a volatile temp.
Definition: KeyManager.cc:227
#define ERR
Definition: Logger.h:81
bool exportKey(const std::string &id, std::ostream &stream)
Exports the key with id into the given stream, returns true on success.
Definition: KeyManager.cc:372
RW_pointer< Impl > _pimpl
Pointer to implementation.
Definition: KeyManager.h:82
Pathname homedir() const
Return the homedir/keyring.
Definition: KeyManager.cc:268
bool empty() const
Test for an empty path.
Definition: Pathname.h:113
bool importKey(const Pathname &keyfile)
Tries to import a key from keyfile, returns true on success.
Definition: KeyManager.cc:437
std::ostream & operator<<(std::ostream &str, const Exception &obj)
Definition: Exception.cc:147
bool verifySignaturesFprs(const Pathname &file_r, const Pathname &signature_r)
Tries to verify the file_r using signature_r.
Definition: KeyManager.cc:119
#define WAR
Definition: Logger.h:80
Pathname myTmpDir()
Global access to the zypp.TMPDIR (created on demand, deleted when libzypp is unloaded)
Definition: ZYppImpl.cc:228
bool deleteKey(const std::string &id)
Tries to delete a key specified by id, returns true on success.
Definition: KeyManager.cc:474
std::list< PublicKeyData > listKeys()
Returns a list of all public keys found in the current keyring.
Definition: KeyManager.cc:276
GpgmeException(const std::string &in_r, const GpgmeErr &err_r)
Definition: KeyManager.cc:99
Reference counted access to a Tp object calling a custom Dispose function when the last AutoDispose h...
Definition: AutoDispose.h:92
Wrapper class for ::stat/::lstat.
Definition: PathInfo.h:220
std::list< std::string > readSignatureFingerprints(const Pathname &signature)
Reads all fingerprints from the signature file , returns a list of all found fingerprints.
Definition: KeyManager.cc:502
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.
Definition: KeyManager.cc:138
static PublicKeyData fromGpgmeKey(_gpgme_key *data)
Definition: PublicKey.cc:297
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:1
bool _volatile
readKeyFromFile workaround bsc#1140670
Definition: KeyManager.cc:127
#define DBG
Definition: Logger.h:78