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..5e4371a --- /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), // mutate-exempt: how far back the start is, a test signs within it; only the end is a test's (signed past it) + }}, 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 + } + 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) + } + 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) + } + 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) + } + envSigner, err := dsse.NewEnvelopeSigner(&sigdsse.SignerAdapter{SignatureSigner: signer, Pub: key.Public()}) + if err != nil { + return nil, fmt.Errorf("fulciotest: %w", err) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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..82ba0d4 --- /dev/null +++ b/fulciotest/fulciotest_test.go @@ -0,0 +1,110 @@ +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 { + 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 + } + + 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..62d5afc 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 @@ -74,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 5741484..441c08f 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,14 +356,15 @@ 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= 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= @@ -369,12 +395,15 @@ 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= +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=