From 5b05f450545275ad6378c26dcd8b4ea23433dea3 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Tue, 25 Jun 2024 16:00:38 -0400 Subject: [PATCH] Add user agent to online Rekor requests and TUF requests. Depends on https://github.com/theupdateframework/go-tuf/pull/642. Signed-off-by: Zach Steindler --- cmd/conformance/main.go | 9 +++++++-- cmd/sigstore-go/main.go | 11 ++++++++++- examples/sigstore-go-signing/main.go | 6 ++++++ pkg/sign/certificate.go | 4 +++- pkg/sign/timestamping.go | 14 +++----------- pkg/sign/transparency.go | 8 +++++--- pkg/tuf/client.go | 7 +++++++ pkg/verify/signed_entity.go | 13 ++++++++++++- pkg/verify/tlog.go | 9 +++++---- pkg/verify/tlog_test.go | 18 +++++++++--------- 10 files changed, 67 insertions(+), 32 deletions(-) diff --git a/cmd/conformance/main.go b/cmd/conformance/main.go index 5ce05275..332f9216 100644 --- a/cmd/conformance/main.go +++ b/cmd/conformance/main.go @@ -25,12 +25,14 @@ import ( protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/sign" "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/sigstore/sigstore-go/pkg/util" "github.com/sigstore/sigstore-go/pkg/verify" ) @@ -60,6 +62,9 @@ func getTrustedRoot(staging bool) root.TrustedMaterial { trustedRootJSON, err = os.ReadFile(*trustedRootPath) } else { opts := tuf.DefaultOptions() + fetcher := fetcher.DefaultFetcher{} + fetcher.SetHTTPUserAgent(util.ConstructUserAgent(Version)) + opts.Fetcher = &fetcher if staging { opts.Root = tuf.StagingRoot() @@ -295,7 +300,7 @@ func main() { tr := getTrustedRoot(staging) verifierConfig := []verify.VerifierOption{} - verifierConfig = append(verifierConfig, verify.WithoutAnyObserverTimestampsUnsafe(), verify.WithSignedCertificateTimestamps(1)) + verifierConfig = append(verifierConfig, verify.WithVersionString(Version), verify.WithoutAnyObserverTimestampsUnsafe(), verify.WithSignedCertificateTimestamps(1)) if len(tr.RekorLogs()) > 0 { verifierConfig = append(verifierConfig, verify.WithOnlineVerification()) } @@ -344,7 +349,7 @@ func main() { tr := getTrustedRoot(staging) verifierConfig := []verify.VerifierOption{} - verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1)) + verifierConfig = append(verifierConfig, verify.WithVersionString(Version), verify.WithSignedCertificateTimestamps(1)) // Check bundle and trusted root for signed timestamp information bundleTimestamps, err := b.Timestamps() diff --git a/cmd/sigstore-go/main.go b/cmd/sigstore-go/main.go index bb03d9a7..cd103a3c 100644 --- a/cmd/sigstore-go/main.go +++ b/cmd/sigstore-go/main.go @@ -27,13 +27,17 @@ import ( "os" "time" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" + "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/sigstore/sigstore-go/pkg/util" "github.com/sigstore/sigstore-go/pkg/verify" - "github.com/sigstore/sigstore/pkg/signature" ) +var Version string var artifact *string var artifactDigest *string var artifactDigestAlgorithm *string @@ -101,6 +105,8 @@ func run() error { identityPolicies := []verify.PolicyOption{} var artifactPolicy verify.ArtifactPolicyOption + verifierConfig = append(verifierConfig, verify.WithVersionString(Version)) + if *requireCTlog { verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1)) } @@ -129,6 +135,9 @@ func run() error { if *tufRootURL != "" { opts := tuf.DefaultOptions() opts.RepositoryBaseURL = *tufRootURL + fetcher := fetcher.DefaultFetcher{} + fetcher.SetHTTPUserAgent(util.ConstructUserAgent(Version)) + opts.Fetcher = &fetcher // Load the tuf root.json if provided, if not use public good if *tufTrustedRoot != "" { diff --git a/examples/sigstore-go-signing/main.go b/examples/sigstore-go-signing/main.go index fe7034d3..dad5c037 100644 --- a/examples/sigstore-go-signing/main.go +++ b/examples/sigstore-go-signing/main.go @@ -21,11 +21,13 @@ import ( "os" "time" + "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/sign" "github.com/sigstore/sigstore-go/pkg/tuf" + "github.com/sigstore/sigstore-go/pkg/util" ) var Version string @@ -83,9 +85,13 @@ func main() { opts := sign.BundleOptions{} // Get trusted_root.json + fetcher := fetcher.DefaultFetcher{} + fetcher.SetHTTPUserAgent(util.ConstructUserAgent(Version)) + tufOptions := &tuf.Options{ Root: tuf.StagingRoot(), RepositoryBaseURL: tuf.StagingMirror, + Fetcher: &fetcher, } tufClient, err := tuf.New(tufOptions) if err != nil { diff --git a/pkg/sign/certificate.go b/pkg/sign/certificate.go index 269be029..9b897101 100644 --- a/pkg/sign/certificate.go +++ b/pkg/sign/certificate.go @@ -27,6 +27,8 @@ import ( "net/http" "strings" "time" + + "github.com/sigstore/sigstore-go/pkg/util" ) type CertificateProviderOptions struct { @@ -169,7 +171,7 @@ func (f *Fulcio) GetCertificate(ctx context.Context, keypair Keypair, opts *Cert } request.Header.Add("Authorization", "Bearer "+opts.IDToken) request.Header.Add("Content-Type", "application/json") - request.Header.Add("User-Agent", constructUserAgent(f.options.LibraryVersion)) + request.Header.Add("User-Agent", util.ConstructUserAgent(f.options.LibraryVersion)) response, err = f.client.Do(request) if err != nil { diff --git a/pkg/sign/timestamping.go b/pkg/sign/timestamping.go index ce85b34d..5c2b8b58 100644 --- a/pkg/sign/timestamping.go +++ b/pkg/sign/timestamping.go @@ -26,6 +26,8 @@ import ( "time" "github.com/digitorus/timestamp" + + "github.com/sigstore/sigstore-go/pkg/util" ) type TimestampAuthorityOptions struct { @@ -83,7 +85,7 @@ func (ta *TimestampAuthority) GetTimestamp(ctx context.Context, signature []byte return nil, err } request.Header.Add("Content-Type", "application/timestamp-query") - request.Header.Add("User-Agent", constructUserAgent(ta.options.LibraryVersion)) + request.Header.Add("User-Agent", util.ConstructUserAgent(ta.options.LibraryVersion)) response, err = ta.client.Do(request) if err != nil { @@ -122,13 +124,3 @@ func (ta *TimestampAuthority) GetTimestamp(ctx context.Context, signature []byte return body, nil } - -func constructUserAgent(version string) string { - userAgent := "sigstore-go" - if version != "" { - userAgent += "/" - userAgent += version - } - - return userAgent -} diff --git a/pkg/sign/transparency.go b/pkg/sign/transparency.go index dcc45560..8f8a1a0f 100644 --- a/pkg/sign/transparency.go +++ b/pkg/sign/transparency.go @@ -31,11 +31,13 @@ import ( "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/dsse" "github.com/sigstore/rekor/pkg/types/hashedrekord" - "github.com/sigstore/rekor/pkg/util" + rekorUtil "github.com/sigstore/rekor/pkg/util" // To initialize rekor types _ "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" _ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" + + "github.com/sigstore/sigstore-go/pkg/util" ) type RekorClient interface { @@ -105,7 +107,7 @@ func (r *Rekor) GetTransparencyLogEntry(pubKeyPEM []byte, b *protobundle.Bundle) artifactProperties.PKIFormat = string(pki.X509) artifactProperties.SignatureBytes = messageSignature.Signature - artifactProperties.ArtifactHash = util.PrefixSHA(hexDigest) + artifactProperties.ArtifactHash = rekorUtil.PrefixSHA(hexDigest) var err error proposedEntry, err = hashedrekordType.CreateProposedEntry(context.TODO(), "", artifactProperties) @@ -126,7 +128,7 @@ func (r *Rekor) GetTransparencyLogEntry(pubKeyPEM []byte, b *protobundle.Bundle) params.SetProposedEntry(proposedEntry) if r.options.Client == nil { - client, err := client.GetRekorClient(r.options.BaseURL, client.WithUserAgent(constructUserAgent(r.options.LibraryVersion)), client.WithRetryCount(r.options.Retries)) + client, err := client.GetRekorClient(r.options.BaseURL, client.WithUserAgent(util.ConstructUserAgent(r.options.LibraryVersion)), client.WithRetryCount(r.options.Retries)) if err != nil { return err } diff --git a/pkg/tuf/client.go b/pkg/tuf/client.go index f655f540..2f37962d 100644 --- a/pkg/tuf/client.go +++ b/pkg/tuf/client.go @@ -21,7 +21,10 @@ import ( "time" "github.com/theupdateframework/go-tuf/v2/metadata/config" + "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" "github.com/theupdateframework/go-tuf/v2/metadata/updater" + + "github.com/sigstore/sigstore-go/pkg/util" ) // Client is a Sigstore TUF client @@ -56,6 +59,10 @@ func New(opts *Options) (*Client, error) { if opts.Fetcher != nil { c.cfg.Fetcher = opts.Fetcher + } else { + fetcher := fetcher.DefaultFetcher{} + fetcher.SetHTTPUserAgent(util.ConstructUserAgent("")) + c.cfg.Fetcher = &fetcher } // Upon client creation, we may not perform a full TUF update, diff --git a/pkg/verify/signed_entity.go b/pkg/verify/signed_entity.go index 45bc014d..90046de5 100644 --- a/pkg/verify/signed_entity.go +++ b/pkg/verify/signed_entity.go @@ -70,6 +70,8 @@ type VerifierConfig struct { // nolint: revive // rather than a provided signed or log timestamp. Most workflows will // not use this option weDoNotExpectAnyObserverTimestamps bool + // optional sigstore-go version to use in user agent for outbound requests + version string } type VerifierOption func(*VerifierConfig) error @@ -202,6 +204,15 @@ func WithoutAnyObserverTimestampsUnsafe() VerifierOption { } } +// WithVersionString is used to optionally specify a specific version of +// sigstore-go, to use in user agent for outbound requests +func WithVersionString(version string) VerifierOption { + return func(c *VerifierConfig) error { + c.version = version + return nil + } +} + func (c *VerifierConfig) Validate() error { if !c.requireObserverTimestamps && !c.weExpectSignedTimestamps && !c.requireIntegratedTimestamps && !c.weDoNotExpectAnyObserverTimestamps { return errors.New("when initializing a new SignedEntityVerifier, you must specify at least one of " + @@ -616,7 +627,7 @@ func (v *SignedEntityVerifier) VerifyTransparencyLogInclusion(entity SignedEntit if v.config.weExpectTlogEntries { // log timestamps should be verified if with WithIntegratedTimestamps or WithObserverTimestamps is used verifiedTlogTimestamps, err := VerifyArtifactTransparencyLog(entity, v.trustedMaterial, v.config.tlogEntriesThreshold, - v.config.requireIntegratedTimestamps || v.config.requireObserverTimestamps, v.config.performOnlineVerification) + v.config.requireIntegratedTimestamps || v.config.requireObserverTimestamps, v.config.performOnlineVerification, v.config.version) if err != nil { return nil, err } diff --git a/pkg/verify/tlog.go b/pkg/verify/tlog.go index 6769b3bc..ff125d26 100644 --- a/pkg/verify/tlog.go +++ b/pkg/verify/tlog.go @@ -32,6 +32,7 @@ import ( "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/tlog" + "github.com/sigstore/sigstore-go/pkg/util" ) // VerifyArtifactTransparencyLog verifies that the given entity has been logged @@ -41,7 +42,7 @@ import ( // that must be verified. // // If online is true, the log entry is verified against the Rekor server. -func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.TrustedMaterial, logThreshold int, trustIntegratedTime, online bool) ([]time.Time, error) { //nolint:revive +func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.TrustedMaterial, logThreshold int, trustIntegratedTime, online bool, version string) ([]time.Time, error) { //nolint:revive entries, err := entity.TlogEntries() if err != nil { return nil, err @@ -120,7 +121,7 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru continue } - client, err := getRekorClient(tlogVerifier.BaseURL) + client, err := getRekorClient(tlogVerifier.BaseURL, version) if err != nil { return nil, err } @@ -198,8 +199,8 @@ func getVerifier(publicKey crypto.PublicKey, hashFunc crypto.Hash) (*signature.V return &verifier, nil } -func getRekorClient(baseURL string) (*rekorGeneratedClient.Rekor, error) { - client, err := rekorClient.GetRekorClient(baseURL) +func getRekorClient(baseURL, version string) (*rekorGeneratedClient.Rekor, error) { + client, err := rekorClient.GetRekorClient(baseURL, rekorClient.WithUserAgent(util.ConstructUserAgent(version))) if err != nil { return nil, err } diff --git a/pkg/verify/tlog_test.go b/pkg/verify/tlog_test.go index 3ff4845c..ae708712 100644 --- a/pkg/verify/tlog_test.go +++ b/pkg/verify/tlog_test.go @@ -36,12 +36,12 @@ func TestTlogVerifier(t *testing.T) { assert.NoError(t, err) var ts []time.Time - ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false) + ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false, "") assert.NoError(t, err) // 1 verified timestamp assert.Len(t, ts, 1) - ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, false, false) + ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, false, false, "") assert.NoError(t, err) // 0 verified timestamps, since integrated timestamps are ignored assert.Len(t, ts, 0) @@ -49,7 +49,7 @@ func TestTlogVerifier(t *testing.T) { virtualSigstore2, err := ca.NewVirtualSigstore() assert.NoError(t, err) - _, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore2, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore2, 1, true, false, "") assert.Error(t, err) // different sigstore instance should fail to verify // Attempt to use tlog with integrated time outside certificate validity. @@ -59,7 +59,7 @@ func TestTlogVerifier(t *testing.T) { entity, err = virtualSigstore.AttestAtTime("foo@fighters.com", "issuer", statement, time.Now().Add(30*time.Minute)) assert.NoError(t, err) - _, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false, "") assert.Error(t, err) } @@ -96,11 +96,11 @@ func TestIgnoredTLogEntries(t *testing.T) { assert.NoError(t, err) // success: entry that cannot be verified is ignored - _, err = verify.VerifyArtifactTransparencyLog(&oneTrustedOneUntrustedLogEntry{entity, untrustedEntity}, virtualSigstore, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(&oneTrustedOneUntrustedLogEntry{entity, untrustedEntity}, virtualSigstore, 1, true, false, "") assert.NoError(t, err) // failure: threshold of 2 is not met since 1 untrusted entry is ignored - _, err = verify.VerifyArtifactTransparencyLog(&oneTrustedOneUntrustedLogEntry{entity, untrustedEntity}, virtualSigstore, 2, true, false) + _, err = verify.VerifyArtifactTransparencyLog(&oneTrustedOneUntrustedLogEntry{entity, untrustedEntity}, virtualSigstore, 2, true, false, "") assert.Error(t, err) } @@ -138,7 +138,7 @@ func TestInvalidTLogEntries(t *testing.T) { assert.NoError(t, err) // failure: threshold of 1 is not met with invalid entry - _, err = verify.VerifyArtifactTransparencyLog(&invalidTLogEntity{entity}, virtualSigstore, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(&invalidTLogEntity{entity}, virtualSigstore, 1, true, false, "") assert.Error(t, err) if err.Error() != "entry must contain an inclusion proof and/or promise" { t.Errorf("expected error with missing proof/promises, got: %v", err.Error()) @@ -162,7 +162,7 @@ func TestNoTLogEntries(t *testing.T) { assert.NoError(t, err) // failure: threshold of 1 is not met with no entries - _, err = verify.VerifyArtifactTransparencyLog(&noTLogEntity{entity}, virtualSigstore, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(&noTLogEntity{entity}, virtualSigstore, 1, true, false, "") assert.Error(t, err) if !strings.Contains(err.Error(), "not enough verified log entries from transparency log") { t.Errorf("expected error with timestamp threshold, got: %v", err.Error()) @@ -190,6 +190,6 @@ func TestDuplicateTlogEntries(t *testing.T) { entity, err := virtualSigstore.Attest("foofighters@example.com", "issuer", statement) assert.NoError(t, err) - _, err = verify.VerifyArtifactTransparencyLog(&dupTlogEntity{entity}, virtualSigstore, 1, true, false) + _, err = verify.VerifyArtifactTransparencyLog(&dupTlogEntity{entity}, virtualSigstore, 1, true, false, "") assert.Error(t, err) // duplicate tlog entries should fail to verify }