Skip to content

Commit 8544bbd

Browse files
author
Lukas Puehringer
committed
Envelope API: rename, add docs, add alias
* Rename Envelope to SimpleEnvelope: Envelope should be the generic term in this context for something that contains a payload and signatures. SimpleEnvelope is the specific DSSE implementation (just like Metadata is the specific traditional canonical JSON -based TUF envelope implementation). * Add SimpleEnvelope class and method docstrings. * Add convenience alias for ``self.signatures`` mapped to keyids for compatibility with Metadata.signatures. Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
1 parent 01ba0cd commit 8544bbd

2 files changed

Lines changed: 86 additions & 25 deletions

File tree

tuf/api/dsse.py

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
"""Low-level TUF Envelope API.
1+
"""Low-level TUF DSSE API. (experimental!)
22
33
"""
44
import json
5-
from typing import Generic, Type, cast
5+
from typing import Dict, Generic, Type, cast
66

7-
from securesystemslib.dsse import Envelope as BaseEnvelope
7+
from securesystemslib.dsse import Envelope as BaseSimpleEnvelope
88

9+
# Expose all payload classes to use API independently of ``tuf.api.metadata``.
910
from tuf.api._payload import ( # noqa: F401
1011
_ROOT,
1112
_SNAPSHOT,
@@ -32,25 +33,73 @@
3233
from tuf.api.serialization import DeserializationError, SerializationError
3334

3435

35-
class Envelope(Generic[T], BaseEnvelope):
36-
"""TODO: doc"""
36+
class SimpleEnvelope(Generic[T], BaseSimpleEnvelope):
37+
"""Dead Simple Signing Envelope (DSSE) for TUF payloads.
38+
39+
* Sign with ``self.sign()`` (inherited).
40+
* Verify with ``verify_delegate`` on a ``Root`` or ``Targets``
41+
object::
42+
43+
delegator.verify_delegate(
44+
role_name,
45+
envelope.pae(), # Note, how we don't pass ``envelope.payload``!
46+
envelope.signatures_dict,
47+
)
48+
49+
Attributes:
50+
payload: Serialized payload bytes.
51+
payload_type: Payload string identifier.
52+
signatures: List of ``Signature`` objects.
53+
signatures_dict: Ordered dictionary of keyids to ``Signature`` objects.
54+
55+
"""
3756

3857
_DEFAULT_PAYLOAD_TYPE = "application/vnd.tuf+json"
3958

59+
@property
60+
def signatures_dict(self) -> Dict:
61+
"""Convenience alias for ``self.signatures`` mapped to keyids."""
62+
# TODO: Propose changing ``signatures`` list to dict upstream
63+
return {sig.keyid: sig for sig in self.signatures}
64+
4065
@classmethod
41-
def from_bytes(cls, data: bytes) -> "Envelope[T]":
42-
"""TODO: doc"""
66+
def from_bytes(cls, data: bytes) -> "SimpleEnvelope[T]":
67+
"""Load envelope from JSON bytes.
68+
69+
NOTE: Unlike ``tuf.api.metadata.Metadata.from_bytes``, this method
70+
does not deserialize the contained payload. Use ``self.get_signed`` to
71+
deserialize the payload into a ``Signed`` object.
72+
73+
Args:
74+
data: envelope JSON bytes.
75+
76+
Raises:
77+
tuf.api.serialization.DeserializationError:
78+
data cannot be deserialized.
79+
80+
Returns:
81+
TUF ``SimpleEnvelope`` object.
82+
"""
4383
try:
4484
envelope_dict = json.loads(data.decode())
45-
envelope = Envelope.from_dict(envelope_dict)
85+
envelope = SimpleEnvelope.from_dict(envelope_dict)
4686

4787
except Exception as e:
48-
raise SerializationError from e
88+
raise DeserializationError from e
4989

5090
return envelope
5191

5292
def to_bytes(self) -> bytes:
53-
"""TODO: doc"""
93+
"""Return envelope as JSON bytes.
94+
95+
NOTE: Unlike ``tuf.api.metadata.Metadata.to_bytes``, this method does
96+
not serialize the payload. Use ``SimpleEnvelope.from_signed`` to
97+
serialize a ``Signed`` object and wrap it in an SimpleEnvelope.
98+
99+
Raises:
100+
tuf.api.serialization.SerializationError:
101+
self cannot be serialized.
102+
"""
54103
try:
55104
envelope_dict = self.to_dict()
56105
json_bytes = json.dumps(envelope_dict).encode()
@@ -61,8 +110,16 @@ def to_bytes(self) -> bytes:
61110
return json_bytes
62111

63112
@classmethod
64-
def from_signed(cls, signed: T) -> "Envelope[T]":
65-
"""TODO: doc"""
113+
def from_signed(cls, signed: T) -> "SimpleEnvelope[T]":
114+
"""Serialize payload as JSON bytes and wrap in envelope.
115+
116+
Args:
117+
signed: ``Signed`` object.
118+
119+
Raises:
120+
tuf.api.serialization.SerializationError:
121+
The signed object cannot be serialized.
122+
"""
66123
try:
67124
signed_dict = signed.to_dict()
68125
json_bytes = json.dumps(signed_dict).encode()
@@ -73,7 +130,13 @@ def from_signed(cls, signed: T) -> "Envelope[T]":
73130
return cls(json_bytes, cls._DEFAULT_PAYLOAD_TYPE, [])
74131

75132
def get_signed(self) -> T:
76-
"""TODO: doc"""
133+
"""Extract and deserialize payload JSON bytes from envelope.
134+
135+
Raises:
136+
tuf.api.serialization.DeserializationError:
137+
The signed object cannot be deserialized.
138+
"""
139+
77140
try:
78141
payload_dict = json.loads(self.payload.decode())
79142

@@ -88,7 +151,7 @@ def get_signed(self) -> T:
88151
elif _type == _ROOT:
89152
inner_cls = Root
90153
else:
91-
raise ValueError(f'unrecognized metadata type "{_type}"')
154+
raise ValueError(f'unrecognized role type "{_type}"')
92155

93156
except Exception as e:
94157
raise DeserializationError from e

tuf/ngclient/_internal/wrapping.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from tuf.api import exceptions
1313
from tuf.api._payload import Root, T, Targets
14-
from tuf.api.dsse import Envelope
14+
from tuf.api.dsse import SimpleEnvelope
1515
from tuf.api.metadata import Metadata
1616

1717
Delegator = Union[Root, Targets]
@@ -99,11 +99,11 @@ class EnvelopeUnwrapper(Unwrapper):
9999
"""
100100

101101
@staticmethod
102-
def _validate_envelope_payload_type(envelope: Envelope) -> None:
102+
def _validate_envelope_payload_type(envelope: SimpleEnvelope) -> None:
103103
# pylint: disable=protected-access
104-
if envelope.payload_type != Envelope._DEFAULT_PAYLOAD_TYPE:
104+
if envelope.payload_type != SimpleEnvelope._DEFAULT_PAYLOAD_TYPE:
105105
raise exceptions.RepositoryError(
106-
f"Expected '{Envelope._DEFAULT_PAYLOAD_TYPE}', "
106+
f"Expected '{SimpleEnvelope._DEFAULT_PAYLOAD_TYPE}', "
107107
f"got '{envelope.payload_type}'"
108108
)
109109

@@ -114,19 +114,17 @@ def unwrap(
114114
delegator: Optional[Delegator] = None,
115115
role_name: Optional[str] = None,
116116
) -> Tuple[T, bytes, Dict[str, Signature]]: # noqa: D102
117-
envelope = Envelope[T].from_bytes(wrapper)
118-
119-
# TODO: Envelope stores signatures as list, but `verify_delegate`
120-
# expects a dict. Should we change the envelope model?
121-
signatures = {sig.keyid: sig for sig in envelope.signatures}
117+
envelope = SimpleEnvelope[T].from_bytes(wrapper)
122118

123119
self._validate_envelope_payload_type(envelope)
124120
if delegator:
125121
if role_name is None:
126122
role_name = role_cls.type
127-
delegator.verify_delegate(role_name, envelope.pae(), signatures)
123+
delegator.verify_delegate(
124+
role_name, envelope.pae(), envelope.signatures_dict
125+
)
128126

129127
signed = envelope.get_signed()
130128
self._validate_signed_type(signed, role_cls)
131129

132-
return signed, envelope.pae(), signatures
130+
return signed, envelope.pae(), envelope.signatures_dict

0 commit comments

Comments
 (0)