summaryrefslogtreecommitdiff
path: root/alot/db/utils.py
diff options
context:
space:
mode:
authorJustus Winter <4winter@informatik.uni-hamburg.de>2013-05-24 17:50:24 +0200
committerPatrick Totzke <patricktotzke@gmail.com>2013-06-16 21:16:57 +0100
commit5be498b1b22ba567fe6e62eb4a7d5a116f543952 (patch)
treea6fd15b02971673f1b8a1fb2560dfa876bcd16c4 /alot/db/utils.py
parentc39e7684769fa31ddd7d0200d5baa7e25476275d (diff)
Verify OpenPGP signatures and display the result
Verify OpenPGP signatures as specified in RFC 3156. Display the result in the header list above the message. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de>
Diffstat (limited to 'alot/db/utils.py')
-rw-r--r--alot/db/utils.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/alot/db/utils.py b/alot/db/utils.py
index 1280667a..545ba2b0 100644
--- a/alot/db/utils.py
+++ b/alot/db/utils.py
@@ -12,13 +12,107 @@ from email.iterators import typed_subpart_iterator
import logging
import mailcap
+import alot.crypto as crypto
import alot.helper as helper
+from alot.errors import GPGProblem
from alot.settings import settings
from alot.helper import string_sanitize
from alot.helper import string_decode
from alot.helper import parse_mailcap_nametemplate
from alot.helper import split_commandstring
+X_SIGNATURE_VALID_HEADER = 'X-Alot-OpenPGP-Signature-Valid'
+X_SIGNATURE_MESSAGE_HEADER = 'X-Alot-OpenPGP-Signature-Message'
+
+
+def add_signature_headers(mail, sigs, error_msg):
+ '''Add pseudo headers to the mail indicating whether the signature
+ verification was successful.
+
+ :param mail: :class:`email.message.Message` the message to entitle
+ :param sigs: list of :class:`gpgme.Signature`
+ :param error_msg: `str` containing an error message, the empty
+ string indicating no error
+ '''
+ sig_from = ''
+
+ if len(sigs) == 0:
+ error_msg = error_msg or 'no signature found'
+ else:
+ try:
+ sig_from = crypto.get_key(sigs[0].fpr).uids[0].uid
+ except:
+ sig_from = sigs[0].fpr
+
+ mail.add_header(
+ X_SIGNATURE_VALID_HEADER,
+ 'False' if error_msg else 'True',
+ )
+ mail.add_header(
+ X_SIGNATURE_MESSAGE_HEADER,
+ 'Invalid: {0}'.format(error_msg)
+ if error_msg else
+ 'Valid: {0}'.format(sig_from),
+ )
+
+
+def message_from_file(handle):
+ '''Reads a mail from the given file-like object and returns an email
+ object, very much like email.message_from_file. In addition to
+ that OpenPGP encrypted data is detected and decrypted. If this
+ succeeds, any mime messages found in the recovered plaintext
+ message are added to the returned message object.
+
+ :param handle: a file-like object
+ :returns: :class:`email.message.Message` possibly augmented with decrypted data
+ '''
+ m = email.message_from_file(handle)
+
+ # make sure noone smuggles a token in (data from m is untrusted)
+ del m[X_SIGNATURE_VALID_HEADER]
+ del m[X_SIGNATURE_MESSAGE_HEADER]
+
+ # handle OpenPGP signed data
+ if m.is_multipart() and m.get_content_subtype() == 'signed':
+ # RFC 3156 is quite strict:
+ # * exactly two messages
+ # * the second is of type 'application/pgp-signature'
+ # * the second contains the detached signature
+
+ malformed = False
+ if len(m.get_payload()) != 2:
+ malformed = 'expected exactly two messages, got {0}'.format(
+ len(m.get_payload()))
+
+ want = 'application/pgp-signature'
+ ct = m.get_payload(1).get_content_type()
+ if ct != want:
+ malformed = 'expected Content-Type: {0}, got: {1}'.format(
+ want, ct)
+
+ p = {k:v for k, v in m.get_params()}
+ if p['protocol'] != want:
+ malformed = 'expected protocol={0}, got: {1}'.format(
+ want, p['protocol'])
+
+ # TODO: RFC 3156 says the alg has to be lower case, but I've
+ # seen a message with 'PGP-'. maybe we should be more
+ # permissive here, or maybe not, this is crypto stuff...
+ if not p['micalg'].startswith('pgp-'):
+ malformed = 'expected micalg=pgp-..., got: {0}'.format(p['micalg'])
+
+ sigs = []
+ if not malformed:
+ try:
+ sigs = crypto.verify_detached(m.get_payload(0).as_string(),
+ m.get_payload(1).get_payload())
+ except GPGProblem as e:
+ malformed = str(e)
+
+ add_signature_headers(m, sigs, malformed)
+
+ return m
+
def extract_headers(mail, headers=None):
"""