Peret checks PGP signed mails and extract submissions
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
6.7 KiB

import re
import subprocess
from test import MailTest
def import_pubkey(key, GNUPG_DIRECTORY):
with subprocess.Popen(["gpg",
"--homedir=" + GNUPG_DIRECTORY,
"--batch",
"--import",
"-"], env={"LANG": 'C'}, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
p.stdin.write(key)
p.stdin.close()
p.wait()
gpg_output = p.stderr.read().decode("utf-8", "replace")
if p.returncode == 0:
yield MailTest("New PGP key successfully imported:", details=gpg_output)
yield False
else:
yield MailTest("An error occurs during PGP key importation:", 1, details=gpg_output)
def assume_rfc3156(msg):
if msg.get_param("protocol") is None or msg.get_param("protocol") != "application/pgp-signature" or msg.get_payload(1).get_content_type() != "application/pgp-signature":
yield MailTest("Message treated as RFC3156 due to Content-Type, but is not compliant", 1)
return
# Extracting signature
try:
data = msg.get_payload(0)
sign = msg.get_payload(1).get_payload().encode()
# Except an exception in the two above lines if one part doesn't exist
yield MailTest("Message treated as RFC3156: content and signature found")
yield (data, sign)
except IndexError:
yield MailTest("Message treated as RFC3156 due to Content-Type, but is not compliant", 1)
return
def assume_oldstyle(payload):
yield MailTest("Found BEGIN PGP SIGNED MESSAGE: message treated as old style PGP email.")
try:
yield payload.encode()
except:
yield MailTest("Non-armored signed message discovered. Avoid using binary message over SMTP (see RFC2015 #2. PGP data formats).", 2)
yield payload
def check(msg, GNUPG_DIRECTORY, accept_public_key=True, beta=False):
ct = msg.get_content_type()
# First, looking for public key
if accept_public_key:
for part in msg.walk():
if part.get_content_type() == "application/pgp-keys" and not part.is_multipart() and part.get_payload(decode=True).find(b"-----BEGIN PGP PUBLIC KEY BLOCK-----") >= 0:
if part.get_content_type() != "application/pgp-keys":
yield MailTest("Public key file discovered, but content-type mismatched: got %s instead of application/pgp-keys." % part.get_content_type(), 2)
yield from import_pubkey(part.get_payload(decode=True), GNUPG_DIRECTORY)
return
if ct == "multipart/signed" and msg.is_multipart():
yield from assume_rfc3156(msg)
else:
yield MailTest("This is not a signed e-mail: %s." % ct, 1)
if ct == "multipart/encrypted":
yield MailTest("As an automated service, I can't access my owner's private key. Please resend your email, unencrypted but signed.", -1)
return
from archive import _guess_mime
# Looking for signed content
lpart = None
for part in msg.walk():
payload = part.get_payload()
if payload is not None and not part.is_multipart() and part.get_payload(decode=True).find(b"-----BEGIN PGP SIGNED MESSAGE-----") >= 0:
res = re.match(".*(-----BEGIN PGP SIGNED MESSAGE-----(.*)-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----).*", payload, re.DOTALL)
if res is not None:
yield from assume_oldstyle(payload)
else:
res = re.match(b".*(-----BEGIN PGP SIGNED MESSAGE-----(.*)-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----).*", part.get_payload(decode=True), re.DOTALL)
if res is not None:
yield from assume_oldstyle(part.get_payload(decode=True))
elif part.get_content_type() == "application/pgp-signature" or (
payload is not None and not part.is_multipart() and part.get_payload(decode=True).find(b"-----BEGIN PGP SIGNATURE-----") >= 0
):
if part.get_content_type() != "application/pgp-signature":
yield MailTest("Standalone PGP signature file discovered, but content-type mismatched: got %s instead of application/pgp-signature." % part.get_content_type(), 2)
p = [x for x in msg.walk()]
for s in range(len(p) - 1, -1, -1):
spart = p[s]
if part is not spart and not spart.is_multipart():
yield MailTest("Separate signature found. Trying it with part %d (%s) ..." % (s, spart.get_content_type()), -1)
yield (spart.get_payload(decode=True), part.get_payload(decode=True))
elif accept_public_key and payload is not None and not part.is_multipart() and part.get_payload(decode=True).find(b"-----BEGIN PGP PUBLIC KEY BLOCK-----") >= 0:
if part.get_content_type() != "application/pgp-keys":
yield MailTest("Public key file discovered, but content-type mismatched: got %s instead of application/pgp-keys." % part.get_content_type(), 2)
yield from import_pubkey(part.get_payload(decode=True), GNUPG_DIRECTORY)
return
elif lpart is not None and part.get_filename() is not None and lpart.get_filename() is not None and part.get_filename()[:len(lpart.get_filename())] == lpart.get_filename():
yield MailTest("Standalone non-armored signature file discovered. Avoid using binary signature over SMTP (see RFC2015 #2. PGP data formats).", 2)
yield (lpart.get_payload(decode=True), part.get_payload(decode=True))
elif lpart is not None and part.get_filename() is not None and lpart.get_filename() is not None and lpart.get_filename()[:len(part.get_filename())] == part.get_filename():
yield MailTest("Standalone non-armored signature file discovered. Avoid using binary signature over SMTP (see RFC2015 #2. PGP data formats).", 2)
yield (part.get_payload(decode=True), lpart.get_payload(decode=True))
elif part.get_filename() is not None and (part.get_filename()[len(part.get_filename())-4:] == ".gpg" or part.get_filename()[len(part.get_filename())-4:] == ".asc") or (
payload is not None and not part.is_multipart() and part.get_payload(decode=True).find(b"-----BEGIN PGP MESSAGE-----") >= 0):
yield MailTest("Standalone PGP message discovered.")
yield part.get_payload(decode=True)
lpart = part
def skip(msg, gpgmail):
ct = msg.get_content_type()
data = msg.get_payload(0)
_, verify = gpgmail.gnupg
yield MailTest("Message treated as OpenPGP signed message")
yield (data, verify.username)