Notes on the use of OpenPGP

OpenPGP messages can be used for various purposes: Signing mail bodies, signing files, signing automatically generated messages and more. Here are some notes about things that can go wrong with that. (Of course, they're not exhaustive.)

Ambiguous plaintext

In general, in crypto protocols that involve some kind of authenticated/signed data, care must be taken to ensure that the type of a message is clear from the signed data. If the interpretation of signed data could be influenced by changing an unsigned header, an attacker could alter the meaning of signed data without invalidating the signature.
OpenPGP signatures do not come with a type field that clearly specifies the type of the signed data. In usecases where signatures are created automatically using the user's normal OpenPGP key and part of the signed data is controlled by an untrusted party, this means that the untrusted part must be restricted carefully to prevent the attacker from crafting the untrusted part so that the whole file has any second interpretation.
For example, git push --signed suffered from such an issue. During a signed push, a message containing a server-chosen token is created, with a layout like this:
certificate version 0.1 pusher {name} <{email}> {time} pushee {target url} nonce {server-chosen nonce} {sha1} {sha1} refs/heads/{branch}
Because the nonce was not explicitly filtered, it could contain any character apart from \0, \t, \n and space, with a maximum length of a few dozen kilobytes. Because PDF readers accept files with some leading and trailing garbage and \r is equivalent to \n and \f is equivalent to space in PDF, this allowed a malicious git server to trick any client that did a signed push into OpenPGP-signing an arbitrary PDF document that would still be accepted by most PDF readers.
If the OpenPGP key used needs to be the user's general-purpose key, such issues can only be fixed by severely limiting the format of potentially attacker-controlled, to-be-signed data. But if possible, it is a good idea to have special-purpose OpenPGP keys.

Validation of clearsigned text

Clearsigned messages are nice because the contents of the signed message are easy to view without first running the signed data through a program capable of taking an OpenPGP message apart and because the message and the signature can still be in the same file. However, when validating such signatures using GPG and when extracting the signed data, it is easy to accidentally treat data as signed that is not, in fact, signed.
GPG allows leading and trailing unsigned data to appear in clearsigned messages:
$ (echo 'this is unsigned data'; echo 'this is signed data' | gpg --clearsign; echo 'this is more unsigned data') > signed_file $ cat signed_file this is unsigned data -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 this is signed data -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAEBAgAGBQJVHyp3AAoJEPasYGZHTO8IVq0H/35fPtZUskXCLkgitKBg1acx Xj0sUg8L+9yzqRdfGRktspHhS13vLqPC/KMttAsorN12h68Y0da1psSwRijy6nJn ymAmOSaJHkiidBxiX7GV62Rza2/8kz/HMs2oLYLr3xwBKTKxRq9cRvo4Do6MsOzt Y3u8+x/w+ieJdzq0jUBBfM9hCCdg0bh8rfAQff2tkZ1Xbl/dsBVb/0/bJm2xno+V 6jS+gVCuzMgezgDrbv2/QrQpIoEWyxhugxfo19HCdnHokyT+Y801J/oktG1h55JF WePW1e/7m4PmJGts+6FxbPUNzEkYMWo1FNFQQzZhua1TDp7+Eg3/pjWv0LdpJQo= =FKR+ -----END PGP SIGNATURE----- this is more unsigned data $ gpg --status-fd=1 --verify < signed_file gpg: Signature made Sat Apr 4 02:04:07 2015 CEST using RSA key ID 474CEF08 [GNUPG:] SIG_ID /aUuR3GxKdo+BN3ybrF3kIuQ8MA 2015-04-04 1428105847 [GNUPG:] GOODSIG F6AC6066474CEF08 Dummy Signer gpg: Good signature from "Dummy Signer" [GNUPG:] VALIDSIG 1577492D1FC6401B72E2161BF6AC6066474CEF08 2015-04-04 1428105847 0 4 0 1 2 01 1577492D1FC6401B72E2161BF6AC6066474CEF08 [GNUPG:] TRUST_ULTIMATE
Even if an implementation attempts to find the correct -----BEGIN PGP SIGNED MESSAGE----- and -----BEGIN PGP SIGNATURE----- delimiters, this is very brittle: Minor differences in the handling of edgecases can lead to GPG and the other program seeing different signed text.
The dpkg-source program, which is used on debian to verify and extract source packages, had such an issue: Because dpkg-source allowed a larger set of trailing whitespace characters after the -----BEGIN PGP SIGNED MESSAGE----- marker than GPG, it was possible to craft a .dsc debian source file which would pass signature verification, but from which dpkg-source would only read unsigned data. Such a file would look like this (where \f is the form feed character):
-----BEGIN PGP SIGNED MESSAGE-----\f Format: 3.0 (native) Source: gnupg Version: 1.4.16 Files: 7f387fd82ae512e88f0461f78ff47db9 340 gnupg_1.4.16.tar.xz -----BEGIN PGP SIGNATURE-----\f -----END PGP SIGNATURE-----\f -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Format: 3.0 (quilt) Source: gnupg Binary: gnupg, gnupg-curl, gpgv, gnupg-udeb, gpgv-udeb, gpgv-win32 Architecture: any all Version: 1.4.16-1 Maintainer: Debian GnuPG-Maintainers Uploaders: Sune Vuorela , Daniel Leidert , Thijs Kinkhorst Homepage: http://www.gnupg.org Standards-Version: 3.9.5 Vcs-Browser: http://anonscm.debian.org/viewvc/pkg-gnupg/gnupg/ Vcs-Svn: svn://anonscm.debian.org/pkg-gnupg/gnupg/trunk/ Build-Depends: debhelper (>> 7), libz-dev, libldap2-dev, libbz2-dev, libusb-dev [!hurd-i386], libreadline-dev, file, gettext, libcurl4-gnutls-dev Build-Depends-Indep: mingw-w64 Package-List: gnupg deb utils important gnupg-curl deb utils optional gnupg-udeb udeb debian-installer extra gpgv deb utils important gpgv-udeb udeb debian-installer extra gpgv-win32 deb utils extra Checksums-Sha1: ea40324a5b2e3a16ffb63ea0ccc950a3faf5b11c 5073484 gnupg_1.4.16.orig.tar.gz 8f01e889a8d7762c31aa5b01b2e256cc9776f8bc 27659 gnupg_1.4.16-1.debian.tar.gz Checksums-Sha256: f3af2f9c34c305869ad38b4ee7ab9e1487f50884ee8d9d42cccb31e1ced5cdef 5073484 gnupg_1.4.16.orig.tar.gz fbcf809bcb4feee2527f84ad84c3b5f7bba61d2cf2b688e7f3d1aaa9b984046c 27659 gnupg_1.4.16-1.debian.tar.gz Files: 3d46439c5ba304dd2cfc9070a5ce1338 5073484 gnupg_1.4.16.orig.tar.gz d082796e798fff007fd2ba2f728e6309 27659 gnupg_1.4.16-1.debian.tar.gz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQEcBAEBAgAGBQJSy92uAAoJEFb2GnlAHawEwHMIAII9MT1mNldscurjXlubBvbM r46waorhFwhzum11Ye8mZB/rmKwm1ih8GmrzazCP5cvMjiZwqHiJy57XRIH/TI3a w8iCmXh3P2bbZ/7Sem1XMYK3yXqWDN/6lYDy4T34t+TX4om940kAP+g5WtsSNL7K qa55yxEh3j5VCwANyLBPVpMMz/gi3WzCVRknj2H5gnFO4Hw4QoOe2bcbhYGqhZOY YUm5NbvAzcb/PmXXKwy2IvPNv9TeyraIjApXfuJF5TaxW3yixNoknoDv68Kf3fmc i5CdGTZm2plQMkxypwZXv007tdiS9M/lfyyT5JbsXaD3KjJJ1mdCfUuDL7FbtN8= =P6Nw -----END PGP SIGNATURE-----
While GPG ignored the upper half because of the trailing form feed characters, dpkg-source parsed the upper half and stopped after the first -----BEGIN PGP SIGNATURE-----, effectively leading to a complete bypass of the signature verification.
There is another quirk: Clearsigned data is Dash-Escaped Text, which means that it is possible to prepend extra dashes to arbitrary lines of the message without invalidating the signature. Here, I took the same signed message from above and edited the cleartext part manually:
$ gpg --verify --status-fd=1 -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 - this is signed data -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAEBAgAGBQJVHyp3AAoJEPasYGZHTO8IVq0H/35fPtZUskXCLkgitKBg1acx Xj0sUg8L+9yzqRdfGRktspHhS13vLqPC/KMttAsorN12h68Y0da1psSwRijy6nJn ymAmOSaJHkiidBxiX7GV62Rza2/8kz/HMs2oLYLr3xwBKTKxRq9cRvo4Do6MsOzt Y3u8+x/w+ieJdzq0jUBBfM9hCCdg0bh8rfAQff2tkZ1Xbl/dsBVb/0/bJm2xno+V 6jS+gVCuzMgezgDrbv2/QrQpIoEWyxhugxfo19HCdnHokyT+Y801J/oktG1h55JF WePW1e/7m4PmJGts+6FxbPUNzEkYMWo1FNFQQzZhua1TDp7+Eg3/pjWv0LdpJQo= =FKR+ -----END PGP SIGNATURE----- gpg: Signature made Sat Apr 4 02:04:07 2015 CEST using RSA key ID 474CEF08 [GNUPG:] SIG_ID /aUuR3GxKdo+BN3ybrF3kIuQ8MA 2015-04-04 1428105847 [GNUPG:] GOODSIG F6AC6066474CEF08 Dummy Signer gpg: Good signature from "Dummy Signer" [GNUPG:] VALIDSIG 1577492D1FC6401B72E2161BF6AC6066474CEF08 2015-04-04 1428105847 0 4 0 1 2 01 1577492D1FC6401B72E2161BF6AC6066474CEF08 [GNUPG:] TRUST_ULTIMATE

To prevent such issues, either split the clearsigned message into message and signature and pass them to GPG seperately (e.g. apt-get does this to verify the authenticity of the Release file) or let GPG extract the clearsigned data for you. In general, it is dangerous to have one parser which validates data and a second parser which uses the data.