Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signing: Store bare signature from autograph, not content-signature #264

Merged
merged 2 commits into from Sep 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions normandy/recipes/models.py
Expand Up @@ -19,7 +19,7 @@
from normandy.recipes.validators import validate_json


logger = logging.getLogger()
logger = logging.getLogger(__name__)


class Approval(models.Model):
Expand All @@ -42,6 +42,13 @@ def update_signatures(self):
autographer = Autographer()
# Convert to a list because order must be preserved
recipes = list(self)

recipe_ids = [r.id for r in recipes]
logger.info(
'Requesting signatures for recipes with ids [%s] from Autograph',
(recipe_ids, ),
extra={'recipe_ids': recipe_ids})

canonical_jsons = [r.canonical_json() for r in recipes]
signatures_data = autographer.sign_data(canonical_jsons)

Expand Down Expand Up @@ -156,7 +163,11 @@ def canonical_json(self):

def update_signature(self):
autographer = Autographer()
# Convert to a list because order must be preserved

logger.info(
'Requesting signatures for recipes with ids [%s] from Autograph',
self.id,
extra={'recipe_ids': [self.id]})

signature_data = autographer.sign_data([self.canonical_json()])[0]
signature = Signature(**signature_data)
Expand Down
44 changes: 42 additions & 2 deletions normandy/recipes/tests/test_utils.py
Expand Up @@ -7,6 +7,7 @@

import pytest

from normandy.base.tests import Whatever
from normandy.recipes.utils import Autographer, fraction_to_key, verify_signature


Expand Down Expand Up @@ -83,19 +84,58 @@ def test_it_checks_settings(self, settings):
# assert doesn't raise
Autographer()

def test_it_makes_good_requests(self, settings):
def test_it_interacts_with_autograph_correctly(self, settings):
settings.AUTOGRAPH_URL = 'https://autograph.example.com'
settings.AUTOGRAPH_HAWK_ID = 'hawk id'
settings.AUTOGRAPH_HAWK_SECRET_KEY = 'hawk secret key'

autographer = Autographer()
autographer.session = MagicMock()

autographer.session.post.return_value.json.return_value = [
{
'content-signature': (
'x5u="https://example.com/fake_x5u_1";p384ecdsa=fake_signature_1'
),
'x5u': 'https://example.com/fake_x5u_1',
'hash_algorithm': 'sha384',
'ref': 'fake_ref_1',
'signature': 'fake_signature_1',
'public_key': 'fake_pubkey_1',
},
{
'content-signature': (
'x5u="https://example.com/fake_x5u_2";p384ecdsa=fake_signature_2'
),
'x5u': 'https://example.com/fake_x5u_2',
'hash_algorithm': 'sha384',
'ref': 'fake_ref_2',
'signature': 'fake_signature_2',
'public_key': 'fake_pubkey_2',
}
]

url = self.test_settings['URL'] + 'sign/data'
foo_base64 = base64.b64encode(b'foo').decode('utf8')
bar_base64 = base64.b64encode(b'bar').decode('utf8')

autographer.sign_data([b'foo', b'bar'])
# Assert the correct data is returned
assert autographer.sign_data([b'foo', b'bar']) == [
{
'timestamp': Whatever(),
'signature': 'fake_signature_1',
'x5u': 'https://example.com/fake_x5u_1',
'public_key': 'fake_pubkey_1',
},
{
'timestamp': Whatever(),
'signature': 'fake_signature_2',
'x5u': 'https://example.com/fake_x5u_2',
'public_key': 'fake_pubkey_2',
}
]

# Assert the correct request was made
assert autographer.session.post.called_once_with(
[url, [
{'template': 'content-signature', 'input': foo_base64},
Expand Down
9 changes: 8 additions & 1 deletion normandy/recipes/utils.py
@@ -1,5 +1,6 @@
import base64
import hashlib
import logging

import ecdsa
import requests
Expand All @@ -11,6 +12,9 @@
from django.utils.functional import cached_property


logger = logging.getLogger(__name__)


def fraction_to_key(frac):
"""Map from the range [0, 1] to [0, max(sha256)]. The result is a string."""
# SHA 256 hashes are 64-digit hexadecimal numbers. The largest possible SHA 256
Expand Down Expand Up @@ -107,11 +111,14 @@ def sign_data(self, content_list):
res.raise_for_status()
signing_responses = res.json()

logger.info('Got %s signatures from Autograph', len(signing_responses))

signatures = []
for res in signing_responses:

signatures.append({
'timestamp': ts,
'signature': res['content-signature'],
'signature': res['signature'],
'x5u': res.get('x5u'),
'public_key': res['public_key'],
})
Expand Down