pypi_attestations

The pypi-attestations APIs.

 1"""The `pypi-attestations` APIs."""
 2
 3__version__ = "0.0.20"
 4
 5from ._impl import (
 6    Attestation,
 7    AttestationBundle,
 8    AttestationError,
 9    AttestationType,
10    ConversionError,
11    Distribution,
12    Envelope,
13    GitHubPublisher,
14    GitLabPublisher,
15    Provenance,
16    Publisher,
17    TransparencyLogEntry,
18    VerificationError,
19    VerificationMaterial,
20)
21
22__all__ = [
23    "Attestation",
24    "AttestationBundle",
25    "AttestationError",
26    "AttestationType",
27    "ConversionError",
28    "Distribution",
29    "Envelope",
30    "GitHubPublisher",
31    "GitLabPublisher",
32    "Provenance",
33    "Publisher",
34    "TransparencyLogEntry",
35    "VerificationError",
36    "VerificationMaterial",
37]
class Attestation(pydantic.main.BaseModel):
150class Attestation(BaseModel):
151    """Attestation object as defined in PEP 740."""
152
153    version: Literal[1]
154    """
155    The attestation format's version, which is always 1.
156    """
157
158    verification_material: VerificationMaterial
159    """
160    Cryptographic materials used to verify `message_signature`.
161    """
162
163    envelope: Envelope
164    """
165    The enveloped attestation statement and signature.
166    """
167
168    @property
169    def statement(self) -> dict[str, Any]:
170        """Return the statement within this attestation's envelope.
171
172        The value returned here is a dictionary, in the shape of an
173        in-toto statement.
174        """
175        return json.loads(self.envelope.statement)  # type: ignore[no-any-return]
176
177    @classmethod
178    def sign(cls, signer: Signer, dist: Distribution) -> Attestation:
179        """Create an envelope, with signature, from the given Python distribution.
180
181        On failure, raises `AttestationError`.
182        """
183        try:
184            stmt = (
185                StatementBuilder()
186                .subjects(
187                    [
188                        Subject(
189                            name=dist.name,
190                            digest=DigestSet(root={"sha256": dist.digest}),
191                        )
192                    ]
193                )
194                .predicate_type(AttestationType.PYPI_PUBLISH_V1)
195                .build()
196            )
197        except DsseError as e:
198            raise AttestationError(str(e))
199
200        try:
201            bundle = signer.sign_dsse(stmt)
202        except (ExpiredCertificate, ExpiredIdentity) as e:
203            raise AttestationError(str(e))
204
205        try:
206            return Attestation.from_bundle(bundle)
207        except ConversionError as e:
208            raise AttestationError(str(e))
209
210    @property
211    def certificate_claims(self) -> dict[str, str]:
212        """Return the claims present in the certificate.
213
214        We only return claims present in `_FULCIO_CLAIMS_OIDS`.
215        Values are decoded and returned as strings.
216        """
217        certificate = x509.load_der_x509_certificate(self.verification_material.certificate)
218        claims = {}
219        for extension in certificate.extensions:
220            if extension.oid in _FULCIO_CLAIMS_OIDS:
221                # 1.3.6.1.4.1.57264.1.8 through 1.3.6.1.4.1.57264.1.22 are formatted as DER-encoded
222                # strings; the ASN.1 tag is UTF8String (0x0C) and the tag class is universal.
223                value = extension.value.value
224                claims[extension.oid.dotted_string] = _der_decode_utf8string(value)
225
226        return claims
227
228    def verify(
229        self,
230        identity: VerificationPolicy | Publisher,
231        dist: Distribution,
232        *,
233        staging: bool = False,
234    ) -> tuple[str, Optional[dict[str, Any]]]:
235        """Verify against an existing Python distribution.
236
237        The `identity` can be an object confirming to
238        `sigstore.policy.VerificationPolicy` or a `Publisher`, which will be
239        transformed into an appropriate verification policy.
240
241        By default, Sigstore's production verifier will be used. The
242        `staging` parameter can be toggled to enable the staging verifier
243        instead.
244
245        On failure, raises an appropriate subclass of `AttestationError`.
246        """
247        # NOTE: Can't do `isinstance` with `Publisher` since it's
248        # a `_GenericAlias`; instead we punch through to the inner
249        # `_Publisher` union.
250        # Use of typing.get_args is needed for Python < 3.10
251        if isinstance(identity, get_args(_Publisher)):
252            policy = identity._as_policy()  # noqa: SLF001
253        else:
254            policy = identity
255
256        if staging:
257            verifier = Verifier.staging()
258        else:
259            verifier = Verifier.production()
260
261        bundle = self.to_bundle()
262        try:
263            type_, payload = verifier.verify_dsse(bundle, policy)
264        except sigstore.errors.VerificationError as err:
265            raise VerificationError(str(err)) from err
266
267        if type_ != DsseEnvelope._TYPE:  # noqa: SLF001
268            raise VerificationError(f"expected JSON envelope, got {type_}")
269
270        try:
271            statement = _Statement.model_validate_json(payload)
272        except ValidationError as e:
273            raise VerificationError(f"invalid statement: {str(e)}")
274
275        if len(statement.subjects) != 1:
276            raise VerificationError("too many subjects in statement (must be exactly one)")
277        subject = statement.subjects[0]
278
279        if not subject.name:
280            raise VerificationError("invalid subject: missing name")
281
282        try:
283            # We always ultranormalize when signing, but other signers may not.
284            subject_name = _ultranormalize_dist_filename(subject.name)
285        except ValueError as e:
286            raise VerificationError(f"invalid subject: {str(e)}")
287
288        if subject_name != dist.name:
289            raise VerificationError(
290                f"subject does not match distribution name: {subject_name} != {dist.name}"
291            )
292
293        digest = subject.digest.root.get("sha256")
294        if digest is None or digest != dist.digest:
295            raise VerificationError("subject does not match distribution digest")
296
297        try:
298            AttestationType(statement.predicate_type)
299        except ValueError:
300            raise VerificationError(f"unknown attestation type: {statement.predicate_type}")
301
302        return statement.predicate_type, statement.predicate
303
304    def to_bundle(self) -> Bundle:
305        """Convert a PyPI attestation object as defined in PEP 740 into a Sigstore Bundle."""
306        cert_bytes = self.verification_material.certificate
307        statement = self.envelope.statement
308        signature = self.envelope.signature
309
310        evp = DsseEnvelope(
311            _Envelope(
312                payload=statement,
313                payload_type=DsseEnvelope._TYPE,  # noqa: SLF001
314                signatures=[_Signature(sig=signature)],
315            )
316        )
317
318        tlog_entry = self.verification_material.transparency_entries[0]
319        try:
320            certificate = x509.load_der_x509_certificate(cert_bytes)
321        except ValueError as err:
322            raise ConversionError("invalid X.509 certificate") from err
323
324        try:
325            log_entry = LogEntry._from_dict_rekor(tlog_entry)  # noqa: SLF001
326        except (ValidationError, sigstore.errors.Error) as err:
327            raise ConversionError("invalid transparency log entry") from err
328
329        return Bundle._from_parts(  # noqa: SLF001
330            cert=certificate,
331            content=evp,
332            log_entry=log_entry,
333        )
334
335    @classmethod
336    def from_bundle(cls, sigstore_bundle: Bundle) -> Attestation:
337        """Convert a Sigstore Bundle into a PyPI attestation as defined in PEP 740."""
338        certificate = sigstore_bundle.signing_certificate.public_bytes(
339            encoding=serialization.Encoding.DER
340        )
341
342        envelope = sigstore_bundle._inner.dsse_envelope  # noqa: SLF001
343
344        if len(envelope.signatures) != 1:
345            raise ConversionError(f"expected exactly one signature, got {len(envelope.signatures)}")
346
347        return cls(
348            version=1,
349            verification_material=VerificationMaterial(
350                certificate=base64.b64encode(certificate),
351                transparency_entries=[
352                    sigstore_bundle.log_entry._to_rekor().to_dict()  # noqa: SLF001
353                ],
354            ),
355            envelope=Envelope(
356                statement=base64.b64encode(envelope.payload),
357                signature=base64.b64encode(envelope.signatures[0].sig),
358            ),
359        )

Attestation object as defined in PEP 740.

version: Literal[1]

The attestation format's version, which is always 1.

verification_material: VerificationMaterial

Cryptographic materials used to verify message_signature.

envelope: Envelope

The enveloped attestation statement and signature.

statement: dict[str, typing.Any]
168    @property
169    def statement(self) -> dict[str, Any]:
170        """Return the statement within this attestation's envelope.
171
172        The value returned here is a dictionary, in the shape of an
173        in-toto statement.
174        """
175        return json.loads(self.envelope.statement)  # type: ignore[no-any-return]

Return the statement within this attestation's envelope.

The value returned here is a dictionary, in the shape of an in-toto statement.

@classmethod
def sign( cls, signer: sigstore.sign.Signer, dist: Distribution) -> Attestation:
177    @classmethod
178    def sign(cls, signer: Signer, dist: Distribution) -> Attestation:
179        """Create an envelope, with signature, from the given Python distribution.
180
181        On failure, raises `AttestationError`.
182        """
183        try:
184            stmt = (
185                StatementBuilder()
186                .subjects(
187                    [
188                        Subject(
189                            name=dist.name,
190                            digest=DigestSet(root={"sha256": dist.digest}),
191                        )
192                    ]
193                )
194                .predicate_type(AttestationType.PYPI_PUBLISH_V1)
195                .build()
196            )
197        except DsseError as e:
198            raise AttestationError(str(e))
199
200        try:
201            bundle = signer.sign_dsse(stmt)
202        except (ExpiredCertificate, ExpiredIdentity) as e:
203            raise AttestationError(str(e))
204
205        try:
206            return Attestation.from_bundle(bundle)
207        except ConversionError as e:
208            raise AttestationError(str(e))

Create an envelope, with signature, from the given Python distribution.

On failure, raises AttestationError.

certificate_claims: dict[str, str]
210    @property
211    def certificate_claims(self) -> dict[str, str]:
212        """Return the claims present in the certificate.
213
214        We only return claims present in `_FULCIO_CLAIMS_OIDS`.
215        Values are decoded and returned as strings.
216        """
217        certificate = x509.load_der_x509_certificate(self.verification_material.certificate)
218        claims = {}
219        for extension in certificate.extensions:
220            if extension.oid in _FULCIO_CLAIMS_OIDS:
221                # 1.3.6.1.4.1.57264.1.8 through 1.3.6.1.4.1.57264.1.22 are formatted as DER-encoded
222                # strings; the ASN.1 tag is UTF8String (0x0C) and the tag class is universal.
223                value = extension.value.value
224                claims[extension.oid.dotted_string] = _der_decode_utf8string(value)
225
226        return claims

Return the claims present in the certificate.

We only return claims present in _FULCIO_CLAIMS_OIDS. Values are decoded and returned as strings.

def verify( self, identity: Union[sigstore.verify.policy.VerificationPolicy, Annotated[Union[GitHubPublisher, GitLabPublisher], FieldInfo(annotation=NoneType, required=True, discriminator='kind')]], dist: Distribution, *, staging: bool = False) -> tuple[str, typing.Optional[dict[str, typing.Any]]]:
228    def verify(
229        self,
230        identity: VerificationPolicy | Publisher,
231        dist: Distribution,
232        *,
233        staging: bool = False,
234    ) -> tuple[str, Optional[dict[str, Any]]]:
235        """Verify against an existing Python distribution.
236
237        The `identity` can be an object confirming to
238        `sigstore.policy.VerificationPolicy` or a `Publisher`, which will be
239        transformed into an appropriate verification policy.
240
241        By default, Sigstore's production verifier will be used. The
242        `staging` parameter can be toggled to enable the staging verifier
243        instead.
244
245        On failure, raises an appropriate subclass of `AttestationError`.
246        """
247        # NOTE: Can't do `isinstance` with `Publisher` since it's
248        # a `_GenericAlias`; instead we punch through to the inner
249        # `_Publisher` union.
250        # Use of typing.get_args is needed for Python < 3.10
251        if isinstance(identity, get_args(_Publisher)):
252            policy = identity._as_policy()  # noqa: SLF001
253        else:
254            policy = identity
255
256        if staging:
257            verifier = Verifier.staging()
258        else:
259            verifier = Verifier.production()
260
261        bundle = self.to_bundle()
262        try:
263            type_, payload = verifier.verify_dsse(bundle, policy)
264        except sigstore.errors.VerificationError as err:
265            raise VerificationError(str(err)) from err
266
267        if type_ != DsseEnvelope._TYPE:  # noqa: SLF001
268            raise VerificationError(f"expected JSON envelope, got {type_}")
269
270        try:
271            statement = _Statement.model_validate_json(payload)
272        except ValidationError as e:
273            raise VerificationError(f"invalid statement: {str(e)}")
274
275        if len(statement.subjects) != 1:
276            raise VerificationError("too many subjects in statement (must be exactly one)")
277        subject = statement.subjects[0]
278
279        if not subject.name:
280            raise VerificationError("invalid subject: missing name")
281
282        try:
283            # We always ultranormalize when signing, but other signers may not.
284            subject_name = _ultranormalize_dist_filename(subject.name)
285        except ValueError as e:
286            raise VerificationError(f"invalid subject: {str(e)}")
287
288        if subject_name != dist.name:
289            raise VerificationError(
290                f"subject does not match distribution name: {subject_name} != {dist.name}"
291            )
292
293        digest = subject.digest.root.get("sha256")
294        if digest is None or digest != dist.digest:
295            raise VerificationError("subject does not match distribution digest")
296
297        try:
298            AttestationType(statement.predicate_type)
299        except ValueError:
300            raise VerificationError(f"unknown attestation type: {statement.predicate_type}")
301
302        return statement.predicate_type, statement.predicate

Verify against an existing Python distribution.

The identity can be an object confirming to sigstore.policy.VerificationPolicy or a Publisher, which will be transformed into an appropriate verification policy.

By default, Sigstore's production verifier will be used. The staging parameter can be toggled to enable the staging verifier instead.

On failure, raises an appropriate subclass of AttestationError.

def to_bundle(self) -> sigstore.models.Bundle:
304    def to_bundle(self) -> Bundle:
305        """Convert a PyPI attestation object as defined in PEP 740 into a Sigstore Bundle."""
306        cert_bytes = self.verification_material.certificate
307        statement = self.envelope.statement
308        signature = self.envelope.signature
309
310        evp = DsseEnvelope(
311            _Envelope(
312                payload=statement,
313                payload_type=DsseEnvelope._TYPE,  # noqa: SLF001
314                signatures=[_Signature(sig=signature)],
315            )
316        )
317
318        tlog_entry = self.verification_material.transparency_entries[0]
319        try:
320            certificate = x509.load_der_x509_certificate(cert_bytes)
321        except ValueError as err:
322            raise ConversionError("invalid X.509 certificate") from err
323
324        try:
325            log_entry = LogEntry._from_dict_rekor(tlog_entry)  # noqa: SLF001
326        except (ValidationError, sigstore.errors.Error) as err:
327            raise ConversionError("invalid transparency log entry") from err
328
329        return Bundle._from_parts(  # noqa: SLF001
330            cert=certificate,
331            content=evp,
332            log_entry=log_entry,
333        )

Convert a PyPI attestation object as defined in PEP 740 into a Sigstore Bundle.

@classmethod
def from_bundle( cls, sigstore_bundle: sigstore.models.Bundle) -> Attestation:
335    @classmethod
336    def from_bundle(cls, sigstore_bundle: Bundle) -> Attestation:
337        """Convert a Sigstore Bundle into a PyPI attestation as defined in PEP 740."""
338        certificate = sigstore_bundle.signing_certificate.public_bytes(
339            encoding=serialization.Encoding.DER
340        )
341
342        envelope = sigstore_bundle._inner.dsse_envelope  # noqa: SLF001
343
344        if len(envelope.signatures) != 1:
345            raise ConversionError(f"expected exactly one signature, got {len(envelope.signatures)}")
346
347        return cls(
348            version=1,
349            verification_material=VerificationMaterial(
350                certificate=base64.b64encode(certificate),
351                transparency_entries=[
352                    sigstore_bundle.log_entry._to_rekor().to_dict()  # noqa: SLF001
353                ],
354            ),
355            envelope=Envelope(
356                statement=base64.b64encode(envelope.payload),
357                signature=base64.b64encode(envelope.signatures[0].sig),
358            ),
359        )

Convert a Sigstore Bundle into a PyPI attestation as defined in PEP 740.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class AttestationBundle(pydantic.main.BaseModel):
639class AttestationBundle(BaseModel):
640    """AttestationBundle object as defined in PEP 740."""
641
642    publisher: Publisher
643    """
644    The publisher associated with this set of attestations.
645    """
646
647    attestations: list[Attestation]
648    """
649    The list of attestations included in this bundle.
650    """

AttestationBundle object as defined in PEP 740.

publisher: Annotated[Union[GitHubPublisher, GitLabPublisher], FieldInfo(annotation=NoneType, required=True, discriminator='kind')]

The publisher associated with this set of attestations.

attestations: list[Attestation]

The list of attestations included in this bundle.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class AttestationError(builtins.ValueError):
116class AttestationError(ValueError):
117    """Base error for all APIs."""

Base error for all APIs.

class AttestationType(builtins.str, enum.Enum):
109class AttestationType(str, Enum):
110    """Attestation types known to PyPI."""
111
112    SLSA_PROVENANCE_V1 = "https://slsa.dev/provenance/v1"
113    PYPI_PUBLISH_V1 = "https://docs.pypi.org/attestations/publish/v1"

Attestation types known to PyPI.

SLSA_PROVENANCE_V1 = <AttestationType.SLSA_PROVENANCE_V1: 'https://slsa.dev/provenance/v1'>
PYPI_PUBLISH_V1 = <AttestationType.PYPI_PUBLISH_V1: 'https://docs.pypi.org/attestations/publish/v1'>
class ConversionError(pypi_attestations.AttestationError):
120class ConversionError(AttestationError):
121    """The base error for all errors during conversion."""

The base error for all errors during conversion.

class Distribution(pydantic.main.BaseModel):
 81class Distribution(BaseModel):
 82    """Represents a Python package distribution.
 83
 84    A distribution is identified by its (sdist or wheel) filename, which
 85    provides the package name and version (at a minimum) plus a SHA-256
 86    digest, which uniquely identifies its contents.
 87    """
 88
 89    name: str
 90    digest: str
 91
 92    @field_validator("name")
 93    @classmethod
 94    def _validate_name(cls, v: str) -> str:
 95        return _ultranormalize_dist_filename(v)
 96
 97    @classmethod
 98    def from_file(cls, dist: Path) -> Distribution:
 99        """Construct a `Distribution` from the given path."""
100        name = dist.name
101        with dist.open(mode="rb", buffering=0) as io:
102            # Replace this with `hashlib.file_digest()` once
103            # our minimum supported Python is >=3.11
104            digest = _sha256_streaming(io).hex()
105
106        return cls(name=name, digest=digest)

Represents a Python package distribution.

A distribution is identified by its (sdist or wheel) filename, which provides the package name and version (at a minimum) plus a SHA-256 digest, which uniquely identifies its contents.

name: str
digest: str
@classmethod
def from_file(cls, dist: pathlib.Path) -> Distribution:
 97    @classmethod
 98    def from_file(cls, dist: Path) -> Distribution:
 99        """Construct a `Distribution` from the given path."""
100        name = dist.name
101        with dist.open(mode="rb", buffering=0) as io:
102            # Replace this with `hashlib.file_digest()` once
103            # our minimum supported Python is >=3.11
104            digest = _sha256_streaming(io).hex()
105
106        return cls(name=name, digest=digest)

Construct a Distribution from the given path.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Envelope(pydantic.main.BaseModel):
362class Envelope(BaseModel):
363    """The attestation envelope, containing the attested-for payload and its signature."""
364
365    statement: Base64Bytes
366    """
367    The attestation statement.
368
369    This is represented as opaque bytes on the wire (encoded as base64),
370    but it MUST be an JSON in-toto v1 Statement.
371    """
372
373    signature: Base64Bytes
374    """
375    A signature for the above statement, encoded as base64.
376    """

The attestation envelope, containing the attested-for payload and its signature.

statement: Annotated[bytes, EncodedBytes(encoder=<class 'pydantic.types.Base64Encoder'>)]

The attestation statement.

This is represented as opaque bytes on the wire (encoded as base64), but it MUST be an JSON in-toto v1 Statement.

signature: Annotated[bytes, EncodedBytes(encoder=<class 'pydantic.types.Base64Encoder'>)]

A signature for the above statement, encoded as base64.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class GitHubPublisher(pypi_attestations._impl._PublisherBase):
516class GitHubPublisher(_PublisherBase):
517    """A GitHub-based Trusted Publisher."""
518
519    kind: Literal["GitHub"] = "GitHub"
520
521    repository: str
522    """
523    The fully qualified publishing repository slug, e.g. `foo/bar` for
524    repository `bar` owned by `foo`.
525    """
526
527    workflow: str
528    """
529    The filename of the GitHub Actions workflow that performed the publishing
530    action.
531    """
532
533    environment: Optional[str] = None
534    """
535    The optional name GitHub Actions environment that the publishing
536    action was performed from.
537    """
538
539    def _as_policy(self) -> VerificationPolicy:
540        return _GitHubTrustedPublisherPolicy(self.repository, self.workflow)

A GitHub-based Trusted Publisher.

kind: Literal['GitHub']
repository: str

The fully qualified publishing repository slug, e.g. foo/bar for repository bar owned by foo.

workflow: str

The filename of the GitHub Actions workflow that performed the publishing action.

environment: Optional[str]

The optional name GitHub Actions environment that the publishing action was performed from.

model_config = {'alias_generator': <function to_snake>}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class GitLabPublisher(pypi_attestations._impl._PublisherBase):
608class GitLabPublisher(_PublisherBase):
609    """A GitLab-based Trusted Publisher."""
610
611    kind: Literal["GitLab"] = "GitLab"
612
613    repository: str
614    """
615    The fully qualified publishing repository slug, e.g. `foo/bar` for
616    repository `bar` owned by `foo` or `foo/baz/bar` for repository
617    `bar` owned by group `foo` and subgroup `baz`.
618    """
619
620    workflow_filepath: str
621    """
622    The path for the CI/CD configuration file. This is usually ".gitlab-ci.yml",
623    but can be customized.
624    """
625
626    environment: Optional[str] = None
627    """
628    The optional environment that the publishing action was performed from.
629    """
630
631    def _as_policy(self) -> VerificationPolicy:
632        return _GitLabTrustedPublisherPolicy(self.repository, self.workflow_filepath)

A GitLab-based Trusted Publisher.

kind: Literal['GitLab']
repository: str

The fully qualified publishing repository slug, e.g. foo/bar for repository bar owned by foo or foo/baz/bar for repository bar owned by group foo and subgroup baz.

workflow_filepath: str

The path for the CI/CD configuration file. This is usually ".gitlab-ci.yml", but can be customized.

environment: Optional[str]

The optional environment that the publishing action was performed from.

model_config = {'alias_generator': <function to_snake>}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Provenance(pydantic.main.BaseModel):
653class Provenance(BaseModel):
654    """Provenance object as defined in PEP 740."""
655
656    version: Literal[1] = 1
657    """
658    The provenance object's version, which is always 1.
659    """
660
661    attestation_bundles: list[AttestationBundle]
662    """
663    One or more attestation "bundles".
664    """

Provenance object as defined in PEP 740.

version: Literal[1]

The provenance object's version, which is always 1.

attestation_bundles: list[AttestationBundle]

One or more attestation "bundles".

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Publisher = typing.Annotated[typing.Union[GitHubPublisher, GitLabPublisher], FieldInfo(annotation=NoneType, required=True, discriminator='kind')]
TransparencyLogEntry = TransparencyLogEntry
class VerificationError(pypi_attestations.AttestationError):
124class VerificationError(AttestationError):
125    """The PyPI Attestation failed verification."""
126
127    def __init__(self: VerificationError, msg: str) -> None:
128        """Initialize an `VerificationError`."""
129        super().__init__(f"Verification failed: {msg}")

The PyPI Attestation failed verification.

VerificationError(msg: str)
127    def __init__(self: VerificationError, msg: str) -> None:
128        """Initialize an `VerificationError`."""
129        super().__init__(f"Verification failed: {msg}")

Initialize an VerificationError.

class VerificationMaterial(pydantic.main.BaseModel):
135class VerificationMaterial(BaseModel):
136    """Cryptographic materials used to verify attestation objects."""
137
138    certificate: Base64Bytes
139    """
140    The signing certificate, as `base64(DER(cert))`.
141    """
142
143    transparency_entries: Annotated[list[TransparencyLogEntry], MinLen(1)]
144    """
145    One or more transparency log entries for this attestation's signature
146    and certificate.
147    """

Cryptographic materials used to verify attestation objects.

certificate: Annotated[bytes, EncodedBytes(encoder=<class 'pydantic.types.Base64Encoder'>)]

The signing certificate, as base64(DER(cert)).

transparency_entries: Annotated[list[TransparencyLogEntry], MinLen(min_length=1)]

One or more transparency log entries for this attestation's signature and certificate.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].