Merge lp:~cjwatson/launchpad/digest-algo-sha512 into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17952
Proposed branch: lp:~cjwatson/launchpad/digest-algo-sha512
Merge into: lp:launchpad
Diff against target: 158 lines (+48/-23)
2 files modified
lib/lp/services/gpg/handler.py (+9/-7)
lib/lp/services/gpg/tests/test_gpghandler.py (+39/-16)
To merge this branch: bzr merge lp:~cjwatson/launchpad/digest-algo-sha512
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+289052@code.launchpad.net

Commit message

Use SHA-512 digests for GPG signing where possible.

Description of the change

Use SHA-512 digests for GPG signing where possible.

This may not work for 1024-bit DSA keys, but it will at least still continue to sign content as gpg falls back to a digest it can use. As far as I can tell, all the key types we've ever used as PPA signing keys can use SHA-512 digests.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
Revision history for this message
Jason Gerard DeRose (jderose) wrote :

Colin - would you consider using SHA-384 instead?

Like SHA-512, SHA-384 uses the same 512-bit internal state size, but because SHA-384 only exposes 384 bits of of this internal state in the digest, it's immune to length extension attacks:

https://en.wikipedia.org/wiki/Length_extension_attack

From page 87 of *Cryptography Engineering* by Niels Ferguson, Bruce Schneier, and Tadayoshi Kohno:

"""
There is another fix to some of these weaknesses with the SHA-2 family of iterative hash functions: Truncate the output. If a hash function produces n-bit outputs, only use the first n - s bit as the hash value for some positive s. In fact, SHA-224 and SHA-384 both already do this; SHA-224 is roughly SHA-256 with 32 output bits dropped, and SHA-384 is roughly SHA-512 with 128 output bits dropped.
"""

They ultimately recommend using SHA-512 truncated to 256 bits (if you're going to use a SHA-2 hash), but that's not feasible in this case as GPG doesn't support truncated SHA-512.

In terms of standard SHA-2 family hash functions supported by GPG, SHA-384 is the best option in my opinion.

Note that I don't recommend SHA-224 as its 256-bit internal state size is too small by modern standards. SHA-384/SHA-512 are also considerably faster than SHA-224/SHA-256 when it comes to 64-bit software implementations.

Revision history for this message
William Grant (wgrant) wrote :

We avoided SHA-512 in Packages/Sources due to data size, and SHA-384 wasn't an option there. But it would be a possibility for OpenPGP signatures.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/gpg/handler.py'
--- lib/lp/services/gpg/handler.py 2016-03-16 00:34:56 +0000
+++ lib/lp/services/gpg/handler.py 2016-03-18 00:51:12 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -13,7 +13,6 @@
13import atexit13import atexit
14import base6414import base64
15import httplib15import httplib
16import json
17import os16import os
18import shutil17import shutil
19import socket18import socket
@@ -93,15 +92,18 @@
93 """92 """
94 self.home = tempfile.mkdtemp(prefix='gpg-')93 self.home = tempfile.mkdtemp(prefix='gpg-')
95 confpath = os.path.join(self.home, 'gpg.conf')94 confpath = os.path.join(self.home, 'gpg.conf')
96 conf = open(confpath, 'w')
97 # set needed GPG options, 'auto-key-retrieve' is necessary for95 # set needed GPG options, 'auto-key-retrieve' is necessary for
98 # automatically retrieve from the keyserver unknown key when96 # automatically retrieve from the keyserver unknown key when
99 # verifying signatures and 'no-auto-check-trustdb' avoid wasting97 # verifying signatures and 'no-auto-check-trustdb' avoid wasting
100 # time verifying the local keyring consistence.98 # time verifying the local keyring consistence.
101 conf.write('keyserver hkp://%s\n'99 with open(confpath, 'w') as conf:
102 'keyserver-options auto-key-retrieve\n'100 conf.write('keyserver hkp://%s\n' % config.gpghandler.host)
103 'no-auto-check-trustdb\n' % config.gpghandler.host)101 conf.write('keyserver-options auto-key-retrieve\n')
104 conf.close()102 conf.write('no-auto-check-trustdb\n')
103 # Prefer a SHA-2 hash where possible, otherwise GPG will fall
104 # back to a hash it can use.
105 conf.write(
106 'personal-digest-preferences SHA512 SHA384 SHA256 SHA224\n')
105 # create a local atexit handler to remove the configuration directory107 # create a local atexit handler to remove the configuration directory
106 # on normal termination.108 # on normal termination.
107109
108110
=== modified file 'lib/lp/services/gpg/tests/test_gpghandler.py'
--- lib/lp/services/gpg/tests/test_gpghandler.py 2016-03-17 04:05:36 +0000
+++ lib/lp/services/gpg/tests/test_gpghandler.py 2016-03-18 00:51:12 +0000
@@ -1,28 +1,22 @@
1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import os
4import random5import random
5import string6import string
7import subprocess
68
9import gpgme
7from testtools.matchers import (10from testtools.matchers import (
8 Contains,
9 ContainsDict,11 ContainsDict,
10 Equals,12 Equals,
11 HasLength,13 HasLength,
12 Not,
13 raises,14 raises,
14 )15 )
15from zope.component import getUtility16from zope.component import getUtility
16from zope.security.proxy import removeSecurityProxy17from zope.security.proxy import removeSecurityProxy
1718
18from lp.registry.interfaces.gpg import IGPGKeySet
19from lp.services.config.fixture import (
20 ConfigFixture,
21 ConfigUseFixture,
22 )
23from lp.services.gpg.handler import GPGClient
24from lp.services.gpg.interfaces import (19from lp.services.gpg.interfaces import (
25 GPGKeyAlgorithm,
26 GPGKeyDoesNotExistOnServer,20 GPGKeyDoesNotExistOnServer,
27 GPGKeyTemporarilyNotFoundError,21 GPGKeyTemporarilyNotFoundError,
28 GPGServiceException,22 GPGServiceException,
@@ -30,7 +24,6 @@
30 IGPGHandler,24 IGPGHandler,
31 )25 )
32from lp.services.log.logger import BufferLogger26from lp.services.log.logger import BufferLogger
33from lp.services.openid.model.openididentifier import OpenIdIdentifier
34from lp.services.timeout import (27from lp.services.timeout import (
35 get_default_timeout_function,28 get_default_timeout_function,
36 set_default_timeout_function,29 set_default_timeout_function,
@@ -49,7 +42,6 @@
49 test_keyrings,42 test_keyrings,
50 test_pubkey_from_email,43 test_pubkey_from_email,
51 )44 )
52from lp.testing.gpgservice import GPGKeyServiceFixture
53from lp.testing.keyserver import KeyServerTac45from lp.testing.keyserver import KeyServerTac
54from lp.testing.layers import (46from lp.testing.layers import (
55 GPGServiceLayer,47 GPGServiceLayer,
@@ -58,13 +50,13 @@
58 )50 )
5951
6052
61class TestImportKeyRing(TestCase):53class TestGPGHandler(TestCase):
62 """Tests for keyring imports"""54 """Unit tests for the GPG handler."""
63 layer = LaunchpadFunctionalLayer55 layer = LaunchpadFunctionalLayer
6456
65 def setUp(self):57 def setUp(self):
66 """Get a gpghandler and login"""58 """Get a gpghandler and login"""
67 super(TestImportKeyRing, self).setUp()59 super(TestGPGHandler, self).setUp()
68 login(ANONYMOUS)60 login(ANONYMOUS)
69 self.gpg_handler = getUtility(IGPGHandler)61 self.gpg_handler = getUtility(IGPGHandler)
70 self.gpg_handler.resetLocalState()62 self.gpg_handler.resetLocalState()
@@ -75,7 +67,7 @@
75 # This should be a zope test cleanup thing per SteveA.67 # This should be a zope test cleanup thing per SteveA.
76 self.gpg_handler.resetLocalState()68 self.gpg_handler.resetLocalState()
77 logout()69 logout()
78 super(TestImportKeyRing, self).tearDown()70 super(TestGPGHandler, self).tearDown()
7971
80 def populateKeyring(self):72 def populateKeyring(self):
81 for email in iter_test_key_emails():73 for email in iter_test_key_emails():
@@ -213,6 +205,37 @@
213 GPGKeyDoesNotExistOnServer,205 GPGKeyDoesNotExistOnServer,
214 removeSecurityProxy(self.gpg_handler)._getPubKey, fingerprint)206 removeSecurityProxy(self.gpg_handler)._getPubKey, fingerprint)
215207
208 def test_signContent_uses_sha512_digests(self):
209 secret_keys = [
210 ("ppa-sample@canonical.com.sec", ""), # 1024R
211 ("ppa-sample-4096@canonical.com.sec", ""), # 4096R
212 ]
213 for key_name, password in secret_keys:
214 self.gpg_handler.resetLocalState()
215 secret_key = import_secret_test_key(key_name)
216 content = "abc\n"
217 signed_content = self.gpg_handler.signContent(
218 content, secret_key.fingerprint, password)
219 signature = self.gpg_handler.getVerifiedSignature(signed_content)
220 self.assertEqual(content, signature.plain_data)
221 self.assertEqual(secret_key.fingerprint, signature.fingerprint)
222 # pygpgme doesn't tell us the hash algorithm used for a verified
223 # signature, so we have to do this by hand. Sending --status-fd
224 # output to stdout is a bit dodgy, but at least with --quiet
225 # it's OK for test purposes and it simplifies subprocess
226 # plumbing.
227 with open(os.devnull, "w") as devnull:
228 gpg_proc = subprocess.Popen(
229 ["gpg", "--quiet", "--status-fd", "1", "--verify"],
230 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
231 stderr=devnull, universal_newlines=True)
232 status = gpg_proc.communicate(signed_content)[0].splitlines()
233 validsig_prefix = "[GNUPG:] VALIDSIG "
234 [validsig_line] = [
235 line for line in status if line.startswith(validsig_prefix)]
236 validsig_tokens = validsig_line[len(validsig_prefix):].split()
237 self.assertEqual(gpgme.MD_SHA512, int(validsig_tokens[7]))
238
216239
217class GPGServiceZopelessLayer(ZopelessDatabaseLayer, GPGServiceLayer):240class GPGServiceZopelessLayer(ZopelessDatabaseLayer, GPGServiceLayer):
218 """A layer specifically for running the IGPGClient utility tests."""241 """A layer specifically for running the IGPGClient utility tests."""