From f5c38160a7023bf2331c74f571f0ff8eb2d51afa Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Fri, 10 Feb 2023 09:12:01 +0000 Subject: [PATCH] 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 --- 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 | 80 +++++++++++++++++++ .../pkg/cosign/tsa/mock/mock_tsa_client.go | 67 ++++------------ internal/pkg/cosign/tsa/signer.go | 34 ++------ pkg/cosign/verify_test.go | 19 ++--- 20 files changed, 148 insertions(+), 149 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..9dbaecdbff3 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.TimestampAuthorityClient{URL: 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..1ccdf2d0d3a 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.TimestampAuthorityClient{URL: 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 eea06d8d660..0ca9eeb2649 100644 --- a/cmd/cosign/cli/options/sign.go +++ b/cmd/cosign/cli/options/sign.go @@ -98,7 +98,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..46cb4218e88 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.TimestampAuthorityClient{URL: o.TSAServerURL}); err != nil { return err } } diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 71c3834fe41..2e3ea5b2714 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" @@ -236,12 +236,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.TimestampAuthorityClient{URL: 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 6c6e231c04b..86fc8b0e5a8 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" @@ -78,12 +78,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.TimestampAuthorityClient{URL: 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..61ac128660d 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.CertificateChain) 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.Timestamp.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.Timestamp.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.CertificateChain) 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.CertificateChain) 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.Timestamp.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 11fb0dd25d5..4f0a343f46a 100644 --- a/doc/cosign_sign.md +++ b/doc/cosign_sign.md @@ -93,7 +93,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..4371327b2cd --- /dev/null +++ b/internal/pkg/cosign/tsa/client/client.go @@ -0,0 +1,80 @@ +// 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" + "crypto/x509" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/digitorus/timestamp" + "github.com/pkg/errors" +) + +// TimestampAuthorityClient provides a client to query RFC 3161 timestamp authorities +type TimestampAuthorityClient struct { + Timestamp TimestampAuthorityService + + // URL is the path to the API to request timestamp responses + URL string + + // CertificateChain contains an optional certificate chain used to verify timestamp responses + CertificateChain []*x509.Certificate +} + +// TimestampAuthorityService should be implemented by clients that want to request timestamp responses +type TimestampAuthorityService interface { + GetTimestampResponse(tsq []byte) ([]byte, error) +} + +// GetTimestampResponse sends a timestamp query to a timestamp authority, returning a timestamp response. +// The query and response are defined by RFC 3161. +func (t *TimestampAuthorityClient) GetTimestampResponse(tsq []byte) ([]byte, error) { + client := http.Client{ + Timeout: time.Second * 5, + } + 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 { + 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 +} diff --git a/internal/pkg/cosign/tsa/mock/mock_tsa_client.go b/internal/pkg/cosign/tsa/mock/mock_tsa_client.go index cb708ba9a61..9779325de8b 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,10 @@ 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 + Signer crypto.Signer + CertChain []*x509.Certificate + Time time.Time + Message []byte } // TSAClientOptions provide customization for the mock TSA client. @@ -58,7 +52,7 @@ type TSAClientOptions struct { Signer crypto.Signer } -func NewTSAClient(o TSAClientOptions) (*client.TimestampAuthority, error) { +func NewTSAClient(o TSAClientOptions) (*client.TimestampAuthorityClient, error) { sv := o.Signer if sv == nil { var err error @@ -71,37 +65,24 @@ 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{ + return &client.TimestampAuthorityClient{ Timestamp: &TSAClient{ - Signer: sv, - CertChain: certChain, - CertChainPEM: string(certChainPEM), - Time: o.Time, - Message: o.Message, + Signer: sv, + CertChain: certChain, + Time: o.Time, + Message: o.Message, }, + CertificateChain: certChain, }, 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 +118,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..e12d1110892 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.Timestamp.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..4a4b294acbd 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.CertificateChain) 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.CertificateChain) 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.CertificateChain) 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") }