Repository navigation
[PULP-2199] Add SLSA attestation verification support #1327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| -----BEGIN PRIVATE KEY----- | ||
| MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDLYCPSdloA94Te | ||
| +68CLqtHJ3qTKttyWyM1uHkcb+AEAExtQGLoKtysmE2QQz8xwpiBKsByAlPTAHZZ | ||
| pYIGzof45xEwiRzBqKxE4um1Uhtq+WCth96UeUkjz2G0xMxkFycjHDdpQQ92Hlg+ | ||
| mvU8VOrs4Xi7PvG5E+lTY7zez89QQ2ZY3OcbFSHVkC1b0COjYqJ7m5TmVWaRyNbW | ||
| Nn+00/uZ+hFzcsxAuKa9B9s4ngWELLA1TnWp5yi4q2Pkold4prI3PwF7IfGWtrfo | ||
| TgyzCKji12QLLjbd5KiKecGqx9zaV8xs7GnCif47JmOFAEDW5dtUstepH8ysy8wC | ||
| IXoZYvOSOsOv2mVz99JikyaWeV0rjNk7p9UPrhiaGKOfzfn+g6bzCJYBlo425pmJ | ||
| S/d1EoBoqgeCGniyLkFByQ9zDmzr1NlCkNKtNII1qNoRSMyqJY0qKPAd2bNMHUfT | ||
| FB3Eb/AvcOjb4fa5kkxl4gMQwasNaFmDgusqt1JjHK4XcL8UIcvzGPj8LsDrx5iT | ||
| HCFSF1gJvAXiLG0o6KFgrHfTBw2V3mxaKZNGr2NA5IMTs2UeGud590VmSvsKhr6R | ||
| tekHHGLCrZT9kPxbicBq6OZOhSb0usSpclSvwMzMSIOFNzACXq33nPYqlY0r1vGN | ||
| 5Gi2P9W2CxAYbnOGgqpzHbt4YoLVrQIDAQABAoICAA2SR3xZPtL8XCGFKhM7NLLK | ||
| 2k3NI60TPTDt3nxx+sD0RCVbkT4XniI7skauNh6xSFFWSQFSpnADgoz46R8LKTJd | ||
| jl1uyOcjb7DTyO9l9e5tixtetbuysZlyJ/2oJFDe5VMHzwAhwfu1NVj4KOVIcA+J | ||
| UZqCg3RA9TuwrCnc3uNm8VgnZZn+ZFjxW2raY4ZuTpQbuGlRHvHGNTqWPcS+C0wl | ||
| zoRQZNDs1t5GZ+/0m5RLvHZ82zKQpRGts5IjmIgJ7QVCxGvdwqwMBWRl0PMhi3jt | ||
| EVVYVXuj2/C3BKAg4NwGf93E6PR9FjoG6x00/HQFYrLZkbVM1JuzUynRPL9KRcvz | ||
| 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 | ||
| Y3acysRQ2KI9Ero+Ga3hDfaohueVFg== | ||
| -----END PRIVATE KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy2Aj0nZaAPeE3vuvAi6r | ||
| Ryd6kyrbclsjNbh5HG/gBABMbUBi6CrcrJhNkEM/McKYgSrAcgJT0wB2WaWCBs6H | ||
| +OcRMIkcwaisROLptVIbavlgrYfelHlJI89htMTMZBcnIxw3aUEPdh5YPpr1PFTq | ||
| 7OF4uz7xuRPpU2O83s/PUENmWNznGxUh1ZAtW9Ajo2Kie5uU5lVmkcjW1jZ/tNP7 | ||
| mfoRc3LMQLimvQfbOJ4FhCywNU51qecouKtj5KJXeKayNz8BeyHxlra36E4Mswio | ||
| 4tdkCy423eSoinnBqsfc2lfMbOxpwon+OyZjhQBA1uXbVLLXqR/MrMvMAiF6GWLz | ||
| kjrDr9plc/fSYpMmlnldK4zZO6fVD64Ymhijn835/oOm8wiWAZaONuaZiUv3dRKA | ||
| aKoHghp4si5BQckPcw5s69TZQpDSrTSCNajaEUjMqiWNKijwHdmzTB1H0xQdxG/w | ||
| L3Do2+H2uZJMZeIDEMGrDWhZg4LrKrdSYxyuF3C/FCHL8xj4/C7A68eYkxwhUhdY | ||
| CbwF4ixtKOihYKx30wcNld5sWimTRq9jQOSDE7NlHhrnefdFZkr7Coa+kbXpBxxi | ||
| wq2U/ZD8W4nAaujmToUm9LrEqXJUr8DMzEiDhTcwAl6t95z2KpWNK9bxjeRotj/V | ||
| tgsQGG5zhoKqcx27eGKC1a0CAwEAAQ== | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Added custom (non-PEP 740) support for verifying attestations signed with a server-configured public key via `ATTESTATION_VERIFICATION_KEY`, with optional embedded key in `verification_material`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,31 @@ | ||
| import json | ||
| import logging | ||
| from typing import Annotated, Literal, Union, get_args | ||
| from urllib.parse import urlparse | ||
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
| from cryptography.exceptions import InvalidSignature | ||
| from cryptography.hazmat.primitives import hashes, serialization | ||
| from cryptography.hazmat.primitives.asymmetric import padding as crypto_padding | ||
| from cryptography.x509 import load_der_x509_certificate | ||
| from django.conf import settings | ||
| from pydantic import Base64Bytes, BaseModel, ConfigDict, Field, model_validator | ||
| from pydantic.alias_generators import to_snake | ||
| from pypi_attestations import Attestation as _UpstreamAttestation | ||
| from pypi_attestations import ( | ||
| Attestation, | ||
| Distribution, | ||
| Envelope, # noqa - needed in module namespace for Pydantic model rebuild | ||
| Publisher, | ||
| VerificationError, | ||
| ) | ||
| from pypi_attestations import VerificationMaterial as _UpstreamVerificationMaterial | ||
| from sigstore.dsse import Envelope as DSSEEnvelope | ||
| from sigstore.dsse import _pae | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
| _verification_key_cache = {} | ||
|
|
||
| SLSA_PROVENANCE_V02 = "https://slsa.dev/provenance/v0.2" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you know why we are using v0.2? It might be something AI choose for me when I added this to services, but I've forgotten. The PyPI docs say the supported SLSA predicate-type is |
||
|
|
||
|
|
||
| class _PermissivePolicy: | ||
|
|
@@ -39,6 +58,44 @@ def _as_policy(self): | |
| ExtendedPublisher = Annotated[_ExtendedPublisherUnion, Field(union_mode="left_to_right")] | ||
|
|
||
|
|
||
| class VerificationMaterial(_UpstreamVerificationMaterial): | ||
| """Extended verification material that supports optional certificate and public key. | ||
|
|
||
| PEP 740 requires a certificate, but this extension allows attestations signed | ||
| with a custom key where the certificate is absent. The public_key field is | ||
| accepted as an extra field and only present in the output when provided. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(extra="allow") | ||
|
|
||
| certificate: Base64Bytes | None = None | ||
|
|
||
| @model_validator(mode="after") | ||
| def _validate_fields(self): | ||
|
Comment on lines
+73
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just add See: Interesting that the example in the sigstore docs is wrong compared to the proto definition. |
||
| unexpected = set(self.model_extra or {}) - {"public_key"} | ||
| if unexpected: | ||
| raise ValueError(f"unexpected fields in verification_material: {unexpected}") | ||
| public_key = getattr(self, "public_key", None) | ||
| if self.certificate is not None and public_key is not None: | ||
| raise ValueError("verification_material cannot contain both certificate and public_key") | ||
| return self | ||
|
|
||
|
|
||
| class Attestation(_UpstreamAttestation): | ||
| """ | ||
| Attestation object as defined in PEP 740. | ||
|
|
||
| Inherits from the upstream pypi_attestations.Attestation to keep Sigstore | ||
| verification methods (to_bundle, verify), but makes verification_material | ||
| optional to support attestations signed with a custom key instead of Sigstore. | ||
| """ | ||
|
|
||
| verification_material: VerificationMaterial | None = None | ||
| """ | ||
| Cryptographic materials used to verify `message_signature`. | ||
| """ | ||
|
|
||
|
|
||
| class AttestationBundle(BaseModel): | ||
| """ | ||
| AttestationBundle object as defined in PEP740. | ||
|
|
@@ -58,14 +115,148 @@ class Provenance(BaseModel): | |
| attestation_bundles: list[AttestationBundle] | ||
|
|
||
|
|
||
| def _load_verification_key(): | ||
| """Load the configured attestation verification public key, with caching.""" | ||
| key_path = getattr(settings, "ATTESTATION_VERIFICATION_KEY", None) | ||
| if not key_path: | ||
| return None | ||
| if key_path not in _verification_key_cache: | ||
| with open(key_path, "rb") as f: | ||
| _verification_key_cache[key_path] = serialization.load_pem_public_key(f.read()) | ||
| return _verification_key_cache[key_path] | ||
|
|
||
|
|
||
| def _has_valid_certificate(attestation): | ||
| """Check whether the attestation contains a valid X.509 certificate.""" | ||
| try: | ||
| vm = attestation.verification_material | ||
| if vm is None: | ||
| return False | ||
| cert_bytes = vm.certificate | ||
| if cert_bytes is None: | ||
| return False | ||
| load_der_x509_certificate(cert_bytes) | ||
| return True | ||
| except (ValueError, Exception): | ||
| return False | ||
|
|
||
|
|
||
| def _verify_statement_subject(attestation, dist): | ||
| """Validate that the in-toto statement subject matches the distribution. | ||
|
|
||
| Returns the parsed statement dict for downstream use. | ||
| """ | ||
| try: | ||
| stmt = json.loads(attestation.envelope.statement) | ||
| except (json.JSONDecodeError, UnicodeDecodeError) as e: | ||
| raise VerificationError(f"invalid statement: {e}") | ||
|
|
||
| subjects = stmt.get("subject", []) | ||
| if len(subjects) != 1: | ||
| raise VerificationError("expected exactly one subject in statement") | ||
|
|
||
| subject = subjects[0] | ||
| name = subject.get("name", "") | ||
| if name != dist.name: | ||
| raise VerificationError(f"subject does not match distribution name: {name} != {dist.name}") | ||
|
|
||
| digest = subject.get("digest", {}).get("sha256") | ||
| if digest != dist.digest: | ||
| raise VerificationError("subject does not match distribution digest") | ||
|
|
||
| return stmt | ||
|
|
||
|
|
||
| def _enrich_publisher_from_statement(stmt, publisher): | ||
| """Populate publisher fields from an SLSA v0.2 provenance statement.""" | ||
| if stmt.get("predicateType") != SLSA_PROVENANCE_V02: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow up on v02, there are 3 newer versions: 1.0, 1.1, 1.2 https://slsa.dev/spec/v1.2/verification_summary#change-history. Hmm, what to do... |
||
| return | ||
|
|
||
| predicate = stmt.get("predicate", {}) | ||
| builder_id = predicate.get("builder", {}).get("id") | ||
| build_type = predicate.get("buildType") | ||
|
|
||
| if builder_id: | ||
| publisher.builder_id = builder_id | ||
| try: | ||
| hostname = urlparse(builder_id).hostname | ||
| if hostname: | ||
| publisher.kind = hostname | ||
| except Exception: | ||
| pass | ||
|
|
||
| if build_type: | ||
| publisher.build_type = build_type | ||
|
|
||
|
|
||
| def _verify_signature(attestation, public_key): | ||
| """Verify the attestation's RSA signature over the DSSE PAE bytes.""" | ||
| statement_bytes = attestation.envelope.statement | ||
| signature_bytes = attestation.envelope.signature | ||
| pae = _pae(DSSEEnvelope._TYPE, statement_bytes) | ||
| try: | ||
| public_key.verify( | ||
| signature_bytes, | ||
| pae, | ||
| crypto_padding.PKCS1v15(), | ||
| hashes.SHA256(), | ||
| ) | ||
| except InvalidSignature as e: | ||
| raise VerificationError(f"signature verification failed: {e}") | ||
|
|
||
|
|
||
| def _verify_embedded_key(attestation, server_key): | ||
| """Verify that the embedded public key matches the server-configured key.""" | ||
| vm = attestation.verification_material | ||
| public_key = getattr(vm, "public_key", None) if vm else None | ||
| if public_key is None: | ||
| return | ||
| try: | ||
| embedded_key = serialization.load_pem_public_key(public_key.encode()) | ||
| except (ValueError, Exception) as e: | ||
|
Comment on lines
+211
to
+216
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding my earlier comment, I believe the data structure is currently wrong. The sigstore support is of the form There is no guarantee that the whole key will be inside the field. The hint is left vague and up to the signer and consumer to agree upon what they will be using before hand. This field, according to sigstore, seems mostly to be a sanity check. |
||
| raise VerificationError(f"invalid embedded public key: {e}") | ||
| server_key_bytes = server_key.public_bytes( | ||
| serialization.Encoding.PEM, | ||
| serialization.PublicFormat.SubjectPublicKeyInfo, | ||
| ) | ||
| embedded_key_bytes = embedded_key.public_bytes( | ||
| serialization.Encoding.PEM, | ||
| serialization.PublicFormat.SubjectPublicKeyInfo, | ||
| ) | ||
| if server_key_bytes != embedded_key_bytes: | ||
| raise VerificationError( | ||
| "embedded public key does not match server-configured ATTESTATION_VERIFICATION_KEY" | ||
| ) | ||
|
|
||
|
|
||
| def verify_provenance(filename, sha256, provenance, offline=True): | ||
| """Verify the provenance object is valid for the package.""" | ||
| """Verify the provenance object is valid for the package. | ||
|
|
||
| Each attestation is routed to one of two coexisting verification paths: | ||
| 1. Attestations with a valid Sigstore certificate follow the standard PEP 740 path. | ||
| 2. Attestations without a certificate are verified against the server-configured | ||
| ATTESTATION_VERIFICATION_KEY using RSA PKCS1v15 signatures, with support for SLSA | ||
| v0.2 publisher enrichment and optional embedded public key in verification_material. | ||
| """ | ||
| dist = Distribution(name=filename, digest=sha256) | ||
| verification_key = _load_verification_key() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed with the rabbit, harden this loading to prevent bad configs taking down our verification. |
||
| for bundle in provenance.attestation_bundles: | ||
|
Comment on lines
232
to
243
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '115,140p' pulp_python/app/provenance.py
sed -n '232,265p' pulp_python/app/provenance.py
sed -n '93,117p' docs/user/guides/attestation.mdRepository: pulp/pulp_python Length of output: 3961 🏁 Script executed: printf '%s\n' '--- documentation ---'
nl -ba docs/user/guides/attestation.md | sed -n '93,130p'
printf '%s\n' '--- provenance and upload callers ---'
nl -ba pulp_python/app/pypi/serializers.py | sed -n '120,145p;400,430p;660,690p'
nl -ba pulp_python/app/tasks/upload.py | sed -n '125,150p'
printf '%s\n' '--- setting references and entrypoints ---'
rg -n -F --glob '*.py' -- 'verify_provenance(' pulp_python
rg -n -F --glob '*.py' -- 'ATTESTATION_VERIFICATION_KEY' pulp_pythonRepository: pulp/pulp_python Length of output: 6970 Load the verification key only for certificate-less attestations.
Lazy-loading on the custom-key branch is required separately from translating key-load exceptions. Exception translation alone would still make certificate-based verification depend on the optional RSA key. Suggested fix """
dist = Distribution(name=filename, digest=sha256)
- verification_key = _load_verification_key()
for bundle in provenance.attestation_bundles:
publisher = bundle.publisher
policy = publisher._as_policy()
for attestation in bundle.attestations:
if _has_valid_certificate(attestation):
sig_bundle = attestation.to_bundle()
checkpoint = sig_bundle.log_entry._inner.inclusion_proof.checkpoint
staging = "sigstage.dev" in checkpoint.envelope
attestation.verify(policy, dist, staging=staging, offline=offline)
- elif verification_key:
- stmt = _verify_statement_subject(attestation, dist)
- _enrich_publisher_from_statement(stmt, publisher)
- _verify_signature(attestation, verification_key)
- _verify_embedded_key(attestation, verification_key)
else:
- raise VerificationError(
- "Attestation has no Sigstore certificate and no custom "
- "verification key configured via ATTESTATION_VERIFICATION_KEY "
- "(embedded key in verification_material is optional)"
- )
+ verification_key = _load_verification_key()
+ if verification_key:
+ stmt = _verify_statement_subject(attestation, dist)
+ _enrich_publisher_from_statement(stmt, publisher)
+ _verify_signature(attestation, verification_key)
+ _verify_embedded_key(attestation, verification_key)
+ else:
+ raise VerificationError(
+ "Attestation has no Sigstore certificate and no custom "
+ "verification key configured via ATTESTATION_VERIFICATION_KEY "
+ "(embedded key in verification_material is optional)"
+ )🤖 Prompt for AI Agents |
||
| publisher = bundle.publisher | ||
| policy = publisher._as_policy() | ||
| for attestation in bundle.attestations: | ||
| sig_bundle = attestation.to_bundle() | ||
| checkpoint = sig_bundle.log_entry._inner.inclusion_proof.checkpoint | ||
| staging = "sigstage.dev" in checkpoint.envelope | ||
| attestation.verify(policy, dist, staging=staging, offline=offline) | ||
| if _has_valid_certificate(attestation): | ||
| sig_bundle = attestation.to_bundle() | ||
| checkpoint = sig_bundle.log_entry._inner.inclusion_proof.checkpoint | ||
| staging = "sigstage.dev" in checkpoint.envelope | ||
| attestation.verify(policy, dist, staging=staging, offline=offline) | ||
| elif verification_key: | ||
| stmt = _verify_statement_subject(attestation, dist) | ||
| _enrich_publisher_from_statement(stmt, publisher) | ||
| _verify_signature(attestation, verification_key) | ||
| _verify_embedded_key(attestation, verification_key) | ||
| else: | ||
| raise VerificationError( | ||
| "Attestation has no Sigstore certificate and no custom " | ||
| "verification key configured via ATTESTATION_VERIFICATION_KEY " | ||
| "(embedded key in verification_material is optional)" | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.