Skip to content

Commit 285ca4a

Browse files
committed
Support timestamps in the attestation
1 parent a585379 commit 285ca4a

File tree

3 files changed

+19
-9
lines changed

3 files changed

+19
-9
lines changed

src/pypi_attestations/_cli.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,6 @@ def _sign(args: argparse.Namespace) -> None:
428428
_die(f"Failed to detect identity: {identity_error}")
429429

430430
trust_config = ClientTrustConfig.staging() if args.staging else ClientTrustConfig.production()
431-
# Make sure we use rekor v1 until attestations are compatible with v2
432-
trust_config.force_tlog_version = 1
433-
434431
signing_ctx = SigningContext.from_trust_config(trust_config)
435432

436433
# Validates that every file we want to sign exist but none of their attestations

src/pypi_attestations/_impl.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pydantic import Base64Bytes, BaseModel, ConfigDict, Field, field_validator
2525
from pydantic.alias_generators import to_snake
2626
from pydantic_core import ValidationError
27+
from rfc3161_client import decode_timestamp_response
2728
from sigstore._utils import _sha256_streaming
2829
from sigstore.dsse import DigestSet, StatementBuilder, Subject, _Statement
2930
from sigstore.dsse import Envelope as DsseEnvelope
@@ -136,6 +137,7 @@ def __init__(self: VerificationError, msg: str) -> None:
136137

137138

138139
TransparencyLogEntry = NewType("TransparencyLogEntry", dict[str, Any])
140+
Timestamp = NewType("Timestamp", bytes)
139141

140142

141143
class VerificationMaterial(BaseModel):
@@ -152,6 +154,11 @@ class VerificationMaterial(BaseModel):
152154
and certificate.
153155
"""
154156

157+
timestamps: list[Timestamp] = []
158+
"""
159+
list of RFC3161 timestamps. List may be empty if all transparency entries are rekor v1.
160+
"""
161+
155162

156163
class Attestation(BaseModel):
157164
"""Attestation object as defined in PEP 740."""
@@ -347,10 +354,16 @@ def to_bundle(self) -> Bundle:
347354
except (ValidationError, sigstore.errors.Error) as err:
348355
raise ConversionError("invalid transparency log entry") from err
349356

357+
timestamps = [
358+
decode_timestamp_response(base64.b64encode(t))
359+
for t in self.verification_material.timestamps
360+
]
361+
350362
return Bundle._from_parts( # noqa: SLF001
351363
cert=certificate,
352364
content=evp,
353365
log_entry=log_entry,
366+
signed_timestamp=timestamps,
354367
)
355368

356369
@classmethod
@@ -368,13 +381,19 @@ def from_bundle(cls, sigstore_bundle: Bundle) -> Attestation:
368381
if len(envelope.signatures) != 1:
369382
raise ConversionError(f"expected exactly one signature, got {len(envelope.signatures)}")
370383

384+
timestamps = []
385+
if sigstore_bundle.verification_material.timestamp_verification_data:
386+
ts_data = sigstore_bundle.verification_material.timestamp_verification_data
387+
timestamps = [base64.b64encode(ts.as_bytes()) for ts in ts_data.rfc3161_timestamps]
388+
371389
return cls(
372390
version=1,
373391
verification_material=VerificationMaterial(
374392
certificate=base64.b64encode(certificate),
375393
transparency_entries=[
376394
sigstore_bundle.log_entry._inner.to_dict() # noqa: SLF001
377395
],
396+
timestamps=timestamps,
378397
),
379398
envelope=Envelope(
380399
statement=base64.b64encode(envelope.payload),

test/test_impl.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ class TestAttestation:
7070
@online
7171
def test_roundtrip(self, id_token: IdentityToken) -> None:
7272
trust_config = ClientTrustConfig.staging()
73-
# Make sure we use rekor v1 until attestations are compatible with v2
74-
trust_config.force_tlog_version = 1
7573
sign_ctx = SigningContext.from_trust_config(trust_config)
7674

7775
with sign_ctx.signer(id_token) as signer:
@@ -107,8 +105,6 @@ def in_validity_period(_: IdentityToken) -> bool:
107105
monkeypatch.setattr(IdentityToken, "in_validity_period", in_validity_period)
108106

109107
trust_config = ClientTrustConfig.staging()
110-
# Make sure we use rekor v1 until attestations are compatible with v2
111-
trust_config.force_tlog_version = 1
112108
sign_ctx = SigningContext.from_trust_config(trust_config)
113109

114110
with sign_ctx.signer(id_token, cache=False) as signer:
@@ -128,8 +124,6 @@ def get_bundle(*_: Any) -> Bundle:
128124
monkeypatch.setattr(sigstore.sign.Signer, "sign_dsse", get_bundle)
129125

130126
trust_config = ClientTrustConfig.staging()
131-
# Make sure we use rekor v1 until attestations are compatible with v2
132-
trust_config.force_tlog_version = 1
133127
sign_ctx = SigningContext.from_trust_config(trust_config)
134128

135129
with pytest.raises(impl.AttestationError):

0 commit comments

Comments
 (0)