From b37b50be26a64da83d3668791ecc1e23376b5443 Mon Sep 17 00:00:00 2001 From: UBOK19 <53264378+UBOK19@users.noreply.github.com> Date: Sun, 20 Feb 2022 12:26:26 +0100 Subject: [PATCH 1/3] replace the keystore dumper with a more capable script --- examples/keystore-dumper.py | 128 ---------------- examples/keystore-tool.py | 282 ++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+), 128 deletions(-) delete mode 100644 examples/keystore-dumper.py create mode 100644 examples/keystore-tool.py diff --git a/examples/keystore-dumper.py b/examples/keystore-dumper.py deleted file mode 100644 index 3ae90b4..0000000 --- a/examples/keystore-dumper.py +++ /dev/null @@ -1,128 +0,0 @@ -import sys -import time -import argparse -import logging -import binascii -import uuid - -import ubirch - - -DEFAULT_SHOW_SIGN = "False" - - -logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) -logger = logging.getLogger() - - -class Main: - def __init__(self): - self.argparser : argparse.ArgumentParser = None - self.args : argparse.Namespace = None - - self.keystore_path : str = None - self.keystore_pass : str = None - self.show_sign_str : str = None - self.show_sign : bool = None - - self.keystore : ubirch.KeyStore = None - - # initialize the argument parser - self.setup_argparse() - - return - - def setup_argparse(self): - self.argparser = argparse.ArgumentParser( - description="Dump the contents of a keystore (.jks)", - epilog="" - ) - - self.argparser.add_argument("keystore", metavar="KEYSTORE", type=str, - help="keystore file path; e.g.: test.jks" - ) - self.argparser.add_argument("keystore_pass", metavar="KEYSTORE_PASS", type=str, - help="keystore password; e.g.: secret" - ) - self.argparser.add_argument("--show-sk", "-s", metavar="SHOW_SIGNING_KEY", type=str, default=DEFAULT_SHOW_SIGN, - help="enables/disables showing of signing keys; e.g.: true, false (default: %s)" % DEFAULT_SHOW_SIGN - ) - - def process_args(self) -> bool: - # parse cli arguments (exists on err) - self.args = self.argparser.parse_args() - - # get all needed args - self.keystore_path = self.args.keystore - self.keystore_pass = self.args.keystore_pass - self.show_sign_str = self.args.show_sk - - # get the bool for show sk - if self.show_sign_str.lower() in ["1", "yes", "y", "true"]: - self.show_sign = True - else: - self.show_sign = False - - return True - - def init_keystore(self) -> bool: - try: - self.keystore = ubirch.KeyStore(self.keystore_path, self.keystore_pass) - except Exception as e: - logger.exception(e) - - return False - - return True - - def dump_keystore(self) -> bool: - verifying_keys = self.keystore._ks.certs - signing_keys = self.keystore._ks.private_keys - - # go trough the list of verifiying keys and print information for each entry - for vk_uuid in verifying_keys.keys(): - if self.show_sign == True: - t = signing_keys.get("pke_" + vk_uuid) - - sk = binascii.hexlify(t.pkey).decode() if t != None else "N / A" - else: - sk = "█" * 128 - - print("=" * 134) - print("UUID: %s" % str(uuid.UUID(hex=vk_uuid))) - print(" VK : %s" % binascii.hexlify(verifying_keys[vk_uuid].cert).decode()) - print(" SK : %s" % sk) - print("=" * 134) - - return True - - def run(self) -> int: - # process all raw argument values - if self.process_args() != True: - logger.error("Errors occured during argument processing - exiting!\n") - - self.argparser.print_usage() - - return 1 - - # initialize the keystore - if self.init_keystore() != True: - logger.error("Errors occured while initializing the uBirch Keystore - exiting!\n") - - self.argparser.print_usage() - - return 1 - - if self.dump_keystore() != True: - logger.error("Errors occured while dumping the uBirch Keystore - exiting!\n") - - self.argparser.print_usage() - - return 1 - - return 0 - - -# initialize/start the main class -if __name__ == "__main__": - sys.exit(Main().run()) diff --git a/examples/keystore-tool.py b/examples/keystore-tool.py new file mode 100644 index 0000000..8b911c3 --- /dev/null +++ b/examples/keystore-tool.py @@ -0,0 +1,282 @@ +import sys +import time +import argparse +import logging +import binascii +import uuid +import ed25519 + +import ubirch + + +DEFAULT_SHOW_SECRET = "False" +COMMAND_GET = "get" +COMMAND_PUT = "put" +COMMAND_DEL = "del" + + +logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) +logger = logging.getLogger() + + +class Main: + def __init__(self): + self.argparser : argparse.ArgumentParser = None + self.get_argparser : argparse.ArgumentParser = None + self.put_argparser : argparse.ArgumentParser = None + self.args : argparse.Namespace = None + + # for all commands + self.keystore_path : str = None + self.keystore_pass : str = None + self.uuid_str : str = None + self.uuid : uuid.UUID = None + self.cmd : str = None + + # for get + self.show_sign_str : str = None + self.show_sign : bool = None + + # for put + self.pubkey_str : str = None + self.pubkey : ed25519.VerifyingKey = None + self.prvkey_str : str = None + self.prvkey : ed25519.SigningKey = None + + self.keystore : ubirch.KeyStore = None + + # initialize the argument parser + self.setup_argparse() + + return + + def setup_argparse(self): + self.argparser = argparse.ArgumentParser( + description="Manipulate/View the contents of a keystore (.jks)", + epilog="Only one entry per UUID is supported. Passing an non-existent KeyStore file as argument will lead to a new KeyStore being created. This new KeyStore will only be persistent if a write operation (-> key insertion) takes place." + ) + + self.argparser.add_argument("keystore", metavar="KEYSTORE", type=str, + help="keystore file path; e.g.: test.jks" + ) + self.argparser.add_argument("keystore_pass", metavar="KEYSTORE_PASS", type=str, + help="keystore password; e.g.: secret" + ) + + # create subparsers + subparsers = self.argparser.add_subparsers(help="Command to execute.", dest="cmd", required=True) + + # subparser for the get-command + self.get_argparser = subparsers.add_parser(COMMAND_GET, help="Get entries from the KeyStore.") + + self.get_argparser.add_argument("--uuid", "-u", type=str, default=None, + help="UUID to filter for. Only keys for this UUID will be returned; e.g.: f99de1c4-3859-5326-a155-5696f00686d9" + ) + self.get_argparser.add_argument("--show-secret", "-s", type=str, default=DEFAULT_SHOW_SECRET, + help="Enables/Disables showing of secret (signing/private) keys; e.g.: true/false (default: %s)" % DEFAULT_SHOW_SECRET + ) + + # subparser for the put-command + self.put_argparser = subparsers.add_parser(COMMAND_PUT, help="Put a new entry into the KeyStore.") + + self.put_argparser.add_argument("uuid", metavar="UUID", type=str, + help="The UUID the new keys belong to; e.g.: f99de1c4-3859-5326-a155-5696f00686d9" + ) + self.put_argparser.add_argument("pubkey", metavar="PUBKEY", type=str, + help="The HEX-encoded ED25519 PubKey; e.g.: 189595c87a972c55eb7348a310fa1ff479a895a1f226d189b5ad505b9d8c8bbf" + ) + self.put_argparser.add_argument("privkey", metavar="PRIVKEY", type=str, + help="The HEX-encoded ED25519 PrivKey; e.g.: 9c7c43e122ae51e08a86e9bb89fe340bd4c7bd6665bf2b40004d4012f1523575127f8ac54a971765126a866428a6c74d4747d1b68e189f0fa3528a73e3f59714" + ) + + # subparser for the del-command + self.del_argparser = subparsers.add_parser(COMMAND_DEL, help="Delete an entry from the KeyStore.") + + self.del_argparser.add_argument("uuid", metavar="UUID", type=str, + help="The UUID to delete the keypair for (this is safe since each UUID can only occur once in the KeyStore); e.g.: f99de1c4-3859-5326-a155-5696f00686d9" + ) + + def process_args(self) -> bool: + # parse cli arguments (exists on err) + self.args = self.argparser.parse_args() + + # get all needed args + self.keystore_path = self.args.keystore + self.keystore_pass = self.args.keystore_pass + self.cmd = self.args.cmd + + # for put + if self.cmd == COMMAND_PUT: + self.uuid_str = self.args.uuid + self.pubkey_str = self.args.pubkey + self.prvkey_str = self.args.privkey + + # load the keypair + try: + self.pubkey = ed25519.VerifyingKey(binascii.unhexlify(self.pubkey_str)) + except Exception as e: + logger.error("Error loading the PubKey!") + logger.exception(e) + + return False + + try: + self.prvkey = ed25519.SigningKey(binascii.unhexlify(self.prvkey_str)) + except Exception as e: + logger.error("Error loading the PrivKey!") + logger.exception(e) + + return False + elif self.cmd == COMMAND_GET: + # for get + self.show_sign_str = self.args.show_secret + self.uuid_str = self.args.uuid + + # get the bool for show sk + if self.show_sign_str.lower() in ["1", "yes", "y", "true"]: + self.show_sign = True + else: + self.show_sign = False + elif self.cmd == COMMAND_DEL: + # for del + self.uuid_str = self.args.uuid + else: + logger.error("Unknown command \"%s\"!" % self.cmd) + + return False + + # load the uuid if specified + if self.uuid_str != None: + try: + self.uuid = uuid.UUID(self.uuid_str) + except Exception as e: + logger.error("Error loading UUID: \"%s\"" % self.uuid_str) + logger.exception(e) + + return False + + return True + + def init_keystore(self) -> bool: + try: + self.keystore = ubirch.KeyStore(self.keystore_path, self.keystore_pass) + except Exception as e: + logger.exception(e) + + return False + + return True + + def dump_keystore(self) -> bool: + verifying_keys = self.keystore._ks.certs + signing_keys = self.keystore._ks.private_keys + + # go trough the list of verifiying keys and print information for each entry + for vk_uuid in verifying_keys.keys(): + # check if a filtering uuid is set; if it is, filter + if self.uuid != None: + if self.uuid.hex != vk_uuid: + continue + + if self.show_sign == True: + t = signing_keys.get("pke_" + vk_uuid) + + sk = binascii.hexlify(t.pkey).decode() if t != None else "N / A" + else: + sk = "█" * 128 + + print("=" * 134) + print("UUID: %s" % str(uuid.UUID(hex=vk_uuid))) + print(" VK : %s" % binascii.hexlify(verifying_keys[vk_uuid].cert).decode()) + print(" SK : %s" % sk) + print("=" * 134) + + return True + + def put_keypair(self) -> bool: + logger.info("Inserting keypair for %s with pubkey %s into %s!" % (self.uuid_str, self.pubkey_str, self.keystore_path)) + + try: + self.keystore.insert_ed25519_keypair(self.uuid, self.pubkey, self.prvkey) + except Exception as e: + logger.error("Error inserting the keypair into the KeyStore!") + logger.exception(e) + + return True + + def del_keypair(self) -> bool: + logger.warning("About to remove the keypair for UUID %s from %s! Enter 'YES' to continue" % (self.uuid_str, self.keystore_path)) + + # get user confirmation to delete + if input("> ") != 'YES': + logger.error("Aborting!") + + # stopped the process by user-choice; not a "real" error + return True + + # delete both the pubkey and the private key entries + try: + # direkt access to the entries variable is needed since .certs and .private_keys + # are class properties which are only temporary (-> editing them has no effect) + self.keystore._ks.entries.pop(self.uuid.hex) + self.keystore._ks.entries.pop("pke_" + self.uuid.hex) + except Exception as e: + logger.error("Error deleting keys! No changes will be written!") + logger.exception(e) + + return False + + # write changes + self.keystore._ks.save(self.keystore._ks_file, self.keystore._ks_password) + + return True + + def run(self) -> int: + # process all raw argument values + if self.process_args() != True: + logger.error("Errors occured during argument processing - exiting!\n") + + self.argparser.print_help() + + return 1 + + # initialize the keystore + if self.init_keystore() != True: + logger.error("Errors occured while initializing the uBirch Keystore - exiting!\n") + + self.argparser.print_help() + + return 1 + + if self.cmd == COMMAND_GET: + if self.dump_keystore() != True: + logger.error("Errors occured while dumping the uBirch Keystore - exiting!\n") + + self.get_argparser.print_help() + + return 1 + elif self.cmd == COMMAND_PUT: + if self.put_keypair() != True: + logger.error("Errors occured while puting a new keypair into the KeyStore - exiting!\n") + + self.put_argparser.print_help() + + return 1 + elif self.cmd == COMMAND_DEL: + if self.del_keypair() != True: + logger.error("Errors occured while deleting a keypair from the KeyStore - exiting!\n") + + self.del_argparser.print_help() + + return 1 + else: + logger.error("Unknown command \"%s\" - exiting!\n" % self.cmd) + + return 1 + + return 0 + + +# initialize/start the main class +if __name__ == "__main__": + sys.exit(Main().run()) From e4d882c691d1012ee4ed7e6a26a3858a92462132 Mon Sep 17 00:00:00 2001 From: UBOK19 <53264378+UBOK19@users.noreply.github.com> Date: Sun, 20 Feb 2022 12:29:22 +0100 Subject: [PATCH 2/3] implement the key service example script --- examples/pubkey-util.py | 536 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 examples/pubkey-util.py diff --git a/examples/pubkey-util.py b/examples/pubkey-util.py new file mode 100644 index 0000000..f329383 --- /dev/null +++ b/examples/pubkey-util.py @@ -0,0 +1,536 @@ +import sys +import logging +import argparse +import requests +import uuid +import ed25519 +import binascii +import json +import base64 +import msgpack + +import ubirch + + +logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) +logger = logging.getLogger() + +# put_key use msgpack +PUT_KEY_USE_MSGPACK_DEFAULT="false" + +# commands +PUT_NEW_KEY_CMD = "put_new_key" +GET_DEV_KEYS_CMD = "get_dev_keys" +GET_KEY_INFO_CMD = "get_key_info" +DELETE_KEY_CMD = "delete_key" +REVOKE_KEY_CMD = "revoke_key" + +# URLs and paths +UBIRCH_ID_SERVICE = "https://identity.%s.ubirch.com/api/keyService/v1/pubkey" +GET_DEVICE_KEYS_PATH = "/current/hardwareId/%s" # completed with the uuid +GET_KEY_INFO_PATH = "/%s" # completed with the pubkeyId (equal to pubkey) in b64 +REVOKE_KEY_PATH = "/revoke" +PUT_KEY_MSGPACK_PATH = "/mpack" + +# body formats +DEL_PUBKEY_FMT = '{'\ + '"publicKey":"%s",'\ + '"signature":"%s"'\ +'}' + +REVOKE_PUBKEY_FMT = '{'\ + '"publicKey":"%s",'\ + '"signature":"%s"'\ +'}' + +PUT_PUBKEY_UPDATE_FMT_OUTER = '{'\ + '"pubKeyInfo":%s,'\ + '"prevSignature":"%s",'\ + '"signature":"%s"'\ +'}' +PUT_PUBKEY_UPDATE_FMT_INNER = '{'\ + '"algorithm":"%s",'\ + '"created":"%s",'\ + '"hwDeviceId":"%s",'\ + '"pubKey":"%s",'\ + '"pubKeyId":"%s",'\ + '"prevPubKeyId":"%s",'\ + '"validNotAfter":"%s",'\ + '"validNotBefore":"%s"'\ +'}' + +PUT_NEW_PUBKEY_FMT_OUTER = '{'\ + '"pubKeyInfo":%s,'\ + '"signature":"%s"'\ +'}' +PUT_NEW_PUBKEY_FMT_INNER = '{'\ + '"algorithm":"%s",'\ + '"created":"%s",'\ + '"hwDeviceId":"%s",'\ + '"pubKey":"%s",'\ + '"pubKeyId":"%s",'\ + '"validNotAfter":"%s",'\ + '"validNotBefore":"%s"'\ +'}' + + +class Main: + def __init__(self): + self.argparser : argparse.ArgumentParser = None + self.get_dev_keys_parser : argparse.ArgumentParser = None + self.get_key_info_parser : argparse.ArgumentParser = None + self.put_new_key_parser : argparse.ArgumentParser = None + self.del_key_parser : argparse.ArgumentParser = None + self.revoke_key_parser : argparse.ArgumentParser = None + self.args : argparse.Namespace = None + + self.cmd_str : str = None # store the command string + self.base_url : str = None # store the complete url (incl. env) + + # not every variable is needed for every command; still, all are listed here for clarity + self.uuid_str : str = None # for get_dev_keys, put_new_key, delete_key and revoke_key + self.uuid : uuid.UUID = None # for get_dev_keys, put_new_key, delete_key and revoke_key + self.pubkey_str : str = None # for put_new_key, delete_key, revoke_key, get_key_info + self.pubkey_b64 : bytes = None # for put_new_key, delete_key, rewoke_key, get_key_info + self.prvkey_str : str = None # for put_new_key, delete_key, revoke_key + self.old_pubkey_str : str = None # for put_new_key (when updating) + self.old_prvkey_str : str = None # for put_new_key (when updating) + self.old_pubkey_b64 : bytes = None # for put_new_key (when updating) + self.key_created_at : str = None # for put_new_key + self.key_valid_not_after : str = None # for put_new_key + self.key_valid_not_before : str = None # for put_new_key + self.use_msgpack_str : str = None # for put_new_key + self.use_msgpack : bool = None # for put_new_key + + # the privkey needs to be loaded as actual key (not string) for some operations + self.prvkey : ed25519.SigningKey = None + self.old_prvkey : ed25519.SigningKey = None + + self.upp : bytes = None + self.api : ubirch.API = None + + # initialize the argument parser + self.setup_argparse() + + return + + def setup_argparse(self): + self.argparser = argparse.ArgumentParser( + description="A tool to perform pubkey operations with the uBirch Identity Service", + epilog="Choose an environment + command and use the '--help'/'-h' option to see a command-specific help message; e.g.: python %s dev revoke_key -h. Note that for many operations (like putting a new PubKey), only the PrivKey is needed. That is because in case of ED25519 keys, the PubKey can be generated out of the PrivKey, because the PrivKey is generally regarded to as a seed for the keypair." + ) + + # set up the main parameters + self.argparser.add_argument("env", metavar="", type=str, + help="Environment to work on. Must be one of 'dev', 'demo' or 'prod'. Case insensitive." + ) + self.argparser.add_argument("--debug", "-d", type=str, default="false", + help="Enables/Disables debug logging. When enabled, all HTTP bodies will be printed before sending; 'true'/'false' (Default: 'false')" + ) + + # generate a subparser group; the dest parameter is needed so that the program knows + # which subparser was triggered later on + subparsers = self.argparser.add_subparsers(help="Command to execute.", dest="cmd", required=True) + + # set up conditional parameters for each command/operation + # subparser + arguments for the get_dev_keys operation + self.get_dev_keys_parser = subparsers.add_parser(GET_DEV_KEYS_CMD, help="Get PubKeys registered for a given device.") + self.get_dev_keys_parser.add_argument("uuid", metavar="UUID", type=str, + help="The device UUID to get the keys for. E.g.: f99de1c4-3859-5326-a155-5696f00686d9" + ) + + # subparser + arguments for the get_key_info operation + self.get_key_info_parser = subparsers.add_parser(GET_KEY_INFO_CMD, help="Get information for a specific PubKey.") + self.get_key_info_parser.add_argument("pubkey", metavar="PUBKEY_HEX", type=str, + help="ED25519 Pubkey to retrieve information for in HEX" + ) + + # subparser + arguments for the put_new_key operation + self.put_new_key_parser = subparsers.add_parser(PUT_NEW_KEY_CMD, help="Register a new PubKey.") + self.put_new_key_parser.add_argument("uuid", metavar="UUID", type=str, + help="The device UUID to register a key for. E.g.: f99de1c4-3859-5326-a155-5696f00686d9" + ) + self.put_new_key_parser.add_argument("prvkey", metavar="PRIVKEY_HEX", type=str, + help="The ED25519 PrivKey corresponding to the PubKey in HEX." + ) + self.put_new_key_parser.add_argument("created", metavar="CREATED", type=str, + help="Date at which the PubKey was created; (format: 2020-12-30T11:11:11.000Z)" + ) + self.put_new_key_parser.add_argument("validNotBefore", metavar="VALID_NOT_BEFORE", type=str, + help="Date at which the PubKey will become valid; (format: 2020-12-30T22:22:22.000Z)." + ) + self.put_new_key_parser.add_argument("validNotAfter", metavar="VALID_NOT_AFTER", type=str, + help="Date at which the PubKey will become invalid; (format: 2030-02-02T02:02:02.000Z)." + ) + self.put_new_key_parser.add_argument("--update", "-u", metavar="OLD_PRIVKEY_HEX", type=str, default=None, + help="Old private key to sign the keypair update in HEX. Only needed if there already is a PubKey registered." + ) + self.put_new_key_parser.add_argument("--msgpack", "-m", metavar="MSGPACK", type=str, default=PUT_KEY_USE_MSGPACK_DEFAULT, + help="NOT IMPLEMENTED! Enables/Disables usage of MsgPack instead of Json. Can't be used for key updates (-u); true or false (default: %s)" % PUT_KEY_USE_MSGPACK_DEFAULT + ) + + # subparser + arguments for the delete_key operation + self.del_key_parser = subparsers.add_parser(DELETE_KEY_CMD, help="Delete a registered PubKey.") + self.del_key_parser.add_argument("prvkey", metavar="PRIVKEY_HEX", type=str, + help="ED25519 PrivKey in HEX corresponding to the PubKey to be deleted." + ) + + # subparser + arguments for the revoke_key operation + self.revoke_key_parser = subparsers.add_parser(REVOKE_KEY_CMD, help="Revoke a registered PubKey.") + self.revoke_key_parser.add_argument("prvkey", metavar="PRIVKEY_HEX", type=str, + help="ED25519 PrivKey in HEX corresponding to the PubKey to be revoked." + ) + + return + + def process_args(self) -> bool: + # parse cli arguments (exists on err) + self.args = self.argparser.parse_args() + + # the env argument is needed for every command - get it + self.env = self.args.env + self.cmd_str = self.args.cmd + + if self.args.debug.lower() in ["1", "yes", "y", "true"]: + logger.level = logging.DEBUG + + logging.debug("Log level set to debug!") + + # format the url + self.base_url = UBIRCH_ID_SERVICE % self.env + + if self.env.lower() not in ["dev", "demo", "prod"]: + logger.error("Invalid value for env: \"%s\"!" % self.env) + + return False + + # load the UUID if needed + if self.cmd_str in [GET_DEV_KEYS_CMD, PUT_NEW_KEY_CMD]: + try: + self.uuid_str = self.args.uuid + self.uuid = uuid.UUID(hex=self.uuid_str) + except Exception as e: + logger.error("Invalid UUID: \"%s\"" % self.uuid_str) + logger.exception(e) + + return False + + # set the pubkey str and encode it as b64 if needed + if self.cmd_str in [GET_KEY_INFO_CMD]: + self.pubkey_str = self.args.pubkey + + try: + self.pubkey_b64 = base64.b64encode(binascii.unhexlify(self.pubkey_str)).decode("utf8").strip("\n") + except Exception as e: + logger.error("Error un-hexlifying the PubKey and encoding it in Base64!") + logger.exception(e) + + return False + + # load the privkey if needed + if self.cmd_str in [PUT_NEW_KEY_CMD, DELETE_KEY_CMD, REVOKE_KEY_CMD]: + self.prvkey_str = self.args.prvkey + + # the prvkey needs to be loaded to be usable + try: + self.prvkey = ed25519.SigningKey(binascii.unhexlify(self.prvkey_str)) + except Exception as e: + logger.error("Error loading the ED25519 private key!") + logger.exception(e) + + return False + + logger.info("PrivKey loaded!") + + # get the pubkey + self.pubkey_str = binascii.hexlify(self.prvkey.get_verifying_key().to_bytes()).decode("utf8") + + logger.info("PubKey extracted from the PrivKey: %s" % self.pubkey_str) + + # b64-encode the pubkey + try: + self.pubkey_b64 = base64.b64encode(binascii.unhexlify(self.pubkey_str)).decode("utf8").strip("\n") + except Exception as e: + logger.error("Error un-hexlifying the PubKey and encoding it in Base64!") + logger.exception(e) + + return False + + # load the old privkey if needed + if self.cmd_str in [PUT_NEW_KEY_CMD]: + # can be none; used for detecting whether a key-update should be done + # and also needed for the update itself + self.old_prvkey_str = self.args.update + + # the old prvkey needs to be loaded to be usable to sign the update + if self.old_prvkey_str != None: + try: + self.old_prvkey = ed25519.SigningKey(binascii.unhexlify(self.old_prvkey_str)) + except Exception as e: + logger.error("Error loading the old ED25519 private key!") + logger.exception(e) + + return False + + logger.info("Old PrivKey loaded!") + + # get the old pubkey from the privkey + self.old_pubkey_str = binascii.hexlify(self.old_prvkey.get_verifying_key().to_bytes()).decode("utf8") + + logger.info("Old PubKey extracted from old PrivKey: %s" % self.old_pubkey_str) + + # b64-encode the old pubkey + try: + self.old_pubkey_b64 = base64.b64encode(binascii.unhexlify(self.old_pubkey_str)).decode("utf8").strip("\n") + except Exception as e: + logger.error("Error un-hexlifying the old PubKey and encoding it in Base64!") + logger.exception(e) + + return False + + self.key_valid_not_before = self.args.validNotBefore + self.key_valid_not_after = self.args.validNotAfter + self.key_created_at = self.args.created + self.use_msgpack_str = self.args.msgpack + + # extract the bool for the use-msgpack flag + if self.use_msgpack_str.lower() in ["1", "yes", "y", "true"]: + self.use_msgpack = True + else: + self.use_msgpack = False + + return True + + def usage(self): + if self.cmd_str == PUT_NEW_KEY_CMD: + self.put_new_key_parser.print_help() + elif self.cmd_str == GET_KEY_INFO_CMD: + self.get_key_info_parser.print_help() + elif self.cmd_str == GET_DEV_KEYS_CMD: + self.get_dev_keys_parser.print_help() + elif self.cmd_str == "del_key": + self.del_key_parser.print_help() + elif self.cmd_str == REVOKE_KEY_CMD: + self.revoke_key_parser.print_help() + else: + logger.error("Unknown cmd \"%s\"!" % self.cmd_str) + + self.argparser.print_help() + + def handle_http_response(self, r : requests.Response) -> bool: + # check the reponse code + if r.status_code != 200: + logger.error("Received NOT-OK HTTP response code %d!" % r.status_code) + logger.error("Status message: %s" % r.content) + + return 1 + + logger.info("Success! (HTTP) 200)") + + # format the response json and print it + formatted = json.dumps(json.loads(r.content.decode("utf8")), indent=4) + + logger.info("HTTP response:\n" + formatted) + + return 0 + + # signs the pubkey from .pubkey_str with .prvkey and returns the signature in base64 + def sign_data_b64(self, data : bytes, use_old_priv=False) -> bytes: + # sign the pubkey (RAW, NOT B64) + try: + if use_old_priv == True: + signed = self.old_prvkey.sign(data) + else: + signed = self.prvkey.sign(data) + except Exception as e: + logger.error("Error de-hexlifying and signing the PubKey!") + logger.exception(e) + + return None + + # encode the signature in base64 + try: + signed_b64 = base64.b64encode(signed).decode("utf8").strip("\n") + except Exception as e: + logger.error("Error B64-encoding the PubKey signature!") + logger.exception(e) + + return None + + return signed_b64 + + def run_get_dev_keys(self): + url = self.base_url + (GET_DEVICE_KEYS_PATH % self.uuid_str) + + logger.info("Getting keys for %s from %s!" % (self.uuid_str, url)) + + # send the request + r = requests.get( + url=url, + headers={'Accept': 'application/json'} + ) + + # handle the reponse + return self.handle_http_response(r) + + def run_get_key_info(self): + url = self.base_url + (GET_KEY_INFO_PATH % self.pubkey_b64) + + logger.info("Getting information for the PubKey %s (B64) from %s" % (self.pubkey_b64, url)) + + # send the request + r = requests.get( + url=url, + headers={'Accept': 'application/json'} + ) + + # handle the response + return self.handle_http_response(r) + + def run_put_new_key_json(self): + url = self.base_url + + # check if this is a key update + if self.old_prvkey_str != None: + # format the innter message + inner_msg = PUT_PUBKEY_UPDATE_FMT_INNER % ( + "ECC_ED25519", self.key_created_at, self.uuid_str, self.pubkey_b64, self.pubkey_b64, + self.old_pubkey_b64, self.key_valid_not_after, self.key_valid_not_before + ) + + # sign the inner message with both privkeys + prevsig = self.sign_data_b64(bytes(inner_msg, "utf8"), use_old_priv=True) + sig = self.sign_data_b64(bytes(inner_msg, "utf8")) + + + print(inner_msg) + + # create the whole msg + msg = PUT_PUBKEY_UPDATE_FMT_OUTER % ( + inner_msg, prevsig, sig + ) + else: + # format the innter message + inner_msg = PUT_NEW_PUBKEY_FMT_INNER % ( + "ECC_ED25519", self.key_created_at, self.uuid_str, self.pubkey_b64, + self.pubkey_b64, self.key_valid_not_after, self.key_valid_not_before + ) + + # sign the inner message with the privkey + sig = self.sign_data_b64(bytes(inner_msg, "utf8")) + + # create the whole msg + msg = PUT_NEW_PUBKEY_FMT_OUTER % ( + inner_msg, sig + ) + + # get user confirmation to register the key + logger.info("Registering new PubKey %s (B64) at %s!" % (self.pubkey_b64, url)) + logger.debug("Data:\n" + msg) + + if input("Enter 'YES' to continue: ") != 'YES': + logger.error("Aborting!") + + return 0 + + # send the request + r = requests.post(url, data=msg) + + # handle the response + return self.handle_http_response(r) + + def run_put_new_key_msgpack(self): + logger.error("NOT IMPLEMENTED YET. SEE 'upp-creator.py' FOR AN IMPLEMENTATION OF THIS FUNCTIONALITY.") + + return 0 + + def run_put_new_key(self): + # call the subfunction depending on the use-msgpack flag + if self.use_msgpack == True: + return self.run_put_new_key_msgpack() + else: + return self.run_put_new_key_json() + + def run_del_key(self): + pubkey_sign_b64 = self.sign_data_b64(binascii.unhexlify(self.pubkey_str)) + + if pubkey_sign_b64 == None: + return 1 + + # format the message + msg = DEL_PUBKEY_FMT % (self.pubkey_b64, pubkey_sign_b64) + + # get the url + url = self.base_url + + # get user confirmation to delete the key + logger.info("Deleting PubKey %s (B64) at %s!" % (self.pubkey_b64, url)) + logger.debug("Data:\n" + msg) + + if input("Enter 'YES' to continue: ") != 'YES': + logger.error("Aborting!") + + return 0 + + # send the request + r = requests.delete(url, data=msg) + + # handle the response + return self.handle_http_response(r) + + def run_revoke_key(self): + pubkey_sign_b64 = self.sign_pubkey() + + if pubkey_sign_b64 == None: + return 1 + + # format the message + msg = REVOKE_PUBKEY_FMT % (self.pubkey_b64, pubkey_sign_b64) + + # get the url + url = self.base_url + REVOKE_KEY_PATH + + # get user confirmation to revoke the key + logger.info("Revoking PubKey %s (B64) at %s!" % (self.pubkey_b64, url)) + logger.debug("Data:\n" + msg) + + if input("Enter 'YES' to continue: ") != 'YES': + logger.error("Aborting!") + + return 0 + + # send the request + r = requests.delete(url, data=msg) + + # handle the response + return self.handle_http_response(r) + + return 0 + + def run(self): + # process all args + if self.process_args() != True: + logger.error("Errors occured during argument processing - exiting!\n") + + self.usage() + + return 1 + + # call the correct sub-run function + if self.cmd_str == PUT_NEW_KEY_CMD: + return self.run_put_new_key() + elif self.cmd_str == GET_KEY_INFO_CMD: + return self.run_get_key_info() + elif self.cmd_str == GET_DEV_KEYS_CMD: + return self.run_get_dev_keys() + elif self.cmd_str == DELETE_KEY_CMD: + return self.run_del_key() + elif self.cmd_str == REVOKE_KEY_CMD: + return self.run_revoke_key() + else: + logger.error("Unknown cmd \"%s\"!" % self.cmd_str) + + return 1 + +if __name__ == "__main__": + sys.exit(Main().run()) \ No newline at end of file From 3e1ee23bf6edec0fba9fdf40f770990548b80589 Mon Sep 17 00:00:00 2001 From: UBOK19 <53264378+UBOK19@users.noreply.github.com> Date: Sun, 20 Feb 2022 12:29:43 +0100 Subject: [PATCH 3/3] update the readme --- examples/EXAMPLES.md | 123 +++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 51 deletions(-) diff --git a/examples/EXAMPLES.md b/examples/EXAMPLES.md index 15e4563..0a7df7f 100644 --- a/examples/EXAMPLES.md +++ b/examples/EXAMPLES.md @@ -1,29 +1,31 @@ -# uBirch-Protocol-Python Examples +# uBirch-Protocol-Python Examples This file documents how to use the examples provided alongside the [uBirch-Protocol-Python](https://github.com/ubirch/ubirch-protocol-python). Those examples aim to provide an insight of how to use the [ubirch-protocol](https://pypi.org/project/ubirch-protocol/) python library, which is implemented in the `/ubirch/` directory in this repository. ## Table of contents -- [uBirch-Protocol-Python Examples](#ubirch-protocol-python-examples) - - [Table of contents](#table-of-contents) - - [From measurement to blockchain-anchored UPP](#from-measurement-to-blockchain-anchored-upp) - - [Setup](#setup) - - [Generating and managing a keypair](#generating-and-managing-a-keypair) - - [Registering a public key](#registering-a-public-key) - - [Gathering Data](#gathering-data) - - [Creating an UPP](#creating-an-upp) - - [Sending an UPP](#sending-an-upp) - - [Verifying an UPP](#verifying-an-upp) - - [Verifying an UPP chain](#verifying-an-upp-chain) - - [Examining an UPP](#examining-an-upp) - - [Checking the anchoring status of an UPP](#checking-the-anchoring-status-of-an-upp) - - [Verifying data](#verifying-data) - - [Sending data to the Simple Data Service](#sending-data-to-the-simple-data-service) - - [Example uBirch client implementation](#example-ubirch-client-implementation) - - [Create a hash from an JSON object](#create-a-hash-from-an-json-object) - - [Test identity of the device](#test-identity-of-the-device) - - [Test the complete protocol](#test-the-complete-protocol) - - [Test the web of trust](#test-the-web-of-trust) - - [Verify ECDSA signed UPP](#verify-ecdsa-signed-upp) - - [Verify ED25519 signed UPP](#verify-ed25519-signed-upp) +- [Table of contents](#table-of-contents) +- [From measurement to blockchain-anchored UPP](#from-measurement-to-blockchain-anchored-upp) + - [Setup](#setup) + - [Generating and managing a keypair](#generating-and-managing-a-keypair) + - [Registering a public key](#registering-a-public-key) + - [Gathering Data](#gathering-data) + - [Creating an UPP](#creating-an-upp) + - [Sending an UPP](#sending-an-upp) + - [Verifying an UPP](#verifying-an-upp) + - [Verifying an UPP chain](#verifying-an-upp-chain) + - [Examining an UPP](#examining-an-upp) + - [Checking the anchoring status of an UPP](#checking-the-anchoring-status-of-an-upp) + - [Verifying data](#verifying-data) +- [Sending data to the Simple Data Service](#sending-data-to-the-simple-data-service) +- [Example uBirch client implementation](#example-ubirch-client-implementation) +- [Create a hash from an JSON object](#create-a-hash-from-an-json-object) +- [Test identity of the device](#test-identity-of-the-device) +- [Test the complete protocol](#test-the-complete-protocol) +- [Test the web of trust](#test-the-web-of-trust) +- [Verify ECDSA signed UPP](#verify-ecdsa-signed-upp) +- [Verify ED25519 signed UPP](#verify-ed25519-signed-upp) +- [Managing Keys](#managing-keys) + - [Managing the local KeyStore](#managing-the-local-keystore) + - [Managing keys inside the uBirch Identity Service](#managing-keys-inside-the-ubirch-identity-service) ## From measurement to blockchain-anchored UPP The process needed to get an UPP to be anchored in the blockchain can be cut down into multiple steps. For each of those steps there is an example in this directory, demonstrating how to handle them. There are also examples showing a full example-client implementation. @@ -68,33 +70,9 @@ The values used below are `f5ded8a3-d462-41c4-a8dc-af3fd072a217` for the UUID, ` ### Generating and managing a keypair To create, or more precisely, to _sign_ an UPP, a device will need a keypair. This keypair consist of a private key (_signing key_) and public key (_verifying key_). The signing key is used to sign UPPs and the verifying key can be used by the uBirch backend to check, if the signature is valid and belongs to the correct sender/signer. So, logically it doesn't matter who knows to verifying key, but the signing key must be kept secret all the time. In a real use case a device might store it in a TPM ([Trusted platform module](https://en.wikipedia.org/wiki/Trusted_Platform_Module)) or use other counter measures against attackers reading the key from the device. For this demo, keypairs will be stored in a [JKS Keystore](https://en.wikipedia.org/wiki/Java_KeyStore) using the [`pyjks`](https://pypi.org/project/pyjks/) library. Therefore, you will have to choose and remember a file path for that keystore and a password used to encrypt it. The process of actually generating the keypair is handled by the [upp-creator.py](upp-creator.py) script and explained [below](#registering-a-public-key). -**NOTE** that losing access to the signing key, especially if it is already registered at the uBirch backend, will take away the ability to create and send any new UPPs from that device/UUID, since there is no way of creating a valid signature that would be accepted by the backend. - -A keystore can be read out with the [`keystore-dumper.py`](keystore-dumper.py) script. +To read generated keys from the KeyStore, see [below](#managing-the-local-keystore). -``` -$ python keystore-dumper.py --help -usage: keystore-dumper.py [-h] [--show-sk SHOW_SIGNING_KEY] KEYSTORE KEYSTORE_PASS -``` -By default, only UUIDs und public keys (verifying keys) will be displayed. Displaying of private keys (signing keys) can be enabled by passing `-s true`. `KEYSTORE` is the path to the keystore file and `KEYSTORE_PASS` is the password needed to open/decrypt it. Below is an example with `-s` being set to `false`. -``` -====================================================================================================================================== -UUID: 292e2f0b-1d9e-407f-9b1a-bda5a9560797 - VK : 2dc6fdf373c7e13abfe1f51ff0fe45417ad713c5fb8df90b76c3077355c4b5e9 - SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ -====================================================================================================================================== -====================================================================================================================================== -UUID: 832e885c-be2a-44b1-ade6-c6c6dd718a3b - VK : 43536e9793ab5b205ba62e614d12104cbb6f906b2a44b79c1cd01e87a724d5b1 - SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ -====================================================================================================================================== -====================================================================================================================================== -UUID: 622e3933-8fdd-446f-b645-d3aa7ed8b638 - VK : cfd32f687366679d7ef73795bafa3bfca43421e092fbc2501f7f4bfe339bb15a - SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ -====================================================================================================================================== -``` -As you can see, the secret key is not printed and `█`s are used as placeholders. +**NOTE** that losing access to the signing key, especially if it is already registered at the uBirch backend, will take away the ability to create and send any new UPPs from that device/UUID, since there is no way of creating a valid signature that would be accepted by the backend. ### Registering a public key To enable the uBirch backend to verify an UPP, it needs to know the corresponding verifying key. Therefore, the device needs to send this key to the backend, before starting to send UPPs, which are supposed to be verified and anchored. Registering a verifying key is done by sending a special kind of UPP containing this key. This can be done by using two scripts: @@ -439,7 +417,7 @@ export UBIRCH_UUID= export UBIRCH_AUTH= export UBIRCH_ENV=[dev|demo|prod] ``` -It uses `test-identity.jsk` as a place to store/look for keypairs. The keystore-password can be read from the [script](test-identity.py) itself. +It uses `test-identity.jks` as a place to store/look for keypairs. The keystore-password can be read from the [script](test-identity.py) itself. ## Test the complete protocol The [`test-protocol.py`](test-protocol.py) script sends a couple of UPPs to uBirch Niomon and verifies the backend response. It reads all information it needs interactively from the terminal. Once entered, all device information (UUID, ENV, AUTH TOKEN) are stored in a file called `demo-device.ini`. Devices keys are stored in `demo-device.jks` and the keystore-password can be read from the [script](test-protocol.py) itself. If no keys for the given UUID are found, the script will generated a keypair and stores it in the keystore file. @@ -452,4 +430,47 @@ The [`test-protocol.py`](test-protocol.py) script sends a couple of UPPs to uBir The [`verify-ecdsa.py`](verify-ecdsa.py) script verifies a hard-coded UPP which was signed with an ECDSA signing key using a ECDSA verifying key. All the information are contained in the script. ## Verify ED25519 signed UPP -The [`verify-25519.py`](verify-25519.py) script verifies a hard-coded UPP which was signed with an ED25519 signing key using a ED25519 verifying key. All the information are contained in the script. This mode is normally used (in all other examples). \ No newline at end of file +The [`verify-25519.py`](verify-25519.py) script verifies a hard-coded UPP which was signed with an ED25519 signing key using a ED25519 verifying key. All the information are contained in the script. This mode is normally used (in all other examples). + +## Managing Keys +### Managing the local KeyStore +`keystore-tool.py` is a script to manipulate contents of JavaKeyStores (JKS) as they are used by other example scripts. It supports displaying Keypair, adding new ones and also deleting entries. +``` +$ python keystore-tool.py --help +usage: keystore-tool.py [-h] KEYSTORE KEYSTORE_PASS {get,put,del} ... +``` +Run `python keystore-tool.py a b get --help` to get a help message for the `get` operation. The first two arguments will be ignored in that case. `get` can be exchanges for `put` or `del` to get information about those operations respectively. One valid invocation of the script might look like this: +``` +$ python keystore-tool.py devices.jks keystore get -u 55425678-1234-bf80-30b4-dcbabf80abcd -s true +``` +It will search for an entry matching the given UUID (specified by `-u`) and print the corresponding KeyPair if found. The PrivateKey will also be shown (`-s true`). + +**Note that once an entry is deleted, it is gone. It is recommended to keep backups of KeyStores containing important keys.** + +### Managing keys inside the uBirch Identity Service +The `pubkey-util.py` script can be used to manually add, delete, revoke or update device keys at the uBirch Identity Service. In most cases this won't be necessary since the other scripts documented above are capable of registering a key, which is enough most of the time. In total, this script supports five operations: +``` +get_dev_keys - Get all PubKeys registered for a given device. This won't include revoked or deleted keys. +get_key_info - Get information about a specific key (basically all information provided when registering it). +put_new_key - Register a new key for a device that has no keys yet, or add a new one if it already has one. +delete_key - Removes a key so that it can't be used/won't be recognized by the backend anymore. +revoke_key - Revokes a key so that it can't be used anymore for sending new UPPs, but is still usable to verify old ones (...). +``` +In general a invocation of the `pubkey-util.py` script will look like this: +``` +$ python pubkey-util.py ENV OPERATION ...PARAMETERS... +``` +Each operation has an own set of sub-parammeters. To see more information about a specific operation run: +``` +$ python pubkey-util.py ENV OPERATION --help +``` +To see a general help message run: +``` +$ python pubkey-util.py --help +``` + +For some operations a date string in a specific format will be needed (a specific case of ISO8601); this command can be used to generate date strings in this format: +``` +$ TZ=UTC date "+%Y-%m-%dT%H:%M:%S.000Z" +2022-02-23T11:11:11.000Z +``` \ No newline at end of file