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")
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
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()))
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
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
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(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_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)
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)
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_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())
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)
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)),])