Skip to content
This repository was archived by the owner on Jan 29, 2025. It is now read-only.

Commit 6742c2b

Browse files
bors[bot]mythmon
andcommitted
Merge #1820
1820: Re-enable signature health checks with a faster library r=rehandalal a=mythmon As a reminder, we disabled these checks because they were too slow. The [library](https://pypi.org/project/ecdsa/) we were using to validate signatures is a pure Python library, and is not focused on speed. I added [a new library](https://pypi.org/project/fastecdsa/) that is more focused on speed, though it includes C dependencies, which might make our life harder. The new library is significantly less friendly, as well. I had to jump through some more hoops to actually use it, and I actually still use the slow library to decode the signature into the format expected by the new one. I think that's probably fine for now, but I could work on removing it entirely if it's important. To test the speed increase, I created 100 signed recipes, re-enabled the test, and timed the signature verification check before and after this change. Before: ``` Checked signatures for 100 recipes in 0:00:20.166276 (4.96 checks/second) ``` After: ``` Checked signatures for 100 recipes in 0:00:00.913976 (109 checks/second) ``` I think an almost 22x speed up should make it ok to re-enable these checks. :smile: I haven't tested the Docker build yet. Given that the new library has a C component, I suspect it might fail to install on some systems. On my laptop, I had to install `gmp-devel` to build the library. I expect CI will catch this issue though, so I'm not worried. Co-authored-by: Mike Cooper <[email protected]> Co-authored-by: Michael Cooper <[email protected]>
2 parents 185f138 + c0776b8 commit 6742c2b

File tree

4 files changed

+30
-26
lines changed

4 files changed

+30
-26
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ RUN groupadd --gid 10001 app && useradd -g app --uid 10001 --shell /usr/sbin/nol
44
RUN apt-get update && \
55
apt-get upgrade -y && \
66
apt-get install -y --no-install-recommends \
7-
gcc libpq-dev curl apt-transport-https libffi-dev openssh-client gnupg
7+
gcc libpq-dev curl apt-transport-https libffi-dev openssh-client gnupg python-dev libgmp3-dev
88

99
# Install node from NodeSource
1010
RUN curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \

normandy/recipes/checks.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,7 @@ def geoip_db_is_available(app_configs, **kwargs):
156156

157157
def register():
158158
register_check(actions_have_consistent_hashes)
159-
# Temporarily disabled, see Issue #900.
160-
# register_check(recipe_signatures_are_correct)
161-
# register_check(action_signatures_are_correct)
159+
register_check(recipe_signatures_are_correct)
160+
register_check(action_signatures_are_correct)
162161
register_check(signatures_use_good_certificates)
163162
register_check(geoip_db_is_available)

normandy/recipes/signing.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import re
66
from datetime import datetime
77

8-
import ecdsa
98
import requests
9+
import ecdsa.util
10+
import fastecdsa.ecdsa
11+
from fastecdsa.encoding.pem import PEMEncoder
1012
from pyasn1.codec.der.decoder import decode as der_decode
1113
from pyasn1.codec.native.encoder import encode as python_encode
1214
from pyasn1_modules import rfc5280
@@ -102,40 +104,34 @@ def verify_signature(data, signature, pubkey):
102104
If the signature is valid, returns True. If the signature is invalid, raise
103105
an exception explaining why.
104106
"""
107+
# Data must be encoded as bytes
105108
if isinstance(data, str):
106109
data = data.encode()
107110

108-
# Add data template
111+
# Content signature implicitly adds a prefix to signed data
109112
data = b"Content-Signature:\x00" + data
110113

111-
try:
112-
verifying_pubkey = ecdsa.VerifyingKey.from_pem(pubkey)
113-
except binascii.Error as e:
114-
if e.args == ("Incorrect padding",):
115-
raise WrongPublicKeySize()
116-
else:
117-
raise
118-
except IndexError:
119-
raise WrongPublicKeySize()
114+
# fastecdsa expects ASCII armored keys, but ours is unarmored. Add the
115+
# armor before passing the key to the library.
116+
EC_PUBLIC_HEADER = "-----BEGIN PUBLIC KEY-----"
117+
EC_PUBLIC_FOOTER = "-----END PUBLIC KEY-----"
118+
verifying_pubkey = PEMEncoder.decode_public_key(
119+
"\n".join([EC_PUBLIC_HEADER, pubkey, EC_PUBLIC_FOOTER])
120+
)
120121

121122
try:
122123
signature = base64.urlsafe_b64decode(signature)
124+
signature = ecdsa.util.sigdecode_string(signature, order=ecdsa.curves.NIST384p.order)
123125
except binascii.Error as e:
124126
if BASE64_WRONG_LENGTH_RE.match(e.args[0]):
125127
raise WrongSignatureSize("Base64 encoded signature was not a multiple of 4")
126128
else:
127129
raise
128-
129-
verified = False
130-
131-
try:
132-
verified = verifying_pubkey.verify(signature, data, hashfunc=hashlib.sha384)
133-
except ecdsa.keys.BadSignatureError:
134-
raise SignatureDoesNotMatch()
135130
except AssertionError as e:
136-
# The signature verifier has a clause like
131+
# The signature decoder has a clause like
137132
# assert len(signature) == 2*l, (len(signature), 2*l)
138-
# Check that the AssertionError is consistent with that
133+
# If the AssertionError is consistent with that signature, translate it
134+
# to a nicer error. Otherwise re-raise.
139135
if (
140136
len(e.args) == 1
141137
and isinstance(e.args[0], tuple)
@@ -147,8 +143,12 @@ def verify_signature(data, signature, pubkey):
147143
else:
148144
raise
149145

146+
verified = fastecdsa.ecdsa.verify(
147+
signature, data, verifying_pubkey, curve=fastecdsa.curve.P384, hashfunc=hashlib.sha384
148+
)
149+
150150
if not verified:
151-
raise BadSignature()
151+
raise SignatureDoesNotMatch()
152152

153153
return True
154154

requirements/default.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,9 @@ untangle==1.1.1 \
219219
--hash=sha256:e7cfa1ad57707e6b74cfea8b9fc50f7cbe9bbaf18401cc9d72192002bcd80bcb
220220
drf-yasg==1.14.0 \
221221
--hash=sha256:a276bc90af1902b1bd9c11927f75658da1a62aad2d87022ae5f653106ed09a17 \
222-
--hash=sha256:ca17555127f8ac59d51c2bf721eff83b46ae20a8626c033f186a4c4d7d6053f4
222+
--hash=sha256:ca17555127f8ac59d51c2bf721eff83b46ae20a8626c033f186a4c4d7d6053f4
223+
fastecdsa==1.7.1 \
224+
--hash=sha256:ca3b70122a95a310020758924f0772527bf4758b830460b87fa948d87ca2205d \
225+
--hash=sha256:018c5aed286ccc81d858593a08fb5600bead4183969f934fec05aaa2249f0c3f \
226+
--hash=sha256:3e493b03050484c4f48ba2a96933908baea49d64a12358e22fd9bff38c483bbd
227+

0 commit comments

Comments
 (0)