Example #1
0
 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))
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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))
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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())
Example #8
0
    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
            )
Example #9
0
    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)
Example #10
0
 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)))
Example #11
0
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)
Example #12
0
        "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 " +
Example #13
0
 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)))
Example #14
0
    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