Skip to content
This repository has been archived by the owner on Mar 28, 2019. It is now read-only.

Commit

Permalink
Merge pull request #72 from mozilla-services/72-add-digicert-pinning
Browse files Browse the repository at this point in the history
Add certificate pinning for DigiCert root certificate.
  • Loading branch information
Natim committed Nov 17, 2015
2 parents 0251f2c + 1bc737f commit 6fabe6e
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 14 deletions.
22 changes: 22 additions & 0 deletions certificates/DigiCert.Global-Root-CA.crt
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions syncto/__init__.py
@@ -1,3 +1,4 @@
import os
import pkg_resources

import cliquet
Expand Down Expand Up @@ -26,6 +27,7 @@
'cache_credentials_ttl_seconds': 300,
'token_server_url': 'https://token.services.mozilla.com/',
'token_server_heartbeat_timeout_seconds': 5,
'certificate_ca_bundle': None,
}


Expand All @@ -41,9 +43,22 @@ def main(global_config, **settings):
settings = config.get_settings()
config.registry.heartbeats['sync'] = ping_sync_cluster

# For the use of a valid cache_hmac_secret key.
if settings['cache_hmac_secret'] is None:
error_msg = "Please configure the `syncto.cache_hmac_secret` settings."
raise ValueError(error_msg)

# Handle Certificate Pinning file.
ca_bundle_path = settings['certificate_ca_bundle']
if ca_bundle_path is not None:
ca_bundle_abspath = os.path.abspath(ca_bundle_path)
if not os.path.isfile(ca_bundle_abspath):
error_msg = "File %s cannot be found." % ca_bundle_abspath
raise ValueError(error_msg)

config.add_settings({
'certificate_ca_bundle': ca_bundle_abspath
})

config.scan("syncto.views")
return config.make_wsgi_app()
5 changes: 3 additions & 2 deletions syncto/authentication.py
Expand Up @@ -70,6 +70,7 @@ def build_sync_client(request):
hmac_secret = settings['cache_hmac_secret']
cache_key = 'credentials_%s' % utils.hmac_digest(hmac_secret,
bid_assertion)
ca_bundle = settings['certificate_ca_bundle']

encrypted_credentials = cache.get(cache_key)

Expand All @@ -79,7 +80,7 @@ def build_sync_client(request):
ttl = min(settings_ttl, bid_ttl or settings_ttl)

tokenserver = TokenserverClient(bid_assertion, client_state,
token_server_url)
token_server_url, verify=ca_bundle)
if statsd:
statsd.watch_execution_time(tokenserver, prefix="tokenserver")
credentials = tokenserver.get_hawk_credentials(duration=ttl)
Expand All @@ -93,7 +94,7 @@ def build_sync_client(request):
timer = statsd.timer("syncclient.start_time")
timer.start()

sync_client = SyncClient(**credentials)
sync_client = SyncClient(verify=ca_bundle, **credentials)

if statsd:
timer.stop()
Expand Down
38 changes: 30 additions & 8 deletions syncto/tests/test_authentication.py
Expand Up @@ -19,7 +19,9 @@ def setUp(self):
'project_docs': 'https://syncto.readthedocs.org/',
'cache_hmac_secret': 'This is not a secret',
'cache_credentials_ttl_seconds': 300,
'token_server_url': 'https://token.services.mozilla.com/'})
'token_server_url': 'https://token.services.mozilla.com/',
'certificate_ca_bundle': None,
})

self.request.registry.cache = Cache()

Expand Down Expand Up @@ -54,8 +56,8 @@ def test_should_client_state_in_url_is_used_to_setup_sync_client(self):
build_sync_client(request)
TSClient.assert_called_with(
'1234', '601c4497372419ee1789bf931f8c68f5',
'https://token.services.mozilla.com/')
SyncClient.assert_called_with(**self.credentials)
'https://token.services.mozilla.com/', verify=None)
SyncClient.assert_called_with(verify=None, **self.credentials)

def test_should_raise_if_client_state_is_url_is_wrong(self):
self.request.headers = {AUTHORIZATION_HEADER: 'Browserid 1234'}
Expand All @@ -71,8 +73,9 @@ def test_should_return_the_client_if_everything_is_fine(self):
with mock.patch('syncto.authentication.SyncClient') as SyncClient:
build_sync_client(self.request)
TSClient.assert_called_with(
'1234', '12345', 'https://token.services.mozilla.com/')
SyncClient.assert_called_with(**self.credentials)
'1234', '12345', 'https://token.services.mozilla.com/',
verify=None)
SyncClient.assert_called_with(verify=None, **self.credentials)

def test_should_return_an_alert_if_client_state_header_is_used(self):
self.request.headers = {AUTHORIZATION_HEADER: 'Browserid 1234',
Expand Down Expand Up @@ -100,13 +103,14 @@ def test_should_cache_credentials_the_second_time(self):
with mock.patch('syncto.authentication.SyncClient') as SyncClient:
# First call
build_sync_client(self.request)
SyncClient.assert_called_with(**self.credentials)
SyncClient.assert_called_with(verify=None, **self.credentials)
# Second time
build_sync_client(self.request)
# TokenServerClient should have been called only once.
TSClient.assert_called_once_with(
'1234', '12345', 'https://token.services.mozilla.com/')
SyncClient.assert_called_with(**self.credentials)
'1234', '12345', 'https://token.services.mozilla.com/',
verify=None)
SyncClient.assert_called_with(verify=None, **self.credentials)

def test_credentials_should_be_cached_encrypted(self):
self.request.headers = {AUTHORIZATION_HEADER: 'Browserid 1234',
Expand Down Expand Up @@ -240,3 +244,21 @@ def test_uses_ttl_from_assertion_if_smaller_than_settings(self):
args, _ = tuple(mocked_set.call_args_list[-1])
ttl = args[-1]
self.assertNotEqual(int(ttl), 3600)

def test_should_handle_the_certificate_ca_parameters(self):
digicert_ca_bundle = '../certificates/DigiCert.Global-Root-CA.crt'
self.request.registry.settings.update({
'certificate_ca_bundle': digicert_ca_bundle,
})
self.request.headers = {AUTHORIZATION_HEADER: 'Browserid 1234',
CLIENT_STATE_HEADER: '12345'}
with mock.patch('syncto.authentication.TokenserverClient') as TSClient:
TSClient.return_value.get_hawk_credentials.return_value = \
self.credentials
with mock.patch('syncto.authentication.SyncClient') as SyncClient:
build_sync_client(self.request)
TSClient.assert_called_once_with(
'1234', '12345', 'https://token.services.mozilla.com/',
verify=digicert_ca_bundle)
SyncClient.assert_called_once_with(verify=digicert_ca_bundle,
**self.credentials)
27 changes: 23 additions & 4 deletions syncto/tests/test_functional.py
@@ -1,4 +1,5 @@
import mock
import os
from contextlib import contextmanager
from uuid import uuid4

Expand All @@ -13,6 +14,11 @@

from .support import BaseWebTest, unittest

HERE = os.path.abspath(os.path.dirname(__file__))

MANDATORY_SETTINGS = {
'cache_hmac_secret': 'This is not a secret'
}

COLLECTION_URL = "/buckets/syncto/collections/tabs/records"
RECORD_URL = "/buckets/syncto/collections/tabs/records/%s" % uuid4()
Expand All @@ -28,16 +34,29 @@


class SettingsMissingTest(unittest.TestCase):
MANDATORY_SETTINGS = {
'cache_hmac_secret': 'This is not a secret'
}

def test_syncto_cache_hmac_secret_missing(self):
settings = self.MANDATORY_SETTINGS.copy()
settings = MANDATORY_SETTINGS.copy()
# Remove the mandatory setting we want to test
del settings['cache_hmac_secret']
self.assertRaises(ValueError, testapp, {}, **settings)

def test_syncto_certificate_ca_bundle_file_not_found(self):
settings = MANDATORY_SETTINGS.copy()
# Remove the mandatory setting we want to test
settings['certificate_ca_bundle'] = 'foobar.crt'
self.assertRaises(ValueError, testapp, {}, **settings)

def test_syncto_certificate_ca_bundle_relative_file_found(self):
settings = MANDATORY_SETTINGS.copy()
# Remove the mandatory setting we want to test
digicert_ca_bundle = 'certificates/DigiCert.Global-Root-CA.crt'
settings['certificate_ca_bundle'] = digicert_ca_bundle
app = testapp({}, **settings)
self.assertEqual(app.registry.settings['certificate_ca_bundle'],
os.path.normpath(os.path.join(
HERE, '..', '..', digicert_ca_bundle)))


class KintoJsCompatTest(BaseWebTest, unittest.TestCase):

Expand Down

0 comments on commit 6fabe6e

Please sign in to comment.