From 7adb30fbc8014cdb70cc9f1e7c103bb20b43dbb9 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Tue, 13 Oct 2026 14:37:08 -0300 Subject: [PATCH 1/2] fulciotest: a test Sigstore whose certificates carry Fulcio's extensions A verifier of a reusable builder must pin the source repository, since any repository may call a public reusable workflow and be issued its SAN. Its tests need certificates that say a source repository, which sigstore-go's VirtualSigstore does not mint. fulciotest keeps the VirtualSigstore's Rekor, CT log and TSA and brings its own Fulcio CA: SignAtTime (a hashedrekord entry, as cosign sign-blob's) and AttestAtTime (DSSE, with or without the inclusion proof), each for a URI SAN and a certificate.Extensions. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 5 +- fulciotest/fulciotest.go | 271 ++++++++++++++++++++++++++++++++++ fulciotest/fulciotest_test.go | 105 +++++++++++++ go.mod | 15 +- go.sum | 29 ++++ 5 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 fulciotest/fulciotest.go create mode 100644 fulciotest/fulciotest_test.go diff --git a/README.md b/README.md index cdd6c78..670f3e7 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ The Go the spin-stack repositories build with, and hold their code to: tools a repository runs from its go.mod (`go tool `), each a command under `cmd/`. Only `versions`, -`versions/upstream`, `trustroot`, `provenance`, `attestgate` and `sbom` are packages another -repository imports; the rest is +`versions/upstream`, `trustroot`, `provenance`, `attestgate`, `sbom` and `fulciotest` (a test +Sigstore whose certificates carry Fulcio's source-repository and build extensions, which a verifier +of a reusable builder pins) are packages another repository imports; the rest is `internal/`. Nothing here is linked into what any of them ships but versions' reading half, which spin's binaries use to read the pins they carry, and the trusted root, which what verifies a release's signatures checks them against. diff --git a/fulciotest/fulciotest.go b/fulciotest/fulciotest.go new file mode 100644 index 0000000..262d3a1 --- /dev/null +++ b/fulciotest/fulciotest.go @@ -0,0 +1,271 @@ +// Package fulciotest is a Sigstore for tests whose certificates carry what Fulcio writes into a +// GitHub Actions run's: the source repository, its ref, the build's signer and config. sigstore-go's +// VirtualSigstore mints a leaf with the issuer and nothing else, so a verifier that pins the +// source repository - which a reusable builder needs, since any repository may call one and be +// issued its SAN - has no certificate to pass in a test. This one has its own Fulcio CA and keeps +// the VirtualSigstore's Rekor, CT log and timestamp authority. +package fulciotest + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "math/big" + "net/url" + "time" + + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/rekor/pkg/pki" + "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/rekor/pkg/types/hashedrekord" + _ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" // the version types.NewProposedEntry makes + "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/testing/ca" + "github.com/sigstore/sigstore-go/pkg/tlog" + "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + sigdsse "github.com/sigstore/sigstore/pkg/signature/dsse" +) + +// Sigstore is trusted material for a verifier, and what signs and attests under it. +type Sigstore struct { + *ca.VirtualSigstore + fulcio *root.FulcioCertificateAuthority + key *ecdsa.PrivateKey +} + +// New is a Sigstore whose Fulcio is valid from five hours ago to an hour from now, as the +// VirtualSigstore's is. +func New() (*Sigstore, error) { + v, err := ca.NewVirtualSigstore() + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + rootCert, rootKey, err := ca.GenerateRootCa() + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + inter, key, err := ca.GenerateFulcioIntermediate(rootCert, rootKey) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + now := time.Now() + return &Sigstore{VirtualSigstore: v, key: key, fulcio: &root.FulcioCertificateAuthority{ + Root: rootCert, Intermediates: []*x509.Certificate{inter}, URI: "https://fulciotest.invalid", + ValidityPeriodStart: now.Add(-5 * time.Hour), ValidityPeriodEnd: now.Add(time.Hour), + }}, nil +} + +// FulcioCertificateAuthorities is this Sigstore's own Fulcio, the one that knows the extensions. +func (s *Sigstore) FulcioCertificateAuthorities() []root.CertificateAuthority { + return []root.CertificateAuthority{s.fulcio} +} + +// The extensions a leaf carries, by their OIDs: the issuer twice, as Fulcio writes it - raw in the +// deprecated OID, DER in the current one - and the rest as DER strings. +func render(e certificate.Extensions) ([]pkix.Extension, error) { + out := []pkix.Extension{{Id: certificate.OIDIssuer, Value: []byte(e.Issuer)}} //nolint:staticcheck // Fulcio still writes it, and older verifiers read only it + if e.GithubWorkflowRepository != "" { + // What cosign's --certificate-github-workflow-repository reads, which Fulcio still writes. + out = append(out, pkix.Extension{Id: certificate.OIDGitHubWorkflowRepository, Value: []byte(e.GithubWorkflowRepository)}) //nolint:staticcheck // see above + } + for _, f := range []struct { + id asn1.ObjectIdentifier + val string + }{ + {certificate.OIDIssuerV2, e.Issuer}, + {certificate.OIDBuildSignerURI, e.BuildSignerURI}, + {certificate.OIDBuildSignerDigest, e.BuildSignerDigest}, + {certificate.OIDRunnerEnvironment, e.RunnerEnvironment}, + {certificate.OIDSourceRepositoryURI, e.SourceRepositoryURI}, + {certificate.OIDSourceRepositoryDigest, e.SourceRepositoryDigest}, + {certificate.OIDSourceRepositoryRef, e.SourceRepositoryRef}, + {certificate.OIDBuildConfigURI, e.BuildConfigURI}, + {certificate.OIDBuildTrigger, e.BuildTrigger}, + } { + if f.val == "" { + continue + } + der, err := asn1.MarshalWithParams(f.val, "utf8") + if err != nil { + return nil, err // mutate-exempt: a Go string always marshals as a UTF8String + } + out = append(out, pkix.Extension{Id: f.id, Value: der}) + } + return out, nil +} + +// leaf is a certificate for san - a URI, as Fulcio's for a workflow is - carrying ext, valid from a +// minute before at to ten minutes after it, and its key. +func (s *Sigstore) leaf(san string, ext certificate.Extensions, at time.Time) (*x509.Certificate, *ecdsa.PrivateKey, error) { + u, err := url.Parse(san) + if err != nil || u.Scheme == "" { + return nil, nil, fmt.Errorf("fulciotest: %q is not a URI", san) + } + if ext.Issuer == "" { + return nil, nil, errors.New("fulciotest: a certificate with no issuer") + } + extra, err := render(ext) + if err != nil { + return nil, nil, fmt.Errorf("fulciotest: %w", err) + } + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: crypto/rand does not fail + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(at.UnixNano()), URIs: []*url.URL{u}, + NotBefore: at.Add(-time.Minute), NotAfter: at.Add(10 * time.Minute), + KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + ExtraExtensions: extra, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, s.fulcio.Intermediates[0], key.Public(), s.key) + if err != nil { + return nil, nil, fmt.Errorf("fulciotest: %w", err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: what CreateCertificate made parses + } + return cert, key, nil +} + +// AttestAtTime is an in-toto attestation of body, signed by a certificate for san with ext, logged at +// at, with the log's proof of inclusion when proof. +func (s *Sigstore) AttestAtTime(san string, ext certificate.Extensions, body []byte, at time.Time, proof bool) (verify.SignedEntity, error) { + cert, key, err := s.leaf(san, ext, at) + if err != nil { + return nil, err + } + signer, err := signature.LoadECDSASignerVerifier(key, crypto.SHA256) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a P-256 key always loads + } + envSigner, err := dsse.NewEnvelopeSigner(&sigdsse.SignerAdapter{SignatureSigner: signer, Pub: key.Public()}) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: one signer is always enough + } + env, err := envSigner.SignPayload(context.Background(), "application/vnd.in-toto+json", body) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + sig, err := base64.StdEncoding.DecodeString(env.Signatures[0].Sig) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: dsse writes standard base64 + } + entry, err := s.GenerateTlogEntry(cert, env, sig, at.Unix(), proof) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + return &entity{chain: s.chain(cert), envelope: env, entries: []*tlog.Entry{entry}}, nil +} + +// SignAtTime is a signature over artifact by a certificate for san with ext, logged at at, as +// cosign sign-blob's is: a hashedrekord entry with the log's promise of inclusion. +func (s *Sigstore) SignAtTime(san string, ext certificate.Extensions, artifact []byte, at time.Time) (verify.SignedEntity, error) { + cert, key, err := s.leaf(san, ext, at) + if err != nil { + return nil, err + } + digest := sha256.Sum256(artifact) + sig, err := ecdsa.SignASN1(rand.Reader, key, digest[:]) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: crypto/rand does not fail + } + entry, err := s.hashedRekord(cert, digest[:], sig, at.Unix()) + if err != nil { + return nil, err + } + return &entity{chain: s.chain(cert), message: bundle.NewMessageSignature(digest[:], "SHA2_256", sig), entries: []*tlog.Entry{entry}}, nil +} + +func (s *Sigstore) chain(leaf *x509.Certificate) []*x509.Certificate { + return []*x509.Certificate{leaf, s.fulcio.Intermediates[0], s.fulcio.Root} +} + +// hashedRekord is the log's entry of a signature over digest by cert, as the VirtualSigstore's own +// is: canonical, signed by its log, at index 1000. +func (s *Sigstore) hashedRekord(cert *x509.Certificate, digest, sig []byte, at int64) (*tlog.Entry, error) { + ctx := context.Background() + pem, err := cryptoutils.MarshalCertificateToPEM(cert) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a parsed certificate always encodes + } + proposed, err := types.NewProposedEntry(ctx, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), types.ArtifactProperties{ + PublicKeyBytes: [][]byte{pem}, PKIFormat: string(pki.X509), ArtifactHash: hex.EncodeToString(digest), SignatureBytes: sig, + }) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + impl, err := types.CreateVersionedEntry(proposed) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + body, err := types.CanonicalizeEntry(ctx, impl) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + logID, err := s.RekorLogID() + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: the VirtualSigstore's key always has an ID + } + const index = 1000 + set, err := s.RekorSignPayload(tlog.RekorPayload{LogID: logID, IntegratedTime: at, LogIndex: index, Body: base64.StdEncoding.EncodeToString(body)}) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + raw, err := hex.DecodeString(logID) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a log ID is hex + } + return tlog.NewEntry(body, at, index, raw, set, nil) //nolint:staticcheck // the constructor the VirtualSigstore uses for the same entry +} + +// entity is a signed thing as a verifier reads it out of a bundle. +type entity struct { + chain []*x509.Certificate + envelope *dsse.Envelope + message *bundle.MessageSignature + entries []*tlog.Entry +} + +func (e *entity) VerificationContent() (verify.VerificationContent, error) { + return bundle.NewCertificate(e.chain[0]), nil +} + +func (e *entity) SignatureContent() (verify.SignatureContent, error) { + if e.envelope != nil { + return &bundle.Envelope{Envelope: e.envelope}, nil + } + return e.message, nil +} + +func (e *entity) HasInclusionPromise() bool { return true } + +func (e *entity) HasInclusionProof() bool { + for _, t := range e.entries { + if t.HasInclusionProof() { + return true + } + } + return false +} + +func (e *entity) Timestamps() ([][]byte, error) { return nil, nil } + +func (e *entity) TlogEntries() ([]*tlog.Entry, error) { return e.entries, nil } + +func (e *entity) Version() (string, error) { return "v0.3", nil } diff --git a/fulciotest/fulciotest_test.go b/fulciotest/fulciotest_test.go new file mode 100644 index 0000000..df4f791 --- /dev/null +++ b/fulciotest/fulciotest_test.go @@ -0,0 +1,105 @@ +package fulciotest_test + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "testing" + "time" + + "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/stretchr/testify/require" + + "github.com/spin-stack/go-tools/fulciotest" +) + +const ( + issuer = "https://token.actions.githubusercontent.com" + builder = "https://github.com/spin-stack/spin-machine/.github/workflows/build-release.yml@refs/heads/main" +) + +var own = certificate.Extensions{ + Issuer: issuer, SourceRepositoryURI: "https://github.com/spin-stack/spin-machine", SourceRepositoryRef: "refs/heads/main", + BuildSignerURI: builder, BuildConfigURI: "https://github.com/spin-stack/spin-machine/.github/workflows/release.yml@refs/heads/main", + GithubWorkflowRepository: "spin-stack/spin-machine", +} + +// statement is an in-toto statement over artifact. +func statement(t *testing.T, artifact []byte) []byte { + t.Helper() + sum := sha256.Sum256(artifact) + b, err := json.Marshal(map[string]any{ + "_type": "https://in-toto.io/Statement/v1", + "subject": []map[string]any{{"name": "f", "digest": map[string]string{"sha256": hexOf(sum[:])}}}, + "predicateType": "https://slsa.dev/provenance/v1", + "predicate": map[string]any{}, + }) + require.NoError(t, err) + return b +} + +func hexOf(b []byte) string { return hex.EncodeToString(b) } + +// What a verifier pinning the source repository and the build's config takes: a signature and an +// attestation whose certificate says so. One from another repository with the builder's SAN - what +// any repository calling a public reusable builder is issued - is refused, and so is one that says +// nothing of its source. +func TestACertificateCarriesWhatFulcioWrites(t *testing.T) { + s, err := fulciotest.New() + require.NoError(t, err) + artifact := []byte("the machine") + sum := sha256.Sum256(artifact) + now := time.Now() + pinned, err := verify.NewShortCertificateIdentity(issuer, "", builder, "") + require.NoError(t, err) + pinned.SourceRepositoryURI = own.SourceRepositoryURI + pinned.BuildConfigURI = own.BuildConfigURI + verifier, err := verify.NewVerifier(s, verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1)) + require.NoError(t, err) + check := func(e verify.SignedEntity) error { + _, err := verifier.Verify(e, verify.NewPolicy(verify.WithArtifactDigest("sha256", sum[:]), verify.WithCertificateIdentity(pinned))) + return err + } + + other := own + other.SourceRepositoryURI, other.GithubWorkflowRepository = "https://github.com/someone/fork", "someone/fork" + none := certificate.Extensions{Issuer: issuer} + for name, tc := range map[string]struct { + ext certificate.Extensions + ok bool + }{ + "its own repository": {ext: own, ok: true}, + "another repository": {ext: other}, + "nothing of a source": {ext: none}, + } { + t.Run(name, func(t *testing.T) { + signed, err := s.SignAtTime(builder, tc.ext, artifact, now) + require.NoError(t, err) + attested, err := s.AttestAtTime(builder, tc.ext, statement(t, artifact), now, true) + require.NoError(t, err) + require.True(t, attested.HasInclusionProof()) + require.False(t, signed.HasInclusionProof()) + for what, e := range map[string]verify.SignedEntity{"signature": signed, "attestation": attested} { + if tc.ok { + require.NoError(t, check(e), what) + } else { + require.Error(t, check(e), what) + } + } + }) + } + + // The SAN is what it was asked for; a certificate needs an issuer and a URI to be for. + signed, err := s.SignAtTime("https://github.com/spin-stack/spin-machine/.github/workflows/release.yml@refs/heads/main", own, artifact, now) + require.NoError(t, err) + require.Error(t, check(signed)) + _, err = s.SignAtTime("not a uri", own, artifact, now) + require.Error(t, err) + _, err = s.AttestAtTime(builder, certificate.Extensions{}, statement(t, artifact), now, true) + require.Error(t, err) + // Signed at a time its certificate was not yet valid, or its Fulcio was not: refused. + late, err := s.SignAtTime(builder, own, artifact, now.Add(3*time.Hour)) + require.NoError(t, err) + require.Error(t, check(late)) +} diff --git a/go.mod b/go.mod index 070090b..dda2e35 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,9 @@ toolchain go1.27.2 require ( github.com/CycloneDX/cyclonedx-go v0.12.0 github.com/in-toto/attestation v1.2.0 + github.com/secure-systems-lab/go-securesystemslib v0.11.0 + github.com/sigstore/rekor v1.5.3 + github.com/sigstore/sigstore v1.10.8 github.com/sigstore/sigstore-go v1.3.0 github.com/stretchr/testify v1.12.1 go.yaml.in/yaml/v3 v3.0.5 @@ -23,6 +26,7 @@ require ( github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.25.5 // indirect @@ -54,17 +58,22 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/in-toto/in-toto-golang v0.11.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect + github.com/letsencrypt/boulder v0.20260309.0 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect + github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect - github.com/sigstore/rekor v1.5.3 // indirect github.com/sigstore/rekor-tiles/v2 v2.3.0 // indirect - github.com/sigstore/sigstore v1.10.8 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.3 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/theupdateframework/go-tuf/v2 v2.4.2 // indirect + github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/formats v0.1.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect diff --git a/go.sum b/go.sum index 5741484..82b3cac 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3 github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8= github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s= github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -80,6 +82,7 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= @@ -170,6 +173,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= @@ -216,6 +221,12 @@ github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7P github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b/go.mod h1:hQmNrgofl+IY/8L+n20H6E6PWBBTokdsv+q49j0QhsU= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= +github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= +github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0= @@ -224,6 +235,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= @@ -237,6 +250,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= @@ -271,6 +295,7 @@ github.com/sigstore/timestamp-authority/v2 v2.1.3 h1:Fc+LjCTfik1lh3YLkaosENfkXa3 github.com/sigstore/timestamp-authority/v2 v2.1.3/go.mod h1:myoFOKJB/u5vNTFwvBBJVkG3NnOBeIJevbfjNeasLjo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= @@ -331,6 +356,7 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= @@ -375,6 +401,9 @@ google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= From 44b4f7888e5a40bef4146cbde0eb352138ba6b64 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Tue, 13 Oct 2026 14:39:40 -0300 Subject: [PATCH 2/2] fulciotest: grpc and x/net at their fixes; exemptions that cover an edit Co-Authored-By: Claude Opus 5.5 (1M context) --- fulciotest/fulciotest.go | 24 ++++++++++++------------ fulciotest/fulciotest_test.go | 7 ++++++- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/fulciotest/fulciotest.go b/fulciotest/fulciotest.go index 262d3a1..5e4371a 100644 --- a/fulciotest/fulciotest.go +++ b/fulciotest/fulciotest.go @@ -65,7 +65,7 @@ func New() (*Sigstore, error) { now := time.Now() return &Sigstore{VirtualSigstore: v, key: key, fulcio: &root.FulcioCertificateAuthority{ Root: rootCert, Intermediates: []*x509.Certificate{inter}, URI: "https://fulciotest.invalid", - ValidityPeriodStart: now.Add(-5 * time.Hour), ValidityPeriodEnd: now.Add(time.Hour), + ValidityPeriodStart: now.Add(-5 * time.Hour), ValidityPeriodEnd: now.Add(time.Hour), // mutate-exempt: how far back the start is, a test signs within it; only the end is a test's (signed past it) }}, nil } @@ -101,7 +101,7 @@ func render(e certificate.Extensions) ([]pkix.Extension, error) { } der, err := asn1.MarshalWithParams(f.val, "utf8") if err != nil { - return nil, err // mutate-exempt: a Go string always marshals as a UTF8String + return nil, err } out = append(out, pkix.Extension{Id: f.id, Value: der}) } @@ -124,7 +124,7 @@ func (s *Sigstore) leaf(san string, ext certificate.Extensions, at time.Time) (* } key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return nil, nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: crypto/rand does not fail + return nil, nil, fmt.Errorf("fulciotest: %w", err) } tmpl := &x509.Certificate{ SerialNumber: big.NewInt(at.UnixNano()), URIs: []*url.URL{u}, @@ -138,7 +138,7 @@ func (s *Sigstore) leaf(san string, ext certificate.Extensions, at time.Time) (* } cert, err := x509.ParseCertificate(der) if err != nil { - return nil, nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: what CreateCertificate made parses + return nil, nil, fmt.Errorf("fulciotest: %w", err) } return cert, key, nil } @@ -152,11 +152,11 @@ func (s *Sigstore) AttestAtTime(san string, ext certificate.Extensions, body []b } signer, err := signature.LoadECDSASignerVerifier(key, crypto.SHA256) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a P-256 key always loads + return nil, fmt.Errorf("fulciotest: %w", err) } envSigner, err := dsse.NewEnvelopeSigner(&sigdsse.SignerAdapter{SignatureSigner: signer, Pub: key.Public()}) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: one signer is always enough + return nil, fmt.Errorf("fulciotest: %w", err) } env, err := envSigner.SignPayload(context.Background(), "application/vnd.in-toto+json", body) if err != nil { @@ -164,7 +164,7 @@ func (s *Sigstore) AttestAtTime(san string, ext certificate.Extensions, body []b } sig, err := base64.StdEncoding.DecodeString(env.Signatures[0].Sig) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: dsse writes standard base64 + return nil, fmt.Errorf("fulciotest: %w", err) } entry, err := s.GenerateTlogEntry(cert, env, sig, at.Unix(), proof) if err != nil { @@ -183,7 +183,7 @@ func (s *Sigstore) SignAtTime(san string, ext certificate.Extensions, artifact [ digest := sha256.Sum256(artifact) sig, err := ecdsa.SignASN1(rand.Reader, key, digest[:]) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: crypto/rand does not fail + return nil, fmt.Errorf("fulciotest: %w", err) } entry, err := s.hashedRekord(cert, digest[:], sig, at.Unix()) if err != nil { @@ -202,7 +202,7 @@ func (s *Sigstore) hashedRekord(cert *x509.Certificate, digest, sig []byte, at i ctx := context.Background() pem, err := cryptoutils.MarshalCertificateToPEM(cert) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a parsed certificate always encodes + return nil, fmt.Errorf("fulciotest: %w", err) } proposed, err := types.NewProposedEntry(ctx, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), types.ArtifactProperties{ PublicKeyBytes: [][]byte{pem}, PKIFormat: string(pki.X509), ArtifactHash: hex.EncodeToString(digest), SignatureBytes: sig, @@ -220,16 +220,16 @@ func (s *Sigstore) hashedRekord(cert *x509.Certificate, digest, sig []byte, at i } logID, err := s.RekorLogID() if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: the VirtualSigstore's key always has an ID + return nil, fmt.Errorf("fulciotest: %w", err) } - const index = 1000 + const index = 1000 // mutate-exempt: the entry's index in a log with no proof; the promise signs whichever it is set, err := s.RekorSignPayload(tlog.RekorPayload{LogID: logID, IntegratedTime: at, LogIndex: index, Body: base64.StdEncoding.EncodeToString(body)}) if err != nil { return nil, fmt.Errorf("fulciotest: %w", err) } raw, err := hex.DecodeString(logID) if err != nil { - return nil, fmt.Errorf("fulciotest: %w", err) // mutate-exempt: a log ID is hex + return nil, fmt.Errorf("fulciotest: %w", err) } return tlog.NewEntry(body, at, index, raw, set, nil) //nolint:staticcheck // the constructor the VirtualSigstore uses for the same entry } diff --git a/fulciotest/fulciotest_test.go b/fulciotest/fulciotest_test.go index df4f791..82ba0d4 100644 --- a/fulciotest/fulciotest_test.go +++ b/fulciotest/fulciotest_test.go @@ -58,7 +58,12 @@ func TestACertificateCarriesWhatFulcioWrites(t *testing.T) { verifier, err := verify.NewVerifier(s, verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1)) require.NoError(t, err) check := func(e verify.SignedEntity) error { - _, err := verifier.Verify(e, verify.NewPolicy(verify.WithArtifactDigest("sha256", sum[:]), verify.WithCertificateIdentity(pinned))) + result, err := verifier.Verify(e, verify.NewPolicy(verify.WithArtifactDigest("sha256", sum[:]), verify.WithCertificateIdentity(pinned))) + if err == nil { + // What cosign's --certificate-github-workflow-repository reads, beside what the policy did. + require.Equal(t, own.GithubWorkflowRepository, result.Signature.Certificate.GithubWorkflowRepository) + require.Equal(t, own.BuildSignerURI, result.Signature.Certificate.BuildSignerURI) + } return err } diff --git a/go.mod b/go.mod index dda2e35..62d5afc 100644 --- a/go.mod +++ b/go.mod @@ -83,14 +83,14 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 // indirect golang.org/x/crypto v0.57.0 // indirect golang.org/x/mod v0.41.0 // indirect - golang.org/x/net v0.59.0 // indirect + golang.org/x/net v0.60.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/telemetry v0.0.0-20260924152758-ed294f943157 // indirect golang.org/x/term v0.46.0 // indirect golang.org/x/text v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect - google.golang.org/grpc v1.82.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.83.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect ) diff --git a/go.sum b/go.sum index 82b3cac..441c08f 100644 --- a/go.sum +++ b/go.sum @@ -363,8 +363,8 @@ golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= @@ -395,10 +395,10 @@ google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgn google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=