Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/model_signing/_signing/sign_sigstore_pb.py: 48%

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

60 statements  

1# Copyright 2025 The Sigstore Authors 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Sigstore based signature, signers and verifiers, using protobuf. 

16 

17The difference between this module and `sign_sigstore` is that here we use 

18Sigstore via the protobuf specs instead of `sigstore-python`. This is to enable 

19support for traditional signing and verification. These require additional data 

20to be stored in the sigstore bundle used for the signature but `sigstore-python` 

21validation does not allow those. 

22""" 

23 

24import abc 

25import json 

26import pathlib 

27import sys 

28from typing import cast 

29 

30from sigstore_models.bundle import v1 as bundle_pb 

31from typing_extensions import override 

32 

33from model_signing._signing import signing 

34 

35 

36if sys.version_info >= (3, 11): 

37 from typing import Self 

38else: 

39 from typing_extensions import Self 

40 

41 

42# The media type to use when creating the protobuf based Sigstore bundle 

43_BUNDLE_MEDIA_TYPE: str = "application/vnd.dev.sigstore.bundle.v0.3+json" 

44 

45 

46def pae(raw_payload: bytes) -> bytes: 

47 """Generates the PAE encoding of statement from the payload. 

48 

49 This is an internal of `sigstore_python`, but since in this module and 

50 classes derived from the signer and verifier defined here we cannot use 

51 `sigstore_python`, we have to reimplement this. 

52 

53 See https://github.com/secure-systems-lab/dsse/blob/v1.0.0/protocol.md 

54 for details. 

55 

56 Args: 

57 raw_payload: The raw payload to encode. 

58 

59 Returns: 

60 The encoded statement from the payload. 

61 """ 

62 payload_type = signing._IN_TOTO_JSON_PAYLOAD_TYPE 

63 payload_type_length = len(payload_type) 

64 payload_length = len(raw_payload) 

65 pae_str = f"DSSEv1 {payload_type_length} {payload_type} {payload_length}" 

66 return b" ".join([pae_str.encode("utf-8"), raw_payload]) 

67 

68 

69def pae_compat(raw_payload: bytes) -> bytes: 

70 """Generates the PAE encoding of statement from the payload. 

71 

72 This is the same as `pae`, but using the version as defined in v0.2.0 of the 

73 `model_signing` library. Due to a bug in that implementation, signatures 

74 generated at that version have to be verified using this compat patch. The 

75 issue is that the raw payload, which is bytes, is added to a string and then 

76 encoded back as bytes, so we get additional escape characters included. 

77 

78 Args: 

79 raw_payload: The raw payload to encode. 

80 

81 Returns: 

82 The encoded statement from the payload. 

83 """ 

84 payload_type = signing._IN_TOTO_JSON_PAYLOAD_TYPE 

85 payload_type_length = len(payload_type) 

86 payload_length = len(raw_payload) 

87 # Notice bug here! 

88 pae_str = ( 

89 f"DSSEV1 {payload_type_length} {payload_type} " 

90 f"{payload_length} {raw_payload}" 

91 ) 

92 return pae_str.encode("utf-8") 

93 

94 

95class Signature(signing.Signature): 

96 """Sigstore signature support, wrapping around `bundle_pb.Bundle`.""" 

97 

98 def __init__(self, bundle: bundle_pb.Bundle): 

99 """Builds an instance of this signature. 

100 

101 Args: 

102 bundle: the sigstore bundle (in `bundle_pb.Bundle` format). 

103 """ 

104 self.bundle = bundle 

105 

106 @override 

107 def write(self, path: pathlib.Path) -> None: 

108 path.write_bytes(self.to_bytes()) 

109 

110 @override 

111 def to_bytes(self) -> bytes: 

112 return self.bundle.to_json().encode("utf-8") 

113 

114 @classmethod 

115 @override 

116 def read(cls, path: pathlib.Path) -> Self: 

117 content = path.read_text(encoding="utf-8") 

118 parsed_dict = json.loads(content) 

119 

120 # adjust parsed_dict due to previous usage of protobufs 

121 if "tlogEntries" not in parsed_dict["verificationMaterial"]: 

122 parsed_dict["verificationMaterial"]["tlogEntries"] = [] 

123 if "publicKey" in parsed_dict["verificationMaterial"]: 

124 if "hint" not in parsed_dict["verificationMaterial"]["publicKey"]: 

125 parsed_dict["verificationMaterial"]["publicKey"]["hint"] = None 

126 for k in ["rawBytes", "keyDetails"]: 

127 if k in parsed_dict["verificationMaterial"]["publicKey"]: 

128 del parsed_dict["verificationMaterial"]["publicKey"][k] 

129 

130 return cls(bundle_pb.Bundle.from_dict(parsed_dict)) 

131 

132 

133class Signer(signing.Signer): 

134 """Signer for traditional signing. 

135 

136 This is subclassed for each traditional signing method we support. 

137 """ 

138 

139 

140class Verifier(signing.Verifier): 

141 """Verifier for traditional signature verification. 

142 

143 This is subclassed for each traditional signing method we support. 

144 """ 

145 

146 @override 

147 def _verify_signed_content( 

148 self, signature: signing.Signature 

149 ) -> tuple[str, bytes]: 

150 # We are guaranteed to only use the local signature type 

151 signature = cast(Signature, signature) 

152 bundle = signature.bundle 

153 

154 # Since the bundle is done via protobuf, check media type first 

155 if bundle.media_type != _BUNDLE_MEDIA_TYPE: 

156 raise ValueError( 

157 f"Invalid sigstore bundle, got media type {bundle.media_type} " 

158 f"but expected {_BUNDLE_MEDIA_TYPE}" 

159 ) 

160 

161 return self._verify_bundle(bundle) 

162 

163 @abc.abstractmethod 

164 def _verify_bundle(self, bundle: bundle_pb.Bundle) -> tuple[str, bytes]: 

165 """Verifies the bundle to extract the payload type and payload. 

166 

167 Since the bundle is generated via proto, we need to do more checks to 

168 replace what `verify_dsse` from `sigstore_python` does. 

169 """