From d15ed74a43a18f53b43096643203ddf264f76063 Mon Sep 17 00:00:00 2001 From: Hayden B Date: Thu, 16 Feb 2023 09:00:04 -0800 Subject: [PATCH] Support non-Sigstore TSA requests (#2708) * Support non-Sigstore TSA requests This changes the client to instead simply exchange a TSQ for a TSR given a URL to the API. This will mean that for clients using timestamping currently, they will need to update their Cosign calls to use the full path (e.g. tsa.sigstore/api/v1/timestamp) Fixes https://github.com/sigstore/cosign/issues/2704 Signed-off-by: Hayden Blauzvern * Address comments Signed-off-by: Hayden Blauzvern --------- Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/attest/attest.go | 9 +- cmd/cosign/cli/attest/attest_blob.go | 8 +- cmd/cosign/cli/options/attest.go | 2 +- cmd/cosign/cli/options/attest_blob.go | 2 +- cmd/cosign/cli/options/policy.go | 2 +- cmd/cosign/cli/options/sign.go | 2 +- cmd/cosign/cli/options/signblob.go | 2 +- cmd/cosign/cli/policy_init.go | 8 +- cmd/cosign/cli/sign/sign.go | 9 +- cmd/cosign/cli/sign/sign_blob.go | 9 +- cmd/cosign/cli/verify/verify_blob_test.go | 34 ++++---- doc/cosign_attest-blob.md | 2 +- doc/cosign_attest.md | 2 +- doc/cosign_policy_sign.md | 2 +- doc/cosign_sign-blob.md | 2 +- doc/cosign_sign.md | 2 +- internal/pkg/cosign/tsa/client/client.go | 83 +++++++++++++++++++ .../pkg/cosign/tsa/mock/mock_tsa_client.go | 70 ++++------------ internal/pkg/cosign/tsa/signer.go | 34 ++------ pkg/cosign/verify_test.go | 19 +++-- test/e2e_test.go | 18 ++-- 21 files changed, 162 insertions(+), 159 deletions(-) create mode 100644 internal/pkg/cosign/tsa/client/client.go diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 98edd1c61f0..2a0aab6beea 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -32,6 +32,7 @@ import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + tsaclient "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" @@ -45,7 +46,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" - tsaclient "github.com/sigstore/timestamp-authority/pkg/client" ) type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error) @@ -173,13 +173,8 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) } if c.KeyOpts.TSAServerURL != "" { - clientTSA, err := tsaclient.GetTimestampClient(c.KeyOpts.TSAServerURL) - if err != nil { - return fmt.Errorf("failed to create TSA client: %w", err) - } - // Here we get the response from the timestamped authority server - responseBytes, err := tsa.GetTimestampedSignature(signedPayload, clientTSA) + responseBytes, err := tsa.GetTimestampedSignature(signedPayload, tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL)) if err != nil { return err } diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index ec52a3d2319..2946ea1ca1f 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -34,6 +34,7 @@ import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" @@ -42,7 +43,6 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" - tsaclient "github.com/sigstore/timestamp-authority/pkg/client" ) // nolint @@ -145,11 +145,7 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error var rfc3161Timestamp *cbundle.RFC3161Timestamp if c.TSAServerURL != "" { - clientTSA, err := tsaclient.GetTimestampClient(c.TSAServerURL) - if err != nil { - return fmt.Errorf("failed to create TSA client: %w", err) - } - respBytes, err := tsa.GetTimestampedSignature(sig, clientTSA) + respBytes, err := tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL)) if err != nil { return err } diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go index 9c480f7da78..cb1a2756926 100644 --- a/cmd/cosign/cli/options/attest.go +++ b/cmd/cosign/cli/options/attest.go @@ -81,5 +81,5 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) { "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none") + "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") } diff --git a/cmd/cosign/cli/options/attest_blob.go b/cmd/cosign/cli/options/attest_blob.go index b724d3cf898..e960884af74 100644 --- a/cmd/cosign/cli/options/attest_blob.go +++ b/cmd/cosign/cli/options/attest_blob.go @@ -93,7 +93,7 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none") + "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "", "path to an RFC 3161 timestamp bundle FILE") diff --git a/cmd/cosign/cli/options/policy.go b/cmd/cosign/cli/options/policy.go index 02be0da89ab..4ecdc9f35c1 100644 --- a/cmd/cosign/cli/options/policy.go +++ b/cmd/cosign/cli/options/policy.go @@ -86,7 +86,7 @@ func (o *PolicySignOptions) AddFlags(cmd *cobra.Command) { "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none") + "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") o.Registry.AddFlags(cmd) o.Fulcio.AddFlags(cmd) diff --git a/cmd/cosign/cli/options/sign.go b/cmd/cosign/cli/options/sign.go index 6e137333d4a..81016c7587c 100644 --- a/cmd/cosign/cli/options/sign.go +++ b/cmd/cosign/cli/options/sign.go @@ -100,7 +100,7 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none") + "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, "issue a code signing certificate from Fulcio, even if a key is provided") diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 654c7ff8209..99c1a473a52 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -78,7 +78,7 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none") + "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "write the RFC3161 timestamp to a file") diff --git a/cmd/cosign/cli/policy_init.go b/cmd/cosign/cli/policy_init.go index 4fde1c4c91a..ef5a0e31c59 100644 --- a/cmd/cosign/cli/policy_init.go +++ b/cmd/cosign/cli/policy_init.go @@ -38,8 +38,8 @@ import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/cmd/cosign/cli/upload" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/sigstore/pkg/cryptoutils" - tsaclient "github.com/sigstore/timestamp-authority/pkg/client" "github.com/sigstore/cosign/v2/pkg/cosign" cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" @@ -268,12 +268,8 @@ func signPolicy() *cobra.Command { } if o.TSAServerURL != "" { - clientTSA, err := tsaclient.GetTimestampClient(o.TSAServerURL) - if err != nil { - return fmt.Errorf("failed to create TSA client: %w", err) - } // Here we get the response from the timestamped authority server - if _, err := tsa.GetTimestampedSignature(signed.Signed, clientTSA); err != nil { + if _, err := tsa.GetTimestampedSignature(signed.Signed, client.NewTSAClient(o.TSAServerURL)); err != nil { return err } } diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 939ac4dc44d..ab7e90535ec 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -41,6 +41,7 @@ import ( ipayload "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" irekor "github.com/sigstore/cosign/v2/internal/pkg/cosign/rekor" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" @@ -55,7 +56,6 @@ import ( "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" sigPayload "github.com/sigstore/sigstore/pkg/signature/payload" - tsaclient "github.com/sigstore/timestamp-authority/pkg/client" // Loads OIDC providers _ "github.com/sigstore/cosign/v2/pkg/providers/all" @@ -237,12 +237,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti } if ko.TSAServerURL != "" { - clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL) - if err != nil { - return fmt.Errorf("failed to create TSA client: %w", err) - } - - s = tsa.NewSigner(s, clientTSA) + s = tsa.NewSigner(s, client.NewTSAClient(ko.TSAServerURL)) } shouldUpload, err := ShouldUploadToTlog(ctx, ko, digest, tlogUpload) if err != nil { diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index 1cde5435b19..231d808d6aa 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -25,8 +25,8 @@ import ( "path/filepath" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" - tsaclient "github.com/sigstore/timestamp-authority/pkg/client" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" @@ -77,12 +77,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string return nil, fmt.Errorf("timestamp output path must be set") } - clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL) - if err != nil { - return nil, fmt.Errorf("failed to create TSA client: %w", err) - } - - respBytes, err := tsa.GetTimestampedSignature(sig, clientTSA) + respBytes, err := tsa.GetTimestampedSignature(sig, client.NewTSAClient(ko.TSAServerURL)) if err != nil { return nil, err } diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index 6a891c15099..fab70bc36a5 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -56,7 +56,6 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" - "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp" ) func TestSignaturesRef(t *testing.T) { @@ -195,38 +194,36 @@ func TestVerifyBlob(t *testing.T) { if err != nil { t.Fatal(err) } - tsChain, err := tsaClient.Timestamp.GetTimestampCertChain(nil) + certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } expiredTSACertChainPath := filepath.Join(td, "exptsacertchain.pem") - if err := os.WriteFile(expiredTSACertChainPath, []byte(tsChain.Payload), 0644); err != nil { + if err := os.WriteFile(expiredTSACertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } - var tsRespBytes bytes.Buffer - _, err = tsaClient.Timestamp.GetTimestampResponse(×tamp.GetTimestampResponseParams{}, &tsRespBytes) + tsr, err := tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } - rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsRespBytes.Bytes()} + rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} expiredTSPath := writeTimestampFile(t, td, rfc3161Timestamp, "expiredrfc3161TS.json") tsaClient, err = mock.NewTSAClient(unexpiredTSAOpts) if err != nil { t.Fatal(err) } - tsRespBytes.Reset() - _, err = tsaClient.Timestamp.GetTimestampResponse(×tamp.GetTimestampResponseParams{}, &tsRespBytes) + tsr, err = tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } - rfc3161Timestamp = &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsRespBytes.Bytes()} + rfc3161Timestamp = &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} unexpiredTSPath := writeTimestampFile(t, td, rfc3161Timestamp, "unexpiredrfc3161TS.json") - tsChain, err = tsaClient.Timestamp.GetTimestampCertChain(nil) + certChainPEM, err = cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } unexpiredTSACertChainPath := filepath.Join(td, "unexptsacertchain.pem") - if err := os.WriteFile(unexpiredTSACertChainPath, []byte(tsChain.Payload), 0644); err != nil { + if err := os.WriteFile(unexpiredTSACertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } @@ -1073,20 +1070,19 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { if err != nil { t.Fatal(err) } - tsChain, err := tsaClient.Timestamp.GetTimestampCertChain(nil) + certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } tsaCertChainPath := filepath.Join(keyless.td, "tsacertchain.pem") - if err := os.WriteFile(tsaCertChainPath, []byte(tsChain.Payload), 0644); err != nil { + if err := os.WriteFile(tsaCertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } - var tsRespBytes bytes.Buffer - _, err = tsaClient.Timestamp.GetTimestampResponse(×tamp.GetTimestampResponseParams{}, &tsRespBytes) + tsr, err := tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } - rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsRespBytes.Bytes()} + rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} tsPath := writeTimestampFile(t, keyless.td, rfc3161Timestamp, "rfc3161TS.json") entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) diff --git a/doc/cosign_attest-blob.md b/doc/cosign_attest-blob.md index 599b3277e6e..b3989a636c3 100644 --- a/doc/cosign_attest-blob.md +++ b/doc/cosign_attest-blob.md @@ -53,7 +53,7 @@ cosign attest-blob [flags] --rfc3161-timestamp-bundle string path to an RFC 3161 timestamp bundle FILE --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) - --timestamp-server-url string url to the Timestamp RFC3161 server, default none + --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --type string specify a predicate type (slsaprovenance|link|spdx|spdxjson|cyclonedx|vuln|custom) or an URI (default "custom") -y, --yes skip confirmation prompts for non-destructive operations diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md index d50c8030cda..babeae6ff08 100644 --- a/doc/cosign_attest.md +++ b/doc/cosign_attest.md @@ -63,7 +63,7 @@ cosign attest [flags] --replace --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) - --timestamp-server-url string url to the Timestamp RFC3161 server, default none + --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --type string specify a predicate type (slsaprovenance|link|spdx|spdxjson|cyclonedx|vuln|custom) or an URI (default "custom") -y, --yes skip confirmation prompts for non-destructive operations diff --git a/doc/cosign_policy_sign.md b/doc/cosign_policy_sign.md index 9e1b4d1460f..1b1ce44c390 100644 --- a/doc/cosign_policy_sign.md +++ b/doc/cosign_policy_sign.md @@ -32,7 +32,7 @@ cosign policy sign [flags] --oidc-redirect-url string [EXPERIMENTAL] OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --out string output policy locally (default "o") --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --timestamp-server-url string url to the Timestamp RFC3161 server, default none + --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) -y, --yes skip confirmation prompts for non-destructive operations ``` diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md index 6a3b7f09e74..e889d3f60be 100644 --- a/doc/cosign_sign-blob.md +++ b/doc/cosign_sign-blob.md @@ -54,7 +54,7 @@ cosign sign-blob [flags] --rfc3161-timestamp string write the RFC3161 timestamp to a file --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) - --timestamp-server-url string url to the Timestamp RFC3161 server, default none + --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) -y, --yes skip confirmation prompts for non-destructive operations ``` diff --git a/doc/cosign_sign.md b/doc/cosign_sign.md index 9000b06322d..f1a17d7cab4 100644 --- a/doc/cosign_sign.md +++ b/doc/cosign_sign.md @@ -94,7 +94,7 @@ cosign sign [flags] --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) - --timestamp-server-url string url to the Timestamp RFC3161 server, default none + --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --upload whether to upload the signature (default true) -y, --yes skip confirmation prompts for non-destructive operations diff --git a/internal/pkg/cosign/tsa/client/client.go b/internal/pkg/cosign/tsa/client/client.go new file mode 100644 index 00000000000..380f270bf54 --- /dev/null +++ b/internal/pkg/cosign/tsa/client/client.go @@ -0,0 +1,83 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/digitorus/timestamp" + "github.com/pkg/errors" +) + +// TimestampAuthorityClient should be implemented by clients that want to request timestamp responses +type TimestampAuthorityClient interface { + GetTimestampResponse(tsq []byte) ([]byte, error) +} + +// TimestampAuthorityClient provides a client to query RFC 3161 timestamp authorities +type TimestampAuthorityClientImpl struct { + TimestampAuthorityClient + + // URL is the path to the API to request timestamp responses + URL string + + // Timeout is the request timeout + Timeout time.Duration +} + +// GetTimestampResponse sends a timestamp query to a timestamp authority, returning a timestamp response. +// The query and response are defined by RFC 3161. +func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, error) { + client := http.Client{ + Timeout: t.Timeout, + } + req, err := http.NewRequest("POST", t.URL, bytes.NewReader(tsq)) + if err != nil { + return nil, errors.Wrap(err, "error creating HTTP request") + } + req.Header.Set("Content-Type", "application/timestamp-query") + + tsr, err := client.Do(req) + if err != nil { + return nil, errors.Wrap(err, "error making request to timestamp authority") + } + if tsr.StatusCode != 200 && tsr.StatusCode != 201 { + return nil, fmt.Errorf("request to timestamp authority failed with status code %d", tsr.StatusCode) + } + + resp, err := io.ReadAll(tsr.Body) + if err != nil { + return nil, errors.Wrap(err, "error reading timestamp response") + } + + // validate that the timestamp response is parseable + ts, err := timestamp.ParseResponse(resp) + if err != nil { + return nil, err + } + + fmt.Fprintln(os.Stderr, "Timestamp fetched with time: ", ts.Time) + + return resp, nil +} + +func NewTSAClient(url string) *TimestampAuthorityClientImpl { + return &TimestampAuthorityClientImpl{URL: url, Timeout: 10 * time.Second} +} diff --git a/internal/pkg/cosign/tsa/mock/mock_tsa_client.go b/internal/pkg/cosign/tsa/mock/mock_tsa_client.go index cb708ba9a61..54d91cff16d 100644 --- a/internal/pkg/cosign/tsa/mock/mock_tsa_client.go +++ b/internal/pkg/cosign/tsa/mock/mock_tsa_client.go @@ -15,24 +15,19 @@ package mock import ( - "bytes" "crypto" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/asn1" - "fmt" - "io" "time" - "github.com/go-openapi/runtime" "github.com/pkg/errors" "github.com/digitorus/timestamp" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" - "github.com/sigstore/timestamp-authority/pkg/generated/client" - ts "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp" "github.com/sigstore/timestamp-authority/pkg/signer" ) @@ -41,11 +36,12 @@ import ( // Time can be provided in the initializer, or defaults to time.Now(). // All other timestamp parameters are hardcoded. type TSAClient struct { - Signer crypto.Signer - CertChain []*x509.Certificate - CertChainPEM string - Time time.Time - Message []byte + client.TimestampAuthorityClient + + Signer crypto.Signer + CertChain []*x509.Certificate + Time time.Time + Message []byte } // TSAClientOptions provide customization for the mock TSA client. @@ -58,7 +54,7 @@ type TSAClientOptions struct { Signer crypto.Signer } -func NewTSAClient(o TSAClientOptions) (*client.TimestampAuthority, error) { +func NewTSAClient(o TSAClientOptions) (*TSAClient, error) { sv := o.Signer if sv == nil { var err error @@ -71,37 +67,21 @@ func NewTSAClient(o TSAClientOptions) (*client.TimestampAuthority, error) { if err != nil { return nil, errors.Wrap(err, "generating timestamping cert chain") } - certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(certChain) - if err != nil { - return nil, fmt.Errorf("marshal certificates to PEM: %w", err) - } - return &client.TimestampAuthority{ - Timestamp: &TSAClient{ - Signer: sv, - CertChain: certChain, - CertChainPEM: string(certChainPEM), - Time: o.Time, - Message: o.Message, - }, + return &TSAClient{ + Signer: sv, + CertChain: certChain, + Time: o.Time, + Message: o.Message, }, nil } -func (c *TSAClient) GetTimestampCertChain(_ *ts.GetTimestampCertChainParams, _ ...ts.ClientOption) (*ts.GetTimestampCertChainOK, error) { - return &ts.GetTimestampCertChainOK{Payload: c.CertChainPEM}, nil -} - -func (c *TSAClient) GetTimestampResponse(params *ts.GetTimestampResponseParams, w io.Writer, _ ...ts.ClientOption) (*ts.GetTimestampResponseCreated, error) { +func (c *TSAClient) GetTimestampResponse(tsq []byte) ([]byte, error) { var hashAlg crypto.Hash var hashedMessage []byte - if params.Request != nil { - requestBytes, err := io.ReadAll(params.Request) - if err != nil { - return nil, err - } - - req, err := timestamp.ParseRequest(requestBytes) + if tsq != nil { + req, err := timestamp.ParseRequest(tsq) if err != nil { return nil, err } @@ -137,21 +117,5 @@ func (c *TSAClient) GetTimestampResponse(params *ts.GetTimestampResponseParams, tsStruct.Time = c.Time } - resp, err := tsStruct.CreateResponse(c.CertChain[0], c.Signer) - if err != nil { - return nil, err - } - - // write response to provided buffer and payload - if w != nil { - _, err := w.Write(resp) - if err != nil { - return nil, err - } - } - return &ts.GetTimestampResponseCreated{Payload: bytes.NewBuffer(resp)}, nil -} - -func (c *TSAClient) SetTransport(transport runtime.ClientTransport) { - // nothing to do + return tsStruct.CreateResponse(c.CertChain[0], c.Signer) } diff --git a/internal/pkg/cosign/tsa/signer.go b/internal/pkg/cosign/tsa/signer.go index 10ac6fd748c..99c05402156 100644 --- a/internal/pkg/cosign/tsa/signer.go +++ b/internal/pkg/cosign/tsa/signer.go @@ -18,57 +18,37 @@ import ( "bytes" "context" "crypto" - "fmt" "io" - "os" "strconv" "strings" - "time" "github.com/digitorus/timestamp" + "github.com/pkg/errors" "github.com/sigstore/cosign/v2/internal/pkg/cosign" + "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/sigstore/pkg/cryptoutils" - tsaclient "github.com/sigstore/timestamp-authority/pkg/generated/client" - ts "github.com/sigstore/timestamp-authority/pkg/generated/client/timestamp" ) // GetTimestampedSignature queries a timestamp authority to fetch an RFC3161 timestamp. sigBytes is an // opaque blob, but is typically a signature over an artifact. -func GetTimestampedSignature(sigBytes []byte, tsaClient *tsaclient.TimestampAuthority) ([]byte, error) { +func GetTimestampedSignature(sigBytes []byte, tsaClient client.TimestampAuthorityClient) ([]byte, error) { requestBytes, err := createTimestampAuthorityRequest(sigBytes, crypto.SHA256, "") if err != nil { - return nil, err + return nil, errors.Wrap(err, "error creating timestamp request") } - params := ts.NewGetTimestampResponseParams() - params.SetTimeout(time.Second * 10) - params.Request = io.NopCloser(bytes.NewReader(requestBytes)) - - var respBytes bytes.Buffer - _, err = tsaClient.Timestamp.GetTimestampResponse(params, &respBytes) - if err != nil { - return nil, fmt.Errorf("unable to get the response: %w", err) - } - - // validate that timestamp is parseable - ts, err := timestamp.ParseResponse(respBytes.Bytes()) - if err != nil { - return nil, err - } - - fmt.Fprintln(os.Stderr, "Timestamp fetched with time: ", ts.Time) - return respBytes.Bytes(), nil + return tsaClient.GetTimestampResponse(requestBytes) } // signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle` type signerWrapper struct { inner cosign.Signer - tsaClient *tsaclient.TimestampAuthority + tsaClient client.TimestampAuthorityClient } var _ cosign.Signer = (*signerWrapper)(nil) @@ -126,7 +106,7 @@ func createTimestampAuthorityRequest(artifactBytes []byte, hash crypto.Hash, pol } // NewSigner returns a `cosign.Signer` which uploads the signature to a TSA -func NewSigner(inner cosign.Signer, tsaClient *tsaclient.TimestampAuthority) cosign.Signer { +func NewSigner(inner cosign.Signer, tsaClient client.TimestampAuthorityClient) cosign.Signer { return &signerWrapper{ inner: inner, tsaClient: tsaClient, diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 3141516bf41..69424d9792f 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -536,12 +536,12 @@ func TestVerifyImageSignatureWithSigVerifierAndTSA(t *testing.T) { payloadSigner := payload.NewSigner(sv) testSigner := tsa.NewSigner(payloadSigner, client) - chain, err := client.Timestamp.GetTimestampCertChain(nil) + certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } - leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain([]byte(chain.Payload)) + leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } @@ -582,12 +582,12 @@ func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) { payloadSigner := payload.NewSigner(sv) tsaSigner := tsa.NewSigner(payloadSigner, client) - chain, err := client.Timestamp.GetTimestampCertChain(nil) + certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } - leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain([]byte(chain.Payload)) + leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } @@ -1378,12 +1378,13 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TS := bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} - chain, err := client.Timestamp.GetTimestampCertChain(nil) + + certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + t.Fatalf("unexpected error marshalling cert chain: %v", err) } - leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain([]byte(chain.Payload)) + leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } diff --git a/test/e2e_test.go b/test/e2e_test.go index a53055b85e8..ba0277ac230 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -768,7 +768,7 @@ func TestAttestationRFC3161Timestamp(t *testing.T) { PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, - TSAServerURL: server.URL, + TSAServerURL: server.URL + "/api/v1/timestamp", TlogUpload: false, } must(attestCommand.Exec(ctx, imgName), t) @@ -923,7 +923,7 @@ func TestRFC3161Timestamp(t *testing.T) { ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, - TSAServerURL: server.URL, + TSAServerURL: server.URL + "/api/v1/timestamp", } so := options.SignOptions{ Upload: true, @@ -977,7 +977,7 @@ func TestRekorBundleAndRFC3161Timestamp(t *testing.T) { ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, - TSAServerURL: server.URL, + TSAServerURL: server.URL + "/api/v1/timestamp", RekorURL: rekorURL, SkipConfirmation: true, } @@ -1220,10 +1220,11 @@ func TestSignBlobBundle(t *testing.T) { // Now sign the blob with one key ko := options.KeyOpts{ - KeyRef: privKeyPath1, - PassFunc: passFunc, - BundlePath: bundlePath, - RekorURL: rekorURL, + KeyRef: privKeyPath1, + PassFunc: passFunc, + BundlePath: bundlePath, + RekorURL: rekorURL, + SkipConfirmation: true, } if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { t.Fatal(err) @@ -1305,8 +1306,9 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { PassFunc: passFunc, BundlePath: bundlePath, RFC3161TimestampPath: tsPath, - TSAServerURL: server.URL, + TSAServerURL: server.URL + "/api/v1/timestamp", RekorURL: rekorURL, + SkipConfirmation: true, } if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { t.Fatal(err)