async def test_verify_name_constraints_raises(aiohttp_session, mock_with_x5u,
                                              cache, now_fixed):
    certs = [
        cryptography.x509.load_pem_x509_certificate(pem,
                                                    backend=default_backend())
        for pem in STAGE_CERT_LIST
    ]
    # Intermediate cert has the name constraint.
    intermediate = certs[1]
    # Change name of leaf cert.
    mock_leaf = mock_cert(certs[0])
    fake_name = mock.Mock()
    fake_name.value = "bazinga.allizom.org"
    mock_leaf.subject = mock.Mock()
    mock_leaf.subject.get_attributes_for_oid.return_value = [fake_name]
    certs[0] = mock_leaf

    with mock.patch(
            "cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
        load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
        s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
        with pytest.raises(
                autograph_utils.CertificateChainNameNotPermitted) as excinfo:
            await s.verify_x5u(FAKE_CERT_URL)

    assert " does not match the permitted names " in excinfo.value.detail
    assert excinfo.value.current == intermediate
    assert excinfo.value.next == mock_leaf
async def test_verify_name_constraints_excludes(aiohttp_session, mock_with_x5u,
                                                cache, now_fixed):
    certs = [
        cryptography.x509.load_pem_x509_certificate(pem,
                                                    backend=default_backend())
        for pem in STAGE_CERT_LIST
    ]
    # Intermediate cert has the name constraint.
    real_intermediate = certs[1]
    real_constraints = real_intermediate.extensions.get_extension_for_class(
        cryptography.x509.NameConstraints).value

    # Reverse meaning of constraints.
    reversed = mock.Mock()
    reversed.permitted_subtrees = real_constraints.excluded_subtrees
    reversed.excluded_subtrees = real_constraints.permitted_subtrees

    intermediate = mock_cert(real_intermediate)
    mock_cert_extension(intermediate, cryptography.x509.NameConstraints,
                        reversed)
    certs[1] = intermediate

    leaf = certs[0]

    with mock.patch(
            "cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
        load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
        s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
        with pytest.raises(
                autograph_utils.CertificateChainNameExcluded) as excinfo:
            await s.verify_x5u(FAKE_CERT_URL)

    assert " matches the excluded names " in excinfo.value.detail
    assert excinfo.value.current == intermediate
    assert excinfo.value.next == leaf
async def test_verify_basic_constraints_must_have_cert_signing(
        aiohttp_session, mock_with_x5u, cache, now_fixed):
    certs = [
        cryptography.x509.load_pem_x509_certificate(pem,
                                                    backend=default_backend())
        for pem in STAGE_CERT_LIST
    ]
    real_intermediate = certs[1]
    intermediate = mock_cert(real_intermediate)
    uses_mock = mock.Mock()
    uses_mock.key_cert_sign = False
    mock_cert_extension(intermediate, cryptography.x509.KeyUsage, uses_mock)
    certs[1] = intermediate

    with mock.patch(
            "cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
        load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
        s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
        with pytest.raises(autograph_utils.CertificateCannotSign) as excinfo:
            await s.verify_x5u(FAKE_CERT_URL)

    assert excinfo.value.detail.startswith(
        "Certificate cannot be used for signing because ")
    assert excinfo.value.cert == intermediate
    assert excinfo.value.extra == "key usage is incomplete"
async def test_verify_leaf_code_signing(aiohttp_session, mock_with_x5u, cache,
                                        now_fixed):
    certs = [
        cryptography.x509.load_pem_x509_certificate(pem,
                                                    backend=default_backend())
        for pem in CERT_LIST
    ]

    # Change extended_key_usage for leaf cert
    real_leaf = certs[0]
    mock_leaf = mock_cert(real_leaf)
    fake_uses = [
        cryptography.x509.oid.ExtendedKeyUsageOID.CODE_SIGNING,
        cryptography.x509.oid.ExtendedKeyUsageOID.TIME_STAMPING,
    ]
    mock_cert_extension(mock_leaf, cryptography.x509.ExtendedKeyUsage,
                        fake_uses)
    certs[0] = mock_leaf

    with mock.patch(
            "cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
        load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
        s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
        with pytest.raises(
                autograph_utils.CertificateLeafHasWrongKeyUsage) as excinfo:
            await s.verify_x5u(FAKE_CERT_URL)

    assert excinfo.value.detail.startswith(
        f"Leaf certificate {mock_leaf!r} should have extended key usage of just "
        "Code Signing. ")
    assert excinfo.value.cert == mock_leaf
    assert excinfo.value.key_usage == fake_uses
示例#5
0
async def run(server: str, collection: str, root_hash: str) -> CheckResult:
    """Fetch recipes from Remote Settings and verify that each attached signature
    is verified with the related recipe attributes.

    :param server: URL of Remote Settings server.
    :param collection: Collection id to obtain recipes from (eg. ``"normandy-recipes"``.
    :param root_hash: The expected hash for the first certificate in a chain.
    """
    resp = await fetch_json(
        RECIPES_URL.format(server=server, collection=collection))
    recipes = resp["data"]

    cache = MemoryCache()

    errors = {}

    async with ClientSession() as session:
        verifier = SignatureVerifier(session, cache,
                                     decode_mozilla_hash(root_hash))

        for recipe in recipes:
            try:
                await validate_signature(verifier, recipe)
            except Exception as e:
                errors[recipe["id"]] = repr(e)

    return len(errors) == 0, errors
async def test_verify_x5u_expired(aiohttp_session, mock_with_x5u, cache,
                                  now_fixed):
    now_fixed.return_value = datetime.datetime(2022, 10, 23, 16, 16, 16)
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    with pytest.raises(autograph_utils.CertificateExpired) as excinfo:
        await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

    assert excinfo.value.detail == "Certificate expired on 2021-07-05 21:57:15"
示例#7
0
async def verify_signature(records, timestamp, signature):
    x5u = signature["x5u"].replace("file:///tmp/autograph/",
                                   "http://certchains/")
    serialized = canonical_json(records, timestamp).encode("utf-8")

    async with aiohttp.ClientSession() as session:
        verifier = SignatureVerifier(session, MemoryCache(), FakeRootHash())
        await verifier.verify(serialized, signature["signature"], x5u)
async def test_verify_x5u_too_soon(aiohttp_session, mock_with_x5u, cache,
                                   now_fixed):
    now_fixed.return_value = datetime.datetime(2010, 10, 23, 16, 16, 16)
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    with pytest.raises(autograph_utils.CertificateNotYetValid) as excinfo:
        await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

    assert excinfo.value.detail == "Certificate is not valid until 2016-07-06 21:57:15"
async def test_verify_x5u_name_exact_match(aiohttp_session, mock_with_x5u,
                                           cache, now_fixed):
    s = SignatureVerifier(
        aiohttp_session,
        cache,
        DEV_ROOT_HASH,
        subject_name_check=ExactMatch(
            "normandy.content-signature.mozilla.org"),
    )
    await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)
async def test_verify_x5u_caches_success(aiohttp_session, mock_with_x5u, cache,
                                         now_fixed):
    with mock.patch.object(cache, "set") as set_mock:
        s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
        await s.verify_x5u(FAKE_CERT_URL)

        assert len(set_mock.call_args_list) == 1
        args, kwargs = set_mock.call_args_list[0]
        assert args[0] == FAKE_CERT_URL
        assert isinstance(args[1], cryptography.x509.Certificate)
        assert kwargs == {}
async def run(server: str, buckets: List[str], root_hash: str) -> CheckResult:
    client = KintoClient(server_url=server)
    entries = [
        entry for entry in await client.get_monitor_changes()
        if entry["bucket"] in buckets
    ]

    # Fetch collections records in parallel.
    futures = [
        client.get_changeset(entry["bucket"],
                             entry["collection"],
                             _expected=entry["last_modified"])
        for entry in entries
    ]
    start_time = time.time()
    results = await run_parallel(*futures)
    elapsed_time = time.time() - start_time
    logger.info(f"Downloaded all data in {elapsed_time:.2f}s")

    cache = MemoryCache()

    async with ClientSession() as session:
        verifier = SignatureVerifier(session, cache,
                                     decode_mozilla_hash(root_hash))

        # Validate signatures sequentially.
        errors = {}
        for i, (entry, changeset) in enumerate(zip(entries, results)):
            cid = "{bucket}/{collection}".format(**entry)
            message = "{:02d}/{:02d} {}: ".format(i + 1, len(entries), cid)
            try:
                start_time = time.time()
                await validate_signature(
                    verifier,
                    changeset["metadata"],
                    changeset["changes"],
                    changeset["timestamp"],
                )
                elapsed_time = time.time() - start_time

                message += f"OK ({elapsed_time:.2f}s)"
                logger.info(message)

            except (BadSignature, BadCertificate) as e:
                message += "⚠ Signature Error ⚠ " + repr(e)
                logger.error(message)
                errors[cid] = repr(e)

    return len(errors) == 0, errors
async def test_verify_broken_chain(aiohttp_session, mock_aioresponses, cache,
                                   now_fixed):
    # Drop next-to-last cert in cert list
    broken_chain = CERT_LIST[:1] + CERT_LIST[2:]
    mock_aioresponses.get(FAKE_CERT_URL,
                          status=200,
                          body=b"\n".join(broken_chain))
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    with pytest.raises(autograph_utils.CertificateChainBroken) as excinfo:
        await s.verify_x5u(FAKE_CERT_URL)

    assert excinfo.value.detail.startswith(
        "Certificate chain is not continuous. ")
    assert excinfo.value.previous_cert == cryptography.x509.load_pem_x509_certificate(
        CERT_LIST[2], backend=default_backend())
    assert excinfo.value.next_cert == cryptography.x509.load_pem_x509_certificate(
        CERT_LIST[0], backend=default_backend())
async def test_verify_x5u_screwy_dates(aiohttp_session, mock_with_x5u, cache,
                                       now_fixed):
    now_fixed.return_value = datetime.datetime(2010, 10, 23, 16, 16, 16)
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    leaf_cert = cryptography.x509.load_pem_x509_certificate(
        CERT_LIST[0], backend=default_backend())
    bad_cert = mock.Mock(spec=leaf_cert)
    bad_cert.not_valid_before = leaf_cert.not_valid_after
    bad_cert.not_valid_after = leaf_cert.not_valid_before
    with mock.patch("autograph_utils.x509.load_pem_x509_certificate") as x509:
        x509.return_value = bad_cert
        with pytest.raises(autograph_utils.BadCertificate) as excinfo:
            await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

    assert excinfo.value.detail == (
        "Bad certificate: not_before (2021-07-05 21:57:15) "
        "after not_after (2016-07-06 21:57:15)")
async def test_verify_x5u_name_exact_doesnt_match(aiohttp_session,
                                                  mock_with_x5u, cache,
                                                  now_fixed):
    s = SignatureVerifier(
        aiohttp_session,
        cache,
        DEV_ROOT_HASH,
        subject_name_check=ExactMatch(
            "remote-settings.content-signature.mozilla.org"),
    )
    with pytest.raises(autograph_utils.CertificateHasWrongSubject) as excinfo:
        await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)

    assert excinfo.value.detail == (
        "Certificate does not have the expected subject. "
        "Got 'normandy.content-signature.mozilla.org', "
        "checking for matches exactly 'remote-settings.content-signature.mozilla.org'"
    )
async def test_verify_wrong_root_hash(aiohttp_session, mock_with_x5u, cache,
                                      now_fixed):
    wrong_root_hash = DEV_ROOT_HASH[:-1] + b"\x03"
    s = SignatureVerifier(
        aiohttp_session,
        cache,
        wrong_root_hash,
        subject_name_check=ExactMatch(
            "remote-settings.content-signature.mozilla.org"),
    )
    with pytest.raises(autograph_utils.CertificateHasWrongRoot) as excinfo:
        await s.verify_x5u(FAKE_CERT_URL)

    actual = "4c35b1c3e312d955e778edd0a7e78a388304ef01bffa0329b2469f3cc5ec3604"
    expected = actual[:-1] + "3"

    assert excinfo.value.detail == (
        "Certificate is not based on expected root hash. "
        f"Got '{actual}' expected '{expected}'")
async def test_unknown_key(aiohttp_session, mock_with_x5u, cache, now_fixed):
    certs = [
        cryptography.x509.load_pem_x509_certificate(pem,
                                                    backend=default_backend())
        for pem in CERT_LIST
    ]

    # Change public_key for an intermediate cert
    real_intermediate = certs[1]
    mock_intermediate = mock_cert(real_intermediate)
    mock_intermediate.public_key = mock.Mock()
    certs[1] = mock_intermediate

    with mock.patch(
            "cryptography.x509.load_pem_x509_certificate") as load_cert_mock:
        load_cert_mock.side_effect = lambda *args, **kwargs: certs.pop(0)
        s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
        with pytest.raises(
                autograph_utils.CertificateUnsupportedKeyType) as excinfo:
            await s.verify_x5u(FAKE_CERT_URL)

    assert excinfo.value.cert == mock_intermediate
    assert excinfo.value.key == mock_intermediate.public_key()
async def test_verify_x5u(aiohttp_session, mock_with_x5u, cache, now_fixed):
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    await s.verify_x5u(FAKE_CERT_URL)
async def test_verify_x5u_returns_cache(aiohttp_session, mock_with_x5u, cache,
                                        now_fixed):
    with mock.patch.object(cache, "get") as get_mock:
        s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
        res = await s.verify_x5u(FAKE_CERT_URL)
        assert res == get_mock.return_value
async def test_verify_signature(aiohttp_session, mock_with_x5u, cache,
                                now_fixed):
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE, FAKE_CERT_URL)
async def test_verify_stage_cert_chain(aiohttp_session, mock_aioresponses,
                                       cache, now_fixed):
    mock_aioresponses.get(FAKE_CERT_URL, status=200, body=STAGE_CERT_CHAIN)
    s = SignatureVerifier(aiohttp_session, cache, STAGE_ROOT_HASH)
    await s.verify_x5u(FAKE_CERT_URL)
async def test_verify_signature_bad_numbers(aiohttp_session, mock_with_x5u,
                                            cache, now_fixed):
    s = SignatureVerifier(aiohttp_session, cache, DEV_ROOT_HASH)
    with pytest.raises(autograph_utils.WrongSignatureSize):
        await s.verify(SIGNED_DATA, SAMPLE_SIGNATURE[:-4], FAKE_CERT_URL)