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

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 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_protobuf_specs.dev.sigstore.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 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 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_text(self.bundle.to_json()) 

109 

110 @classmethod 

111 @override 

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

113 content = path.read_text() 

114 parsed_dict = json.loads(content) 

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

116 

117 

118class Signer(signing.Signer): 

119 """Signer for traditional signing. 

120 

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

122 """ 

123 

124 

125class Verifier(signing.Verifier): 

126 """Verifier for traditional signature verification. 

127 

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

129 """ 

130 

131 @override 

132 def _verify_signed_content( 

133 self, signature: signing.Signature 

134 ) -> tuple[str, bytes]: 

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

136 signature = cast(Signature, signature) 

137 bundle = signature.bundle 

138 

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

140 if bundle.media_type != _BUNDLE_MEDIA_TYPE: 

141 raise ValueError( 

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

143 f"but expected {_BUNDLE_MEDIA_TYPE}" 

144 ) 

145 

146 return self._verify_bundle(bundle) 

147 

148 @abc.abstractmethod 

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

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

151 

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

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

154 """