Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/model_signing/signing.py: 62%

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

55 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"""High level API for the signing interface of `model_signing` library. 

16 

17The module allows signing a model with a default configuration: 

18 

19```python 

20model_signing.signing.sign("finbert", "finbert.sig") 

21``` 

22 

23The module allows customizing the signing configuration before signing: 

24 

25```python 

26model_signing.signing.Config().use_elliptic_key_signer(private_key="key").sign( 

27 "finbert", "finbert.sig" 

28) 

29``` 

30 

31The same signing configuration can be used to sign multiple models: 

32 

33```python 

34signing_config = model_signing.signing.Config().use_elliptic_key_signer( 

35 private_key="key" 

36) 

37 

38for model in all_models: 

39 signing_config.sign(model, f"{model}_sharded.sig") 

40``` 

41 

42The API defined here is stable and backwards compatible. 

43""" 

44 

45from collections.abc import Iterable 

46import pathlib 

47from typing import Self 

48 

49from model_signing import hashing 

50from model_signing._signing import sign_certificate as certificate 

51from model_signing._signing import sign_ec_key as ec_key 

52from model_signing._signing import sign_sigstore as sigstore 

53from model_signing._signing import signing 

54 

55 

56def sign(model_path: hashing.PathLike, signature_path: hashing.PathLike): 

57 """Signs a model using the default configuration. 

58 

59 In this default configuration we sign using Sigstore and the default hashing 

60 configuration from `model_signing.hashing`. 

61 

62 The resulting signature is in the Sigstore bundle format. 

63 

64 Args: 

65 model_path: the path to the model to sign. 

66 signature_path: the path of the resulting signature. 

67 """ 

68 Config().sign(model_path, signature_path) 

69 

70 

71def sign_to_bytes(model_path: hashing.PathLike) -> bytes: 

72 """Signs a model using the default configuration and returns the signature. 

73 

74 In this default configuration we sign using Sigstore and the default hashing 

75 configuration from `model_signing.hashing`. 

76 

77 The resulting signature is the Sigstore bundle, returned in memory as UTF-8 

78 encoded JSON bytes instead of being written to disk. 

79 

80 Args: 

81 model_path: the path to the model to sign. 

82 

83 Returns: 

84 The signature, as a Sigstore bundle encoded in UTF-8 JSON bytes. 

85 """ 

86 return Config().sign_to_bytes(model_path) 

87 

88 

89class Config: 

90 """Configuration to use when signing models. 

91 

92 Currently, we support signing with Sigstore (both the public 

93 instance and staging instance), signing with private keys, 

94 signing with signing certificates, and signing with custom 

95 PKI configurations using the `--trust_config` option. 

96 This allows users to bring their own trust configuration 

97 to sign and verify models. Other signing modes may be 

98 added in the future. 

99 """ 

100 

101 def __init__(self): 

102 """Initializes the default configuration for signing.""" 

103 self._hashing_config = hashing.Config() 

104 # lazy initialize default signer at signing to avoid network calls 

105 self._signer = None 

106 

107 def sign( 

108 self, model_path: hashing.PathLike, signature_path: hashing.PathLike 

109 ): 

110 """Signs a model using the current configuration. 

111 

112 Args: 

113 model_path: The path to the model to sign. 

114 signature_path: The path of the resulting signature. 

115 """ 

116 signature = self._sign(model_path) 

117 signature.write(pathlib.Path(signature_path)) 

118 

119 def sign_to_bytes(self, model_path: hashing.PathLike) -> bytes: 

120 """Signs a model and returns the signature as bytes. 

121 

122 This mirrors `sign`, but instead of writing the signature to disk it 

123 returns the Sigstore bundle in memory. This is useful in serverless or 

124 pipeline contexts where writing to the filesystem is undesirable or 

125 impossible, and the bundle needs to be streamed, persisted to an object 

126 store, or passed directly to another process. 

127 

128 The returned bytes are the UTF-8 encoded Sigstore bundle, identical to 

129 what `sign` would have written to the signature path. 

130 

131 Args: 

132 model_path: The path to the model to sign. 

133 

134 Returns: 

135 The signature, as a Sigstore bundle encoded in UTF-8 JSON bytes. 

136 """ 

137 return self._sign(model_path).to_bytes() 

138 

139 def _sign(self, model_path: hashing.PathLike) -> signing.Signature: 

140 """Hashes and signs a model, returning the in-memory signature. 

141 

142 Args: 

143 model_path: The path to the model to sign. 

144 

145 Returns: 

146 The `Signature` produced by the configured signer. 

147 """ 

148 if self._signer is None: 

149 self.use_sigstore_signer() 

150 manifest = self._hashing_config.hash(model_path) 

151 payload = signing.Payload(manifest) 

152 return self._signer.sign(payload) 

153 

154 def set_hashing_config(self, hashing_config: hashing.Config) -> Self: 

155 """Sets the new configuration for hashing models. 

156 

157 Args: 

158 hashing_config: The new hashing configuration. 

159 

160 Returns: 

161 The new signing configuration. 

162 """ 

163 self._hashing_config = hashing_config 

164 return self 

165 

166 def use_sigstore_signer( 

167 self, 

168 *, 

169 oidc_issuer: str | None = None, 

170 use_ambient_credentials: bool = False, 

171 use_staging: bool = False, 

172 force_oob: bool = False, 

173 identity_token: str | None = None, 

174 client_id: str | None = None, 

175 client_secret: str | None = None, 

176 trust_config: pathlib.Path | None = None, 

177 ) -> Self: 

178 """Configures the signing to be performed with Sigstore. 

179 

180 The signer in this configuration is changed to one that performs signing 

181 with Sigstore. 

182 

183 Args: 

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

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

186 Default is empty, relying on the Sigstore configuration. 

187 use_ambient_credentials: Use ambient credentials (also known as 

188 Workload Identity). Default is False. If ambient credentials 

189 cannot be used (not available, or option disabled), a flow to get 

190 signer identity via OIDC will start. 

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

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

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

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

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

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

197 opened automatically if possible. 

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

199 taking precedence over any ambient credential or OAuth workflow. 

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

201 authentication. This is typically used to identify the 

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

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

204 used. 

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

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

207 required for confidential clients that need to prove their 

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

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

210 secret. 

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

212 the signature verification process will rely on the supplied 

213 PKI and trust configurations, instead of the default Sigstore 

214 setup. If not specified, the default Sigstore configuration 

215 is used. 

216 

217 Return: 

218 The new signing configuration. 

219 """ 

220 self._signer = sigstore.Signer( 

221 oidc_issuer=oidc_issuer, 

222 use_ambient_credentials=use_ambient_credentials, 

223 use_staging=use_staging, 

224 identity_token=identity_token, 

225 force_oob=force_oob, 

226 client_id=client_id, 

227 client_secret=client_secret, 

228 trust_config=trust_config, 

229 ) 

230 return self 

231 

232 def use_elliptic_key_signer( 

233 self, 

234 *, 

235 private_key: hashing.PathLike, 

236 password: str | None = None, 

237 tsa_url: str | None = None, 

238 ) -> Self: 

239 """Configures the signing to be performed using elliptic curve keys. 

240 

241 The signer in this configuration is changed to one that performs signing 

242 using a private key based on elliptic curve cryptography. 

243 

244 Args: 

245 private_key: The path to the private key to use for signing. 

246 password: An optional password for the key, if encrypted. 

247 tsa_url: Optional URL of an RFC 3161 Timestamp Authority. When 

248 provided, the signature will include a trusted timestamp. 

249 

250 Return: 

251 The new signing configuration. 

252 """ 

253 self._signer = ec_key.Signer( 

254 pathlib.Path(private_key), password, tsa_url=tsa_url 

255 ) 

256 return self 

257 

258 def use_certificate_signer( 

259 self, 

260 *, 

261 private_key: hashing.PathLike, 

262 signing_certificate: hashing.PathLike, 

263 certificate_chain: Iterable[hashing.PathLike], 

264 tsa_url: str | None = None, 

265 ) -> Self: 

266 """Configures the signing to be performed using signing certificates. 

267 

268 The signer in this configuration is changed to one that performs signing 

269 using cryptographic signing certificates. 

270 

271 Args: 

272 private_key: The path to the private key to use for signing. 

273 signing_certificate: The path to the signing certificate. 

274 certificate_chain: Optional paths to other certificates to establish 

275 a chain of trust. 

276 tsa_url: Optional URL of an RFC 3161 Timestamp Authority. When 

277 provided, the signature will include a trusted timestamp. 

278 

279 Return: 

280 The new signing configuration. 

281 """ 

282 self._signer = certificate.Signer( 

283 pathlib.Path(private_key), 

284 pathlib.Path(signing_certificate), 

285 [pathlib.Path(c) for c in certificate_chain], 

286 tsa_url=tsa_url, 

287 ) 

288 return self 

289 

290 def use_pkcs11_signer( 

291 self, 

292 *, 

293 pkcs11_uri: str, 

294 module_paths: Iterable[str] = frozenset(), 

295 tsa_url: str | None = None, 

296 ) -> Self: 

297 """Configures the signing to be performed using PKCS #11. 

298 

299 The signer in this configuration is changed to one that performs signing 

300 using a private key based on elliptic curve cryptography. 

301 

302 Args: 

303 pkcs11_uri: The PKCS11 URI. 

304 module_paths: Optional list of paths of PKCS #11 modules. 

305 tsa_url: Optional URL of an RFC 3161 Timestamp Authority. When 

306 provided, the signature will include a trusted timestamp. 

307 

308 Return: 

309 The new signing configuration. 

310 """ 

311 try: 

312 from model_signing._signing import sign_pkcs11 as pkcs11 

313 except ImportError as e: 

314 raise RuntimeError( 

315 "PKCS #11 functionality requires the 'pkcs11' extra. " 

316 "Install with 'pip install model-signing[pkcs11]'." 

317 ) from e 

318 self._signer = pkcs11.Signer(pkcs11_uri, module_paths, tsa_url=tsa_url) 

319 return self 

320 

321 def use_pkcs11_certificate_signer( 

322 self, 

323 *, 

324 pkcs11_uri: str, 

325 signing_certificate: pathlib.Path, 

326 certificate_chain: Iterable[pathlib.Path], 

327 module_paths: Iterable[str] = frozenset(), 

328 tsa_url: str | None = None, 

329 ) -> Self: 

330 """Configures the signing to be performed using signing certificates. 

331 

332 The signer in this configuration is changed to one that performs signing 

333 using cryptographic certificates. 

334 

335 Args: 

336 pkcs11_uri: The PKCS #11 URI. 

337 signing_certificate: The path to the signing certificate. 

338 certificate_chain: Optional paths to other certificates to establish 

339 a chain of trust. 

340 module_paths: Optional list of paths of PKCS #11 modules. 

341 tsa_url: Optional URL of an RFC 3161 Timestamp Authority. When 

342 provided, the signature will include a trusted timestamp. 

343 

344 Return: 

345 The new signing configuration. 

346 """ 

347 try: 

348 from model_signing._signing import sign_pkcs11 as pkcs11 

349 except ImportError as e: 

350 raise RuntimeError( 

351 "PKCS #11 functionality requires the 'pkcs11' extra. " 

352 "Install with 'pip install model-signing[pkcs11]'." 

353 ) from e 

354 

355 self._signer = pkcs11.CertSigner( 

356 pkcs11_uri, 

357 signing_certificate, 

358 certificate_chain, 

359 module_paths=module_paths, 

360 tsa_url=tsa_url, 

361 ) 

362 return self