From 67a847605769d5e255168a65d780594383569b75 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 2 Nov 2019 01:28:24 -0400 Subject: email-print-mime-structure: add decryption capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add simple decryption capability for email-print-mime-structure, so that it can do stuff like this: $ email-print-mime-structure --pgpkey alice@openpgp.example.sec.asc < msg.eml └┬╴multipart/encrypted 2190 bytes ├─╴application/pgp-encrypted 11 bytes └─╴application/octet-stream 1613 bytes ↧ (decrypts to) └─╴text/plain 425 bytes $ At the moment, it only works with keys that can be found in the filesystem, and when the pgpy module is installed. Possible future work: - try using gpg to do the decryption from whatever gpg's system capabilities are I've added python3-pgpy to the list of Recommends, since it is not a hard dependency. Signed-off-by: Daniel Kahn Gillmor --- email-print-mime-structure | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'email-print-mime-structure') diff --git a/email-print-mime-structure b/email-print-mime-structure index 33579a7..eb513b3 100755 --- a/email-print-mime-structure +++ b/email-print-mime-structure @@ -38,6 +38,11 @@ from typing import Optional, Union, List, Tuple, Any from email.charset import Charset from email.message import Message +try: + import pgpy #type: ignore +except ImportError: + pgpy = None + class MimePrinter(object): def __init__(self, args:Namespace): self.args = args @@ -66,6 +71,33 @@ class MimePrinter(object): print(f'{prefix}{z.get_content_type()}{cset}{disposition}{fname} {nbytes:d} bytes') + if self.args.pgpkey and \ + (parent is not None) and \ + (parent.get_content_type().lower() == 'multipart/encrypted') and \ + (str(parent.get_param('protocol')).lower() == 'application/pgp-encrypted') and \ + (num == 2): + if pgpy is None: + logging.warning(f'Python module pgpy is not available, not decrypting (try "apt install python3-pgpy")') + else: + cryptopayload:Optional[Message] = None + keyname:str + for keyname in self.args.pgpkey: + try: + key:pgpy.PGPKey + key, _ = pgpy.PGPKey.from_file(keyname) + msg:pgpy.PGPMessage = pgpy.PGPMessage.from_blob(z.get_payload()) + msg = key.decrypt(msg) + cryptopayload = email.message_from_bytes(msg.message) + break + except: + pass + if cryptopayload is None: + logging.warning(f'Unable to decrypt') + else: + newprefix = prefix[:-3] + ' ' + print(f'{newprefix}↧ (decrypts to)') + self.print_tree(cryptopayload, newprefix + '└', z, 0) + def print_tree(self, z:Message, prefix:str, parent:Optional[Message], num:int) -> None: if (z.is_multipart()): self.print_part(z, prefix+'┬╴', parent, num) @@ -88,6 +120,8 @@ class MimePrinter(object): def main() -> None: parser:ArgumentParser = ArgumentParser(description='Read RFC2822 MIME message from stdin and emit a tree diagram to stdout.', epilog="Example: email-print-mime-structure < message.eml") + parser.add_argument('--pgpkey', metavar='KEYFILE', action='append', + help='OpenPGP Transferable Secret Key for decrypting') args:Namespace = parser.parse_args() msg:Union[Message, str, int, Any] = email.message_from_file(sys.stdin) -- cgit v1.2.3