From f06d610371ab64e4dab4beddf87f9f02d7372180 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 30 Nov 2022 22:54:48 +0000 Subject: [PATCH] Add testing in verify, update e2e test Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/verify/verify_blob_test.go | 4 + pkg/cosign/verify.go | 32 +++---- pkg/cosign/verify_test.go | 111 ++++++++++++++++++++++ test/e2e_test.go | 9 +- 4 files changed, 137 insertions(+), 19 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index 6eb76426c1cd..89b91d4c3a9b 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -525,6 +525,10 @@ func TestVerifyBlob(t *testing.T) { expiredLeafPem, true)}, shouldErr: true, }, + // TODO: Add tests for TSA: + // * With or without bundle + // * Mismatched signature + // * Unexpired and expired certificate } for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 82685113e2fc..4424b1e0baec 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -666,7 +666,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, } } if co.TSACerts != nil { - bundleVerified, err = VerifyRFC3161Timestamp(ctx, sig, co.TSACerts) + err = VerifyRFC3161Timestamp(sig, co.TSACerts) if err != nil { return false, fmt.Errorf("unable to verify RFC3161 timestamp bundle: %w", err) } @@ -986,22 +986,22 @@ func VerifyBundle(ctx context.Context, sig oci.Signature, co *CheckOpts, rekorCl return true, nil } -func VerifyRFC3161Timestamp(ctx context.Context, sig oci.Signature, tsaCerts *x509.CertPool) (bool, error) { - bundle, err := sig.RFC3161Timestamp() +func VerifyRFC3161Timestamp(sig oci.Signature, tsaCerts *x509.CertPool) error { + ts, err := sig.RFC3161Timestamp() if err != nil { - return false, err - } else if bundle == nil { - return false, nil + return err + } else if ts == nil { + return nil } b64Sig, err := sig.Base64Signature() if err != nil { - return false, fmt.Errorf("reading base64signature: %w", err) + return fmt.Errorf("reading base64signature: %w", err) } cert, err := sig.Cert() if err != nil { - return false, err + return err } var tsBytes []byte @@ -1009,36 +1009,36 @@ func VerifyRFC3161Timestamp(ctx context.Context, sig oci.Signature, tsaCerts *x5 // For attestations, the Base64Signature is not set, therefore we rely on the signed payload signedPayload, err := sig.Payload() if err != nil { - return false, fmt.Errorf("reading the payload: %w", err) + return fmt.Errorf("reading the payload: %w", err) } tsBytes = signedPayload } else { // create timestamp over raw bytes of signature rawSig, err := base64.StdEncoding.DecodeString(b64Sig) if err != nil { - return false, err + return err } tsBytes = rawSig } - err = tsaverification.VerifyTimestampResponse(bundle.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaCerts) + err = tsaverification.VerifyTimestampResponse(ts.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaCerts) if err != nil { - return false, fmt.Errorf("unable to verify TimestampResponse: %w", err) + return fmt.Errorf("unable to verify TimestampResponse: %w", err) } if cert != nil { - ts, err := timestamp.ParseResponse(bundle.SignedRFC3161Timestamp) + ts, err := timestamp.ParseResponse(ts.SignedRFC3161Timestamp) if err != nil { - return false, fmt.Errorf("error parsing response into timestamp: %w", err) + return fmt.Errorf("error parsing response into timestamp: %w", err) } // Verify the cert against the integrated time. // Note that if the caller requires the certificate to be present, it has to ensure that itself. if err := CheckExpiry(cert, ts.Time); err != nil { - return false, fmt.Errorf("checking expiry on cert: %w", err) + return fmt.Errorf("checking expiry on certificate: %w", err) } } - return true, nil + return nil } // compare bundle signature to the signature we are verifying diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 0843256f40a9..c54e3071e3f1 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "crypto" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -1286,3 +1287,113 @@ func Test_getSubjectAltnernativeNames(t *testing.T) { t.Fatalf("unexpected URL SAN value") } } + +func TestVerifyRFC3161Timestamp(t *testing.T) { + // generate signed artifact + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + // TODO: Replace with a TSA mock client, blocked by https://github.com/sigstore/timestamp-authority/issues/146 + viper.Set("timestamp-signer", "memory") + apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second) + server := httptest.NewServer(apiServer.GetHandler()) + t.Cleanup(server.Close) + client, err := tsaclient.GetTimestampClient(server.URL) + if err != nil { + t.Fatal(err) + } + + tsBytes, err := tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS := bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + chain, err := client.Timestamp.GetTimestampCertChain(nil) + if err != nil { + t.Fatalf("unexpected error getting timestamp chain: %v", err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM([]byte(chain.Payload)) { + t.Fatalf("error creating trust root pool") + } + + ociSig, _ := static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + + // success, signing over signature + err = VerifyRFC3161Timestamp(ociSig, pool) + if err != nil { + t.Fatalf("unexpected error verifying timestamp with signature: %v", err) + } + + // success, signing over payload + tsBytes, err = tsa.GetTimestampedSignature(payload, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + ociSig, _ = static.NewSignature(payload, + "", /*signature*/ + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err != nil { + t.Fatalf("unexpected error verifying timestamp with payload: %v", err) + } + + // failure with non-base64 encoded signature + ociSig, _ = static.NewSignature(payload, + string(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "base64 data") { + t.Fatalf("expected error verifying timestamp with raw signature, got: %v", err) + } + + // failure with mismatched signature + tsBytes, err = tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + // regenerate signature + signature, _ = privKey.Sign(rand.Reader, h[:], crypto.SHA256) + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "hashed messages don't match") { + t.Fatalf("expected error verifying mismatched signatures, got: %v", err) + } + + // failure with old certificate + leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + // generate old certificate + leafCert, _ = test.GenerateLeafCertWithExpiration("subject", "oidc-issuer", time.Now().AddDate(-1, 0, 0), leafPriv, rootCert, rootKey) + pemLeaf = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + tsBytes, err = tsa.GetTimestampedSignature(signature, client) + if err != nil { + t.Fatalf("unexpected error creating timestamp: %v", err) + } + rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(signature), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + err = VerifyRFC3161Timestamp(ociSig, pool) + if err == nil || !strings.Contains(err.Error(), "checking expiry on certificate") { + t.Fatalf("expected error verifying old certificate, got: %v", err) + } +} diff --git a/test/e2e_test.go b/test/e2e_test.go index e36b9a4d0dbe..3729f092dabc 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -991,7 +991,8 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { os.RemoveAll(td1) }) bp := filepath.Join(td1, blob) - bundlePath := filepath.Join(td1, "rfc3161TimestampBundle.sig") + bundlePath := filepath.Join(td1, "bundle.sig") + tsPath := filepath.Join(td1, "rfc3161Timestamp.json") if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) @@ -1023,7 +1024,8 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { ko1 := options.KeyOpts{ KeyRef: pubKeyPath1, - RFC3161TimestampPath: bundlePath, + BundlePath: bundlePath, + RFC3161TimestampPath: tsPath, TSACertChainPath: file.Name(), } // Verify should fail on a bad input @@ -1036,7 +1038,8 @@ func TestSignBlobRFC3161TimestampBundle(t *testing.T) { ko := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, - RFC3161TimestampPath: bundlePath, + BundlePath: bundlePath, + RFC3161TimestampPath: tsPath, TSAServerURL: server.URL, } if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "", false); err != nil {