From c86c6206687056818efa8951f5c11037ede70e2f Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Tue, 7 Jun 2022 22:14:32 +0200 Subject: [PATCH 1/2] Extract delimiter support, add unit tests Also: fix recursive call to get_cert_for_email. --- gpg-mailgate.py | 21 ++++++++++++--------- lacre/text.py | 8 ++++++++ test/test_lacre_text.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 6c057f2..9dcbe59 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -315,26 +315,29 @@ def smime_encrypt( raw_message, recipients ): def get_cert_for_email( to_addr, cert_path ): global LOG + insensitive = conf.config_item_equals('default', 'mail_case_insensitive', 'yes') + files_in_directory = os.listdir(cert_path) for filename in files_in_directory: file_path = os.path.join(cert_path, filename) if not os.path.isfile(file_path): continue - if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'): - if filename.lower() == to_addr: + if insensitive: + if filename.casefold() == to_addr: return (file_path, to_addr) else: if filename == to_addr: return (file_path, to_addr) - # support foo+ignore@bar.com -> foo@bar.com - multi_email = re.match('^([^\+]+)\+([^@]+)@(.*)$', to_addr) - if multi_email: - fixed_up_email = "%s@%s" % (multi_email.group(1), multi_email.group(3)) - LOG.debug("Multi-email %s converted to %s" % (to_addr, fixed_up_email)) - return get_cert_for_email(fixed_up_email) - return None + # support foo+ignore@bar.com -> foo@bar.com + (fixed_up_email, topic) = text.parse_delimiter(to_addr) + if topic is None: + # delimiter not used + return None + else: + LOG.debug(f"Looking up certificate for {fixed_up_email} after parsing {to_addr}") + return get_cert_for_email(fixed_up_email, cert_path) def sanitize_case_sense( address ): if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'): diff --git a/lacre/text.py b/lacre/text.py index 9b3247f..17baaa7 100644 --- a/lacre/text.py +++ b/lacre/text.py @@ -1,4 +1,5 @@ import sys +import re # The standard way to encode line-ending in email: EOL = "\r\n" @@ -22,6 +23,13 @@ def parse_content_type(content_type): else: return (ctype, sys.getdefaultencoding()) +def parse_delimiter(address): + withdelim = re.match('^([^\+]+)\+([^@]+)@(.*)$', address) + if withdelim: + return (withdelim.group(1) + '@' + withdelim.group(3), withdelim.group(2)) + else: + return (address, None) + def is_pgp_inline(payload): """Finds out if the payload (bytes) contains PGP/INLINE markers.""" return PGP_INLINE_BEGIN in payload and PGP_INLINE_END in payload diff --git a/test/test_lacre_text.py b/test/test_lacre_text.py index 943ad28..ea92f0a 100644 --- a/test/test_lacre_text.py +++ b/test/test_lacre_text.py @@ -23,3 +23,15 @@ class LacreTextTest(unittest.TestCase): (mtype, mcharset) = lacre.text.parse_content_type('text/plain; charset="UTF-8"; some-param="Some Value"') self.assertEqual(mtype, 'text/plain') self.assertEqual(mcharset, '"UTF-8"') + + def test_parse_email_without_delimiter(self): + addr = "Some.Name@example.com" + (addr2, topic) = lacre.text.parse_delimiter(addr) + self.assertEqual(addr2, "Some.Name@example.com") + self.assertEqual(topic, None) + + def test_parse_email_with_delimiter(self): + addr = "Some.Name+some-topic@example.com" + (addr2, topic) = lacre.text.parse_delimiter(addr) + self.assertEqual(addr2, "Some.Name@example.com") + self.assertEqual(topic, "some-topic") From 881a8d17564a2c2af77081b14eb7f332407beee8 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Wed, 8 Jun 2022 21:20:58 +0200 Subject: [PATCH 2/2] Add GnuPG encryption support for addresses with delimiters If a user registers their key for address alice@example.com but receives a message sent to alice+something@example.com, this message should be encrypted as well. - Implement delimiter support for GnuPG encryption. - Add E2E test case for a clear text message delivered to an address with delimiter. - Fix minor bug: wrong configuration parameter was retrieved when logging information about enc_domain_keymap being active. --- gpg-mailgate.py | 21 +++++++++++++++++---- test/e2e.ini | 8 +++++++- test/msgin/clear2ed-delim.msg | 5 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 test/msgin/clear2ed-delim.msg diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 9dcbe59..ada93b0 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -59,9 +59,15 @@ def gpg_encrypt( raw_message, recipients ): for fingerprint in keys: keys[fingerprint] = sanitize_case_sense(keys[fingerprint]) + # This list will be filled with pairs (M, N), where M is the destination + # address we're going to deliver the message to and N is the identity we're + # going to encrypt it for. gpg_to = list() + ungpg_to = list() + enc_keymap_only = conf.config_item_equals('default', 'enc_keymap_only', 'yes') + for to in recipients: # Check if recipient is in keymap @@ -75,16 +81,23 @@ def gpg_encrypt( raw_message, recipients ): LOG.info("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to)) # Check if key in keychain is present - if to in keys.values() and not conf.config_item_equals('default', 'enc_keymap_only', 'yes'): - gpg_to.append( (to, to) ) - continue + if not enc_keymap_only: + if to in keys.values(): + gpg_to.append( (to, to) ) + continue + + # If this is an address with a delimiter (i.e. "foo+bar@example.com"), + # then strip whatever is found after the delimiter and try this address. + (newto, topic) = text.parse_delimiter(to) + if newto in keys.values(): + gpg_to.append((to, newto)) # Check if there is a default key for the domain splitted_to = to.split('@') if len(splitted_to) > 1: domain = splitted_to[1] if conf.config_item_set('enc_domain_keymap', domain): - LOG.info("Encrypt domain keymap has key '%s'" % conf.get_item('enc_dec_keymap', domain) ) + LOG.info("Encrypt domain keymap has key '%s'" % conf.get_item('enc_domain_keymap', domain) ) # Check we've got a matching key! if conf.get_item('enc_domain_keymap', domain) in keys: LOG.info("Using default domain key for recipient '%s'" % to) diff --git a/test/e2e.ini b/test/e2e.ini index e6ba7c7..9513ad8 100644 --- a/test/e2e.ini +++ b/test/e2e.ini @@ -30,7 +30,7 @@ certs: test/certs [tests] # Number of "test-*" sections in this file, describing test cases. -cases: 7 +cases: 8 e2e_log: test/logs/e2e.log e2e_log_format: %(asctime)s %(pathname)s:%(lineno)d %(levelname)s [%(funcName)s] %(message)s e2e_log_datefmt: %Y-%m-%d %H:%M:%S @@ -78,3 +78,9 @@ descr: Clear text message to a user with an RSA key and PGP/MIME enabled in conf to: evan@disposlab in: test/msgin/clear2rsa2.msg out: -----BEGIN PGP MESSAGE----- + +[case-8] +descr: Clear text message to address with delimiter and a user with an Ed25519 key. +to: bob@disposlab +in: test/msgin/clear2ed-delim.msg +out: -----BEGIN PGP MESSAGE----- diff --git a/test/msgin/clear2ed-delim.msg b/test/msgin/clear2ed-delim.msg new file mode 100644 index 0000000..3fa6a32 --- /dev/null +++ b/test/msgin/clear2ed-delim.msg @@ -0,0 +1,5 @@ +From: Dave +To: Bob +Subject: Test + +Body of the message.