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

139 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.x509 import ( 

27 Certificate, 

28 ExtensionNotFound, 

29 ObjectIdentifier, 

30 OtherName, 

31 RFC822Name, 

32 SubjectAlternativeName, 

33 UniformResourceIdentifier, 

34) 

35from pyasn1.codec.der.decoder import decode as der_decode 

36from pyasn1.type.char import UTF8String 

37 

38from sigstore.errors import VerificationError 

39 

40_logger = logging.getLogger(__name__) 

41 

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

43_OIDC_ISSUER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.1") 

44_OIDC_GITHUB_WORKFLOW_TRIGGER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.2") 

45_OIDC_GITHUB_WORKFLOW_SHA_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.3") 

46_OIDC_GITHUB_WORKFLOW_NAME_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.4") 

47_OIDC_GITHUB_WORKFLOW_REPOSITORY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.5") 

48_OIDC_GITHUB_WORKFLOW_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.6") 

49_OTHERNAME_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.7") 

50_OIDC_ISSUER_V2_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.8") 

51_OIDC_BUILD_SIGNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.9") 

52_OIDC_BUILD_SIGNER_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.10") 

53_OIDC_RUNNER_ENVIRONMENT_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.11") 

54_OIDC_SOURCE_REPOSITORY_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.12") 

55_OIDC_SOURCE_REPOSITORY_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.13") 

56_OIDC_SOURCE_REPOSITORY_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.14") 

57_OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.15") 

58_OIDC_SOURCE_REPOSITORY_OWNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.16") 

59_OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID = ObjectIdentifier( 

60 "1.3.6.1.4.1.57264.1.17" 

61) 

62_OIDC_BUILD_CONFIG_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.18") 

63_OIDC_BUILD_CONFIG_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.19") 

64_OIDC_BUILD_TRIGGER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.20") 

65_OIDC_RUN_INVOCATION_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.21") 

66_OIDC_SOURCE_REPOSITORY_VISIBILITY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.22") 

67 

68 

69class _SingleX509ExtPolicy(ABC): 

70 """ 

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

72 X.509 extension's value. 

73 """ 

74 

75 oid: ObjectIdentifier 

76 """ 

77 The OID of the extension being checked. 

78 """ 

79 

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

81 """ 

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

83 verification. 

84 """ 

85 self._value = value 

86 

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

88 """ 

89 Verify this policy against `cert`. 

90 

91 Raises `VerificationError` on failure. 

92 """ 

93 try: 

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

95 except ExtensionNotFound: 

96 raise VerificationError( 

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

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

99 ) 

100 

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

102 # by `get_extension_for_oid` above. 

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

104 if ext_value != self._value: 

105 raise VerificationError( 

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

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

108 ) 

109 

110 

111class _SingleX509ExtPolicyV2(_SingleX509ExtPolicy): 

112 """ 

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

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

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

116 """ 

117 

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

119 """ 

120 Verify this policy against `cert`. 

121 

122 Raises `VerificationError` on failure. 

123 """ 

124 try: 

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

126 except ExtensionNotFound: 

127 raise VerificationError( 

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

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

130 ) 

131 

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

133 # by `get_extension_for_oid` above. 

134 ext_value = der_decode(ext.value, UTF8String)[0].decode() # type: ignore[attr-defined] 

135 if ext_value != self._value: 

136 raise VerificationError( 

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

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

139 ) 

140 

141 

142class OIDCIssuer(_SingleX509ExtPolicy): 

143 """ 

144 Verifies the certificate's OIDC issuer, identified by 

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

146 """ 

147 

148 oid = _OIDC_ISSUER_OID 

149 

150 

151class GitHubWorkflowTrigger(_SingleX509ExtPolicy): 

152 """ 

153 Verifies the certificate's GitHub Actions workflow trigger, 

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

155 """ 

156 

157 oid = _OIDC_GITHUB_WORKFLOW_TRIGGER_OID 

158 

159 

160class GitHubWorkflowSHA(_SingleX509ExtPolicy): 

161 """ 

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

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

164 """ 

165 

166 oid = _OIDC_GITHUB_WORKFLOW_SHA_OID 

167 

168 

169class GitHubWorkflowName(_SingleX509ExtPolicy): 

170 """ 

171 Verifies the certificate's GitHub Actions workflow name, 

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

173 """ 

174 

175 oid = _OIDC_GITHUB_WORKFLOW_NAME_OID 

176 

177 

178class GitHubWorkflowRepository(_SingleX509ExtPolicy): 

179 """ 

180 Verifies the certificate's GitHub Actions workflow repository, 

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

182 """ 

183 

184 oid = _OIDC_GITHUB_WORKFLOW_REPOSITORY_OID 

185 

186 

187class GitHubWorkflowRef(_SingleX509ExtPolicy): 

188 """ 

189 Verifies the certificate's GitHub Actions workflow ref, 

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

191 """ 

192 

193 oid = _OIDC_GITHUB_WORKFLOW_REF_OID 

194 

195 

196class OIDCIssuerV2(_SingleX509ExtPolicyV2): 

197 """ 

198 Verifies the certificate's OIDC issuer, identified by 

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

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

201 this extension is formatted to the RFC 5280 specification 

202 as a DER-encoded string. 

203 """ 

204 

205 oid = _OIDC_ISSUER_V2_OID 

206 

207 

208class OIDCBuildSignerURI(_SingleX509ExtPolicyV2): 

209 """ 

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

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

212 """ 

213 

214 oid = _OIDC_BUILD_SIGNER_URI_OID 

215 

216 

217class OIDCBuildSignerDigest(_SingleX509ExtPolicyV2): 

218 """ 

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

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

221 """ 

222 

223 oid = _OIDC_BUILD_SIGNER_DIGEST_OID 

224 

225 

226class OIDCRunnerEnvironment(_SingleX509ExtPolicyV2): 

227 """ 

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

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

230 """ 

231 

232 oid = _OIDC_RUNNER_ENVIRONMENT_OID 

233 

234 

235class OIDCSourceRepositoryURI(_SingleX509ExtPolicyV2): 

236 """ 

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

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

239 """ 

240 

241 oid = _OIDC_SOURCE_REPOSITORY_URI_OID 

242 

243 

244class OIDCSourceRepositoryDigest(_SingleX509ExtPolicyV2): 

245 """ 

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

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

248 """ 

249 

250 oid = _OIDC_SOURCE_REPOSITORY_DIGEST_OID 

251 

252 

253class OIDCSourceRepositoryRef(_SingleX509ExtPolicyV2): 

254 """ 

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

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

257 """ 

258 

259 oid = _OIDC_SOURCE_REPOSITORY_REF_OID 

260 

261 

262class OIDCSourceRepositoryIdentifier(_SingleX509ExtPolicyV2): 

263 """ 

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

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

266 """ 

267 

268 oid = _OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID 

269 

270 

271class OIDCSourceRepositoryOwnerURI(_SingleX509ExtPolicyV2): 

272 """ 

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

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

275 """ 

276 

277 oid = _OIDC_SOURCE_REPOSITORY_OWNER_URI_OID 

278 

279 

280class OIDCSourceRepositoryOwnerIdentifier(_SingleX509ExtPolicyV2): 

281 """ 

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

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

284 """ 

285 

286 oid = _OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID 

287 

288 

289class OIDCBuildConfigURI(_SingleX509ExtPolicyV2): 

290 """ 

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

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

293 """ 

294 

295 oid = _OIDC_BUILD_CONFIG_URI_OID 

296 

297 

298class OIDCBuildConfigDigest(_SingleX509ExtPolicyV2): 

299 """ 

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

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

302 """ 

303 

304 oid = _OIDC_BUILD_CONFIG_DIGEST_OID 

305 

306 

307class OIDCBuildTrigger(_SingleX509ExtPolicyV2): 

308 """ 

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

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

311 """ 

312 

313 oid = _OIDC_BUILD_TRIGGER_OID 

314 

315 

316class OIDCRunInvocationURI(_SingleX509ExtPolicyV2): 

317 """ 

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

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

320 """ 

321 

322 oid = _OIDC_RUN_INVOCATION_URI_OID 

323 

324 

325class OIDCSourceRepositoryVisibility(_SingleX509ExtPolicyV2): 

326 """ 

327 Verifies the certificate's OIDC Source Repository Visibility 

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

329 `1.3.6.1.4.1.57264.1.22`. 

330 """ 

331 

332 oid = _OIDC_SOURCE_REPOSITORY_VISIBILITY_OID 

333 

334 

335class VerificationPolicy(Protocol): 

336 """ 

337 A protocol type describing the interface that all verification policies 

338 conform to. 

339 """ 

340 

341 @abstractmethod 

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

343 """ 

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

345 on failure. 

346 """ 

347 raise NotImplementedError # pragma: no cover 

348 

349 

350class AnyOf: 

351 """ 

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

353 

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

355 """ 

356 

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

358 """ 

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

360 """ 

361 self._children = children 

362 

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

364 """ 

365 Verify `cert` against the policy. 

366 

367 Raises `VerificationError` on failure. 

368 """ 

369 

370 for child in self._children: 

371 try: 

372 child.verify(cert) 

373 except VerificationError: 

374 pass 

375 else: 

376 return 

377 

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

379 

380 

381class AllOf: 

382 """ 

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

384 policies. 

385 

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

387 """ 

388 

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

390 """ 

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

392 """ 

393 

394 self._children = children 

395 

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

397 """ 

398 Verify `cert` against the policy. 

399 """ 

400 

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

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

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

404 if len(self._children) < 1: 

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

406 

407 for child in self._children: 

408 child.verify(cert) 

409 

410 

411class UnsafeNoOp: 

412 """ 

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

414 

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

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

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

418 verification proof down to an integrity check against a completely 

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

420 be used for testing purposes.** 

421 """ 

422 

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

424 """ 

425 Verify `cert` against the policy. 

426 """ 

427 

428 _logger.warning( 

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

430 ) 

431 

432 

433class Identity: 

434 """ 

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

436 

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

438 issuer from offering a particular identity. 

439 

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

441 """ 

442 

443 _issuer: OIDCIssuer | None 

444 

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

446 """ 

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

448 """ 

449 

450 self._identity = identity 

451 if issuer: 

452 self._issuer = OIDCIssuer(issuer) 

453 else: 

454 self._issuer = None 

455 

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

457 """ 

458 Verify `cert` against the policy. 

459 """ 

460 

461 if self._issuer: 

462 self._issuer.verify(cert) 

463 

464 # Build a set of all valid identities. 

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

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

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

468 all_sans.update( 

469 [ 

470 on.value.decode() 

471 for on in san_ext.get_values_for_type(OtherName) 

472 if on.type_id == _OTHERNAME_OID 

473 ] 

474 ) 

475 

476 verified = self._identity in all_sans 

477 if not verified: 

478 raise VerificationError( 

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

480 )