diff options
author | Justus Winter <4winter@informatik.uni-hamburg.de> | 2013-05-24 02:12:22 +0200 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2013-06-16 21:17:09 +0100 |
commit | 223cb4cf93684242bf701cea87ae6b4cdffbc891 (patch) | |
tree | 9a888fae878abe61f1a4763c81fd004d4faf8adb /alot/db/utils.py | |
parent | 5be498b1b22ba567fe6e62eb4a7d5a116f543952 (diff) |
Parse and decrypt OpenPGP encrypted data
Parse and decrypt OpenPGP encrypted data as specified by RFC 3156. If
such a message is detected and found to be well-formed, it is
decrypted and any MIME messages found within the plain text are
attached to the original 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 | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/alot/db/utils.py b/alot/db/utils.py index 545ba2b0..9ddd6524 100644 --- a/alot/db/utils.py +++ b/alot/db/utils.py @@ -11,6 +11,7 @@ charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8') from email.iterators import typed_subpart_iterator import logging import mailcap +from cStringIO import StringIO import alot.crypto as crypto import alot.helper as helper @@ -111,9 +112,89 @@ def message_from_file(handle): add_signature_headers(m, sigs, malformed) + # handle OpenPGP encrypted data + elif (m.is_multipart() and + m.get_payload(0).get_content_type() == 'application/pgp-encrypted' and + m.get_payload(0).get_payload() == 'Version: 1'): + # RFC 3156 is quite strict: + # * exactly two messages + # * the first is of type 'application/pgp-encrypted' + # * the first contains 'Version: 1' + # * the second is of type 'application/octet-stream' + # * the second contains the encrypted and possibly signed data + malformed = False + + want = 'application/octet-stream' + ct = m.get_payload(1).get_content_type() + if ct != want: + malformed = 'expected Content-Type: {0}, got: {1}'.format(want, ct) + + if not malformed: + try: + sigs, d = crypto.decrypt_verify(m.get_payload(1).get_payload()) + except GPGProblem as e: + # signature verification failures end up here too if + # the combined method is used, currently this prevents + # the interpretation of the recovered plain text + # mail. maybe that's a feature. + malformed = str(e) + else: + # parse decrypted message + n = message_from_string(d) + + # add the decrypted message to m. note that n contains + # all the attachments, no need to walk over n here. + m.attach(n) + + # add any defects found + m.defects.extend(n.defects) + + # there are two methods for both signed and encrypted + # data, one is called 'RFC 1847 Encapsulation' by + # RFC 3156, and one is the 'Combined method'. + if len(sigs) == 0: + # 'RFC 1847 Encapsulation', the signature is a + # detached signature found in the recovered mime + # message of type multipart/signed. + if X_SIGNATURE_VALID_HEADER in n: + for k in (X_SIGNATURE_VALID_HEADER, + X_SIGNATURE_MESSAGE_HEADER): + m[k] = n[k] + else: + # an encrypted message without signatures + # should arouse some suspicion, better warn + # the user + add_signature_headers(m, [], 'no signature found') + else: + # 'Combined method', the signatures are returned + # by the decrypt_verify function. + + # note that if we reached this point, we know the + # signatures are valid. if they were not valid, + # the else block of the current try would not have + # been executed + add_signature_headers(m, sigs, '') + + if malformed: + msg = 'Malformed OpenPGP message: {0}'.format(malformed) + m.attach(email.message_from_string(msg)) + return m +def message_from_string(s): + '''Reads a mail from the given string. This is the equivalent of + :func:`email.message_from_string` which does nothing but to wrap + the given string in a StringIO object and to call + :func:`email.message_from_file`. + + Please refer to the documentation of :func:`message_from_file` for + details. + + ''' + return message_from_file(StringIO(s)) + + def extract_headers(mail, headers=None): """ returns subset of this messages headers as human-readable format: |