diff options
author | Justus Winter <4winter@informatik.uni-hamburg.de> | 2013-05-24 17:50:24 +0200 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2013-06-16 21:16:57 +0100 |
commit | 5be498b1b22ba567fe6e62eb4a7d5a116f543952 (patch) | |
tree | a6fd15b02971673f1b8a1fb2560dfa876bcd16c4 /alot/db/utils.py | |
parent | c39e7684769fa31ddd7d0200d5baa7e25476275d (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.py | 94 |
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): """ |