From e0f450ab47b58f43ffdaa349f01d816fa7558d4a Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 7 Mar 2013 17:23:05 -0300 Subject: [PATCH 1/6] Move source files to proper subdirectory. --- util.py | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 util.py diff --git a/util.py b/util.py new file mode 100644 index 0000000..c64d4c5 --- /dev/null +++ b/util.py @@ -0,0 +1,187 @@ +""" +Utilities for Soledad. +""" + +import os +import gnupg +import re +from gnupg import ( + logger, + _is_sequence, + _make_binary_stream, +) + + +class ListPackets(): + """ + Handle status messages for --list-packets. + """ + + def __init__(self, gpg): + self.gpg = gpg + self.nodata = None + self.key = None + self.need_passphrase = None + self.need_passphrase_sym = None + self.userid_hint = None + + def handle_status(self, key, value): + # TODO: write tests for handle_status + if key == 'NODATA': + self.nodata = True + if key == 'ENC_TO': + # This will only capture keys in our keyring. In the future we + # may want to include multiple unknown keys in this list. + self.key, _, _ = value.split() + if key == 'NEED_PASSPHRASE': + self.need_passphrase = True + if key == 'NEED_PASSPHRASE_SYM': + self.need_passphrase_sym = True + if key == 'USERID_HINT': + self.userid_hint = value.strip().split() + + +class GPGWrapper(gnupg.GPG): + """ + This is a temporary class for handling GPG requests, and should be + replaced by a more general class used throughout the project. + """ + + GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" + GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS + + def __init__(self, gpgbinary=GNUPG_BINARY, gnupghome=GNUPG_HOME, + verbose=False, use_agent=False, keyring=None, options=None): + super(GPGWrapper, self).__init__(gnupghome=gnupghome, + gpgbinary=gpgbinary, + verbose=verbose, + use_agent=use_agent, + keyring=keyring, + options=options) + self.result_map['list-packets'] = ListPackets + + def find_key_by_email(self, email, secret=False): + """ + Find user's key based on their email. + """ + for key in self.list_keys(secret=secret): + for uid in key['uids']: + if re.search(email, uid): + return key + raise LookupError("GnuPG public key for email %s not found!" % email) + + def find_key_by_subkey(self, subkey): + for key in self.list_keys(): + for sub in key['subkeys']: + if sub[0] == subkey: + return key + raise LookupError( + "GnuPG public key for subkey %s not found!" % subkey) + + def find_key_by_keyid(self, keyid): + for key in self.list_keys(): + if keyid == key['keyid']: + return key + raise LookupError( + "GnuPG public key for subkey %s not found!" % subkey) + + def encrypt(self, data, recipient, sign=None, always_trust=True, + passphrase=None, symmetric=False): + """ + Encrypt data using GPG. + """ + # TODO: devise a way so we don't need to "always trust". + return super(GPGWrapper, self).encrypt(data, recipient, sign=sign, + always_trust=always_trust, + passphrase=passphrase, + symmetric=symmetric, + cipher_algo='AES256') + + def decrypt(self, data, always_trust=True, passphrase=None): + """ + Decrypt data using GPG. + """ + # TODO: devise a way so we don't need to "always trust". + return super(GPGWrapper, self).decrypt(data, + always_trust=always_trust, + passphrase=passphrase) + + def send_keys(self, keyserver, *keyids): + """ + Send keys to a keyserver + """ + result = self.result_map['list'](self) + gnupg.logger.debug('send_keys: %r', keyids) + data = gnupg._make_binary_stream("", self.encoding) + args = ['--keyserver', keyserver, '--send-keys'] + args.extend(keyids) + self._handle_io(args, data, result, binary=True) + gnupg.logger.debug('send_keys result: %r', result.__dict__) + data.close() + return result + + def encrypt_file(self, file, recipients, sign=None, + always_trust=False, passphrase=None, + armor=True, output=None, symmetric=False, + cipher_algo=None): + "Encrypt the message read from the file-like object 'file'" + args = ['--encrypt'] + if symmetric: + args = ['--symmetric'] + if cipher_algo: + args.append('--cipher-algo %s' % cipher_algo) + else: + args = ['--encrypt'] + if not _is_sequence(recipients): + recipients = (recipients,) + for recipient in recipients: + args.append('--recipient "%s"' % recipient) + if armor: # create ascii-armored output - set to False for binary + args.append('--armor') + if output: # write the output to a file with the specified name + if os.path.exists(output): + os.remove(output) # to avoid overwrite confirmation message + args.append('--output "%s"' % output) + if sign: + args.append('--sign --default-key "%s"' % sign) + if always_trust: + args.append("--always-trust") + result = self.result_map['crypt'](self) + self._handle_io(args, file, result, passphrase=passphrase, binary=True) + logger.debug('encrypt result: %r', result.data) + return result + + def list_packets(self, raw_data): + args = ["--list-packets"] + result = self.result_map['list-packets'](self) + self._handle_io( + args, + _make_binary_stream(raw_data, self.encoding), + result, + ) + return result + + def encrypted_to(self, raw_data): + """ + Return the key to which raw_data is encrypted to. + """ + # TODO: make this support multiple keys. + result = self.list_packets(raw_data) + if not result.key: + raise LookupError( + "Content is not encrypted to a GnuPG key!") + try: + return self.find_key_by_keyid(result.key) + except: + return self.find_key_by_subkey(result.key) + + def is_encrypted_sym(self, raw_data): + result = self.list_packets(raw_data) + return bool(result.need_passphrase_sym) + + def is_encrypted_asym(self, raw_data): + result = self.list_packets(raw_data) + return bool(result.key) + + def is_encrypted(self, raw_data): + self.is_encrypted_asym() or self.is_encrypted_sym() From 2953bad41ed85ea60c219dd5e89088823bfb121d Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 10 Mar 2013 15:58:15 -0300 Subject: [PATCH 2/6] Add namespace package. From fbff6fb6ddace0316d6f48008a579f707a144527 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 10 Mar 2013 17:11:50 -0300 Subject: [PATCH 3/6] Remove monkeypatch logging for now. From 58fd7bedac08e6692819b3044436c0640f00d8b5 Mon Sep 17 00:00:00 2001 From: drebs Date: Sun, 10 Mar 2013 18:45:01 -0300 Subject: [PATCH 4/6] Add packages to namespace. From faadc426cf0ac58572217256cc5794d359a8168c Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 11 Mar 2013 12:20:11 -0300 Subject: [PATCH 5/6] Add recovery document export. From 37f70cd98622c41a67be2fde4e7200ca97168e8d Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 11 Mar 2013 13:14:14 -0300 Subject: [PATCH 6/6] Add import recovery document.