In some cases, the user may want to try to use their own GnuPG secret
keys to decrypt encrypted parts of the message.

By default it is disabled so that we aren't accidentally triggering
the use of user secret key material.

Note that gpg(1) says:

       --batch
       --no-batch
              Use  batch  mode.  Never ask, do not allow interactive commands.
              --no-batch disables this option.  Note that even with a filename
              given  on  the  command  line, gpg might still need to read from
              STDIN (in particular if gpg figures that the input is a detached
              signature  and no data file has been specified).  Thus if you do
              not want to feed data via STDIN, you  should  connect  STDIN  to
              g‘/dev/null’.

              It  is  highly recommended to use this option along with the op‐
              tions --status-fd and --with-colons for any  unattended  use  of
              gpg.

I am deliberately choosing to not use either --status-fd or
--with-colons for email-print-mime-structure.

I'm not using --with-colons because there is no output from GnuPG that
we expect to be machine-readable -- we're just looking for the cleartext
of whatever ciphertext is in the message part.

I'm not using --status-fd because there is nothing actionable we can do
with GnuPG status messages, and asking for them would require switching
from subprocess.run to subprocess.Popen to take advantage of the
pass_fds argument, which in turn would make the script only work in a
POSIX environment (i believe, but have not tested, that the script can
currently be used on Windows).

Signed-off-by: Daniel Kahn Gillmor <d...@fifthhorseman.net>
---
 debian/control                   |  2 ++
 email-print-mime-structure       | 24 ++++++++++++++++++++++--
 email-print-mime-structure.1.pod | 24 +++++++++++++++++++-----
 3 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/debian/control b/debian/control
index fc2bccc..4c3b956 100644
--- a/debian/control
+++ b/debian/control
@@ -38,6 +38,8 @@ Depends:
 Recommends:
  devscripts,
  git,
+ gpg,
+ gpg-agent,
  notmuch,
  python3-pgpy,
 Architecture: all
diff --git a/email-print-mime-structure b/email-print-mime-structure
index d780883..5497597 100755
--- a/email-print-mime-structure
+++ b/email-print-mime-structure
@@ -29,9 +29,11 @@ Example:
 If you want to number the parts, i suggest piping the output through
 something like "cat -n"
 '''
+import os
 import sys
 import email
 import logging
+import subprocess
 
 from argparse import ArgumentParser, Namespace
 from typing import Optional, Union, List, Tuple, Any
@@ -70,7 +72,7 @@ class MimePrinter(object):
             nbytes = len(payload)
 
         print(f'{prefix}{z.get_content_type()}{cset}{disposition}{fname} 
{nbytes:d} bytes')
-        try_decrypt:bool = True if self.args.pgpkey else False
+        try_decrypt:bool = self.args.pgpkey or self.args.use_gpg_agent
 
         if try_decrypt and \
            (parent is not None) and \
@@ -84,6 +86,8 @@ class MimePrinter(object):
                 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
@@ -107,7 +111,20 @@ class MimePrinter(object):
             except:
                 pass
         return None
-                    
+
+    def gpg_decrypt(self, ciphertext:str) -> Optional[Message]:
+        inp:int
+        outp:int
+        inp, outp = os.pipe()
+        with open(outp, 'w') as outf:
+            outf.write(ciphertext)
+        out:subprocess.CompletedProcess[bytes] = subprocess.run(['gpg', 
'--batch', '--decrypt'],
+                                                                stdin=inp,
+                                                                
capture_output=True)
+        if out.returncode == 0:
+            return email.message_from_bytes(out.stdout)
+        return None
+
     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)
@@ -132,6 +149,9 @@ def main() -> None:
                                            epilog="Example: 
email-print-mime-structure <message.eml")
     parser.add_argument('--pgpkey', metavar='KEYFILE', action='append',
                         help='OpenPGP Transferable Secret Key for decrypting')
+    parser.add_argument('--use-gpg-agent', metavar='true|false', type=bool,
+                        default=False,
+                        help='Ask local GnuPG installation for decryption')
     args:Namespace = parser.parse_args()
     msg:Union[Message, str, int, Any] = email.message_from_file(sys.stdin)
 
diff --git a/email-print-mime-structure.1.pod b/email-print-mime-structure.1.pod
index b846d87..69b1cdc 100644
--- a/email-print-mime-structure.1.pod
+++ b/email-print-mime-structure.1.pod
@@ -29,6 +29,25 @@ standard input, this key will be tried for decryption.  May 
be used
 multiple times if you want to try decrypting with more than one secret
 key.
 
+OpenPGP secret keys listed in B<--pgpkey=> are used ephemerally, and
+do not interact with any local GnuPG keyring.
+
+=item B<--use-gpg-agent=>I<true>|I<false>
+
+If I<true>, and B<email-print-mime-structure> encounters a
+PGP/MIME-encrypted part, it will try to decrypt the part using the
+secret keys found in the local installation of GnuPG. (default:
+I<false>)
+
+If both B<--pgpkey=>I<KEYFILE> and B<--use-gpg-agent=true> are
+supplied, 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=true>, and it
+is unable to decrypt an encrypted part, it will emit a warning to
+stderr.
+
 =item B<--help>, B<-h>
 
 Show usage instructions.
@@ -49,11 +68,6 @@ Show usage instructions.
 
 =head1 LIMITATIONS
 
-B<email-print-mime-structure> only decrypts encrypted e-mails using
-raw, non-password-protected OpenPGP secret keys (see B<--pgpkey>,
-above).  If it is unable to decrypt an encrypted part with the
-supplied keys, it will warn on stderr.
-
 B<email-print-mime-structure>'s output is not stable, and is not
 intended to be interpreted by machines, so please do not depend on it
 in scripts!
-- 
2.24.0

Reply via email to