2024-01-29 16:26:19 -05:00
|
|
|
PEP: 740
|
|
|
|
Title: Index support for digital attestations
|
|
|
|
Author: William Woodruff <william@yossarian.net>,
|
2024-04-24 13:54:44 -04:00
|
|
|
Facundo Tuesca <facundo.tuesca@trailofbits.com>,
|
|
|
|
Dustin Ingram <di@python.org>
|
2024-01-29 16:26:19 -05:00
|
|
|
Sponsor: Donald Stufft <donald@stufft.io>
|
|
|
|
PEP-Delegate: Donald Stufft <donald@stufft.io>
|
2024-01-29 18:47:43 -05:00
|
|
|
Discussions-To: https://discuss.python.org/t/pep-740-index-support-for-digital-attestations/44498
|
2024-07-18 11:49:15 -04:00
|
|
|
Status: Provisional
|
|
|
|
Type: Standards Track
|
2024-01-29 16:26:19 -05:00
|
|
|
Topic: Packaging
|
|
|
|
Created: 08-Jan-2024
|
2024-01-29 18:47:43 -05:00
|
|
|
Post-History: `02-Jan-2024 <https://discuss.python.org/t/pre-pep-exposing-trusted-publisher-provenance-on-pypi/42337>`__,
|
|
|
|
`29-Jan-2024 <https://discuss.python.org/t/pep-740-index-support-for-digital-attestations/44498>`__
|
2024-07-18 11:49:15 -04:00
|
|
|
Resolution: https://discuss.python.org/t/pep-740-index-support-for-digital-attestations/44498/26
|
|
|
|
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
Abstract
|
|
|
|
========
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
* Changes to the currently unstandardized PyPI upload API, allowing clients
|
2024-04-24 13:54:44 -04:00
|
|
|
to upload digital attestations as :ref:`attestation objects <attestation-object>`;
|
2024-06-12 16:08:35 -04:00
|
|
|
* Changes to the :ref:`HTML and JSON "simple" APIs <packaging:simple-repository-api>`,
|
|
|
|
allowing clients to retrieve both digital attestations and
|
2024-01-29 16:26:19 -05:00
|
|
|
`Trusted Publishing <https://docs.pypi.org/trusted-publishers/>`_ metadata
|
2024-04-24 13:54:44 -04:00
|
|
|
for individual release files as :ref:`provenance objects <provenance-object>`.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
This PEP does not make a policy recommendation around mandatory digital
|
|
|
|
attestations on release uploads or their subsequent verification by installing
|
|
|
|
clients like ``pip``.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-02-10 13:48:19 -05:00
|
|
|
Rationale and Motivation
|
|
|
|
========================
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
Desire for digital signatures on Python packages has been repeatedly
|
|
|
|
expressed by both package maintainers and downstream users:
|
|
|
|
|
|
|
|
* Maintainers wish to demonstrate the integrity and authenticity of their
|
|
|
|
package uploads;
|
|
|
|
* Individual downstream users wish to verify package integrity and authenticity
|
|
|
|
without placing additional trust in their index's honesty;
|
|
|
|
* "Bulk" downstream users (such as Operating System distributions) wish to
|
|
|
|
perform similar verifications and potentially re-expose or countersign
|
|
|
|
for their own downstream packaging ecosystems.
|
|
|
|
|
|
|
|
This proposal seeks to accommodate each of the above use cases.
|
|
|
|
|
2024-02-10 13:48:19 -05:00
|
|
|
Additionally, this proposal identifies the following motivations:
|
|
|
|
|
|
|
|
* Verifiable provenance for Python package distributions: many Python
|
|
|
|
packages currently contain *unauthenticated* provenance metadata, such
|
|
|
|
as URLs for source hosts. A cryptographic attestation format could enable
|
|
|
|
strong *authenticated* links between these packages and their source hosts,
|
|
|
|
allowing both the index and downstream users to cryptographically verify that
|
|
|
|
a package originates from its claimed source repository.
|
|
|
|
* Raising attacker requirements: an attacker who seeks to take
|
|
|
|
over a Python package can be described along *sophistication*
|
|
|
|
(unsophisticated to sophisticated) and *targeting* dimensions
|
|
|
|
(opportunistic to targeted).
|
|
|
|
|
|
|
|
Digital attestations impose additional sophistication requirements: the
|
|
|
|
attacker must be sufficiently sophisticated to access private signing material
|
2024-04-24 13:54:44 -04:00
|
|
|
(or signing identities).
|
|
|
|
* Index verifiability: in the status quo, the only attestation provided by the
|
2024-02-10 13:48:19 -05:00
|
|
|
index is an optional PGP signature per release file
|
|
|
|
(see :ref:`PGP signatures <pgp-signatures>`). These signatures are not
|
2024-04-24 13:54:44 -04:00
|
|
|
(and cannot be) checked by the index either for well-formedness or for
|
|
|
|
validity, since the index has no mechanism for identifying the right public
|
|
|
|
key for the signature. This PEP overcomes this limitation
|
|
|
|
by ensuring that :ref:`provenance objects <provenance-object>` contain all
|
|
|
|
of the metadata needed by the index to verify an attestation's validity.
|
|
|
|
|
|
|
|
This PEP proposes a generic attestation format, containing an
|
2024-06-12 16:08:35 -04:00
|
|
|
:ref:`attestation statement for signature generation <payload-and-signature-generation>`,
|
2024-04-24 13:54:44 -04:00
|
|
|
with the expectation that index providers adopt the
|
|
|
|
format with a suitable source of identity for signature verification, such as
|
|
|
|
Trusted Publishing.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
Design Considerations
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
1. Index accessibility: digital attestations for Python packages
|
|
|
|
are ideally retrievable directly from the index itself, as "detached"
|
|
|
|
resources.
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
2. Verification by the index itself: in addition to enabling verification
|
|
|
|
by installing clients, each digital attestation is *ideally* verifiable
|
|
|
|
in some form by the index itself.
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
3. General applicability: digital attestations should be applicable to
|
|
|
|
*any and every* package uploaded to the index, regardless of its format
|
|
|
|
(sdist or wheel) or interior contents.
|
|
|
|
|
|
|
|
4. Metadata support: this PEP refers to "digital attestations" rather than
|
|
|
|
just "digital signatures" to emphasize the ideal presence of additional
|
|
|
|
metadata within the cryptographic envelope.
|
|
|
|
|
|
|
|
For example, to prevent domain separation between a distribution's name and
|
2024-06-12 16:08:35 -04:00
|
|
|
its contents, this PEP uses '`Statements <https://github.com/in-toto/attestation/blob/v1.0/spec/v1.0/statement.md>`__'
|
|
|
|
from the `in-toto project <https://in-toto.io/>`__ to bind the distribution's
|
|
|
|
contents (via SHA-256 digest) to its filename.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
Previous Work
|
|
|
|
-------------
|
|
|
|
|
2024-02-10 13:48:19 -05:00
|
|
|
.. _pgp-signatures:
|
|
|
|
|
2024-01-29 16:26:19 -05:00
|
|
|
PGP signatures
|
|
|
|
^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
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 <https://blog.pypi.org/posts/2023-05-23-removing-pgp/>`_, after
|
|
|
|
`an investigation <https://blog.yossarian.net/2023/05/21/PGP-signatures-on-PyPI-worse-than-useless>`_
|
|
|
|
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).
|
|
|
|
|
|
|
|
Wheel signatures
|
|
|
|
^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
:pep:`427` (and its :ref:`living PyPA counterpart <packaging:binary-distribution-format>`)
|
|
|
|
specify the :term:`wheel format <packaging:Wheel>`.
|
|
|
|
|
|
|
|
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 <https://github.com/pypa/wheel>`_ deprecated
|
|
|
|
signature generation and verification support
|
|
|
|
`in 0.32.0 <https://wheel.readthedocs.io/en/stable/news.html>`_, 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).
|
|
|
|
|
|
|
|
Specification
|
|
|
|
=============
|
|
|
|
|
|
|
|
.. _upload-endpoint:
|
|
|
|
|
|
|
|
Upload endpoint changes
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
The current upload API is not standardized. However, we propose the following
|
|
|
|
changes to it:
|
|
|
|
|
|
|
|
* In addition to the current top-level ``content`` and ``gpg_signature`` fields,
|
|
|
|
the index **SHALL** accept ``attestations`` as an additional multipart form
|
|
|
|
field.
|
2024-04-24 13:54:44 -04:00
|
|
|
* The new ``attestations`` field **SHALL** be a JSON array.
|
|
|
|
* The ``attestations`` array **SHALL** have one or more items, each a JSON object
|
|
|
|
representing an individual attestation.
|
|
|
|
* Each attestation object **MUST** be verifiable by the index. If the index fails
|
2024-01-29 16:26:19 -05:00
|
|
|
to verify any attestation in ``attestations``, it **MUST** reject the upload.
|
2024-04-24 13:54:44 -04:00
|
|
|
The format of attestation objects is defined under :ref:`attestation-object`
|
|
|
|
and the process for verifying attestations is defined under
|
|
|
|
:ref:`attestation-verification`.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Index changes
|
|
|
|
-------------
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Simple Index
|
|
|
|
^^^^^^^^^^^^
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
The following changes are made to the
|
|
|
|
:ref:`simple repository API <packaging:simple-repository-api-base>`:
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* When an uploaded file has one or more attestations, the index **MAY**
|
|
|
|
provide a ``.provenance`` file adjacent to the hosted distribution.
|
|
|
|
The format of the ``.provenance`` file **SHALL** be a JSON-encoded
|
|
|
|
:ref:`provenance object <provenance-object>`, which **SHALL** contain
|
|
|
|
the file's attestations.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
For example, if an uploaded file is hosted at
|
|
|
|
the URL ``https://example.com/sampleproject-1.2.3.tar.gz``, the provenance
|
|
|
|
URL would be ``https://example.com/sampleproject-1.2.3.tar.gz.provenance``.
|
|
|
|
|
|
|
|
* When a ``.provenance`` file is present, the index **MAY** include a
|
|
|
|
``data-provenance`` attribute on its file link. The value of the
|
2024-06-12 16:08:35 -04:00
|
|
|
``data-provenance`` attribute **SHALL** be the SHA-256 digest of the
|
2024-04-24 13:54:44 -04:00
|
|
|
associated ``.provenance`` file.
|
|
|
|
|
|
|
|
* The index **MAY** choose to modify the ``.provenance`` file. For example,
|
|
|
|
the index **MAY** permit adding additional attestations and verification
|
|
|
|
materials, such as attestations from third-party auditors or other services.
|
|
|
|
When the index modifies the ``.provenance`` file, it **MUST** also update the
|
2024-06-12 16:08:35 -04:00
|
|
|
``data-provenance`` attribute's value to the new SHA-256 digest.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
|
|
|
See :ref:`changes-to-provenance-objects` for an additional discussion of
|
|
|
|
reasons why a file's provenance may change.
|
|
|
|
|
|
|
|
JSON-based Simple API
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
The following changes are made to the
|
|
|
|
:ref:`JSON simple API <packaging:simple-repository-api-json>`:
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* When an uploaded file has one or more attestations, the index **MAY**
|
2024-06-12 16:08:35 -04:00
|
|
|
include a ``provenance`` key in the ``file`` dictionary for that file.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-08-22 02:00:24 -04:00
|
|
|
The value of the ``provenance`` key **SHALL** be either a JSON string
|
|
|
|
or ``null``. If ``provenance`` is not ``null``, it **SHALL** be the SHA-256
|
|
|
|
digest of the associated ``.provenance`` file, as in the Simple Index.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
See :ref:`appendix-3` for an explanation of the technical decision to
|
|
|
|
embed the SHA-256 digest in the JSON API, rather than the full
|
|
|
|
:ref:`provenance object <provenance-object>`.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
|
|
|
These changes require a version change to the JSON API:
|
|
|
|
|
|
|
|
* The ``api-version`` **SHALL** specify version 1.2 or later.
|
|
|
|
|
|
|
|
.. _attestation-object:
|
|
|
|
|
|
|
|
Attestation objects
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Attestation:
|
|
|
|
version: Literal[1]
|
|
|
|
"""
|
|
|
|
The attestation object's version, which is always 1.
|
|
|
|
"""
|
|
|
|
|
|
|
|
verification_material: VerificationMaterial
|
|
|
|
"""
|
2024-06-12 16:08:35 -04:00
|
|
|
Cryptographic materials used to verify `envelope`.
|
|
|
|
"""
|
|
|
|
|
|
|
|
envelope: Envelope
|
|
|
|
"""
|
|
|
|
The enveloped attestation statement and signature.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Envelope:
|
|
|
|
statement: bytes
|
|
|
|
"""
|
|
|
|
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.
|
2024-04-24 13:54:44 -04:00
|
|
|
"""
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
signature: bytes
|
2024-04-24 13:54:44 -04:00
|
|
|
"""
|
2024-06-12 16:08:35 -04:00
|
|
|
A signature for the above statement, encoded as base64.
|
2024-04-24 13:54:44 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class VerificationMaterial:
|
|
|
|
certificate: str
|
|
|
|
"""
|
|
|
|
The signing certificate, as `base64(DER(cert))`.
|
|
|
|
"""
|
|
|
|
|
|
|
|
transparency_entries: list[object]
|
|
|
|
"""
|
|
|
|
One or more transparency log entries for this attestation's signature
|
|
|
|
and certificate.
|
|
|
|
"""
|
|
|
|
|
|
|
|
A full data model for each object in ``transparency_entries`` is provided in
|
|
|
|
:ref:`appendix-2`. 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 <https://blog.cloudflare.com/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:
|
|
|
|
|
|
|
|
* Certificates are specified as X.509 certificates, and comply with the
|
|
|
|
profile in :rfc:`5280`.
|
|
|
|
* The message signature algorithm is ECDSA, with the P-256 curve for public keys
|
|
|
|
and SHA-256 as the cryptographic digest function.
|
|
|
|
|
|
|
|
Future PEPs may change this suite (and the overall shape of the attestation
|
|
|
|
object) by selecting a new version number.
|
|
|
|
|
|
|
|
.. _payload-and-signature-generation:
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
Attestation statement and signature generation
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
The *attestation statement* is the actual claim that is cryptographically signed
|
|
|
|
over within the attestation object (i.e., the ``envelope.statement``).
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
The attestation statement is encoded as a
|
|
|
|
`v1 in-toto Statement object <https://github.com/in-toto/attestation/blob/v1.0/spec/v1.0/statement.md>`__,
|
|
|
|
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 :ref:`appendix-4`.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
In addition to being a v1 in-toto Statement, the attestation statement is constrained
|
|
|
|
in the following ways:
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
* The in-toto ``subject`` **MUST** contain only a single subject.
|
|
|
|
* ``subject[0].name`` is the distribution's filename, which **MUST** be
|
|
|
|
a valid :ref:`source distribution <packaging:source-distribution-format>` or
|
|
|
|
:ref:`wheel distribution <packaging:binary-distribution-format>` filename.
|
|
|
|
* ``subject[0].digest`` **MUST** contain a SHA-256 digest. Other digests
|
|
|
|
**MAY** be present. The digests **MUST** be represented as hexadecimal strings.
|
|
|
|
* The following ``predicateType`` values are supported:
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
* `SLSA Provenance <https://slsa.dev/provenance/v1>`__: ``https://slsa.dev/provenance/v1``
|
|
|
|
* `PyPI Publish Attestation <https://docs.pypi.org/attestations/publish/v1>`__: ``https://docs.pypi.org/attestations/publish/v1``
|
2024-04-24 13:54:44 -04:00
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
The signature over this statement is constructed using the
|
|
|
|
`v1 DSSE signature protocol <https://github.com/secure-systems-lab/dsse/blob/v1.0.0/protocol.md>`__,
|
|
|
|
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.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
.. _provenance-object:
|
|
|
|
|
|
|
|
Provenance objects
|
2024-04-24 13:54:44 -04:00
|
|
|
------------------
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
The index will serve uploaded attestations along with metadata that can assist
|
|
|
|
in verifying them in the form of JSON serialized objects.
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
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:
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
.. code-block:: json
|
|
|
|
|
|
|
|
{
|
2024-04-24 13:54:44 -04:00
|
|
|
"version": 1,
|
|
|
|
"attestation_bundles": [
|
|
|
|
{
|
|
|
|
"publisher": {
|
|
|
|
"kind": "important-ci-service",
|
|
|
|
"claims": {},
|
|
|
|
"vendor-property": "foo",
|
|
|
|
"another-property": 123
|
|
|
|
},
|
|
|
|
"attestations": [
|
|
|
|
{ /* attestation 1 ... */ },
|
|
|
|
{ /* attestation 2 ... */ }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2024-01-29 16:26:19 -05:00
|
|
|
}
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
or, as pseudocode:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Publisher:
|
|
|
|
kind: string
|
|
|
|
"""
|
|
|
|
The kind of Trusted Publisher.
|
|
|
|
"""
|
|
|
|
|
|
|
|
claims: object | None
|
|
|
|
"""
|
|
|
|
Any context-specific claims retained by the index during Trusted Publisher
|
|
|
|
authentication.
|
|
|
|
"""
|
|
|
|
|
|
|
|
_rest: object
|
|
|
|
"""
|
|
|
|
Each publisher object is open-ended, meaning that it MAY contain additional
|
|
|
|
fields beyond the ones specified explicitly above. This field signals that,
|
|
|
|
but is not itself present.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class AttestationBundle:
|
|
|
|
publisher: Publisher
|
|
|
|
"""
|
|
|
|
The publisher associated with this set of attestations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
attestations: list[Attestation]
|
|
|
|
"""
|
|
|
|
The set of attestations included in this bundle.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Provenance:
|
|
|
|
version: Literal[1]
|
|
|
|
"""
|
|
|
|
The provenance object's version, which is always 1.
|
|
|
|
"""
|
|
|
|
|
|
|
|
attestation_bundles: list[AttestationBundle]
|
|
|
|
"""
|
|
|
|
One or more attestation "bundles".
|
|
|
|
"""
|
|
|
|
|
|
|
|
* ``version`` is ``1``. Like attestation objects, provenance objects are
|
|
|
|
versioned, and this PEP only defines version ``1``.
|
|
|
|
* ``attestation_bundles`` is a **required** JSON array, containing one
|
|
|
|
or more "bundles" of attestations. Each bundle corresponds to a
|
|
|
|
signing identity (such as a Trusted Publishing identity), and contains
|
|
|
|
one or more attestation objects.
|
|
|
|
|
|
|
|
As noted in the ``Publisher`` model,
|
|
|
|
each ``AttestationBundle.publisher`` object is specific to its Trusted Publisher
|
|
|
|
but must include at minimum:
|
|
|
|
|
|
|
|
* A ``kind`` key, which **MUST** be a JSON string that uniquely identifies the
|
2024-01-29 16:26:19 -05:00
|
|
|
kind of Trusted Publisher.
|
|
|
|
* A ``claims`` key, which **MUST** be a JSON object containing any context-specific
|
|
|
|
claims retained by the index during Trusted Publisher authentication.
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
All other keys in the publisher object are publisher-specific. A full
|
|
|
|
illustrative example of a publisher object is provided in :ref:`appendix-1`.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
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 :ref:`upload-endpoint` and
|
|
|
|
:ref:`changes-to-provenance-objects`.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
.. _changes-to-provenance-objects:
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Changes to provenance objects
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Provenance objects are *not* immutable, and may change over time. Reasons
|
|
|
|
for changes to the provenance object include but are not limited to:
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* Addition of new attestations for a pre-existing signing identity: the index
|
|
|
|
**MAY** choose to allow additional attestations by pre-existing signing
|
|
|
|
identities, such as newer attestation versions for already uploaded
|
|
|
|
files.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* Addition of new signing identities and associated attestations: the index
|
|
|
|
**MAY** choose to support attestations from sources other than the file's
|
|
|
|
uploader, such as third-party auditors or the index itself. These attestations
|
|
|
|
may be performed asynchronously, requiring the index to insert them into
|
|
|
|
the provenance object *post facto*.
|
|
|
|
|
|
|
|
.. _attestation-verification:
|
|
|
|
|
|
|
|
Attestation verification
|
|
|
|
------------------------
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
Verifying an attestation object against a distribution file requires verification of each of the
|
|
|
|
following:
|
2024-02-10 13:48:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* ``version`` is ``1``. The verifier **MUST** reject any other version.
|
|
|
|
* ``verification_material.certificate`` is a valid signing certificate, as
|
|
|
|
issued by an *a priori* trusted authority (such as a root of trust already
|
|
|
|
present within the verifying client).
|
|
|
|
* ``verification_material.certificate`` identifies an appropriate signing
|
|
|
|
subject, such as the machine identity of the Trusted Publisher that published
|
|
|
|
the package.
|
2024-06-12 16:08:35 -04:00
|
|
|
* ``envelope.statement`` is a valid in-toto v1 Statement, with a subject
|
|
|
|
and digest that **MUST** match the distribution's filename and contents.
|
|
|
|
For the distribution's filename, matching **MUST** be performed by parsing
|
|
|
|
using the appropriate source distribution or wheel filename format, as
|
|
|
|
the statement's subject may be equivalent but normalized.
|
|
|
|
* ``envelope.signature`` is a valid signature for ``envelope.statement``
|
|
|
|
corresponding to ``verification_material.certificate``,
|
|
|
|
as reconstituted via the
|
|
|
|
`v1 DSSE signature protocol <https://github.com/secure-systems-lab/dsse/blob/v1.0.0/protocol.md>`__.
|
2024-04-24 13:54:44 -04:00
|
|
|
|
|
|
|
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.
|
2024-02-10 13:48:19 -05:00
|
|
|
|
2024-01-29 16:26:19 -05:00
|
|
|
Security Implications
|
|
|
|
=====================
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
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.
|
|
|
|
|
|
|
|
Cryptographic agility in attestations
|
|
|
|
-------------------------------------
|
|
|
|
|
|
|
|
Algorithmic agility is a common source of exploitable vulnerabilities
|
|
|
|
in cryptographic schemes. This PEP limits algorithmic agility in two ways:
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
* All algorithms are specified in a single suite, rather than a geometric
|
|
|
|
collection of parameters. This makes it impossible (for example) for an
|
|
|
|
attacker to select a strong signature algorithm with a weak hash function,
|
|
|
|
compromising the scheme as a whole.
|
|
|
|
* Attestation objects are versioned, and may only contain the algorithmic
|
|
|
|
suite specified for their version. If a specific suite
|
|
|
|
is considered insecure in the future, clients may choose to blanket reject
|
|
|
|
or qualify verifications of attestations that contain that suite.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
Index trust
|
|
|
|
-----------
|
|
|
|
|
|
|
|
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
|
2024-04-24 13:54:44 -04:00
|
|
|
unstated presumption with earlier mechanisms, like PGP and wheel signatures.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
This PEP does not preclude or exclude future index trust mechanisms, such
|
|
|
|
as :pep:`458` and/or :pep:`480`.
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Recommendations
|
|
|
|
===============
|
2024-01-29 16:26:19 -05:00
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
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.
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
.. _appendix-1:
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
Appendix 1: Example Trusted Publisher Representation
|
2024-01-29 16:26:19 -05:00
|
|
|
====================================================
|
|
|
|
|
|
|
|
This appendix provides a fictional example of a ``publisher`` key within
|
2024-04-24 13:54:44 -04:00
|
|
|
a simple JSON API ``project.files[].provenance`` listing:
|
2024-01-29 16:26:19 -05:00
|
|
|
|
|
|
|
.. code-block:: json
|
|
|
|
|
|
|
|
"publisher": {
|
2024-04-24 13:54:44 -04:00
|
|
|
"kind": "GitHub",
|
2024-01-29 16:26:19 -05:00
|
|
|
"claims": {
|
|
|
|
"ref": "refs/tags/v1.0.0",
|
|
|
|
"sha": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
|
|
|
},
|
|
|
|
"repository_name": "HolyGrail",
|
|
|
|
"repository_owner": "octocat",
|
|
|
|
"repository_owner_id": "1",
|
|
|
|
"workflow_filename": "publish.yml",
|
|
|
|
"environment": null
|
|
|
|
}
|
|
|
|
|
2024-04-24 13:54:44 -04:00
|
|
|
|
|
|
|
.. _appendix-2:
|
|
|
|
|
|
|
|
Appendix 2: Data models for Transparency Log Entries
|
|
|
|
====================================================
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class TransparencyLogEntry:
|
|
|
|
log_index: int
|
|
|
|
"""
|
|
|
|
The global index of the log entry, used when querying the log.
|
|
|
|
"""
|
|
|
|
|
|
|
|
log_id: str
|
|
|
|
"""
|
|
|
|
An opaque, unique identifier for the log.
|
|
|
|
"""
|
|
|
|
|
|
|
|
entry_kind: str
|
|
|
|
"""
|
|
|
|
The kind (type) of log entry.
|
|
|
|
"""
|
|
|
|
|
|
|
|
entry_version: str
|
|
|
|
"""
|
|
|
|
The version of the log entry's submitted format.
|
|
|
|
"""
|
|
|
|
|
|
|
|
integrated_time: int
|
|
|
|
"""
|
|
|
|
The UNIX timestamp from the log from when the entry was persisted.
|
|
|
|
"""
|
|
|
|
|
|
|
|
inclusion_proof: InclusionProof
|
|
|
|
"""
|
2024-06-12 16:08:35 -04:00
|
|
|
The actual inclusion proof of the log entry.
|
2024-04-24 13:54:44 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class InclusionProof:
|
|
|
|
log_index: int
|
|
|
|
"""
|
|
|
|
The index of the entry in the tree it was written to.
|
|
|
|
"""
|
|
|
|
|
|
|
|
root_hash: str
|
|
|
|
"""
|
|
|
|
The digest stored at the root of the Merkle tree at the time of proof
|
|
|
|
generation.
|
|
|
|
"""
|
|
|
|
|
|
|
|
tree_size: int
|
|
|
|
"""
|
|
|
|
The size of the Merkle tree at the time of proof generation.
|
|
|
|
"""
|
|
|
|
|
|
|
|
hashes: list[str]
|
|
|
|
"""
|
|
|
|
A list of hashes required to complete the inclusion proof, sorted
|
|
|
|
in order from leaf to root. The leaf and root hashes are not themselves
|
|
|
|
included in this list; the root is supplied via `root_hash` and the client
|
|
|
|
must calculate the leaf hash.
|
|
|
|
"""
|
|
|
|
|
|
|
|
checkpoint: str
|
|
|
|
"""
|
|
|
|
The signed tree head's signature, at the time of proof generation.
|
|
|
|
"""
|
|
|
|
|
|
|
|
cosigned_checkpoints: list[str]
|
|
|
|
"""
|
|
|
|
Cosigned checkpoints from zero or more log witnesses.
|
|
|
|
"""
|
|
|
|
|
2024-06-12 16:08:35 -04:00
|
|
|
.. _appendix-3:
|
|
|
|
|
|
|
|
Appendix 3: Simple JSON API size considerations
|
|
|
|
===============================================
|
|
|
|
|
|
|
|
A previous draft of this PEP required embedding each
|
|
|
|
:ref:`provenance object <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:
|
|
|
|
|
|
|
|
1. We estimate the typical size of an attestation object to be approximately
|
|
|
|
5.3 KB of JSON.
|
|
|
|
2. We conservatively estimate that indices eventually host around 3 attestations
|
|
|
|
per release file, or approximately 15.9 KB of JSON per combined provenance
|
|
|
|
object.
|
|
|
|
3. As of May 2024, the average project on PyPI has approximately 21 release
|
|
|
|
files. We conservatively expect this average to increase over time.
|
|
|
|
4. Combined, these numbers imply that a typical project might expect to host
|
|
|
|
between 60 and 70 attestations, or approximately 339 KB of additional JSON
|
|
|
|
in its "project detail" endpoint.
|
|
|
|
|
|
|
|
These numbers are significantly worse in "pathological" cases, where projects
|
|
|
|
have hundreds or thousands of releases and/or dozens of files per release.
|
|
|
|
|
|
|
|
.. _appendix-4:
|
|
|
|
|
|
|
|
Appendix 4: Example attestation statement
|
|
|
|
=========================================
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
.. code-block:: json
|
|
|
|
|
|
|
|
{
|
|
|
|
"_type": "https://in-toto.io/Statement/v1",
|
|
|
|
"subject": [
|
|
|
|
{
|
|
|
|
"name": "sampleproject-1.2.3.tar.gz",
|
|
|
|
"digest": {"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"predicateType": "https://some-arbitrary-predicate.example.com/v1",
|
|
|
|
"predicate": {
|
|
|
|
"something-else": "foo"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-29 16:26:19 -05:00
|
|
|
Copyright
|
|
|
|
=========
|
|
|
|
|
|
|
|
This document is placed in the public domain or under the
|
|
|
|
CC0-1.0-Universal license, whichever is more permissive.
|