2021-03-04 15:25:36 +00:00
|
|
|
# Copyright New York University and the TUF contributors
|
|
|
|
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
|
|
2025-02-19 12:44:21 +00:00
|
|
|
"""``tuf.api.serialization.json`` module provides concrete implementations to
|
|
|
|
|
serialize and deserialize TUF role metadata to and from the JSON wireline
|
|
|
|
|
format for transportation, and to serialize the 'signed' part of TUF role
|
|
|
|
|
metadata to the OLPC Canonical JSON format for signature generation and
|
|
|
|
|
verification.
|
|
|
|
|
"""
|
2025-02-19 11:42:13 +00:00
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2021-02-08 16:32:06 +00:00
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from securesystemslib.formats import encode_canonical
|
|
|
|
|
|
2021-03-04 15:31:36 +00:00
|
|
|
# ... to allow de/serializing Metadata and Signed objects here, while also
|
|
|
|
|
# creating default de/serializers there (see metadata local scope imports).
|
|
|
|
|
# NOTE: A less desirable alternative would be to add more abstraction layers.
|
2021-02-08 16:32:06 +00:00
|
|
|
from tuf.api.metadata import Metadata, Signed
|
2021-03-11 17:00:19 +00:00
|
|
|
from tuf.api.serialization import (
|
|
|
|
|
DeserializationError,
|
|
|
|
|
MetadataDeserializer,
|
|
|
|
|
MetadataSerializer,
|
|
|
|
|
SerializationError,
|
|
|
|
|
SignedSerializer,
|
|
|
|
|
)
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class JSONDeserializer(MetadataDeserializer):
|
2021-04-27 11:08:24 +00:00
|
|
|
"""Provides JSON to Metadata deserialize method."""
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
def deserialize(self, raw_data: bytes) -> Metadata:
|
2021-04-27 11:08:24 +00:00
|
|
|
"""Deserialize utf-8 encoded JSON bytes into Metadata object."""
|
2021-02-09 14:36:49 +00:00
|
|
|
try:
|
2021-03-05 10:46:00 +00:00
|
|
|
json_dict = json.loads(raw_data.decode("utf-8"))
|
2021-03-05 11:54:31 +00:00
|
|
|
metadata_obj = Metadata.from_dict(json_dict)
|
2021-02-09 14:36:49 +00:00
|
|
|
|
2024-04-30 07:32:37 +00:00
|
|
|
except Exception as e:
|
2022-02-21 14:04:55 +00:00
|
|
|
raise DeserializationError("Failed to deserialize JSON") from e
|
2021-02-08 16:32:06 +00:00
|
|
|
|
2021-03-05 11:54:31 +00:00
|
|
|
return metadata_obj
|
|
|
|
|
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
class JSONSerializer(MetadataSerializer):
|
2021-03-09 16:35:08 +00:00
|
|
|
"""Provides Metadata to JSON serialize method.
|
2021-02-08 16:32:06 +00:00
|
|
|
|
2021-12-21 16:12:30 +00:00
|
|
|
Args:
|
2021-02-08 16:32:06 +00:00
|
|
|
compact: A boolean indicating if the JSON bytes generated in
|
2021-12-21 16:12:30 +00:00
|
|
|
'serialize' should be compact by excluding whitespace.
|
|
|
|
|
validate: Check that the metadata object can be deserialized again
|
|
|
|
|
without change of contents and thus find common mistakes.
|
|
|
|
|
This validation might slow down serialization significantly.
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
"""
|
2021-03-11 17:00:19 +00:00
|
|
|
|
2025-02-19 11:42:13 +00:00
|
|
|
def __init__(self, compact: bool = False, validate: bool | None = False):
|
2021-02-08 16:32:06 +00:00
|
|
|
self.compact = compact
|
2021-12-21 16:12:30 +00:00
|
|
|
self.validate = validate
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
def serialize(self, metadata_obj: Metadata) -> bytes:
|
2021-04-27 11:08:24 +00:00
|
|
|
"""Serialize Metadata object into utf-8 encoded JSON bytes."""
|
2021-12-21 16:12:30 +00:00
|
|
|
|
2021-02-09 14:36:49 +00:00
|
|
|
try:
|
2021-03-11 17:00:19 +00:00
|
|
|
indent = None if self.compact else 1
|
|
|
|
|
separators = (",", ":") if self.compact else (",", ": ")
|
|
|
|
|
json_bytes = json.dumps(
|
|
|
|
|
metadata_obj.to_dict(),
|
|
|
|
|
indent=indent,
|
|
|
|
|
separators=separators,
|
|
|
|
|
sort_keys=True,
|
|
|
|
|
).encode("utf-8")
|
|
|
|
|
|
2021-12-21 16:12:30 +00:00
|
|
|
if self.validate:
|
|
|
|
|
try:
|
|
|
|
|
new_md_obj = JSONDeserializer().deserialize(json_bytes)
|
|
|
|
|
if metadata_obj != new_md_obj:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Metadata changes if you serialize and deserialize."
|
|
|
|
|
)
|
2024-04-30 07:32:37 +00:00
|
|
|
except Exception as e:
|
2021-12-21 16:12:30 +00:00
|
|
|
raise ValueError("Metadata cannot be validated!") from e
|
|
|
|
|
|
2024-04-30 07:32:37 +00:00
|
|
|
except Exception as e:
|
2022-02-21 14:04:55 +00:00
|
|
|
raise SerializationError("Failed to serialize JSON") from e
|
2021-02-08 16:32:06 +00:00
|
|
|
|
2021-03-05 11:54:31 +00:00
|
|
|
return json_bytes
|
|
|
|
|
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
class CanonicalJSONSerializer(SignedSerializer):
|
2021-04-27 11:08:24 +00:00
|
|
|
"""Provides Signed to OLPC Canonical JSON serialize method."""
|
2021-02-08 16:32:06 +00:00
|
|
|
|
|
|
|
|
def serialize(self, signed_obj: Signed) -> bytes:
|
2021-03-09 16:35:08 +00:00
|
|
|
"""Serialize Signed object into utf-8 encoded OLPC Canonical JSON
|
|
|
|
|
bytes.
|
|
|
|
|
"""
|
2021-02-09 14:36:49 +00:00
|
|
|
try:
|
2021-03-04 11:46:16 +00:00
|
|
|
signed_dict = signed_obj.to_dict()
|
2025-01-30 16:20:39 +00:00
|
|
|
canon_str = encode_canonical(signed_dict)
|
|
|
|
|
# encode_canonical cannot return None if output_function is not set
|
|
|
|
|
assert canon_str is not None # noqa: S101
|
|
|
|
|
canonical_bytes = canon_str.encode("utf-8")
|
2021-02-09 14:36:49 +00:00
|
|
|
|
2024-04-30 07:32:37 +00:00
|
|
|
except Exception as e:
|
2021-03-09 16:27:39 +00:00
|
|
|
raise SerializationError from e
|
2021-03-05 11:54:31 +00:00
|
|
|
|
|
|
|
|
return canonical_bytes
|