summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alot/db/utils.py101
-rw-r--r--tests/db/utils_test.py1
2 files changed, 62 insertions, 40 deletions
diff --git a/alot/db/utils.py b/alot/db/utils.py
index 8f71f20a..198966ba 100644
--- a/alot/db/utils.py
+++ b/alot/db/utils.py
@@ -29,6 +29,9 @@ charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
X_SIGNATURE_VALID_HEADER = 'X-Alot-OpenPGP-Signature-Valid'
X_SIGNATURE_MESSAGE_HEADER = 'X-Alot-OpenPGP-Signature-Message'
+_APP_PGP_SIG = 'application/pgp-signature'
+_APP_PGP_ENC = 'application/pgp-encrypted'
+
def add_signature_headers(mail, sigs, error_msg):
'''Add pseudo headers to the mail indicating whether the signature
@@ -90,6 +93,51 @@ def get_params(mail, failobj=None, header='content-type', unquote=True):
return {k.lower(): v for k, v in mail.get_params(failobj, header, unquote)}
+def _handle_signatures(original, message, params):
+ """Shared code for handling message signatures.
+
+ RFC 3156 is quite strict:
+ * exactly two messages
+ * the second is of type 'application/pgp-signature'
+ * the second contains the detached signature
+
+ :param original: The original top-level mail. This is required to attache
+ special headers to
+ :type original: :class:`email.message.Message`
+ :param message: The multipart/signed payload to verify
+ :type message: :class:`email.message.Message`
+ :param params: the message parameters as returned by :func:`get_params`
+ :type params: dict[str, str]
+ """
+ malformed = False
+ if len(message.get_payload()) != 2:
+ malformed = u'expected exactly two messages, got {0}'.format(
+ len(message.get_payload()))
+ else:
+ ct = message.get_payload(1).get_content_type()
+ if ct != _APP_PGP_SIG:
+ malformed = u'expected Content-Type: {0}, got: {1}'.format(
+ _APP_PGP_SIG, ct)
+
+ # 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 params.get('micalg', 'nothing').startswith('pgp-'):
+ malformed = u'expected micalg=pgp-..., got: {0}'.format(
+ params.get('micalg', 'nothing'))
+
+ sigs = []
+ if not malformed:
+ try:
+ sigs = crypto.verify_detached(
+ helper.email_as_string(message.get_payload(0)),
+ message.get_payload(1).get_payload())
+ except GPGProblem as e:
+ malformed = unicode(e)
+
+ add_signature_headers(original, sigs, malformed)
+
+
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
@@ -108,50 +156,25 @@ def message_from_file(handle):
del m[X_SIGNATURE_MESSAGE_HEADER]
p = get_params(m)
- app_pgp_sig = 'application/pgp-signature'
- app_pgp_enc = 'application/pgp-encrypted'
# handle OpenPGP signed data
if (m.is_multipart() and
m.get_content_subtype() == 'signed' and
- p.get('protocol') == app_pgp_sig):
- # 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 = u'expected exactly two messages, got {0}'.format(
- len(m.get_payload()))
- else:
- ct = m.get_payload(1).get_content_type()
- if ct != app_pgp_sig:
- malformed = u'expected Content-Type: {0}, got: {1}'.format(
- app_pgp_sig, ct)
-
- # 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.get('micalg', 'nothing').startswith('pgp-'):
- malformed = u'expected micalg=pgp-..., got: {0}'.format(
- p.get('micalg', 'nothing'))
-
- sigs = []
- if not malformed:
- try:
- sigs = crypto.verify_detached(
- helper.email_as_string(m.get_payload(0)),
- m.get_payload(1).get_payload())
- except GPGProblem as e:
- malformed = unicode(e)
-
- add_signature_headers(m, sigs, malformed)
-
+ p.get('protocol') == _APP_PGP_SIG):
+ _handle_signatures(m, m, p)
+ elif (m.is_multipart() and m.get_content_subtype() == 'mixed' and
+ m.get_payload(0).get_content_subtype() == 'signed'):
+ p = get_params(m.get_payload(0))
+ if p.get('protocol') == _APP_PGP_SIG:
+ _handle_signatures(m, m.get_payload(0), p)
+
+ # XXX: It is presumably possible to put an encrypted message in a
+ # multipart/mixed, but I have no such examples and haven't looked through
+ # RFC's thuroughly
# handle OpenPGP encrypted data
elif (m.is_multipart() and
m.get_content_subtype() == 'encrypted' and
- p.get('protocol') == app_pgp_enc and
+ p.get('protocol') == _APP_PGP_ENC and
'Version: 1' in m.get_payload(0).get_payload()):
# RFC 3156 is quite strict:
# * exactly two messages
@@ -162,9 +185,9 @@ def message_from_file(handle):
malformed = False
ct = m.get_payload(0).get_content_type()
- if ct != app_pgp_enc:
+ if ct != _APP_PGP_ENC:
malformed = u'expected Content-Type: {0}, got: {1}'.format(
- app_pgp_enc, ct)
+ _APP_PGP_ENC, ct)
want = 'application/octet-stream'
ct = m.get_payload(1).get_content_type()
diff --git a/tests/db/utils_test.py b/tests/db/utils_test.py
index ea0b6f20..d2c92577 100644
--- a/tests/db/utils_test.py
+++ b/tests/db/utils_test.py
@@ -593,7 +593,6 @@ class TestMessageFromFile(TestCaseClassCleanup):
self.assertIn('Malformed OpenPGP message:',
m.get_payload(2).get_payload())
- @unittest.expectedFailure
def test_signed_in_multipart_mixed(self):
"""It is valid to encapsulate a multipart/signed payload inside a
multipart/mixed payload, verify that works.