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

48 statements  

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

2 

3from __future__ import annotations 

4 

5import json 

6from typing import Generic, cast 

7 

8from securesystemslib.dsse import Envelope as BaseSimpleEnvelope 

9 

10# Expose all payload classes to use API independently of ``tuf.api.metadata``. 

11from tuf.api._payload import ( # noqa: F401 

12 _ROOT, 

13 _SNAPSHOT, 

14 _TARGETS, 

15 _TIMESTAMP, 

16 SPECIFICATION_VERSION, 

17 TOP_LEVEL_ROLE_NAMES, 

18 BaseFile, 

19 DelegatedRole, 

20 Delegations, 

21 MetaFile, 

22 Role, 

23 Root, 

24 RootVerificationResult, 

25 Signed, 

26 Snapshot, 

27 SuccinctRoles, 

28 T, 

29 TargetFile, 

30 Targets, 

31 Timestamp, 

32 VerificationResult, 

33) 

34from tuf.api.serialization import DeserializationError, SerializationError 

35 

36 

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

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

39 

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

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

42 object:: 

43 

44 delegator.verify_delegate( 

45 role_name, 

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

47 envelope.signatures, 

48 ) 

49 

50 Attributes: 

51 payload: Serialized payload bytes. 

52 payload_type: Payload string identifier. 

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

54 

55 """ 

56 

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

58 

59 @classmethod 

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

61 """Load envelope from JSON bytes. 

62 

63 NOTE: Unlike ``tuf.api.metadata.Metadata.from_bytes``, this method 

64 does not deserialize the contained payload. Use ``self.get_signed`` to 

65 deserialize the payload into a ``Signed`` object. 

66 

67 Args: 

68 data: envelope JSON bytes. 

69 

70 Raises: 

71 tuf.api.serialization.DeserializationError: 

72 data cannot be deserialized. 

73 

74 Returns: 

75 TUF ``SimpleEnvelope`` object. 

76 """ 

77 try: 

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

79 envelope = SimpleEnvelope.from_dict(envelope_dict) 

80 

81 except Exception as e: 

82 raise DeserializationError from e 

83 

84 return cast(SimpleEnvelope[T], envelope) 

85 

86 def to_bytes(self) -> bytes: 

87 """Return envelope as JSON bytes. 

88 

89 NOTE: Unlike ``tuf.api.metadata.Metadata.to_bytes``, this method does 

90 not serialize the payload. Use ``SimpleEnvelope.from_signed`` to 

91 serialize a ``Signed`` object and wrap it in an SimpleEnvelope. 

92 

93 Raises: 

94 tuf.api.serialization.SerializationError: 

95 self cannot be serialized. 

96 """ 

97 try: 

98 envelope_dict = self.to_dict() 

99 json_bytes = json.dumps(envelope_dict).encode() 

100 

101 except Exception as e: 

102 raise SerializationError from e 

103 

104 return json_bytes 

105 

106 @classmethod 

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

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

109 

110 Args: 

111 signed: ``Signed`` object. 

112 

113 Raises: 

114 tuf.api.serialization.SerializationError: 

115 The signed object cannot be serialized. 

116 """ 

117 try: 

118 signed_dict = signed.to_dict() 

119 json_bytes = json.dumps(signed_dict).encode() 

120 

121 except Exception as e: 

122 raise SerializationError from e 

123 

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

125 

126 def get_signed(self) -> T: 

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

128 

129 Raises: 

130 tuf.api.serialization.DeserializationError: 

131 The signed object cannot be deserialized. 

132 """ 

133 

134 try: 

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

136 

137 # TODO: can we move this to tuf.api._payload? 

138 _type = payload_dict["_type"] 

139 if _type == _TARGETS: 

140 inner_cls: type[Signed] = Targets 

141 elif _type == _SNAPSHOT: 

142 inner_cls = Snapshot 

143 elif _type == _TIMESTAMP: 

144 inner_cls = Timestamp 

145 elif _type == _ROOT: 

146 inner_cls = Root 

147 else: 

148 raise ValueError(f'unrecognized role type "{_type}"') 

149 

150 except Exception as e: 

151 raise DeserializationError from e 

152 

153 return cast(T, inner_cls.from_dict(payload_dict))