From a10a3b780a7f057fce55e80588cd3d6f5ab30768 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Wed, 5 Mar 2025 12:50:58 +0100 Subject: [PATCH 1/8] code reuse for expected key sizes --- src/kyber_py/ml_kem/ml_kem.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/kyber_py/ml_kem/ml_kem.py b/src/kyber_py/ml_kem/ml_kem.py index c27c14a..248960a 100644 --- a/src/kyber_py/ml_kem/ml_kem.py +++ b/src/kyber_py/ml_kem/ml_kem.py @@ -30,6 +30,22 @@ def __init__(self, params): # use the method `set_drbg_seed()` self.random_bytes = os.urandom + def _ek_size(self): + """ + Return the size of the encapsulation key for the selected paramters. + + :rtype: int + """ + return 384 * self.k + 32 + + def _dk_size(self): + """ + Return the size of the decapsulation key for the selected parameters. + + :rtype: int + """ + return 768 * self.k + 96 + def set_drbg_seed(self, seed): """ Change entropy source to a DRBG and seed it with provided value. @@ -201,9 +217,9 @@ def _k_pke_encrypt(self, ek_pke, m, r): either fails. """ # First check if the encap key has the right length - if len(ek_pke) != 384 * self.k + 32: + if len(ek_pke) != self._ek_size(): raise ValueError( - f"Type check failed, ek_pke has the wrong length, expected {384 * self.k + 32} bytes and received {len(ek_pke)}" + f"Type check failed, ek_pke has the wrong length, expected {self._ek_size()} bytes and received {len(ek_pke)}" ) # Unpack ek @@ -379,9 +395,9 @@ def _decaps_internal(self, dk, c): raise ValueError( f"ciphertext type check failed. Expected {32 * (self.du * self.k + self.dv)} bytes and obtained {len(c)}" ) - if len(dk) != 768 * self.k + 96: + if len(dk) != self._dk_size(): raise ValueError( - f"decapsulation type check failed. Expected {768 * self.k + 96} bytes and obtained {len(dk)}" + f"decapsulation type check failed. Expected {self._dk_size()} bytes and obtained {len(dk)}" ) # Parse out data from dk From b7c348eea6e6929cbca70f3d0c8847c896a90041 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Wed, 5 Mar 2025 12:51:58 +0100 Subject: [PATCH 2/8] add NIST OIDs to KEM parameters --- src/kyber_py/ml_kem/default_parameters.py | 15 ++++++++++++--- src/kyber_py/ml_kem/ml_kem.py | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/kyber_py/ml_kem/default_parameters.py b/src/kyber_py/ml_kem/default_parameters.py index 7db9636..06e2ea4 100644 --- a/src/kyber_py/ml_kem/default_parameters.py +++ b/src/kyber_py/ml_kem/default_parameters.py @@ -11,9 +11,18 @@ # we should maybe put these into the class and only allow a user to # select 128, 192 or 256 bit security. DEFAULT_PARAMETERS = { - "ML512": {"k": 2, "eta_1": 3, "eta_2": 2, "du": 10, "dv": 4}, - "ML768": {"k": 3, "eta_1": 2, "eta_2": 2, "du": 10, "dv": 4}, - "ML1024": {"k": 4, "eta_1": 2, "eta_2": 2, "du": 11, "dv": 5}, + "ML512": { + "k": 2, "eta_1": 3, "eta_2": 2, "du": 10, "dv": 4, + "oid": (2, 16, 840, 1, 101, 3, 4, 4, 1), + }, + "ML768": { + "k": 3, "eta_1": 2, "eta_2": 2, "du": 10, "dv": 4, + "oid": (2, 16, 840, 1, 101, 3, 4, 4, 2), + }, + "ML1024": { + "k": 4, "eta_1": 2, "eta_2": 2, "du": 11, "dv": 5, + "oid": (2, 16, 840, 1, 101, 3, 4, 4, 3), + }, } """Parameters for the :py:obj:`.ML_KEM` objects.""" diff --git a/src/kyber_py/ml_kem/ml_kem.py b/src/kyber_py/ml_kem/ml_kem.py index 248960a..7bf1573 100644 --- a/src/kyber_py/ml_kem/ml_kem.py +++ b/src/kyber_py/ml_kem/ml_kem.py @@ -25,6 +25,7 @@ def __init__(self, params): self.M = ModuleKyber() self.R = self.M.ring + self.oid = params["oid"] if "oid" in params else None # Use system randomness by default, for deterministic randomness # use the method `set_drbg_seed()` From 8faec371da479f6cce789bd8edd640f401989cd9 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Wed, 5 Mar 2025 18:12:05 +0100 Subject: [PATCH 3/8] support for reading and writing files with keys --- pyproject.toml | 5 + src/kyber_py/ml_kem/default_parameters.py | 18 +- src/kyber_py/ml_kem/pkcs.py | 281 ++++++++++++++++++++++ 3 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 src/kyber_py/ml_kem/pkcs.py diff --git a/pyproject.toml b/pyproject.toml index 447fc96..3ffe916 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,11 @@ classifiers = [ ] license = "MIT" +[project.optional-dependencies] +pkcs = [ + "ecdsa", +] + [project.urls] Homepage = "https://github.com/GiacomoPope/kyber-py" Issues = "https://github.com/GiacomoPope/kyber-py/issues" diff --git a/src/kyber_py/ml_kem/default_parameters.py b/src/kyber_py/ml_kem/default_parameters.py index 06e2ea4..24f6be1 100644 --- a/src/kyber_py/ml_kem/default_parameters.py +++ b/src/kyber_py/ml_kem/default_parameters.py @@ -12,15 +12,27 @@ # select 128, 192 or 256 bit security. DEFAULT_PARAMETERS = { "ML512": { - "k": 2, "eta_1": 3, "eta_2": 2, "du": 10, "dv": 4, + "k": 2, + "eta_1": 3, + "eta_2": 2, + "du": 10, + "dv": 4, "oid": (2, 16, 840, 1, 101, 3, 4, 4, 1), }, "ML768": { - "k": 3, "eta_1": 2, "eta_2": 2, "du": 10, "dv": 4, + "k": 3, + "eta_1": 2, + "eta_2": 2, + "du": 10, + "dv": 4, "oid": (2, 16, 840, 1, 101, 3, 4, 4, 2), }, "ML1024": { - "k": 4, "eta_1": 2, "eta_2": 2, "du": 11, "dv": 5, + "k": 4, + "eta_1": 2, + "eta_2": 2, + "du": 11, + "dv": 5, "oid": (2, 16, 840, 1, 101, 3, 4, 4, 3), }, } diff --git a/src/kyber_py/ml_kem/pkcs.py b/src/kyber_py/ml_kem/pkcs.py new file mode 100644 index 0000000..a8909fc --- /dev/null +++ b/src/kyber_py/ml_kem/pkcs.py @@ -0,0 +1,281 @@ +""" +Import and export of keys to PKCS #8 and SPKI format. + +This module allows for importing private keys (decapsulation keys) from +PKCS #8 files, exporting them to PKCS #8 files, and both import and export +of the public keys (encapsulation keys) from the Subject Public Key Info +format used in X.509 certificates and in bare public keys. +""" + +from ecdsa import der +from .default_parameters import ML_KEM_512, ML_KEM_768, ML_KEM_1024 + + +OIDS = { + ML_KEM_512.oid: ML_KEM_512, + ML_KEM_768.oid: ML_KEM_768, + ML_KEM_1024.oid: ML_KEM_1024, +} + + +def ek_to_der(kem, ek): + """ + Convert an encapsulation key to a SPKI DER structure. + + :param kem: an ``ML_KEM`` object instance + :param bytes ek: encapsulation key + :rtype: bytes + """ + if not kem.oid: + raise ValueError("Only KEMs with specified OIDs can be encoded") + + if len(ek) != kem._ek_size(): + raise ValueError(f"Provided key size doesn't match the provided kem") + + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*kem.oid), + ), + der.encode_bitstring(ek, 0), + ) + + return enc + + +def ek_to_pem(kem, ek): + """ + Convert an encapsulation key to a SPKI PEM structure. + + :param kem: an ``ML_KEM`` object instance + :param bytes ek: encapsulation key + :rtype: str + """ + der_enc = ek_to_der(kem, ek) + + pem_enc = der.topem(der_enc, "PUBLIC KEY") + + return pem_enc + + +def ek_from_der(enc_key): + """ + Extract an encapsulation key from DER encoding. + + :param bytes enc_key: SPKI DER encoding of a key + :rtype: tuple(ML_KEM, bytes) + """ + s1, empty = der.remove_sequence(enc_key) + if empty: + raise der.UnexpectedDER("Trailing junk after DER public key") + + alg_id, rem = der.remove_sequence(s1) + + alg_id, rest = der.remove_object(alg_id) + if alg_id not in OIDS: + raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}") + if rest: + raise der.UnexpectedDER("Parameters specified for ML-KEM OID") + + kem = OIDS[alg_id] + + key, empty = der.remove_bitstring(rem, 0) + if empty: + raise der.UnexpectedDER("Trailing junk after public key bitsting") + + if len(key) != kem._ek_size(): + raise der.UnexpectedDER("Wrong key size for the OID in structure") + + return kem, key + + +def ek_from_pem(enc_key): + """ + Extract an encapsulation key from PEM encoding. + + :param str enc_key: SPKI PEM encoding of a key + :rtype: tuple(ML_KEM, bytes) + """ + der_key = der.unpem(enc_key) + return ek_from_der(der_key) + + +def dk_to_der(kem, dk=None, seed=None, form=None): + """ + Convert the decapsulation key to a PKCS #8 DER structure. + + ``ek``, ``seed``, or both need to be provided. + If ``form`` is not specified (is set to ``None``), the format that + preserves maximum amount of information will be used. + + Proposed in + https://mailarchive.ietf.org/arch/msg/spasm/50v8oLi5XObC7AIL4DH337_Anos/ + for the draft-ietf-lamps-kyber-certificates + + :param kem: an ``ML_KEM`` object instance + :param bytes dk: decapsulation key + :param bytes seed: seed to generate ML-KEM keys + :param str form: What format to write the key in, options are: + None - for automatic selection based on ``dk`` and ``seed``, + ``seed`` for writing seed only, ``expanded`` for writing + the expanded key only, ``both`` for writing both key and expanded key. + :rtype: bytes + """ + if form not in ("seed", "expanded", "both", None): + raise ValueError( + f"Invalid form specified: {form}. " + "Only 'seed', 'expanded', 'both', and None are allowed" + ) + if not dk and not seed: + raise ValueError("dk or seed must be provided") + + if dk and len(dk) != kem._dk_size(): + raise ValueError("Invalid decapsulation key size for the provided KEM") + + if seed and len(seed) != 64: + raise ValueError("Invalid seed size") + + if form in ("both", "seed") and not seed: + raise ValueError(f'Format "{form}" requires specifing seed') + + if form is None: + if dk and seed: + form = "both" + elif dk: + form = "expanded" + else: + assert seed + form = "seed" + + if form in ("both", "seed") and not dk: + _, dk = kem.key_derive(seed) + + if form == "seed": + enc_key = der.encode_implicit(0, seed) + elif form == "expanded": + enc_key = der.encode_octet_string(dk) + else: + assert form == "both" + enc_key = der.encode_sequence( + der.encode_octet_string(seed), der.encode_octet_string(dk) + ) + + encoded_pkcs8 = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence(der.encode_oid(*kem.oid)), + der.encode_octet_string(enc_key), + ) + + return encoded_pkcs8 + + +def dk_to_pem(kem, dk=None, seed=None, form=None): + """ + Convert the decapsulation key to a PKCS #8 DER structure. + + ``ek``, ``seed``, or both need to be provided. + If ``form`` is not specified (is set to ``None``), the format that + preserves maximum amount of information will be used. + + Proposed in + https://mailarchive.ietf.org/arch/msg/spasm/50v8oLi5XObC7AIL4DH337_Anos/ + for the draft-ietf-lamps-kyber-certificates + + :param kem: an ``ML_KEM`` object instance + :param bytes dk: decapsulation key + :param bytes seed: seed to generate ML-KEM keys + :param str form: What format to write the key in, options are: + None - for automatic selection based on ``dk`` and ``seed``, + ``seed`` for writing seed only, ``expanded`` for writing + the expanded key only, ``both`` for writing both key and expanded key. + :rtype: bytes + """ + der_enc = dk_to_der(kem, dk, seed, form) + + pem_enc = der.topem(der_enc, "PRIVATE KEY") + + return pem_enc + + +def dk_from_der(enc_key): + """ + Extract encapsulation and decapsulation keys from PKCS #8 DER encoding. + + :param bytes enc_key: PKCS #8 DER encoding of the key + :return: the first element returned is the ``ML_KEM`` object instance, + second element is the decapsulation key, third is the seed (if present), + and fourth is the encapsulation key. + :rtype: tuple(ML_KEM, bytes, bytes, bytes) + """ + s1, empty = der.remove_sequence(enc_key) + if empty: + raise der.UnexpectedDER("Trailing junk after private key structure") + + ver, rest = der.remove_integer(s1) + + if ver != 0: + raise der.UnexpectedDER(f"Unsupported version: {ver}") + + alg_id, rest = der.remove_sequence(rest) + + alg_id, empty = der.remove_object(alg_id) + if alg_id not in OIDS: + raise der.UnexpectedDER(f"Not recognised algorithm OID: {alg_id}") + if empty: + raise der.UnexpectedDER("Junk after algorithm OID") + + kem = OIDS[alg_id] + + priv_key, _ = der.remove_octet_string(rest) + # "rest" here can be either parameters of public key: we ignore those + + seed = None + expanded = None + ek = None + + if der.str_idx_as_int(priv_key, 0) == 0x04: + # we have OCTET STRING: expanded only format + expanded, empty = der.remove_octet_string(priv_key) + if empty: + raise der.UnexpectedDER("Junk after expandedKey") + elif der.is_sequence(priv_key): + both, empty = der.remove_sequence(priv_key) + if empty: + raise der.UnexpectedDER("Junk after both encoding") + seed, key_val = der.remove_octet_string(both) + expanded, empty = der.remove_octet_string(key_val) + if empty: + raise der.UnexpectedDER("Junk after 'expandedKey' in 'both' value") + else: + tag, seed, empty = der.remove_implicit(priv_key) + if tag != 0: + raise der.UnexpectedDER(f"Unexpected tag in private key encoding") + if empty: + raise der.UnexpectedDER("Junk after seed encoding") + + if expanded and len(expanded) != kem._dk_size(): + raise der.UnexpectedDER("Invalid expanded key size in encoding") + + if not expanded: + ek, expanded = kem.key_derive(seed) + + if seed and len(seed) != 64: + raise der.UnexpectedDER("Invalid length of seed in encoding") + + if not ek: + ek = expanded[384 * kem.k : 768 * kem.k + 32] + + return kem, expanded, seed, ek + + +def dk_from_pem(enc_key): + """ + Extract encapsulation and decapsulation keys from PKCS #8 PEM encoding. + + :param str enc_key: PKCS #8 PEM encoding of the key + :return: the first element returned is the ``ML_KEM`` object instance, + second element is the decapsulation key, third is the seed (if present), + and fourth is the encapsulation key. + :rtype: tuple(ML_KEM, bytes, bytes, bytes) + """ + der_key = der.unpem(enc_key) + return dk_from_der(der_key) From b44b417d1aa1f1f2d580d5f6cdfc59593f20ce17 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 6 Mar 2025 19:43:09 +0100 Subject: [PATCH 4/8] minimal documentation --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 7bade98..676fc23 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,11 @@ However, I have not implemented AES itself, instead I import this from `pycrypto To install dependencies, run `pip -r install requirements`. +The support for reading and writing the keys into PKCS formats requires +the python-ecdsa package. It can be installed using `pip install ecdsa`, +or by installing this package with the `pip install 'kyber-py[pkcs]'` +command. + ## Using kyber-py ### ML-KEM @@ -100,6 +105,18 @@ use: - `ML_KEM.encaps(ek)`: generate a key and ciphertext pair `(key, ct)` - `ML_KEM.decaps(dk, ct)`: generate the shared key `key` +Additionally there are few methods for reading and writing both types of keys +in the `ml_kem.pkcs` module: + +- `ek_to_der`: for serialising the encapsulation key to a DER byte string +- `ek_to_pem`: for serialising the encapsulation key to a PEM string +- `ek_from_der`: for extracting the encapsulation key from a DER encoding +- `ek_from_pem`: for extracting the encapsulation key from a PEM encoding +- `dk_to_der`: for serialising the decapsulation key to a DER byte string +- `dk_to_pem`: for serialising the decapsulation key to a PEM string +- `dk_from_der`: for extracting the decapsulation key from a DER encoding +- `dk_from_pem`: for extracting the decapsulation key from a PEM encoding + Those, together with the `ML_KEM_512`, `ML_KEM_768`, and `ML_KEM_1024` objects comprise the kyber-py library stable API. From 6a73692e967e4afce49b26714c762664bb85b7eb Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 13 Mar 2025 14:23:38 +0100 Subject: [PATCH 5/8] test import-export with official vectors --- pyproject.toml | 2 +- src/kyber_py/ml_kem/pkcs.py | 6 +- tests/test_pkcs.py | 617 ++++++++++++++++++++++++++++++++++++ 3 files changed, 623 insertions(+), 2 deletions(-) create mode 100644 tests/test_pkcs.py diff --git a/pyproject.toml b/pyproject.toml index 3ffe916..1285871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ license = "MIT" [project.optional-dependencies] pkcs = [ - "ecdsa", + "ecdsa>=0.19.1", ] [project.urls] diff --git a/src/kyber_py/ml_kem/pkcs.py b/src/kyber_py/ml_kem/pkcs.py index a8909fc..a6e23a5 100644 --- a/src/kyber_py/ml_kem/pkcs.py +++ b/src/kyber_py/ml_kem/pkcs.py @@ -7,7 +7,11 @@ format used in X.509 certificates and in bare public keys. """ -from ecdsa import der +try: + from ecdsa import der +except ImportError: + raise ImportError("PKCS functionality requires the ecdsa library") + from .default_parameters import ML_KEM_512, ML_KEM_768, ML_KEM_1024 diff --git a/tests/test_pkcs.py b/tests/test_pkcs.py new file mode 100644 index 0000000..3dce284 --- /dev/null +++ b/tests/test_pkcs.py @@ -0,0 +1,617 @@ +import unittest +import copy + +from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024 + +try: + from ecdsa.der import unpem + from kyber_py.ml_kem.pkcs import ( + ek_to_pem, + ek_from_pem, + dk_to_pem, + dk_from_pem, + ) + + ECDSA_PRESENT = True +except ImportError: + ECDSA_PRESENT = False + + +class BaseTestSetup(object): + def test_export_ek_to_pem(self): + pem = ek_to_pem(self.kem, self.ek) + + self.assertEqual(unpem(pem), unpem(self.ek_pem)) + + def test_export_ek_to_pem_with_too_long_value(self): + with self.assertRaises(ValueError) as e: + ek_to_pem(self.kem, self.ek + b"X") + + self.assertIn("Provided key size", str(e.exception)) + + def test_export_with_oid_missing(self): + kem = copy.copy(self.kem) + kem.oid = None + + with self.assertRaises(ValueError) as e: + ek_to_pem(kem, self.ek) + + self.assertIn("Only KEMs with specified OIDs", str(e.exception)) + + def test_import_ek_from_pem(self): + kem, ek = ek_from_pem(self.ek_pem) + + self.assertIs(kem, self.kem) + self.assertEqual(ek, self.ek) + + def test_export_dk_to_pem_seed_only(self): + pem = dk_to_pem(self.kem, self.dk, self.seed, form="seed") + + self.assertEqual(unpem(pem), unpem(self.dk_seed_pem)) + + def test_export_dk_to_pem_priv_only(self): + pem = dk_to_pem(self.kem, self.dk, self.seed, form="expanded") + + self.assertEqual(unpem(pem), unpem(self.dk_priv_pem)) + + def test_export_dk_to_pem_both(self): + pem = dk_to_pem(self.kem, self.dk, self.seed, form="both") + + self.assertEqual(unpem(pem), unpem(self.dk_seed_priv_pem)) + + def test_export_dk_to_pem_seed_only_auto(self): + pem = dk_to_pem(self.kem, seed=self.seed) + + self.assertEqual(unpem(pem), unpem(self.dk_seed_pem)) + + def test_export_dk_to_pem_priv_only_auto(self): + pem = dk_to_pem(self.kem, dk=self.dk) + + self.assertEqual(unpem(pem), unpem(self.dk_priv_pem)) + + def test_export_dk_to_pem_both_auto(self): + pem = dk_to_pem(self.kem, self.dk, self.seed) + + self.assertEqual(unpem(pem), unpem(self.dk_seed_priv_pem)) + + def test_export_dk_to_pem_derive_expanded(self): + pem = dk_to_pem(self.kem, seed=self.seed, form="both") + + self.assertEqual(unpem(pem), unpem(self.dk_seed_priv_pem)) + + def test_export_dk_to_seed_with_seed_missing(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, dk=self.dk, form="seed") + + self.assertIn("requires specifing seed", str(e.exception)) + + def test_export_dk_to_both_with_seed_missing(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, dk=self.dk, form="both") + + self.assertIn("requires specifing seed", str(e.exception)) + + def test_export_with_no_keys_specified(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, form="both") + + self.assertIn("dk or seed must be provided", str(e.exception)) + + def test_export_with_wrong_form_specified(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, self.dk, self.seed, form="foobar") + + self.assertIn("Invalid form", str(e.exception)) + + def test_export_with_too_long_dk(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, self.dk + b"X", self.seed) + + self.assertIn("Invalid decapsulation key size", str(e.exception)) + + def test_export_with_too_short_dk(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, self.dk[:-1], self.seed) + + self.assertIn("Invalid decapsulation key size", str(e.exception)) + + def test_export_with_too_long_seed(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, self.dk, self.seed + b"X") + + self.assertIn("Invalid seed size", str(e.exception)) + + def test_export_with_too_short_seed(self): + with self.assertRaises(ValueError) as e: + dk_to_pem(self.kem, self.dk, self.seed[:-1]) + + self.assertIn("Invalid seed size", str(e.exception)) + + def test_import_from_seed_only(self): + kem, dk, seed, ek = dk_from_pem(self.dk_seed_pem) + + self.assertIs(kem, self.kem) + self.assertEqual(dk, self.dk) + self.assertEqual(seed, self.seed) + self.assertEqual(ek, self.ek) + + def test_import_from_both(self): + kem, dk, seed, ek = dk_from_pem(self.dk_seed_priv_pem) + + self.assertIs(kem, self.kem) + self.assertEqual(dk, self.dk) + self.assertEqual(seed, self.seed) + self.assertEqual(ek, self.ek) + + def test_import_from_expanded(self): + kem, dk, seed, ek = dk_from_pem(self.dk_priv_pem) + + self.assertIs(kem, self.kem) + self.assertEqual(dk, self.dk) + self.assertEqual(seed, None) + self.assertEqual(ek, self.ek) + + +@unittest.skipUnless(ECDSA_PRESENT, "requires ecdsa package") +class TestMLKEM512(unittest.TestCase, BaseTestSetup): + @classmethod + def setUpClass(cls): + cls.kem = ML_KEM_512 + cls.seed = bytes(range(64)) + + cls.ek, cls.dk = cls.kem.key_derive(cls.seed) + + cls.dk_seed_pem = b"""-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQBBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ +GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= +-----END PRIVATE KEY----- +""" + cls.dk_priv_pem = b"""-----BEGIN PRIVATE KEY----- +MIIGeAIBADALBglghkgBZQMEBAEEggZkBIIGYHBVT9Q2NE8nhbGzsbrBhLZnkAMz +bCbxWn3oeMSCXGvgPzxKSA91t0hqrTHToAUYYj/SB6tSjdYnIUlYNa4AYsNnt0px +uvEKrQ6KKQIHa+MTSL6xXMwJV83rtK/yJnVrvGAbZWireErLrrNHAvD4aiYgIRiy +KyP4NVh3bHnBTbqYM3nIA+DcwxYKEXVwMOacaRl5jYHraYqaRIOpnlpcssMcmmYX +mfPMiceQcG6gQWKQRdQqg67YiGDjlMaRh+IQXSjMFOw5NZLWfdAKpD/otOrkQUAC +hmtccTxqjX0Wz3i4GdbxLp5adCM5CPCxXjxLqDKcXN2lXISSjjqoBj5aqWdkA/kX +NbEQEMf1kwkTZNyGRFvIBIQKmiFyQhJGn4p7DOCsaY64bK05p/SCTZpRY6rCHuaA +iwU8ij+ssLZ0S1Jiu8smpD9mTIcytkz8es8JlgX0HHlgYJdqxDODP+ADQ/sYKDAK +QkdBEW5LRbsnbqgRKaDbTG5gvOYREB6MYlR0kl4CImeTCKPncI0Zcqe0I+sjKFHD +bS7VPT7Tu3UAY3BhpdwikvocRmwHNUaDMovsLB7Sy1yZt47KCWkDjPfDTdEYck4x +yuCGIGs0MCtSD10Xet7Vs8zgKszoCOomvMByYl/bk/F0WKX8HU2jlDgKH1fpzGYQ +lDigdfDSgT/MShmcx22zgj8nCwBhWUGSlAQRo3/7r64sFQFlzsXGv3PFlfuSzRUx +JgfaBwd4ZSvZlEvEi8fRpTQzi60LrWZWxdUCznhQqxWHJE7rWPQ5q14IV0pxjIqs +PXfHmLuhVCczvnNEjyP7cMDlNTonyIMixSGEk6+7OAhkNNbWCla6iH3UmMOrJqCH +CZOBWqakCXXyGK3KFYLWT/yGUvuzqab7wwT5GUX6Sq7yh4/XFd9wET0jefRIhvgS +yD/ytxmmnh7HSuSxWszTrtWlPOdqewmCRxYzuXPLQKGgAV0KQk+hGkecAjAXQ20q +KQDpk+taCgZ0AMf0qt8gH8T6MSZKY7rpXMjWXDmVgV5ZfRBDVc8pqlMzyTJRhp1b +zb5IcST2Ari2pmwWxHYWSK12XPXYAGtRXpBafwrAdrDGLvoygVPnylcBaZ8TBfHm +vG+QsOSbaTUSts6ZKouAFt38GmYsfj+WGcvYad13GvMIlszVkYrGy3dGbF53mZbW +f/mqvJdQPyx7fi0ADYZFD7GAfKTKvaRlgloxx4mht6SRqzhydl0yDQtxkg+iE8lA +k0Frg7gSTmn2XmLLUADcw3qpoP/3OXDEdy81fSQYnKb1MFVowOI3ajdipoxgXlY8 +XSCVcuD8dTLKKUcpU1VntfxBPF6HktJGRTbMgI+YrddGZPFBVm+QFqkKVBgpqYoE +ZM5BqLtEwtT6PCwglGByjvFKGnxMm5jRIgO0zDUpFgqasteDj3/2tTrgWqMafWRr +evpsRZMlJqPDdVYZvplMIRwqMcBbNEeDbLIVC+GCna5rBMVTXP9Ubjkrp5dBFyD5 +JPSQpaxUlfITVtVQt4KmTBaItrZVvMeEIZekNML2Vjtbfwmni8xIgjJ4NWHRb0y6 +tnVUAAUHgVcMZmBLgXrRJSKUc26LAYYaS1p0UZuLb+UUiaUHI5Llh2JscTd2V10z +gGocjicyr5fCaA9RZmMxxOuLvAQxxPloMtrxs8RVKPuhU/bHixwZhwKUfM0zdyek +b7U7oR3ly0GRNGhZUWy2rXJADzzyCbI2rvNaWArIfrPjD6/WaXPKin3SZ1r0H3oX +thQzzRr4D3cIhp9mVIhJeYCxrBCgzctjagDthoGzXkKRJMqANQcluF+DperDpKPM +FgCQPmUpNWC5szblrw1SnawaBIEZMCy3qbzBELlIUb8CEX8ZncSFqFK3Rz8JuDGm +gx1bVMC3kNIlz2u5LZRiomzbM92lEjx6rw4moLg2Ve6ii/OoB0clAY/WuuS2Ac9h +uqtxp6PTUZejQ+dLSicsEl1UCJZCbYW3lY07OKa6mH7DciXHtEzbEt3kU5tKsII2 +NoPwS/egnMXEHf6DChsWLgsyQzQ2LwhKFEZ3IzRLrdAA+NjFN8SPmY8FMHzr0e3g +uBw7xZoGWhttY7JsgvEB/2SAY7N24rtsW3RV9lWlDC/q2t4VDvoODm82WuogISIj +JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== +-----END PRIVATE KEY----- +""" + cls.dk_seed_priv_pem = b"""-----BEGIN PRIVATE KEY----- +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 +Pj8= +-----END PRIVATE KEY----- +""" + cls.ek_pem = b"""-----BEGIN PUBLIC KEY----- +MIIDMjALBglghkgBZQMEBAEDggMhADmVgV5ZfRBDVc8pqlMzyTJRhp1bzb5IcST2 +Ari2pmwWxHYWSK12XPXYAGtRXpBafwrAdrDGLvoygVPnylcBaZ8TBfHmvG+QsOSb +aTUSts6ZKouAFt38GmYsfj+WGcvYad13GvMIlszVkYrGy3dGbF53mZbWf/mqvJdQ +Pyx7fi0ADYZFD7GAfKTKvaRlgloxx4mht6SRqzhydl0yDQtxkg+iE8lAk0Frg7gS +Tmn2XmLLUADcw3qpoP/3OXDEdy81fSQYnKb1MFVowOI3ajdipoxgXlY8XSCVcuD8 +dTLKKUcpU1VntfxBPF6HktJGRTbMgI+YrddGZPFBVm+QFqkKVBgpqYoEZM5BqLtE +wtT6PCwglGByjvFKGnxMm5jRIgO0zDUpFgqasteDj3/2tTrgWqMafWRrevpsRZMl +JqPDdVYZvplMIRwqMcBbNEeDbLIVC+GCna5rBMVTXP9Ubjkrp5dBFyD5JPSQpaxU +lfITVtVQt4KmTBaItrZVvMeEIZekNML2Vjtbfwmni8xIgjJ4NWHRb0y6tnVUAAUH +gVcMZmBLgXrRJSKUc26LAYYaS1p0UZuLb+UUiaUHI5Llh2JscTd2V10zgGocjicy +r5fCaA9RZmMxxOuLvAQxxPloMtrxs8RVKPuhU/bHixwZhwKUfM0zdyekb7U7oR3l +y0GRNGhZUWy2rXJADzzyCbI2rvNaWArIfrPjD6/WaXPKin3SZ1r0H3oXthQzzRr4 +D3cIhp9mVIhJeYCxrBCgzctjagDthoGzXkKRJMqANQcluF+DperDpKPMFgCQPmUp +NWC5szblrw1SnawaBIEZMCy3qbzBELlIUb8CEX8ZncSFqFK3Rz8JuDGmgx1bVMC3 +kNIlz2u5LZRiomzbM92lEjx6rw4moLg2Ve6ii/OoB0clAY/WuuS2Ac9huqtxp6PT +UZejQ+dLSicsEl1UCJZCbYW3lY07OKa6mH7DciXHtEzbEt3kU5tKsII2NoPwS/eg +nMXEHf6DChsWLgsyQzQ2LwhKFEZ3IzRLrdAA+NjFN8SPmY8FMHzr0e3guBw7xZoG +WhttY7Js +-----END PUBLIC KEY----- +""" + + +@unittest.skipUnless(ECDSA_PRESENT, "requires ecdsa package") +class TestMLKEM768(unittest.TestCase, BaseTestSetup): + @classmethod + def setUpClass(cls): + cls.kem = ML_KEM_768 + cls.seed = bytes(range(64)) + + cls.ek, cls.dk = cls.kem.key_derive(cls.seed) + + cls.dk_seed_pem = b"""-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQCBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ +GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= +-----END PRIVATE KEY----- +""" + cls.dk_priv_pem = b"""-----BEGIN PRIVATE KEY----- +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 +JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== +-----END PRIVATE KEY----- +""" + cls.dk_seed_priv_pem = b"""-----BEGIN PRIVATE KEY----- +MIIJvgIBADALBglghkgBZQMEBAIEggmqMIIJpgRAAAECAwQFBgcICQoLDA0ODxAR +EhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+PwSC +CWAn0qd/M3VvYSCO8ROr6CWVhz1KvHMOW11nlSm/akzrY4NCcjGoYS9BVQUVrLpS +5I6ti5QoM7vmhl0T0Up50sXD4H8KBW2N56rfyroFjEk8gLN8q4xWJ1O7O6a27IKX ++IXqp1QNUwAVqEQG5VsTZrV34jbOWKJtih61pE1UIyPCFn2b9KR/mFaZygW65DuN +7GF/AjgKOJCv1LjH7H7eJlU6Al885bxdemITAwQjXLGtSDa1ZrW4Y72b20WihEpw +R7bI04PkSFJeBAtNyKK0jGw3yW1i1D8/2I4ogcQKIFyeJI9lK1kngad5+GiA8qFH +tnhj85HMGlqQjACV4HISKR4u+KNuuanAxgcyJbNHA6SvBJOCxHVz2mj96SRa1ETj +Gx+9tSHx9h83vAzvKSBn5nDSih/9kE9vEZCplpGKEwN6bKvzw3O/gpbNN6szundG +gJzD+K3hs2Ob1Xv8xpZQqq8d4Zj8TARjKZ5SxGF4DMQo/F0EpcUYUMumwqUnQ0Bn +V5PdoJvkTCnmOVxl+F0qCnxt9BHmkRsfLLbDUc0uh19Rtji+d2CX6T4vKy+D2gvu +9KqFup52OrZFAqDKUiLp6rWztwiO1SBg6Mgmm5Q6casK4cWxtofS4BnPgDa8+b9u +e6w6qjbkFmD6pFQPJkjNk6GJ7Fwt6nC6yqpP/JBvkIEOobZ78k8seM9rqIGq6mHA +ZSv/lbG65EJtF3O5zCyoLCHjjGNuOxxSMkSYawvoqD9d1c8tVHYvs8Xr9ZuOiFMC +sc5HAz7fdg9OApvkC21WaxnddYrNXHQSh4ExJE+QFyxT8mZjwh2QUwHUi6+RyRfM +d3np2IAswQ2Jo3BQmaKtOjqIlnQ8EURpgJO+JX2stm3HhSKLkSyNll0Uqig0LDrE +qT/vpTKyCUXdwQIBOcFNY4uQjE3d6aBkW5Wy5EFNQLt58EQTgw8VqHPCi7cFnCdB +ACAV8gQI8FjnFbC/mVtTgLfdMloFarl+ZZor4M32wzcxxoOmNLdx6MkqE5ruS7Dk +nHB3Mh1C/BmffB8pjKYl0iOlwmOgPMSBWbeBJmW3hjfk4YcgssKaa5n0J2aky8Tc +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 +Pj8= +-----END PRIVATE KEY----- +""" + cls.ek_pem = b"""-----BEGIN PUBLIC KEY----- +MIIEsjALBglghkgBZQMEBAIDggShACmKoQ1CPI3aBp0CvFnmzfA6CWuLPaTKubgM +pKFJB2cszvHsT68jSgvFt+nUc/KzEzs7JqHRdctnp4BZGWmcAvdlMbmcX4kYBwS7 +TKRTXFuJcmecZgoHxeUUuHAJyGLrj1FXaV77P8QKne9rgcHMAqJJrk8JStDZvTSF +wcHGgIBSCnyMYyAyzuc4FU5cUXbAfaVgJHdqQw/nbqz2ZaP3uDIQIhW8gvEJOcg1 +VwQzao+sHYHkuwSFql18dNa1m75cXpcqDYusQRtVtdVVfNaAoaj3G064a8SMmgUJ +cxpUvZ1ykLJ5Y+Q3Lcmxmc/crAsBrNKKYjlREuTENkjWIsSMgjTQFEDozDdskn8j +pa/JrAR0xmInTkJFJchVLs47P+JlFt6QG8fVFb3olVjmJslcgLkzQvgBAATznmxs +lIccXjRMqzlmyDX5qWpZr9McQChrOLHBp4RwurlHUYk0RTzoZzapGfH1ptUQqG9U +VPw5gMtcdlvSvV97NrFBDWY1yM60fE3aDXaijqyTnHHDAkgEhmxxYmZYRCFjwsIh +F+UKzvzmN4qYVlIwKk7wws4Mxxa3eW4ray43d9+hrD2iWaMbWptTD4y2OKgaYqww +GEmrr5WnMBvaMAaJCb/bfmfbzLs4pVUaJbGjoPaFdIrVdT2IgPABbGJ0hhZjhMVX +H+I2WQA2TQODEeLYdds2ZoaTK17GAkMKNp6Hpu9cM4eGZXglvUwFes65I+sJNeaQ +XmO0ztf4CFenc91ksVDSZhLqmsEgUtsgF78YQ8y0sygbaQ3HKK36hcACgbjjwJKH +M1+Fa0/CiS9povV5Ia2gGRTECYhmLVd2lmKnhjUbm2ZJPat5WU2YbeIQDWW6D/Tq +WLgVONJKRDWiWPrCVASqf0H2WLE4UGXhWNy2ARVzJyD0BFmqrBXkBpU6kKxSmX0c +zQcAYO/GXbnmUzVEZ/rVbscTyG51QMQjrPJmn1L6b0rGiI2HHvPoR8ApqKr7uS4X +skqgebH0GbphdbRCr7EZCdSla3CgM1soc5IYqnyTSOLDwvPrPRWkHmQXwN2Uv+sh +QZsxGnuxOhgLvoMyGKmmsXRHzIXyJYWVh6cwdwSay8/UTQ8CVDjhXRU4Jw1Ybhv4 +MZKpRZz2PA6XL4UpdnmDHs8SFQmFHLg0D28Qew+hoO/Rs2qBibwIXE9ct4TlU/Qb +kY+AOXzhlW94W+43fKmqi+aZitowwmt8PYxrVSVMyWIDsgxCruCsTh67QI5JqeP4 +edCrB4XrcCVCXRMFoimcAV4SDRY7DhlJTOVyU9AkbRgnRcuBl6t0OLPBu3lyvsWj +BuujVnhVwBRpn+9lrlTHcKDYXBhADPZCrtxmB3e6SxOFAr1aeBL2IfhKSClrmN1D +IrbxWCi4qPDgCoukSlPDqLFDVxsHQKvVZ9rxzenHnCBLbV4lnRdmoxu7y05qBc9F +AhdrMBwcL0Ekd1AVe87IXoCbMKTWDXdHzdD1uZqoyCaYdRd5OqqAgKCxJKhVjfcr +vje3X07btr6CFtbGM/srIoDiURPYaV5DSBw+6zl+sZJQUim2eiAeqJPD4ssy2ovD +QvpN6gV4 +-----END PUBLIC KEY----- +""" + + +@unittest.skipUnless(ECDSA_PRESENT, "requires ecdsa package") +class TestMLKEM1024(unittest.TestCase, BaseTestSetup): + @classmethod + def setUpClass(cls): + cls.kem = ML_KEM_1024 + cls.seed = bytes(range(64)) + + cls.ek, cls.dk = cls.kem.key_derive(cls.seed) + + cls.dk_seed_pem = b"""-----BEGIN PRIVATE KEY----- +MFQCAQAwCwYJYIZIAWUDBAQDBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ +GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= +-----END PRIVATE KEY----- +""" + cls.dk_priv_pem = b"""-----BEGIN PRIVATE KEY----- +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 +JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== +-----END PRIVATE KEY----- +""" + cls.dk_seed_priv_pem = b"""-----BEGIN PRIVATE KEY----- +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 +Pj8= +-----END PRIVATE KEY----- +""" + cls.ek_pem = b"""-----BEGIN PUBLIC KEY----- +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 +uYOILhF1 +-----END PUBLIC KEY----- +""" From 81d057d7b9826e6b2755191c6d699e93b260a6e5 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 13 Mar 2025 14:28:00 +0100 Subject: [PATCH 6/8] Run CI with ecdsa too --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7a311c..5f395a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,6 +146,22 @@ jobs: - name: py3.12 os: ubuntu-latest python-version: "3.12" + - name: py3.9 with ecdsa + os: ubuntu-latest + python-version: "3.9" + opt-deps: ['ecdsa'] + - name: py3.10 with ecdsa + os: ubuntu-latest + python-version: "3.10" + opt-deps: ['ecdsa'] + - name: py3.11 with ecdsa + os: ubuntu-latest + python-version: "3.11" + opt-deps: ['ecdsa'] + - name: py3.12 with ecdsa + os: ubuntu-latest + python-version: "3.12" + opt-deps: ['ecdsa'] steps: - uses: actions/checkout@v4 with: @@ -169,6 +185,10 @@ jobs: - name: Install build dependencies run: | pip install -r requirements.txt + - name: Install ecdsa + if: ${{ contains(matrix.opt-deps, 'ecdsa') }} + run: | + pip install ecdsa - name: Display installed python package versions run: | pip list || : From e7e7a422d314792a7c9583cf7caab1926932c602 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Mon, 2 Jun 2025 18:33:37 +0200 Subject: [PATCH 7/8] test coverage for malformed PKCS files --- tests/test_pkcs.py | 374 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) diff --git a/tests/test_pkcs.py b/tests/test_pkcs.py index 3dce284..8c80023 100644 --- a/tests/test_pkcs.py +++ b/tests/test_pkcs.py @@ -5,11 +5,14 @@ try: from ecdsa.der import unpem + from ecdsa import der from kyber_py.ml_kem.pkcs import ( ek_to_pem, ek_from_pem, + ek_from_der, dk_to_pem, dk_from_pem, + dk_from_der, ) ECDSA_PRESENT = True @@ -615,3 +618,374 @@ def setUpClass(cls): uYOILhF1 -----END PUBLIC KEY----- """ + + +@unittest.skipUnless(ECDSA_PRESENT, "requires ecdsa package") +class TestMalfomedKeys(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.kem = ML_KEM_512 + cls.seed = bytes(64) + cls.ek, cls.dk = cls.kem.key_derive(cls.seed) + + def test_ek_sanity_check(self): + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_bitstring(self.ek, 0), + ) + + kem, key = ek_from_der(enc) + + self.assertEqual(kem, self.kem) + self.assertEqual(key, self.ek) + + def test_ek_trailing_junk_after_pub_key(self): + enc = ( + der.encode_sequence( + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_bitstring(self.ek, 0), + ) + + b"\x00" + ) + + with self.assertRaises(der.UnexpectedDER) as e: + ek_from_der(enc) + + self.assertIn("Trailing junk after DER public key", str(e.exception)) + + def test_ek_wrong_oid(self): + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(1, 2, 3, 4), + ), + der.encode_bitstring(self.ek, 0), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + ek_from_der(enc) + + self.assertIn( + "Not recognised algoritm OID: (1, 2, 3, 4)", str(e.exception) + ) + + def test_ek_parameters_in_alg_id(self): + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*self.kem.oid), + der.encode_sequence(), + ), + der.encode_bitstring(self.ek, 0), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + ek_from_der(enc) + + self.assertIn("Parameters specified for ML-KEM OID", str(e.exception)) + + def test_ek_junk_after_public_key(self): + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_bitstring(self.ek, 0), + der.encode_integer(2), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + ek_from_der(enc) + + self.assertIn( + "Trailing junk after public key bitsting", str(e.exception) + ) + + def test_ek_unexpected_size_of_key(self): + enc = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_bitstring(self.ek + b"\x00", 0), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + ek_from_der(enc) + + self.assertIn( + "Wrong key size for the OID in structure", str(e.exception) + ) + + def test_dk_sanity_both(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ), + ), + ) + + kem, expanded, seed, ek = dk_from_der(enc) + + self.assertEqual(self.kem, kem) + self.assertEqual(self.dk, expanded) + self.assertEqual(self.seed, seed) + self.assertEqual(self.ek, ek) + + def test_dk_sanity_seed(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_implicit(0, self.seed), + ), + ) + + kem, expanded, seed, ek = dk_from_der(enc) + + self.assertEqual(self.kem, kem) + self.assertEqual(self.dk, expanded) + self.assertEqual(self.seed, seed) + self.assertEqual(self.ek, ek) + + def test_dk_sanity_expanded_only(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_octet_string(self.dk), + ), + ) + + kem, expanded, seed, ek = dk_from_der(enc) + + self.assertEqual(self.kem, kem) + self.assertEqual(self.dk, expanded) + self.assertEqual(None, seed) + self.assertEqual(self.ek, ek) + + def test_dk_trailing_junk(self): + enc = ( + der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ), + ), + ) + + b"\x00" + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn( + "Trailing junk after private key structure", str(e.exception) + ) + + def test_dk_wrong_version(self): + enc = der.encode_sequence( + der.encode_integer(1), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Unsupported version: 1", str(e.exception)) + + def test_dk_wrong_oid(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(1, 3, 2, 1), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn( + "Not recognised algorithm OID: (1, 3, 2, 1)", str(e.exception) + ) + + def test_dk_junk_after_oid(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + der.encode_integer(1), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Junk after algorithm OID", str(e.exception)) + + def test_dk_junk_after_both_encoding(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + ) + + b"\x00", + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Junk after both encoding", str(e.exception)) + + def test_dk_junk_in_both_encoding(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk), + der.encode_integer(1), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn( + "Junk after 'expandedKey' in 'both' value", str(e.exception) + ) + + def test_dk_wrong_expanded_key_size(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed), + der.encode_octet_string(self.dk + b"\x00"), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn( + "Invalid expanded key size in encoding", str(e.exception) + ) + + def test_dk_wrong_seed_size(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_sequence( + der.encode_octet_string(self.seed + b"\x00"), + der.encode_octet_string(self.dk), + ), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Invalid length of seed in encoding", str(e.exception)) + + def test_dk_wrong_tag_in_seed_encoding(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_implicit(1, self.seed), + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn( + "Unexpected tag in private key encoding", str(e.exception) + ) + + def test_dk_junk_after_seed_encoding(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_implicit(0, self.seed) + b"\x00", + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Junk after seed encoding", str(e.exception)) + + def test_dk_junk_after_expanded_key(self): + enc = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*self.kem.oid), + ), + der.encode_octet_string( + der.encode_octet_string(self.dk) + b"\x00", + ), + ) + + with self.assertRaises(der.UnexpectedDER) as e: + dk_from_der(enc) + + self.assertIn("Junk after expandedKey", str(e.exception)) From b5115c402ef727eb58046526a83f99660694c91d Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 13 Mar 2025 17:37:47 +0100 Subject: [PATCH 8/8] scripts to generate IETF standard key formats and use them --- scripts/decaps.py | 91 ++++++++++++++++++++++++++++++++++ scripts/encaps.py | 91 ++++++++++++++++++++++++++++++++++ scripts/keygen.py | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 scripts/decaps.py create mode 100644 scripts/encaps.py create mode 100644 scripts/keygen.py diff --git a/scripts/decaps.py b/scripts/decaps.py new file mode 100644 index 0000000..e9516e9 --- /dev/null +++ b/scripts/decaps.py @@ -0,0 +1,91 @@ +import sys +import getopt +from kyber_py.ml_kem.pkcs import ( + dk_from_pem, + dk_from_der, +) + + +def help_msg(): + print( + f"""Usage: {sys.argv[0]} [options] + --dk FILE Decapsulation key file name + --dk-form FORM Decapsulation key format name: "DER" or "PEM, with "PEM" + being the default. + --secret FILE Name of file to write the derived secret to + --ciphertext FILE Name of file to read the ciphertext from + --help This message +""" + ) + + +def main(): + dk_file = None + dk_form = "PEM" + secret_file = None + ciphertext_file = None + + argv = sys.argv[1:] + opts, args = getopt.getopt( + argv, + "", + ["dk=", "dk-form=", "secret=", "ciphertext=", "help"], + ) + + for opt, arg in opts: + if opt == "--dk": + dk_file = arg + elif opt == "--dk-form": + dk_form = arg + elif opt == "--secret": + secret_file = arg + elif opt == "--ciphertext": + ciphertext_file = arg + elif opt == "--help": + help_msg() + sys.exit(0) + else: + print(f"Unrecognised option: {opt}") + sys.exit(1) + + if args: + print(f"Unrecognised options: {args}") + + if not dk_file: + print("Specify encapsulation key name with --dk option") + help_msg() + sys.exit(1) + + if not secret_file: + print("Specify file name to write the derived secret to") + help_msg() + sys.exit(1) + + if not ciphertext_file: + print("Specify file name to read the ciphertext from") + help_msg() + sys.exit(1) + + if dk_form == "PEM": + dk_dec_func = dk_from_pem + elif dk_form == "DER": + dk_dec_func = dk_from_der + else: + print(f"Wrong name of key format: {dk_form}") + help_msg() + sys.exit(1) + + with open(dk_file, "rb") as file: + kem, dk, _, _ = dk_dec_func(file.read()) + + with open(ciphertext_file, "rb") as file: + ciphertext = file.read() + + secret = kem.decaps(dk, ciphertext) + + with open(secret_file, "wb") as file: + file.write(secret) + + +if __name__ == "__main__": + main() diff --git a/scripts/encaps.py b/scripts/encaps.py new file mode 100644 index 0000000..22d8096 --- /dev/null +++ b/scripts/encaps.py @@ -0,0 +1,91 @@ +import sys +import getopt +from kyber_py.ml_kem.pkcs import ( + ek_from_pem, + ek_from_der, +) + + +def help_msg(): + print( + f"""Usage: {sys.argv[0]} [options] + --ek FILE Encapsulation key file name + --ek-form FORM Encapsulation key format name: "DER" or "PEM, with "PEM" + being the default. + --secret FILE Name of file to write the derived secret to + --ciphertext FILE Name of file to write the ciphertext to + --help This message +""" + ) + + +def main(): + ek_file = None + ek_form = "PEM" + secret_file = None + ciphertext_file = None + + argv = sys.argv[1:] + opts, args = getopt.getopt( + argv, + "", + ["ek=", "ek-form=", "secret=", "ciphertext=", "help"], + ) + + for opt, arg in opts: + if opt == "--ek": + ek_file = arg + elif opt == "--ek-form": + ek_form = arg + elif opt == "--secret": + secret_file = arg + elif opt == "--ciphertext": + ciphertext_file = arg + elif opt == "--help": + help_msg() + sys.exit(0) + else: + print(f"Unrecognised option: {opt}") + sys.exit(1) + + if args: + print(f"Unrecognised options: {args}") + + if not ek_file: + print("Specify encapsulation key name with --ek option") + help_msg() + sys.exit(1) + + if not secret_file: + print("Specify file name to write the derived secret to") + help_msg() + sys.exit(1) + + if not ciphertext_file: + print("Specify file name to write the ciphertext to") + help_msg() + sys.exit(1) + + if ek_form == "PEM": + ek_dec_func = ek_from_pem + elif ek_form == "DER": + ek_dec_func = ek_from_der + else: + print(f"Wrong name of key format: {ek_form}") + help_msg() + sys.exit(1) + + with open(ek_file, "rb") as file: + kem, ek = ek_dec_func(file.read()) + + secret, ciphertext = kem.encaps(ek) + + with open(secret_file, "wb") as file: + file.write(secret) + + with open(ciphertext_file, "wb") as file: + file.write(ciphertext) + + +if __name__ == "__main__": + main() diff --git a/scripts/keygen.py b/scripts/keygen.py new file mode 100644 index 0000000..3b5847f --- /dev/null +++ b/scripts/keygen.py @@ -0,0 +1,122 @@ +import sys +import getopt +import os +from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024 + +from kyber_py.ml_kem.pkcs import ( + ek_to_der, + ek_to_pem, + dk_to_der, + dk_to_pem, +) + + +def help_msg(): + print( + f"""Usage: {sys.argv[0]} [options] + + --dk FILE Decapsulation key file name + --dk-form FORM Decapsulation key format name: "DER" or "PEM", with "PEM" + being the default. + --dk-cont CONT Decapsulation key contents: "seed", "expanded", or "both", + with "both" being the default. + --ek FILE Encapsulation key file name, none by default + --ek-form FORM Encapsulation key format name: "DER" or "PEM", with "PEM" + being the default. + --kem NAME Name of the KEM to use: ML-KEM-512, ML-KEM-768, or ML-KEM-1024 + --help This message +""" + ) + + +def main(): + ek_file = None + ek_form = "PEM" + dk_file = None + dk_form = "PEM" + dk_cont = None + kem = None + + argv = sys.argv[1:] + opts, args = getopt.getopt( + argv, + "", + ["dk=", "dk-form=", "dk-cont=", "ek=", "ek-form=", "kem=", "help"], + ) + for opt, arg in opts: + if opt == "--dk": + dk_file = arg + elif opt == "--dk-form": + dk_form = arg + elif opt == "--dk-cont": + dk_cont = arg + elif opt == "--ek": + ek_file = arg + elif opt == "--ek-form": + ek_form = arg + elif opt == "--kem": + kem = arg + elif opt == "--help": + help_msg() + sys.exit(0) + else: + print(f"unrecognised option: {opt}") + sys.exit(1) + + if args: + print(f"unrecognised options: {args}") + sys.exit(1) + + if not dk_file: + print("Specify output file with --dk option") + help_msg() + sys.exit(1) + + if not kem: + print("Specify the kem to generate with --kem option") + help_msg() + sys.exit(1) + + if kem == "ML-KEM-512": + kem = ML_KEM_512 + elif kem == "ML-KEM-768": + kem = ML_KEM_768 + elif kem == "ML-KEM-1024": + kem = ML_KEM_1024 + else: + print(f"Unrecognised KEM name: {kem}") + help_msg() + sys.exit(1) + + if ek_form == "PEM": + ek_out_func = ek_to_pem + elif ek_form == "DER": + ek_out_func = ek_to_der + else: + print(f"Unrecognised ek format: {ek_form}") + help_msg() + sys.exit(1) + + if dk_form == "PEM": + dk_out_func = dk_to_pem + elif dk_form == "DER": + dk_out_func = dk_to_der + else: + print(f"Unrecognised dk format: {dk_form}") + help_msg() + sys.exit(1) + + seed = os.urandom(64) + + ek, dk = kem.key_derive(seed) + + with open(dk_file, "wb") as file: + file.write(dk_out_func(kem, dk, seed, dk_cont)) + + if ek_file: + with open(ek_file, "wb") as file: + file.write(ek_out_func(kem, ek)) + + +if __name__ == "__main__": + main()