def test_serialize_with_validate(self) -> None: # Assert that by changing one required attribute validation will fail. root = Metadata.from_file( os.path.join(self.repo_dir, "metadata", "root.json")) root.signed.version = 0 with self.assertRaises(SerializationError): root.to_bytes(JSONSerializer(validate=True))
def to_file( self, filename: str, serializer: Optional[MetadataSerializer] = None, storage_backend: Optional[StorageBackendInterface] = None, ) -> None: """Writes TUF metadata to file storage. Arguments: filename: The path to write the file to. serializer: A MetadataSerializer subclass instance that implements the desired wireline format serialization. Per default a JSONSerializer is used. storage_backend: An object that implements securesystemslib.storage.StorageBackendInterface. Per default a (local) FilesystemBackend is used. Raises: tuf.api.serialization.SerializationError: The metadata object cannot be serialized. securesystemslib.exceptions.StorageError: The file cannot be written. """ if serializer is None: # Use local scope import to avoid circular import errors # pylint: disable=import-outside-toplevel from tuf.api.serialization.json import JSONSerializer serializer = JSONSerializer(compact=True) with tempfile.TemporaryFile() as temp_file: temp_file.write(serializer.serialize(self)) persist_temp_file(temp_file, filename, storage_backend)
def publish_root(self) -> None: """Sign and store a new serialized version of root.""" self.md_root.signatures.clear() for signer in self.signers[Root.type].values(): self.md_root.sign(signer, append=True) self.signed_roots.append(self.md_root.to_bytes(JSONSerializer())) logger.debug("Published root v%d", self.root.version)
def modify_metadata(cls, rolename: str, modification_func: Callable) -> bytes: """Instantiate metadata from rolename type, call modification_func and sign it again with self.keystore[rolename] signer. Attributes: rolename: Denoting the name of the metadata which will be modified. modification_func: Function that will be called to modify the signed portion of metadata bytes. """ metadata = Metadata.from_bytes(cls.metadata[rolename]) modification_func(metadata.signed) metadata.sign(cls.keystore[rolename]) return metadata.to_bytes(JSONSerializer(validate=True))
def test_sign_verify(self) -> None: root_path = os.path.join(self.repo_dir, "metadata", "root.json") root = Metadata[Root].from_file(root_path).signed # Locate the public keys we need from root targets_keyid = next(iter(root.roles[Targets.type].keyids)) targets_key = root.keys[targets_keyid] snapshot_keyid = next(iter(root.roles[Snapshot.type].keyids)) snapshot_key = root.keys[snapshot_keyid] timestamp_keyid = next(iter(root.roles[Timestamp.type].keyids)) timestamp_key = root.keys[timestamp_keyid] # Load sample metadata (targets) and assert ... path = os.path.join(self.repo_dir, "metadata", "targets.json") md_obj = Metadata.from_file(path) # ... it has a single existing signature, self.assertEqual(len(md_obj.signatures), 1) # ... which is valid for the correct key. targets_key.verify_signature(md_obj) with self.assertRaises(exceptions.UnsignedMetadataError): snapshot_key.verify_signature(md_obj) # Test verifying with explicitly set serializer targets_key.verify_signature(md_obj, CanonicalJSONSerializer()) with self.assertRaises(exceptions.UnsignedMetadataError): targets_key.verify_signature( md_obj, JSONSerializer()) # type: ignore[arg-type] sslib_signer = SSlibSigner(self.keystore[Snapshot.type]) # Append a new signature with the unrelated key and assert that ... sig = md_obj.sign(sslib_signer, append=True) # ... there are now two signatures, and self.assertEqual(len(md_obj.signatures), 2) # ... both are valid for the corresponding keys. targets_key.verify_signature(md_obj) snapshot_key.verify_signature(md_obj) # ... the returned (appended) signature is for snapshot key self.assertEqual(sig.keyid, snapshot_keyid) sslib_signer = SSlibSigner(self.keystore[Timestamp.type]) # Create and assign (don't append) a new signature and assert that ... md_obj.sign(sslib_signer, append=False) # ... there now is only one signature, self.assertEqual(len(md_obj.signatures), 1) # ... valid for that key. timestamp_key.verify_signature(md_obj) with self.assertRaises(exceptions.UnsignedMetadataError): targets_key.verify_signature(md_obj)
def test_to_from_bytes(self) -> None: for metadata in TOP_LEVEL_ROLE_NAMES: path = os.path.join(self.repo_dir, "metadata", metadata + ".json") with open(path, "rb") as f: metadata_bytes = f.read() md_obj = Metadata.from_bytes(metadata_bytes) # Comparate that from_bytes/to_bytes doesn't change the content # for two cases for the serializer: noncompact and compact. # Case 1: test noncompact by overriding the default serializer. self.assertEqual(md_obj.to_bytes(JSONSerializer()), metadata_bytes) # Case 2: test compact by using the default serializer. obj_bytes = md_obj.to_bytes() metadata_obj_2 = Metadata.from_bytes(obj_bytes) self.assertEqual(metadata_obj_2.to_bytes(), obj_bytes)
def fetch_metadata(self, role: str, version: Optional[int] = None) -> bytes: """Return signed metadata for 'role', using 'version' if it is given. If version is None, non-versioned metadata is being requested. """ self.fetch_tracker.metadata.append((role, version)) # decode role for the metadata role = parse.unquote(role, encoding="utf-8") if role == Root.type: # return a version previously serialized in publish_root() if version is None or version > len(self.signed_roots): raise DownloadHTTPError(f"Unknown root version {version}", 404) logger.debug("fetched root version %d", version) return self.signed_roots[version - 1] # sign and serialize the requested metadata md: Optional[Metadata] if role == Timestamp.type: md = self.md_timestamp elif role == Snapshot.type: md = self.md_snapshot elif role == Targets.type: md = self.md_targets else: md = self.md_delegates.get(role) if md is None: raise DownloadHTTPError(f"Unknown role {role}", 404) md.signatures.clear() for signer in self.signers[role].values(): md.sign(signer, append=True) logger.debug( "fetched %s v%d with %d sigs", role, md.signed.version, len(self.signers[role]), ) return md.to_bytes(JSONSerializer())
def test_to_from_bytes(self): for metadata in ["root", "snapshot", "timestamp", "targets"]: path = os.path.join(self.repo_dir, 'metadata', metadata + '.json') with open(path, 'rb') as f: metadata_bytes = f.read() metadata_obj = Metadata.from_bytes(metadata_bytes) # Comparate that from_bytes/to_bytes doesn't change the content # for two cases for the serializer: noncompact and compact. # Case 1: test noncompact by overriding the default serializer. self.assertEqual( metadata_obj.to_bytes(JSONSerializer()), metadata_bytes ) # Case 2: test compact by using the default serializer. obj_bytes = metadata_obj.to_bytes() metadata_obj_2 = Metadata.from_bytes(obj_bytes) self.assertEqual( metadata_obj_2.to_bytes(), obj_bytes )
def to_bytes(self, serializer: Optional[MetadataSerializer] = None) -> bytes: """Return the serialized TUF file format as bytes. Arguments: serializer: A MetadataSerializer instance that implements the desired serialization format. Default is JSONSerializer. Raises: tuf.api.serialization.SerializationError: The metadata object cannot be serialized. """ if serializer is None: # Use local scope import to avoid circular import errors # pylint: disable=import-outside-toplevel from tuf.api.serialization.json import JSONSerializer serializer = JSONSerializer(compact=True) return serializer.serialize(self)
def test_compact_json(self): path = os.path.join(self.repo_dir, 'metadata', 'targets.json') metadata_obj = Metadata.from_file(path) self.assertTrue( len(JSONSerializer(compact=True).serialize(metadata_obj)) < len(JSONSerializer().serialize(metadata_obj)))
target_path = f"{local_path.parts[-2]}/{local_path.parts[-1]}" target_file_info = TargetFile.from_file(target_path, str(local_path)) # The right bin for a target file is determined by the 'target_path' hash, e.g.: # # target_path: 'repo_example/hashed_bin_delegation.py' # target_path (hash digest): '85e1a6c06305bd9c1e15c7ae565fd16ea304bfc...' # # --> considered hash prefix '85', falls into bin '80-87' bin_for_target = find_hash_bin(target_path) roles[bin_for_target].signed.targets[target_path] = target_file_info # Sign and persist # ---------------- # Sign all metadata and persist to temporary directory at CWD for review # (most notably see 'bins.json' and '80-87.json'). # NOTE: See "Persist metadata" paragraph in 'basic_repo.py' example for more # details about serialization formats and metadata file name convention. PRETTY = JSONSerializer(compact=False) 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) role.sign(signer) filename = f"{role_name}.json" filepath = os.path.join(TMP_DIR, filename) role.to_file(filepath, serializer=PRETTY)
"ed25519", "keyid": keyids[index], "keyval": { "public": public_values[index], "private": private_values[index], }, }) expires_str = "2050-01-01T00:00:00Z" EXPIRY = datetime.strptime(expires_str, "%Y-%m-%dT%H:%M:%SZ") OUT_DIR = "generated_data/ed25519_metadata" if not os.path.exists(OUT_DIR): os.mkdir(OUT_DIR) SERIALIZER = JSONSerializer() def verify_generation(md: Metadata, path: str) -> None: """Verify that newly generated file equals the locally stored one. Args: md: Newly generated metadata object. path: Path to the locally stored metadata file. """ with open(path, "rb") as f: static_md_bytes = f.read() md_bytes = md.to_bytes(SERIALIZER) if static_md_bytes != md_bytes: raise ValueError( f"Generated data != local data at {path}. Generate a new " +
def test_compact_json(self) -> None: path = os.path.join(self.repo_dir, "metadata", "targets.json") md_obj = Metadata.from_file(path) self.assertTrue( len(JSONSerializer(compact=True).serialize(md_obj)) < len( JSONSerializer().serialize(md_obj)))
def test_sign_verify(self): root_path = os.path.join(self.repo_dir, 'metadata', 'root.json') root:Root = Metadata.from_file(root_path).signed # Locate the public keys we need from root targets_keyid = next(iter(root.roles["targets"].keyids)) targets_key = root.keys[targets_keyid] snapshot_keyid = next(iter(root.roles["snapshot"].keyids)) snapshot_key = root.keys[snapshot_keyid] timestamp_keyid = next(iter(root.roles["timestamp"].keyids)) timestamp_key = root.keys[timestamp_keyid] # Load sample metadata (targets) and assert ... path = os.path.join(self.repo_dir, 'metadata', 'targets.json') metadata_obj = Metadata.from_file(path) # ... it has a single existing signature, self.assertEqual(len(metadata_obj.signatures), 1) # ... which is valid for the correct key. targets_key.verify_signature(metadata_obj) with self.assertRaises(exceptions.UnsignedMetadataError): snapshot_key.verify_signature(metadata_obj) # Test verifying with explicitly set serializer targets_key.verify_signature(metadata_obj, CanonicalJSONSerializer()) with self.assertRaises(exceptions.UnsignedMetadataError): targets_key.verify_signature(metadata_obj, JSONSerializer()) sslib_signer = SSlibSigner(self.keystore['snapshot']) # Append a new signature with the unrelated key and assert that ... sig = metadata_obj.sign(sslib_signer, append=True) # ... there are now two signatures, and self.assertEqual(len(metadata_obj.signatures), 2) # ... both are valid for the corresponding keys. targets_key.verify_signature(metadata_obj) snapshot_key.verify_signature(metadata_obj) # ... the returned (appended) signature is for snapshot key self.assertEqual(sig.keyid, snapshot_keyid) sslib_signer = SSlibSigner(self.keystore['timestamp']) # Create and assign (don't append) a new signature and assert that ... metadata_obj.sign(sslib_signer, append=False) # ... there now is only one signature, self.assertEqual(len(metadata_obj.signatures), 1) # ... valid for that key. timestamp_key.verify_signature(metadata_obj) with self.assertRaises(exceptions.UnsignedMetadataError): targets_key.verify_signature(metadata_obj) # Test failure on unknown scheme (securesystemslib UnsupportedAlgorithmError) scheme = timestamp_key.scheme timestamp_key.scheme = "foo" with self.assertRaises(exceptions.UnsignedMetadataError): timestamp_key.verify_signature(metadata_obj) timestamp_key.scheme = scheme # Test failure on broken public key data (securesystemslib CryptoError) public = timestamp_key.keyval["public"] timestamp_key.keyval["public"] = "ffff" with self.assertRaises(exceptions.UnsignedMetadataError): timestamp_key.verify_signature(metadata_obj) timestamp_key.keyval["public"] = public # Test failure with invalid signature (securesystemslib FormatError) sig = metadata_obj.signatures[timestamp_keyid] correct_sig = sig.signature sig.signature = "foo" with self.assertRaises(exceptions.UnsignedMetadataError): timestamp_key.verify_signature(metadata_obj) # Test failure with valid but incorrect signature sig.signature = "ff"*64 with self.assertRaises(exceptions.UnsignedMetadataError): timestamp_key.verify_signature(metadata_obj) sig.signature = correct_sig