318a32aa96832aa87e807900786d8a79c38290e6
[platform/upstream/libzypp.git] / zypp / KeyManager.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 #include "zypp/KeyManager.h"
10 #include "zypp/PublicKey.h"
11 #include "zypp/PathInfo.h"
12 #include "zypp/base/Logger.h"
13 #include "zypp/TmpPath.h"
14 #include "zypp/base/String.h"
15
16 #include <boost/thread/once.hpp>
17 #include <boost/interprocess/smart_ptr/scoped_ptr.hpp>
18 #include <gpgme.h>
19
20 #include <stdio.h>
21
22 #undef  ZYPP_BASE_LOGGER_LOGGROUP
23 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::gpg"
24
25 // @TODO [threading]
26 // make sure to call the init code of gpgme only once
27 // this might need to be moved to a different location when
28 // threads are introduced into libzypp
29 boost::once_flag gpgme_init_once = BOOST_ONCE_INIT;
30 using std::endl;
31
32 //using boost::interprocess pointer because it allows a custom deleter
33 typedef boost::interprocess::scoped_ptr<gpgme_data, boost::function<void (gpgme_data_t)>> GpgmeDataPtr;
34 typedef boost::interprocess::scoped_ptr<_gpgme_key, boost::function<void (gpgme_key_t)>>  GpgmeKeyPtr;
35 typedef boost::interprocess::scoped_ptr<FILE, boost::function<int (FILE *)>> FILEPtr;
36
37 struct GpgmeErr
38 {
39   GpgmeErr( gpgme_error_t err_r = GPG_ERR_NO_ERROR )
40   : _err( err_r )
41   {}
42   operator gpgme_error_t() const { return _err; }
43 private:
44   gpgme_error_t _err;
45 };
46 std::ostream & operator<<( std::ostream & str, const GpgmeErr & obj )
47 { return str << "<" << gpgme_strsource(obj) << "> " << gpgme_strerror(obj); }
48
49 static void initGpgme ()
50 {
51   const char *version = gpgme_check_version(NULL);
52   if ( version )
53   {
54     MIL << "Initialized libgpgme version: " << version << endl;
55   }
56   else
57   {
58     MIL << "Initialized libgpgme with unknown version" << endl;
59   }
60 }
61
62 namespace zypp
63 {
64
65 class KeyManagerCtx::Impl
66 {
67 public:
68   Impl();
69   ~Impl();
70
71   /** Return all fingerprints found in \a signature_r. */
72   std::list<std::string> readSignaturesFprs( const Pathname & signature_r )
73   { return readSignaturesFprsOptVerify( signature_r ); }
74
75   /** Tries to verify the \a file_r using \a signature_r. */
76   bool verifySignaturesFprs( const Pathname & file_r, const Pathname & signature_r )
77   {
78     bool verify = false;
79     readSignaturesFprsOptVerify( signature_r, file_r, &verify );
80     return verify;
81   }
82
83   gpgme_ctx_t _ctx;
84
85 private:
86   /** Return all fingerprints found in \a signature_r and optionally verify the \a file_r on the fly.
87    *
88    * If \a verify_r is not a \c nullptr, log verification errors and return
89    * whether all signatures are good.
90    */
91   std::list<std::string> readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r = "/dev/null", bool * verify_r = nullptr );
92 };
93
94 KeyManagerCtx::Impl::Impl()
95 { }
96
97
98 KeyManagerCtx::KeyManagerCtx()
99   : _pimpl( new Impl )
100 {
101
102 }
103
104 std::list<std::string> KeyManagerCtx::Impl::readSignaturesFprsOptVerify( const Pathname & signature_r, const Pathname & file_r, bool * verify_r )
105 {
106   //lets be pessimistic
107   if ( verify_r )
108     *verify_r = false;
109
110
111   if (!PathInfo( signature_r ).isExist())
112     return std::list<std::string>();
113
114   FILEPtr dataFile(fopen(file_r.c_str(), "rb"), fclose);
115   if (!dataFile)
116     return std::list<std::string>();
117
118   GpgmeDataPtr fileData(nullptr, gpgme_data_release);
119   GpgmeErr err = gpgme_data_new_from_stream (&fileData.get(), dataFile.get());
120   if (err) {
121     ERR << err << endl;
122     return std::list<std::string>();
123   }
124
125   FILEPtr sigFile(fopen(signature_r.c_str(), "rb"), fclose);
126   if (!sigFile) {
127     ERR << "Unable to open signature file '" << signature_r << "'" <<endl;
128     return std::list<std::string>();
129   }
130
131   GpgmeDataPtr sigData(nullptr, gpgme_data_release);
132   err = gpgme_data_new_from_stream (&sigData.get(), sigFile.get());
133   if (err) {
134     ERR << err << endl;
135     return std::list<std::string>();
136   }
137
138   err = gpgme_op_verify(_ctx, sigData.get(), fileData.get(), NULL);
139   if (err != GPG_ERR_NO_ERROR) {
140     ERR << err << endl;
141     return std::list<std::string>();
142   }
143
144   gpgme_verify_result_t res = gpgme_op_verify_result(_ctx);
145   if (!res || !res->signatures) {
146     ERR << "Unable to read signature fingerprints" <<endl;
147     return std::list<std::string>();
148   }
149
150   bool foundBadSignature = false;
151   std::list<std::string> signatures;
152   for ( gpgme_signature_t sig = res->signatures; sig; sig = sig->next ) {
153
154     if ( sig->fpr )
155     {
156       // bsc#1100427: With ibgpgme11-1.11.0 and if a recent gpg version was used
157       // to create the signature, the field may contain the full fingerprint, but
158       // we're expected to return the ID.
159       // [https://github.com/gpg/gpgme/commit/478d1650bbef84958ccce439fac982ef57b16cd0]
160       std::string id( sig->fpr );
161       if ( id.size() > 16 )
162         id = id.substr( id.size()-16 );
163       signatures.push_back( std::move(id) );
164     }
165
166     if ( sig->status != GPG_ERR_NO_ERROR )
167     {
168       if ( gpgme_err_code(sig->status) != GPG_ERR_KEY_EXPIRED )
169       {
170         if ( !foundBadSignature )
171           foundBadSignature = true;
172         if ( verify_r )
173           WAR << "Failed signature check: " << file_r << " " << GpgmeErr(sig->status) << endl;
174       }
175       else
176       {
177         if ( verify_r )
178           WAR << "Legacy: Ignore expired key: " << file_r << " " << GpgmeErr(sig->status) << endl;
179       }
180     }
181   }
182
183   if ( verify_r )
184     *verify_r = (!foundBadSignature);
185   return signatures;
186 }
187
188 KeyManagerCtx::Impl::~Impl()
189 {
190   gpgme_release(_ctx);
191 }
192
193 KeyManagerCtx::Ptr KeyManagerCtx::createForOpenPGP()
194 {
195   //make sure gpgpme is initialized
196   boost::call_once(gpgme_init_once, initGpgme);
197
198   gpgme_ctx_t ctx;
199   GpgmeErr err = gpgme_new(&ctx);
200   if (err != GPG_ERR_NO_ERROR) {
201     ERR << err << endl;
202     return shared_ptr<KeyManagerCtx>();
203   }
204
205   //use OpenPGP
206   err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
207   if (err != GPG_ERR_NO_ERROR) {
208     ERR << err << endl;
209     gpgme_release(ctx);
210     return shared_ptr<KeyManagerCtx>();
211   }
212
213   shared_ptr<KeyManagerCtx> me( new KeyManagerCtx());
214   me->_pimpl->_ctx = ctx;
215   return me;
216 }
217
218 bool KeyManagerCtx::setHomedir(const Pathname &keyring_r)
219 {
220
221   /* get engine information to read current state*/
222   gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info(_pimpl->_ctx);
223   if (!enginfo)
224     return false;
225
226   GpgmeErr err = gpgme_ctx_set_engine_info(
227         _pimpl->_ctx,
228         GPGME_PROTOCOL_OpenPGP,
229         enginfo->file_name,
230         keyring_r.c_str());
231
232   if (err != GPG_ERR_NO_ERROR) {
233     ERR << "Unable to set homedir " << err << endl;
234     return false;
235   }
236
237   return true;
238 }
239
240 Pathname KeyManagerCtx::homedir() const
241 {
242   gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info(_pimpl->_ctx);
243   if (!enginfo)
244     return Pathname();
245
246   return Pathname(enginfo->home_dir);
247 }
248
249 std::list<PublicKeyData> KeyManagerCtx::listKeys()
250 {
251   std::list<PublicKeyData> keys;
252   gpgme_key_t key;
253   GpgmeErr err = GPG_ERR_NO_ERROR;
254
255   gpgme_keylist_mode_t mode = GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_SIGS;
256   gpgme_set_keylist_mode (_pimpl->_ctx, mode);
257   gpgme_op_keylist_start (_pimpl->_ctx, NULL, 0);
258
259   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
260     PublicKeyData data = PublicKeyData::fromGpgmeKey(key);
261     if (data) {
262       keys.push_back(data);
263     }
264     gpgme_key_release(key);
265   }
266   gpgme_op_keylist_end(_pimpl->_ctx);
267   return keys;
268 }
269
270 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile(const Pathname &file)
271 {
272   //seems GPGME does not support reading keys from a keyfile using
273   //gpgme_data_t and gpgme_op_keylist_from_data_start, this always
274   //return unsupported errors. However importing and listing the key works.
275   zypp::Pathname realHomedir = homedir();
276
277   zypp::filesystem::TmpDir tmpKeyring;
278   if (!setHomedir(tmpKeyring.path()))
279     return std::list<PublicKeyData>();
280
281   if (!importKey(file)) {
282     setHomedir(realHomedir);
283     return std::list<PublicKeyData>();
284   }
285
286   std::list<PublicKeyData> keys = listKeys();
287   setHomedir(realHomedir);
288   return keys;
289 }
290
291 bool KeyManagerCtx::verify(const Pathname &file, const Pathname &signature)
292 {
293   if ( !PathInfo( file ).isExist() || !PathInfo( signature ).isExist() )
294     return false;
295
296   return _pimpl->verifySignaturesFprs(file, signature);
297 }
298
299 bool KeyManagerCtx::exportKey(const std::string &id, std::ostream &stream)
300 {
301   GpgmeErr err = GPG_ERR_NO_ERROR;
302
303   GpgmeKeyPtr foundKey;
304
305   //search for requested key id
306   gpgme_key_t key;
307   gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
308   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
309     if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
310       GpgmeKeyPtr(key, gpgme_key_release).swap(foundKey);
311       break;
312     }
313     gpgme_key_release(key);
314   }
315   gpgme_op_keylist_end(_pimpl->_ctx);
316
317   if (!foundKey) {
318     WAR << "Key " << id << "not found" << endl;
319     return false;
320   }
321
322   //function needs a array of keys to export
323   gpgme_key_t keyarray[2];
324   keyarray[0] = foundKey.get();
325   keyarray[1] = NULL;
326
327   GpgmeDataPtr out(nullptr, gpgme_data_release);
328   err = gpgme_data_new (&out.get());
329   if (err) {
330     ERR << err << endl;
331     return false;
332   }
333
334   //format as ascii armored
335   gpgme_set_armor (_pimpl->_ctx, 1);
336   err = gpgme_op_export_keys (_pimpl->_ctx, keyarray, 0, out.get());
337   if (!err) {
338     int ret = gpgme_data_seek (out.get(), 0, SEEK_SET);
339     if (ret) {
340       ERR << "Unable to seek in exported key data" << endl;
341       return false;
342     }
343
344     const int bufsize = 512;
345     char buf[bufsize + 1];
346     while ((ret = gpgme_data_read(out.get(), buf, bufsize)) > 0) {
347       stream.write(buf, ret);
348     }
349
350     //failed to read from buffer
351     if (ret < 0) {
352       ERR << "Unable to read exported key data" << endl;
353       return false;
354     }
355   } else {
356     ERR << "Error exporting key: "<< err << endl;
357     return false;
358   }
359
360   //if we reach this point export was successful
361   return true;
362 }
363
364 bool KeyManagerCtx::importKey(const Pathname &keyfile)
365 {
366   if ( !PathInfo( keyfile ).isExist() ) {
367     ERR << "Keyfile '" << keyfile << "' does not exist.";
368     return false;
369   }
370
371   GpgmeDataPtr data(nullptr, gpgme_data_release);
372   GpgmeErr err;
373
374   err = gpgme_data_new_from_file(&data.get(), keyfile.c_str(), 1);
375   if (err) {
376     ERR << "Error importing key: "<< err << endl;
377     return false;
378   }
379
380   err = gpgme_op_import(_pimpl->_ctx, data.get());
381   if (err) {
382     ERR << "Error importing key: "<< err << endl;
383   }
384   return (err == GPG_ERR_NO_ERROR);
385 }
386
387 bool KeyManagerCtx::deleteKey(const std::string &id)
388 {
389   gpgme_key_t key;
390   GpgmeErr err = GPG_ERR_NO_ERROR;
391
392   gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
393
394   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
395     if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
396         err = gpgme_op_delete(_pimpl->_ctx, key, 0);
397
398         gpgme_key_release(key);
399         gpgme_op_keylist_end(_pimpl->_ctx);
400
401         if (err) {
402           ERR << "Error deleting key: "<< err << endl;
403           return false;
404         }
405         return true;
406     }
407     gpgme_key_release(key);
408   }
409
410   gpgme_op_keylist_end(_pimpl->_ctx);
411   WAR << "Key: '"<< id << "' not found." << endl;
412   return false;
413 }
414
415 std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const Pathname &signature)
416 { return _pimpl->readSignaturesFprs(signature); }
417
418 }