def test_genKeys(self): assert HASH_ALGO == 'sha3_256' seed = b'ABCD' # Do _not_ change the seed value or the following asserts will fail! keys = Keys(4, seed) # All nodes in the hash tree are checked # z_0 assert str( keys.keys[0] ) == '86fcf391e5741c8c72bf67c99d50d0157fb9b02db4e3e12ff9e87cbaebf7aa3e' # z_1_2 = h(z_1 || z_2) assert str( keys.hash_tree_root.left_child ) == '387fec1a51aa8e02509065abf2903fdca4d72c59ce6a06b9c626b5b82d93b8d0' # z_3_4 = h(z_3 || z_4) assert str( keys.hash_tree_root.right_child ) == '11553e32125fb3743c7f541e9b9dc7d088436f6cf1bfcd2e158e470df16138d5' # z_1_2_3_4 = h(z_1_2 || z_3_4) assert str( keys.hash_tree_root ) == '79151d0a354254f666c9652003ec58a667cec715bab1f09fb5080ab5c31d9673' assert keys.hash_tree_root.right_child.left_child.is_leaf() is True # Merkle tree diagram keys.hash_tree_root.clear_mark() # This is for code coverage g = graphviz.Digraph(name="merkle tree", directory="./output", format="dot", node_attr={"shape": "box"}) g = keys.hash_tree_root.to_graphviz(g) g.render() # z_i diagram g_z = graphviz.Digraph(name="z_i", directory="./output", format="dot", node_attr={"shape": "box"}, edge_attr={"color": "red"}) last_idx = len(keys.keys) - 1 g_z.node(str(keys.keys[last_idx].uuid), label="z_" + str(last_idx) + " : " + keys.keys[last_idx].short_hex()) for i in range(last_idx, 0, -1): g_z.node(str(keys.keys[i - 1].uuid), label="z_" + str(i - 1) + " : " + keys.keys[i - 1].short_hex()) g_z.edge(str(keys.keys[i].uuid), str(keys.keys[i - 1].uuid)) g_z.node(seed.hex(), label="seed : " + str(seed)) g_z.edge(seed.hex(), str(keys.keys[last_idx].uuid)) g_z.render()
def test_3_missing_client_certificate(self): # This test works because the DAO memory share certificates among the client and server DAO dao_factory = factory(DAOMemoryFactory) server = KSIServer(self.id_server, dao_factory.get_server()) l = 8 keys = Keys(l=l, seed=b'SEED') client = KSIClient(server, dao_factory.get_client(), keys=keys) client.sign(b'AAAA') assert len(dao_factory.get_client().signatures) == 1 and len( dao_factory.get_server().signed) == 1
def test_default(self): id_client = Identifier("client") id_server = Identifier("server") seed = b'\xde\xad\xbe\xef' keys = Keys(l=4, seed=seed) z0 = keys.keys[0].hash r = keys.hash_tree_root.hash t0 = datetime.now() cert = Certificate(id_client, z0, r, t0, id_server) print(cert) # There isn't much of testing to be done here as Certificate is a convenience class... assert cert is not None
def test_all(self): dao_factory = factory(DAOMongoFactory) server = KSIServer(Identifier('server'), dao_factory.get_server()) keys = Keys(l=256, seed=b'SEED') client = KSIClient(server, dao_factory.get_client(), keys=keys) ref_cert = client.certificate mongo_cert = server.dao.get_user_certificate( client.certificate.id_client) self.assertTrue(ref_cert == mongo_cert) ref_msg = hash_factory(data=b'DATA').digest() sig = client.sign(ref_msg) client.verify(sig, client.certificate, ref_msg)
def test_verify(self): dao_factory = factory(DAOMemoryFactory) l = 512 keys = Keys(l=l, seed=b'SEED') client = KSIClient(KSIServer(Identifier("server"), dao_factory.get_server()), dao_factory.get_client(), keys=keys) message = b'DDDD' sig = client.sign(message) self.assertTrue(client.verify(sig, client.certificate, sig.message)) # Tampering with the hash chain sig.c_i.right_child.hash = b'1234567890' # This is an arbitrary value, any other than the original one would do self.assertFalse(client.verify(sig, client.certificate, sig.message))
def test_4_client_certificate_too_early(self): dao_factory = factory(DAOMemoryFactory) dao_memory_server = dao_factory.get_server() # type: DAOMemoryServer server = KSIServer(self.id_server, dao_factory.get_server()) l = 8 keys = Keys(l=l, seed=b'SEED2') client2 = KSIClient(server, dao_factory.get_client(), keys=keys, ID_C_str='client2') dao_memory_server.client_certificates[str( client2.certificate.id_client)].t_0 += timedelta(seconds=100) with self.assertRaises(ValueError): client2.sign(b'AAAA') assert len(dao_factory.get_client().signatures) == 1 and len( dao_memory_server.signed) == 1
def test_genKeysRandom(self): # Test with random seed random_keys = Keys(2**4)
def __init__(self, server: KSIServer, dao: DAOClient, certificate: Certificate = None, keys: Keys = None, ID_C_str: str = "client", api_user: str = "", api_password: str = "", api_ID_S: str = "", public_key_filename: str = "output/public_key." + SIGN_KEY_FORMAT): """ Create a new client with the provided parameters. This constructor support 2 "modes": local and API. API uses the REST API (requires a server). The local variant is the "legacy" version, although it is great for testing purposes (i.e. with travis or unit-testing). To use the local version you must set the server argument and leave api_* arguments. To use the API you do the opposite, that is set server to None and fill api_*. :param server: The server to ask for timestamps :type server: KSIServer :param certificate: The client's certificate. It can be None in which case it is filled with self.keys. :type certificate: Certificate :param keys: The client's keys. It can be None in which case it is generated with the default values. :type keys: Keys :param ID_C_str: The client's identifier string :type ID_C_str: str :param api_user: The client's username for the API HTTP Basic Auth :type api_user: str :param api_password: The client's password for the API HTTP Basic Auth :type api_password: str :param api_ID_S: The ID_S for the server if you are using the API (i.e. server must be None) :type api_ID_S: str :param public_key_filename: Path to the public key used to verify signed timestamps :type public_key_filename: str """ assert isinstance(dao, DAOClient) assert isinstance(api_user, str) and isinstance( api_password, str) and isinstance(api_ID_S, str) self.server = server if self.server: assert isinstance( server, KSIServer ) and api_ID_S == "" and api_user == "" and api_password == "" self.server_id = server.ID_S else: assert api_ID_S != "" and api_user != "" and api_password != "" self.server_id = Identifier(api_ID_S) self.dao = dao self.keys = keys self.api_user = api_user self.api_password = api_password # Generate keys with default values if not self.keys: self.keys = Keys() self.certificate = certificate # Configure the user's certificate from self.keys, t_0 is the current UTC time if not self.certificate: assert isinstance(self.keys, Keys) z_0 = self.keys.keys[0].hash r = self.keys.hash_tree_root.hash t_0 = datetime.utcnow().replace(microsecond=0) self.certificate = Certificate(Identifier(ID_C_str), z_0, r, t_0, self.server_id, self.keys.l) assert dao.publish_certificate(self.certificate) is True # Dictionary of requests made (indexed by x) self.requests = {} self.logger = logging.getLogger(__name__ + '.' + self.__class__.__name__) # Asymmetric signature verifier self.verifier = SignVerify() self.verifier.import_public_keys(public_key_filename) logging.debug( "Using public key in file '{}'".format(public_key_filename))
# REST API client example. # This file is executable as a "standalone" script. # logging.basicConfig(level=logging.DEBUG) # Filter messages to come only from the client's logger for handler in logging.root.handlers: handler.addFilter(logging.Filter("ksi.ksi_client.KSIClient")) if __name__ == "__main__": dao_factory = factory(DAOMongoFactory) client = KSIClient(None, dao_factory.get_client(), keys=Keys(l=8, seed=b'SEED2'), ID_C_str="client2", api_user="******", api_password="******", api_ID_S="server", public_key_filename="/tmp/public_key." + SIGN_KEY_FORMAT) sig = client.sign(b'EFGH', use_rest_api=True) dao_client = dao_factory.get_client() # type: DAOMongoClient r = requests.get(API_HOST_PORT + API_ROUTE_BASE + 'signed') assert str(client.certificate.id_client) in r.json()['signed_timestamps'] assert client.verify(sig, client.certificate, sig.message) is True
if __name__ == '__main__': add_logger() logging.basicConfig(level=logging.INFO) assert PERFORM_BENCHMARKS is True # This switch need to be enabled! assert BENCHMARK_MOTIF == "ksi.keys" l_nums = list(map(lambda i: 2**i, [i for i in range(2, MAX_L_POW + 1)])) _max_l = 2**MAX_L_POW for l in l_nums: open(LOGGER_DEFAULT_OUTFILE, 'w+').close() print("Computing time: \t{} / {}".format(l, _max_l)) # Benchmark Start keys = Keys(l=l) # Benchmark End logger = logging.getLogger(LOGGER_NAME) for h in logger.handlers: h.flush() logger.removeHandler(h) os.rename(LOGGER_DEFAULT_OUTFILE, LOGGER_SAVE_OUTFILE.format("Keys", HASH_ALGO, "l_" + str(l))) # Generating keys is quite long, we wish to restore them at a latter time (with the hash function of our choice) with shelve.open(KEY_SHELVE_OUTFILE.format("Keys", HASH_ALGO, "l_" + str(l)), flag='c') as save:
def test_sign(self): dao_factory = factory(DAOMemoryFactory) dao_memory_client = dao_factory.get_client() # type: DAOMemoryClient logging.basicConfig(level=logging.DEBUG) server_id = Identifier("server") server = KSIServer(server_id, dao_factory.get_server()) l = 8 keys = Keys(l=l, seed=b'SEED') client = KSIClient(server, dao_memory_client, keys=keys) # dao_memory_server.client_certificates[str(client.certificate.id_client)] = client.certificate sleep_counter = 2 sleep(sleep_counter) client.sign(b'AAAA') # Compute graphviz on the whole merkle graph g1 = graphviz.Digraph(name="after sign", directory="./output", format="dot", node_attr={"shape": "box"}) g1 = keys.hash_tree_root.to_graphviz(g1) g1.render() # Compute graphviz only the hash chain print("Signatures: ") for k, v in client.dao.signatures.items( ): # type: _sha3.SHA3, Signature print("[{k}] = {v}".format(k=k.hex(), v=v)) assert v.S_t.status_code == KSIErrorCodes.NO_ERROR g2 = graphviz.Digraph(name="hash chain", directory="./output", format="dot", node_attr={"shape": "box"}) g2 = v.c_i.to_graphviz(g2) g2.render() # +1 for "the sleep before publishing the signature" mechanism sleep_counter += 2 + 1 client.signatures = {} sleep(2) client.sign(b'BBBB') # Compute graphviz on the whole merkle graph g3 = graphviz.Digraph(name="after sign 2", directory="./output", format="dot", node_attr={"shape": "box"}) g3 = keys.hash_tree_root.to_graphviz(g3) g3.render() # Compute graphviz only the hash chain print("Signatures: ") for k, v in client.dao.signatures.items( ): # type: _sha3.SHA3, Signature print("[{k}] = {v}".format(k=k.hex(), v=v)) g4 = graphviz.Digraph(name="hash chain 2", directory="./output", format="dot", node_attr={"shape": "box"}) g4 = v.c_i.to_graphviz(g4) g4.render() sleep(l - sleep_counter - 1) with self.assertRaises(ValueError): client.sign(b'CCCC')