class LogVerifierRsaTest(LogVerifierTest, unittest.TestCase):
    sth_fixture = client_pb2.SthResponse()
    sth_fixture.tree_size = 1130
    sth_fixture.timestamp = 1442500998291
    sth_fixture.sha256_root_hash = (
        "58f4e84d26f179829da3359a23f2ec519f83e99d9230aad6bfb37e2faa82c663"
        ).decode("hex")

    sth_fixture.tree_head_signature = (
        "040101002595c278829d558feb560c5024048ce1ca9e5329cc79b074307f0b6168dda1"
        "5b27f84c94cce39f8371aa8205d73a7101b434b6aeaf3c852b8471daa05d654463b334"
        "5103c7406dbd4642c8cc89eababa84e9ad663ffb3cc87940c3689d0c2ac6246915f221"
        "5da254981206fed8505eed268bcc94e05cd83c8e8e5a14407a6d15c8071fabaed9728a"
        "02830c6aef95969b0576c7ae09d50bdfc8b0b58fa759458c6d62383d6fe1072c0da103"
        "1baddfa363b58ca78f93f329b1f1a15b9575988974dcba2421b9a1bb2a617d8b3f4046"
        "ead6095f8496075edc686ae4fa672d4974de0fb9326dc3c628f7e44c7675d2c56d1c66"
        "32bbb9e4a69e0a7e34bd1d6dc7b4b2").decode("hex")

    key_info_fixture = client_pb2.KeyInfo()
    key_info_fixture.type = client_pb2.KeyInfo.RSA
    key_info_fixture.pem_key = (
        "-----BEGIN PUBLIC KEY-----\n"
        "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAolpIHxdSlTXLo1s6H1OC\n"
        "dpSj/4DyHDc8wLG9wVmLqy1lk9fz4ATVmm+/1iN2Nk8jmctUKK2MFUtlWXZBSpym\n"
        "97M7frGlSaQXUWyA3CqQUEuIJOmlEjKTBEiQAvpfDjCHjlV2Be4qTM6jamkJbiWt\n"
        "gnYPhJL6ONaGTiSPm7Byy57iaz/hbckldSOIoRhYBiMzeNoA0DiRZ9KmfSeXZ1rB\n"
        "8y8X5urSW+iBzf2SaOfzBvDpcoTuAaWx2DPazoOl28fP1hZ+kHUYvxbcMjttjauC\n"
        "Fx+JII0dmuZNIwjfeG/GBb9frpSX219k1O4Wi6OEbHEr8at/XQ0y7gTikOxBn/s5\n"
        "wQIDAQAB\n"
        "-----END PUBLIC KEY-----\n")
示例#2
0
    def _compute_projected_sth(self, extra_leaves):
        """Compute a partial projected STH.

        Useful for when an intermediate STH is not directly available from the
        server, but you still want to do something with the root hash.

        Args:
            extra_leaves: Extra leaves present in the tree for the new STH, in
                the same order as in that tree.

        Returns:
            (partial_sth, new_tree)
            partial_sth: A partial STH with timestamp 0 and empty signature.
            new_tree: New CompactMerkleTree with the extra_leaves integrated.
        """
        partial_sth = client_pb2.SthResponse()
        old_size = self.__verified_tree.tree_size
        partial_sth.tree_size = old_size + len(extra_leaves)
        # we only want to check the hash, so just use a dummy timestamp
        # that looks valid so the temporal verifier doesn't complain
        partial_sth.timestamp = 0
        extra_raw_leaves = [leaf.leaf_input for leaf in extra_leaves]
        new_tree = self.__verified_tree.extended(extra_raw_leaves)
        partial_sth.sha256_root_hash = new_tree.root_hash()
        return partial_sth, new_tree
示例#3
0
 def __encode_sth(self, audited_sth):
     timestamp = audited_sth.sth.timestamp
     sth = client_pb2.SthResponse()
     sth.CopyFrom(audited_sth.sth)
     sth.ClearField("timestamp")
     audit = client_pb2.AuditInfo()
     audit.CopyFrom(audited_sth.audit)
     return (timestamp, sqlite3.Binary(sth.SerializeToString()),
             sqlite3.Binary(audit.SerializeToString()))
示例#4
0
 def _compute_projected_sth_from_tree(self, tree, extra_leaves):
     partial_sth = client_pb2.SthResponse()
     old_size = tree.tree_size
     partial_sth.tree_size = old_size + len(extra_leaves)
     # we only want to check the hash, so just use a dummy timestamp
     # that looks valid so the temporal verifier doesn't complain
     partial_sth.timestamp = 0
     extra_raw_leaves = [leaf.leaf_input for leaf in extra_leaves]
     new_tree = tree.extended(extra_raw_leaves)
     partial_sth.sha256_root_hash = new_tree.root_hash()
     return partial_sth, new_tree
示例#5
0
def dummy_compute_projected_sth(old_sth):
    sth = client_pb2.SthResponse()
    sth.timestamp = old_sth.timestamp
    sth.tree_size = size = old_sth.tree_size
    tree = merkle.CompactMerkleTree(merkle.TreeHasher(), size,
                                    ["a"] * merkle.count_bits_set(size))
    f = mock.Mock(return_value=(sth, tree))
    f.dummy_sth = sth
    f.dummy_tree = tree
    old_sth.sha256_root_hash = tree.root_hash()
    return f
    def GetSTH(self):
        sth_str = self.GetOne('sth')
        sth = client_pb2.SthResponse()
        parts = str(sth_str).split('.')
        sth.tree_size = int(parts[0])
        sth.timestamp = int(parts[1])
        sth.sha256_root_hash = base64.b64decode(parts[2])
        sth.tree_head_signature = base64.b64decode(parts[3])

        self.verifier.verify_sth(sth)

        return sth
示例#7
0
    def test_verify_sth_temporal_consistency_reversed_timestamps(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.tree_size = old_sth.tree_size + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info, None)

        self.assertRaises(ValueError, verifier.verify_sth_temporal_consistency,
                          new_sth, old_sth)
示例#8
0
    def test_verify_sth_temporal_consistency(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info, None)

        # Note we do not care about root hash inconsistency here.
        self.assertTrue(
            verifier.verify_sth_temporal_consistency(old_sth, new_sth))
示例#9
0
    def test_verify_sth_temporal_consistency_newer_tree_is_smaller(self):
        old_sth = self.sth_fixture
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.tree_size = old_sth.tree_size - 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(self.key_info_fixture, None)

        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency, old_sth,
                          new_sth)
    def test_update_sth_fails_for_stale_sth(self):
        sth = client_pb2.SthResponse()
        sth.CopyFrom(self._DEFAULT_STH)
        sth.tree_size -= 1
        sth.timestamp -= 1
        client = FakeLogClient(sth)

        m = self.create_monitor(client)
        self.assertFalse(m._update_sth())

        # Check that we kept the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)
示例#11
0
def _parse_sth(sth_body):
    """Parse a serialized STH JSON response."""
    sth_response = client_pb2.SthResponse()
    try:
        sth = json.loads(sth_body)
        sth_response.timestamp = sth["timestamp"]
        sth_response.tree_size = sth["tree_size"]
        sth_response.sha256_root_hash = base64.b64decode(sth[
            "sha256_root_hash"])
        sth_response.tree_head_signature = base64.b64decode(sth[
            "tree_head_signature"])
        # TypeError for base64 decoding, TypeError/ValueError for invalid
        # JSON field types, KeyError for missing JSON fields.
    except (TypeError, ValueError, KeyError) as e:
        raise InvalidResponseError("Invalid STH %s\n%s" % (sth_body, e))
    return sth_response
    def test_verify_sth_consistency_invalid_proof(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.sha256_root_hash = "a new hash"
        proof = ["some proof the mock does not care about"]

        mock_merkle_verifier = mock.Mock()
        mock_merkle_verifier.verify_tree_consistency.side_effect = (
            error.ConsistencyError("Evil"))

        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      mock_merkle_verifier)
        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_consistency,
                          old_sth, new_sth, proof)
    def test_verify_sth_fails_for_bad_signature(self):
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info)
        default_sth = LogVerifierTest.default_sth

        for i in range(len(default_sth.tree_head_signature)):
            # Skip the bytes that encode ASN.1 lengths: this is covered in a
            # separate test
            if i == 5 or i == 7 or i == 42:
                continue
            sth = client_pb2.SthResponse()
            sth.CopyFrom(default_sth)
            sth.tree_head_signature = (
                default_sth.tree_head_signature[:i] +
                chr(ord(default_sth.tree_head_signature[i]) ^ 1) +
                default_sth.tree_head_signature[i+1:])
            # Encoding- or SignatureError, depending on whether the modified
            # byte is a content byte or not.
            self.assertRaises((error.EncodingError, error.SignatureError),
                              verifier.verify_sth, sth)
    def test_verify_sth_consistency(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.sha256_root_hash = "a new hash"
        proof = ["some proof the mock does not care about"]

        mock_merkle_verifier = mock.Mock()
        mock_merkle_verifier.verify_tree_consistency.return_value = True

        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      mock_merkle_verifier)
        self.assertTrue(verifier.verify_sth_consistency(old_sth, new_sth,
                                                        proof))
        mock_merkle_verifier.verify_tree_consistency.assert_called_once_with(
            old_sth.tree_size, new_sth.tree_size, old_sth.sha256_root_hash,
            new_sth.sha256_root_hash, proof)
示例#15
0
    def test_verify_sth_temporal_consistency_equal_timestamps(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info, None)

        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency, old_sth,
                          new_sth)

        new_sth.tree_size = old_sth.tree_size - 1
        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency, old_sth,
                          new_sth)

        # But identical STHs are OK
        self.assertTrue(
            verifier.verify_sth_temporal_consistency(old_sth, old_sth))
示例#16
0
    def test_update_sth_fails_for_stale_sth(self):
        sth = client_pb2.SthResponse()
        sth.CopyFrom(self._DEFAULT_STH)
        sth.tree_size -= 1
        sth.timestamp -= 1
        client = FakeLogClient(sth)

        m = self.create_monitor(client)
        d = defer.Deferred()
        d.callback(True)
        m._verify_consistency = mock.Mock(return_value=d)

        def check_state(result):
            self.assertTrue(m._verify_consistency.called)
            args, _ = m._verify_consistency.call_args
            self.assertTrue(args[0].timestamp < args[1].timestamp)

            # Check that we kept the state.
            self.verify_state(self._DEFAULT_STATE)

        return m._update_sth().addCallback(
            self.assertFalse).addCallback(check_state)
import copy

from ct.client import log_client
from ct.crypto import merkle
from ct.proto import client_pb2

DEFAULT_STH = client_pb2.SthResponse()
DEFAULT_STH.timestamp = 1234
DEFAULT_STH.tree_size = 1000
DEFAULT_STH.sha256_root_hash = "hash\x00"
DEFAULT_STH.tree_head_signature = "sig\xff"
DEFAULT_FAKE_PROOF = [(_c * 32) for _c in "abc"]
DEFAULT_FAKE_ROOTS = [("cert-%d" % _i) for _i in range(4)]
DEFAULT_URI = "https://example.com"


class FakeHandlerBase(log_client.RequestHandler):
    """A fake request handler for generating responses locally."""
    def __init__(self, uri, entry_limit=0, tree_size=0):
        self._uri = uri
        self._entry_limit = entry_limit

        self._sth = copy.deepcopy(DEFAULT_STH)
        # Override with custom size
        if tree_size > 0:
            self._sth.tree_size = tree_size

    @classmethod
    def make_response(cls, code, reason, json_content=None):
        """Generate a response of the desired format."""
        raise NotImplementedError
    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info)
        default_sth = LogVerifierTest.default_sth

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence, however the ecdsa module silently slices it.
        # TODO(ekasper): contribute a patch to upstream and make the tests fail
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Decreasing this length corrupts the integer, however
        # increased length is silently sliced, as above.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(default_sth.tree_head_signature[3]) + 1) +
            default_sth.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
class LogVerifierTest(unittest.TestCase):
    default_sth = client_pb2.SthResponse()
    default_sth.tree_size = 42
    default_sth.timestamp = 1348589667204
    default_sth.sha256_root_hash = (
        "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23"
        ).decode("hex")

    default_sth.tree_head_signature = (
        "040300483046022100befd8060563763a5e49ba53e6443c13f7624fd6403178113736e"
        "16012aca983e022100f572568dbfe9a86490eb915c4ee16ad5ecd708fed35ed4e5cd1b"
        "2c3f087b4130").decode("hex")

    default_key_info = client_pb2.KeyInfo()
    default_key_info.type = client_pb2.KeyInfo.ECDSA
    default_key_info.pem_key = (
        "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES0AfBk"
        "jr7b8b19p5Gk8plSAN16wW\nXZyhYsH6FMCEUK60t7pem/ckoPX8hupuaiJzJS0ZQ0SEoJ"
        "GlFxkUFwft5g==\n-----END PUBLIC KEY-----\n")

    def test_verify_sth(self):
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info)
        self.assertTrue(verifier.verify_sth(LogVerifierTest.default_sth))

    def test_verify_sth_fails_for_bad_signature(self):
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info)
        default_sth = LogVerifierTest.default_sth

        for i in range(len(default_sth.tree_head_signature)):
            # Skip the bytes that encode ASN.1 lengths: this is covered in a
            # separate test
            if i == 5 or i == 7 or i == 42:
                continue
            sth = client_pb2.SthResponse()
            sth.CopyFrom(default_sth)
            sth.tree_head_signature = (
                default_sth.tree_head_signature[:i] +
                chr(ord(default_sth.tree_head_signature[i]) ^ 1) +
                default_sth.tree_head_signature[i+1:])
            # Encoding- or SignatureError, depending on whether the modified
            # byte is a content byte or not.
            self.assertRaises((error.EncodingError, error.SignatureError),
                              verifier.verify_sth, sth)

    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info)
        default_sth = LogVerifierTest.default_sth

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence, however the ecdsa module silently slices it.
        # TODO(ekasper): contribute a patch to upstream and make the tests fail
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Decreasing this length corrupts the integer, however
        # increased length is silently sliced, as above.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) - 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:i] +
            chr(ord(default_sth.tree_head_signature[i]) + 1) +
            default_sth.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(default_sth)
        sth.tree_head_signature = (
            default_sth.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(default_sth.tree_head_signature[3]) + 1) +
            default_sth.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

    def test_verify_sth_consistency(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.sha256_root_hash = "a new hash"
        proof = ["some proof the mock does not care about"]

        mock_merkle_verifier = mock.Mock()
        mock_merkle_verifier.verify_tree_consistency.return_value = True

        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      mock_merkle_verifier)
        self.assertTrue(verifier.verify_sth_consistency(old_sth, new_sth,
                                                        proof))
        mock_merkle_verifier.verify_tree_consistency.assert_called_once_with(
            old_sth.tree_size, new_sth.tree_size, old_sth.sha256_root_hash,
            new_sth.sha256_root_hash, proof)

    def test_verify_sth_temporal_consistency(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      None)

        # Note we do not care about root hash inconsistency here.
        self.assertTrue(verifier.verify_sth_temporal_consistency(
            old_sth, new_sth))

    def test_verify_sth_temporal_consistency_equal_timestamps(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      None)

        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency,
                          old_sth, new_sth)

        new_sth.tree_size = old_sth.tree_size - 1
        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency,
                          old_sth, new_sth)

        # But identical STHs are OK
        self.assertTrue(verifier.verify_sth_temporal_consistency(
            old_sth, old_sth))

    def test_verify_sth_temporal_consistency_reversed_timestamps(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.tree_size = old_sth.tree_size + 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      None)

        self.assertRaises(ValueError,
                          verifier.verify_sth_temporal_consistency,
                          new_sth, old_sth)

    def test_verify_sth_temporal_consistency_newer_tree_is_smaller(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.tree_size = old_sth.tree_size - 1

        # Merkle verifier is never used so simply set to None
        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      None)

        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_temporal_consistency,
                          old_sth, new_sth)

    def test_verify_sth_consistency_invalid_proof(self):
        old_sth = LogVerifierTest.default_sth
        new_sth = client_pb2.SthResponse()
        new_sth.CopyFrom(old_sth)
        new_sth.tree_size = old_sth.tree_size + 1
        new_sth.timestamp = old_sth.timestamp + 1
        new_sth.sha256_root_hash = "a new hash"
        proof = ["some proof the mock does not care about"]

        mock_merkle_verifier = mock.Mock()
        mock_merkle_verifier.verify_tree_consistency.side_effect = (
            error.ConsistencyError("Evil"))

        verifier = verify.LogVerifier(LogVerifierTest.default_key_info,
                                      mock_merkle_verifier)
        self.assertRaises(error.ConsistencyError,
                          verifier.verify_sth_consistency,
                          old_sth, new_sth, proof)

    def _test_verify_sct(self, proof, chain, fake_timestamp = None):
        sct = client_pb2.SignedCertificateTimestamp()
        tls_message.decode(read_testdata_file(proof), sct)
        if fake_timestamp is not None:
            sct.timestamp = fake_timestamp

        chain = map(lambda name: cert.Certificate.from_pem_file(
                        os.path.join(FLAGS.testdata_dir, name)), chain)

        key_info = client_pb2.KeyInfo()
        key_info.type = client_pb2.KeyInfo.ECDSA
        key_info.pem_key = read_testdata_file('ct-server-key-public.pem')

        verifier = verify.LogVerifier(key_info)
        return verifier.verify_sct(sct, chain)

    def _test_verify_embedded_scts(self, chain):
        chain = map(lambda name: cert.Certificate.from_pem_file(
                        os.path.join(FLAGS.testdata_dir, name)), chain)

        key_info = client_pb2.KeyInfo()
        key_info.type = client_pb2.KeyInfo.ECDSA
        key_info.pem_key = read_testdata_file('ct-server-key-public.pem')

        verifier = verify.LogVerifier(key_info)
        return verifier.verify_embedded_scts(chain)

    def test_verify_sct_valid_signature(self):
        self.assertTrue(self._test_verify_sct(
                                'test-cert.proof',
                                ['test-cert.pem', 'ca-cert.pem']))

    def test_verify_sct_invalid_signature(self):
        self.assertRaises(error.SignatureError,
                          self._test_verify_sct,
                          'test-cert.proof',
                          ['test-cert.pem', 'ca-cert.pem'],
                          fake_timestamp = 1234567)

    def test_verify_sct_precertificate_valid_signature(self):
        self.assertTrue(self._test_verify_sct(
                                'test-embedded-pre-cert.proof',
                                ['test-embedded-pre-cert.pem', 'ca-cert.pem']))

    def test_verify_sct_precertificate_invalid_signature(self):
        self.assertRaises(error.SignatureError,
                          self._test_verify_sct,
                          'test-embedded-pre-cert.proof',
                          ['test-embedded-pre-cert.pem', 'ca-cert.pem'],
                          fake_timestamp = 1234567)

    def test_verify_sct_precertificate_with_preca_valid_signature(self):
        self.assertTrue(self._test_verify_sct(
                                'test-embedded-with-preca-pre-cert.proof',
                                ['test-embedded-with-preca-pre-cert.pem',
                                 'ca-pre-cert.pem', 'ca-cert.pem']))

    def test_verify_sct_missing_leaf_cert(self):
        self.assertRaises(error.IncompleteChainError,
                          self._test_verify_sct,
                          'test-cert.proof',
                          [])

    def test_verify_sct_missing_issuer_cert(self):
        self.assertRaises(error.IncompleteChainError,
                          self._test_verify_sct,
                          'test-embedded-pre-cert.proof',
                          ['test-embedded-pre-cert.pem'])

    def test_verify_sct_with_preca_missing_issuer_cert(self):
        self.assertRaises(error.IncompleteChainError,
                          self._test_verify_sct,
                          'test-embedded-with-preca-pre-cert.proof',
                          ['test-embedded-with-preca-pre-cert.pem',
                           'ca-pre-cert.pem'])

    def test_verify_embedded_scts_valid_signature(self):
        sct = client_pb2.SignedCertificateTimestamp()
        tls_message.decode(read_testdata_file('test-embedded-pre-cert.proof'),
                           sct)

        result = self._test_verify_embedded_scts(
                    ['test-embedded-cert.pem', 'ca-cert.pem'])
        self.assertEqual(result, [(sct, True)])

    def test_verify_embedded_scts_invalid_signature(self):
        result = self._test_verify_embedded_scts(
                    ['test-invalid-embedded-cert.pem', 'ca-cert.pem'])
        self.assertFalse(result[0][1])

    def test_verify_embedded_scts_with_preca_valid_signature(self):
        sct = client_pb2.SignedCertificateTimestamp()
        tls_message.decode(
                read_testdata_file('test-embedded-with-preca-pre-cert.proof'),
                sct)

        result = self._test_verify_embedded_scts(
                    ['test-embedded-with-preca-cert.pem', 'ca-cert.pem'])
        self.assertEqual(result, [(sct, True)])
class MonitorTest(unittest.TestCase):
    _DEFAULT_STH = client_pb2.SthResponse()
    _DEFAULT_STH.timestamp = 2000
    _DEFAULT_STH.tree_size = 10
    _DEFAULT_STH.tree_head_signature = "sig"
    _DEFAULT_STH_compute_projected = dummy_compute_projected_sth(_DEFAULT_STH)

    _NEW_STH = client_pb2.SthResponse()
    _NEW_STH.timestamp = 3000
    _NEW_STH.tree_size = _DEFAULT_STH.tree_size + 10
    _NEW_STH.tree_head_signature = "sig2"
    _NEW_STH_compute_projected = dummy_compute_projected_sth(_NEW_STH)

    def setUp(self):
        if not FLAGS.verbose_tests:
            logging.disable(logging.CRITICAL)
        self.db = sqlite_log_db.SQLiteLogDB(
            sqlitecon.SQLiteConnectionManager(":memory:", keepalive=True))
        self.temp_db = sqlite_temp_db.SQLiteTempDB(
            sqlitecon.SQLiteConnectionManager(":memory:", keepalive=True))

        default_state = client_pb2.MonitorState()
        default_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.state_keeper = InMemoryStateKeeper(default_state)
        self.verifier = mock.Mock()
        self.hasher = merkle.TreeHasher()

        # Make sure the DB knows about the default log server.
        log = client_pb2.CtLogMetadata()
        log.log_server = "log_server"
        self.db.add_log(log)

    def verify_state(self, expected_state):
        self.assertEqual(self.state_keeper.state,
                         expected_state,
                         msg="%s== vs ==\n%s" %
                         (self.state_keeper.state, expected_state))

    def verify_tmp_data(self, start, end):
        # TODO: we are no longer using the temp db
        # all the callsites should be updated to test the main db instead
        pass

    def create_monitor(self, client):
        return monitor.Monitor(client, self.verifier, self.hasher, self.db,
                               self.temp_db, self.state_keeper)

    def test_update(self):
        client = FakeLogClient(self._NEW_STH)

        m = self.create_monitor(client)
        m._compute_projected_sth = self._NEW_STH_compute_projected
        self.assertTrue(m.update())

        # Check that we wrote the state...
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._NEW_STH)
        m._compute_projected_sth.dummy_tree.save(expected_state.verified_tree)
        self.verify_state(expected_state)

        self.verify_tmp_data(self._DEFAULT_STH.tree_size,
                             self._NEW_STH.tree_size - 1)

    def test_first_update(self):
        client = FakeLogClient(self._DEFAULT_STH)

        self.state_keeper.state = None
        m = self.create_monitor(client)
        m._compute_projected_sth = self._DEFAULT_STH_compute_projected
        self.assertTrue(m.update())

        # Check that we wrote the state...
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        m._compute_projected_sth.dummy_tree.save(expected_state.verified_tree)
        self.verify_state(expected_state)

        self.verify_tmp_data(0, self._DEFAULT_STH.tree_size - 1)

    def test_update_no_new_entries(self):
        client = FakeLogClient(self._DEFAULT_STH)

        self.temp_db.store_entries = mock.Mock()

        m = self.create_monitor(client)
        self.assertTrue(m.update())

        # Check that we kept the state...
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)

        # ...and wrote no entries.
        self.assertFalse(self.temp_db.store_entries.called)

    def test_update_call_sequence(self):
        # Test that update calls update_sth and update_entries in sequence,
        # and bails on first error, so we can test each of them separately.
        client = FakeLogClient(self._DEFAULT_STH)

        m = self.create_monitor(client)
        m._update_sth = mock.Mock(return_value=True)
        m._update_entries = mock.Mock(return_value=True)
        self.assertTrue(m.update())
        m._update_sth.assert_called_once_with()
        m._update_entries.assert_called_once_with()

        m._update_sth.reset_mock()
        m._update_entries.reset_mock()
        m._update_sth.return_value = False
        self.assertFalse(m.update())
        m._update_sth.assert_called_once_with()
        self.assertFalse(m._update_entries.called)

        m._update_sth.reset_mock()
        m._update_entries.reset_mock()
        m._update_sth.return_value = True
        m._update_entries.return_value = False
        self.assertFalse(m.update())
        m._update_sth.assert_called_once_with()
        m._update_entries.assert_called_once_with()

    def test_update_sth(self):
        client = FakeLogClient(self._NEW_STH)

        m = self.create_monitor(client)
        self.assertTrue(m._update_sth())

        # Check that we updated the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        expected_state.pending_sth.CopyFrom(self._NEW_STH)
        merkle.CompactMerkleTree().save(expected_state.verified_tree)
        self.verify_state(expected_state)

    def test_update_sth_fails_for_invalid_sth(self):
        client = FakeLogClient(self._NEW_STH)
        self.verifier.verify_sth.side_effect = error.VerifyError("Boom!")

        m = self.create_monitor(client)
        self.assertFalse(m._update_sth())

        # Check that we kept the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)

    def test_update_sth_fails_for_stale_sth(self):
        sth = client_pb2.SthResponse()
        sth.CopyFrom(self._DEFAULT_STH)
        sth.tree_size -= 1
        sth.timestamp -= 1
        client = FakeLogClient(sth)

        m = self.create_monitor(client)
        self.assertFalse(m._update_sth())

        # Check that we kept the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)

    def test_update_sth_fails_for_inconsistent_sth(self):
        client = FakeLogClient(self._NEW_STH)
        # The STH is in fact OK but fake failure.
        self.verifier.verify_sth_consistency.side_effect = (
            error.ConsistencyError("Boom!"))

        m = self.create_monitor(client)
        self.assertFalse(m._update_sth())

        # Check that we kept the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)

    def test_update_sth_fails_on_client_error(self):
        client = FakeLogClient(self._NEW_STH)
        client.get_sth = mock.Mock(side_effect=log_client.HTTPError("Boom!"))

        m = self.create_monitor(client)
        self.assertFalse(m._update_sth())

        # Check that we kept the state.
        expected_state = client_pb2.MonitorState()
        expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.verify_state(expected_state)

    def test_update_entries_fails_on_client_error(self):
        client = FakeLogClient(self._NEW_STH)
        client.get_entries = mock.MagicMock()
        client.get_entries.next.side_effect = log_client.HTTPError("Boom!")
        self.temp_db.store_entries = mock.Mock()

        m = self.create_monitor(client)
        # Get the new STH first.
        self.assertTrue(m._update_sth())
        self.assertFalse(m._update_entries())

        # Check that we wrote no entries.
        self.assertFalse(self.temp_db.store_entries.called)

    def test_update_entries_fails_not_enough_entries(self):
        client = FakeLogClient(self._NEW_STH)
        client.get_entries = mock.MagicMock()
        entry = client_pb2.EntryResponse()
        entry.leaf_input = "leaf"
        entry.extra_data = "extra"
        client.get_entries.return_value = iter([entry])

        m = self.create_monitor(client)
        m._compute_projected_sth = self._NEW_STH_compute_projected
        # Get the new STH first.
        self.assertTrue(m._update_sth())

        self.assertFalse(m._update_entries())
示例#21
0
class MonitorTest(unittest.TestCase):
    _DEFAULT_STH = client_pb2.SthResponse()
    _DEFAULT_STH.timestamp = 2000
    _DEFAULT_STH.tree_size = 10
    _DEFAULT_STH.tree_head_signature = "sig"
    _DEFAULT_STH_compute_projected = dummy_compute_projected_sth(_DEFAULT_STH)

    _NEW_STH = client_pb2.SthResponse()
    _NEW_STH.timestamp = 3000
    _NEW_STH.tree_size = _DEFAULT_STH.tree_size + 10
    _NEW_STH.tree_head_signature = "sig2"
    _NEW_STH_compute_projected = dummy_compute_projected_sth(_NEW_STH)

    def setUp(self):
        if not FLAGS.verbose_tests:
            logging.disable(logging.CRITICAL)
        self.db = sqlite_log_db.SQLiteLogDB(
            sqlitecon.SQLiteConnectionManager(":memory:", keepalive=True))
        self.temp_db = sqlite_temp_db.SQLiteTempDB(
            sqlitecon.SQLiteConnectionManager(":memory:", keepalive=True))
        # We can't simply use DB in memory with keepalive True, because different
        # thread is writing to the database which results in an sqlite exception.
        self.cert_db = mock.MagicMock()

        default_state = client_pb2.MonitorState()
        default_state.verified_sth.CopyFrom(self._DEFAULT_STH)
        self.state_keeper = InMemoryStateKeeper(default_state)
        self.verifier = mock.Mock()
        self.hasher = merkle.TreeHasher()

        # Make sure the DB knows about the default log server.
        log = client_pb2.CtLogMetadata()
        log.log_server = "log_server"
        self.db.add_log(log)

    def verify_state(self, expected_state):
        self.assertEqual(self.state_keeper.state,
                         expected_state,
                         msg="%s== vs ==\n%s" %
                         (self.state_keeper.state, expected_state))

    def verify_tmp_data(self, start, end):
        # TODO: we are no longer using the temp db
        # all the callsites should be updated to test the main db instead
        pass

    def create_monitor(self, client, skip_scan_entry=True):
        m = monitor.Monitor(client, self.verifier, self.hasher, self.db,
                            self.cert_db, 7, self.state_keeper)
        if m:
            m._scan_entries = mock.Mock()
        return m

    def check_db_state_after_successful_updates(self, number_of_updates):
        audited_sths = list(self.db.scan_latest_sth_range("log_server"))
        for index, audited_sth in enumerate(audited_sths):
            if index % 2 != 0:
                self.assertEqual(client_pb2.UNVERIFIED,
                                 audited_sth.audit.status)
            else:
                self.assertEqual(client_pb2.VERIFIED, audited_sth.audit.status)
        self.assertEqual(len(audited_sths), number_of_updates * 2)

    def test_update(self):
        client = FakeLogClient(self._NEW_STH)

        m = self.create_monitor(client)
        m._compute_projected_sth_from_tree = self._NEW_STH_compute_projected

        def check_state(result):
            # Check that we wrote the state...
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._NEW_STH)
            m._compute_projected_sth_from_tree.dummy_tree.save(
                expected_state.verified_tree)
            m._compute_projected_sth_from_tree.dummy_tree.save(
                expected_state.unverified_tree)
            self.verify_state(expected_state)

            self.verify_tmp_data(self._DEFAULT_STH.tree_size,
                                 self._NEW_STH.tree_size - 1)
            self.check_db_state_after_successful_updates(1)
            for audited_sth in list(
                    self.db.scan_latest_sth_range("log_server")):
                self.assertEqual(self._NEW_STH, audited_sth.sth)

        return m.update().addCallback(self.assertTrue).addCallback(check_state)

    def test_first_update(self):
        client = FakeLogClient(self._DEFAULT_STH)

        self.state_keeper.state = None
        m = self.create_monitor(client)
        m._compute_projected_sth_from_tree = self._DEFAULT_STH_compute_projected

        def check_state(result):
            # Check that we wrote the state...
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            m._compute_projected_sth_from_tree.dummy_tree.save(
                expected_state.verified_tree)
            m._compute_projected_sth_from_tree.dummy_tree.save(
                expected_state.unverified_tree)
            self.verify_state(expected_state)

            self.verify_tmp_data(0, self._DEFAULT_STH.tree_size - 1)
            self.check_db_state_after_successful_updates(1)
            for audited_sth in list(
                    self.db.scan_latest_sth_range("log_server")):
                self.assertEqual(self._DEFAULT_STH, audited_sth.sth)

        d = m.update().addCallback(self.assertTrue).addCallback(check_state)
        return d

    def test_update_no_new_entries(self):
        client = FakeLogClient(self._DEFAULT_STH)

        self.temp_db.store_entries = mock.Mock()

        m = self.create_monitor(client)
        d = m.update()
        d.addCallback(self.assertTrue)

        def check_state(result):
            # Check that we kept the state...
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            self.verify_state(expected_state)

            # ...and wrote no entries.
            self.assertFalse(self.temp_db.store_entries.called)
            self.check_db_state_after_successful_updates(0)

        d.addCallback(check_state)
        return d

    def test_update_call_sequence(self):
        # Test that update calls update_sth and update_entries in sequence,
        # and bails on first error, so we can test each of them separately.
        # Each of these functions checks if functions were properly called
        # and runs step in sequence of updates.
        def check_calls_sth_fails(result):
            m._update_sth.assert_called_once_with()
            m._update_entries.assert_called_once_with()

            m._update_sth.reset_mock()
            m._update_entries.reset_mock()
            m._update_sth.return_value = copy.deepcopy(d_false)
            return m.update().addCallback(self.assertFalse)

        def check_calls_entries_fail(result):
            m._update_sth.assert_called_once_with()
            self.assertFalse(m._update_entries.called)

            m._update_sth.reset_mock()
            m._update_entries.reset_mock()
            m._update_sth.return_value = copy.deepcopy(d_true)
            m._update_entries.return_value = copy.deepcopy(d_false)
            return m.update().addCallback(self.assertFalse)

        def check_calls_assert_last_calls(result):
            m._update_sth.assert_called_once_with()
            m._update_entries.assert_called_once_with()

        client = FakeLogClient(self._DEFAULT_STH)

        m = self.create_monitor(client)
        d_true = defer.Deferred()
        d_true.callback(True)
        d_false = defer.Deferred()
        d_false.callback(False)
        #check regular correct update
        m._update_sth = mock.Mock(return_value=copy.deepcopy(d_true))
        m._update_entries = mock.Mock(return_value=copy.deepcopy(d_true))
        d = m.update().addCallback(self.assertTrue)
        d.addCallback(check_calls_sth_fails)
        d.addCallback(check_calls_entries_fail)
        d.addCallback(check_calls_assert_last_calls)
        return d

    def test_update_sth(self):
        client = FakeLogClient(self._NEW_STH)

        m = self.create_monitor(client)

        def check_state(result):
            # Check that we updated the state.
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            expected_state.pending_sth.CopyFrom(self._NEW_STH)
            merkle.CompactMerkleTree().save(expected_state.verified_tree)
            merkle.CompactMerkleTree().save(expected_state.unverified_tree)
            self.verify_state(expected_state)
            audited_sths = list(self.db.scan_latest_sth_range("log_server"))
            self.assertEqual(audited_sths[0].audit.status, client_pb2.VERIFIED)
            self.assertEqual(audited_sths[1].audit.status,
                             client_pb2.UNVERIFIED)
            self.assertEqual(len(audited_sths), 2)

        return m._update_sth().addCallback(
            self.assertTrue).addCallback(check_state)

    def test_update_sth_fails_for_invalid_sth(self):
        client = FakeLogClient(self._NEW_STH)
        self.verifier.verify_sth.side_effect = error.VerifyError("Boom!")

        m = self.create_monitor(client)

        def check_state(result):
            # Check that we kept the state.
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            self.verify_state(expected_state)
            self.check_db_state_after_successful_updates(0)

        return m._update_sth().addCallback(
            self.assertFalse).addCallback(check_state)

    def test_update_sth_fails_for_stale_sth(self):
        sth = client_pb2.SthResponse()
        sth.CopyFrom(self._DEFAULT_STH)
        sth.tree_size -= 1
        sth.timestamp -= 1
        client = FakeLogClient(sth)

        m = self.create_monitor(client)
        d = defer.Deferred()
        d.callback(True)
        m._verify_consistency = mock.Mock(return_value=d)

        def check_state(result):
            self.assertTrue(m._verify_consistency.called)
            args, _ = m._verify_consistency.call_args
            self.assertTrue(args[0].timestamp < args[1].timestamp)

            # Check that we kept the state.
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            self.verify_state(expected_state)

        return m._update_sth().addCallback(
            self.assertFalse).addCallback(check_state)

    def test_update_sth_fails_for_inconsistent_sth(self):
        client = FakeLogClient(self._NEW_STH)
        # The STH is in fact OK but fake failure.
        self.verifier.verify_sth_consistency.side_effect = (
            error.ConsistencyError("Boom!"))

        m = self.create_monitor(client)

        def check_state(result):
            # Check that we kept the state.
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            self.verify_state(expected_state)
            audited_sths = list(self.db.scan_latest_sth_range("log_server"))
            self.assertEqual(len(audited_sths), 2)
            self.assertEqual(audited_sths[0].audit.status,
                             client_pb2.VERIFY_ERROR)
            self.assertEqual(audited_sths[1].audit.status,
                             client_pb2.UNVERIFIED)
            for audited_sth in audited_sths:
                self.assertEqual(self._DEFAULT_STH.sha256_root_hash,
                                 audited_sth.sth.sha256_root_hash)

        return m._update_sth().addCallback(
            self.assertFalse).addCallback(check_state)

    def test_update_sth_fails_on_client_error(self):
        client = FakeLogClient(self._NEW_STH)

        def get_sth():
            return defer.maybeDeferred(
                mock.Mock(side_effect=log_client.HTTPError("Boom!")))

        client.get_sth = get_sth
        m = self.create_monitor(client)

        def check_state(result):
            # Check that we kept the state.
            expected_state = client_pb2.MonitorState()
            expected_state.verified_sth.CopyFrom(self._DEFAULT_STH)
            self.verify_state(expected_state)
            self.check_db_state_after_successful_updates(0)

        return m._update_sth().addCallback(
            self.assertFalse).addCallback(check_state)

    def test_update_entries_fails_on_client_error(self):
        client = FakeLogClient(self._NEW_STH,
                               get_entries_throw=log_client.HTTPError("Boom!"))
        client.get_entries = mock.Mock(
            return_value=client.get_entries(0, self._NEW_STH.tree_size - 2))
        self.temp_db.store_entries = mock.Mock()

        m = self.create_monitor(client)

        # Get the new STH first.
        d = m._update_sth().addCallback(self.assertTrue)
        d.addCallback(
            lambda x: m._update_entries().addCallback(self.assertFalse))

        # Check that we wrote no entries.
        d.addCallback(
            lambda x: self.assertFalse(self.temp_db.store_entries.called))
        return d

    def test_update_entries_fails_not_enough_entries(self):
        client = FakeLogClient(self._NEW_STH)
        faker_fake_entry_producer = FakeEntryProducer(0,
                                                      self._NEW_STH.tree_size)
        faker_fake_entry_producer.change_range_after_start(0, 5)
        client.get_entries = mock.Mock(return_value=faker_fake_entry_producer)

        m = self.create_monitor(client)
        m._compute_projected_sth = self._NEW_STH_compute_projected
        # Get the new STH first.
        return m._update_sth().addCallback(self.assertTrue).addCallback(
            lambda x: m._update_entries().addCallback(self.assertFalse))

    def test_update_entries_fails_in_the_middle(self):
        client = FakeLogClient(self._NEW_STH)
        faker_fake_entry_producer = FakeEntryProducer(0,
                                                      self._NEW_STH.tree_size)
        faker_fake_entry_producer.change_range_after_start(0, 5)
        client.get_entries = mock.Mock(return_value=faker_fake_entry_producer)

        m = self.create_monitor(client)
        m._compute_projected_sth = self._NEW_STH_compute_projected
        fake_fetch = mock.MagicMock()

        def try_again_with_all_entries(_):
            m._fetch_entries = fake_fetch
            return m._update_entries()

        # Get the new STH first.
        return m._update_sth().addCallback(self.assertTrue).addCallback(
            lambda _: m._update_entries().addCallback(self.assertFalse)
        ).addCallback(try_again_with_all_entries).addCallback(
            lambda _: fake_fetch.assert_called_once_with(5, 19))
class LogVerifierEcdsaTest(LogVerifierTest, unittest.TestCase):
    sth_fixture = client_pb2.SthResponse()
    sth_fixture.tree_size = 42
    sth_fixture.timestamp = 1348589667204
    sth_fixture.sha256_root_hash = (
        "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23"
        ).decode("hex")

    sth_fixture.tree_head_signature = (
        "040300483046022100befd8060563763a5e49ba53e6443c13f7624fd6403178113736e"
        "16012aca983e022100f572568dbfe9a86490eb915c4ee16ad5ecd708fed35ed4e5cd1b"
        "2c3f087b4130").decode("hex")

    key_info_fixture = client_pb2.KeyInfo()
    key_info_fixture.type = client_pb2.KeyInfo.ECDSA
    key_info_fixture.pem_key = (
        "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES0AfBk"
        "jr7b8b19p5Gk8plSAN16wW\nXZyhYsH6FMCEUK60t7pem/ckoPX8hupuaiJzJS0ZQ0SEoJ"
        "GlFxkUFwft5g==\n-----END PUBLIC KEY-----\n")

    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(self.key_info_fixture)
        sth_fixture = self.sth_fixture

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence, however the ecdsa module silently slices it.
        # TODO(ekasper): contribute a patch to upstream and make the tests fail
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Decreasing this length corrupts the integer, however
        # increased length is silently sliced, as above.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertTrue(verifier.verify_sth(sth))

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(sth_fixture.tree_head_signature[3]) + 1) +
            sth_fixture.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
示例#23
0
    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(self.key_info_fixture)
        sth_fixture = self.sth_fixture

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence, however the ecdsa module silently slices it.
        # Our ECDSA verifier checks for it and will fail.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(
            error.EncodingError,
            verifier.verify_sth,
            sth)

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Increasing this length leaves bytes unread which
        # is now also detected in the verify_ecdsa module.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(sth_fixture.tree_head_signature[3]) + 1) +
            sth_fixture.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(self.key_info_fixture)
        sth_fixture = self.sth_fixture

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Modifying the length corrupts the integer and causes
        # a decoding error.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(sth_fixture.tree_head_signature[3]) + 1) +
            sth_fixture.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
class LogVerifierEcdsaTest(LogVerifierTest, unittest.TestCase):
    sth_fixture = client_pb2.SthResponse()
    sth_fixture.tree_size = 42
    sth_fixture.timestamp = 1348589667204
    sth_fixture.sha256_root_hash = (
        "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23"
        ).decode("hex")

    sth_fixture.tree_head_signature = (
        "040300483046022100befd8060563763a5e49ba53e6443c13f7624fd6403178113736e"
        "16012aca983e022100f572568dbfe9a86490eb915c4ee16ad5ecd708fed35ed4e5cd1b"
        "2c3f087b4130").decode("hex")

    key_info_fixture = client_pb2.KeyInfo()
    key_info_fixture.type = client_pb2.KeyInfo.ECDSA
    key_info_fixture.pem_key = (
        "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES0AfBk"
        "jr7b8b19p5Gk8plSAN16wW\nXZyhYsH6FMCEUK60t7pem/ckoPX8hupuaiJzJS0ZQ0SEoJ"
        "GlFxkUFwft5g==\n-----END PUBLIC KEY-----\n")

    def test_verify_sth_for_bad_asn1_length(self):
        verifier = verify.LogVerifier(self.key_info_fixture)
        sth_fixture = self.sth_fixture

        # The byte that encodes the length of the ASN.1 signature sequence
        i = 5

        # Decreasing the length truncates the sequence and causes a decoding
        # error.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # Increasing the length means there are not enough ASN.1 bytes left to
        # decode the sequence.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # The byte that encodes the length of the first integer r in the
        # sequence (r, s). Modifying the length corrupts the second integer
        # offset and causes a decoding error.
        i = 7
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # The byte that encodes the length of the second integer s in the
        # sequence (r, s). Modifying the length corrupts the integer and causes
        # a decoding error.
        i = 42
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:i] +
            chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
            sth_fixture.tree_head_signature[i+1:])
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

        # Trailing garbage is correctly detected.
        sth = client_pb2.SthResponse()
        sth.CopyFrom(sth_fixture)
        sth.tree_head_signature = (
            sth_fixture.tree_head_signature[:3] +
            # Correct outer length to include trailing garbage.
            chr(ord(sth_fixture.tree_head_signature[3]) + 1) +
            sth_fixture.tree_head_signature[4:]) + "\x01"
        self.assertRaises(error.SignatureError, verifier.verify_sth, sth)

    def test_verify_sth_for_bad_asn1_signature(self):
        # www.google.com certificate for which a bad SCT was issued.
        google_cert = (
            '-----BEGIN CERTIFICATE-----',
            'MIIEgDCCA2igAwIBAgIIdJ7+eILLLSgwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE',
            'BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl',
            'cm5ldCBBdXRob3JpdHkgRzIwHhcNMTUxMDA3MTExMDM4WhcNMTYwMTA1MDAwMDAw',
            'WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN',
            'TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3',
            'Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCR6Knj',
            'TG6eyvY6C1VO7daC0AbWe3cenr9y9lVFQH2ej5r87znUvep4pC/bmG71aTd25wds',
            'ScpclWNR4lkR9Ph45j8K+SjMXU7syiqFiWPWgVzyi4N3bXZw4w83RoTzfyUTn4Kx',
            '9nsQLmjVS4wUMSEpWBmYfORwUwMF8BYp5qSkIUogZTADPY7Qr8tmwEq8jLHv9z62',
            'SiYd9JEcGdhnajgXg/+/f+iIb1jhkbjsTjFJBHClgrtRqLZHSU1THZCK6iULTd1B',
            '4yBNvXcHDaSBTPUSvZvZXo/msKfOqd0fHtny1icgl5CSU0tZrZPteomMnLMGdLlN',
            'KHyqIX7XsAd3pNoXAgMBAAGjggFLMIIBRzAdBgNVHSUEFjAUBggrBgEFBQcDAQYI',
            'KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE',
            'XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0',
            'MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G',
            'A1UdDgQWBBSUPOkxr+tGC3JYs2JIdXVB2R+f8zAMBgNVHRMBAf8EAjAAMB8GA1Ud',
            'IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMCEGA1UdIAQaMBgwDAYKKwYBBAHW',
            'eQIFATAIBgZngQwBAgIwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29n',
            'bGUuY29tL0dJQUcyLmNybDANBgkqhkiG9w0BAQsFAAOCAQEAfBoIl5qeaJ7NZ6hB',
            'WqeBZwbDV/DOHCPg3/84n8YGlfYdfXQpQdOWC5hfgEkkinBT0yp8dDTdXMUIT9Al',
            'ZMrxE54xJ1cU6FPuZPDWOnzV+6YEW6P9RnTbqKgYCNkHFiFwVvFRm5RTEGei5TLv',
            'l0zFDBusT/mgyvYBMIfW3vVPteEKKEz+aRCZHRiLAHbmJHj2+blVJeHGSF+eKN5q',
            'GWgk7/pMww4JAXsLQ0mmL8qdJKivuiNcyyhbr8IeERiVcItKqfBsX1nwyUnYFWY3',
            'HPkV+sXAPnpTGuxgYvTjcYDf8UO9lgDX5QubEFjjTuTIYAAabmc6Z4UKOS0O46Ne',
            'z28m7Q==',
            '-----END CERTIFICATE-----')
        # The SCT with the bad signature.
        sct_bytes = (
            '00ddeb1d2b7a0d4fa6208b81ad8168707e2e8e9d01d55c888d3d11c4cdb6ecbecc'
            '00000150421dfbb6000004030047304502200035de73784699d2ad8c3631aeda77'
            'f70b2c899492b16f051fd6d38d46afc892022100a4d1b58c63002e5d0862a9f623'
            'f67c8ccf5fc934bd28133fbc8f240aae4cab38'
        ).decode('hex')

        symantec_sct = client_pb2.SignedCertificateTimestamp()
        tls_message.decode(sct_bytes, symantec_sct)
        key_info = client_pb2.KeyInfo()
        key_info.type = client_pb2.KeyInfo.ECDSA
        key_info.pem_key = pem.to_pem(
            base64.decodestring(SYMANTEC_B64_KEY),
            'PUBLIC KEY')
        verifier = verify.LogVerifier(key_info)
        self.assertRaises(
            error.SignatureError,
            verifier.verify_sct,
            symantec_sct,
            [cert.Certificate.from_pem("\n".join(google_cert)),])