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.
119 lines
6.3 KiB
119 lines
6.3 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.") |
|
yield payload.encode() |
|
|
|
|
|
|
|
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
|
|
|