Skip to content

Commit 8fc177d

Browse files
facutuescawoodruffw
authored andcommitted
Add AttestationPayload model
1 parent 3eefb9e commit 8fc177d

File tree

4 files changed

+49
-3
lines changed

4 files changed

+49
-3
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ target-version = "py39"
7373

7474
[tool.ruff.lint]
7575
select = ["ALL"]
76+
# ANN102 is deprecated
7677
# D203 and D213 are incompatible with D211 and D212 respectively.
7778
# COM812 and ISC001 can cause conflicts when using ruff as a formatter.
7879
# See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules.
79-
ignore = ["D203", "D213", "COM812", "ISC001"]
80+
ignore = ["ANN102", "D203", "D213", "COM812", "ISC001"]
8081

8182
[tool.ruff.lint.per-file-ignores]
8283

src/pypi_attestation_models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ._impl import (
66
Attestation,
7+
AttestationPayload,
78
ConversionError,
89
InvalidAttestationError,
910
VerificationMaterial,
@@ -13,6 +14,7 @@
1314

1415
__all__ = [
1516
"Attestation",
17+
"AttestationPayload",
1618
"ConversionError",
1719
"InvalidAttestationError",
1820
"VerificationMaterial",

src/pypi_attestation_models/_impl.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77

88
import binascii
99
from base64 import b64decode, b64encode
10-
from typing import Annotated, Any, Literal, NewType
10+
from hashlib import sha256
11+
from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType
1112

13+
import rfc8785
1214
from annotated_types import MinLen # noqa: TCH002
1315
from cryptography import x509
1416
from cryptography.hazmat.primitives import serialization
1517
from pydantic import BaseModel
1618
from sigstore.models import Bundle, LogEntry
1719

20+
if TYPE_CHECKING:
21+
from pathlib import Path # pragma: no cover
22+
1823

1924
class ConversionError(ValueError):
2025
"""The base error for all errors during conversion."""
@@ -62,8 +67,34 @@ class Attestation(BaseModel):
6267
message_signature: str
6368
"""
6469
The attestation's signature, as `base64(raw-sig)`, where `raw-sig`
65-
is the raw bytes of the signing operation.
70+
is the raw bytes of the signing operation over the attestation payload.
71+
"""
72+
73+
74+
class AttestationPayload(BaseModel):
75+
"""Attestation Payload object as defined in PEP 740."""
76+
77+
distribution: str
6678
"""
79+
The file name of the Python package distribution.
80+
"""
81+
82+
digest: str
83+
"""
84+
The SHA-256 digest of the distribution's contents, as a hexadecimal string.
85+
"""
86+
87+
@classmethod
88+
def from_dist(cls, dist: Path) -> AttestationPayload:
89+
"""Create an `AttestationPayload` from a distribution file."""
90+
return AttestationPayload(
91+
distribution=dist.name,
92+
digest=sha256(dist.read_bytes()).hexdigest(),
93+
)
94+
95+
def __bytes__(self: AttestationPayload) -> bytes:
96+
"""Convert to bytes using a canonicalized JSON representation (from RFC8785)."""
97+
return rfc8785.dumps(self.model_dump())
6798

6899

69100
def sigstore_to_pypi(sigstore_bundle: Bundle) -> Attestation:

test/test_impl.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Internal implementation tests."""
22

3+
import hashlib
34
import json
45
from pathlib import Path
56

@@ -80,3 +81,14 @@ def test_verification_roundtrip() -> None:
8081
identity="facundo.tuesca@trailofbits.com", issuer="https://accounts.google.com"
8182
),
8283
)
84+
85+
86+
def test_attestation_payload() -> None:
87+
payload = impl.AttestationPayload.from_dist(artifact_path)
88+
89+
assert payload.digest == hashlib.sha256(artifact_path.read_bytes()).hexdigest()
90+
assert payload.distribution == artifact_path.name
91+
92+
expected = f'{{"digest":"{payload.digest}","distribution":"{payload.distribution}"}}'
93+
94+
assert bytes(payload) == bytes(expected, "utf-8")

0 commit comments

Comments
 (0)