Pushing a release tag vX.Y.Z starts the attest job in .gitlab-ci.yml. It
writes a provenance document, provenance.json, signs it with
Sigstore under the identity GitLab gives that
pipeline, and stores the document and its signature bundle,
provenance.json.sigstore.json, in this project's package registry. The GitLab
release of the tag links both files. No key is involved: the signing
certificate is issued for that one job and names it, and the signature is
recorded in Sigstore's public transparency log.
Releases tagged before this job existed have no document.
- It was signed by a pipeline of
gitlab.com/cznic/sqlite(project ID 9241019), started by the push of that tag, on a GitLab-hosted runner, running the.gitlab-ci.ymlof the commit the document names. - Before signing, that pipeline checked that the tag named the commit and that
vendor.jsonagreed withgo.modand with the committedlib/andvec/(internal/vendorstamp), and it cloned thelibsqlite3andlibsqlite_veccommitsvendor.jsonnames and vendored again from them, without the cross-builds (make vendor-check). Every filevendor.jsoncovers (lib/sqlite*.go,vec/vec*.go,LICENSE-SQLITE_VEC) came back byte for byte, with no file missing or extra, and so didvendor.jsonitself apart from the Go release it records:vendorin the document is the maintainer's stamp, andbuilder.gothe Go that reproduced it. It then downloaded the module twice, once from gitlab.com and once from proxy.golang.org checked against sum.golang.org, each into an empty cache, and got the same hashes from both.observedin the document gives the time window of those checks by the runner's clock; the time of the signature is in the bundle. vendor, a copy of the release'svendor.json, names themodernc.org/libsqlite3andmodernc.org/libsqlite_veccommits and the Go toolchainlib/andvec/were vendored with.
- That the code is safe, or that the builders were green when the tag was pushed. Tags are pushed by hand; see HACKING.md.
- Who pushed the tag. Only maintainers can create tags in this project, and whoever holds a maintainer account can push one and get it attested.
- That the Go in
libsqlite3andlibsqlite_vecat those commits is a faithful transpilation of the C it comes from. The job reproduces the copy of that Go intolib/andvec/, not the transpilation behind it. - More integrity than you already have. sum.golang.org already guarantees that everyone gets the same bytes for a version. The document adds where those bytes came from.
- Anything beyond the commit itself. The tagged commit's own code runs in the
job that holds the signing identity, so a commit that changes
.gitlab-ci.ymlorattestgen/can have the job sign whatever it likes. A document is as trustworthy as the commit it names.
You need Go and cosign v3.0.4 or later, or v2.6.2 or later. Older versions are affected by GHSA-whqx-f9j3-ch6m.
V=vX.Y.Z # a release whose GitLab release links a provenance document
P=https://gitlab.com/api/v4/projects/cznic%2Fsqlite/packages/generic/attestation/$V
curl -fsSLO $P/provenance.json
curl -fsSLO $P/provenance.json.sigstore.json
(cd attestgen && go build -tags none -o ../attest .) # in a checkout of this repository
./attest -verify -tag $V provenance.json provenance.json.sigstore.json
It runs cosign verify-blob, then checks the claims in the certificate listed
below, then downloads the module through proxy.golang.org and compares its
hashes and its vendor.json with the document. Exit status 0 means every check
passed.
cosign verify-blob provenance.json \
--bundle provenance.json.sigstore.json \
--certificate-identity "https://gitlab.com/cznic/sqlite//.gitlab-ci.yml@refs/tags/$V" \
--certificate-oidc-issuer https://gitlab.com \
--use-signed-timestamps
This checks the signature, the transparency log entry, the timestamp, and that the certificate names this project's pipeline for the tag. cosign has no flags for the rest of what GitLab puts in the certificate, so read it yourself:
jq -r .verificationMaterial.certificate.rawBytes provenance.json.sigstore.json |
base64 -d | openssl x509 -inform der -noout -text
| Extension | Must be |
|---|---|
1.3.6.1.4.1.57264.1.13 |
the commit in the document |
1.3.6.1.4.1.57264.1.10 |
the same commit |
1.3.6.1.4.1.57264.1.14 |
refs/tags/ followed by the tag |
1.3.6.1.4.1.57264.1.15 |
9241019 |
1.3.6.1.4.1.57264.1.20 |
push |
1.3.6.1.4.1.57264.1.11 |
gitlab-hosted |
openssl prints each value after one or two characters of encoding.
Then compare the document with the module. Download it into an empty module
of its own, with a fresh cache and none of your Go settings, so that neither a
go.sum nor a GONOSUMDB or GOFLAGS of yours answers in place of
sum.golang.org:
D=$(mktemp -d) && cd "$D" && printf 'module scratch\n\ngo 1.25.0\n' > go.mod
env -i PATH="$PATH" HOME="$D" GOENV=off GOFLAGS=-modcacherw GOWORK=off \
GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org \
GOMODCACHE="$D/cache" GOPATH="$D/gopath" GOCACHE="$D/gocache" \
go mod download -json modernc.org/sqlite@$V
In the document, schema must be modernc.org/sqlite/attestation/v1,
module modernc.org/sqlite, version the tag, and commit the commit in the
certificate. Against the download: Sum must equal zip_h1, GoModSum must
equal gomod_h1, the SHA-256 of vendor.json in the reported Dir must equal
vendor_json_sha256, and vendor in the document must hold the same JSON as
that file. attest -verify makes all of these checks.