From 613d2a3850d5d57c95dde5b6076f568fde6f1ff7 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 12 Jul 2016 01:13:01 +0200 Subject: [feat] use a Replace namedtuple to flag replacements --- memoryhole/protection.py | 65 +++++++++++++++++++++++++++++------------------- tests/test_protection.py | 12 ++++----- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/memoryhole/protection.py b/memoryhole/protection.py index da99ddf..c0b2dda 100644 --- a/memoryhole/protection.py +++ b/memoryhole/protection.py @@ -8,6 +8,7 @@ from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import getaddresses +from collections import namedtuple from copy import deepcopy from memoryhole.gpg import Gnupg @@ -19,35 +20,47 @@ from memoryhole.rfc3156 import ( class ProtectConfig(object): - PROTECTED_HEADERS = ('subject', 'message-id', 'date', 'to', 'from') - OBSCURED_HEADERS = { - 'subject': 'encrypted email', - 'message-id': 'C@memoryhole.example', - 'date': 'Thu, 1 Jan 1970 00:00:00 +0000', + Replace = namedtuple("Replace", ("force_display", "replacement")) + + REPLACED_HEADERS = { + "subject": Replace(True, "encrypted email"), + "message-id": Replace(True, "C@memoryhole.example"), + "date": Replace(True, "Thu, 1 Jan 1970 00:00:00 +0000"), + "in-reply-to": Replace(False, None), + "references": Replace(False, None), + "user-agent": Replace(False, None), } - def __init__(self, openpgp=None, protected_headers=PROTECTED_HEADERS, - obscured_headers=OBSCURED_HEADERS): + def __init__(self, openpgp=None, replaced_headers=REPLACED_HEADERS, + skipped_headers=[]): """ - Configuration parameters for the protection + Configuration parameters for the protection. + + Every header will be included in the memory hole protected headers part + unless is in the skipped_headers list. + + For encrypted emails the header will be replaced if they are present in + the replaced_headers list. Each header will be putted in the MIMEpart + headers unless 'force_display' is True. Top level headers will be + replaced by 'replacement' unless 'replacement' is None, in which case + the header will be removed completely from the top level headers. + + All header names need to be in lower case. :param openpgp: the implementation of openpgp to use for encryption and/or signature :type openpgp: IOpenPGP - :param protected_headers: list of headers to protect in the signed - part, they need to be in lower case - :type protected_headers: [str] - :param obscured_headers: list of headers to obscure replacing it by - a predefined value in the email headers, - the headers need to be in lower case - :type obscured_headers: {str: str} + :param replaced_headers: a dict of headers to be replaced + :type replaced_headers: {str: Header} + :param skipped_headers: list of headers to skip + :type skipped_headers: [str] """ if openpgp is None: openpgp = Gnupg() self.openpgp = openpgp - self.protected_headers = protected_headers - self.obscured_headers = obscured_headers + self.skipped_headers = skipped_headers + self.replaced_headers = replaced_headers def protect(msg, encrypt=True, config=None): @@ -77,8 +90,8 @@ def _encrypt_mime(msg, config): newmsg, part = _protect_headers( msg, MultipartEncrypted('application/pgp-encrypted'), config) - if config.obscured_headers: - newmsg, part = _obscure_headers(newmsg, part, config) + if config.replaced_headers: + newmsg, part = _replace_headers(newmsg, part, config) encstr = config.openpgp.encrypt(part.as_string(unixfrom=False), encraddr) encmsg = MIMEApplication( @@ -120,20 +133,22 @@ def _sign_mime(msg, config): return newmsg -def _obscure_headers(msg, part, config): +def _replace_headers(msg, part, config): headers = "" for header, value in msg.items(): - if header.lower() in config.obscured_headers: + h = header.lower() + if (h in config.replaced_headers and + config.replaced_headers[h].force_display): headers += header + ": " + value + "\n" headerspart = MIMEText(headers, 'rfc822-headers') # TODO: should this be an attachment???? newpart = MIMEMultipart('mixed', _subparts=[headerspart, part]) - for header, value in config.obscured_headers.items(): + for header, value in config.replaced_headers.items(): if header in msg: del(msg[header]) - if value: - msg.add_header(header, value) + if value.replacement is not None: + msg.add_header(header, value.replacement) return msg, newpart @@ -141,7 +156,7 @@ def _protect_headers(oldmsg, newmsg, config): part = deepcopy(oldmsg) for header, value in part.items(): newmsg.add_header(header, value) - if header.lower() not in config.protected_headers: + if header.lower() in config.skipped_headers: del(part[header]) return newmsg, part diff --git a/tests/test_protection.py b/tests/test_protection.py index 70c1a57..226a8cd 100644 --- a/tests/test_protection.py +++ b/tests/test_protection.py @@ -28,7 +28,7 @@ parser = Parser() def test_pgp_encrypted_mime(): msg = parser.parsestr(EMAIL) encrypter = Encrypter() - conf = ProtectConfig(openpgp=encrypter, obscured_headers=[]) + conf = ProtectConfig(openpgp=encrypter, replaced_headers=[]) encmsg = protect(msg, config=conf) assert encmsg.get_payload(1).get_payload() == encrypter.encstr @@ -37,10 +37,10 @@ def test_pgp_encrypted_mime(): assert get_body(encrypter.data) == BODY + '\n' -def test_unobscured_headers(): +def test_kept_headers(): msg = parser.parsestr(EMAIL) encrypter = Encrypter() - conf = ProtectConfig(openpgp=encrypter, obscured_headers=[]) + conf = ProtectConfig(openpgp=encrypter, replaced_headers=[]) encmsg = protect(msg, config=conf) assert encmsg['from'] == FROM @@ -48,16 +48,16 @@ def test_unobscured_headers(): assert encmsg['subject'] == SUBJECT -def test_obscured_headers(): +def test_replaced_headers(): msg = parser.parsestr(EMAIL) encrypter = Encrypter() conf = ProtectConfig(openpgp=encrypter) encmsg = protect(msg, config=conf) - for header, value in conf.obscured_headers.items(): + for header, value in conf.replaced_headers.items(): msgheaders = encmsg.get_all(header, []) if msgheaders: - assert msgheaders == [value] + assert msgheaders == [value.replacement] encpart = parser.parsestr(encrypter.data) assert encpart.get_content_type() == "multipart/mixed" -- cgit v1.2.3