Repository navigation
Conversation
ea5aaba to
c218fa1
Compare
79adede to
47dde5e
Compare
47dde5e to
28c09b4
Compare
28c09b4 to
dc37a88
Compare
b1dc5ea to
2d3e6e9
Compare
2d3e6e9 to
e397ce2
Compare
d3c6c4a to
e0c3456
Compare
e0c3456 to
c33ac01
Compare
c33ac01 to
d6008e4
Compare
547c547 to
2f0bd9e
Compare
70d774f to
90f3bf1
Compare
90f3bf1 to
95f8ad3
Compare
c4ff175 to
e61d51f
Compare
e61d51f to
8761f5d
Compare
8761f5d to
5e81745
Compare
5e81745 to
c41eb61
Compare
sigurdm
left a comment
There was a problem hiding this comment.
The Sep 24 rewrite addresses all three points from my earlier comment — thanks. Two policy-level problems remain before this can go in, plus a handful of smaller things inline.
What the client check proves. As implemented, a passing verification means: the bundle was signed by dart-lang/setup-dart/.github/workflows/publish.yml, and the archive was built from the GitHub repository that the archive's own pubspec names. It does not establish that this repository is the one authorized for the package — that binding lives on pub.dev at publish time, and on the client a switch is a warning (and only on machines with a marker). I think that is fine for v1, but the PR description ("Package Repository Binding") and the spec text should say exactly this.
Trigger field. slsa_level only appears on the pub-dev side in dart-lang/pub-dev#9550; it should be split out of the badge PR into #9546 so the API change can land with verification.
Also note the branch is one commit behind publish-with-attestation (789afc51), so doc/repository-spec-v2.md will conflict on rebase.
| final previousProvenance = packageProvenanceFromCache(description, cache); | ||
| // Attestations are fetched when the repository says there is one, but also | ||
| // when we have seen one before: the repository must not be able to skip | ||
| // verification by simply not reporting `slsa_level`. | ||
| final expectAttestation = | ||
| versionInfo.slsaLevel != null || previousProvenance != null; |
There was a problem hiding this comment.
The marker is per package, not per version, so once any attested version has been downloaded on a machine, every unattested version of that package becomes uninstallable there: dart pub downgrade (which most dart-lang repos run in CI), a second project pinning a pre-attestation version, pub cache repair. And it only happens on machines that have the marker, so a dev box and fresh CI behave differently. The test covers the newer-version case only.
The marker needs a version dimension — e.g. record the lowest attested version seen and only require attestations for versions above it — and the lockfile provenance of the version being downloaded should feed into expectAttestation too (see below); that is the cross-machine signal CI can actually use.
| if (previousLockedProvenance != null && currentProvenance == null) { | ||
| throw PackageProvenanceException(''' | ||
| Package "$packageName" version $version is not signed with provenance. | ||
| However, previously locked versions of this package were signed by: | ||
| ${previousLockedProvenance.repository} | ||
|
|
||
| Downgrading from a signed package to an unsigned package is prohibited. | ||
| '''); | ||
| } |
There was a problem hiding this comment.
Nothing here is about the lockfile or about downgrading — the caller passes the cache marker, and the condition fires for older versions just as well as newer ones. The parameter name and the message should describe what is actually compared, and the message needs to tell the user what to do (which there currently is no way to do).
| /// Returns a description with [newProvenance], if it is not `null`. | ||
| /// | ||
| /// A known provenance is never cleared: that would silently drop the | ||
| /// repository binding of an already verified package. | ||
| ResolvedHostedDescription withProvenance(String? newProvenance) => | ||
| ResolvedHostedDescription( | ||
| description, | ||
| sha256: sha256, | ||
| provenance: newProvenance ?? provenance, | ||
| ); |
There was a problem hiding this comment.
This is the wrong way round: if the download just performed was not verified (repository stopped reporting slsa_level, no marker on this machine), keeping the old value makes pubspec.lock assert a binding this download did not establish — and hides the change from code review. Either clear it, or better, treat a lockfile provenance as "attestation expected" in _downloadAndExtract so this situation fails like the marker case does.
Adds loadTrustedRoot() and loadTrustedRootJson() in package:pub to resolve the Sigstore root of trust from: 1. Explicit override paths passed to the loader. 2. The PUB_SIGSTORE_TRUST_ROOT environment variable. 3. The built Dart SDK distribution at lib/_internal/sigstore/trusted_root.json. 4. The Dart SDK repository checkout at third_party/sigstore/trusted_root.json.
…t/upgrade - Adds `package:sigstore` dependency from labs monorepo. - Implements `PubAttestationVerifier` wrapping Sigstore attestation verifier. - Fetches and cryptographically verifies Sigstore attestations during hosted downloads in `pub get` and `pub upgrade`. - Enforces lockfile downgrade protection, SLSA provenance, digest match, and certificate roots. - Adds comprehensive unit and integration tests.
… argument, quotes)
…ed root handling - `PubAttestationVerifier.verify` takes the archive's sha256 digest (the content hash computed while the archive was downloaded) and passes it to `package:sigstore` with `isDigest: true`, instead of reading the archive back and hashing it twice more. - The attestation is fetched in its own request with its own retries, after the archive download: a failing attestation endpoint no longer re-downloads the archive on every attempt, and is reported as such (naming the endpoint, and pointing at proxies that do not forward it) instead of as a failure to find the package. A `401`/`403` from the endpoint is handled the same way. - The trusted root is loaded outside of the verification `try`: a missing `PUB_SIGSTORE_TRUST_ROOT` file fails as a local configuration error instead of being attributed to the attestation; a corrupt cached `trusted_root.json` is ignored with a warning and the built-in root is used. - `requireTaggedSignerRef` stays `false` while the publishing workflow lives on a branch; the default and the `job_workflow_sha` TODO now reference the tracking issue #4942. - repository-spec-v2.md: spell out that the attestation must be retrievable before the listing advertises it, and that proxies must forward the endpoint; drop the `Accept` header the client does not send.
|
Two follow-ups on
|
Client-side Ingestion & Cryptographic Verification on
pub get/upgradeWhen downloading packages in
HostedSource, queries the repository forGET /api/packages/<pkg>/versions/<ver>/attestationand runsPubAttestationVerifieragainst the cached or embedded Sigstoretrusted_root.json:ProvenancePolicy.enforcePolicy()blocks downgrades from signed to unsigned packages and detects repository switches across versions..tar.gzSHA-256 hash matches the in-toto subject digest in the attestation.<package>-<version>.tar.gz.trusted_root.jsonFulcio Root CAs.trusted_root.json.https://token.actions.githubusercontent.comissuer identity.pubspec.yaml's declared repository.Tests
test/get/hosted/attestation_verification_test.darttest/sigstore/verifier_test.darttest/sigstore/trusted_root_test.darttest/sigstore/lockfile_policy_test.dart