Browse Source

Add E2E case: a user with a key and PGP/MIME configured

- Add a new test input message for a new test identity, test scenario
  configuration and a test key.

- While retrieving message payload, determine charset based on the
  Content-Type header.  When missing, default to UTF-8.

- Use more comprehensible variables names.

- Adjust logging levels.
pull/82/head
Piotr F. Mieszkowski 1 month ago
parent
commit
3bcc1151e5
  1. 16
      GnuPG/__init__.py
  2. 45
      gpg-mailgate.py
  3. 8
      test/e2e.ini
  4. 6
      test/e2e_test.py
  5. BIN
      test/keyhome/pubring.kbx
  6. 6
      test/msgin/clear2rsa2.msg

16
GnuPG/__init__.py

@ -35,20 +35,6 @@ def build_command(key_home, *args, **kwargs):
cmd = ["gpg", '--homedir', key_home] + list(args)
return cmd
def private_keys( keyhome ):
cmd = build_command(keyhome, '--list-secret-keys', '--with-colons')
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()
keys = dict()
for line in p.stdout.readlines():
if line[0:3] == 'uid' or line[0:3] == 'sec':
if ('<' not in line or '>' not in line):
continue
email = line.split('<')[1].split('>')[0]
fingerprint = line.split(':')[4]
keys[fingerprint] = email
return keys
def public_keys( keyhome ):
cmd = build_command(keyhome, '--list-keys', '--with-colons')
p = subprocess.Popen( cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
@ -58,7 +44,7 @@ def public_keys( keyhome ):
fingerprint = None
email = None
for line in p.stdout.readlines():
line = line.decode('utf-8')
line = line.decode(sys.getdefaultencoding())
if line[0:3] == LINE_FINGERPRINT:
fingerprint = line.split(':')[POS_FINGERPRINT]
if line[0:3] == LINE_USER_ID:

45
gpg-mailgate.py

@ -113,7 +113,7 @@ def gpg_encrypt( raw_message, recipients ):
else:
# Log message only if an unknown style is defined
if conf.config_item_set('pgp_style', rcpt[0]):
LOG.info("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0]))
LOG.debug("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0]))
# If no style is in settings defined for recipient, use default from settings
if conf.config_item_equals('default', 'mime_conversion', 'yes'):
@ -177,17 +177,17 @@ def encrypt_all_payloads_inline( message, gpg_to_cmdline ):
def encrypt_all_payloads_mime( message, gpg_to_cmdline ):
# Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail.
submsg1 = email.message.Message()
submsg1.set_payload("Version: 1\n")
submsg1.set_type("application/pgp-encrypted")
submsg1.set_param('PGP/MIME version identification', "", 'Content-Description' )
submsg2 = email.message.Message()
submsg2.set_type("application/octet-stream")
submsg2.set_param('name', "encrypted.asc")
submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description' )
submsg2.set_param('inline', "", 'Content-Disposition' )
submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition' )
pgp_ver_part = email.message.Message()
pgp_ver_part.set_payload("Version: 1\n")
pgp_ver_part.set_type("application/pgp-encrypted")
pgp_ver_part.set_param('PGP/MIME version identification', "", 'Content-Description' )
encrypted_part = email.message.Message()
encrypted_part.set_type("application/octet-stream")
encrypted_part.set_param('name', "encrypted.asc")
encrypted_part.set_param('OpenPGP encrypted message', "", 'Content-Description' )
encrypted_part.set_param('inline', "", 'Content-Disposition' )
encrypted_part.set_param('filename', "encrypted.asc", 'Content-Disposition' )
if isinstance(message.get_payload(), str):
# WTF! It seems to swallow the first line. Not sure why. Perhaps
@ -195,13 +195,16 @@ def encrypt_all_payloads_mime( message, gpg_to_cmdline ):
# Workaround it here by prepending a blank line.
# This happens only on text only messages.
additionalSubHeader=""
encoding = sys.getdefaultencoding()
if 'Content-Type' in message and not message['Content-Type'].startswith('multipart'):
additionalSubHeader="Content-Type: "+message['Content-Type']+"\n"
submsg2.set_payload(additionalSubHeader+"\n" +message.get_payload(decode=True))
(base, encoding) = parse_content_type(message['Content-Type'])
LOG.debug(f"Identified encoding as {encoding}")
encrypted_part.set_payload(additionalSubHeader+"\n" +message.get_payload(decode=True).decode(encoding))
check_nested = True
else:
processed_payloads = generate_message_from_payloads(message)
submsg2.set_payload(processed_payloads.as_string())
encrypted_part.set_payload(processed_payloads.as_string())
check_nested = False
message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)"
@ -217,7 +220,15 @@ def encrypt_all_payloads_mime( message, gpg_to_cmdline ):
else:
message['Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary
return [ submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested) ]
return [ pgp_ver_part, encrypt_payload(encrypted_part, gpg_to_cmdline, check_nested) ]
def parse_content_type(content_type):
split_at = content_type.index(';')
second_part = content_type[split_at+1 : ].strip()
if second_part.startswith('charset'):
return (content_type[0 : split_at], second_part[second_part.index('=') + 1 : ].strip())
else:
return (content_type[0 : split_at], sys.getdefaultencoding())
def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ):
global LOG
@ -297,11 +308,11 @@ def smime_encrypt( raw_message, recipients ):
s.write(out, p7)
LOG.debug("Sending message from " + from_addr + " to " + str(smime_to))
LOG.debug(f"Sending message from {from_addr} to {smime_to}")
send_msg(out.read(), smime_to)
if unsmime_to:
LOG.debug("Unable to find valid S/MIME certificates for " + str(unsmime_to))
LOG.debug(f"Unable to find valid S/MIME certificates for {unsmime_to}")
return unsmime_to

8
test/e2e.ini

@ -30,7 +30,7 @@ certs: test/certs
[tests]
# Number of "test-*" sections in this file, describing test cases.
cases: 6
cases: 7
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
@ -72,3 +72,9 @@ descr: Multipart encrypted message to a user with an Ed25519 key.
to: bob@disposlab
in: test/msgin/multipart2rsa.msg
out: -----BEGIN PGP MESSAGE-----
[case-7]
descr: Clear text message to a user with an RSA key and PGP/MIME enabled in configuration
to: evan@disposlab
in: test/msgin/clear2rsa2.msg
out: -----BEGIN PGP MESSAGE-----

6
test/e2e_test.py

@ -51,6 +51,12 @@ def build_config(config):
cp.add_section("enc_keymap")
cp.set("enc_keymap", "alice@disposlab", "1CD245308F0963D038E88357973CF4D9387C44D7")
cp.set("enc_keymap", "bob@disposlab", "19CF4B47ECC9C47AFA84D4BD96F39FDA0E31BB67")
cp.set("enc_keymap", "evan@disposlab", "530B1BB2D0CC7971648198BBA4774E507D3AF5BC")
cp.add_section("pgp_style")
# Default style is PGP/Inline, so to cover more branches, one test identity
# uses PGP/MIME.
cp.set("pgp_style", "evan@disposlab", "mime")
logging.debug(f"Created config with keyhome={config['gpg_keyhome']}, cert_path={config['smime_certpath']} and relay at port {config['port']}")
return cp

BIN
test/keyhome/pubring.kbx

Binary file not shown.

6
test/msgin/clear2rsa2.msg

@ -0,0 +1,6 @@
From: Dave <dave@disposlab
To: Evan <evan@disposlab>
Subject: Test
Content-Type: text/plain; charset="utf-8"
Body of the message.
Loading…
Cancel
Save