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