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

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

58 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 

47import sys 

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 

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

57 from typing import Self 

58else: 

59 from typing_extensions import Self 

60 

61 

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

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

64 

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

66 configuration from `model_signing.hashing`. 

67 

68 The resulting signature is in the Sigstore bundle format. 

69 

70 Args: 

71 model_path: the path to the model to sign. 

72 signature_path: the path of the resulting signature. 

73 """ 

74 Config().sign(model_path, signature_path) 

75 

76 

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

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

79 

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

81 configuration from `model_signing.hashing`. 

82 

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

84 encoded JSON bytes instead of being written to disk. 

85 

86 Args: 

87 model_path: the path to the model to sign. 

88 

89 Returns: 

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

91 """ 

92 return Config().sign_to_bytes(model_path) 

93 

94 

95class Config: 

96 """Configuration to use when signing models. 

97 

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

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

100 signing with signing certificates, and signing with custom 

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

102 This allows users to bring their own trust configuration 

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

104 added in the future. 

105 """ 

106 

107 def __init__(self): 

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

109 self._hashing_config = hashing.Config() 

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

111 self._signer = None 

112 

113 def sign( 

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

115 ): 

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

117 

118 Args: 

119 model_path: The path to the model to sign. 

120 signature_path: The path of the resulting signature. 

121 """ 

122 signature = self._sign(model_path) 

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

124 

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

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

127 

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

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

130 pipeline contexts where writing to the filesystem is undesirable or 

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

132 store, or passed directly to another process. 

133 

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

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

136 

137 Args: 

138 model_path: The path to the model to sign. 

139 

140 Returns: 

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

142 """ 

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

144 

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

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

147 

148 Args: 

149 model_path: The path to the model to sign. 

150 

151 Returns: 

152 The `Signature` produced by the configured signer. 

153 """ 

154 if self._signer is None: 

155 self.use_sigstore_signer() 

156 manifest = self._hashing_config.hash(model_path) 

157 payload = signing.Payload(manifest) 

158 return self._signer.sign(payload) 

159 

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

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

162 

163 Args: 

164 hashing_config: The new hashing configuration. 

165 

166 Returns: 

167 The new signing configuration. 

168 """ 

169 self._hashing_config = hashing_config 

170 return self 

171 

172 def use_sigstore_signer( 

173 self, 

174 *, 

175 oidc_issuer: str | None = None, 

176 use_ambient_credentials: bool = False, 

177 use_staging: bool = False, 

178 force_oob: bool = False, 

179 identity_token: str | None = None, 

180 client_id: str | None = None, 

181 client_secret: str | None = None, 

182 trust_config: pathlib.Path | None = None, 

183 ) -> Self: 

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

185 

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

187 with Sigstore. 

188 

189 Args: 

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

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

192 Default is empty, relying on the Sigstore configuration. 

193 use_ambient_credentials: Use ambient credentials (also known as 

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

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

196 signer identity via OIDC will start. 

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

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

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

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

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

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

203 opened automatically if possible. 

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

205 taking precedence over any ambient credential or OAuth workflow. 

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

207 authentication. This is typically used to identify the 

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

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

210 used. 

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

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

213 required for confidential clients that need to prove their 

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

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

216 secret. 

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

218 the signature verification process will rely on the supplied 

219 PKI and trust configurations, instead of the default Sigstore 

220 setup. If not specified, the default Sigstore configuration 

221 is used. 

222 

223 Return: 

224 The new signing configuration. 

225 """ 

226 self._signer = sigstore.Signer( 

227 oidc_issuer=oidc_issuer, 

228 use_ambient_credentials=use_ambient_credentials, 

229 use_staging=use_staging, 

230 identity_token=identity_token, 

231 force_oob=force_oob, 

232 client_id=client_id, 

233 client_secret=client_secret, 

234 trust_config=trust_config, 

235 ) 

236 return self 

237 

238 def use_elliptic_key_signer( 

239 self, *, private_key: hashing.PathLike, password: str | None = None 

240 ) -> Self: 

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

242 

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

244 using a private key based on elliptic curve cryptography. 

245 

246 Args: 

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

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

249 

250 Return: 

251 The new signing configuration. 

252 """ 

253 self._signer = ec_key.Signer(pathlib.Path(private_key), password) 

254 return self 

255 

256 def use_certificate_signer( 

257 self, 

258 *, 

259 private_key: hashing.PathLike, 

260 signing_certificate: hashing.PathLike, 

261 certificate_chain: Iterable[hashing.PathLike], 

262 ) -> Self: 

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

264 

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

266 using cryptographic signing certificates. 

267 

268 Args: 

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

270 signing_certificate: The path to the signing certificate. 

271 certificate_chain: Optional paths to other certificates to establish 

272 a chain of trust. 

273 

274 Return: 

275 The new signing configuration. 

276 """ 

277 self._signer = certificate.Signer( 

278 pathlib.Path(private_key), 

279 pathlib.Path(signing_certificate), 

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

281 ) 

282 return self 

283 

284 def use_pkcs11_signer( 

285 self, *, pkcs11_uri: str, module_paths: Iterable[str] = frozenset() 

286 ) -> Self: 

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

288 

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

290 using a private key based on elliptic curve cryptography. 

291 

292 Args: 

293 pkcs11_uri: The PKCS11 URI. 

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

295 

296 Return: 

297 The new signing configuration. 

298 """ 

299 try: 

300 from model_signing._signing import sign_pkcs11 as pkcs11 

301 except ImportError as e: 

302 raise RuntimeError( 

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

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

305 ) from e 

306 self._signer = pkcs11.Signer(pkcs11_uri, module_paths) 

307 return self 

308 

309 def use_pkcs11_certificate_signer( 

310 self, 

311 *, 

312 pkcs11_uri: str, 

313 signing_certificate: pathlib.Path, 

314 certificate_chain: Iterable[pathlib.Path], 

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

316 ) -> Self: 

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

318 

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

320 using cryptographic certificates. 

321 

322 Args: 

323 pkcs11_uri: The PKCS #11 URI. 

324 signing_certificate: The path to the signing certificate. 

325 certificate_chain: Optional paths to other certificates to establish 

326 a chain of trust. 

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

328 

329 Return: 

330 The new signing configuration. 

331 """ 

332 try: 

333 from model_signing._signing import sign_pkcs11 as pkcs11 

334 except ImportError as e: 

335 raise RuntimeError( 

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

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

338 ) from e 

339 

340 self._signer = pkcs11.CertSigner( 

341 pkcs11_uri, 

342 signing_certificate, 

343 certificate_chain, 

344 module_paths=module_paths, 

345 ) 

346 return self