summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2016-12-06 14:41:20 +0000
committerPatrick Totzke <patricktotzke@gmail.com>2016-12-06 14:41:20 +0000
commit30dfab46509ac54a918a056bcc890b45df9c2b6a (patch)
tree56a7664318175f8347857dbd1487d74fb6ce7fb2 /alot
parent1b4abe0b3c70457044c3ddaab7f023c415a3f889 (diff)
parent39e4208cd8a677d986cf9489769e19bccec30c8b (diff)
Merge branch '0.3.8-settings-encrypt-by-default-854'
Diffstat (limited to 'alot')
-rw-r--r--alot/account.py15
-rw-r--r--alot/commands/envelope.py20
-rw-r--r--alot/commands/globals.py25
-rw-r--r--alot/commands/utils.py8
-rw-r--r--alot/crypto.py38
-rw-r--r--alot/defaults/alot.rc.spec22
6 files changed, 112 insertions, 16 deletions
diff --git a/alot/account.py b/alot/account.py
index db1ef288..017fa31e 100644
--- a/alot/account.py
+++ b/alot/account.py
@@ -54,7 +54,7 @@ class Account(object):
signature_filename=None, signature_as_attachment=False,
sent_box=None, sent_tags=['sent'], draft_box=None,
draft_tags=['draft'], abook=None, sign_by_default=False,
- encrypt_by_default=False,
+ encrypt_by_default=u"none",
**rest):
self.address = address
self.aliases = aliases
@@ -65,12 +65,23 @@ class Account(object):
self.signature_filename = signature_filename
self.signature_as_attachment = signature_as_attachment
self.sign_by_default = sign_by_default
- self.encrypt_by_default = encrypt_by_default
self.sent_box = sent_box
self.sent_tags = sent_tags
self.draft_box = draft_box
self.draft_tags = draft_tags
self.abook = abook
+ # Handle encrypt_by_default in an backwards compatible way. The
+ # logging info call can later be upgraded to warning or error.
+ encrypt_by_default = encrypt_by_default.lower()
+ msg = "Deprecation warning: The format for the encrypt_by_default " \
+ "option changed. Please use 'none', 'all' or 'trusted'."
+ if encrypt_by_default in (u"true", u"yes", u"1"):
+ encrypt_by_default = u"all"
+ logging.info(msg)
+ elif encrypt_by_default in (u"false", u"no", u"0"):
+ encrypt_by_default = u"none"
+ logging.info(msg)
+ self.encrypt_by_default = encrypt_by_default
def get_addresses(self):
"""return all email addresses connected to this account, in order of
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py
index e1a6eca7..c8362037 100644
--- a/alot/commands/envelope.py
+++ b/alot/commands/envelope.py
@@ -500,6 +500,7 @@ class SignCommand(Command):
@registerCommand(MODE, 'encrypt', forced={'action': 'encrypt'}, arguments=[
+ (['--trusted'], {'action': 'store_true', 'help': 'only add trusted keys'}),
(['keyids'], {'nargs': argparse.REMAINDER,
'help': 'keyid of the key to encrypt with'})],
help='request encryption of message before sendout')
@@ -507,6 +508,8 @@ class SignCommand(Command):
help='remove request to encrypt message before sending')
@registerCommand(MODE, 'toggleencrypt', forced={'action': 'toggleencrypt'},
arguments=[
+ (['--trusted'], {'action': 'store_true',
+ 'help': 'only add trusted keys'}),
(['keyids'], {'nargs': argparse.REMAINDER,
'help': 'keyid of the key to encrypt with'})],
help='toggle if message should be encrypted before sendout')
@@ -516,16 +519,19 @@ class SignCommand(Command):
'help': 'keyid of the key to encrypt with'})],
help='do not encrypt to given recipient key')
class EncryptCommand(Command):
- def __init__(self, action=None, keyids=None, **kwargs):
+ def __init__(self, action=None, keyids=None, trusted=False, **kwargs):
"""
:param action: wether to encrypt/unencrypt/toggleencrypt
:type action: str
:param keyid: the id of the key to encrypt
:type keyid: str
+ :param trusted: wether to filter keys and only use trusted ones
+ :type trusted: bool
"""
self.encrypt_keys = keyids
self.action = action
+ self.trusted = trusted
Command.__init__(self, **kwargs)
@inlineCallbacks
@@ -556,14 +562,22 @@ class EncryptCommand(Command):
continue
match = re.search("<(.*@.*)>", recipient)
if match:
- recipient = match.group(0)
+ recipient = match.group(1)
self.encrypt_keys.append(recipient)
logging.debug("encryption keys: " + str(self.encrypt_keys))
- keys = yield get_keys(ui, self.encrypt_keys)
+ keys = yield get_keys(ui, self.encrypt_keys,
+ signed_only=self.trusted)
+ if self.trusted:
+ logging.debug("filtered encrytion keys: " +
+ " ".join(x.uids[0].uid for x in keys.values()))
if keys:
envelope.encrypt_keys.update(keys)
else:
envelope.encrypt = False
+ if not envelope.encrypt:
+ # This is an extra conditional as it can even happen if encrypt is
+ # True.
+ envelope.encrypt_keys = {}
# reload buffer
ui.current_buffer.rebuild()
diff --git a/alot/commands/globals.py b/alot/commands/globals.py
index d234f54f..519cf2aa 100644
--- a/alot/commands/globals.py
+++ b/alot/commands/globals.py
@@ -851,8 +851,20 @@ class ComposeCommand(Command):
logging.debug('attaching: ' + a)
# set encryption if needed
- if self.encrypt or account.encrypt_by_default:
+ if self.encrypt or account.encrypt_by_default == u"all":
+ logging.debug("Trying to encrypt message because encrypt={} and "
+ "encrypt_by_default={}".format(
+ self.encrypt, account.encrypt_by_default))
yield self._set_encrypt(ui, self.envelope)
+ elif account.encrypt_by_default == u"trusted":
+ logging.debug("Trying to encrypt message because "
+ "account.encrypt_by_default={}".format(
+ account.encrypt_by_default))
+ yield self._set_encrypt(ui, self.envelope, trusted_only=True)
+ else:
+ logging.debug(
+ "No encryption by default, encrypt_by_default={}".format(
+ account.encrypt_by_default))
cmd = commands.envelope.EditCommand(envelope=self.envelope,
spawn=self.force_spawn,
@@ -860,26 +872,31 @@ class ComposeCommand(Command):
ui.apply_command(cmd)
@inlineCallbacks
- def _set_encrypt(self, ui, envelope):
+ def _set_encrypt(self, ui, envelope, trusted_only=False):
"""Find and set the encryption keys in an envolope.
:param ui: the main user interface object
:type ui: alot.ui.UI
:param envolope: the envolope buffer object
:type envolope: alot.buffers.EnvelopeBuffer
+ :param trusted_only: only add keys to the list of encryption
+ keys whose uid is signed (trusted to belong to the key)
+ :type trusted_only: bool
"""
encrypt_keys = []
for recipient in envelope.headers['To'][0].split(','):
+ recipient = recipient.strip()
if not recipient:
continue
match = re.search("<(.*@.*)>", recipient)
if match:
- recipient = match.group(0)
+ recipient = match.group(1)
encrypt_keys.append(recipient)
logging.debug("encryption keys: " + str(encrypt_keys))
- keys = yield get_keys(ui, encrypt_keys, block_error=self.encrypt)
+ keys = yield get_keys(ui, encrypt_keys, block_error=self.encrypt,
+ signed_only=trusted_only)
if keys:
envelope.encrypt_keys.update(keys)
envelope.encrypt = True
diff --git a/alot/commands/utils.py b/alot/commands/utils.py
index 5ac8a74c..48d7aac9 100644
--- a/alot/commands/utils.py
+++ b/alot/commands/utils.py
@@ -8,7 +8,7 @@ from alot import crypto
@inlineCallbacks
-def get_keys(ui, encrypt_keyids, block_error=False):
+def get_keys(ui, encrypt_keyids, block_error=False, signed_only=False):
"""Get several keys from the GPG keyring. The keys are selected by keyid
and are checked if they can be used for encryption.
@@ -19,6 +19,9 @@ def get_keys(ui, encrypt_keyids, block_error=False):
:param block_error: wether error messages for the user should expire
automatically or block the ui
:type block_error: bool
+ :param signed_only: only return keys whose uid is signed (trusted to belong
+ to the key)
+ :type signed_only: bool
:returns: the available keys indexed by their key hash
:rtype: dict(str->gpgme.Key)
@@ -26,7 +29,8 @@ def get_keys(ui, encrypt_keyids, block_error=False):
keys = {}
for keyid in encrypt_keyids:
try:
- key = crypto.get_key(keyid, validate=True, encrypt=True)
+ key = crypto.get_key(keyid, validate=True, encrypt=True,
+ signed_only=signed_only)
except GPGProblem as e:
if e.code == GPGCode.AMBIGUOUS_NAME:
possible_keys = crypto.list_keys(hint=keyid)
diff --git a/alot/crypto.py b/alot/crypto.py
index 5565b890..5f9290fd 100644
--- a/alot/crypto.py
+++ b/alot/crypto.py
@@ -54,7 +54,8 @@ def RFC3156_micalg_from_algo(hash_algo):
return 'pgp-' + hash_algo.lower()
-def get_key(keyid, validate=False, encrypt=False, sign=False):
+def get_key(keyid, validate=False, encrypt=False, sign=False,
+ signed_only=False):
"""
Gets a key from the keyring by filtering for the specified keyid, but
only if the given keyid is specific enough (if it matches multiple
@@ -63,12 +64,22 @@ def get_key(keyid, validate=False, encrypt=False, sign=False):
If validate is True also make sure that returned key is not invalid,
revoked or expired. In addition if encrypt or sign is True also validate
that key is valid for that action. For example only keys with private key
- can sign.
+ can sign. If signed_only is True make sure that the user id can be can be
+ trusted to belong to the key (is signed). This last check will only work if
+ the keyid is part of the user id associated with the key, not if it is part
+ of the key fingerprint.
:param keyid: filter term for the keyring (usually a key ID)
+ :type keyid: str
:param validate: validate that returned keyid is valid
+ :type validate: bool
:param encrypt: when validating confirm that returned key can encrypt
+ :type encrypt: bool
:param sign: when validating confirm that returned key can sign
+ :type sign: bool
+ :param signed_only: only return keys whose uid is signed (trusted to
+ belong to the key)
+ :type signed_only: bool
:rtype: gpgme.Key
"""
ctx = gpgme.Context()
@@ -117,6 +128,9 @@ def get_key(keyid, validate=False, encrypt=False, sign=False):
code=GPGCode.NOT_FOUND)
else:
raise e
+ if signed_only and not check_uid_validity(key, keyid):
+ raise GPGProblem("Can not find a trusworthy key for '" + keyid + "'.",
+ code=GPGCode.NOT_FOUND)
return key
@@ -259,3 +273,23 @@ def validate_key(key, sign=False, encrypt=False):
if sign and not key.can_sign:
raise GPGProblem("The key \"" + key.uids[0].uid + "\" can not sign.",
code=GPGCode.KEY_CANNOT_SIGN)
+
+
+def check_uid_validity(key, email):
+ """Check that a the email belongs to the given key. Also check the trust
+ level of this connection. Only if the trust level is high enough (>=4) the
+ email is assumed to belong to the key.
+
+ :param key: the GPG key to which the email should belong
+ :type key: gpgme.Key
+ :param email: the email address that should belong to the key
+ :type email: str
+ :returns: whether the key can be assumed to belong to the given email
+ :rtype: bool
+
+ """
+ for key_uid in key.uids:
+ if email == key_uid.email and not key_uid.revoked and \
+ not key_uid.invalid and key_uid.validity >= 4:
+ return True
+ return False
diff --git a/alot/defaults/alot.rc.spec b/alot/defaults/alot.rc.spec
index a02fecd5..0967bf4f 100644
--- a/alot/defaults/alot.rc.spec
+++ b/alot/defaults/alot.rc.spec
@@ -229,7 +229,7 @@ prefer_plaintext = boolean(default=False)
# messages in that thread.
msg_summary_hides_threadwide_tags = boolean(default=True)
-# Key bindings
+# Key bindings
[bindings]
__many__ = string(default=None)
[[___many___]]
@@ -299,8 +299,24 @@ msg_summary_hides_threadwide_tags = boolean(default=True)
# Outgoing messages will be GPG signed by default if this is set to True.
sign_by_default = boolean(default=False)
- # Outgoing messages will be GPG encrypted by default if this is set to True.
- encrypt_by_default = boolean(default=False)
+ # Alot will try to GPG encrypt outgoing messages by default when this
+ # is set to `all` or `trusted`. If set to `all` the message will be
+ # encrypted for all recipients for who a key is available in the key
+ # ring. If set to `trusted` it will be encrypted to all
+ # recipients if a trusted key is available for all recipients (one
+ # where the user id for the key is signed with a trusted signature).
+ #
+ # .. note:: If the message will not be encrypted by default you can
+ # still use the :ref:`toggleencrypt
+ # <cmd.envelope.toggleencrypt>`, :ref:`encrypt
+ # <cmd.envelope.encrypt>` and :ref:`unencrypt
+ # <cmd.envelope.unencrypt>` commands to encrypt it.
+ # .. note:: The values `True` and `False` are interpreted as `all` and
+ # `none` respectively. They are kept for backwards
+ # compatibility to give users a change to migrate to the new
+ # option type. They might become deprecated in future
+ # versions.
+ encrypt_by_default = option('all', 'none', 'trusted', 'True', 'False', 'true', 'false', 'Yes', 'No', 'yes', 'no', '1', '0', default='none')
# The GPG key ID you want to use with this account. If unset, alot will
# use your default key.