Imported Upstream version 17.4.0
[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       signatures.push_back(str::asString(sig->fpr));
156
157     if ( sig->status != GPG_ERR_NO_ERROR )
158     {
159       if ( gpgme_err_code(sig->status) != GPG_ERR_KEY_EXPIRED )
160       {
161         if ( !foundBadSignature )
162           foundBadSignature = true;
163         if ( verify_r )
164           WAR << "Failed signature check: " << file_r << " " << GpgmeErr(sig->status) << endl;
165       }
166       else
167       {
168         if ( verify_r )
169           WAR << "Legacy: Ignore expired key: " << file_r << " " << GpgmeErr(sig->status) << endl;
170       }
171     }
172   }
173
174   if ( verify_r )
175     *verify_r = (!foundBadSignature);
176   return signatures;
177 }
178
179 KeyManagerCtx::Impl::~Impl()
180 {
181   gpgme_release(_ctx);
182 }
183
184 KeyManagerCtx::Ptr KeyManagerCtx::createForOpenPGP()
185 {
186   //make sure gpgpme is initialized
187   boost::call_once(gpgme_init_once, initGpgme);
188
189   gpgme_ctx_t ctx;
190   GpgmeErr err = gpgme_new(&ctx);
191   if (err != GPG_ERR_NO_ERROR) {
192     ERR << err << endl;
193     return shared_ptr<KeyManagerCtx>();
194   }
195
196   //use OpenPGP
197   err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
198   if (err != GPG_ERR_NO_ERROR) {
199     ERR << err << endl;
200     gpgme_release(ctx);
201     return shared_ptr<KeyManagerCtx>();
202   }
203
204   shared_ptr<KeyManagerCtx> me( new KeyManagerCtx());
205   me->_pimpl->_ctx = ctx;
206   return me;
207 }
208
209 bool KeyManagerCtx::setHomedir(const Pathname &keyring_r)
210 {
211
212   /* get engine information to read current state*/
213   gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info(_pimpl->_ctx);
214   if (!enginfo)
215     return false;
216
217   GpgmeErr err = gpgme_ctx_set_engine_info(
218         _pimpl->_ctx,
219         GPGME_PROTOCOL_OpenPGP,
220         enginfo->file_name,
221         keyring_r.c_str());
222
223   if (err != GPG_ERR_NO_ERROR) {
224     ERR << "Unable to set homedir " << err << endl;
225     return false;
226   }
227
228   return true;
229 }
230
231 Pathname KeyManagerCtx::homedir() const
232 {
233   gpgme_engine_info_t enginfo = gpgme_ctx_get_engine_info(_pimpl->_ctx);
234   if (!enginfo)
235     return Pathname();
236
237   return Pathname(enginfo->home_dir);
238 }
239
240 std::list<PublicKeyData> KeyManagerCtx::listKeys()
241 {
242   std::list<PublicKeyData> keys;
243   gpgme_key_t key;
244   GpgmeErr err = GPG_ERR_NO_ERROR;
245
246   gpgme_keylist_mode_t mode = GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_SIGS;
247   gpgme_set_keylist_mode (_pimpl->_ctx, mode);
248   gpgme_op_keylist_start (_pimpl->_ctx, NULL, 0);
249
250   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
251     PublicKeyData data = PublicKeyData::fromGpgmeKey(key);
252     if (data) {
253       keys.push_back(data);
254     }
255     gpgme_key_release(key);
256   }
257   gpgme_op_keylist_end(_pimpl->_ctx);
258   return keys;
259 }
260
261 std::list<PublicKeyData> KeyManagerCtx::readKeyFromFile(const Pathname &file)
262 {
263   //seems GPGME does not support reading keys from a keyfile using
264   //gpgme_data_t and gpgme_op_keylist_from_data_start, this always
265   //return unsupported errors. However importing and listing the key works.
266   zypp::Pathname realHomedir = homedir();
267
268   zypp::filesystem::TmpDir tmpKeyring;
269   if (!setHomedir(tmpKeyring.path()))
270     return std::list<PublicKeyData>();
271
272   if (!importKey(file)) {
273     setHomedir(realHomedir);
274     return std::list<PublicKeyData>();
275   }
276
277   std::list<PublicKeyData> keys = listKeys();
278   setHomedir(realHomedir);
279   return keys;
280 }
281
282 bool KeyManagerCtx::verify(const Pathname &file, const Pathname &signature)
283 {
284   if ( !PathInfo( file ).isExist() || !PathInfo( signature ).isExist() )
285     return false;
286
287   return _pimpl->verifySignaturesFprs(file, signature);
288 }
289
290 bool KeyManagerCtx::exportKey(const std::string &id, std::ostream &stream)
291 {
292   GpgmeErr err = GPG_ERR_NO_ERROR;
293
294   GpgmeKeyPtr foundKey;
295
296   //search for requested key id
297   gpgme_key_t key;
298   gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
299   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
300     if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
301       GpgmeKeyPtr(key, gpgme_key_release).swap(foundKey);
302       break;
303     }
304     gpgme_key_release(key);
305   }
306   gpgme_op_keylist_end(_pimpl->_ctx);
307
308   if (!foundKey) {
309     WAR << "Key " << id << "not found" << endl;
310     return false;
311   }
312
313   //function needs a array of keys to export
314   gpgme_key_t keyarray[2];
315   keyarray[0] = foundKey.get();
316   keyarray[1] = NULL;
317
318   GpgmeDataPtr out(nullptr, gpgme_data_release);
319   err = gpgme_data_new (&out.get());
320   if (err) {
321     ERR << err << endl;
322     return false;
323   }
324
325   //format as ascii armored
326   gpgme_set_armor (_pimpl->_ctx, 1);
327   err = gpgme_op_export_keys (_pimpl->_ctx, keyarray, 0, out.get());
328   if (!err) {
329     int ret = gpgme_data_seek (out.get(), 0, SEEK_SET);
330     if (ret) {
331       ERR << "Unable to seek in exported key data" << endl;
332       return false;
333     }
334
335     const int bufsize = 512;
336     char buf[bufsize + 1];
337     while ((ret = gpgme_data_read(out.get(), buf, bufsize)) > 0) {
338       stream.write(buf, ret);
339     }
340
341     //failed to read from buffer
342     if (ret < 0) {
343       ERR << "Unable to read exported key data" << endl;
344       return false;
345     }
346   } else {
347     ERR << "Error exporting key: "<< err << endl;
348     return false;
349   }
350
351   //if we reach this point export was successful
352   return true;
353 }
354
355 bool KeyManagerCtx::importKey(const Pathname &keyfile)
356 {
357   if ( !PathInfo( keyfile ).isExist() ) {
358     ERR << "Keyfile '" << keyfile << "' does not exist.";
359     return false;
360   }
361
362   GpgmeDataPtr data(nullptr, gpgme_data_release);
363   GpgmeErr err;
364
365   err = gpgme_data_new_from_file(&data.get(), keyfile.c_str(), 1);
366   if (err) {
367     ERR << "Error importing key: "<< err << endl;
368     return false;
369   }
370
371   err = gpgme_op_import(_pimpl->_ctx, data.get());
372   if (err) {
373     ERR << "Error importing key: "<< err << endl;
374   }
375   return (err == GPG_ERR_NO_ERROR);
376 }
377
378 bool KeyManagerCtx::deleteKey(const std::string &id)
379 {
380   gpgme_key_t key;
381   GpgmeErr err = GPG_ERR_NO_ERROR;
382
383   gpgme_op_keylist_start(_pimpl->_ctx, NULL, 0);
384
385   while (!(err = gpgme_op_keylist_next(_pimpl->_ctx, &key))) {
386     if (key->subkeys && id == str::asString(key->subkeys->keyid)) {
387         err = gpgme_op_delete(_pimpl->_ctx, key, 0);
388
389         gpgme_key_release(key);
390         gpgme_op_keylist_end(_pimpl->_ctx);
391
392         if (err) {
393           ERR << "Error deleting key: "<< err << endl;
394           return false;
395         }
396         return true;
397     }
398     gpgme_key_release(key);
399   }
400
401   gpgme_op_keylist_end(_pimpl->_ctx);
402   WAR << "Key: '"<< id << "' not found." << endl;
403   return false;
404 }
405
406 std::list<std::string> KeyManagerCtx::readSignatureFingerprints(const Pathname &signature)
407 { return _pimpl->readSignaturesFprs(signature); }
408
409 }