Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tuf/api/dsse.py: 31%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright 2023-2025, New York University and the TUF contributors
2# SPDX-License-Identifier: MIT OR Apache-2.0
4"""Low-level TUF DSSE API. (experimental!)"""
6from __future__ import annotations
8import json
9from typing import Generic, cast
11from securesystemslib.dsse import Envelope as BaseSimpleEnvelope
13# Expose all payload classes to use API independently of ``tuf.api.metadata``.
14from tuf.api._payload import ( # noqa: F401
15 _ROOT,
16 _SNAPSHOT,
17 _TARGETS,
18 _TIMESTAMP,
19 SPECIFICATION_VERSION,
20 TOP_LEVEL_ROLE_NAMES,
21 BaseFile,
22 DelegatedRole,
23 Delegations,
24 MetaFile,
25 Role,
26 Root,
27 RootVerificationResult,
28 Signed,
29 Snapshot,
30 SuccinctRoles,
31 T,
32 TargetFile,
33 Targets,
34 Timestamp,
35 VerificationResult,
36)
37from tuf.api.serialization import DeserializationError, SerializationError
40class SimpleEnvelope(BaseSimpleEnvelope, Generic[T]):
41 """Dead Simple Signing Envelope (DSSE) for TUF payloads.
43 * Sign with ``self.sign()`` (inherited).
44 * Verify with ``verify_delegate`` on a ``Root`` or ``Targets``
45 object::
47 delegator.verify_delegate(
48 role_name,
49 envelope.pae(), # Note, how we don't pass ``envelope.payload``!
50 envelope.signatures,
51 )
53 Attributes:
54 payload: Serialized payload bytes.
55 payload_type: Payload string identifier.
56 signatures: Ordered dictionary of keyids to ``Signature`` objects.
58 """
60 DEFAULT_PAYLOAD_TYPE = "application/vnd.tuf+json"
62 @classmethod
63 def from_bytes(cls, data: bytes) -> SimpleEnvelope[T]:
64 """Load envelope from JSON bytes.
66 NOTE: Unlike ``tuf.api.metadata.Metadata.from_bytes``, this method
67 does not deserialize the contained payload. Use ``self.get_signed`` to
68 deserialize the payload into a ``Signed`` object.
70 Args:
71 data: envelope JSON bytes.
73 Raises:
74 tuf.api.serialization.DeserializationError:
75 data cannot be deserialized.
77 Returns:
78 TUF ``SimpleEnvelope`` object.
79 """
80 try:
81 envelope_dict = json.loads(data.decode())
82 envelope = SimpleEnvelope.from_dict(envelope_dict)
84 except Exception as e:
85 raise DeserializationError from e
87 return cast("SimpleEnvelope[T]", envelope)
89 def to_bytes(self) -> bytes:
90 """Return envelope as JSON bytes.
92 NOTE: Unlike ``tuf.api.metadata.Metadata.to_bytes``, this method does
93 not serialize the payload. Use ``SimpleEnvelope.from_signed`` to
94 serialize a ``Signed`` object and wrap it in an SimpleEnvelope.
96 Raises:
97 tuf.api.serialization.SerializationError:
98 self cannot be serialized.
99 """
100 try:
101 envelope_dict = self.to_dict()
102 json_bytes = json.dumps(envelope_dict).encode()
104 except Exception as e:
105 raise SerializationError from e
107 return json_bytes
109 @classmethod
110 def from_signed(cls, signed: T) -> SimpleEnvelope[T]:
111 """Serialize payload as JSON bytes and wrap in envelope.
113 Args:
114 signed: ``Signed`` object.
116 Raises:
117 tuf.api.serialization.SerializationError:
118 The signed object cannot be serialized.
119 """
120 try:
121 signed_dict = signed.to_dict()
122 json_bytes = json.dumps(signed_dict).encode()
124 except Exception as e:
125 raise SerializationError from e
127 return cls(json_bytes, cls.DEFAULT_PAYLOAD_TYPE, {})
129 def get_signed(self) -> T:
130 """Extract and deserialize payload JSON bytes from envelope.
132 Raises:
133 tuf.api.serialization.DeserializationError:
134 The signed object cannot be deserialized.
135 """
137 try:
138 payload_dict = json.loads(self.payload.decode())
140 # TODO: can we move this to tuf.api._payload?
141 _type = payload_dict["_type"]
142 if _type == _TARGETS:
143 inner_cls: type[Signed] = Targets
144 elif _type == _SNAPSHOT:
145 inner_cls = Snapshot
146 elif _type == _TIMESTAMP:
147 inner_cls = Timestamp
148 elif _type == _ROOT:
149 inner_cls = Root
150 else:
151 raise ValueError(f'unrecognized role type "{_type}"')
153 except Exception as e:
154 raise DeserializationError from e
156 return cast("T", inner_cls.from_dict(payload_dict))