Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sigstore/verify/policy.py: 69%

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

138 statements  

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

16APIs for describing identity verification "policies", which describe how the identities 

17passed into an individual verification step are verified. 

18""" 

19 

20from __future__ import annotations 

21 

22import logging 

23from abc import ABC, abstractmethod 

24from typing import Protocol 

25 

26from cryptography.hazmat import asn1 

27from cryptography.x509 import ( 

28 Certificate, 

29 ExtensionNotFound, 

30 ObjectIdentifier, 

31 OtherName, 

32 RFC822Name, 

33 SubjectAlternativeName, 

34 UniformResourceIdentifier, 

35) 

36 

37from sigstore.errors import VerificationError 

38 

39_logger = logging.getLogger(__name__) 

40 

41# From: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md 

42_OIDC_ISSUER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.1") 

43_OIDC_GITHUB_WORKFLOW_TRIGGER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.2") 

44_OIDC_GITHUB_WORKFLOW_SHA_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.3") 

45_OIDC_GITHUB_WORKFLOW_NAME_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.4") 

46_OIDC_GITHUB_WORKFLOW_REPOSITORY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.5") 

47_OIDC_GITHUB_WORKFLOW_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.6") 

48_OTHERNAME_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.7") 

49_OIDC_ISSUER_V2_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.8") 

50_OIDC_BUILD_SIGNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.9") 

51_OIDC_BUILD_SIGNER_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.10") 

52_OIDC_RUNNER_ENVIRONMENT_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.11") 

53_OIDC_SOURCE_REPOSITORY_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.12") 

54_OIDC_SOURCE_REPOSITORY_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.13") 

55_OIDC_SOURCE_REPOSITORY_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.14") 

56_OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.15") 

57_OIDC_SOURCE_REPOSITORY_OWNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.16") 

58_OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID = ObjectIdentifier( 

59 "1.3.6.1.4.1.57264.1.17" 

60) 

61_OIDC_BUILD_CONFIG_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.18") 

62_OIDC_BUILD_CONFIG_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.19") 

63_OIDC_BUILD_TRIGGER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.20") 

64_OIDC_RUN_INVOCATION_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.21") 

65_OIDC_SOURCE_REPOSITORY_VISIBILITY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.22") 

66 

67 

68class _SingleX509ExtPolicy(ABC): 

69 """ 

70 An ABC for verification policies that boil down to checking a single 

71 X.509 extension's value. 

72 """ 

73 

74 oid: ObjectIdentifier 

75 """ 

76 The OID of the extension being checked. 

77 """ 

78 

79 def __init__(self, value: str) -> None: 

80 """ 

81 Creates the new policy, with `value` as the expected value during 

82 verification. 

83 """ 

84 self._value = value 

85 

86 def verify(self, cert: Certificate) -> None: 

87 """ 

88 Verify this policy against `cert`. 

89 

90 Raises `VerificationError` on failure. 

91 """ 

92 try: 

93 ext = cert.extensions.get_extension_for_oid(self.oid).value 

94 except ExtensionNotFound: 

95 raise VerificationError( 

96 f"Certificate does not contain {self.__class__.__name__} " 

97 f"({self.oid.dotted_string}) extension" 

98 ) 

99 

100 # NOTE(ww): mypy is confused by the `Extension[ExtensionType]` returned 

101 # by `get_extension_for_oid` above. 

102 ext_value = ext.value.decode() # type: ignore[attr-defined] 

103 if ext_value != self._value: 

104 raise VerificationError( 

105 f"Certificate's {self.__class__.__name__} does not match " 

106 f"(got '{ext_value}', expected '{self._value}')" 

107 ) 

108 

109 

110class _SingleX509ExtPolicyV2(_SingleX509ExtPolicy): 

111 """ 

112 An base class for verification policies that boil down to checking a single 

113 X.509 extension's value, where the value is formatted as a DER-encoded string, 

114 the ASN.1 tag is UTF8String (0x0C) and the tag class is universal. 

115 """ 

116 

117 def verify(self, cert: Certificate) -> None: 

118 """ 

119 Verify this policy against `cert`. 

120 

121 Raises `VerificationError` on failure. 

122 """ 

123 try: 

124 ext = cert.extensions.get_extension_for_oid(self.oid).value 

125 except ExtensionNotFound: 

126 raise VerificationError( 

127 f"Certificate does not contain {self.__class__.__name__} " 

128 f"({self.oid.dotted_string}) extension" 

129 ) 

130 

131 # NOTE(ww): mypy is confused by the `Extension[ExtensionType]` returned 

132 # by `get_extension_for_oid` above. 

133 ext_value = asn1.decode_der(str, ext.value) # type: ignore[attr-defined] 

134 if ext_value != self._value: 

135 raise VerificationError( 

136 f"Certificate's {self.__class__.__name__} does not match " 

137 f"(got {ext_value}, expected {self._value})" 

138 ) 

139 

140 

141class OIDCIssuer(_SingleX509ExtPolicy): 

142 """ 

143 Verifies the certificate's OIDC issuer, identified by 

144 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.1`. 

145 """ 

146 

147 oid = _OIDC_ISSUER_OID 

148 

149 

150class GitHubWorkflowTrigger(_SingleX509ExtPolicy): 

151 """ 

152 Verifies the certificate's GitHub Actions workflow trigger, 

153 identified by an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.2`. 

154 """ 

155 

156 oid = _OIDC_GITHUB_WORKFLOW_TRIGGER_OID 

157 

158 

159class GitHubWorkflowSHA(_SingleX509ExtPolicy): 

160 """ 

161 Verifies the certificate's GitHub Actions workflow commit SHA, 

162 identified by an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.3`. 

163 """ 

164 

165 oid = _OIDC_GITHUB_WORKFLOW_SHA_OID 

166 

167 

168class GitHubWorkflowName(_SingleX509ExtPolicy): 

169 """ 

170 Verifies the certificate's GitHub Actions workflow name, 

171 identified by an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.4`. 

172 """ 

173 

174 oid = _OIDC_GITHUB_WORKFLOW_NAME_OID 

175 

176 

177class GitHubWorkflowRepository(_SingleX509ExtPolicy): 

178 """ 

179 Verifies the certificate's GitHub Actions workflow repository, 

180 identified by an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.5`. 

181 """ 

182 

183 oid = _OIDC_GITHUB_WORKFLOW_REPOSITORY_OID 

184 

185 

186class GitHubWorkflowRef(_SingleX509ExtPolicy): 

187 """ 

188 Verifies the certificate's GitHub Actions workflow ref, 

189 identified by an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.6`. 

190 """ 

191 

192 oid = _OIDC_GITHUB_WORKFLOW_REF_OID 

193 

194 

195class OIDCIssuerV2(_SingleX509ExtPolicyV2): 

196 """ 

197 Verifies the certificate's OIDC issuer, identified by 

198 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.8`. 

199 The difference with `OIDCIssuer` is that the value for 

200 this extension is formatted to the RFC 5280 specification 

201 as a DER-encoded string. 

202 """ 

203 

204 oid = _OIDC_ISSUER_V2_OID 

205 

206 

207class OIDCBuildSignerURI(_SingleX509ExtPolicyV2): 

208 """ 

209 Verifies the certificate's OIDC Build Signer URI, identified by 

210 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.9`. 

211 """ 

212 

213 oid = _OIDC_BUILD_SIGNER_URI_OID 

214 

215 

216class OIDCBuildSignerDigest(_SingleX509ExtPolicyV2): 

217 """ 

218 Verifies the certificate's OIDC Build Signer Digest, identified by 

219 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.10`. 

220 """ 

221 

222 oid = _OIDC_BUILD_SIGNER_DIGEST_OID 

223 

224 

225class OIDCRunnerEnvironment(_SingleX509ExtPolicyV2): 

226 """ 

227 Verifies the certificate's OIDC Runner Environment, identified by 

228 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.11`. 

229 """ 

230 

231 oid = _OIDC_RUNNER_ENVIRONMENT_OID 

232 

233 

234class OIDCSourceRepositoryURI(_SingleX509ExtPolicyV2): 

235 """ 

236 Verifies the certificate's OIDC Source Repository URI, identified by 

237 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.12`. 

238 """ 

239 

240 oid = _OIDC_SOURCE_REPOSITORY_URI_OID 

241 

242 

243class OIDCSourceRepositoryDigest(_SingleX509ExtPolicyV2): 

244 """ 

245 Verifies the certificate's OIDC Source Repository Digest, identified by 

246 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.13`. 

247 """ 

248 

249 oid = _OIDC_SOURCE_REPOSITORY_DIGEST_OID 

250 

251 

252class OIDCSourceRepositoryRef(_SingleX509ExtPolicyV2): 

253 """ 

254 Verifies the certificate's OIDC Source Repository Ref, identified by 

255 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.14`. 

256 """ 

257 

258 oid = _OIDC_SOURCE_REPOSITORY_REF_OID 

259 

260 

261class OIDCSourceRepositoryIdentifier(_SingleX509ExtPolicyV2): 

262 """ 

263 Verifies the certificate's OIDC Source Repository Identifier, identified by 

264 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.15`. 

265 """ 

266 

267 oid = _OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID 

268 

269 

270class OIDCSourceRepositoryOwnerURI(_SingleX509ExtPolicyV2): 

271 """ 

272 Verifies the certificate's OIDC Source Repository Owner URI, identified by 

273 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.16`. 

274 """ 

275 

276 oid = _OIDC_SOURCE_REPOSITORY_OWNER_URI_OID 

277 

278 

279class OIDCSourceRepositoryOwnerIdentifier(_SingleX509ExtPolicyV2): 

280 """ 

281 Verifies the certificate's OIDC Source Repository Owner Identifier, identified by 

282 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.17`. 

283 """ 

284 

285 oid = _OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID 

286 

287 

288class OIDCBuildConfigURI(_SingleX509ExtPolicyV2): 

289 """ 

290 Verifies the certificate's OIDC Build Config URI, identified by 

291 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.18`. 

292 """ 

293 

294 oid = _OIDC_BUILD_CONFIG_URI_OID 

295 

296 

297class OIDCBuildConfigDigest(_SingleX509ExtPolicyV2): 

298 """ 

299 Verifies the certificate's OIDC Build Config Digest, identified by 

300 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.19`. 

301 """ 

302 

303 oid = _OIDC_BUILD_CONFIG_DIGEST_OID 

304 

305 

306class OIDCBuildTrigger(_SingleX509ExtPolicyV2): 

307 """ 

308 Verifies the certificate's OIDC Build Trigger, identified by 

309 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.20`. 

310 """ 

311 

312 oid = _OIDC_BUILD_TRIGGER_OID 

313 

314 

315class OIDCRunInvocationURI(_SingleX509ExtPolicyV2): 

316 """ 

317 Verifies the certificate's OIDC Run Invocation URI, identified by 

318 an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.21`. 

319 """ 

320 

321 oid = _OIDC_RUN_INVOCATION_URI_OID 

322 

323 

324class OIDCSourceRepositoryVisibility(_SingleX509ExtPolicyV2): 

325 """ 

326 Verifies the certificate's OIDC Source Repository Visibility 

327 At Signing, identified by an X.509v3 extension tagged with 

328 `1.3.6.1.4.1.57264.1.22`. 

329 """ 

330 

331 oid = _OIDC_SOURCE_REPOSITORY_VISIBILITY_OID 

332 

333 

334class VerificationPolicy(Protocol): 

335 """ 

336 A protocol type describing the interface that all verification policies 

337 conform to. 

338 """ 

339 

340 @abstractmethod 

341 def verify(self, cert: Certificate) -> None: 

342 """ 

343 Verify the given `cert` against this policy, raising `VerificationError` 

344 on failure. 

345 """ 

346 raise NotImplementedError # pragma: no cover 

347 

348 

349class AnyOf: 

350 """ 

351 The "any of" policy, corresponding to a logical OR between child policies. 

352 

353 An empty list of child policies is considered trivially invalid. 

354 """ 

355 

356 def __init__(self, children: list[VerificationPolicy]): 

357 """ 

358 Create a new `AnyOf`, with the given child policies. 

359 """ 

360 self._children = children 

361 

362 def verify(self, cert: Certificate) -> None: 

363 """ 

364 Verify `cert` against the policy. 

365 

366 Raises `VerificationError` on failure. 

367 """ 

368 

369 for child in self._children: 

370 try: 

371 child.verify(cert) 

372 except VerificationError: 

373 pass 

374 else: 

375 return 

376 

377 raise VerificationError(f"0 of {len(self._children)} policies succeeded") 

378 

379 

380class AllOf: 

381 """ 

382 The "all of" policy, corresponding to a logical AND between child 

383 policies. 

384 

385 An empty list of child policies is considered trivially invalid. 

386 """ 

387 

388 def __init__(self, children: list[VerificationPolicy]): 

389 """ 

390 Create a new `AllOf`, with the given child policies. 

391 """ 

392 

393 self._children = children 

394 

395 def verify(self, cert: Certificate) -> None: 

396 """ 

397 Verify `cert` against the policy. 

398 """ 

399 

400 # Without this, we'd consider empty lists of child policies trivially valid. 

401 # This is almost certainly not what the user wants and is a potential 

402 # source of API misuse, so we explicitly disallow it. 

403 if len(self._children) < 1: 

404 raise VerificationError("no child policies to verify") 

405 

406 for child in self._children: 

407 child.verify(cert) 

408 

409 

410class UnsafeNoOp: 

411 """ 

412 The "no-op" policy, corresponding to a no-op "verification". 

413 

414 **This policy is fundamentally insecure. You cannot use it safely. 

415 It must not be used to verify any sort of certificate identity, because 

416 it cannot do so. Using this policy is equivalent to reducing the 

417 verification proof down to an integrity check against a completely 

418 untrusted and potentially attacker-created signature. It must only 

419 be used for testing purposes.** 

420 """ 

421 

422 def verify(self, cert: Certificate) -> None: 

423 """ 

424 Verify `cert` against the policy. 

425 """ 

426 

427 _logger.warning( 

428 "unsafe (no-op) verification policy used! no verification performed!" 

429 ) 

430 

431 

432class Identity: 

433 """ 

434 Verifies the certificate's "identity", corresponding to the X.509v3 SAN. 

435 

436 Identities can be verified modulo an OIDC issuer, to prevent an unexpected 

437 issuer from offering a particular identity. 

438 

439 Supported SAN types include emails, URIs, and Sigstore-specific "other names". 

440 """ 

441 

442 _issuer: OIDCIssuer | None 

443 

444 def __init__(self, *, identity: str, issuer: str | None = None): 

445 """ 

446 Create a new `Identity`, with the given expected identity and issuer values. 

447 """ 

448 

449 self._identity = identity 

450 if issuer: 

451 self._issuer = OIDCIssuer(issuer) 

452 else: 

453 self._issuer = None 

454 

455 def verify(self, cert: Certificate) -> None: 

456 """ 

457 Verify `cert` against the policy. 

458 """ 

459 

460 if self._issuer: 

461 self._issuer.verify(cert) 

462 

463 # Build a set of all valid identities. 

464 san_ext = cert.extensions.get_extension_for_class(SubjectAlternativeName).value 

465 all_sans = set(san_ext.get_values_for_type(RFC822Name)) 

466 all_sans.update(san_ext.get_values_for_type(UniformResourceIdentifier)) 

467 all_sans.update( 

468 [ 

469 on.value.decode() 

470 for on in san_ext.get_values_for_type(OtherName) 

471 if on.type_id == _OTHERNAME_OID 

472 ] 

473 ) 

474 

475 verified = self._identity in all_sans 

476 if not verified: 

477 raise VerificationError( 

478 f"Certificate's SANs do not match {self._identity}; actual SANs: {all_sans}" 

479 )