If the user supplies a secret key like the ones found in https://www.ietf.org/id/draft-dkg-lamps-samples-01.html, then email-print-mime-structure will try to use that for decryption of CMS-encrypted (S/MIME) message parts.
Signed-off-by: Daniel Kahn Gillmor <d...@fifthhorseman.net> --- debian/control | 1 + email-print-mime-structure | 31 ++++++++++++++++++++++++++++--- email-print-mime-structure.1.pod | 17 ++++++++++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index aa44863..7641aa8 100644 --- a/debian/control +++ b/debian/control @@ -47,6 +47,7 @@ Suggests: gpg, gpg-agent, gpgsm, + openssl, Architecture: all Description: collection of scripts for manipulating e-mail on Debian This package provides a collection of scripts for manipulating e-mail diff --git a/email-print-mime-structure b/email-print-mime-structure index ab90976..069648a 100755 --- a/email-print-mime-structure +++ b/email-print-mime-structure @@ -81,7 +81,7 @@ class MimePrinter(object): 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.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 \ @@ -107,7 +107,9 @@ class MimePrinter(object): if not isinstance(ciphertext, bytes): logging.warning('encrypted part was not a leaf mime part somehow') return - if self.args.use_gpg_agent: + if self.args.cmskey: + cryptopayload = self.openssl_decrypt(self.args.cmskey, ciphertext) + if cryptopayload is None and self.args.use_gpg_agent: cryptopayload = self.gpgsm_decrypt(ciphertext) if cryptopayload is None: logging.warning(f'Unable to decrypt') @@ -135,6 +137,27 @@ class MimePrinter(object): pass return None + def openssl_decrypt(self, keys:List[str], ciphertext:bytes) -> Optional[Message]: + keyname:str + ret:Optional[Message] = None + for keyname in keys: + inp:int + outp:int + inp, outp = os.pipe() + with open(outp, 'wb') as outf: + outf.write(ciphertext) + cmd = ['openssl', 'smime', '-decrypt', '-inform', 'DER', '-inkey', keyname] + try: + out:subprocess.CompletedProcess[bytes] = subprocess.run(cmd, + stdin=inp, + capture_output=True) + except Exception as e: + logging.warning(f'Failed to decrypt with openssl: {e}') + return None + if out.returncode == 0: + return email.message_from_bytes(out.stdout) + return None + def gpg_decrypt(self, ciphertext:str) -> Optional[Message]: inp:int outp:int @@ -188,7 +211,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', diff --git a/email-print-mime-structure.1.pod b/email-print-mime-structure.1.pod index f109997..037c1a9 100644 --- a/email-print-mime-structure.1.pod +++ b/email-print-mime-structure.1.pod @@ -32,15 +32,26 @@ key. OpenPGP secret keys listed in B<--pgpkey=> are used ephemerally, and do not interact with any local GnuPG keyring. +=item B<--cmskey=>I<KEYFILE> + +I<KEYFILE> should name a PEM- or DER-encoded X.509 private key that is +not password-protected. If an S/MIME-encrypted message that uses CMS +is found on standard input, this key will be tried for decryption. +May be used multiple times if you want to try decrypting with more +than one such key. + +X.509 private keys listed in B<--cmskey=> are used ephemerally, and do +not interact with any local GnuPG keyring. + =item B<--use-gpg-agent> If this flag is present, and B<email-print-mime-structure> encounters a PGP/MIME- or S/MIME-encrypted part, it will try to decrypt the part using the secret keys found in the local installation of GnuPG. -If both B<--pgpkey=>I<KEYFILE> and B<--use-gpg-agent> are -supplied, I<KEYFILE> arguments will be tried before falling back to -GnuPG. +If B<--use-gpg-agent> is supplied along with either +B<--pgpkey=>I<KEYFILE> or B<--cmskey=>I<KEYFILE> arguments, the +I<KEYFILE> arguments will be tried before falling back to GnuPG. If B<email-print-mime-structure> has been asked to decrypt parts with either B<--pgpkey=>I<KEYFILE> or with B<--use-gpg-agent>, and it -- 2.24.0