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

49 statements  

1# Copyright 2023-2025, New York University and the TUF contributors 

2# SPDX-License-Identifier: MIT OR Apache-2.0 

3 

4"""Low-level TUF DSSE API. (experimental!)""" 

5 

6from __future__ import annotations 

7 

8import json 

9from typing import Generic, cast 

10 

11from securesystemslib.dsse import Envelope as BaseSimpleEnvelope 

12 

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 

38 

39 

40class SimpleEnvelope(BaseSimpleEnvelope, Generic[T]): 

41 """Dead Simple Signing Envelope (DSSE) for TUF payloads. 

42 

43 * Sign with ``self.sign()`` (inherited). 

44 * Verify with ``verify_delegate`` on a ``Root`` or ``Targets`` 

45 object:: 

46 

47 delegator.verify_delegate( 

48 role_name, 

49 envelope.pae(), # Note, how we don't pass ``envelope.payload``! 

50 envelope.signatures, 

51 ) 

52 

53 Attributes: 

54 payload: Serialized payload bytes. 

55 payload_type: Payload string identifier. 

56 signatures: Ordered dictionary of keyids to ``Signature`` objects. 

57 

58 """ 

59 

60 DEFAULT_PAYLOAD_TYPE = "application/vnd.tuf+json" 

61 

62 @classmethod 

63 def from_bytes(cls, data: bytes) -> SimpleEnvelope[T]: 

64 """Load envelope from JSON bytes. 

65 

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. 

69 

70 Args: 

71 data: envelope JSON bytes. 

72 

73 Raises: 

74 tuf.api.serialization.DeserializationError: 

75 data cannot be deserialized. 

76 

77 Returns: 

78 TUF ``SimpleEnvelope`` object. 

79 """ 

80 try: 

81 envelope_dict = json.loads(data.decode()) 

82 envelope = SimpleEnvelope.from_dict(envelope_dict) 

83 

84 except Exception as e: 

85 raise DeserializationError from e 

86 

87 return cast("SimpleEnvelope[T]", envelope) 

88 

89 def to_bytes(self) -> bytes: 

90 """Return envelope as JSON bytes. 

91 

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. 

95 

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() 

103 

104 except Exception as e: 

105 raise SerializationError from e 

106 

107 return json_bytes 

108 

109 @classmethod 

110 def from_signed(cls, signed: T) -> SimpleEnvelope[T]: 

111 """Serialize payload as JSON bytes and wrap in envelope. 

112 

113 Args: 

114 signed: ``Signed`` object. 

115 

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() 

123 

124 except Exception as e: 

125 raise SerializationError from e 

126 

127 return cls(json_bytes, cls.DEFAULT_PAYLOAD_TYPE, {}) 

128 

129 def get_signed(self) -> T: 

130 """Extract and deserialize payload JSON bytes from envelope. 

131 

132 Raises: 

133 tuf.api.serialization.DeserializationError: 

134 The signed object cannot be deserialized. 

135 """ 

136 

137 try: 

138 payload_dict = json.loads(self.payload.decode()) 

139 

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}"') 

152 

153 except Exception as e: 

154 raise DeserializationError from e 

155 

156 return cast("T", inner_cls.from_dict(payload_dict))