diff --git a/examples/EXAMPLES.md b/examples/EXAMPLES.md index 15e4563..b8b0095 100644 --- a/examples/EXAMPLES.md +++ b/examples/EXAMPLES.md @@ -79,22 +79,25 @@ usage: keystore-dumper.py [-h] [--show-sk SHOW_SIGNING_KEY] KEYSTORE KEYSTORE_PA 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 +UUID: 8b52204f-74b1-42ae-a2e9-d689237fb88c + VK : 93fbb67804a4cdd0fe3eaa8714d1c9c549358a1d6b8b90b3f4dc036da1c547d3 SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ +TYPE: ED25519 ====================================================================================================================================== ====================================================================================================================================== -UUID: 832e885c-be2a-44b1-ade6-c6c6dd718a3b - VK : 43536e9793ab5b205ba62e614d12104cbb6f906b2a44b79c1cd01e87a724d5b1 +UUID: c8e5026e-5aef-4ad1-a7f0-1111820bf060 + VK : 2f5c23add1894b122781e1333f6bce84cce3417bc6c68c136a5f432373233cc9513d096958724deacbe799c48d0f931fb78c6ac1db02aae3592e82b86e7b60c1 SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ +TYPE: ED25519 ====================================================================================================================================== ====================================================================================================================================== -UUID: 622e3933-8fdd-446f-b645-d3aa7ed8b638 - VK : cfd32f687366679d7ef73795bafa3bfca43421e092fbc2501f7f4bfe339bb15a +UUID: 1d99d8a2-ec8a-46c2-bd6e-1a6aef998d77 + VK : d76cff456d47d5e50105ac331979e68f7628a182161c66377fa64542b55452cb164b0571c0f8662fbb5916921c074c11848d0485685b65ab3b9cf114a34c3658 SK : ████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ +TYPE: ECDSA NIST256p SHA256 ====================================================================================================================================== ``` -As you can see, the secret key is not printed and `█`s are used as placeholders. +As you can see, the secret key is not printed and `█`s are used as placeholders. The last key in the list stands out by being an ECDSA key, identified by the `TYPE`. ### 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: @@ -102,7 +105,7 @@ To enable the uBirch backend to verify an UPP, it needs to know the correspondin upp-creator.py upp-sender.py ``` -Both of these scripts will be explained in more detail in [Creating an UPP](#creating-a-upp) and [Sending an UPP](#sending-a-upp). To generate a _Public Key Registration UPP_ this command can be used: +Both of these scripts will be explained in more detail in [Creating an UPP](#creating-an-upp) and [Sending an UPP](#sending-an-upp). To generate a _Public Key Registration UPP_ this command can be used: ``` $ python upp-creator.py -t 1 --ks devices.jks --kspwd keystore --keyreg true --output keyreg_upp.bin f5ded8a3-d462-41c4-a8dc-af3fd072a217 none @@ -152,7 +155,7 @@ After gathering some measurement data an UPP can be created. The UPP won't conta ``` $ python3 upp-creator.py --help -usage: upp-creator.py [-h] [--version VERISON] [--type TYPE] [--ks KS] [--kspwd KSPWD] [--keyreg KEYREG] [--hash HASH] [--isjson ISJSON] [--output OUTPUT] [--nostdout nostdout] UUID DATA +usage: upp-creator.py [-h] [--version VERISON] [--type TYPE] [--ks KS] [--kspwd KSPWD] [--keyreg KEYREG] [--hash HASH] [--isjson ISJSON] [--output OUTPUT] [--nostdout nostdout] [--ecdsa ECDSA] UUID DATA Note that, when using chained UPPs (--version 0x23), this tool will try to load/save signatures from/to .sig, where UUID will be replaced with the actual UUID. Make sure that the UUID.sig file is in your current working directory if you try to continue an UPP chain using this tool. Also beware that you will only be able to access the contents of a keystore when you use the same password you used when creating it. Otherwise all contents are lost. When --hash off is set, contents of the DATA argument will be copied into the payload field of the UPP. Normally used for special messages (e.g. key registration). For more information on possible values for --type and --version see https://github.com/ubirch/ubirch-protocol. ``` @@ -166,6 +169,7 @@ The script allows multiple modes of operation, which can be set through differen - `--isjson/-j` A binary flag that indicates that the input data is in JSON format. The script will serialize the JSON object before calculating the hash. This has the advantage one doesn't have to remember the order in which fields are listed in a JSON object to still be able to reconstruct the hash later on. Serializing the JSON is done like this: `json.dumps(self.data, separators=(',', ':'), sort_keys=True, ensure_ascii=False)` where `self.data` contains the JSON object which was loaded like this: `self.data = json.loads(self.dataStr)` where `dataStr` contains the input string, which should represent a JSON object. This flag can have two values: `true` or `false`. - `--output/-o` Tells the script where to write the generated UPP to. - `--nostdout/-n` Binary flag to disable printing of any log messages to standard output. This can be used for piping a created UPP to another program. For this `--output /dev/stdout` would have to be set. +- `--ecdsa/-c` Tells the script to generate an ECDSA keypair instead of an ED25519 keypair in case no keypair was found for the specified UUID. The used curve will be NIST256p and the hash algorithm will be SHA256. - `UUID` The UUID of the device as a hex-string, like `f5ded8a3-d462-41c4-a8dc-af3fd072a217`. - `DATA` The data that is going to be hashed. If `--isjson true` is provided, it has to be a string representing a valid JSON object. **Note** that even though this argument will be ignored when `--keyreg true` is set, it must still exist. @@ -212,14 +216,14 @@ To make sure, that the response UPP actually was sent by the uBirch backend, its ``` $ python3 upp-verifier.py --help -usage: upp-verifier.py [-h] [--verifying-key VK] [--verifying-key-uuid UUID] [--input INPUT] +usage: upp-verifier.py [-h] [--verifying-key VK] [--verifying-key-uuid UUID] [--isecd ISECD] [--input INPUT] Note that, when trying to verify an UPP, sent by the uBirch backend (Niomon), a verifying key doesn't have to be provided via the -k option. Instead, this script will try to pick the correct stage key based on the UUID which is contained in the UPP, identifying the creator. If the UUID doesn't match any Niomon stage and no key was specified using -k, an error message will be printed. ``` - `--verifying-key/-k` If not trying to verify an UPP coming from uBirch Niomon but from another source, the verifying key for that source needs to be provided. This parameter expects the key as a hex-string like `b12a906051f102881bbb487ee8264aa05d8d0fcc51218f2a47f562ceb9b0d068`. - `--verifying-key-uuid/-u` The UUID for the verifying key from `--verifying-key`. This parameter will be ignored when `--verifying-key` is not set. Not setting this parameter when `--verifying-key` is set will cause an error. - `--input/-i` The file path to read the UPP from. - +- `--isecd/-c` Tells the script whether the provided key is a ECDSA (NIST256p; SHA256) key - true - or an ED25519 key. ``` $ python3 upp-verifier.py --input response_upp.bin 2021-07-02 15:43:36,273 root read_upp() INFO Reading the input UPP from "response_upp.bin" diff --git a/examples/keystore-dumper.py b/examples/keystore-dumper.py index 3ae90b4..1d98161 100644 --- a/examples/keystore-dumper.py +++ b/examples/keystore-dumper.py @@ -80,7 +80,16 @@ def dump_keystore(self) -> bool: 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(): + for vk_uuid_mod in verifying_keys.keys(): + if vk_uuid_mod.find("_ecd") != -1: + vk_uuid = vk_uuid_mod[:-4] + + ktype = "ECDSA NIST256p SHA256" + else: + vk_uuid = vk_uuid_mod + + ktype = "ED25519" + if self.show_sign == True: t = signing_keys.get("pke_" + vk_uuid) @@ -90,8 +99,9 @@ def dump_keystore(self) -> bool: print("=" * 134) print("UUID: %s" % str(uuid.UUID(hex=vk_uuid))) - print(" VK : %s" % binascii.hexlify(verifying_keys[vk_uuid].cert).decode()) + print(" VK : %s" % binascii.hexlify(verifying_keys[vk_uuid_mod].cert).decode()) print(" SK : %s" % sk) + print("TYPE: %s" % ktype) print("=" * 134) return True diff --git a/examples/upp-creator.py b/examples/upp-creator.py index 2654f7e..ecb65f9 100644 --- a/examples/upp-creator.py +++ b/examples/upp-creator.py @@ -19,7 +19,8 @@ DEFAULT_ISJSON = "False" DEFAULT_OUTPUT = "upp.bin" DEFAULT_NOSTDOUT = "False" -DEFAULT_ISHL = "False" +DEFAULT_ISHL = "False" +DEFAULT_ECDSA = "False" logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) logger = logging.getLogger() @@ -78,6 +79,8 @@ def __init__(self): self.ishl : bool = None self.ishl_str : str = None self.ishash : bool = False + self.ecdsa_str : str = None + self.ecdsa : bool = None self.hasher : object = None self.keystore : ubirch.KeyStore = None @@ -137,6 +140,9 @@ def setup_argparse(self): self.argparser.add_argument("--ishl", "-l", metavar="ISHASHLINK", type=str, default=DEFAULT_ISHL, help="implied --isjson to be true; if set to true, the script will look for a hashlink list in the json object and use it to decide which fields to hash; true or false (default: %s)" % DEFAULT_ISHL ) + self.argparser.add_argument("--ecdsa", "-c", metavar="ECDSA", type=str, default=DEFAULT_ECDSA, + help="if set to true, the script will generate a ECDSA key (NIST256p, SHA256) instead of an ED25519 key in case no key was found for the UUID in the given keystore (default: %s)" % DEFAULT_ECDSA + ) def process_args(self) -> bool: # parse cli arguments (exists on err) @@ -155,6 +161,7 @@ def process_args(self) -> bool: self.output = self.args.output self.nostdout_str = self.args.nostdout self.ishl_str = self.args.ishl + self.ecdsa_str = self.args.ecdsa # get the keyreg value if self.keyreg_str.lower() in ["1", "yes", "y", "true"]: @@ -219,10 +226,16 @@ def process_args(self) -> bool: # check if ishl is true if (self.ishl == True and self.isjson == False): - logger.warning("Overwriting '--isjson false' because --ishl is true") + logger.warning("Overwriting '--isjson false' because '--ishl' is 'true'") self.isjson = True + # get the bool for ishl + if self.ecdsa_str.lower() in ["1", "yes", "y", "true"]: + self.ecdsa = True + else: + self.ecdsa = False + # success return True @@ -235,11 +248,26 @@ def init_keystore(self) -> bool: if self.nostdout == False: logger.info("No keys found for \"%s\" in \"%s\" - generating a keypair" % (self.uuid_str, self.keystore_path)) - self.keystore.create_ed25519_keypair(self.uuid) + if self.ecdsa == True: + logger.info("Generating a ECDSA keypair instead of ED25519!") + + if self.ecdsa == True: + self.keystore.create_ecdsa_keypair(self.uuid) + else: + self.keystore.create_ed25519_keypair(self.uuid) if self.nostdout == False: - logger.info("Public/Verifying key for \"%s\" [base64]: \"%s\"" % - (self.uuid_str, binascii.b2a_base64(self.keystore.find_verifying_key(self.uuid).to_bytes(), newline=False).decode())) + vk = self.keystore.find_verifying_key(self.uuid) + + if type(vk) == ubirch.ubirch_ks.ecdsa.VerifyingKey: + vk_b = vk.to_string() + k_t = "ECDSA" + else: + vk_b = vk.to_bytes() + k_t = "ED25519" + + logger.info("Public/Verifying key for \"%s\" [%s, base64]: \"%s\"" % + (self.uuid_str, k_t, binascii.b2a_base64(vk_b, newline=False).decode())) except Exception as e: logger.exception(e) diff --git a/examples/upp-sender.py b/examples/upp-sender.py index 8d34134..a554f02 100644 --- a/examples/upp-sender.py +++ b/examples/upp-sender.py @@ -12,6 +12,7 @@ DEFAULT_ENV = "dev" DEFAULT_INPUT = "upp.bin" DEFAULT_OUTPUT = "response_upp.bin" +DEFAULT_ISHEX = "False" logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) @@ -30,6 +31,7 @@ def __init__(self): self.input : str = None self.iskeyreg : bool = False + self.ishex : bool = False self.upp : bytes = None self.api : ubirch.API = None @@ -51,6 +53,9 @@ def setup_argparse(self): self.argparser.add_argument("auth", metavar="AUTH", type=str, help="uBirch device authentication token" ) + self.argparser.add_argument("--ishex", "-x", metavar="ISHEX", type=str, default=DEFAULT_ISHEX, + help="if true, the input will be interpreted as a hex-encoded string (default: %s)" % DEFAULT_ISHEX + ) self.argparser.add_argument("--env", "-e", metavar="ENV", type=str, default=DEFAULT_ENV, help="environment to operate in; dev, demo or prod (default: %s)" % DEFAULT_ENV ) @@ -73,6 +78,7 @@ def process_args(self) -> bool: self.env = self.args.env self.input = self.args.input self.output = self.args.output + self.ishex_str = self.args.ishex # process the uuid try: @@ -89,6 +95,12 @@ def process_args(self) -> bool: return False + # get the bool for ishex + if self.ishex_str.lower() in ["1", "yes", "y", "true"]: + self.ishex = True + else: + self.ishex = False + return True def read_upp(self) -> bool: @@ -98,6 +110,10 @@ def read_upp(self) -> bool: with open(self.input, "rb") as fd: self.upp = fd.read() + + # check whether hex decoding is needed + if self.ishex == True: + self.upp = binascii.unhexlify(self.upp) except Exception as e: logger.exception(e) @@ -153,7 +169,8 @@ def send_upp(self) -> bool: logger.info(r.content) else: logger.error("The key resgistration message for \"%s\" was not accepted; code: %d" % (self.uuid_str, r.status_code)) - logger.error(binascii.hexlify(r.content).decode()) + logger.error("Reason: %s" % r.reason) + logger.error("Text: %s" % r.content) else: r = self.api.send(self.uuid, self.upp) diff --git a/examples/upp-verifier.py b/examples/upp-verifier.py index adccb51..0360095 100644 --- a/examples/upp-verifier.py +++ b/examples/upp-verifier.py @@ -5,12 +5,15 @@ import binascii import uuid import ed25519 +import ecdsa +import hashlib import ubirch DEFAULT_INPUT = "/dev/stdin" DEFAULT_ISHEX = "false" +DEFAULT_ISECD = "false" logging.basicConfig(format='%(asctime)s %(name)20.20s %(funcName)20.20s() %(levelname)-8.8s %(message)s', level=logging.INFO) @@ -64,6 +67,8 @@ def __init__(self): self.ishex : bool = None self.ishex_str : str = None + self.isecd : bool = None + self.isecd_str : str = None # initialize the argument parser self.setup_argparse() @@ -87,6 +92,9 @@ def setup_argparse(self): self.argparser.add_argument("--ishex", "-x", metavar="ISHEX", type=str, default=DEFAULT_ISHEX, help="Sets whether the UPP input data is a hex string or binary; e.g. true, false (default: %s)" % DEFAULT_ISHEX ) + self.argparser.add_argument("--isecd", "-c", metavar="ISECD", type=str, default=DEFAULT_ISECD, + help="Sets whether the key provided with -k is a ECDSA NIST256p SHA256 key (true) or a ED25519 key (false) (default: %s)" % DEFAULT_ISECD + ) self.argparser.add_argument("--input", "-i", metavar="INPUT", type=str, default=DEFAULT_INPUT, help="UPP input file path; e.g. upp.bin or /dev/stdin (default: %s)" % DEFAULT_INPUT ) @@ -102,30 +110,7 @@ def process_args(self) -> bool: self.vk_uuid_str = self.args.verifying_key_uuid self.input = self.args.input self.ishex_str = self.args.ishex - - # check if a verifying key was supplied - if self.vk_str != "AUTO": - try: - # convert the string - self.vk = ed25519.VerifyingKey(self.vk_str, encoding="hex") - except Exception as e: - logger.error("Invalid verifying key supplied via --verifying-key/-k: \"%s\"" % self.vk_str) - logger.exception(e) - - return False - - if self.vk_uuid_str == "EMPTY": - logger.error("--verifying-key-uuid/-u must be specified when --verifying-key/-k is specified!") - - return False - - try: - self.vk_uuid = uuid.UUID(hex=self.vk_uuid_str) - except Exception as e: - logger.error("Invalid UUID supplied via --verifying-key-uuid/-u: \"%s\"" % self.vk_uuid_str) - logger.exception(e) - - return False + self.isecd_str = self.args.isecd # get the ishex value if self.ishex_str.lower() in ["1", "yes", "y", "true"]: @@ -133,6 +118,12 @@ def process_args(self) -> bool: else: self.ishex = False + # get the isecd value + if self.isecd_str.lower() in ["1", "yes", "y", "true"]: + self.isecd = True + else: + self.isecd = False + return True def read_upp(self) -> bool: @@ -164,15 +155,43 @@ def init_keystore(self) -> bool: return True def check_cli_vk(self) -> bool: - try: - if self.vk != None: - self.keystore.insert_ed25519_verifying_key(self.vk_uuid, self.vk) + # check if a verifying key was supplied + if self.vk_str != "AUTO": + # check if a uuid was supplied + if self.vk_uuid_str == "EMPTY": + logger.error("--verifying-key-uuid/-u must be specified when --verifying-key/-k is specified!") - logger.info("Inserted \"%s\": \"%s\" (UUID/VK) into the keystore" % (self.vk_uuid_str, self.vk_str)) - except Exception as e: - logger.exception(e) + return False + + # load the uuid + try: + self.vk_uuid = uuid.UUID(hex=self.vk_uuid_str) + except Exception as e: + logger.error("Invalid UUID supplied via --verifying-key-uuid/-u: \"%s\"" % self.vk_uuid_str) + logger.exception(e) + + return False + + # check the keytype, load it and insert it into the keystore + try: + if self.isecd == False: + logger.info("Loading the key as ED25519 verifying key") + + self.vk = ed25519.VerifyingKey(self.vk_str, encoding="hex") + self.keystore.insert_ed25519_verifying_key(self.vk_uuid, self.vk) + else: + logger.info("Loading the key as ECDSA NIST256p SHA256 verifying key") + + self.vk = ecdsa.VerifyingKey.from_string(binascii.unhexlify(self.vk_str), curve=ecdsa.NIST256p, hashfunc=hashlib.sha256) + self.keystore.insert_ecdsa_verifying_key(self.vk_uuid, self.vk) + except Exception as e: + logger.error("Error loading the verifying key and inserting it into the keystore") + logger.exception(e) + + return False + + logger.info("Inserted \"%s\": \"%s\" (UUID/VK) into the keystore" % (self.vk_uuid_str, self.vk_str)) - return False return True diff --git a/ubirch/ubirch_ks.py b/ubirch/ubirch_ks.py index 83341ab..ccd1d4a 100644 --- a/ubirch/ubirch_ks.py +++ b/ubirch/ubirch_ks.py @@ -15,30 +15,38 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import hashlib from datetime import datetime, timedelta from logging import getLogger from os import urandom from uuid import UUID +import ecdsa import ed25519 -from ed25519 import SigningKey, VerifyingKey from jks import jks, AlgorithmIdentifier, rfc5208, TrustedCertEntry from pyasn1.codec.ber import encoder logger = getLogger(__name__) -ECC_ENCRYPTION_OID = (1, 2, 1, 3, 101, 112) +EDDSA_OID = (1, 2, 1, 3, 101, 112) +ECDSA_OID = (1, 2, 840, 10045, 4, 3, 2) class ED25519Certificate(TrustedCertEntry): - def __init__(self, alias: str, verifying_key: VerifyingKey, **kwargs): + def __init__(self, alias: str, verifying_key: ed25519.VerifyingKey, **kwargs): super().__init__(**kwargs) self.alias = alias self.cert = verifying_key.to_bytes() self.timestamp = int(datetime.utcnow().timestamp()) +class ECDSACertificate(TrustedCertEntry): + + def __init__(self, alias: str, verifying_key: ecdsa.VerifyingKey, **kwargs): + super().__init__(**kwargs) + self.alias = alias + self.cert = verifying_key.to_string() + self.timestamp = int(datetime.utcnow().timestamp()) class KeyStore(object): """ @@ -60,24 +68,25 @@ def _load_keys(self) -> None: logger.warning("creating new key store: {}".format(self._ks_file)) self._ks = jks.KeyStore.new("jks", []) - def insert_ed25519_signing_key(self, uuid: UUID, sk: SigningKey): + def insert_ed25519_signing_key(self, uuid: UUID, sk: ed25519.SigningKey): """Store an existing ED25519 signing key in the key store.""" # encode the ED25519 private key as PKCS#8 private_key_info = rfc5208.PrivateKeyInfo() private_key_info.setComponentByName('version', 'v1') a = AlgorithmIdentifier() - a.setComponentByName('algorithm', ECC_ENCRYPTION_OID) + a.setComponentByName('algorithm', EDDSA_OID) private_key_info.setComponentByName('privateKeyAlgorithm', a) private_key_info.setComponentByName('privateKey', sk.to_bytes()) pkey_pkcs8 = encoder.encode(private_key_info) pke = jks.PrivateKeyEntry.new(alias=str(uuid.hex), certs=[], key=pkey_pkcs8) self._ks.entries['pke_' + uuid.hex] = pke - def insert_ed25519_verifying_key(self, uuid: UUID, vk: VerifyingKey): + def insert_ed25519_verifying_key(self, uuid: UUID, vk: ed25519.VerifyingKey): """Store an existing ED25519 verifying key in the key store.""" self._ks.entries[uuid.hex] = ED25519Certificate(uuid.hex, vk) - def insert_ed25519_keypair(self, uuid: UUID, vk: VerifyingKey, sk: SigningKey) -> (VerifyingKey, SigningKey): + def insert_ed25519_keypair(self, uuid: UUID, vk: ed25519.VerifyingKey, sk: ed25519.SigningKey) -> ( + ed25519.VerifyingKey, ed25519.SigningKey): """Store an existing ED25519 key pair in the key store.""" if uuid.hex in self._ks.entries or uuid.hex in self._ks.certs: raise Exception("uuid '{}' already exists in keystore".format(uuid.hex)) @@ -88,46 +97,152 @@ def insert_ed25519_keypair(self, uuid: UUID, vk: VerifyingKey, sk: SigningKey) - logger.info("inserted new key pair for {}: {}".format(uuid.hex, bytes.decode(vk.to_ascii(encoding='hex')))) return vk, sk - def create_ed25519_keypair(self, uuid: UUID) -> (VerifyingKey, SigningKey): + def create_ed25519_keypair(self, uuid: UUID) -> (ed25519.VerifyingKey, ed25519.SigningKey): """Create a new ED25519 key pair and store in key store.""" sk, vk = ed25519.create_keypair(entropy=urandom) return self.insert_ed25519_keypair(uuid, vk, sk) + def insert_ecdsa_signing_key(self, uuid, sk: ecdsa.SigningKey): + """Insert an existing ECDSA signing key.""" + # encode the ECDSA private key as PKCS#8 + private_key_info = rfc5208.PrivateKeyInfo() + private_key_info.setComponentByName('version', 'v1') + a = AlgorithmIdentifier() + a.setComponentByName('algorithm', ECDSA_OID) + private_key_info.setComponentByName('privateKeyAlgorithm', a) + private_key_info.setComponentByName('privateKey', sk.to_string()) + pkey_pkcs8 = encoder.encode(private_key_info) + pke = jks.PrivateKeyEntry.new(alias=str(uuid.hex), certs=[], key=pkey_pkcs8) + self._ks.entries['pke_' + uuid.hex] = pke + + def insert_ecdsa_verifying_key(self, uuid, vk: ecdsa.VerifyingKey): + # store verifying key in certificate store + # ecdsa VKs are marked with a "_ecd" suffix + self._ks.entries[uuid.hex + '_ecd'] = ECDSACertificate(uuid.hex, vk) + + def insert_ecdsa_keypair(self, uuid: UUID, vk: ecdsa.VerifyingKey, sk: ecdsa.SigningKey) -> (ecdsa.VerifyingKey, ecdsa.SigningKey): + """Insert an existing ECDSA key pair into the key store.""" + if uuid.hex in self._ks.entries or uuid.hex in self._ks.certs: + raise Exception("uuid '{}' already exists in keystore".format(uuid.hex)) + + self.insert_ecdsa_verifying_key(uuid, vk) + self.insert_ecdsa_signing_key(uuid, sk) + self._ks.save(self._ks_file, self._ks_password) + #logger.info("inserted new key pair for {}: {}".format(uuid.hex, vk.to_string().decode())) + return (vk, sk) + + def create_ecdsa_keypair(self, uuid: UUID, curve: ecdsa.curves.Curve = ecdsa.NIST256p, hashfunc=hashlib.sha256) -> (ecdsa.VerifyingKey, ecdsa.SigningKey): + """Create new ECDSA key pair and store in key store""" + + sk = ecdsa.SigningKey.generate(curve=curve, entropy=urandom, hashfunc=hashfunc) + vk = sk.get_verifying_key() + return self.insert_ecdsa_keypair(uuid, vk, sk) + def exists_signing_key(self, uuid: UUID): """Check whether this UUID has a signing key in the key store.""" return 'pke_' + uuid.hex in self._ks.private_keys def exists_verifying_key(self, uuid: UUID): """Check whether this UUID has a verifying key in the key store.""" - return uuid.hex in self._ks.certs + return uuid.hex in self._ks.certs or (uuid.hex + '_ecd') in self._ks.certs - def find_signing_key(self, uuid: UUID) -> SigningKey: + def find_signing_key(self, uuid: UUID) -> ed25519.SigningKey or ecdsa.SigningKey: """Find the signing key for this UUID.""" - sk = self._ks.private_keys['pke_' + uuid.hex] - return SigningKey(sk.pkey) - - def find_verifying_key(self, uuid: UUID) -> VerifyingKey: + # try to find a matching sk for the uuid + try: + sk : PrivateKeyEntry = self._ks.private_keys['pke_' + uuid.hex] + except KeyError as e: + # there is no sk for the given uuid + return None + + # check whether the entry is encrypted + if sk.is_decrypted() == False: + sk.decrypt(self._ks_password) + + # check the _OID to identify the key type + if sk._algorithm_oid == EDDSA_OID: + return ed25519.SigningKey(sk.pkey) + elif sk._algorithm_oid == ECDSA_OID: + # ==================================== IMPORTANT ==================================== + # The used curve as well as the used hash function have to be explicitly set here + # to match the ones used in create_ecdsa_keypair(), otherwise the ._from_string() + # function will throw exceptions because of unexpected/wrong keystr lengths (...) + # =================================================================================== + return ecdsa.SigningKey.from_string(sk.pkey, curve=ecdsa.NIST256p, hashfunc=hashlib.sha256) + else: + raise Exception("stored key with unknown algorithm OID: '{}'".format(sk._algorithm_oid)) + + def _find_cert(self, uuid: UUID) -> ECDSACertificate or ED25519Certificate: + """ Find the stored cert for uuid """ + cert = None + + if self.exists_verifying_key(uuid) == True: + # try to get the edd key first + try: + cert = ED25519Certificate( + self._ks.certs[uuid.hex].alias, + + # the ED25519Certificate requires an ed25519.VerifyingKey + ed25519.VerifyingKey(self._ks.certs[uuid.hex].cert) + ) + except KeyError: + pass + + # no edd key found, try to get ecd + try: + cert = ECDSACertificate( + self._ks.certs[uuid.hex + '_ecd'].alias, + + # the ECDSACertifcate requires an ecdsa.VerifyingKey + ecdsa.VerifyingKey.from_string( + self._ks.certs[uuid.hex + '_ecd'].cert, + hashfunc=hashlib.sha256, curve=ecdsa.NIST256p + ) + ) + except KeyError: + pass + + return cert + + def find_verifying_key(self, uuid: UUID) -> ed25519.VerifyingKey or ecdsa.VerifyingKey: """Find the verifying key for this UUID.""" - cert = self._ks.certs[uuid.hex] - return VerifyingKey(cert.cert) + cert = self._find_cert(uuid) + + if type(cert) == ED25519Certificate: + return ed25519.VerifyingKey(cert.cert) + elif type(cert) == ECDSACertificate: + return ecdsa.VerifyingKey.from_string(cert.cert, curve=ecdsa.NIST256p, hashfunc=hashlib.sha256) + + return None def get_certificate(self, uuid: UUID) -> dict or None: """Get the public key info for key registration""" - if uuid.hex not in self._ks.certs: + # try to find the cert + cert = self._find_cert(uuid) + + if cert == None: return None - cert = self._ks.certs[uuid.hex] - vk = VerifyingKey(cert.cert) + # set the timestamps (validity = +10 years) + # TODO set propper validity timestamp created = datetime.fromtimestamp(cert.timestamp) not_before = datetime.fromtimestamp(cert.timestamp) - # TODO fix handling of key validity - not_after = created + timedelta(days=365) + not_after = created + timedelta(days=3650) + + # set the alogrithm + if type(cert) == ED25519Certificate: + algo = 'ECC_ED25519' + elif type(cert) == ECDSACertificate: + algo = 'ecdsa-p256v1' + else: + raise Exception("Unexpected certificate class %s" % str(vk.__class__)) + return { - "algorithm": 'ECC_ED25519', + "algorithm": algo, "created": int(created.timestamp()), "hwDeviceId": uuid.bytes, - "pubKey": vk.to_bytes(), - "pubKeyId": vk.to_bytes(), + "pubKey": bytes(cert.cert), + "pubKeyId": bytes(cert.cert), "validNotAfter": int(not_after.timestamp()), "validNotBefore": int(not_before.timestamp()) }