Skip to content

Commit a85fa91

Browse files
woodruffwhugovkfacutuesca
authored
PEP 740: Index support for digital attestations (#3618)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Facundo Tuesca <facundo.tuesca@trailofbits.com>
1 parent 9d82dd1 commit a85fa91

File tree

2 files changed

+371
-0
lines changed

2 files changed

+371
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ peps/pep-0735.rst @brettcannon
616616
peps/pep-0736.rst @gvanrossum @Rosuav
617617
peps/pep-0737.rst @vstinner
618618
peps/pep-0738.rst @encukou
619+
peps/pep-0740.rst @dstufft
619620
# ...
620621
# peps/pep-0754.rst
621622
# ...

peps/pep-0740.rst

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
PEP: 740
2+
Title: Index support for digital attestations
3+
Author: William Woodruff <william@yossarian.net>,
4+
Facundo Tuesca <facundo.tuesca@trailofbits.com>
5+
Sponsor: Donald Stufft <donald@stufft.io>
6+
PEP-Delegate: Donald Stufft <donald@stufft.io>
7+
Discussions-To: https://discuss.python.org/t/pre-pep-exposing-trusted-publisher-provenance-on-pypi/42337
8+
Status: Draft
9+
Type: Informational
10+
Topic: Packaging
11+
Created: 08-Jan-2024
12+
13+
Abstract
14+
========
15+
16+
This PEP proposes a collection of changes related to the upload and distribution
17+
of digitally signed attestations and metadata used to verify them on a Python
18+
package repository, such as PyPI.
19+
20+
These changes have two subcomponents:
21+
22+
* Changes to the currently unstandardized PyPI upload API, allowing clients
23+
to upload digital attestations;
24+
* Changes to the :pep:`503` and :pep:`691` "simple" APIs, allowing clients
25+
to retrieve both digital attestations and
26+
`Trusted Publishing <https://docs.pypi.org/trusted-publishers/>`_ metadata
27+
for individual release files.
28+
29+
This PEP does not recommend a specific digital attestation format, nor does
30+
it make a policy recommendation around mandatory digital attestations on
31+
release uploads or their subsequent verification by installing clients like
32+
``pip``.
33+
34+
Rationale
35+
=========
36+
37+
Desire for digital signatures on Python packages has been repeatedly
38+
expressed by both package maintainers and downstream users:
39+
40+
* Maintainers wish to demonstrate the integrity and authenticity of their
41+
package uploads;
42+
* Individual downstream users wish to verify package integrity and authenticity
43+
without placing additional trust in their index's honesty;
44+
* "Bulk" downstream users (such as Operating System distributions) wish to
45+
perform similar verifications and potentially re-expose or countersign
46+
for their own downstream packaging ecosystems.
47+
48+
This proposal seeks to accommodate each of the above use cases.
49+
50+
While this PEP does not recommend a specific digital attestation format,
51+
it does recognize the utility of Trusted Publishing as a pre-existing,
52+
"zero-configuration" source of strong provenance for Python packages.
53+
Consequently this PEP includes a proposed scheme for exposing each release
54+
file's Trusted Publisher metadata, with the expectation that a future digital
55+
attestation format will likely make use of it.
56+
57+
Design Considerations
58+
---------------------
59+
60+
This PEP identifies the following design considerations when evaluating
61+
both its own proposed changes and previous work in the same or adjacent
62+
areas of Python packaging:
63+
64+
1. Index accessibility: digital attestations for Python packages
65+
are ideally retrievable directly from the index itself, as "detached"
66+
resources.
67+
68+
This both simplifies some compatibility concerns (by avoiding
69+
the need to modify the distribution formats themselves) and also simplifies
70+
the behavior of potential installing clients (by allowing them to
71+
retrieve each attestation before its corresponding package without needing
72+
to do streaming decompression).
73+
74+
2. Verification by the index itself: in addition to enabling verification
75+
by installing clients, each digital attestation is *ideally* verifiable
76+
in some form by the index itself.
77+
78+
This both increases the overall quality
79+
of attestations uploaded to the index (preventing, for example, users
80+
from accidentally uploading incorrect or invalid attestations) and also
81+
enables UI and UX refinements on the index itself (such as a "provenance"
82+
view for each uploaded package).
83+
84+
3. General applicability: digital attestations should be applicable to
85+
*any and every* package uploaded to the index, regardless of its format
86+
(sdist or wheel) or interior contents.
87+
88+
4. Metadata support: this PEP refers to "digital attestations" rather than
89+
just "digital signatures" to emphasize the ideal presence of additional
90+
metadata within the cryptographic envelope.
91+
92+
For example, to prevent domain separation between a distribution's name and
93+
its contents, the digital attestation could be performed over
94+
``HASH(name || HASH(contents))`` rather than just ``HASH(contents)``.
95+
96+
5. Consistent release attestations: if a file belonging to a release has a
97+
set of digital attestations, then all of the other files belonging to that
98+
release should also have the same types of attestations.
99+
100+
This simplifies the downstream use story for digital attestations, and
101+
prevents potentially vulnerable "swiss cheese" release patterns (where
102+
a verifier checks for a valid attestation on ``HolyGrail-1.0.tar.gz``
103+
but their installing client actually resolves an attacker-controlled,
104+
platform-specific ``.whl`` instead).
105+
106+
107+
Previous Work
108+
-------------
109+
110+
PGP signatures
111+
^^^^^^^^^^^^^^
112+
113+
PyPI and other indices have historically supported PGP signatures on uploaded
114+
distributions. These could be supplied during upload, and could be retrieved
115+
by installing clients via the ``data-gpg-sig`` attribute in the :pep:`503`
116+
API, the ``gpg-sig`` key on the :pep:`691` API, or via an adjacent
117+
``.asc``-suffixed URL.
118+
119+
PGP signature uploads have been disabled on PyPI since
120+
`May 2023 <https://blog.pypi.org/posts/2023-05-23-removing-pgp/>`_, after
121+
`an investigation <https://blog.yossarian.net/2023/05/21/PGP-signatures-on-PyPI-worse-than-useless>`_
122+
determined that the majority of signatures (which, themselves, constituted a
123+
tiny percentage of overall uploads) could not be associated with a public key or
124+
otherwise meaningfully verified.
125+
126+
In their previously supported form on PyPI, PGP signatures satisfied
127+
considerations (1) and (3) above but not (2) (owing to the need for external
128+
keyservers and key distribution) or (4) (due to PGP signatures typically being
129+
constructed over just an input file, without any associated signed metadata).
130+
Similarly, PyPI's historical implementation of PGP did not satisfy consideration
131+
(5), due to a lack of consistency checks between different release files
132+
(and an inability to perform those checks due to no access to the signer's
133+
public key).
134+
135+
Wheel signatures
136+
^^^^^^^^^^^^^^^^
137+
138+
:pep:`427` (and its :ref:`living PyPA counterpart <packaging:binary-distribution-format>`)
139+
specify the :term:`wheel format <packaging:Wheel>`.
140+
141+
This format includes accommodations for digital signatures embedded directly
142+
into the wheel, in either JWS or S/MIME format. These signatures are specified
143+
over a :pep:`376` RECORD, which is modified to include a cryptographic digest
144+
for each recorded file in the wheel.
145+
146+
While wheel signatures are fully specified, they do not appear to be broadly
147+
used; the official `wheel tooling <https://github.com/pypa/wheel>`_ deprecated
148+
signature generation and verification support
149+
`in 0.32.0 <https://wheel.readthedocs.io/en/stable/news.html>`_, which was
150+
released in 2018.
151+
152+
Additionally, wheel signatures do not satisfy any of
153+
the above considerations (due to the "attached" nature of the signatures,
154+
non-verifiability on the index itself, and support for wheels only).
155+
156+
Specification
157+
=============
158+
159+
.. _upload-endpoint:
160+
161+
Upload endpoint changes
162+
-----------------------
163+
164+
The current upload API is not standardized. However, we propose the following
165+
changes to it:
166+
167+
* In addition to the current top-level ``content`` and ``gpg_signature`` fields,
168+
the index **SHALL** accept ``attestations`` as an additional multipart form
169+
field.
170+
* The new ``attestations`` field **SHALL** be a JSON object.
171+
* The JSON object **SHALL** have one or more keys, each identifying an
172+
attestation format known to the index. If any key does not identify an
173+
attestation format known to the index, the index **MUST** reject the upload.
174+
* The value associated with each well-known key **SHALL** be a JSON object.
175+
* Each attestation value **MUST** be verifiable by the index. If the index fails
176+
to verify any attestation in ``attestations``, it **MUST** reject the upload.
177+
178+
In addition to the above, the index **SHALL** enforce a consistency
179+
policy for release attestations via the following:
180+
181+
* If the first file under a new release is supplied with ``attestations``,
182+
then all subsequently uploaded files under the same release **MUST** also
183+
have ``attestations``. Conversely, if the first file under a new release
184+
does not have any ``attestations``, then all subsequent uploads under the
185+
same release **MUST NOT** have ``attestations``.
186+
* All files under the same release **MUST** have the same set of well-known
187+
attestation format keys.
188+
189+
The index **MUST** reject any file upload that does not satisfy these
190+
consistency properties.
191+
192+
Index changes
193+
-------------
194+
195+
.. _provenance-object:
196+
197+
Provenance objects
198+
^^^^^^^^^^^^^^^^^^
199+
200+
The index will serve uploaded attestations along with metadata that can assist
201+
in verifying them in the form of JSON serialized objects.
202+
203+
These "provenance objects" will be available via both the :pep:`503` Simple Index
204+
and :pep:`691` JSON-based Simple API as described below, and will have the
205+
following structure:
206+
207+
.. code-block:: json
208+
209+
{
210+
"publisher": {
211+
"type": "important-ci-service",
212+
"claims": {},
213+
"vendor-property": "foo",
214+
"another-property": 123
215+
},
216+
"attestations": {
217+
"some-attestation": {/* ... */},
218+
"another-attestation": {/* ... */}
219+
}
220+
}
221+
222+
* ``publisher`` is an **optional** JSON object, containing a
223+
representation of the file's Trusted Publisher configuration at the time
224+
the file was uploaded to the package index. The keys within the ``publisher``
225+
object are specific to each Trusted Publisher but include, at minimum:
226+
227+
* A ``type`` key, which **MUST** be a JSON string that uniquely identifies the
228+
kind of Trusted Publisher.
229+
* A ``claims`` key, which **MUST** be a JSON object containing any context-specific
230+
claims retained by the index during Trusted Publisher authentication.
231+
232+
All other keys in the ``publisher`` object are publisher-specific. A full
233+
illustrative example of a ``publisher`` object is provided in :ref:`appendix-2`.
234+
* ``attestations`` is a **required** JSON object, containing one or
235+
more attestation objects as identified by their keys. This object is
236+
a superset of ``attestations`` object supplied by the uploader through the
237+
``attestations`` field, as described in :ref:`upload-endpoint`.
238+
239+
Because ``attestations`` is a superset of the file's original uploaded attestations,
240+
the index **MAY** chose to embed additional attestations of its own.
241+
242+
Simple Index
243+
^^^^^^^^^^^^
244+
245+
* When an uploaded file has one or more attestations, the index **MAY** include a
246+
``data-provenance`` attribute on its file link, with a value of either
247+
``true`` or ``false``.
248+
* When ``data-provenance`` is ``true``, the index **MUST** serve a
249+
:ref:`provenance object <provenance-object>` at the same URL, but with
250+
``.provenance`` appended to it. For example, if ``HolyGrail-1.0.tar.gz``
251+
exists and has associated attestations, those attestations would be located
252+
within the provenance object hosted at ``HolyGrail-1.0.tar.gz.provenance``.
253+
254+
JSON-based Simple API
255+
^^^^^^^^^^^^^^^^^^^^^
256+
257+
* When an uploaded file has one or more attestations, the index **MAY** include a
258+
``provenance`` object in the ``file`` dictionary for that file.
259+
* ``provenance``, when present, **MUST** be a :ref:`provenance object <provenance-object>`.
260+
261+
Security Implications
262+
=====================
263+
264+
This PEP is "mechanical" in nature; it provides only the plumbing for future
265+
digital attestations on package indices, without specifying their concrete
266+
cryptographic details.
267+
268+
As such, we do not identify any positive or negative security implications
269+
for this PEP.
270+
271+
Index trust
272+
-----------
273+
274+
This PEP does **not** increase (or decrease) trust in the index itself:
275+
the index is still effectively trusted to honestly deliver unmodified package
276+
distributions, since a dishonest index capable of modifying package
277+
contents could also dishonestly modify or omit package attestations.
278+
As a result, this PEP's presumption of index trust is equivalent to the
279+
unstated presumption with earlier mechanisms, like PGP and Wheel signatures.
280+
281+
This PEP does not preclude or exclude future index trust mechanisms, such
282+
as :pep:`458` and/or :pep:`480`.
283+
284+
Recommendations
285+
===============
286+
287+
This PEP does not recommend specific attestation formats. It does,
288+
however, make the following recommendations to package indices seeking
289+
to create new or implement pre-existing attestation formats:
290+
291+
1. Consult the :ref:`living PyPA specifications <packaging:packaging-specifications>`
292+
first, and determine if any currently defined attestation formats suit
293+
your purpose.
294+
2. If no suitable attestation format is defined under the PyPA specifications,
295+
consider submitting it to the PyPA specifications for longevity and reuse
296+
purposes.
297+
298+
When designing a new attestation format, we make the following recommendations:
299+
300+
1. Pick a short, but unique name for your attestation format; this name will
301+
serve as the attestation's identifier in the upload and index APIs.
302+
303+
When appropriate for an attestation format, we recommend using ``:`` as a
304+
domain separator. For example, an attestation format that provides publish
305+
provenance using `Sigstore <https://www.sigstore.dev/>`_ might have the
306+
name ``sigstore:publish``.
307+
2. Prefer parsimony in your format: avoid optional fields and functionality,
308+
avoid unnecessary cryptographic agility and message malleability, and ensure
309+
that verifying the attestation communicates something meaningful beyond a
310+
basic integrity check (since the index itself already supplies cryptographic
311+
digests for this purpose).
312+
313+
.. _appendix-1:
314+
315+
Appendix 1: Example Uploaded Attestations
316+
=========================================
317+
318+
This appendix provides a fictional example of the ``attestations`` field
319+
submitted on file upload, with two fictional attestations (``publish`` and
320+
``timestamp``):
321+
322+
.. code-block:: json
323+
324+
{
325+
"publish": {
326+
"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.2",
327+
"verificationMaterial": { /* omitted for brevity */ },
328+
"messageSignature": {
329+
"messageDigest": {
330+
"algorithm": "some-hash-algo",
331+
"digest": "digest-here"
332+
},
333+
"signature": "signature-here"
334+
}
335+
},
336+
"timestamp": {
337+
"cms": "some-long-blob-here"
338+
}
339+
}
340+
341+
The payloads of these fictional attestations are purely illustrative.
342+
343+
.. _appendix-2:
344+
345+
Appendix 2: Example Trusted Publisher Representation
346+
====================================================
347+
348+
This appendix provides a fictional example of a ``publisher`` key within
349+
a :pep:`691` ``project.files[].provenance`` listing:
350+
351+
.. code-block:: json
352+
353+
"publisher": {
354+
"type": "GitHub",
355+
"claims": {
356+
"ref": "refs/tags/v1.0.0",
357+
"sha": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
358+
},
359+
"repository_name": "HolyGrail",
360+
"repository_owner": "octocat",
361+
"repository_owner_id": "1",
362+
"workflow_filename": "publish.yml",
363+
"environment": null
364+
}
365+
366+
Copyright
367+
=========
368+
369+
This document is placed in the public domain or under the
370+
CC0-1.0-Universal license, whichever is more permissive.

0 commit comments

Comments
 (0)