summaryrefslogtreecommitdiff
path: root/email-print-mime-structure
diff options
context:
space:
mode:
Diffstat (limited to 'email-print-mime-structure')
-rwxr-xr-xemail-print-mime-structure75
1 files changed, 57 insertions, 18 deletions
diff --git a/email-print-mime-structure b/email-print-mime-structure
index 27fb532..b7646e0 100755
--- a/email-print-mime-structure
+++ b/email-print-mime-structure
@@ -32,6 +32,7 @@ something like "cat -n"
'''
import os
import sys
+import enum
import email
import logging
import subprocess
@@ -51,6 +52,8 @@ try:
except ImportError:
argcomplete = None
+EncType = enum.Enum('EncType', ['PGPMIME', 'SMIME'])
+
class MimePrinter(object):
def __init__(self, args:Namespace):
self.args = args
@@ -79,32 +82,66 @@ class MimePrinter(object):
print(f'{prefix}{z.get_content_type()}{cset}{disposition}{fname} {nbytes:d} bytes')
cryptopayload:Optional[Message] = None
- ciphertext:Union[List[Message],str,bytes,None] = None
try_pgp_decrypt:bool = self.args.pgpkey or self.args.use_gpg_agent
+ try_cms_decrypt:bool = self.args.cmskey or self.args.use_gpg_agent
if try_pgp_decrypt 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):
- ciphertext = z.get_payload()
- if not isinstance(ciphertext, str):
- logging.warning('encrypted part was not a leaf mime part somehow')
- return
- if self.args.pgpkey:
- cryptopayload = self.pgpy_decrypt(self.args.pgpkey, ciphertext)
- if cryptopayload is None and self.args.use_gpg_agent:
- cryptopayload = self.gpg_decrypt(ciphertext)
- if cryptopayload is None:
- logging.warning(f'Unable to decrypt')
- return
+ cryptopayload = self.decrypt_part(z, EncType.PGPMIME)
+
+ if try_cms_decrypt and \
+ cryptopayload is None and \
+ z.get_content_type().lower() == 'application/pkcs7-mime' and \
+ str(z.get_param('smime-type')).lower() in ['authenveloped-data',
+ 'enveloped-data']:
+ cryptopayload = self.decrypt_part(z, EncType.SMIME)
if cryptopayload is not None:
newprefix = prefix[:-3] + ' '
print(f'{newprefix}↧ (decrypts to)')
self.print_tree(cryptopayload, newprefix + '└', z, 0)
-
- def pgpy_decrypt(self, keys:List[str], ciphertext:str) -> Optional[Message]:
+ else:
+ if z.get_content_type().lower() == 'application/pkcs7-mime' and \
+ str(z.get_param('smime-type')).lower() == 'signed-data':
+ bodypart:Union[List[Message],str,bytes,None] = z.get_payload(decode=True)
+ if isinstance(bodypart, bytes):
+ unwrapped = self.pipe_transform(bodypart, ['certtool', '--p7-show-data', '--p7-info', '--inder'])
+ if unwrapped:
+ newprefix = prefix[:-3] + ' '
+ print(f'{newprefix}⇩ (unwraps to)')
+ self.print_tree(unwrapped, newprefix + '└', z, 0)
+ else:
+ logging.warning(f'Unable to unwrap one-part PKCS#7 signed message (maybe try "apt install gnutls-bin")')
+
+
+ def decrypt_part(self, msg:Message, flavor:EncType) -> Optional[Message]:
+ ciphertext:Union[List[Message],str,bytes,None] = msg.get_payload(decode=True)
+ cryptopayload:Optional[Message] = None
+ if not isinstance(ciphertext, bytes):
+ logging.warning('encrypted part was not a leaf mime part somehow')
+ return None
+ if flavor == EncType.PGPMIME:
+ if self.args.pgpkey:
+ cryptopayload = self.pgpy_decrypt(self.args.pgpkey, ciphertext)
+ if cryptopayload is None and self.args.use_gpg_agent:
+ cryptopayload = self.pipe_transform(ciphertext, ['gpg', '--batch', '--decrypt'])
+ elif flavor == EncType.SMIME:
+ if self.args.cmskey:
+ for keyname in self.args.cmskey:
+ cmd = ['openssl', 'smime', '-decrypt', '-inform', 'DER', '-inkey', keyname]
+ cryptopayload = self.pipe_transform(ciphertext, cmd)
+ if cryptopayload:
+ return cryptopayload
+ if self.args.use_gpg_agent:
+ cryptopayload = self.pipe_transform(ciphertext, ['gpgsm', '--batch', '--decrypt'])
+ if cryptopayload is None:
+ logging.warning(f'Unable to decrypt')
+ return cryptopayload
+
+ def pgpy_decrypt(self, keys:List[str], ciphertext:bytes) -> Optional[Message]:
if pgpy is None:
logging.warning(f'Python module pgpy is not available, not decrypting (try "apt install python3-pgpy")')
return None
@@ -121,13 +158,13 @@ class MimePrinter(object):
pass
return None
- def gpg_decrypt(self, ciphertext:str) -> Optional[Message]:
+ def pipe_transform(self, ciphertext:bytes, cmd:List[str]) -> Optional[Message]:
inp:int
outp:int
inp, outp = os.pipe()
- with open(outp, 'w') as outf:
+ with open(outp, 'wb') as outf:
outf.write(ciphertext)
- out:subprocess.CompletedProcess[bytes] = subprocess.run(['gpg', '--batch', '--decrypt'],
+ out:subprocess.CompletedProcess[bytes] = subprocess.run(cmd,
stdin=inp,
capture_output=True)
if out.returncode == 0:
@@ -157,7 +194,9 @@ 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')
+ help='OpenPGP Transferable Secret Key for decrypting PGP/MIME')
+ parser.add_argument('--cmskey', metavar='KEYFILE', action='append',
+ help='X.509 Private Key for decrypting S/MIME')
parser.add_argument('--use-gpg-agent', action='store_true',
help='Ask local GnuPG installation for decryption')
parser.add_argument('--no-use-gpg-agent', action='store_false',