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

77 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_text(self.bundle.to_json(), encoding="utf-8") 

55 

56 @classmethod 

57 @override 

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

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

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

61 

62 

63class Signer(signing.Signer): 

64 """Signing using Sigstore.""" 

65 

66 def __init__( 

67 self, 

68 *, 

69 oidc_issuer: str | None = None, 

70 use_ambient_credentials: bool = True, 

71 use_staging: bool = False, 

72 identity_token: str | None = None, 

73 force_oob: bool = False, 

74 client_id: str | None = None, 

75 client_secret: str | None = None, 

76 trust_config: pathlib.Path | None = None, 

77 ): 

78 """Initializes Sigstore signers. 

79 

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

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

82 

83 Args: 

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

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

86 Default is empty, relying on the Sigstore configuration. 

87 use_ambient_credentials: Use ambient credentials (also known as 

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

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

90 identity via OIDC will start. 

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

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

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

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

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

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

97 opened automatically if possible. 

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

99 taking precedence over any ambient credential or OAuth workflow. 

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

101 authentication. This is typically used to identify the 

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

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

104 used. 

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

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

107 required for confidential clients that need to prove their 

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

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

110 secret. 

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

112 provided, the signature verification process will rely on the 

113 supplied PKI and trust configurations, instead of the default 

114 Sigstore setup. If not specified, the default Sigstore 

115 configuration is used. 

116 """ 

117 if use_staging: 

118 trust_config = sigstore_models.ClientTrustConfig.staging() 

119 elif trust_config: 

120 trust_config = sigstore_models.ClientTrustConfig.from_json( 

121 trust_config.read_text() 

122 ) 

123 else: 

124 trust_config = sigstore_models.ClientTrustConfig.production() 

125 

126 if not oidc_issuer: 

127 oidc_issuer = trust_config.signing_config.get_oidc_url() 

128 

129 self._oidc_issuer = oidc_issuer 

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

131 self._signing_context = ( 

132 sigstore_signer.SigningContext.from_trust_config(trust_config) 

133 ) 

134 self._use_ambient_credentials = use_ambient_credentials 

135 self._identity_token = identity_token 

136 self._force_oob = force_oob 

137 self._client_id = client_id or _DEFAULT_CLIENT_ID 

138 self._client_secret = client_secret or _DEFAULT_CLIENT_SECRET 

139 

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

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

142 

143 The precedence matches that of sigstore-python: 

144 1) Explicitly supplied identity token 

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

146 3) Interactive OAuth flow 

147 """ 

148 if self._identity_token: 

149 return sigstore_oidc.IdentityToken( 

150 self._identity_token, self._client_id 

151 ) 

152 if self._use_ambient_credentials: 

153 token = sigstore_oidc.detect_credential(self._client_id) 

154 if token: 

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

156 

157 if self._issuer is None: 

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

159 

160 return self._issuer.identity_token( 

161 force_oob=self._force_oob, 

162 client_id=self._client_id, 

163 client_secret=self._client_secret, 

164 ) 

165 

166 @override 

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

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

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

170 # to coerce one type to the other. 

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

172 statement = sigstore_dsse.Statement( 

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

174 ) 

175 

176 token = self._get_identity_token() 

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

178 bundle = signer.sign_dsse(statement) 

179 

180 return Signature(bundle) 

181 

182 

183class Verifier(signing.Verifier): 

184 """Signature verification using Sigstore.""" 

185 

186 def __init__( 

187 self, 

188 *, 

189 identity: str, 

190 oidc_issuer: str, 

191 use_staging: bool = False, 

192 trust_config: pathlib.Path | None = None, 

193 ): 

194 """Initializes Sigstore verifiers. 

195 

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

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

198 given OpenID Connect issuer. 

199 

200 Args: 

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

202 oidc_issuer: The expected OpenID Connect issuer that provided the 

203 certificate used for the signature. 

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

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

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

207 the signature verification process will rely on the supplied 

208 PKI and trust configurations, instead of the default Sigstore 

209 setup. If not specified, the default Sigstore configuration 

210 is used. 

211 """ 

212 if trust_config: 

213 trust_config = sigstore_models.ClientTrustConfig.from_json( 

214 trust_config.read_text() 

215 ) 

216 elif use_staging: 

217 trust_config = sigstore_models.ClientTrustConfig.staging() 

218 else: 

219 trust_config = sigstore_models.ClientTrustConfig.production() 

220 

221 self._verifier = sigstore_verifier.Verifier( 

222 trusted_root=trust_config.trusted_root 

223 ) 

224 

225 self._policy = sigstore_verifier.policy.Identity( 

226 identity=identity, issuer=oidc_issuer 

227 ) 

228 

229 @override 

230 def _verify_signed_content( 

231 self, signature: signing.Signature 

232 ) -> tuple[str, bytes]: 

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

234 signature = cast(Signature, signature) 

235 bundle = signature.bundle 

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