mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
Latest ruff complains about Generic[T] not being the last base class in the bases tuple of Simple Envelope (generic-not-last-base-class (PYI059)). This commit applies the default fix by changing the order of the bases. While this can change the MRO, there shouldn't be a change of behavior given the used bases. See https://docs.astral.sh/ruff/rules/generic-not-last-base-class/ for details. Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
153 lines
4.3 KiB
Python
153 lines
4.3 KiB
Python
"""Low-level TUF DSSE API. (experimental!)"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Generic, cast
|
|
|
|
from securesystemslib.dsse import Envelope as BaseSimpleEnvelope
|
|
|
|
# Expose all payload classes to use API independently of ``tuf.api.metadata``.
|
|
from tuf.api._payload import ( # noqa: F401
|
|
_ROOT,
|
|
_SNAPSHOT,
|
|
_TARGETS,
|
|
_TIMESTAMP,
|
|
SPECIFICATION_VERSION,
|
|
TOP_LEVEL_ROLE_NAMES,
|
|
BaseFile,
|
|
DelegatedRole,
|
|
Delegations,
|
|
MetaFile,
|
|
Role,
|
|
Root,
|
|
RootVerificationResult,
|
|
Signed,
|
|
Snapshot,
|
|
SuccinctRoles,
|
|
T,
|
|
TargetFile,
|
|
Targets,
|
|
Timestamp,
|
|
VerificationResult,
|
|
)
|
|
from tuf.api.serialization import DeserializationError, SerializationError
|
|
|
|
|
|
class SimpleEnvelope(BaseSimpleEnvelope, Generic[T]):
|
|
"""Dead Simple Signing Envelope (DSSE) for TUF payloads.
|
|
|
|
* Sign with ``self.sign()`` (inherited).
|
|
* Verify with ``verify_delegate`` on a ``Root`` or ``Targets``
|
|
object::
|
|
|
|
delegator.verify_delegate(
|
|
role_name,
|
|
envelope.pae(), # Note, how we don't pass ``envelope.payload``!
|
|
envelope.signatures,
|
|
)
|
|
|
|
Attributes:
|
|
payload: Serialized payload bytes.
|
|
payload_type: Payload string identifier.
|
|
signatures: Ordered dictionary of keyids to ``Signature`` objects.
|
|
|
|
"""
|
|
|
|
DEFAULT_PAYLOAD_TYPE = "application/vnd.tuf+json"
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data: bytes) -> SimpleEnvelope[T]:
|
|
"""Load envelope from JSON bytes.
|
|
|
|
NOTE: Unlike ``tuf.api.metadata.Metadata.from_bytes``, this method
|
|
does not deserialize the contained payload. Use ``self.get_signed`` to
|
|
deserialize the payload into a ``Signed`` object.
|
|
|
|
Args:
|
|
data: envelope JSON bytes.
|
|
|
|
Raises:
|
|
tuf.api.serialization.DeserializationError:
|
|
data cannot be deserialized.
|
|
|
|
Returns:
|
|
TUF ``SimpleEnvelope`` object.
|
|
"""
|
|
try:
|
|
envelope_dict = json.loads(data.decode())
|
|
envelope = SimpleEnvelope.from_dict(envelope_dict)
|
|
|
|
except Exception as e:
|
|
raise DeserializationError from e
|
|
|
|
return cast("SimpleEnvelope[T]", envelope)
|
|
|
|
def to_bytes(self) -> bytes:
|
|
"""Return envelope as JSON bytes.
|
|
|
|
NOTE: Unlike ``tuf.api.metadata.Metadata.to_bytes``, this method does
|
|
not serialize the payload. Use ``SimpleEnvelope.from_signed`` to
|
|
serialize a ``Signed`` object and wrap it in an SimpleEnvelope.
|
|
|
|
Raises:
|
|
tuf.api.serialization.SerializationError:
|
|
self cannot be serialized.
|
|
"""
|
|
try:
|
|
envelope_dict = self.to_dict()
|
|
json_bytes = json.dumps(envelope_dict).encode()
|
|
|
|
except Exception as e:
|
|
raise SerializationError from e
|
|
|
|
return json_bytes
|
|
|
|
@classmethod
|
|
def from_signed(cls, signed: T) -> SimpleEnvelope[T]:
|
|
"""Serialize payload as JSON bytes and wrap in envelope.
|
|
|
|
Args:
|
|
signed: ``Signed`` object.
|
|
|
|
Raises:
|
|
tuf.api.serialization.SerializationError:
|
|
The signed object cannot be serialized.
|
|
"""
|
|
try:
|
|
signed_dict = signed.to_dict()
|
|
json_bytes = json.dumps(signed_dict).encode()
|
|
|
|
except Exception as e:
|
|
raise SerializationError from e
|
|
|
|
return cls(json_bytes, cls.DEFAULT_PAYLOAD_TYPE, {})
|
|
|
|
def get_signed(self) -> T:
|
|
"""Extract and deserialize payload JSON bytes from envelope.
|
|
|
|
Raises:
|
|
tuf.api.serialization.DeserializationError:
|
|
The signed object cannot be deserialized.
|
|
"""
|
|
|
|
try:
|
|
payload_dict = json.loads(self.payload.decode())
|
|
|
|
# TODO: can we move this to tuf.api._payload?
|
|
_type = payload_dict["_type"]
|
|
if _type == _TARGETS:
|
|
inner_cls: type[Signed] = Targets
|
|
elif _type == _SNAPSHOT:
|
|
inner_cls = Snapshot
|
|
elif _type == _TIMESTAMP:
|
|
inner_cls = Timestamp
|
|
elif _type == _ROOT:
|
|
inner_cls = Root
|
|
else:
|
|
raise ValueError(f'unrecognized role type "{_type}"')
|
|
|
|
except Exception as e:
|
|
raise DeserializationError from e
|
|
|
|
return cast("T", inner_cls.from_dict(payload_dict))
|