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

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

80 statements  

1# Copyright 2024 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.""" 

16 

17import pathlib 

18import sys 

19from typing import cast 

20 

21from google.protobuf import json_format 

22from sigstore import dsse as sigstore_dsse 

23from sigstore import models as sigstore_models 

24from sigstore import oidc as sigstore_oidc 

25from sigstore import sign as sigstore_signer 

26from sigstore import verify as sigstore_verifier 

27from typing_extensions import override 

28 

29from model_signing._signing import signing 

30 

31 

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

33 from typing import Self 

34else: 

35 from typing_extensions import Self 

36 

37_DEFAULT_CLIENT_ID = "sigstore" 

38_DEFAULT_CLIENT_SECRET = "" 

39 

40 

41class Signature(signing.Signature): 

42 """Sigstore signature support, wrapping around `sigstore_models.Bundle`.""" 

43 

44 def __init__(self, bundle: sigstore_models.Bundle): 

45 """Builds an instance of this signature. 

46 

47 Args: 

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

49 """ 

50 self.bundle = bundle 

51 

52 @override 

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

54 path.write_bytes(self.to_bytes()) 

55 

56 @override 

57 def to_bytes(self) -> bytes: 

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

59 

60 @classmethod 

61 @override 

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

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

64 return cls(sigstore_models.Bundle.from_json(content)) 

65 

66 

67class Signer(signing.Signer): 

68 """Signing using Sigstore.""" 

69 

70 def __init__( 

71 self, 

72 *, 

73 oidc_issuer: str | None = None, 

74 use_ambient_credentials: bool = True, 

75 use_staging: bool = False, 

76 identity_token: str | None = None, 

77 force_oob: bool = False, 

78 client_id: str | None = None, 

79 client_secret: str | None = None, 

80 trust_config: pathlib.Path | None = None, 

81 ): 

82 """Initializes Sigstore signers. 

83 

84 Needs to set-up a signing context to use the public goods instance and 

85 machinery for getting an identity token to use in signing. 

86 

87 Args: 

88 oidc_issuer: An optional OpenID Connect issuer to use instead of the 

89 default production one. Only relevant if `use_staging = False`. 

90 Default is empty, relying on the Sigstore configuration. 

91 use_ambient_credentials: Use ambient credentials (also known as 

92 Workload Identity). Default is True. If ambient credentials cannot 

93 be used (not available, or option disabled), a flow to get signer 

94 identity via OIDC will start. 

95 use_staging: Use staging configurations, instead of production. This 

96 is supposed to be set to True only when testing. Default is False. 

97 force_oob: If True, forces an out-of-band (OOB) OAuth flow. If set, 

98 the OAuth authentication will not attempt to open the default web 

99 browser. Instead, it will display a URL and code for manual 

100 authentication. Default is False, which means the browser will be 

101 opened automatically if possible. 

102 identity_token: An explicit identity token to use when signing, 

103 taking precedence over any ambient credential or OAuth workflow. 

104 client_id: An optional client ID to use when performing OIDC-based 

105 authentication. This is typically used to identify the 

106 application making the request to the OIDC provider. If not 

107 provided, the default client ID configured by Sigstore will be 

108 used. 

109 client_secret: An optional client secret to use along with the 

110 client ID when authenticating with the OIDC provider. This is 

111 required for confidential clients that need to prove their 

112 identity to the OIDC provider. If not provided, it is assumed 

113 that the client is public or the provider does not require a 

114 secret. 

115 trust_config: A path to a custom trust configuration. When 

116 provided, the signature verification process will rely on the 

117 supplied PKI and trust configurations, instead of the default 

118 Sigstore setup. If not specified, the default Sigstore 

119 configuration is used. 

120 """ 

121 if use_staging: 

122 trust_config = sigstore_models.ClientTrustConfig.staging() 

123 elif trust_config: 

124 trust_config = sigstore_models.ClientTrustConfig.from_json( 

125 trust_config.read_text() 

126 ) 

127 else: 

128 trust_config = sigstore_models.ClientTrustConfig.production() 

129 

130 if not oidc_issuer: 

131 oidc_issuer = trust_config.signing_config.get_oidc_url() 

132 

133 self._oidc_issuer = oidc_issuer 

134 self._issuer: sigstore_oidc.Issuer | None = None 

135 self._signing_context = ( 

136 sigstore_signer.SigningContext.from_trust_config(trust_config) 

137 ) 

138 self._use_ambient_credentials = use_ambient_credentials 

139 self._identity_token = identity_token 

140 self._force_oob = force_oob 

141 self._client_id = client_id or _DEFAULT_CLIENT_ID 

142 self._client_secret = client_secret or _DEFAULT_CLIENT_SECRET 

143 

144 def _get_identity_token(self) -> sigstore_oidc.IdentityToken: 

145 """Obtains an identity token to use in signing. 

146 

147 The precedence matches that of sigstore-python: 

148 1) Explicitly supplied identity token 

149 2) Ambient credential detected in the environment, if enabled 

150 3) Interactive OAuth flow 

151 """ 

152 if self._identity_token: 

153 return sigstore_oidc.IdentityToken( 

154 self._identity_token, self._client_id 

155 ) 

156 if self._use_ambient_credentials: 

157 token = sigstore_oidc.detect_credential(self._client_id) 

158 if token: 

159 return sigstore_oidc.IdentityToken(token, self._client_id) 

160 

161 if self._issuer is None: 

162 self._issuer = sigstore_oidc.Issuer(self._oidc_issuer) 

163 

164 return self._issuer.identity_token( 

165 force_oob=self._force_oob, 

166 client_id=self._client_id, 

167 client_secret=self._client_secret, 

168 ) 

169 

170 @override 

171 def sign(self, payload: signing.Payload) -> Signature: 

172 # We need to convert from in-toto statement to Sigstore's DSSE 

173 # version. They both contain the same contents, but there is no way 

174 # to coerce one type to the other. 

175 # See also: https://github.com/sigstore/sigstore-python/issues/1076 

176 statement = sigstore_dsse.Statement( 

177 json_format.MessageToJson(payload.statement.pb).encode("utf-8") 

178 ) 

179 

180 token = self._get_identity_token() 

181 with self._signing_context.signer(token) as signer: 

182 bundle = signer.sign_dsse(statement) 

183 

184 return Signature(bundle) 

185 

186 

187class Verifier(signing.Verifier): 

188 """Signature verification using Sigstore.""" 

189 

190 def __init__( 

191 self, 

192 *, 

193 identity: str, 

194 oidc_issuer: str, 

195 use_staging: bool = False, 

196 trust_config: pathlib.Path | None = None, 

197 ): 

198 """Initializes Sigstore verifiers. 

199 

200 When verifying a signature, we also check an identity policy: the 

201 certificate must belong to a given "identity", and must be issued by a 

202 given OpenID Connect issuer. 

203 

204 Args: 

205 identity: The expected identity that has signed the model. 

206 oidc_issuer: The expected OpenID Connect issuer that provided the 

207 certificate used for the signature. 

208 use_staging: Use staging configurations, instead of production. This 

209 is supposed to be set to True only when testing. Default is False. 

210 trust_config: A path to a custom trust configuration. When provided, 

211 the signature verification process will rely on the supplied 

212 PKI and trust configurations, instead of the default Sigstore 

213 setup. If not specified, the default Sigstore configuration 

214 is used. 

215 """ 

216 if trust_config: 

217 trust_config = sigstore_models.ClientTrustConfig.from_json( 

218 trust_config.read_text() 

219 ) 

220 elif use_staging: 

221 trust_config = sigstore_models.ClientTrustConfig.staging() 

222 else: 

223 trust_config = sigstore_models.ClientTrustConfig.production() 

224 

225 self._verifier = sigstore_verifier.Verifier( 

226 trusted_root=trust_config.trusted_root 

227 ) 

228 

229 self._policy = sigstore_verifier.policy.Identity( 

230 identity=identity, issuer=oidc_issuer 

231 ) 

232 

233 @override 

234 def _verify_signed_content( 

235 self, signature: signing.Signature 

236 ) -> tuple[str, bytes]: 

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

238 signature = cast(Signature, signature) 

239 bundle = signature.bundle 

240 return self._verifier.verify_dsse(bundle=bundle, policy=self._policy)