d6481377946ac7b4cb18eb36b4674fe3c9b282af
[keymanager.git] / src / leap / keymanager / openpgp.py
1 # -*- coding: utf-8 -*-
2 # openpgp.py
3 # Copyright (C) 2013-2015 LEAP
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 """
18 Infrastructure for using OpenPGP keys in Key Manager.
19 """
20 import logging
21 import os
22 import re
23 import shutil
24 import tempfile
25 import traceback
26 import io
27
28
29 from datetime import datetime
30 from gnupg import GPG
31 from gnupg.gnupg import GPGUtilities
32 from twisted.internet import defer
33
34 from leap.common.check import leap_assert, leap_assert_type, leap_check
35 from leap.keymanager import errors
36 from leap.keymanager.keys import (
37     EncryptionKey,
38     EncryptionScheme,
39     is_address,
40     build_key_from_dict,
41     TYPE_ID_PRIVATE_INDEX,
42     TYPE_ADDRESS_PRIVATE_INDEX,
43     KEY_ADDRESS_KEY,
44     KEY_ID_KEY,
45     KEY_FINGERPRINT_KEY,
46     KEY_REFRESHED_AT_KEY,
47     KEYMANAGER_ACTIVE_TYPE,
48 )
49
50
51 logger = logging.getLogger(__name__)
52
53
54 #
55 # A temporary GPG keyring wrapped to provide OpenPGP functionality.
56 #
57
58 class TempGPGWrapper(object):
59     """
60     A context manager that wraps a temporary GPG keyring which only contains
61     the keys given at object creation.
62     """
63
64     def __init__(self, keys=None, gpgbinary=None):
65         """
66         Create an empty temporary keyring and import any given C{keys} into
67         it.
68
69         :param keys: OpenPGP key, or list of.
70         :type keys: OpenPGPKey or list of OpenPGPKeys
71         :param gpgbinary: Name for GnuPG binary executable.
72         :type gpgbinary: C{str}
73         """
74         self._gpg = None
75         self._gpgbinary = gpgbinary
76         if not keys:
77             keys = list()
78         if not isinstance(keys, list):
79             keys = [keys]
80         self._keys = keys
81         for key in keys:
82             leap_assert_type(key, OpenPGPKey)
83
84     def __enter__(self):
85         """
86         Build and return a GPG keyring containing the keys given on
87         object creation.
88
89         :return: A GPG instance containing the keys given on object creation.
90         :rtype: gnupg.GPG
91         """
92         self._build_keyring()
93         return self._gpg
94
95     def __exit__(self, exc_type, exc_value, traceback):
96         """
97         Ensure the gpg is properly destroyed.
98         """
99         # TODO handle exceptions and log here
100         self._destroy_keyring()
101
102     def _build_keyring(self):
103         """
104         Create a GPG keyring containing the keys given on object creation.
105
106         :return: A GPG instance containing the keys given on object creation.
107         :rtype: gnupg.GPG
108         """
109         privkeys = [key for key in self._keys if key and key.private is True]
110         publkeys = [key for key in self._keys if key and key.private is False]
111         # here we filter out public keys that have a correspondent
112         # private key in the list because the private key_data by
113         # itself is enough to also have the public key in the keyring,
114         # and we want to count the keys afterwards.
115
116         privids = map(lambda privkey: privkey.key_id, privkeys)
117         publkeys = filter(
118             lambda pubkey: pubkey.key_id not in privids, publkeys)
119
120         listkeys = lambda: self._gpg.list_keys()
121         listsecretkeys = lambda: self._gpg.list_keys(secret=True)
122
123         self._gpg = GPG(binary=self._gpgbinary,
124                         homedir=tempfile.mkdtemp())
125         leap_assert(len(listkeys()) is 0, 'Keyring not empty.')
126
127         # import keys into the keyring:
128         # concatenating ascii-armored keys, which is correctly
129         # understood by GPG.
130
131         self._gpg.import_keys("".join(
132             [x.key_data for x in publkeys + privkeys]))
133
134         # assert the number of keys in the keyring
135         leap_assert(
136             len(listkeys()) == len(publkeys) + len(privkeys),
137             'Wrong number of public keys in keyring: %d, should be %d)' %
138             (len(listkeys()), len(publkeys) + len(privkeys)))
139         leap_assert(
140             len(listsecretkeys()) == len(privkeys),
141             'Wrong number of private keys in keyring: %d, should be %d)' %
142             (len(listsecretkeys()), len(privkeys)))
143
144     def _destroy_keyring(self):
145         """
146         Securely erase the keyring.
147         """
148         # TODO: implement some kind of wiping of data or a more
149         # secure way that
150         # does not write to disk.
151
152         try:
153             for secret in [True, False]:
154                 for key in self._gpg.list_keys(secret=secret):
155                     self._gpg.delete_keys(
156                         key['fingerprint'],
157                         secret=secret)
158             leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!')
159
160         except:
161             raise
162
163         finally:
164             leap_assert(self._gpg.homedir != os.path.expanduser('~/.gnupg'),
165                         "watch out! Tried to remove default gnupg home!")
166             shutil.rmtree(self._gpg.homedir)
167
168
169 def _parse_address(address):
170     """
171     Remove name, '<', '>' and the identity suffix after the '+' until the '@'
172     e.g.: test_user+something@provider.com becomes test_user@provider.com
173     since the key belongs to the identity without the '+' suffix.
174
175     :type address: str
176     :rtype: str
177     """
178     mail_regex = '(.*<)?([\w.-]+)(\+.*)?(@[\w.-]+)(>.*)?'
179     match = re.match(mail_regex, address)
180     if match is None:
181         return None
182     return ''.join(match.group(2, 4))
183
184
185 #
186 # The OpenPGP wrapper
187 #
188
189 class OpenPGPKey(EncryptionKey):
190     """
191     Base class for OpenPGP keys.
192     """
193
194     def __init__(self, address, gpgbinary=None, **kwargs):
195         self._gpgbinary = gpgbinary
196         super(OpenPGPKey, self).__init__(address, **kwargs)
197
198     @property
199     def signatures(self):
200         """
201         Get the key signatures
202
203         :return: the key IDs that have signed the key
204         :rtype: list(str)
205         """
206         with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg:
207             res = gpg.list_sigs(self.key_id)
208             for uid, sigs in res.sigs.iteritems():
209                 if _parse_address(uid) in self.address:
210                     return sigs
211
212         return []
213
214
215 class OpenPGPScheme(EncryptionScheme):
216     """
217     A wrapper for OpenPGP keys management and use (encryption, decyption,
218     signing and verification).
219     """
220
221     # type used on the soledad documents
222     KEY_TYPE = OpenPGPKey.__name__
223     ACTIVE_TYPE = KEY_TYPE + KEYMANAGER_ACTIVE_TYPE
224
225     def __init__(self, soledad, gpgbinary=None):
226         """
227         Initialize the OpenPGP wrapper.
228
229         :param soledad: A Soledad instance for key storage.
230         :type soledad: leap.soledad.Soledad
231         :param gpgbinary: Name for GnuPG binary executable.
232         :type gpgbinary: C{str}
233         """
234         EncryptionScheme.__init__(self, soledad)
235         self._wait_indexes("get_key", "put_key")
236         self._gpgbinary = gpgbinary
237
238     #
239     # Keys management
240     #
241
242     def gen_key(self, address):
243         """
244         Generate an OpenPGP keypair bound to C{address}.
245
246         :param address: The address bound to the key.
247         :type address: str
248
249         :return: A Deferred which fires with the key bound to address, or fails
250                  with KeyAlreadyExists if key already exists in local database.
251         :rtype: Deferred
252         """
253         # make sure the key does not already exist
254         leap_assert(is_address(address), 'Not an user address: %s' % address)
255
256         def _gen_key(_):
257             with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg:
258                 # TODO: inspect result, or use decorator
259                 params = gpg.gen_key_input(
260                     key_type='RSA',
261                     key_length=4096,
262                     name_real=address,
263                     name_email=address,
264                     name_comment='')
265                 logger.info("About to generate keys... "
266                             "This might take SOME time.")
267                 gpg.gen_key(params)
268                 logger.info("Keys for %s have been successfully "
269                             "generated." % (address,))
270                 pubkeys = gpg.list_keys()
271
272                 # assert for new key characteristics
273                 leap_assert(
274                     len(pubkeys) is 1,  # a unitary keyring!
275                     'Keyring has wrong number of keys: %d.' % len(pubkeys))
276                 key = gpg.list_keys(secret=True).pop()
277                 leap_assert(
278                     len(key['uids']) is 1,  # with just one uid!
279                     'Wrong number of uids for key: %d.' % len(key['uids']))
280                 uid_match = False
281                 for uid in key['uids']:
282                     if re.match('.*<%s>$' % address, uid) is not None:
283                         uid_match = True
284                         break
285                 leap_assert(uid_match, 'Key not correctly bound to address.')
286
287                 # insert both public and private keys in storage
288                 deferreds = []
289                 for secret in [True, False]:
290                     key = gpg.list_keys(secret=secret).pop()
291                     openpgp_key = self._build_key_from_gpg(
292                         key,
293                         gpg.export_keys(key['fingerprint'], secret=secret))
294                     d = self.put_key(openpgp_key, address)
295                     deferreds.append(d)
296                 return defer.gatherResults(deferreds)
297
298         def key_already_exists(_):
299             raise errors.KeyAlreadyExists(address)
300
301         d = self.get_key(address)
302         d.addCallbacks(key_already_exists, _gen_key)
303         d.addCallback(lambda _: self.get_key(address, private=True))
304         return d
305
306     def get_key(self, address, private=False):
307         """
308         Get key bound to C{address} from local storage.
309
310         :param address: The address bound to the key.
311         :type address: str
312         :param private: Look for a private key instead of a public one?
313         :type private: bool
314
315         :return: A Deferred which fires with the OpenPGPKey bound to address,
316                  or which fails with KeyNotFound if the key was not found on
317                  local storage.
318         :rtype: Deferred
319         """
320         address = _parse_address(address)
321
322         def build_key(doc):
323             if doc is None:
324                 raise errors.KeyNotFound(address)
325             leap_assert(
326                 address in doc.content[KEY_ADDRESS_KEY],
327                 'Wrong address in key %s. Expected %s, found %s.'
328                 % (doc.content[KEY_ID_KEY], address,
329                    doc.content[KEY_ADDRESS_KEY]))
330             key = build_key_from_dict(OpenPGPKey, doc.content)
331             key._gpgbinary = self._gpgbinary
332             return key
333
334         d = self._get_key_doc(address, private)
335         d.addCallback(build_key)
336         return d
337
338     def parse_ascii_key(self, key_data):
339         """
340         Parses an ascii armored key (or key pair) data and returns
341         the OpenPGPKey keys.
342
343         :param key_data: the key data to be parsed.
344         :type key_data: str or unicode
345
346         :returns: the public key and private key (if applies) for that data.
347         :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey)
348                 the tuple may have one or both components None
349         """
350         leap_assert_type(key_data, (str, unicode))
351         # TODO: add more checks for correct key data.
352         leap_assert(key_data is not None, 'Data does not represent a key.')
353
354         priv_info, privkey = process_ascii_key(
355             key_data, self._gpgbinary, secret=True)
356         pub_info, pubkey = process_ascii_key(
357             key_data, self._gpgbinary, secret=False)
358
359         if not pubkey:
360             return (None, None)
361
362         openpgp_privkey = None
363         if privkey:
364             # build private key
365             openpgp_privkey = self._build_key_from_gpg(priv_info, privkey)
366             leap_check(pub_info['fingerprint'] == priv_info['fingerprint'],
367                        'Fingerprints for public and private key differ.',
368                        errors.KeyFingerprintMismatch)
369         # build public key
370         openpgp_pubkey = self._build_key_from_gpg(pub_info, pubkey)
371
372         return (openpgp_pubkey, openpgp_privkey)
373
374     def put_ascii_key(self, key_data, address):
375         """
376         Put key contained in ascii-armored C{key_data} in local storage.
377
378         :param key_data: The key data to be stored.
379         :type key_data: str or unicode
380         :param address: address for which this key will be active
381         :type address: str
382
383         :return: A Deferred which fires when the OpenPGPKey is in the storage.
384         :rtype: Deferred
385         """
386         leap_assert_type(key_data, (str, unicode))
387
388         openpgp_privkey = None
389         try:
390             openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data)
391         except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e:
392             return defer.fail(e)
393
394         def put_key(_, key):
395             return self.put_key(key, address)
396
397         d = defer.succeed(None)
398         if openpgp_pubkey is not None:
399             d.addCallback(put_key, openpgp_pubkey)
400         if openpgp_privkey is not None:
401             d.addCallback(put_key, openpgp_privkey)
402         return d
403
404     def put_key(self, key, address):
405         """
406         Put C{key} in local storage.
407
408         :param key: The key to be stored.
409         :type key: OpenPGPKey
410         :param address: address for which this key will be active.
411         :type address: str
412
413         :return: A Deferred which fires when the key is in the storage.
414         :rtype: Deferred
415         """
416         d = self._put_key_doc(key)
417         d.addCallback(lambda _: self._put_active_doc(key, address))
418         return d
419
420     def _put_key_doc(self, key):
421         """
422         Put key document in soledad
423
424         :type key: OpenPGPKey
425         :rtype: Deferred
426         """
427         def check_and_put(docs, key):
428             deferred_repair = defer.succeed(None)
429             if len(docs) == 0:
430                 return self._soledad.create_doc_from_json(key.get_json())
431             elif len(docs) > 1:
432                 deferred_repair = self._repair_key_docs(docs, key.key_id)
433
434             doc = docs[0]
435             oldkey = build_key_from_dict(OpenPGPKey, doc.content)
436             if key.fingerprint != oldkey.fingerprint:
437                 logger.critical(
438                     "Can't put a key whith the same key_id and different "
439                     "fingerprint: %s, %s"
440                     % (key.fingerprint, oldkey.fingerprint))
441                 return defer.fail(
442                     errors.KeyFingerprintMismatch(key.fingerprint))
443
444             # in case of an update of the key merge them with gnupg
445             with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg:
446                 gpg.import_keys(oldkey.key_data)
447                 gpg.import_keys(key.key_data)
448                 gpgkey = gpg.list_keys(secret=key.private).pop()
449                 mergedkey = self._build_key_from_gpg(
450                     gpgkey,
451                     gpg.export_keys(gpgkey['fingerprint'],
452                                     secret=key.private))
453             mergedkey.validation = max(
454                 [key.validation, oldkey.validation])
455             mergedkey.last_audited_at = oldkey.last_audited_at
456             mergedkey.refreshed_at = key.refreshed_at
457             mergedkey.encr_used = key.encr_used or oldkey.encr_used
458             mergedkey.sign_used = key.sign_used or oldkey.sign_used
459             doc.set_json(mergedkey.get_json())
460             deferred_put = self._soledad.put_doc(doc)
461
462             d = defer.gatherResults([deferred_put, deferred_repair])
463             d.addCallback(lambda res: res[0])
464             return d
465
466         d = self._soledad.get_from_index(
467             TYPE_ID_PRIVATE_INDEX,
468             self.KEY_TYPE,
469             key.key_id,
470             '1' if key.private else '0')
471         d.addCallback(check_and_put, key)
472         return d
473
474     def _put_active_doc(self, key, address):
475         """
476         Put active key document in soledad
477
478         :type key: OpenPGPKey
479         :type addresses: str
480         :rtype: Deferred
481         """
482         def check_and_put(docs):
483             if len(docs) == 1:
484                 doc = docs.pop()
485                 doc.set_json(key.get_active_json(address))
486                 d = self._soledad.put_doc(doc)
487             else:
488                 if len(docs) > 1:
489                     logger.error("There is more than one active key document "
490                                  "for the address %s" % (address,))
491                     deferreds = []
492                     for doc in docs:
493                         delete = self._soledad.delete_doc(doc)
494                         deferreds.append(delete)
495                     d = defer.gatherResults(deferreds, consumeErrors=True)
496                 else:
497                     d = defer.succeed(None)
498
499                 d.addCallback(
500                     lambda _: self._soledad.create_doc_from_json(
501                         key.get_active_json(address)))
502             return d
503
504         d = self._soledad.get_from_index(
505             TYPE_ADDRESS_PRIVATE_INDEX,
506             self.ACTIVE_TYPE,
507             address,
508             '1' if key.private else '0')
509         d.addCallback(check_and_put)
510         return d
511
512     def _get_key_doc(self, address, private=False):
513         """
514         Get the document with a key (public, by default) bound to C{address}.
515
516         If C{private} is True, looks for a private key instead of a public.
517
518         :param address: The address bound to the key.
519         :type address: str
520         :param private: Whether to look for a private key.
521         :type private: bool
522
523         :return: A Deferred which fires with the SoledadDocument with the key
524                  or None if it does not exist.
525         :rtype: Deferred
526         """
527         def get_key_from_active_doc(activedoc):
528             if len(activedoc) is 0:
529                 return None
530             leap_assert(
531                 len(activedoc) is 1,
532                 'Found more than one key for address %s!' % (address,))
533
534             key_id = activedoc[0].content[KEY_ID_KEY]
535             d = self._soledad.get_from_index(
536                 TYPE_ID_PRIVATE_INDEX,
537                 self.KEY_TYPE,
538                 key_id,
539                 '1' if private else '0')
540             d.addCallback(get_doc, key_id, activedoc)
541             return d
542
543         def get_doc(doclist, key_id, activedoc):
544             if len(doclist) == 0:
545                 logger.warning('There is no key for id %s! Self-repairing it.'
546                                % (key_id))
547                 d = self._soledad.delete_doc(activedoc)
548                 d.addCallback(lambda _: None)
549                 return d
550             elif len(doclist) > 1:
551                 d = self._repair_key_docs(doclist, key_id)
552                 d.addCallback(lambda _: doclist[0])
553                 return d
554             return doclist[0]
555
556         d = self._soledad.get_from_index(
557             TYPE_ADDRESS_PRIVATE_INDEX,
558             self.ACTIVE_TYPE,
559             address,
560             '1' if private else '0')
561         d.addCallback(get_key_from_active_doc)
562         return d
563
564     def _build_key_from_gpg(self, key, key_data):
565         """
566         Build an OpenPGPKey for C{address} based on C{key} from
567         local gpg storage.
568
569         ASCII armored GPG key data has to be queried independently in this
570         wrapper, so we receive it in C{key_data}.
571
572         :param key: Key obtained from GPG storage.
573         :type key: dict
574         :param key_data: Key data obtained from GPG storage.
575         :type key_data: str
576         :return: An instance of the key.
577         :rtype: OpenPGPKey
578         """
579         return build_gpg_key(key, key_data, self._gpgbinary)
580
581     def delete_key(self, key):
582         """
583         Remove C{key} from storage.
584
585         :param key: The key to be removed.
586         :type key: EncryptionKey
587
588         :return: A Deferred which fires when the key is deleted, or which
589                  fails with KeyNotFound if the key was not found on local
590                  storage.
591         :rtype: Deferred
592         """
593         leap_assert_type(key, OpenPGPKey)
594
595         def delete_docs(activedocs):
596             deferreds = []
597             for doc in activedocs:
598                 d = self._soledad.delete_doc(doc)
599                 deferreds.append(d)
600             return defer.gatherResults(deferreds)
601
602         def get_key_docs(_):
603             return self._soledad.get_from_index(
604                 TYPE_ID_PRIVATE_INDEX,
605                 self.KEY_TYPE,
606                 key.key_id,
607                 '1' if key.private else '0')
608
609         def delete_key(docs):
610             if len(docs) == 0:
611                 raise errors.KeyNotFound(key)
612             elif len(docs) > 1:
613                 logger.warning("There is more than one key for key_id %s"
614                                % key.key_id)
615
616             has_deleted = False
617             deferreds = []
618             for doc in docs:
619                 if doc.content['fingerprint'] == key.fingerprint:
620                     d = self._soledad.delete_doc(doc)
621                     deferreds.append(d)
622                     has_deleted = True
623             if not has_deleted:
624                 raise errors.KeyNotFound(key)
625             return defer.gatherResults(deferreds)
626
627         d = self._soledad.get_from_index(
628             TYPE_ID_PRIVATE_INDEX,
629             self.ACTIVE_TYPE,
630             key.key_id,
631             '1' if key.private else '0')
632         d.addCallback(delete_docs)
633         d.addCallback(get_key_docs)
634         d.addCallback(delete_key)
635         return d
636
637     def _repair_key_docs(self, doclist, key_id):
638         """
639         If there is more than one key for a key id try to self-repair it
640
641         :return: a Deferred that will be fired once all the deletions are
642                  completed
643         :rtype: Deferred
644         """
645         logger.error("BUG ---------------------------------------------------")
646         logger.error("There is more than one key with the same key_id %s:"
647                      % (key_id,))
648
649         def log_key_doc(doc):
650             logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY],
651                                        doc.content[KEY_FINGERPRINT_KEY]))
652
653         doclist.sort(key=lambda doc: doc.content[KEY_REFRESHED_AT_KEY],
654                      reverse=True)
655         log_key_doc(doclist[0])
656         deferreds = []
657         for doc in doclist[1:]:
658             log_key_doc(doc)
659             d = self._soledad.delete_doc(doc)
660             deferreds.append(d)
661
662         logger.error("")
663         logger.error(traceback.extract_stack())
664         logger.error("BUG (please report above info) ------------------------")
665         return defer.gatherResults(deferreds, consumeErrors=True)
666
667     #
668     # Data encryption, decryption, signing and verifying
669     #
670
671     @staticmethod
672     def _assert_gpg_result_ok(result):
673         """
674         Check if GPG result is 'ok' and log stderr outputs.
675
676         :param result: GPG results, which have a field calld 'ok' that states
677                        whether the gpg operation was successful or not.
678         :type result: object
679
680         :raise GPGError: Raised when the gpg operation was not successful.
681         """
682         stderr = getattr(result, 'stderr', None)
683         if stderr:
684             logger.debug("%s" % (stderr,))
685         if getattr(result, 'ok', None) is not True:
686             raise errors.GPGError(
687                 'Failed to encrypt/decrypt: %s' % stderr)
688
689     def encrypt(self, data, pubkey, passphrase=None, sign=None,
690                 cipher_algo='AES256'):
691         """
692         Encrypt C{data} using public @{pubkey} and sign with C{sign} key.
693
694         :param data: The data to be encrypted.
695         :type data: str
696         :param pubkey: The key used to encrypt.
697         :type pubkey: OpenPGPKey
698         :param sign: The key used for signing.
699         :type sign: OpenPGPKey
700         :param cipher_algo: The cipher algorithm to use.
701         :type cipher_algo: str
702
703         :return: The encrypted data.
704         :rtype: str
705
706         :raise EncryptError: Raised if failed encrypting for some reason.
707         """
708         leap_assert_type(pubkey, OpenPGPKey)
709         leap_assert(pubkey.private is False, 'Key is not public.')
710         keys = [pubkey]
711         if sign is not None:
712             leap_assert_type(sign, OpenPGPKey)
713             leap_assert(sign.private is True)
714             keys.append(sign)
715         with TempGPGWrapper(keys, self._gpgbinary) as gpg:
716             result = gpg.encrypt(
717                 data, pubkey.fingerprint,
718                 default_key=sign.key_id if sign else None,
719                 passphrase=passphrase, symmetric=False,
720                 cipher_algo=cipher_algo)
721             # Here we cannot assert for correctness of sig because the sig is
722             # in the ciphertext.
723             # result.ok    - (bool) indicates if the operation succeeded
724             # result.data  - (bool) contains the result of the operation
725             try:
726                 self._assert_gpg_result_ok(result)
727                 return result.data
728             except errors.GPGError as e:
729                 logger.error('Failed to decrypt: %s.' % str(e))
730                 raise errors.EncryptError()
731
732     def decrypt(self, data, privkey, passphrase=None, verify=None):
733         """
734         Decrypt C{data} using private @{privkey} and verify with C{verify} key.
735
736         :param data: The data to be decrypted.
737         :type data: str
738         :param privkey: The key used to decrypt.
739         :type privkey: OpenPGPKey
740         :param passphrase: The passphrase for the secret key used for
741                            decryption.
742         :type passphrase: str
743         :param verify: The key used to verify a signature.
744         :type verify: OpenPGPKey
745
746         :return: The decrypted data and if signature verifies
747         :rtype: (unicode, bool)
748
749         :raise DecryptError: Raised if failed decrypting for some reason.
750         """
751         leap_assert(privkey.private is True, 'Key is not private.')
752         keys = [privkey]
753         if verify is not None:
754             leap_assert_type(verify, OpenPGPKey)
755             leap_assert(verify.private is False)
756             keys.append(verify)
757         with TempGPGWrapper(keys, self._gpgbinary) as gpg:
758             try:
759                 result = gpg.decrypt(
760                     data, passphrase=passphrase, always_trust=True)
761                 self._assert_gpg_result_ok(result)
762
763                 # verify signature
764                 sign_valid = False
765                 if (verify is not None and
766                         result.valid is True and
767                         verify.fingerprint == result.pubkey_fingerprint):
768                     sign_valid = True
769
770                 return (result.data, sign_valid)
771             except errors.GPGError as e:
772                 logger.error('Failed to decrypt: %s.' % str(e))
773                 raise errors.DecryptError(str(e))
774
775     def is_encrypted(self, data):
776         """
777         Return whether C{data} was asymmetrically encrypted using OpenPGP.
778
779         :param data: The data we want to know about.
780         :type data: str
781
782         :return: Whether C{data} was encrypted using this wrapper.
783         :rtype: bool
784         """
785         with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg:
786             gpgutil = GPGUtilities(gpg)
787             return gpgutil.is_encrypted_asym(data)
788
789     def sign(self, data, privkey, digest_algo='SHA512', clearsign=False,
790              detach=True, binary=False):
791         """
792         Sign C{data} with C{privkey}.
793
794         :param data: The data to be signed.
795         :type data: str
796
797         :param privkey: The private key to be used to sign.
798         :type privkey: OpenPGPKey
799         :param digest_algo: The hash digest to use.
800         :type digest_algo: str
801         :param clearsign: If True, create a cleartext signature.
802         :type clearsign: bool
803         :param detach: If True, create a detached signature.
804         :type detach: bool
805         :param binary: If True, do not ascii armour the output.
806         :type binary: bool
807
808         :return: The ascii-armored signed data.
809         :rtype: str
810         """
811         leap_assert_type(privkey, OpenPGPKey)
812         leap_assert(privkey.private is True)
813
814         # result.fingerprint - contains the fingerprint of the key used to
815         #                      sign.
816         with TempGPGWrapper(privkey, self._gpgbinary) as gpg:
817             result = gpg.sign(data, default_key=privkey.key_id,
818                               digest_algo=digest_algo, clearsign=clearsign,
819                               detach=detach, binary=binary)
820             rfprint = privkey.fingerprint
821             privkey = gpg.list_keys(secret=True).pop()
822             kfprint = privkey['fingerprint']
823             if result.fingerprint is None:
824                 raise errors.SignFailed(
825                     'Failed to sign with key %s: %s' %
826                     (privkey['keyid'], result.stderr))
827             leap_assert(
828                 result.fingerprint == kfprint,
829                 'Signature and private key fingerprints mismatch: '
830                 '%s != %s' % (rfprint, kfprint))
831         return result.data
832
833     def verify(self, data, pubkey, detached_sig=None):
834         """
835         Verify signed C{data} with C{pubkey}, eventually using
836         C{detached_sig}.
837
838         :param data: The data to be verified.
839         :type data: str
840         :param pubkey: The public key to be used on verification.
841         :type pubkey: OpenPGPKey
842         :param detached_sig: A detached signature. If given, C{data} is
843                              verified against this detached signature.
844         :type detached_sig: str
845
846         :return: signature matches
847         :rtype: bool
848         """
849         leap_assert_type(pubkey, OpenPGPKey)
850         leap_assert(pubkey.private is False)
851         with TempGPGWrapper(pubkey, self._gpgbinary) as gpg:
852             result = None
853             if detached_sig is None:
854                 result = gpg.verify(data)
855             else:
856                 # to verify using a detached sig we have to use
857                 # gpg.verify_file(), which receives the data as a binary
858                 # stream and the name of a file containing the signature.
859                 sf, sfname = tempfile.mkstemp()
860                 with os.fdopen(sf, 'w') as sfd:
861                     sfd.write(detached_sig)
862                 result = gpg.verify_file(io.BytesIO(data), sig_file=sfname)
863                 os.unlink(sfname)
864             gpgpubkey = gpg.list_keys().pop()
865             valid = result.valid
866             rfprint = result.fingerprint
867             kfprint = gpgpubkey['fingerprint']
868             return valid and rfprint == kfprint
869
870
871 def process_ascii_key(key_data, gpgbinary, secret=False):
872     with TempGPGWrapper(gpgbinary=gpgbinary) as gpg:
873         try:
874             gpg.import_keys(key_data)
875             info = gpg.list_keys(secret=secret).pop()
876             key = gpg.export_keys(info['fingerprint'], secret=secret)
877         except IndexError:
878             info = {}
879             key = None
880     return info, key
881
882
883 def build_gpg_key(key_info, key_data, gpgbinary=None):
884     expiry_date = None
885     if key_info['expires']:
886         expiry_date = datetime.fromtimestamp(int(key_info['expires']))
887     address = []
888     for uid in key_info['uids']:
889         address.append(_parse_address(uid))
890
891     return OpenPGPKey(
892         address,
893         gpgbinary=gpgbinary,
894         key_id=key_info['keyid'],
895         fingerprint=key_info['fingerprint'],
896         key_data=key_data,
897         private=True if key_info['type'] == 'sec' else False,
898         length=int(key_info['length']),
899         expiry_date=expiry_date,
900         refreshed_at=datetime.now())