You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
7.3 KiB
Python
144 lines
7.3 KiB
Python
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 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)
|
|
|
|
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"):
|
|
yield MailTest("Standalone PGP message discovered with mismatched content-type: %s. This can lead to unexpected behaviour." % part.get_content_type(), 2)
|
|
data = part.get_payload(decode=True)
|
|
|
|
mime = _guess_mime(data)
|
|
if mime == "application/octet-stream":
|
|
yield MailTest("Non-armored file discovered. Avoid using binary PGP message over SMTP (see RFC2015 #2. PGP data formats).", 2)
|
|
yield MailTest("Falling back to default automation as nothing helped. Be warned it'll most likely failed.", 2)
|
|
|
|
yield data
|
|
|
|
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)
|