summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2019-09-06 07:03:56 -0700
committerSean Whitton <spwhitton@spwhitton.name>2019-09-06 07:03:56 -0700
commite73cee37bff56c4e3f5fed99c53a3b859dce4044 (patch)
tree1559a1b12ad92723cbbba97e91a39fc394ff2419
parent2029d4a51f82a4d27d0e75106bd4107b77719e40 (diff)
parent1cb54e1d5000856bfedd98c5cf4274aa411e44c3 (diff)
downloadmailscripts-e73cee37bff56c4e3f5fed99c53a3b859dce4044.tar.gz
Merge tag 'debian/0.10-1' into buster-bpo
mailscripts release 0.10-1 for unstable (sid) [dgit] [dgit distro=debian no-split --quilt=linear] # gpg: Signature made Tue 30 Jul 2019 05:29:55 AM MST # gpg: using RSA key 9B917007AE030E36E4FC248B695B7AE4BF066240 # gpg: Good signature from "Sean Whitton <spwhitton@spwhitton.name>" [ultimate] # Primary key fingerprint: 8DC2 487E 51AB DD90 B5C4 753F 0F56 D055 3B6D 411B # Subkey fingerprint: 9B91 7007 AE03 0E36 E4FC 248B 695B 7AE4 BF06 6240
-rw-r--r--CONTRIBUTING.rst15
-rw-r--r--Makefile1
-rw-r--r--debian/changelog9
-rw-r--r--debian/copyright6
-rw-r--r--debian/mailscripts.install1
-rw-r--r--debian/mailscripts.manpages1
-rwxr-xr-xemail-extract-openpgp-certs114
-rw-r--r--email-extract-openpgp-certs.1.pod57
8 files changed, 202 insertions, 2 deletions
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..707e2f6
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,15 @@
+Signing off your commits
+========================
+
+Contributions are accepted upstream under the terms set out in
+``debian/copyright`` and in the headers of individual source files.
+Completely new scripts can use any DFSG-compatible license. You must
+certify the contents of the file ``DEVELOPER-CERTIFICATE`` for your
+contribution. To do this, append a ``Signed-off-by`` line to end of
+your commit message. An easy way to add this line is to pass the
+``-s`` option to git-commit(1). Here is an example of a
+``Signed-off-by`` line:
+
+::
+
+ Signed-off-by: Sean Whitton <spwhitton@spwhitton.name>
diff --git a/Makefile b/Makefile
index 220aa6f..48cb2fa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
MANPAGES=mdmv.1 mbox2maildir.1 \
notmuch-slurp-debbug.1 notmuch-extract-patch.1 maildir-import-patch.1 \
+ email-extract-openpgp-certs.1 \
notmuch-import-patch.1
all: $(MANPAGES)
diff --git a/debian/changelog b/debian/changelog
index d454fce..ced2186 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+mailscripts (0.10-1) unstable; urgency=medium
+
+ * New script: email-extract-openpgp-certs(1) (Closes: #932993).
+ Thanks to Daniel Kahn Gillmor for the patch.
+ * Add CONTRIBUTING.rst.
+ * d/copyright: completely new scripts may use any DFSG-compatible license.
+
+ -- Sean Whitton <spwhitton@spwhitton.name> Tue, 30 Jul 2019 13:25:36 +0100
+
mailscripts (0.9-1~bpo10+1) buster-backports; urgency=medium
* Rebuild for buster-backports.
diff --git a/debian/copyright b/debian/copyright
index b3d860e..f6f2e0e 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -3,6 +3,7 @@ Collection of scripts for manipulating e-mail on Debian
Copyright (C)2017 Aurelien Aptel
Copyright (C)2017-2019 Sean Whitton
+Copyright (C)2019 Daniel Kahn Gillmor
These programs are free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@@ -22,8 +23,9 @@ The contents of the notmuch-extract-patch/ directory was imported from
https://github.com/aaptel/notmuch-extract-patch/ using git-subtree(1)
-Contributions are accepted upstram under the same terms; please sign
-off your patches (by writing an approprite Signed-Off-By tag in your
+Contributions are accepted upstream under the same terms (or another
+DFSG-compatible license for completely new scripts); please sign off
+your patches (by writing an approprite Signed-Off-By tag in your
commit message or patch submission) to indicate your attestation that
the Developer Certificate of Origin (version 1.1) applies.
diff --git a/debian/mailscripts.install b/debian/mailscripts.install
index 36b87ea..d6f69f5 100644
--- a/debian/mailscripts.install
+++ b/debian/mailscripts.install
@@ -4,3 +4,4 @@ notmuch-slurp-debbug /usr/bin
maildir-import-patch /usr/bin
notmuch-import-patch /usr/bin
notmuch-extract-patch/notmuch-extract-patch /usr/bin
+email-extract-openpgp-certs /usr/bin
diff --git a/debian/mailscripts.manpages b/debian/mailscripts.manpages
index fa878ca..ab761b2 100644
--- a/debian/mailscripts.manpages
+++ b/debian/mailscripts.manpages
@@ -4,3 +4,4 @@ notmuch-slurp-debbug.1
maildir-import-patch.1
notmuch-import-patch.1
notmuch-extract-patch.1
+email-extract-openpgp-certs.1
diff --git a/email-extract-openpgp-certs b/email-extract-openpgp-certs
new file mode 100755
index 0000000..2a95748
--- /dev/null
+++ b/email-extract-openpgp-certs
@@ -0,0 +1,114 @@
+#!/usr/bin/python3
+
+# Copyright (C) 2019 Daniel Kahn Gillmor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+'''Extract all OpenPGP certificates from an e-mail message
+
+This is a simple script that is designed to take an e-mail
+(rfc822/message) on standard input, and produces a series of
+ASCII-armored OpenPGP certificates on standard output.
+
+It currently tries to find OpenPGP certificates based on MIME types of
+attachments (application/pgp-keys), and by pulling out anything that
+looks like an Autocrypt: or Autocrypt-Gossip: header (see
+https://autocrypt.org).
+
+'''
+
+import email
+import sys
+import base64
+import binascii
+import codecs
+from typing import Optional, Generator
+
+# parse email from stdin
+message = email.message_from_binary_file(sys.stdin.buffer)
+
+def openpgp_ascii_armor_checksum(data: bytes) -> bytearray:
+ '''OpenPGP ASCII-armor checksum
+
+(see https://tools.ietf.org/html/rfc4880#section-6.1)'''
+
+ init = 0xB704CE
+ poly = 0x1864CFB
+ crc = init
+ for b in data:
+ crc ^= b << 16
+ for i in range(8):
+ crc <<= 1
+ if crc & 0x1000000:
+ crc ^= poly
+ val = crc & 0xFFFFFF
+ out = bytearray(3)
+ out[0] = (val >> 16) & 0xFF
+ out[1] = (val >> 8) & 0xFF
+ out[2] = val & 0xFF
+ return out
+
+def enarmor_certificate(data: bytes) -> str:
+ '''OpenPGP ASCII-armor
+
+(see https://tools.ietf.org/html/rfc4880#section-6.2)'''
+
+ cksum = openpgp_ascii_armor_checksum(data)
+ key = codecs.decode(base64.b64encode(data), 'ascii')
+ linelen = 64
+ key = '\n'.join([key[i:i+linelen] for i in range(0, len(key), linelen)])
+ return '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n' +\
+ key + \
+ '\n=' + codecs.decode(base64.b64encode(cksum), 'ascii') +\
+ '\n-----END PGP PUBLIC KEY BLOCK-----\n'
+
+def get_autocrypt_keys(m: email.message.Message) -> Generator[str, None, None]:
+ '''Extract all Autocrypt headers from message
+
+Note that we ignore the addr= property.
+'''
+ hdrs = m.get_all('Autocrypt')
+ if hdrs is None: # the email.get_all() api is kindn of sad.
+ hdrs = []
+ ghdrs = m.get_all('Autocrypt-Gossip')
+ if ghdrs is None: # the email.get_all() api is kindn of sad.
+ ghdrs = []
+ for ac in hdrs + ghdrs:
+ # parse the base64 part
+ try:
+ keydata = str(ac).split('keydata=')[1].strip()
+ keydata = keydata.replace(' ', '').replace('\t', '')
+ keydatabin = base64.b64decode(keydata)
+ yield enarmor_certificate(keydatabin)
+ except (binascii.Error, IndexError) as e:
+ print("failure to parse Autocrypt header: %s" % e,
+ file=sys.stderr)
+
+def extract_attached_keys(m: email.message.Message) -> Generator[str, None, None]:
+ for part in m.walk():
+ if part.get_content_type() == 'application/pgp-keys':
+ p = part.get_payload(decode=True)
+ if not isinstance(p, bytes):
+ raise TypeError('Expected part payload to be bytes')
+ if p.startswith(b'-----BEGIN PGP PUBLIC KEY BLOCK-----\n'):
+ yield codecs.decode(p, 'ascii')
+ else: # this is probably binary-encoded, let's pretend that it is!
+ yield enarmor_certificate(p)
+
+# FIXME: should we try to decrypt encrypted messages as well?
+
+for a in get_autocrypt_keys(message):
+ print(a, end='')
+for a in extract_attached_keys(message):
+ print(a, end='')
diff --git a/email-extract-openpgp-certs.1.pod b/email-extract-openpgp-certs.1.pod
new file mode 100644
index 0000000..9983de0
--- /dev/null
+++ b/email-extract-openpgp-certs.1.pod
@@ -0,0 +1,57 @@
+=head1 NAME
+
+email-extract-openpgp-certs - extract OpenPGP certificates from an e-mail
+
+=head1 SYNOPSIS
+
+B<email-extract-openpgp-certs> <B<message.eml> | B<gpg> B<--import>
+
+=head1 DESCRIPTION
+
+B<email-extract-openpgp-certs> extracts all the things it can find
+that look like they might be OpenPGP certificates in an e-mail, and
+produces them on standard output.
+
+It currently knows about how to find OpenPGP certificates as
+attachments of MIME type application/pgp-keys, and Autocrypt: style
+headers.
+
+=head1 OPTIONS
+
+None.
+
+=head1 EXAMPLE
+
+=over 4
+
+ $ notmuch show --format-raw id:b7e48905-842f@example.net >test.eml
+ $ email-extract-openpgp-certs <test.eml | gpg --import
+
+=back
+
+=head1 LIMITATIONS
+
+B<email-extract-openpgp-certs> currently does not try to decrypt
+encrypted e-mails, so it cannot find certificates that are inside the
+message's cryptographic envelope.
+
+B<email-extract-openpgp-certs> does not attempt to validate the
+certificates it finds in any way. It does not ensure that they are
+valid OpenPGP certificates, or even that they are of a sane size. It
+doeds not try to establish any relationship between the extracted
+certificates and the messages in which they are sent. For example, it
+does not check the Autocrypt addr= attribute against the message's From:
+header.
+
+Importing certificates extracted from an arbitrary e-mail in this way
+into a curated keyring is not a good idea. Better to extract into an
+ephemeral location, inspect, filter, and then selectively import.
+
+=head1 SEE ALSO
+
+gpg(1), https://autocrypt.org, https://tools.ietf.org/html/rfc4880, https://tools.ietf.org/html/rfc3156
+
+=head1 AUTHOR
+
+B<email-extract-openpgp-certs> and this manpage were written by Daniel
+Kahn Gillmor, with guidance and advice from many others.