This PEP proposes a collection of changes related to the upload and distribution of digitally signed attestations and metadata used to verify them on a Python package repository, such as PyPI.
These changes have two subcomponents:
This PEP does not make a policy recommendation around mandatory digital attestations on release uploads or their subsequent verification by installing clients like pip.
Desire for digital signatures on Python packages has been repeatedly expressed by both package maintainers and downstream users:
This proposal seeks to accommodate each of the above use cases.
Additionally, this proposal identifies the following motivations:
Digital attestations impose additional sophistication requirements: the attacker must be sufficiently sophisticated to access private signing material (or signing identities).
This PEP proposes a generic attestation format, containing an attestation statement for signature generation, with the expectation that index providers adopt the format with a suitable source of identity for signature verification, such as Trusted Publishing.
This PEP identifies the following design considerations when evaluating both its own proposed changes and previous work in the same or adjacent areas of Python packaging:
This both simplifies some compatibility concerns (by avoiding the need to modify the distribution formats themselves) and also simplifies the behavior of potential installing clients (by allowing them to retrieve each attestation before its corresponding package without needing to do streaming decompression).
This both increases the overall quality of attestations uploaded to the index (preventing, for example, users from accidentally uploading incorrect or invalid attestations) and also enables UI and UX refinements on the index itself (such as a “provenance” view for each uploaded package).
For example, to prevent domain separation between a distribution’s name and its contents, this PEP uses ‘Statements’ from the in-toto project to bind the distribution’s contents (via SHA-256 digest) to its filename.
PyPI and other indices have historically supported PGP signatures on uploaded distributions. These could be supplied during upload, and could be retrieved by installing clients via the data-gpg-sig attribute in the PEP 503 API, the gpg-sig key on the PEP 691 API, or via an adjacent .asc-suffixed URL.
PGP signature uploads have been disabled on PyPI since May 2023, after an investigation determined that the majority of signatures (which, themselves, constituted a tiny percentage of overall uploads) could not be associated with a public key or otherwise meaningfully verified.
In their previously supported form on PyPI, PGP signatures satisfied considerations (1) and (3) above but not (2) (owing to the need for external keyservers and key distribution) or (4) (due to PGP signatures typically being constructed over just an input file, without any associated signed metadata).
PEP 427 (and its living PyPA counterpart) specify the wheel format.
This format includes accommodations for digital signatures embedded directly into the wheel, in either JWS or S/MIME format. These signatures are specified over a PEP 376 RECORD, which is modified to include a cryptographic digest for each recorded file in the wheel.
While wheel signatures are fully specified, they do not appear to be broadly used; the official wheel tooling deprecated signature generation and verification support in 0.32.0, which was released in 2018.
Additionally, wheel signatures do not satisfy any of the above considerations (due to the “attached” nature of the signatures, non-verifiability on the index itself, and support for wheels only).
The current upload API is not standardized. However, we propose the following changes to it:
The following changes are made to the simple repository API:
The location of the provenance file is signaled by the index via the data-provenance attribute.
The following table provides examples of release file URLs, data-provenance values, and their resulting provenance file URLs.
| https://example.com/sampleproject-1.2.3.tar.gz | https://example.com/sampleproject-1.2.3.tar.gz.provenance | https://example.com/sampleproject-1.2.3.tar.gz.provenance |
| https://example.com/sampleproject-1.2.3.tar.gz | https://other.example.com/sampleproject-1.2.3.tar.gz/provenance | https://other.example.com/sampleproject-1.2.3.tar.gz/provenance |
| https://example.com/sampleproject-1.2.3.tar.gz | ../relative | (invalid: not a fully qualified URL) |
| https://example.com/sampleproject-1.2.3.tar.gz | http://unencrypted.example.com/provenance | (invalid: not a secure origin) |
See Changes to provenance objects for an additional discussion of reasons why a file’s provenance may change.
The following changes are made to the JSON simple API:
The value of the provenance key SHALL be either a JSON string or null. If provenance is not null, it SHALL be a URL to the associated provenance file.
See Appendix 3: Simple JSON API size considerations for an explanation of the technical decision to embed the SHA-256 digest in the JSON API, rather than the full provenance object.
These changes require a version change to the JSON API:
An attestation object is a JSON object with several required keys; applications or signers may include additional keys so long as all explicitly listed keys are provided. The required layout of an attestation object is provided as pseudocode below.
A full data model for each object in transparency_entries is provided in Appendix 2: Data models for Transparency Log Entries. Attestation objects SHOULD include one or more transparency log entries, and MAY include additional keys for other sources of signed time (such as an RFC 3161 Time Stamping Authority or a Roughtime server).
Attestation objects are versioned; this PEP specifies version 1. Each version is tied to a single cryptographic suite to minimize unnecessary cryptographic agility. In version 1, the suite is as follows:
Future PEPs may change this suite (and the overall shape of the attestation object) by selecting a new version number.
The attestation statement is the actual claim that is cryptographically signed over within the attestation object (i.e., the envelope.statement).
The attestation statement is encoded as a v1 in-toto Statement object, in JSON form. When serialized the statement is treated as an opaque binary blob, avoiding the need for canonicalization. An example JSON-encoded statement is provided in Appendix 4: Example attestation statement.
In addition to being a v1 in-toto Statement, the attestation statement is constrained in the following ways:
The signature over this statement is constructed using the v1 DSSE signature protocol, with a PAYLOAD_TYPE of application/vnd.in-toto+json and a PAYLOAD_BODY of the JSON-encoded statement above. No other PAYLOAD_TYPE is permitted.
The index will serve uploaded attestations along with metadata that can assist in verifying them in the form of JSON serialized objects.
These provenance objects will be available via both the Simple Index and JSON-based Simple API as described above, and will have the following layout:
or, as pseudocode:
As noted in the Publisher model, each AttestationBundle.publisher object is specific to its Trusted Publisher but must include at minimum:
All other keys in the publisher object are publisher-specific. A full illustrative example of a publisher object is provided in Appendix 1: Example Trusted Publisher Representation.
Each array of attestation objects is a superset of the attestations array supplied by the uploaded through the attestations field at upload time, as described in Upload endpoint changes and Changes to provenance objects.
Provenance objects are not immutable, and may change over time. Reasons for changes to the provenance object include but are not limited to:
Verifying an attestation object against a distribution file requires verification of each of the following:
In addition to the above required steps, a verifier MAY additionally verify verification_material.transparency_entries on a policy basis, e.g. requiring at least one transparency log entry or a threshold of entries. When verifying transparency entries, the verifier MUST confirm that the inclusion time for each entry lies within the signing certificate’s validity period.
This PEP is primarily “mechanical” in nature; it provides layouts for structuring and serving verifiable digital attestations without specifying higher level security “policies” around attestation validity, thresholds between attestations, and so forth.
Algorithmic agility is a common source of exploitable vulnerabilities in cryptographic schemes. This PEP limits algorithmic agility in two ways:
This PEP does not increase (or decrease) trust in the index itself: the index is still effectively trusted to honestly deliver unmodified package distributions, since a dishonest index capable of modifying package contents could also dishonestly modify or omit package attestations. As a result, this PEP’s presumption of index trust is equivalent to the unstated presumption with earlier mechanisms, like PGP and wheel signatures.
This PEP does not preclude or exclude future index trust mechanisms, such as PEP 458 and/or PEP 480.
This PEP recommends, but does not mandate, that attestation objects contain one or more verifiable sources of signed time that corroborate the signing certificate’s claimed validity period. Indices that implement this PEP may choose to strictly enforce this requirement.
This appendix provides a fictional example of a publisher key within a simple JSON API project.files[].provenance listing:
This appendix contains pseudocoded data models for transparency log entries in attestation objects. Each transparency log entry serves as a source of signed inclusion time, and can be verified either online or offline.
A previous draft of this PEP required embedding each provenance object directly into its appropriate part of the JSON Simple API.
The current version of this PEP embeds the SHA-256 digest of the provenance object instead. This is done for size and network bandwidth consideration reasons:
These numbers are significantly worse in “pathological” cases, where projects have hundreds or thousands of releases and/or dozens of files per release.
Given a source distribution sampleproject-1.2.3.tar.gz with a SHA-256 digest of e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, the following is an appropriate in-toto Statement, as a JSON object:
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0740.rst
Last modified: 2024-12-03 18:16:41 UTC