diff --git a/examples/manual_repo/basic_repo.py b/examples/manual_repo/basic_repo.py index eb32523b23..6fbaea48a4 100644 --- a/examples/manual_repo/basic_repo.py +++ b/examples/manual_repo/basic_repo.py @@ -25,10 +25,9 @@ import tempfile from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Dict +from typing import Dict -from securesystemslib.keys import generate_ed25519_key -from securesystemslib.signer import SSlibKey, SSlibSigner +from securesystemslib.signer import CryptoSigner, Signer from tuf.api.metadata import ( SPECIFICATION_VERSION, @@ -89,7 +88,7 @@ def _in(days: float) -> datetime: # Define containers for role objects and cryptographic keys created below. This # allows us to sign and write metadata in a batch more easily. roles: Dict[str, Metadata] = {} -keys: Dict[str, Dict[str, Any]] = {} +signers: Dict[str, Signer] = {} # Targets (integrity) @@ -157,10 +156,8 @@ def _in(days: float) -> datetime: # See https://github.com/secure-systems-lab/securesystemslib for more details # about key handling, and don't forget to password-encrypt your private keys! for name in ["targets", "snapshot", "timestamp", "root"]: - keys[name] = generate_ed25519_key() - roles["root"].signed.add_key( - SSlibKey.from_securesystemslib_key(keys[name]), name - ) + signers[name] = CryptoSigner.generate_ecdsa() + roles["root"].signed.add_key(signers[name].public_key, name) # NOTE: We only need the public part to populate root, so it is possible to use # out-of-band mechanisms to generate key pairs and only expose the public part @@ -173,10 +170,8 @@ def _in(days: float) -> datetime: # threshold of multiple keys to sign root metadata. For this example we # generate another root key (you can pretend it's out-of-band) and increase the # required signature threshold. -another_root_key = generate_ed25519_key() -roles["root"].signed.add_key( - SSlibKey.from_securesystemslib_key(another_root_key), "root" -) +another_root_signer = CryptoSigner.generate_ecdsa() +roles["root"].signed.add_key(another_root_signer.public_key, "root") roles["root"].signed.roles["root"].threshold = 2 @@ -185,9 +180,7 @@ def _in(days: float) -> datetime: # In this example we have access to all top-level signing keys, so we can use # them to create and add a signature for each role metadata. for name in ["targets", "snapshot", "timestamp", "root"]: - key = keys[roles[name].signed.type] - signer = SSlibSigner(key) - roles[name].sign(signer) + roles[name].sign(signers[name]) # Persist metadata (consistent snapshot) @@ -227,9 +220,9 @@ def _in(days: float) -> datetime: # file, sign it, and write it back to the same file, and this can be repeated # until the threshold is satisfied. root_path = os.path.join(TMP_DIR, "1.root.json") -roles["root"].from_file(root_path) -roles["root"].sign(SSlibSigner(another_root_key), append=True) -roles["root"].to_file(root_path, serializer=PRETTY) +root = Metadata.from_file(root_path) +root.sign(another_root_signer, append=True) +root.to_file(root_path, serializer=PRETTY) # Targets delegation @@ -243,7 +236,7 @@ def _in(days: float) -> datetime: # In this example the top-level targets role trusts a new "python-scripts" # targets role to provide integrity for any target file that ends with ".py". delegatee_name = "python-scripts" -keys[delegatee_name] = generate_ed25519_key() +signers[delegatee_name] = CryptoSigner.generate_ecdsa() # Delegatee # --------- @@ -271,16 +264,13 @@ def _in(days: float) -> datetime: # delegatee is responsible for, e.g. a list of path patterns. For details about # all configuration parameters see # https://theupdateframework.github.io/specification/latest/#delegations +delegatee_key = signers[delegatee_name].public_key roles["targets"].signed.delegations = Delegations( - keys={ - keys[delegatee_name]["keyid"]: SSlibKey.from_securesystemslib_key( - keys[delegatee_name] - ) - }, + keys={delegatee_key.keyid: delegatee_key}, roles={ delegatee_name: DelegatedRole( name=delegatee_name, - keyids=[keys[delegatee_name]["keyid"]], + keyids=[delegatee_key.keyid], threshold=1, terminating=True, paths=["*.py"], @@ -319,8 +309,7 @@ def _in(days: float) -> datetime: # Sign and write metadata for all changed roles, i.e. all but root for role_name in ["targets", "python-scripts", "snapshot", "timestamp"]: - signer = SSlibSigner(keys[role_name]) - roles[role_name].sign(signer) + roles[role_name].sign(signers[role_name]) # Prefix all but timestamp with version number (see consistent snapshot) filename = f"{role_name}.json" @@ -343,17 +332,15 @@ def _in(days: float) -> datetime: # In this example we will replace a root key, and sign a new version of root # with the threshold of old and new keys. Since one of the previous root keys # remains in place, it can be used to count towards the old and new threshold. -new_root_key = generate_ed25519_key() +new_root_signer = CryptoSigner.generate_ecdsa() -roles["root"].signed.revoke_key(keys["root"]["keyid"], "root") -roles["root"].signed.add_key( - SSlibKey.from_securesystemslib_key(new_root_key), "root" -) +roles["root"].signed.revoke_key(signers["root"].public_key.keyid, "root") +roles["root"].signed.add_key(new_root_signer.public_key, "root") roles["root"].signed.version += 1 roles["root"].signatures.clear() -for key in [keys["root"], another_root_key, new_root_key]: - roles["root"].sign(SSlibSigner(key), append=True) +for signer in [signers["root"], another_root_signer, new_root_signer]: + roles["root"].sign(signer, append=True) roles["root"].to_file( os.path.join(TMP_DIR, f"{roles['root'].signed.version}.root.json"), diff --git a/examples/manual_repo/hashed_bin_delegation.py b/examples/manual_repo/hashed_bin_delegation.py index df476e091f..8a90415d87 100644 --- a/examples/manual_repo/hashed_bin_delegation.py +++ b/examples/manual_repo/hashed_bin_delegation.py @@ -21,10 +21,9 @@ import tempfile from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Dict, Iterator, List, Tuple +from typing import Dict, Iterator, List, Tuple -from securesystemslib.keys import generate_ed25519_key -from securesystemslib.signer import SSlibKey, SSlibSigner +from securesystemslib.signer import CryptoSigner, Signer from tuf.api.metadata import ( DelegatedRole, @@ -44,7 +43,7 @@ def _in(days: float) -> datetime: roles: Dict[str, Metadata[Targets]] = {} -keys: Dict[str, Dict[str, Any]] = {} +signers: Dict[str, Signer] = {} # Hash bin delegation # =================== @@ -138,7 +137,7 @@ def find_hash_bin(path: str) -> str: # NOTE: See "Targets delegation" and "Signature thresholds" paragraphs in # 'basic_repo.py' for more details for name in ["bin-n", "bins"]: - keys[name] = generate_ed25519_key() + signers[name] = CryptoSigner.generate_ecdsa() # Targets roles @@ -149,7 +148,7 @@ def find_hash_bin(path: str) -> str: # Create preliminary delegating targets role (bins) and add public key for # delegated targets (bin_n) to key store. Delegation details are update below. roles["bins"] = Metadata(Targets(expires=_in(365))) -bin_n_key = SSlibKey.from_securesystemslib_key(keys["bin-n"]) +bin_n_key = signers["bin-n"].public_key roles["bins"].signed.delegations = Delegations( keys={bin_n_key.keyid: bin_n_key}, roles={}, @@ -169,7 +168,7 @@ def find_hash_bin(path: str) -> str: # delegated targets role (bin_n). roles["bins"].signed.delegations.roles[bin_n_name] = DelegatedRole( name=bin_n_name, - keyids=[keys["bin-n"]["keyid"]], + keyids=[signers["bin-n"].public_key.keyid], threshold=1, terminating=False, path_hash_prefixes=bin_n_hash_prefixes, @@ -210,8 +209,7 @@ def find_hash_bin(path: str) -> str: TMP_DIR = tempfile.mkdtemp(dir=os.getcwd()) for role_name, role in roles.items(): - key = keys["bins"] if role_name == "bins" else keys["bin-n"] - signer = SSlibSigner(key) + signer = signers["bins"] if role_name == "bins" else signers["bin-n"] role.sign(signer) filename = f"1.{role_name}.json" diff --git a/examples/manual_repo/succinct_hash_bin_delegations.py b/examples/manual_repo/succinct_hash_bin_delegations.py index 4e27874a24..b13a28c0b4 100644 --- a/examples/manual_repo/succinct_hash_bin_delegations.py +++ b/examples/manual_repo/succinct_hash_bin_delegations.py @@ -23,10 +23,9 @@ import tempfile from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Dict, Tuple +from typing import Dict -from securesystemslib.keys import generate_ed25519_key -from securesystemslib.signer import SSlibKey, SSlibSigner +from securesystemslib.signer import CryptoSigner from tuf.api.metadata import ( Delegations, @@ -80,15 +79,10 @@ THRESHOLD = 1 -def create_key() -> Tuple[Key, SSlibSigner]: - """Generates a new Key and Signer.""" - sslib_key = generate_ed25519_key() - return SSlibKey.from_securesystemslib_key(sslib_key), SSlibSigner(sslib_key) - - # Create one signing key for all bins, and one for the delegating targets role. -bins_key, bins_signer = create_key() -_, targets_signer = create_key() +bins_signer = CryptoSigner.generate_ecdsa() +bins_key = bins_signer.public_key +targets_signer = CryptoSigner.generate_ecdsa() # Delegating targets role # ----------------------- diff --git a/examples/repository/_simplerepo.py b/examples/repository/_simplerepo.py index a9ddaa8731..b92ce9ca54 100644 --- a/examples/repository/_simplerepo.py +++ b/examples/repository/_simplerepo.py @@ -10,8 +10,7 @@ from datetime import datetime, timedelta, timezone from typing import Dict, List, Union -from securesystemslib import keys -from securesystemslib.signer import Key, Signer, SSlibKey, SSlibSigner +from securesystemslib.signer import CryptoSigner, Key, Signer from tuf.api.exceptions import RepositoryError from tuf.api.metadata import ( @@ -76,9 +75,9 @@ def __init__(self) -> None: # setup a basic repository, generate signing key per top-level role with self.edit_root() as root: for role in ["root", "timestamp", "snapshot", "targets"]: - key = keys.generate_ed25519_key() - self.signer_cache[role].append(SSlibSigner(key)) - root.add_key(SSlibKey.from_securesystemslib_key(key), role) + signer = CryptoSigner.generate_ecdsa() + self.signer_cache[role].append(signer) + root.add_key(signer.public_key, role) for role in ["timestamp", "snapshot", "targets"]: with self.edit(role): diff --git a/examples/uploader/_localrepo.py b/examples/uploader/_localrepo.py index 84259d7ac1..218c860a4e 100644 --- a/examples/uploader/_localrepo.py +++ b/examples/uploader/_localrepo.py @@ -12,8 +12,12 @@ from typing import Dict import requests -from securesystemslib import keys -from securesystemslib.signer import SSlibKey, SSlibSigner +from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, +) +from securesystemslib.signer import CryptoSigner, Signer from tuf.api.exceptions import RepositoryError from tuf.api.metadata import Metadata, MetaFile, TargetFile, Targets @@ -75,18 +79,22 @@ def open(self, role: str) -> Metadata: md.signed.version = 0 return md - def close(self, role: str, md: Metadata) -> None: + def close(self, role_name: str, md: Metadata) -> None: """Store a version of metadata. Handle version bumps, expiry, signing""" + targets = self.targets() + role = targets.get_delegated_role(role_name) + public_key = targets.get_key(role.keyids[0]) + uri = f"file2:{self.key_dir}/{role_name}" + + signer = Signer.from_priv_key_uri(uri, public_key) + md.signed.version += 1 md.signed.expires = datetime.now(timezone.utc) + self.expiry_period - with open(f"{self.key_dir}/{role}", encoding="utf-8") as f: - signer = SSlibSigner(json.loads(f.read())) - md.sign(signer, append=False) # Upload using "api/role" - uri = f"{self.base_url}/api/role/{role}" + uri = f"{self.base_url}/api/role/{role_name}" r = requests.post(uri, data=md.to_bytes(JSONSerializer()), timeout=5) r.raise_for_status() @@ -115,10 +123,9 @@ def add_target(self, role: str, targetpath: str) -> bool: def add_delegation(self, role: str) -> bool: """Use the (unauthenticated) delegation adding API endpoint""" - keydict = keys.generate_ed25519_key() - pubkey = SSlibKey.from_securesystemslib_key(keydict) + signer = CryptoSigner.generate_ecdsa() - data = {pubkey.keyid: pubkey.to_dict()} + data = {signer.public_key.keyid: signer.public_key.to_dict()} url = f"{self.base_url}/api/delegation/{role}" r = requests.post(url, data=json.dumps(data), timeout=5) if r.status_code != 200: @@ -126,8 +133,14 @@ def add_delegation(self, role: str) -> bool: return False # Store the private key using rolename as filename - with open(f"{self.key_dir}/{role}", "w", encoding="utf-8") as f: - f.write(json.dumps(keydict)) + with open(f"{self.key_dir}/{role}", "wb") as f: + # TODO this is dumb and needs to be securesystemslibs job... + priv_key = signer._private_key.private_bytes( + encoding=Encoding.PEM, + format=PrivateFormat.PKCS8, + encryption_algorithm=NoEncryption(), + ) + f.write(priv_key) print(f"Uploaded new delegation, stored key in {self.key_dir}/{role}") return True diff --git a/requirements/pinned.txt b/requirements/pinned.txt index 8ebf19080d..ea5b9bae93 100644 --- a/requirements/pinned.txt +++ b/requirements/pinned.txt @@ -6,5 +6,5 @@ idna==3.7 # via requests pycparser==2.22 # via cffi pynacl==1.5.0 # via securesystemslib requests==2.31.0 -securesystemslib[crypto,pynacl]==0.31.0 +securesystemslib[crypto,pynacl] @ git+https://github.com/secure-systems-lab/securesystemslib@34a42954b5064610ec98406bb55719fd19874089 urllib3==2.2.1 # via requests diff --git a/tests/generated_data/generate_md.py b/tests/generated_data/generate_md.py index e3db3f7edc..23c4b26d96 100644 --- a/tests/generated_data/generate_md.py +++ b/tests/generated_data/generate_md.py @@ -6,12 +6,13 @@ import os import sys from datetime import datetime, timezone -from typing import Dict, List, Optional +from typing import List, Optional -from securesystemslib.signer import SSlibKey, SSlibSigner +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from securesystemslib.signer import CryptoSigner, Signer, SSlibKey from tests import utils -from tuf.api.metadata import Key, Metadata, Root, Snapshot, Targets, Timestamp +from tuf.api.metadata import Metadata, Root, Snapshot, Targets, Timestamp from tuf.api.serialization.json import JSONSerializer # Hardcode keys and expiry time to achieve reproducibility. @@ -21,11 +22,19 @@ "82380623abb9666d4bf274b1a02577469445a972e5650d270101faa5107b19c8", "0e6738fc1ac6fb4de680b4be99ecbcd99b030f3963f291277eef67bb9bd123e9", ] -private_values: List[str] = [ - "510e5e04d7a364af850533856eacdf65d30cc0f8803ecd5fdc0acc56ca2aa91c", - "e6645b00312c8a257782e3e61e85bafda4317ad072c52251ef933d480c387abd", - "cd13dd2180334b24c19b32aaf27f7e375a614d7ba0777220d5c2290bb2f9b868", - "7e2e751145d1b22f6e40d4ba2aa47158207acfd3c003f1cbd5a08141dfc22a15", +private_values: List[bytes] = [ + bytes.fromhex( + "510e5e04d7a364af850533856eacdf65d30cc0f8803ecd5fdc0acc56ca2aa91c" + ), + bytes.fromhex( + "e6645b00312c8a257782e3e61e85bafda4317ad072c52251ef933d480c387abd" + ), + bytes.fromhex( + "cd13dd2180334b24c19b32aaf27f7e375a614d7ba0777220d5c2290bb2f9b868" + ), + bytes.fromhex( + "7e2e751145d1b22f6e40d4ba2aa47158207acfd3c003f1cbd5a08141dfc22a15" + ), ] keyids: List[str] = [ "5822582e7072996c1eef1cec24b61115d364987faa486659fe3d3dce8dae2aba", @@ -34,19 +43,16 @@ "2be5c21e3614f9f178fb49c4a34d0c18ffac30abd14ced917c60a52c8d8094b7", ] -keys: Dict[str, Key] = {} -for index in range(4): - keys[f"ed25519_{index}"] = SSlibKey.from_securesystemslib_key( - { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid": keyids[index], - "keyval": { - "public": public_values[index], - "private": private_values[index], - }, - } +signers: List[Signer] = [] +for index in range(len(keyids)): + key = SSlibKey( + keyids[index], + "ed25519", + "ed25519", + {"public": public_values[index]}, ) + private_key = Ed25519PrivateKey.from_private_bytes(private_values[index]) + signers.append(CryptoSigner(private_key, key)) EXPIRY = datetime(2050, 1, 1, tzinfo=timezone.utc) OUT_DIR = "generated_data/ed25519_metadata" @@ -88,25 +94,14 @@ def generate_all_files( md_snapshot = Metadata(Snapshot(expires=EXPIRY)) md_targets = Metadata(Targets(expires=EXPIRY)) - md_root.signed.add_key(keys["ed25519_0"], "root") - md_root.signed.add_key(keys["ed25519_1"], "timestamp") - md_root.signed.add_key(keys["ed25519_2"], "snapshot") - md_root.signed.add_key(keys["ed25519_3"], "targets") + md_root.signed.add_key(signers[0].public_key, "root") + md_root.signed.add_key(signers[1].public_key, "timestamp") + md_root.signed.add_key(signers[2].public_key, "snapshot") + md_root.signed.add_key(signers[3].public_key, "targets") for i, md in enumerate([md_root, md_timestamp, md_snapshot, md_targets]): assert isinstance(md, Metadata) - signer = SSlibSigner( - { - "keytype": "ed25519", - "scheme": "ed25519", - "keyid": keyids[i], - "keyval": { - "public": public_values[i], - "private": private_values[i], - }, - } - ) - md.sign(signer) + md.sign(signers[i]) path = os.path.join(OUT_DIR, f"{md.signed.type}_with_ed25519.json") if verify: verify_generation(md, path) diff --git a/tests/repository_data/keystore/delegation_key b/tests/repository_data/keystore/delegation_key index 461169d63c..bd04523dbc 100644 --- a/tests/repository_data/keystore/delegation_key +++ b/tests/repository_data/keystore/delegation_key @@ -1 +1,3 @@ -68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be \ No newline at end of file +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIJ12nHk+mGJcC5/tw3PzDZq9gDr6NW/b4ezXfx5dSgsM +-----END PRIVATE KEY----- diff --git a/tests/repository_data/keystore/delegation_key.pub b/tests/repository_data/keystore/delegation_key.pub deleted file mode 100644 index d600bffbfa..0000000000 --- a/tests/repository_data/keystore/delegation_key.pub +++ /dev/null @@ -1 +0,0 @@ -{"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tests/repository_data/keystore/root_key b/tests/repository_data/keystore/root_key index 1b8fb14529..54aaab93e3 100644 --- a/tests/repository_data/keystore/root_key +++ b/tests/repository_data/keystore/root_key @@ -1,42 +1,40 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -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 ------END ENCRYPTED PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDQaM+hWuNL14Kr +OhDxVF4+QLRwjqS2ISCo98cRIXPLHKMLj3QK7LX2e7E9wm5l83rgwLjyg6RH5baa +rikznWLGZ0bnGO804FGQnnBQJzx8MKW0xRMGWq33Ll4ux//j8SgFT7MLhJbWI9T7 +6YKyK3J9BDtTos6fcRgLKuQfnWFn928oLij1M9gxXE15wncIvWrTZDXjkmXKMFO4 +zdd2qxqd1MehdJE+abOAH/V0v9zhCMycKjCspbTqYUur1EBgYholZ8z/QJus4mlU +OQBOYRwx2kYlgN9b+xFC9F2Uc0+jbhlbu+RBQKp1HG+dNaQrlTzXLtG2Mu3XKyRt +ZcEhs5XP3gZeyDLMA3IJP2pGMdcMPPaaQur70jaIq0SpoR49xZG7cfpEtqGkz9PE +XPDrzhhHjvB5d0N5w83FbKJaLIwZK1EU/gKD22tzAAacSbXDxHaCCZamHOcJ3l1c +aE82N8BrzbI0Vjy5uc5RUk5/SdcaDcR3D2JjfHKMlvb6euyTl/cCAwEAAQKCAYEA +kQzxvb7RRd7n3h6a3iw3L6K/MzvEXdKutYtGbKDYw7vZqtkcDeJ0PuoWEQL67VBJ +7JWV44xF0ZiKwBuJJ5hZv/bvfTZ4flfFzR7I0rCMQ29kVW14cUq5m7kU6gBfFBmr +Hg87cT/F76KewPnj8feVRnekhvBgWM5Qyqz+exaBTegD4HZIIWkFBk3UynLTgCy9 +ZgVwEES7Pb7m9k+lr70k2EbY7oF/+W199iXII4rJw4HpTqN6nx7xzNMM5LnkWHDN +uj+g9cCRCPS8BNXcbUmBNthVpaDU79NhHwoFFaYswAOeW1jKpssF9hf1cLpQyaLp +jQqSEF5VMdygEOzuKijq5oJef5zyuSgqkBpvtuUFLkcz9RkJQk3lTpIO5QUy9sek +iikGjucVay5f3N1iJOQi+D+qDAI7cIJTi9hIL/0Xrt0PmSbcAPTvTGP/05I/wyi6 +VD4ClpQFgyZ7OiCiDuwOjv+/mWusN4+mxNyJqtr2b4YZNupRBmsmTvjXSWuqHiih +AoHBAOnnLy9MbeN+WDkbteHs4XE09NR4D6yEbMpEXTzJygZj8DPmoEAn6ojfanKC +NipHvJ0JX+uphzDJ3ZlAdYjZr1ny2VziQNBcfcmf3o1VVxW0KZ8WI4eRmsljFJka +Av+YaLtI+nKvNQxPgD3mS5t/Y6p/kxnGOMIpjbUhKT4HP1u/DdyzIuC5Ur+KJxlJ +pvauHXz0xx6bszNvMIiuddDG0AG8jwZuiZzYGBEsFmscWDgrG3Hk90ir1416m1+7 +jpgIMQKBwQDkGRO7qXNSYtfsWL9UcFTnjb+OYwoKMTppMNb2u+aBZXkWjTJFdT0H +aJp1lsfsFARsNWCq/4uRQute+CMbxDDlXP72jZB407FAWlQie7UWsnRy6/+WeHRM +5gSeRl9n8NSOmb/EH5bsV0sjkLt4VXD0FTeDnu2SwhqVNZ+qdWnbhKmwxxTd2dAA +VoEEftohucYDdRfKrp+YbZn8Sa8Dfge9QiLgE28MrHhy/ZlRUlhiSg9Bl4CDFlpL +sn0wFV56QKcCgcEAnBhETPRcgW1XwwTTJKrI6JvGp+RX0XGuiG2HK4Ie6JTZQEmw +uB/rTNyMVU7AhwbIwKP493RzXAPbduKljWZ4tzZyCKKVTnfrGhsuknNZYoqRHDHS +FC7/dVZB8MqDJb+4ZQQW32I9rLGBi82ct3EUOjxZFuJKDolcoHw44cREbB3cSmTh +6cbDij/QR/f3DLi1xSY1nB+cP778TLrgtSt4tS/44vnxrFIp/YvGikSoOxPJhQCg +ZkcH2srv1bt9NciBAoHAU0JcE5oMwDvYOStD25yNQWBaVa0NEx9ZBOCQ9ssrnnvd +sT+k4/mhZzzldJqvKxs7agwp1wEkfseAhs/ocNAyUOabIoAWBiSvhJ/0Kgoh1cEa +BIDkcJZTTWaAtQ1W8efUjqDMgNhPDMHoaXkBFTGK422DMAYpDfLQJTrHpz7ofvpz +vlVM5pYE+LqaqXtsP/dBsi1hm9gV5VvMY2y593pfdNPZSxWM6YFjDgZHmomGPYpu ++zBD9pWILC1gyNZkABftAoHBANXJibUQ35zhQJEnvHXZ5WJqzJN6R5Zv4sQVd6+o +NM5EotwNMvmjHcW+Q13M2UtaH2mFsUJuOnYCwqqNarJw5+LLEq+CgO0GaJ0wE4TU +1n2/11vAdKMUqvsj92YYq5Y+L/sue9PAYHUMvPsTG75u6fv3ZhJEfneNRqen3jco +3uxlzo/Yjv1fPO6RD9821dRwZDawaoLFidj/Gnqm9PKHux2papWnfP/dkWKLQwl2 +Vu3D0GBOEF8YB2ae3BSVpM+T1Q== +-----END PRIVATE KEY----- diff --git a/tests/repository_data/keystore/root_key.pub b/tests/repository_data/keystore/root_key.pub deleted file mode 100644 index 11cc245f38..0000000000 --- a/tests/repository_data/keystore/root_key.pub +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0GjPoVrjS9eCqzoQ8VRe -PkC0cI6ktiEgqPfHESFzyxyjC490Cuy19nuxPcJuZfN64MC48oOkR+W2mq4pM51i -xmdG5xjvNOBRkJ5wUCc8fDCltMUTBlqt9y5eLsf/4/EoBU+zC4SW1iPU++mCsity -fQQ7U6LOn3EYCyrkH51hZ/dvKC4o9TPYMVxNecJ3CL1q02Q145JlyjBTuM3Xdqsa -ndTHoXSRPmmzgB/1dL/c4QjMnCowrKW06mFLq9RAYGIaJWfM/0CbrOJpVDkATmEc -MdpGJYDfW/sRQvRdlHNPo24ZW7vkQUCqdRxvnTWkK5U81y7RtjLt1yskbWXBIbOV -z94GXsgyzANyCT9qRjHXDDz2mkLq+9I2iKtEqaEePcWRu3H6RLahpM/TxFzw684Y -R47weXdDecPNxWyiWiyMGStRFP4Cg9trcwAGnEm1w8R2ggmWphznCd5dXGhPNjfA -a82yNFY8ubnOUVJOf0nXGg3Edw9iY3xyjJb2+nrsk5f3AgMBAAE= ------END PUBLIC KEY----- diff --git a/tests/repository_data/keystore/root_key2.pub b/tests/repository_data/keystore/root_key2.pub deleted file mode 100644 index dd5c43b5f3..0000000000 --- a/tests/repository_data/keystore/root_key2.pub +++ /dev/null @@ -1 +0,0 @@ -{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "3ba219e69666298bce5d1d653a166346aef807c02e32a846aaefcb5190fddeb4"}} \ No newline at end of file diff --git a/tests/repository_data/keystore/root_key3.pub b/tests/repository_data/keystore/root_key3.pub deleted file mode 100644 index ee5d48725b..0000000000 --- a/tests/repository_data/keystore/root_key3.pub +++ /dev/null @@ -1 +0,0 @@ -{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4huWFUZelzzZk2xLwnLqyc2q7cfI\nIqgg3qOWSddQ3Q/GBXCzgg7zqNqS+xSt+D3gy3mMBbkeo+6OVm8/W9BrqQ=="}} \ No newline at end of file diff --git a/tests/repository_data/keystore/snapshot_key b/tests/repository_data/keystore/snapshot_key index 08c954fdd1..1719a2aaf0 100644 --- a/tests/repository_data/keystore/snapshot_key +++ b/tests/repository_data/keystore/snapshot_key @@ -1 +1,3 @@ -a87b80b8a0d39b919b9638181e7b274e@@@@100000@@@@132edd670981aaf1980673966266174d944d735eb5b0b7ec83ed97da5c212249@@@@bd08ae9898ac5f81fc14e418e9790f9b@@@@399250c9aad40035e0acff48db59697bc3cf33d55b52aa272246addeaaf318d931d3a72964f0c84eccf5b89279b8233685330ad884f7b39bf369553133b985f9396bd5e24cb8e343643923022565a645e188a1165e427aedc389cca821d6a93cb2d8d16cea8ffeb56469bcb9f2f66e03d581a2ea37da271980dd02b84717fe475e13a305b4ae714c11c94f6711c744bb291a146d7419474584bad4be152d0299273c1fad6cd95232a4bf07f39c16da7f4d13201a88fad822cb328008e8a2762baf974b5d5080451751fb8ef53a01ca734157be78b3eb13c6270e4e98b138c78388360e7f558389871b7a32b4d5572626b3112264a0b56dbbb1138c9765872a71dd4e7d31006c2e690f5ede608ce633ad94ebb7d1ddec1a7eac2168fc5d36efe590c4c2059c6f3bcf75ab63474eede3ce4fdc93c6564058b14a0fa9bf3cb6d58c53315b406409ee4aeb18abe072734df0 \ No newline at end of file +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIIOSksDAfmq3o/kDq7QpZ3/Kg1bium+Svw5pvR2ZBhs6 +-----END PRIVATE KEY----- diff --git a/tests/repository_data/keystore/snapshot_key.pub b/tests/repository_data/keystore/snapshot_key.pub deleted file mode 100644 index d08bb848c1..0000000000 --- a/tests/repository_data/keystore/snapshot_key.pub +++ /dev/null @@ -1 +0,0 @@ -{"keyval": {"public": "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tests/repository_data/keystore/targets_key b/tests/repository_data/keystore/targets_key index c3883ec3c5..2d023c85be 100644 --- a/tests/repository_data/keystore/targets_key +++ b/tests/repository_data/keystore/targets_key @@ -1 +1,3 @@ -a5a903322888df0bf8275b215f2044fe@@@@100000@@@@5f6b803652cb6d5bce4e07b1482597adb96d06c2efa3393abdcc0425f70be692@@@@0664811967e2f413927ce51a7f43a80e@@@@cf1dccd034400195c667c064198ef25555f3f94bf9cf77fbe300246618e557ad0efa775ef90bd46c842696c45d14033199860b2214c3641e87889a41171f8a2c763d004681b66b462ff34599e8d9da87f5642d2a015b75d3f601d198e0467fa4bc28f65c76260585e0cce71281f67a8053116f0f06883155f602811071b56bf75bf54daae5968b0a31cf829510f3c52c0eeb8f1c6bb8b8cb0c3edb4c6c2dd9d13bee00c5d63c3f98e0904eebb609864f4ab4fcc2c17bba8fd36aa06bc96bc1922eb10557051a674acf2cb01ff3efb7d55411df6915bbc49a095ff4472dc441e2765244f801d0df07b754c952d039f39b4530930a14be42cb2041f22eeb306b12f12158fcd2beb033db1be21f5a6ab72335cf16dfbd19cbf39c00b0a571d2b0e25df032be53a49a7a70ecebebb441d327c638cf31804381afaf809cd1c75f9070e83240fbaaa87bea0799404ece788862 \ No newline at end of file +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIMKnhTXOqdJvhJ2bJd5dn80MvCykZTplwJ0SUpKiHfI5 +-----END PRIVATE KEY----- diff --git a/tests/repository_data/keystore/targets_key.pub b/tests/repository_data/keystore/targets_key.pub deleted file mode 100644 index e859eb228e..0000000000 --- a/tests/repository_data/keystore/targets_key.pub +++ /dev/null @@ -1 +0,0 @@ -{"keyval": {"public": "89f28bd4ede5ec3786ab923fd154f39588d20881903e69c7b08fb504c6750815"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tests/repository_data/keystore/timestamp_key b/tests/repository_data/keystore/timestamp_key index ca82579003..7ea4a12684 100644 --- a/tests/repository_data/keystore/timestamp_key +++ b/tests/repository_data/keystore/timestamp_key @@ -1 +1,3 @@ -677a42cd6c1df08d0c6156ae356c2875@@@@100000@@@@3850dbcf2973b80044912d630f05039df64775b63d1cf43e750d3cd8a457c64f@@@@bf01961c386d9fefb4b29db7f6ef0c7f@@@@96d37abafb902f821134d2034855d23b78c82e5b768b092fcf0d3b6b28a74734877a5014b26e5fed289d24f7cf6b393445c3231554c5b6d9711192cf9bd2fb7490497d7d76c619a0cfc70abae026b5068fb66db0138b04f890917daad66ca1f7baabdcbb5282e46a2f1c6ff2e8c241ff16ef31e918ca1387a15bc2ceadb2f75ce68fcff08186b5b901a499efe1f674319b503ff8b6fc004b71d0ecb94253f38c58349ab749e72f492e541e7504d25a0bfe791f53eb95c4524431b0f952fc3d7c7204a2a4aab44d33fe09cb36b337339e2a004bf15dfd925b63930905972749441a0c6e50ec9b1748a4cfbacf10b402ebd9c0074fcb38d236fd3146f60232862b0501e8e6caa9f81c223de03ba7b25a1d4bc2d031901dc445f25ce302d2189b8b8de443bc6f562f941b55595655193ab6b84c1ec2302ca056c70e8efb1cad909c50e82e0b7da9ad64202d149e4e837409 \ No newline at end of file +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIB5Zzk1MbB0e30cDCjV7H3c712RsaRJgLn5GgUvbSRzH +-----END PRIVATE KEY----- diff --git a/tests/repository_data/keystore/timestamp_key.pub b/tests/repository_data/keystore/timestamp_key.pub deleted file mode 100644 index 69ba7ded1d..0000000000 --- a/tests/repository_data/keystore/timestamp_key.pub +++ /dev/null @@ -1 +0,0 @@ -{"keyval": {"public": "82ccf6ac47298ff43bfa0cd639868894e305a99c723ff0515ae2e9856eb5bbf4"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} \ No newline at end of file diff --git a/tests/repository_simulator.py b/tests/repository_simulator.py index de6bbb1422..e4853f6b1d 100644 --- a/tests/repository_simulator.py +++ b/tests/repository_simulator.py @@ -53,8 +53,7 @@ from urllib import parse import securesystemslib.hash as sslib_hash -from securesystemslib.keys import generate_ed25519_key -from securesystemslib.signer import SSlibKey, SSlibSigner +from securesystemslib.signer import CryptoSigner, Signer from tuf.api.exceptions import DownloadHTTPError from tuf.api.metadata import ( @@ -62,7 +61,6 @@ TOP_LEVEL_ROLE_NAMES, DelegatedRole, Delegations, - Key, Metadata, MetaFile, Root, @@ -108,7 +106,7 @@ def __init__(self) -> None: # signers are used on-demand at fetch time to sign metadata # keys are roles, values are dicts of {keyid: signer} - self.signers: Dict[str, Dict[str, SSlibSigner]] = {} + self.signers: Dict[str, Dict[str, Signer]] = {} # target downloads are served from this dict self.target_files: Dict[str, RepositoryTarget] = {} @@ -153,23 +151,18 @@ def all_targets(self) -> Iterator[Tuple[str, Targets]]: for role, md in self.md_delegates.items(): yield role, md.signed - @staticmethod - def create_key() -> Tuple[Key, SSlibSigner]: - key = generate_ed25519_key() - return SSlibKey.from_securesystemslib_key(key), SSlibSigner(key) - - def add_signer(self, role: str, signer: SSlibSigner) -> None: + def add_signer(self, role: str, signer: Signer) -> None: if role not in self.signers: self.signers[role] = {} - self.signers[role][signer.key_dict["keyid"]] = signer + self.signers[role][signer.public_key.keyid] = signer def rotate_keys(self, role: str) -> None: """remove all keys for role, then add threshold of new keys""" self.root.roles[role].keyids.clear() self.signers[role].clear() for _ in range(0, self.root.roles[role].threshold): - key, signer = self.create_key() - self.root.add_key(key, role) + signer = CryptoSigner.generate_ed25519() + self.root.add_key(signer.public_key, role) self.add_signer(role, signer) def _initialize(self) -> None: @@ -181,8 +174,8 @@ def _initialize(self) -> None: self.md_root = Metadata(Root(expires=self.safe_expiry)) for role in TOP_LEVEL_ROLE_NAMES: - key, signer = self.create_key() - self.md_root.signed.add_key(key, role) + signer = CryptoSigner.generate_ed25519() + self.md_root.signed.add_key(signer.public_key, role) self.add_signer(role, signer) self.publish_root() @@ -370,8 +363,8 @@ def add_delegation( delegator.delegations.roles[role.name] = role # By default add one new key for the role - key, signer = self.create_key() - delegator.add_key(key, role.name) + signer = CryptoSigner.generate_ed25519() + delegator.add_key(signer.public_key, role.name) self.add_signer(role.name, signer) # Add metadata for the role @@ -396,7 +389,7 @@ def add_succinct_roles( "Can't add a succinct_roles when delegated roles are used" ) - key, signer = self.create_key() + signer = CryptoSigner.generate_ed25519() succinct_roles = SuccinctRoles([], 1, bit_length, name_prefix) delegator.delegations = Delegations({}, None, succinct_roles) @@ -408,7 +401,7 @@ def add_succinct_roles( self.add_signer(delegated_name, signer) - delegator.add_key(key) + delegator.add_key(signer.public_key) def write(self) -> None: """Dump current repository metadata to self.dump_dir diff --git a/tests/test_api.py b/tests/test_api.py index 502b2da128..1a5930e78c 100755 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -14,20 +14,15 @@ from copy import copy, deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, ClassVar, Dict, Optional +from typing import ClassVar, Dict, Optional from securesystemslib import exceptions as sslib_exceptions from securesystemslib import hash as sslib_hash -from securesystemslib.interface import ( - import_ed25519_privatekey_from_file, - import_ed25519_publickey_from_file, -) -from securesystemslib.keys import generate_ed25519_key from securesystemslib.signer import ( + CryptoSigner, + Key, SecretsHandler, Signer, - SSlibKey, - SSlibSigner, ) from tests import utils @@ -37,7 +32,6 @@ TOP_LEVEL_ROLE_NAMES, DelegatedRole, Delegations, - Key, Metadata, MetaFile, Root, @@ -62,7 +56,7 @@ class TestMetadata(unittest.TestCase): temporary_directory: ClassVar[str] repo_dir: ClassVar[str] keystore_dir: ClassVar[str] - keystore: ClassVar[Dict[str, Dict[str, Any]]] + signers: ClassVar[Dict[str, Signer]] @classmethod def setUpClass(cls) -> None: @@ -86,13 +80,17 @@ def setUpClass(cls) -> None: os.path.join(test_repo_data, "keystore"), cls.keystore_dir ) - # Load keys into memory - cls.keystore = {} - for role in ["delegation", Snapshot.type, Targets.type, Timestamp.type]: - cls.keystore[role] = import_ed25519_privatekey_from_file( - os.path.join(cls.keystore_dir, role + "_key"), - password="password", - ) + path = os.path.join(cls.repo_dir, "metadata", "root.json") + root = Metadata[Root].from_file(path).signed + + # Load signers + + cls.signers = {} + for role in [Snapshot.type, Targets.type, Timestamp.type]: + uri = f"file2:{os.path.join(cls.keystore_dir, role + '_key')}" + role_obj = root.get_delegated_role(role) + key = root.get_key(role_obj.keyids[0]) + cls.signers[role] = CryptoSigner.from_priv_key_uri(uri, key) @classmethod def tearDownClass(cls) -> None: @@ -218,9 +216,8 @@ def test_sign_verify(self) -> None: with self.assertRaises(sslib_exceptions.VerificationError): snapshot_key.verify_signature(sig, data) - sslib_signer = SSlibSigner(self.keystore[Snapshot.type]) # Append a new signature with the unrelated key and assert that ... - snapshot_sig = md_obj.sign(sslib_signer, append=True) + snapshot_sig = md_obj.sign(self.signers[Snapshot.type], append=True) # ... there are now two signatures, and self.assertEqual(len(md_obj.signatures), 2) # ... both are valid for the corresponding keys. @@ -229,9 +226,8 @@ def test_sign_verify(self) -> None: # ... the returned (appended) signature is for snapshot key self.assertEqual(snapshot_sig.keyid, snapshot_keyid) - sslib_signer = SSlibSigner(self.keystore[Timestamp.type]) # Create and assign (don't append) a new signature and assert that ... - ts_sig = md_obj.sign(sslib_signer, append=False) + ts_sig = md_obj.sign(self.signers[Timestamp.type], append=False) # ... there now is only one signature, self.assertEqual(len(md_obj.signatures), 1) # ... valid for that key. @@ -468,9 +464,7 @@ def test_signed_verify_delegate(self) -> None: # verify succeeds when we correct the new signature and reach the # threshold of 2 keys - snapshot_md.sign( - SSlibSigner(self.keystore[Timestamp.type]), append=True - ) + snapshot_md.sign(self.signers[Timestamp.type], append=True) root.verify_delegate( Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures ) @@ -526,11 +520,10 @@ def test_signed_get_verification_result(self) -> None: key2_id = root.signed.roles[Timestamp.type].keyids[0] key2 = root.signed.get_key(key2_id) - priv_key2 = self.keystore[Timestamp.type] key3_id = "123456789abcdefg" - priv_key4 = self.keystore[Snapshot.type] - key4_id = priv_key4["keyid"] + + key4_id = self.signers[Snapshot.type].public_key.keyid # Test: 1 authorized key, 1 valid signature result = root.signed.get_verification_result( @@ -563,7 +556,7 @@ def test_signed_get_verification_result(self) -> None: # Test: 3 authorized keys, 1 valid signature, 1 invalid signature, 1 # key missing key data - root.sign(SSlibSigner(priv_key2), append=True) + root.sign(self.signers[Timestamp.type], append=True) result = root.signed.get_verification_result( Root.type, root.signed_bytes, root.signatures ) @@ -573,7 +566,7 @@ def test_signed_get_verification_result(self) -> None: # Test: 3 authorized keys, 1 valid signature, 1 invalid signature, 1 # key missing key data, 1 ignored unrelated signature - root.sign(SSlibSigner(priv_key4), append=True) + root.sign(self.signers[Snapshot.type], append=True) self.assertEqual( set(root.signatures.keys()), {key1_id, key2_id, key4_id} ) @@ -593,9 +586,6 @@ def test_root_get_root_verification_result(self) -> None: key2_id = root.signed.roles[Timestamp.type].keyids[0] key2 = root.signed.get_key(key2_id) - priv_key2 = self.keystore[Timestamp.type] - - priv_key4 = self.keystore[Snapshot.type] # Test: Verify with no previous root version result = root.signed.get_root_verification_result( @@ -640,7 +630,7 @@ def test_root_get_root_verification_result(self) -> None: self.assertEqual(result.unsigned, {key2_id: key2}) # Test: Sign root with both keys - root.sign(SSlibSigner(priv_key2), append=True) + root.sign(self.signers[Timestamp.type], append=True) result = root.signed.get_root_verification_result( prev_root.signed, root.signed_bytes, root.signatures ) @@ -649,7 +639,7 @@ def test_root_get_root_verification_result(self) -> None: self.assertEqual(result.unsigned, {}) # Test: Sign root with an unrelated key - root.sign(SSlibSigner(priv_key4), append=True) + root.sign(self.signers[Snapshot.type], append=True) result = root.signed.get_root_verification_result( prev_root.signed, root.signed_bytes, root.signatures ) @@ -675,43 +665,28 @@ def test_root_get_root_verification_result(self) -> None: self.assertEqual(result.signed, {key1_id: key1, key2_id: key2}) self.assertEqual(result.unsigned, {}) - def test_key_class(self) -> None: - # Test if from_securesystemslib_key removes the private key from keyval - # of a securesystemslib key dictionary. - sslib_key = generate_ed25519_key() - key = SSlibKey.from_securesystemslib_key(sslib_key) - self.assertFalse("private" in key.keyval) - def test_root_add_key_and_revoke_key(self) -> None: root_path = os.path.join(self.repo_dir, "metadata", "root.json") root = Metadata[Root].from_file(root_path) # Create a new key - root_key2 = import_ed25519_publickey_from_file( - os.path.join(self.keystore_dir, "root_key2.pub") - ) - keyid = root_key2["keyid"] - key_metadata = SSlibKey( - keyid, - root_key2["keytype"], - root_key2["scheme"], - root_key2["keyval"], - ) + signer = CryptoSigner.generate_ecdsa() + key = signer.public_key # Assert that root does not contain the new key - self.assertNotIn(keyid, root.signed.roles[Root.type].keyids) - self.assertNotIn(keyid, root.signed.keys) + self.assertNotIn(key.keyid, root.signed.roles[Root.type].keyids) + self.assertNotIn(key.keyid, root.signed.keys) # Assert that add_key with old argument order will raise an error with self.assertRaises(ValueError): - root.signed.add_key(Root.type, key_metadata) + root.signed.add_key(Root.type, key) # Add new root key - root.signed.add_key(key_metadata, Root.type) + root.signed.add_key(key, Root.type) # Assert that key is added - self.assertIn(keyid, root.signed.roles[Root.type].keyids) - self.assertIn(keyid, root.signed.keys) + self.assertIn(key.keyid, root.signed.roles[Root.type].keyids) + self.assertIn(key.keyid, root.signed.keys) # Confirm that the newly added key does not break # the object serialization @@ -719,30 +694,30 @@ def test_root_add_key_and_revoke_key(self) -> None: # Try adding the same key again and assert its ignored. pre_add_keyid = root.signed.roles[Root.type].keyids.copy() - root.signed.add_key(key_metadata, Root.type) + root.signed.add_key(key, Root.type) self.assertEqual(pre_add_keyid, root.signed.roles[Root.type].keyids) # Add the same key to targets role as well - root.signed.add_key(key_metadata, Targets.type) + root.signed.add_key(key, Targets.type) # Add the same key to a nonexistent role. with self.assertRaises(ValueError): - root.signed.add_key(key_metadata, "nosuchrole") + root.signed.add_key(key, "nosuchrole") # Remove the key from root role (targets role still uses it) - root.signed.revoke_key(keyid, Root.type) - self.assertNotIn(keyid, root.signed.roles[Root.type].keyids) - self.assertIn(keyid, root.signed.keys) + root.signed.revoke_key(key.keyid, Root.type) + self.assertNotIn(key.keyid, root.signed.roles[Root.type].keyids) + self.assertIn(key.keyid, root.signed.keys) # Remove the key from targets as well - root.signed.revoke_key(keyid, Targets.type) - self.assertNotIn(keyid, root.signed.roles[Targets.type].keyids) - self.assertNotIn(keyid, root.signed.keys) + root.signed.revoke_key(key.keyid, Targets.type) + self.assertNotIn(key.keyid, root.signed.roles[Targets.type].keyids) + self.assertNotIn(key.keyid, root.signed.keys) with self.assertRaises(ValueError): root.signed.revoke_key("nosuchkey", Root.type) with self.assertRaises(ValueError): - root.signed.revoke_key(keyid, "nosuchrole") + root.signed.revoke_key(key.keyid, "nosuchrole") def test_is_target_in_pathpattern(self) -> None: supported_use_cases = [ @@ -1156,14 +1131,16 @@ class TestSimpleEnvelope(unittest.TestCase): def setUpClass(cls) -> None: repo_data_dir = Path(utils.TESTS_DIR) / "repository_data" cls.metadata_dir = repo_data_dir / "repository" / "metadata" - cls.signer_store = {} + cls.keystore_dir = repo_data_dir / "keystore" + cls.signers = {} + root_path = os.path.join(cls.metadata_dir, "root.json") + root: Root = Metadata.from_file(root_path).signed + for role in [Snapshot, Targets, Timestamp]: - key_path = repo_data_dir / "keystore" / f"{role.type}_key" - key = import_ed25519_privatekey_from_file( - str(key_path), - password="password", - ) - cls.signer_store[role.type] = SSlibSigner(key) + uri = f"file2:{os.path.join(cls.keystore_dir, role.type + '_key')}" + role_obj = root.get_delegated_role(role.type) + key = root.get_key(role_obj.keyids[0]) + cls.signers[role.type] = CryptoSigner.from_priv_key_uri(uri, key) def test_serialization(self) -> None: """Basic de/serialization test. @@ -1224,18 +1201,14 @@ def test_verify_delegate(self) -> None: metadata = Metadata.from_file(str(metadata_path)) self.assertIsInstance(metadata.signed, role) - signer = self.signer_store[role.type] - self.assertIn( - signer.key_dict["keyid"], root.roles[role.type].keyids - ) + signer = self.signers[role.type] + self.assertIn(signer.public_key.keyid, root.roles[role.type].keyids) envelope = SimpleEnvelope.from_signed(metadata.signed) envelope.sign(signer) self.assertTrue(len(envelope.signatures) == 1) - root.verify_delegate( - role.type, envelope.pae(), envelope.signatures_dict - ) + root.verify_delegate(role.type, envelope.pae(), envelope.signatures) # Run unit test. diff --git a/tests/test_trusted_metadata_set.py b/tests/test_trusted_metadata_set.py index 377c7b5fa7..2811cf25ef 100644 --- a/tests/test_trusted_metadata_set.py +++ b/tests/test_trusted_metadata_set.py @@ -7,11 +7,7 @@ from datetime import datetime, timezone from typing import Callable, ClassVar, Dict, List, Optional, Tuple -from securesystemslib.interface import ( - import_ed25519_privatekey_from_file, - import_rsa_privatekey_from_file, -) -from securesystemslib.signer import SSlibSigner +from securesystemslib.signer import Signer from tests import utils from tuf.api import exceptions @@ -38,7 +34,7 @@ class TestTrustedMetadataSet(unittest.TestCase): """Tests for all public API of the TrustedMetadataSet class.""" - keystore: ClassVar[Dict[str, SSlibSigner]] + keystore: ClassVar[Dict[str, Signer]] metadata: ClassVar[Dict[str, bytes]] repo_dir: ClassVar[str] @@ -79,16 +75,19 @@ def setUpClass(cls) -> None: keystore_dir = os.path.join( utils.TESTS_DIR, "repository_data", "keystore" ) + root = Metadata[Root].from_bytes(cls.metadata[Root.type]).signed + cls.keystore = {} - root_key_dict = import_rsa_privatekey_from_file( - os.path.join(keystore_dir, Root.type + "_key"), password="password" - ) - cls.keystore[Root.type] = SSlibSigner(root_key_dict) - for role in ["delegation", Snapshot.type, Targets.type, Timestamp.type]: - key_dict = import_ed25519_privatekey_from_file( - os.path.join(keystore_dir, role + "_key"), password="password" - ) - cls.keystore[role] = SSlibSigner(key_dict) + for role in [ + Root.type, + Snapshot.type, + Targets.type, + Timestamp.type, + ]: + uri = f"file2:{os.path.join(keystore_dir, role + '_key')}" + role_obj = root.get_delegated_role(role) + key = root.get_key(role_obj.keyids[0]) + cls.keystore[role] = Signer.from_priv_key_uri(uri, key) def hashes_length_modifier(timestamp: Timestamp) -> None: timestamp.snapshot_meta.hashes = None diff --git a/tests/test_updater_key_rotations.py b/tests/test_updater_key_rotations.py index 6221280631..8ff0fb577e 100644 --- a/tests/test_updater_key_rotations.py +++ b/tests/test_updater_key_rotations.py @@ -12,7 +12,7 @@ from dataclasses import dataclass from typing import ClassVar, Dict, List, Optional, Type -from securesystemslib.signer import SSlibSigner +from securesystemslib.signer import CryptoSigner, Signer from tests import utils from tests.repository_simulator import RepositorySimulator @@ -37,18 +37,16 @@ class TestUpdaterKeyRotations(unittest.TestCase): dump_dir: Optional[str] = None temp_dir: ClassVar[tempfile.TemporaryDirectory] keys: ClassVar[List[Key]] - signers: ClassVar[List[SSlibSigner]] + signers: ClassVar[List[Signer]] @classmethod def setUpClass(cls) -> None: cls.temp_dir = tempfile.TemporaryDirectory() # Pre-create a bunch of keys and signers - cls.keys = [] cls.signers = [] for _ in range(10): - key, signer = RepositorySimulator.create_key() - cls.keys.append(key) + signer = CryptoSigner.generate_ed25519() cls.signers.append(signer) @classmethod @@ -180,7 +178,7 @@ def test_root_rotation(self, root_versions: List[MdVersion]) -> None: self.sim.root.roles[Root.type].threshold = rootver.threshold for i in rootver.keys: - self.sim.root.add_key(self.keys[i], Root.type) + self.sim.root.add_key(self.signers[i].public_key, Root.type) for i in rootver.sigs: self.sim.add_signer(Root.type, self.signers[i]) self.sim.root.version += 1 @@ -249,7 +247,7 @@ def test_non_root_rotations(self, md_version: MdVersion) -> None: self.sim.root.roles[role].threshold = md_version.threshold for i in md_version.keys: - self.sim.root.add_key(self.keys[i], role) + self.sim.root.add_key(self.signers[i].public_key, role) for i in md_version.sigs: self.sim.add_signer(role, self.signers[i]) diff --git a/tests/test_updater_ng.py b/tests/test_updater_ng.py index b853d40dda..6f077bf1e7 100644 --- a/tests/test_updater_ng.py +++ b/tests/test_updater_ng.py @@ -14,8 +14,7 @@ from typing import Callable, ClassVar, List from unittest.mock import MagicMock, patch -from securesystemslib.interface import import_rsa_privatekey_from_file -from securesystemslib.signer import SSlibSigner +from securesystemslib.signer import Signer from tests import utils from tuf.api import exceptions @@ -127,15 +126,17 @@ def _modify_repository_root( role_path = os.path.join( self.repository_directory, "metadata", "root.json" ) - root = Metadata.from_file(role_path) + root = Metadata[Root].from_file(role_path) modification_func(root) if bump_version: root.signed.version += 1 root_key_path = os.path.join(self.keystore_directory, "root_key") - root_key_dict = import_rsa_privatekey_from_file( - root_key_path, password="password" - ) - signer = SSlibSigner(root_key_dict) + + uri = f"file2:{root_key_path}" + role = root.signed.get_delegated_role(Root.type) + key = root.signed.get_key(role.keyids[0]) + signer = Signer.from_priv_key_uri(uri, key) + root.sign(signer) root.to_file( os.path.join(self.repository_directory, "metadata", "root.json") diff --git a/tuf/api/dsse.py b/tuf/api/dsse.py index 1e00a49804..74276866e7 100644 --- a/tuf/api/dsse.py +++ b/tuf/api/dsse.py @@ -1,7 +1,7 @@ """Low-level TUF DSSE API. (experimental!)""" import json -from typing import Dict, Generic, Type, cast +from typing import Generic, Type, cast from securesystemslib.dsse import Envelope as BaseSimpleEnvelope @@ -42,25 +42,18 @@ class SimpleEnvelope(Generic[T], BaseSimpleEnvelope): delegator.verify_delegate( role_name, envelope.pae(), # Note, how we don't pass ``envelope.payload``! - envelope.signatures_dict, + envelope.signatures, ) Attributes: payload: Serialized payload bytes. payload_type: Payload string identifier. - signatures: List of ``Signature`` objects. - signatures_dict: Ordered dictionary of keyids to ``Signature`` objects. + signatures: Ordered dictionary of keyids to ``Signature`` objects. """ _DEFAULT_PAYLOAD_TYPE = "application/vnd.tuf+json" - @property - def signatures_dict(self) -> Dict: - """Convenience alias for ``self.signatures`` mapped to keyids.""" - # TODO: Propose changing ``signatures`` list to dict upstream - return {sig.keyid: sig for sig in self.signatures} - @classmethod def from_bytes(cls, data: bytes) -> "SimpleEnvelope[T]": """Load envelope from JSON bytes. @@ -126,7 +119,7 @@ def from_signed(cls, signed: T) -> "SimpleEnvelope[T]": except Exception as e: # noqa: BLE001 raise SerializationError from e - return cls(json_bytes, cls._DEFAULT_PAYLOAD_TYPE, []) + return cls(json_bytes, cls._DEFAULT_PAYLOAD_TYPE, {}) def get_signed(self) -> T: """Extract and deserialize payload JSON bytes from envelope. diff --git a/tuf/ngclient/_internal/trusted_metadata_set.py b/tuf/ngclient/_internal/trusted_metadata_set.py index ba4be4a2c6..09977285c5 100644 --- a/tuf/ngclient/_internal/trusted_metadata_set.py +++ b/tuf/ngclient/_internal/trusted_metadata_set.py @@ -500,7 +500,7 @@ def _load_from_simple_envelope( if role_name is None: role_name = role.type delegator.verify_delegate( - role_name, envelope.pae(), envelope.signatures_dict + role_name, envelope.pae(), envelope.signatures ) signed = envelope.get_signed() @@ -509,4 +509,4 @@ def _load_from_simple_envelope( f"Expected '{role.type}', got '{signed.type}'" ) - return signed, envelope.pae(), envelope.signatures_dict + return signed, envelope.pae(), envelope.signatures