Ejemplo n.º 1
0
    def test_invalid_dates(self, mocker, settings):
        settings.CERTIFICATES_CHECK_VALIDITY = True
        settings.CERTIFICATES_EXPECTED_ROOT_HASH = None
        settings.CERTIFICATES_EXPECTED_SUBJECT_CN = None

        mock_requests = mocker.patch("normandy.recipes.signing.requests")
        mock_extract_certs_from_pem = mocker.patch(
            "normandy.recipes.signing.extract_certs_from_pem")
        mock_parse_cert_from_der = mocker.patch(
            "normandy.recipes.signing.parse_cert_from_der")

        url = "https://example.com/cert.pem"
        now = datetime.now().replace(tzinfo=pytz.UTC)
        not_before = now - timedelta(days=2)
        not_after = now - timedelta(days=1)

        mock_extract_certs_from_pem.return_value = ["a"]
        mock_parse_cert_from_der.return_value = self._fake_cert(
            not_before=not_before, not_after=not_after)

        with pytest.raises(signing.CertificateExpired):
            signing.verify_x5u(url)
        assert mock_requests.get.called_once_with(url)
        body = mock_requests.get.return_value.content.decode.return_value
        assert mock_extract_certs_from_pem.called_once_with(body)
        assert mock_parse_cert_from_der.called_once_with(
            mock_extract_certs_from_pem.return_value[0])
Ejemplo n.º 2
0
    def test_invalid_dates(self, mocker):
        mock_requests = mocker.patch("normandy.recipes.signing.requests")
        mock_parse_pem_to_certs = mocker.patch("normandy.recipes.signing.parse_pem_to_certs")

        date_format = "%y%m%d%H%M%SZ"
        url = "https://example.com/cert.pem"
        now = datetime.now()
        not_before = (now - timedelta(days=2)).strftime(date_format).encode()
        not_after = (now - timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [
            {
                "tbsCertificate": {
                    "validity": {
                        "notBefore": {"utcTime": not_before},
                        "notAfter": {"utcTime": not_after},
                    }
                }
            }
        ]

        with pytest.raises(signing.CertificateExpired):
            signing.verify_x5u(url)
        assert mock_requests.get.called_once_with(url)
        body = mock_requests.get.return_value.content.decode.return_value
        assert mock_parse_pem_to_certs.called_once_with(body)
Ejemplo n.º 3
0
    def test_invalid_dates(self, mocker):
        mock_requests = mocker.patch('normandy.recipes.signing.requests')
        mock_parse_pem_to_certs = mocker.patch(
            'normandy.recipes.signing.parse_pem_to_certs')

        date_format = '%y%m%d%H%M%SZ'
        url = 'https://example.com/cert.pem'
        now = datetime.now()
        not_before = (now - timedelta(days=2)).strftime(date_format).encode()
        not_after = (now - timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [{
            'tbsCertificate': {
                'validity': {
                    'notBefore': {
                        'utcTime': not_before
                    },
                    'notAfter': {
                        'utcTime': not_after
                    },
                }
            }
        }]

        with pytest.raises(signing.CertificateExpired):
            signing.verify_x5u(url)
        assert mock_requests.get.called_once_with(url)
        body = mock_requests.get.return_value.content.decode.return_value
        assert mock_parse_pem_to_certs.called_once_with(body)
Ejemplo n.º 4
0
    def test_bad_unexpected_format(self, mocker):
        mocker.patch("normandy.recipes.signing.requests")
        mock_parse_pem_to_certs = mocker.patch("normandy.recipes.signing.parse_pem_to_certs")

        date_format = "%Y%m%d%H%M%S"
        url = "https://example.com/cert.pem"
        now = datetime.now()
        not_before = (now - timedelta(days=2)).strftime(date_format).encode()
        not_after = (now - timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [
            {
                "tbsCertificate": {
                    "validity": {
                        "notBefore": {"unsupportedTimestamp": not_before},
                        "notAfter": {"unsupportedTimestamp": not_after},
                    }
                }
            }
        ]

        with pytest.raises(signing.BadCertificate) as exc:
            signing.verify_x5u(url)
        assert "Timestamp not in expected format" in str(exc)
        assert "unsupportedTimestamp" in str(exc)
Ejemplo n.º 5
0
def signatures_use_good_certificates(app_configs, **kwargs):
    errors = []
    expire_early = None
    if settings.CERTIFICATES_EXPIRE_EARLY_DAYS:
        expire_early = timedelta(days=settings.CERTIFICATES_EXPIRE_EARLY_DAYS)

    try:
        Signature = apps.get_model('recipes', 'Signature')
        Recipe = apps.get_model('recipes', 'Recipe')
        Action = apps.get_model('recipes', 'Action')
        signatures = list(Signature.objects.all())
    except (ProgrammingError, OperationalError, ImproperlyConfigured) as e:
        msg = f'Could not retrieve signatures: {e}'
        errors.append(Info(msg, id=INFO_COULD_NOT_RETRIEVE_SIGNATURES))
    else:
        urls = set(s.x5u for s in signatures)
        for url in urls:
            try:
                signing.verify_x5u(url, expire_early)
            except signing.BadCertificate as exc:
                matching_recipes = Recipe.objects.filter(signature__x5u=url)
                matching_actions = Action.objects.filter(signature__x5u=url)
                bad_objects = list(matching_recipes) + list(matching_actions)

                object_names = ', '.join(bad_objects)
                msg = (
                    f'{len(bad_objects)} objects are signed with a bad cert: {object_names}. '
                    f'{exc.detail}. Certificate url is {url}. ')
                errors.append(Warning(msg, id=WARNING_BAD_SIGNING_CERTIFICATE))

    return errors
Ejemplo n.º 6
0
    def test_bad_unexpected_format(self, mocker):
        mocker.patch("normandy.recipes.signing.requests")
        mock_parse_pem_to_certs = mocker.patch(
            "normandy.recipes.signing.parse_pem_to_certs")

        date_format = "%Y%m%d%H%M%S"
        url = "https://example.com/cert.pem"
        now = datetime.now()
        not_before = (now - timedelta(days=2)).strftime(date_format).encode()
        not_after = (now - timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [{
            "tbsCertificate": {
                "validity": {
                    "notBefore": {
                        "unsupportedTimestamp": not_before
                    },
                    "notAfter": {
                        "unsupportedTimestamp": not_after
                    },
                }
            }
        }]

        with pytest.raises(signing.BadCertificate) as exc:
            signing.verify_x5u(url)
        assert "Timestamp not in expected format" in str(exc)
        assert "unsupportedTimestamp" in str(exc)
Ejemplo n.º 7
0
    def test_bad_unexpected_format(self, mocker):
        mocker.patch('normandy.recipes.signing.requests')
        mock_parse_pem_to_certs = mocker.patch(
            'normandy.recipes.signing.parse_pem_to_certs')

        date_format = '%Y%m%d%H%M%S'
        url = 'https://example.com/cert.pem'
        now = datetime.now()
        not_before = (now - timedelta(days=2)).strftime(date_format).encode()
        not_after = (now - timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [{
            'tbsCertificate': {
                'validity': {
                    'notBefore': {
                        'unsupportedTimestamp': not_before
                    },
                    'notAfter': {
                        'unsupportedTimestamp': not_after
                    },
                }
            }
        }]

        with pytest.raises(signing.BadCertificate) as exc:
            signing.verify_x5u(url)
        assert 'Timestamp not in expected format' in str(exc)
        assert 'unsupportedTimestamp' in str(exc)
Ejemplo n.º 8
0
def recipe_signatures_use_good_certificates(app_configs, **kwargs):
    errors = []
    expire_early = None
    if settings.CERTIFICATES_EXPIRE_EARLY_DAYS:
        expire_early = timedelta(days=settings.CERTIFICATES_EXPIRE_EARLY_DAYS)

    try:
        Recipe = apps.get_model('recipes', 'Recipe')
        signed_recipes = list(Recipe.objects.exclude(signature=None))
    except (ProgrammingError, OperationalError, ImproperlyConfigured):
        errors.append(Info('Could not retrieve recipes', id=INFO_COULD_NOT_RETRIEVE_RECIPES))
    else:
        urls = set(r.signature.x5u for r in signed_recipes)
        for url in urls:
            try:
                signing.verify_x5u(url, expire_early)
            except signing.BadCertificate as exc:
                matching_recipes = Recipe.objects.filter(signature__x5u=url)
                count = matching_recipes.count()
                ids = ', '.join(str(r.id) for r in matching_recipes)
                msg = (f'{count} recipes (ids {ids}) are signed with a bad cert. {exc.detail}. '
                       f'Certificate url is {url}')
                errors.append(Warning(msg, id=WARNING_BAD_SIGNING_CERTIFICATE))

    return errors
    def test_it_checks_cert_subject(self, mocker, settings):
        path = os.path.join(os.path.dirname(__file__), "data", "test_certs.pem")
        with open(path) as f:
            cert_pem = f.read()

        settings.CERTIFICATES_CHECK_VALIDITY = False
        settings.CERTIFICATES_EXPECTED_ROOT_HASH = None
        settings.CERTIFICATES_EXPECTED_SUBJECT_CN = "wrong.subject.example.com"

        mock_requests = mocker.patch("normandy.recipes.signing.requests")
        mock_requests.get.return_value.content.decode.return_value = cert_pem

        with pytest.raises(signing.CertificateHasWrongSubject):
            signing.verify_x5u("https://example.com/cert.pem")
Ejemplo n.º 10
0
    def test_it_works(self, mocker, settings):
        settings.CERTIFICATES_CHECK_VALIDITY = True
        settings.CERTIFICATES_EXPECTED_ROOT_HASH = None
        settings.CERTIFICATES_EXPECTED_SUBJECT_CN = None

        mock_requests = mocker.patch("normandy.recipes.signing.requests")
        mock_extract_certs_from_pem = mocker.patch(
            "normandy.recipes.signing.extract_certs_from_pem")
        mock_parse_cert_from_der = mocker.patch(
            "normandy.recipes.signing.parse_cert_from_der")

        url = "https://example.com/cert.pem"
        now = datetime.now()
        not_before = now - timedelta(days=1)
        not_after = now + timedelta(days=1)

        mock_extract_certs_from_pem.return_value = ["a", "b"]
        mock_parse_cert_from_der.return_value = self._fake_cert(
            not_before=not_before, not_after=not_after)

        assert signing.verify_x5u(url)
        assert mock_requests.get.called_once_with(url)
        body = mock_requests.get.return_value.content.decode.return_value
        assert mock_extract_certs_from_pem.called_once_with(body)
        assert mock_parse_cert_from_der.called_twice()
Ejemplo n.º 11
0
 def test_mixed_timestamp_format(self, mocker):
     # The certificate used for testing expired on 2018-04-24. This test is
     # only concerned with the parsing of the dates, so mock the call to the
     # validate function and assert about the values of the dates.
     mock_requests = mocker.patch("normandy.recipes.signing.requests")
     mock_check_validity = mocker.patch(
         "normandy.recipes.signing.check_validity")
     path = os.path.join(os.path.dirname(__file__), "data",
                         "mixed_timestamps_certs.pem")
     with open(path, "rb") as f:
         mock_requests.get.return_value.content = f.read()
     assert signing.verify_x5u("https://example.com/cert.pem")
     assert mock_check_validity.mock_calls == [
         call(
             datetime(2017, 12, 25, tzinfo=pytz.UTC),
             datetime(2018, 4, 24, tzinfo=pytz.UTC),
             None,
         ),
         call(
             datetime(2017, 5, 4, 0, 12, 39, tzinfo=pytz.UTC),
             datetime(2019, 5, 4, 0, 12, 39, tzinfo=pytz.UTC),
             None,
         ),
         call(
             datetime(2015, 3, 17, 22, 53, 57, tzinfo=pytz.UTC),
             datetime(2025, 3, 14, 22, 53, 57, tzinfo=pytz.UTC),
             None,
         ),
     ]
Ejemplo n.º 12
0
 def test_mixed_timestamp_format(self, mocker):
     mock_requests = mocker.patch('normandy.recipes.signing.requests')
     path = os.path.join(os.path.dirname(__file__), 'data',
                         'mixed_timestamps_certs.pem')
     with open(path, 'rb') as f:
         mock_requests.get.return_value.content = f.read()
     assert signing.verify_x5u('https://example.com/cert.pem')
Ejemplo n.º 13
0
def signatures_use_good_certificates(app_configs, **kwargs):
    errors = []
    expire_early = None
    if settings.CERTIFICATES_EXPIRE_EARLY_DAYS:
        expire_early = timedelta(days=settings.CERTIFICATES_EXPIRE_EARLY_DAYS)

    try:
        Recipe = apps.get_model("recipes", "Recipe")
        Action = apps.get_model("recipes", "Action")

        urls = set()
        for recipe in Recipe.objects.exclude(signature=None):
            urls.add(recipe.signature.x5u)
        for action in Action.objects.exclude(signature=None):
            urls.add(action.signature.x5u)
    except (ProgrammingError, OperationalError, ImproperlyConfigured) as e:
        msg = f"Could not retrieve signatures: {e}"
        errors.append(Info(msg, id=INFO_COULD_NOT_RETRIEVE_SIGNATURES))
    else:

        def get_matching_object_names(url):
            matching_recipes = Recipe.objects.filter(signature__x5u=url)
            matching_actions = Action.objects.filter(signature__x5u=url)
            matching_objects = list(matching_recipes) + list(matching_actions)
            object_names = [str(o) for o in matching_objects]
            return object_names

        for url in urls:
            try:
                signing.verify_x5u(url, expire_early)
            except signing.BadCertificate as exc:
                bad_object_names = get_matching_object_names(url)
                msg = (
                    f"{len(bad_object_names)} objects are signed with a bad cert: "
                    f"{bad_object_names}. {exc.detail}. Certificate url is {url}. "
                )
                errors.append(Error(msg, id=ERROR_BAD_SIGNING_CERTIFICATE))
            except requests.RequestException as exc:
                bad_object_names = get_matching_object_names(url)
                msg = (
                    f"The certificate at {url} could not be fetched due to a network error to "
                    f"verify. {len(bad_object_names)} objects are signed with this certificate: "
                    f"{bad_object_names}. {exc}")
                errors.append(Error(msg,
                                    id=ERROR_COULD_NOT_VERIFY_CERTIFICATE))

    return errors
Ejemplo n.º 14
0
def test_recipe_signatures(conf, requests_session):
    r = requests_session.get(conf.getoption("server") + "/api/v1/recipe/signed/")
    r.raise_for_status()
    data = r.json()

    if len(data) == 0:
        pytest.skip("No signed recipes")

    cert_urls = set()

    for item in data:
        canonical_recipe = canonical_json(item["recipe"])
        signature = item["signature"]["signature"]
        pubkey = item["signature"]["public_key"]
        cert_urls.add(item["signature"]["x5u"])
        assert signing.verify_signature(canonical_recipe, signature, pubkey)

    for url in cert_urls:
        signing.verify_x5u(url)
Ejemplo n.º 15
0
def test_action_signatures(conf, requests_session):
    r = requests_session.get(conf.getoption("server") + "/api/v1/action/signed/")
    r.raise_for_status()
    data = r.json()

    if len(data) == 0:
        pytest.skip("No signed actions")

    cert_urls = set()

    for item in data:
        canonical_action = canonical_json(item["action"])
        signature = item["signature"]["signature"]
        pubkey = item["signature"]["public_key"]
        cert_urls.add(item["signature"]["x5u"])
        assert signing.verify_signature(canonical_action, signature, pubkey)

    for url in cert_urls:
        signing.verify_x5u(url)
Ejemplo n.º 16
0
def signatures_use_good_certificates(app_configs, **kwargs):
    errors = []
    expire_early = None
    if settings.CERTIFICATES_EXPIRE_EARLY_DAYS:
        expire_early = timedelta(days=settings.CERTIFICATES_EXPIRE_EARLY_DAYS)

    try:
        Recipe = apps.get_model("recipes", "Recipe")
        Action = apps.get_model("recipes", "Action")

        x5u_urls = defaultdict(list)
        for recipe in Recipe.objects.exclude(
                signature__x5u=None).select_related("signature"):
            x5u_urls[recipe.signature.x5u].append(str(recipe))
        for action in Action.objects.exclude(
                signature__x5u=None).select_related("signature"):
            x5u_urls[action.signature.x5u].append(str(action))
    except (ProgrammingError, OperationalError, ImproperlyConfigured) as e:
        msg = f"Could not retrieve signatures: {e}"
        errors.append(Info(msg, id=INFO_COULD_NOT_RETRIEVE_SIGNATURES))
    else:

        for url in x5u_urls:
            try:
                signing.verify_x5u(url, expire_early)
            except signing.BadCertificate as exc:
                bad_object_names = x5u_urls[url]
                msg = (
                    f"{len(bad_object_names)} objects are signed with a bad cert: "
                    f"{bad_object_names}. {exc.detail}. Certificate url is {url}. "
                )
                errors.append(Error(msg, id=ERROR_BAD_SIGNING_CERTIFICATE))
            except requests.RequestException as exc:
                bad_object_names = x5u_urls[url]
                msg = (
                    f"The certificate at {url} could not be fetched due to a network error to "
                    f"verify. {len(bad_object_names)} objects are signed with this certificate: "
                    f"{bad_object_names}. {exc}")
                errors.append(Error(msg,
                                    id=ERROR_COULD_NOT_VERIFY_CERTIFICATE))

    return errors
Ejemplo n.º 17
0
def test_recipe_signatures(conf, requests_session):
    r = requests_session.get(
        conf.getoption('server') + '/api/v1/recipe/signed/')
    r.raise_for_status()
    data = r.json()

    if len(data) == 0:
        pytest.skip('No signed recipes')

    cert_urls = set()

    for item in data:
        canonical_recipe = canonical_json(item['recipe'])
        signature = item['signature']['signature']
        pubkey = item['signature']['public_key']
        cert_urls.add(item['signature']['x5u'])
        assert signing.verify_signature(canonical_recipe, signature, pubkey)

    for url in cert_urls:
        signing.verify_x5u(url)
Ejemplo n.º 18
0
def signatures_use_good_certificates(app_configs, **kwargs):
    errors = []
    expire_early = None
    if settings.CERTIFICATES_EXPIRE_EARLY_DAYS:
        expire_early = timedelta(days=settings.CERTIFICATES_EXPIRE_EARLY_DAYS)

    try:
        Recipe = apps.get_model("recipes", "Recipe")
        Action = apps.get_model("recipes", "Action")

        x5u_urls = defaultdict(list)
        for recipe in Recipe.objects.exclude(signature__x5u=None).select_related("signature"):
            x5u_urls[recipe.signature.x5u].append(str(recipe))
        for action in Action.objects.exclude(signature__x5u=None).select_related("signature"):
            x5u_urls[action.signature.x5u].append(str(action))
    except (ProgrammingError, OperationalError, ImproperlyConfigured) as e:
        msg = f"Could not retrieve signatures: {e}"
        errors.append(Info(msg, id=INFO_COULD_NOT_RETRIEVE_SIGNATURES))
    else:

        for url in x5u_urls:
            try:
                signing.verify_x5u(url, expire_early)
            except signing.BadCertificate as exc:
                bad_object_names = x5u_urls[url]
                msg = (
                    f"{len(bad_object_names)} objects are signed with a bad cert: "
                    f"{bad_object_names}. {exc.detail}. Certificate url is {url}. "
                )
                errors.append(Error(msg, id=ERROR_BAD_SIGNING_CERTIFICATE))
            except requests.RequestException as exc:
                bad_object_names = x5u_urls[url]
                msg = (
                    f"The certificate at {url} could not be fetched due to a network error to "
                    f"verify. {len(bad_object_names)} objects are signed with this certificate: "
                    f"{bad_object_names}. {exc}"
                )
                errors.append(Error(msg, id=ERROR_COULD_NOT_VERIFY_CERTIFICATE))

    return errors
Ejemplo n.º 19
0
 def test_mixed_timestamp_format(self, mocker):
     # The certificate used for testing expired on 2018-04-24. This test is
     # only concerned with the parsing of the dates, so mock the call to the
     # validate function and assert about the value also s of the dates.
     mock_requests = mocker.patch("normandy.recipes.signing.requests")
     mock_check_validity = mocker.patch("normandy.recipes.signing.check_validity")
     path = os.path.join(os.path.dirname(__file__), "data", "mixed_timestamps_certs.pem")
     with open(path, "rb") as f:
         mock_requests.get.return_value.content = f.read()
     assert signing.verify_x5u("https://example.com/cert.pem")
     assert mock_check_validity.mock_calls == [
         call(datetime(2017, 12, 25), datetime(2018, 4, 24), None),
         call(datetime(2017, 5, 4, 0, 12, 39), datetime(2019, 5, 4, 0, 12, 39), None),
         call(datetime(2015, 3, 17, 22, 53, 57), datetime(2025, 3, 14, 22, 53, 57), None),
     ]
Ejemplo n.º 20
0
    def test_it_works(self, mocker):
        mock_requests = mocker.patch("normandy.recipes.signing.requests")
        mock_parse_pem_to_certs = mocker.patch(
            "normandy.recipes.signing.parse_pem_to_certs")

        date_format = "%y%m%d%H%M%SZ"
        url = "https://example.com/cert.pem"
        now = datetime.now()
        not_before = (now - timedelta(days=1)).strftime(date_format).encode()
        not_after = (now + timedelta(days=1)).strftime(date_format).encode()

        mock_parse_pem_to_certs.return_value = [
            {
                "tbsCertificate": {
                    "validity": {
                        "notBefore": {
                            "utcTime": not_before
                        },
                        "notAfter": {
                            "utcTime": not_after
                        },
                    }
                }
            },
            {
                "tbsCertificate": {
                    "validity": {
                        "notBefore": {
                            "utcTime": not_before
                        },
                        "notAfter": {
                            "utcTime": not_after
                        },
                    }
                }
            },
        ]

        assert signing.verify_x5u(url)
        assert mock_requests.get.called_once_with(url)
        body = mock_requests.get.return_value.content.decode.return_value
        assert mock_parse_pem_to_certs.called_once_with(body)