diff --git a/pyproject.toml b/pyproject.toml index 821b51f6..0c2bed5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ lint = [ "setuptools", "interrogate", "mypy ~= 1.1", + "pyasn1", # NOTE(ww): ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. "ruff < 0.4.4", diff --git a/sigstore/verify/policy.py b/sigstore/verify/policy.py index 24bab4be..4e931918 100644 --- a/sigstore/verify/policy.py +++ b/sigstore/verify/policy.py @@ -32,6 +32,8 @@ SubjectAlternativeName, UniformResourceIdentifier, ) +from pyasn1.codec.der.decoder import decode as der_decode +from pyasn1.type.char import UTF8String from sigstore.errors import VerificationError @@ -45,6 +47,23 @@ _OIDC_GITHUB_WORKFLOW_REPOSITORY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.5") _OIDC_GITHUB_WORKFLOW_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.6") _OTHERNAME_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.7") +_OIDC_ISSUER_V2_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.8") +_OIDC_BUILD_SIGNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.9") +_OIDC_BUILD_SIGNER_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.10") +_OIDC_RUNNER_ENVIRONMENT_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.11") +_OIDC_SOURCE_REPOSITORY_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.12") +_OIDC_SOURCE_REPOSITORY_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.13") +_OIDC_SOURCE_REPOSITORY_REF_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.14") +_OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.15") +_OIDC_SOURCE_REPOSITORY_OWNER_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.16") +_OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID = ObjectIdentifier( + "1.3.6.1.4.1.57264.1.17" +) +_OIDC_BUILD_CONFIG_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.18") +_OIDC_BUILD_CONFIG_DIGEST_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.19") +_OIDC_BUILD_TRIGGER_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.20") +_OIDC_RUN_INVOCATION_URI_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.21") +_OIDC_SOURCE_REPOSITORY_VISIBILITY_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.22") class _SingleX509ExtPolicy(ABC): @@ -93,6 +112,41 @@ def verify(self, cert: Certificate) -> None: ) +class _SingleX509ExtPolicyDer(_SingleX509ExtPolicy): + """ + An base class for verification policies that boil down to checking a single + X.509 extension's value, where the value is formatted as a DER-encoded string, + the ASN.1 tag is UTF8String (0x0C) and the tag class is universal. + """ + + def verify(self, cert: Certificate) -> None: + """ + Verify this policy against `cert`. + + Raises `VerificationError` on failure. + """ + try: + ext = cert.extensions.get_extension_for_oid(self.oid).value + except ExtensionNotFound: + raise VerificationError( + ( + f"Certificate does not contain {self.__class__.__name__} " + f"({self.oid.dotted_string}) extension" + ) + ) + + # NOTE(ww): mypy is confused by the `Extension[ExtensionType]` returned + # by `get_extension_for_oid` above. + ext_value = der_decode(ext.value, UTF8String)[0].decode() # type: ignore[attr-defined] + if ext_value != self._value: + raise VerificationError( + ( + f"Certificate's {self.__class__.__name__} does not match " + f"(got {ext_value}, expected {self._value})" + ) + ) + + class OIDCIssuer(_SingleX509ExtPolicy): """ Verifies the certificate's OIDC issuer, identified by @@ -147,6 +201,145 @@ class GitHubWorkflowRef(_SingleX509ExtPolicy): oid = _OIDC_GITHUB_WORKFLOW_REF_OID +class OIDCIssuerV2(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC issuer, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.8`. + The difference with `OIDCIssuer` is that the value for + this extension is formatted to the RFC 5280 specification + as a DER-encoded string. + """ + + oid = _OIDC_ISSUER_V2_OID + + +class OIDCBuildSignerURI(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Build Signer URI, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.9`. + """ + + oid = _OIDC_BUILD_SIGNER_URI_OID + + +class OIDCBuildSignerDigest(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Build Signer Digest, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.10`. + """ + + oid = _OIDC_BUILD_SIGNER_DIGEST_OID + + +class OIDCRunnerEnvironment(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Runner Environment, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.11`. + """ + + oid = _OIDC_RUNNER_ENVIRONMENT_OID + + +class OIDCSourceRepositoryURI(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository URI, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.12`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_URI_OID + + +class OIDCSourceRepositoryDigest(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Digest, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.13`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_DIGEST_OID + + +class OIDCSourceRepositoryRef(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Ref, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.14`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_REF_OID + + +class OIDCSourceRepositoryIdentifier(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Identifier, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.15`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_IDENTIFIER_OID + + +class OIDCSourceRepositoryOwnerURI(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Owner URI, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.16`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_OWNER_URI_OID + + +class OIDCSourceRepositoryOwnerIdentifier(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Owner Identifier, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.17`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_OWNER_IDENTIFIER_OID + + +class OIDCBuildConfigURI(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Build Config URI, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.18`. + """ + + oid = _OIDC_BUILD_CONFIG_URI_OID + + +class OIDCBuildConfigDigest(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Build Config Digest, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.19`. + """ + + oid = _OIDC_BUILD_CONFIG_DIGEST_OID + + +class OIDCBuildTrigger(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Build Trigger, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.20`. + """ + + oid = _OIDC_BUILD_TRIGGER_OID + + +class OIDCRunInvocationURI(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Run Invocation URI, identified by + an X.509v3 extension tagged with `1.3.6.1.4.1.57264.1.21`. + """ + + oid = _OIDC_RUN_INVOCATION_URI_OID + + +class OIDCSourceRepositoryVisibility(_SingleX509ExtPolicyDer): + """ + Verifies the certificate's OIDC Source Repository Visibility + At Signing, identified by an X.509v3 extension tagged with + `1.3.6.1.4.1.57264.1.22`. + """ + + oid = _OIDC_SOURCE_REPOSITORY_VISIBILITY_OID + + class VerificationPolicy(Protocol): """ A protocol type describing the interface that all verification policies diff --git a/test/unit/assets/bundle_v3_github.whl b/test/unit/assets/bundle_v3_github.whl new file mode 100644 index 00000000..00225acb Binary files /dev/null and b/test/unit/assets/bundle_v3_github.whl differ diff --git a/test/unit/assets/bundle_v3_github.whl.sigstore b/test/unit/assets/bundle_v3_github.whl.sigstore new file mode 100644 index 00000000..f00a4a78 --- /dev/null +++ b/test/unit/assets/bundle_v3_github.whl.sigstore @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.2", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "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"}]}, "tlogEntries": [{"logIndex": "79605083", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1710888076", "inclusionPromise": {"signedEntryTimestamp": "MEYCIQD8ohK48/Ls8D4Qd3dQZl6geplAt0p5Sgpa1wabniB/ZgIhALsVfKCe1m2KKtaEImxijm5bO2K49NltHWafJE2a1hnr"}, "inclusionProof": {"logIndex": "75441652", "rootHash": "uAqI3id6JHPMMNUltHIKHuX1kVHpm5y7jSfnbaRO+E4=", "treeSize": "75441653", "hashes": ["XoeIGlDW7f2lVjTlQEXPaV7szUXY2BECAEKtNA/lgfk=", "Pz5CyFQH78eikJoZuJ44Ls4R5najWJ1nKWunxb/vxeM=", "COo4wZnRb/d6zZOa7RP1euSRFb7H5EX5bYXs4HEQ0uU=", "1A4EnFDN5UCHjrJDWPuYDmY+ZLb4B+Jvis+k3ti+wjs=", "bBpWKtQryG7/tMDt9HDvKk/Fp3S+q7gTnYF56qGKMiI=", "ZR8qbYzXTNaK4SaofTZtbR0srNmOJ0Yx891OF5/G2gQ=", "7MueyMCRkh/GaluPkJl3xQFyXFq/SS9xykP299KtvS0=", "kFt/VRwfXksHcnd9vpdeifz3N16KyWQoDxAPfLlRwTA=", "gtt9e0foHZTCS9w+epNsmDWbwvX4FNV1EAg0rhxLfjg=", "BGqH+LzVuhuqCLiUvBJaB2hlsvtu2a15qq1WGw6mG44=", "OeS7D4kPES7ChE7kWSEmhbAMqBcKVj/z8/afMK4Y3pI=", "JtjqvAqFyXXYjWlZfDzElHpEzdBjsz1LmGFJuYx0kTU=", "s/ZIVcfcD4/nuZwUtQf4ydGsIAkGTPTzk3b0zhUC95k=", "YU1jZY/fp5tJdGF/i+/7ez8107O4/lOUp7acMPFEaOA=", "7Z18YLBAvejEV4nJHIKoks/xlijnhR005qTW2w4QtHg=", "98enzMaC+x5oCMvIZQA5z8vu2apDMCFvE/935NfuPw8="], "checkpoint": {"envelope": "rekor.sigstore.dev - 2605736670972794746\n75441653\nuAqI3id6JHPMMNUltHIKHuX1kVHpm5y7jSfnbaRO+E4=\n\n\u2014 rekor.sigstore.dev wNI9ajBGAiEA5perJLLm94gCQOQT5/vO29OXWNZ1SoengZDZ/U6vsOUCIQDBL0BIkCjWGR6V622znnVpXF5D1g0jPgajBlHh8uSc8g==\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjNGU5MmU5ZWNjODI4YmVmMmFhN2RiYTFkZThhYzk4MzUxMWY3NTMyYTBkZjExYzc3MGQzOTA5OWEyNWNmMjAxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNlSDZFM01wWm5nV0E2UlBnOEhBbC9aNzY0aFRGWXljTnlGM1IrbVBUU2JBSWhBUGdNUzhxQk04bENFVTJYVzc2NW15TU16Mnp1eXU5aVRGNDBQSCtYWmxKUSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVZDZla05EUW14VFowRjNTVUpCWjBsVlRUSTVZblpaYTNKRVMyNUNWbHB0Vm1Wc2IxUlZUV3hhY1U1WmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDE2UlRWTmFra3dUVlJGTVZkb1kwNU5hbEYzVFhwRk5VMXFTVEZOVkVVeFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV4Y1RoM2JYQnRTekIyWlhORFJEQTFXa1V4YnpWS2VYVXJaeTlEZEV4YVRGaE9SVm9LYVVsdmJXZ3hhbkYxVUUxRFduSm9iRkJrVDJaNlVYZHpLMFVyU1ZWQ1dETndZMVpWZUhSdU5ISlpTMjVOU0RNNWIyRlBRMEpZVFhkbloxWjJUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlV3VUdGVkNtSm9kSEE0TkU5eVlqSlpZWFIyV210SmFtdGFhVTlGZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFwbldVUldVakJTUVZGSUwwSkdkM2RYYjFwWllVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcFlWaFNlZ3BNTTBwdFdYcG5NMDlFVlhWalNHdDJURzFrY0dSSGFERlphVGt6WWpOS2NscHRlSFprTTAxMlkyMVdjMXBYUm5wYVV6VTFZbGQ0UVdOdFZtMWplVGt3Q2xsWFpIcE1NMWwzVEdwRmRVMXFRVFZDWjI5eVFtZEZSVUZaVHk5TlFVVkNRa04wYjJSSVVuZGplbTkyVEROU2RtRXlWblZNYlVacVpFZHNkbUp1VFhVS1dqSnNNR0ZJVm1sa1dFNXNZMjFPZG1KdVVteGlibEYxV1RJNWRFMUNWVWREYVhOSFFWRlJRbWMzT0hkQlVVbEZRak5LYkdKSFZtaGpNbFYzVG1kWlN3cExkMWxDUWtGSFJIWjZRVUpCZDFGdldrUm9hVTVIUlRKT1JGRXhXbXBOTkZsNlVUUlphbXQ0VFhwa2FFOUVRVFZQVkdOM1RtMVJOVmxxWjNkT2VrMTRDazVFV214T1JFRldRbWR2Y2tKblJVVkJXVTh2VFVGRlJVSkJaSGxhVjNoc1dWaE9iRTFEVVVkRGFYTkhRVkZSUW1jM09IZEJVVlZGUm01U2VWbFhiSE1LWWpKYWFXRllVbnBNTTBwdFdYcG5NMDlFVlhWalNHdDNTR2RaUzB0M1dVSkNRVWRFZG5wQlFrSm5VVkZqYlZadFkzazVNRmxYWkhwTU0xbDNUR3BGZFFwTmFrRTNRbWR2Y2tKblJVVkJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveENtTXlWbmxaTWpsMVpFZFdkV1JETldwaU1qQjNZVUZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbUZFUm1odlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5Xb0tZakl3ZG1SSVNtaGhWM2gyV20xS2NHUklUWFpqYlZwcVQwUmpORTVUTlhkbFV6aDFXakpzTUdGSVZtbE1NMlIyWTIxMGJXSkhPVE5qZVRsNVdsZDRiQXBaV0U1c1RHNXNkR0pGUW5sYVYxcDZURE5TYUZvelRYWmtha0YxVFZNMGVVMUVaMGREYVhOSFFWRlJRbWMzT0hkQlVXOUZTMmQzYjFwRWFHbE9SMFV5Q2s1RVVURmFhazAwV1hwUk5GbHFhM2hOZW1Sb1QwUkJOVTlVWTNkT2JWRTFXV3BuZDA1NlRYaE9SRnBzVGtSQlpFSm5iM0pDWjBWRlFWbFBMMDFCUlV3S1FrRTRUVVJYWkhCa1IyZ3hXV2t4YjJJelRqQmFWMUYzVDFGWlMwdDNXVUpDUVVkRWRucEJRa1JCVVhKRVEyeHZaRWhTZDJONmIzWk1NbVJ3WkVkb01RcFphVFZxWWpJd2RtUklTbWhoVjNoMldtMUtjR1JJVFhaamJWcHFUMFJqTkU1VE5YZGxWRUUwUW1kdmNrSm5SVVZCV1U4dlRVRkZUa0pEYjAxTFIxRTBDbGxxVW1oT2FsRXdUbGRaZWs5SFRUQlBSMGsxVFZSTk0xbFVaM2RQVkdzelRVUmFhMDlYU1RSTlJHTjZUVlJSTWxwVVVYZEpRVmxMUzNkWlFrSkJSMFFLZG5wQlFrUm5VVk5FUWtKNVdsZGFla3d6VW1oYU0wMTJaR3BCZFUxVE5IbE5RbXRIUTJselIwRlJVVUpuTnpoM1FWRTRSVU4zZDBwT2VsazBUV3BGZWdwUFZHc3pUVU0wUjBOcGMwZEJVVkZDWnpjNGQwRlNRVVZKUVhkbFlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcENtRllVbnBOUW1OSFEybHpSMEZSVVVKbk56aDNRVkpGUlVOUmQwaE5hazE0VGtSUmVVMTZRbTlDWjI5eVFtZEZSVUZaVHk5TlFVVlRRa1p2VFZkSGFEQUtaRWhDZWs5cE9IWmFNbXd3WVVoV2FVeHRUblppVXprd1kyMUdjR0pIT1cxWmJXd3dZM2s1ZVZwdFRUUk9lbWN4VEc1Q05VeDVOVzVoV0ZKdlpGZEpkZ3BrTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFprUjBadVkzazVNazFETkhoTWFrbDNUMEZaUzB0M1dVSkNRVWRFQ25aNlFVSkZkMUZ4UkVOb2EwOUhTVEJaVkZrd1RrUldiVTE2YUdwT1JHaHBUMVJGZWs0eVJUUk5SR3MxVG5wQk1scEViR2xQUkVFelRYcEZNRTV0VlRBS1RVSmpSME5wYzBkQlVWRkNaemM0ZDBGU1VVVkRVWGRJWTIxV2MxcFhSbnBhVkVKalFtZHZja0puUlVWQldVOHZUVUZGVmtKRk5FMVVSMmd3WkVoQ2VncFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVNR050Um5CaVJ6bHRXVzFzTUdONU9YbGFiVTAwVG5wbk1VeHVRalZNTWtacVpFZHNkbUp1VFhaamJsWjFDbU41T0RSTmVsVjRUVVJWTkU1VVFYaE1Na1l3WkVkV2RHTklVbnBNZWtWM1JtZFpTMHQzV1VKQ1FVZEVkbnBCUWtablVVbEVRVnAzWkZkS2MyRlhUWGNLWjFsdlIwTnBjMGRCVVZGQ01XNXJRMEpCU1VWbVFWSTJRVWhuUVdSblJHUlFWRUp4ZUhOalVrMXRUVnBJYUhsYVducGpRMjlyY0dWMVRqUTRjbVlyU0FwcGJrdEJUSGx1ZFdwblFVRkJXVFZaTkVWTEswRkJRVVZCZDBKSVRVVlZRMGxFWVdkbWFuQjNNVUZhV0RNM05IWkdXRWRFVTFwblNqbExjWEpqY1RkVUNtc3ZWWE16WmpkdWJWWlJNVUZwUlVFMFpYTkhRbkpFYUdac1lrbFZkV3BWYlZsRE0yVlZWMFpHUW1kWVNHWkJRa3hwVTBSM1kybFVVWGM0ZDBObldVa0tTMjlhU1hwcU1FVkJkMDFFWVZGQmQxcG5TWGhCVFRablMwazFka3R2Y1dOMlZHdDJPRGRHYjNFelYxaE9XVzFCYUZCcU0zRmhVVFZ2WTFoUldITlhlZ3BJWlU1WFIwSTJiRk5JVkVjelJVNTVZWEJ4V1VKblNYaEJUVXBYT1d4NU0wcFlSV1JKTlhsa1NHWjZLMGRhYjJneGEzbGpNRmhHVlZCd05GWTBhMVpxQ201VldGa3JTM1J2VVZkTFUxQklZVnBOYTFsREwzTjZXR2huUFQwS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19"}]}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "xOkunsyCi+8qp9uh3orJg1EfdTKg3xHHcNOQmaJc8gE="}, "signature": "MEYCIQCeH6E3MpZngWA6RPg8HAl/Z764hTFYycNyF3R+mPTSbAIhAPgMS8qBM8lCEU2XW765myMMz2zuyu9iTF40PH+XZlJQ"}} diff --git a/test/unit/verify/test_policy.py b/test/unit/verify/test_policy.py index fcdabc52..40ab44d5 100644 --- a/test/unit/verify/test_policy.py +++ b/test/unit/verify/test_policy.py @@ -151,3 +151,43 @@ def test_fails_no_san_match(self, signing_bundle): match="Certificate's SANs do not match", ): policy_.verify(bundle.signing_certificate) + + +class TestSingleExtPolicy: + def test_succeeds(self, signing_bundle): + _, bundle = signing_bundle("bundle_v3_github.whl") + + verification_policy_extensions = [ + policy.OIDCIssuer("https://token.actions.githubusercontent.com"), + policy.GitHubWorkflowTrigger("release"), + policy.GitHubWorkflowSHA("d8b4a6445f38c48b9137a8099706d9b8073146e4"), + policy.GitHubWorkflowName("release"), + policy.GitHubWorkflowRepository("trailofbits/rfc8785.py"), + policy.GitHubWorkflowRef("refs/tags/v0.1.2"), + policy.OIDCIssuerV2("https://token.actions.githubusercontent.com"), + policy.OIDCBuildSignerURI( + "https://github.com/trailofbits/rfc8785.py/.github/workflows/release.yml@refs/tags/v0.1.2" + ), + policy.OIDCBuildSignerDigest("d8b4a6445f38c48b9137a8099706d9b8073146e4"), + policy.OIDCRunnerEnvironment("github-hosted"), + policy.OIDCSourceRepositoryURI("https://github.com/trailofbits/rfc8785.py"), + policy.OIDCSourceRepositoryDigest( + "d8b4a6445f38c48b9137a8099706d9b8073146e4" + ), + policy.OIDCSourceRepositoryRef("refs/tags/v0.1.2"), + policy.OIDCSourceRepositoryIdentifier("768213997"), + policy.OIDCSourceRepositoryOwnerURI("https://github.com/trailofbits"), + policy.OIDCSourceRepositoryOwnerIdentifier("2314423"), + policy.OIDCBuildConfigURI( + "https://github.com/trailofbits/rfc8785.py/.github/workflows/release.yml@refs/tags/v0.1.2" + ), + policy.OIDCBuildConfigDigest("d8b4a6445f38c48b9137a8099706d9b8073146e4"), + policy.OIDCBuildTrigger("release"), + policy.OIDCRunInvocationURI( + "https://github.com/trailofbits/rfc8785.py/actions/runs/8351058501/attempts/1" + ), + policy.OIDCSourceRepositoryVisibility("public"), + ] + + policy_ = policy.AllOf(verification_policy_extensions) + policy_.verify(bundle.signing_certificate)