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

Reply via email to