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 | 2 ++ email-print-mime-structure | 12 ++++++++++-- email-print-mime-structure.1.pod | 17 ++++++++++++++--- tests/email-print-mime-structure.sh | 6 ++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index f04ce79..d2e07da 100644 --- a/debian/control +++ b/debian/control @@ -12,6 +12,7 @@ Build-Depends: gpg-agent <!nocheck>, gpgsm <!nocheck>, mypy <!nocheck>, + openssl <!nocheck>, perl, python3 <!nocheck>, python3-argcomplete, @@ -54,6 +55,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 e82d56e..4de0789 100755 --- a/email-print-mime-structure +++ b/email-print-mime-structure @@ -83,7 +83,7 @@ class MimePrinter(object): print(f'{prefix}{z.get_content_type()}{cset}{disposition}{fname} {nbytes:d} bytes') cryptopayload:Optional[Message] = 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 \ @@ -116,6 +116,12 @@ class MimePrinter(object): if cryptopayload is None and self.args.use_gpg_agent: cryptopayload = self.pipe_decrypt(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_decrypt(ciphertext, cmd) + if cryptopayload: + return cryptopayload if self.args.use_gpg_agent: cryptopayload = self.pipe_decrypt(ciphertext, ['gpgsm', '--batch', '--decrypt']) if cryptopayload is None: @@ -175,7 +181,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 diff --git a/tests/email-print-mime-structure.sh b/tests/email-print-mime-structure.sh index 5cd34b2..8c646d0 100755 --- a/tests/email-print-mime-structure.sh +++ b/tests/email-print-mime-structure.sh @@ -22,6 +22,12 @@ for eml in tests/email-print-mime-structure/*.eml; do GNUPGHOME="$testgpghome" test_eml "$base" --use-gpg-agent rm -rf "$testgpghome" elif [ -e "$p12key" ]; then + printf "Testing %s (OpenSSL)\n" "${eml##*/}" + grep -v ^- < "$p12key" | base64 -d | \ + openssl pkcs12 -nocerts -nodes -passin pass: -passout pass: -out "$base.pemkey" + test_eml "$base" --cmskey "$base.pemkey" + rm -f "$base.pemkey" + testgpghome=$(mktemp -d) printf "Testing %s (GnuPG S/MIME)\n" "${eml##*/}" gpgsm --pinentry-mode=loopback --passphrase-fd 4 4<<<'' --homedir="$testgpghome" --batch --quiet --import <"$p12key" -- 2.24.0