From b9d0d4a109ae6e3cdca864e699e6e40350c1fd0e Mon Sep 17 00:00:00 2001 From: priyawadhwa Date: Fri, 7 Jan 2022 12:03:05 -0500 Subject: [PATCH] Reorganize verify-blob code and add a unit test (#1286) Signed-off-by: Priya Wadhwa --- cmd/cosign/cli/verify/verify_blob.go | 146 ++++++++++++++-------- cmd/cosign/cli/verify/verify_blob_test.go | 59 +++++++++ 2 files changed, 151 insertions(+), 54 deletions(-) create mode 100644 cmd/cosign/cli/verify/verify_blob_test.go diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index efbb28e293c..eb01b00e115 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -55,38 +55,18 @@ func isb64(data []byte) bool { // nolint func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRef string) error { var pubKey sigstoresigs.Verifier - var err error var cert *x509.Certificate if !options.OneOf(ko.KeyRef, ko.Sk, certRef) && !options.EnableExperimental() { return &options.PubKeyParseError{} } - var b64sig string - if sigRef == "" { - return fmt.Errorf("missing flag '--signature'") - } - targetSig, err := blob.LoadFileOrURL(sigRef) + sig, b64sig, err := signatures(sigRef) if err != nil { - if !os.IsNotExist(err) { - // ignore if file does not exist, it can be a base64 encoded string as well - return err - } - targetSig = []byte(sigRef) - } - - if isb64(targetSig) { - b64sig = string(targetSig) - } else { - b64sig = base64.StdEncoding.EncodeToString(targetSig) + return err } - var blobBytes []byte - if blobRef == "-" { - blobBytes, err = io.ReadAll(os.Stdin) - } else { - blobBytes, err = blob.LoadFileOrURL(blobRef) - } + blobBytes, err := payloadBytes(blobRef) if err != nil { return err } @@ -148,53 +128,111 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe } } - sig, err := base64.StdEncoding.DecodeString(b64sig) - if err != nil { + // verify the signature + if err := pubKey.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil { return err } - if err := pubKey.VerifySignature(bytes.NewReader(sig), bytes.NewReader(blobBytes)); err != nil { + + // verify the cert + if err := verifyCert(cert); err != nil { return err } - if cert != nil { // cert - if err := cosign.TrustedCert(cert, fulcio.GetRoots()); err != nil { - return err - } - fmt.Fprintln(os.Stderr, "Certificate is trusted by Fulcio Root CA") - fmt.Fprintln(os.Stderr, "Email:", cert.EmailAddresses) - for _, uri := range cert.URIs { - fmt.Fprintf(os.Stderr, "URI: %s://%s%s\n", uri.Scheme, uri.Host, uri.Path) - } - fmt.Fprintln(os.Stderr, "Issuer: ", sigs.CertIssuerExtension(cert)) + // verify the rekor entry + if err := verifyRekorEntry(ctx, ko, pubKey, cert, b64sig, blobBytes); err != nil { + return err } + fmt.Fprintln(os.Stderr, "Verified OK") + return nil +} - if options.EnableExperimental() { - rekorClient, err := rekor.NewClient(ko.RekorURL) +// signatures returns the raw signature and the base64 encoded signature +func signatures(sigRef string) (string, string, error) { + var targetSig []byte + var err error + switch { + case sigRef != "": + targetSig, err = blob.LoadFileOrURL(sigRef) if err != nil { - return err - } - var pubBytes []byte - if pubKey != nil { - pubBytes, err = sigs.PublicKeyPem(pubKey, signatureoptions.WithContext(ctx)) - if err != nil { - return err + if !os.IsNotExist(err) { + // ignore if file does not exist, it can be a base64 encoded string as well + return "", "", err } + targetSig = []byte(sigRef) } - if cert != nil { - pubBytes, err = cryptoutils.MarshalCertificateToPEM(cert) - if err != nil { - return err - } + default: + return "", "", fmt.Errorf("missing flag '--signature'") + } + + var sig, b64sig string + if isb64(targetSig) { + b64sig = string(targetSig) + sigBytes, _ := base64.StdEncoding.DecodeString(b64sig) + sig = string(sigBytes) + } else { + sig = string(targetSig) + b64sig = base64.StdEncoding.EncodeToString(targetSig) + } + return sig, b64sig, nil +} + +func payloadBytes(blobRef string) ([]byte, error) { + var blobBytes []byte + var err error + if blobRef == "-" { + blobBytes, err = io.ReadAll(os.Stdin) + } else { + blobBytes, err = blob.LoadFileOrURL(blobRef) + } + if err != nil { + return nil, err + } + return blobBytes, nil +} + +func verifyCert(cert *x509.Certificate) error { + if cert == nil { + return nil + } + if err := cosign.TrustedCert(cert, fulcio.GetRoots()); err != nil { + return err + } + fmt.Fprintln(os.Stderr, "Certificate is trusted by Fulcio Root CA") + fmt.Fprintln(os.Stderr, "Email:", cert.EmailAddresses) + for _, uri := range cert.URIs { + fmt.Fprintf(os.Stderr, "URI: %s://%s%s\n", uri.Scheme, uri.Host, uri.Path) + } + fmt.Fprintln(os.Stderr, "Issuer: ", sigs.CertIssuerExtension(cert)) + return nil +} + +func verifyRekorEntry(ctx context.Context, ko sign.KeyOpts, pubKey sigstoresigs.Verifier, cert *x509.Certificate, b64sig string, blobBytes []byte) error { + if !options.EnableExperimental() { + return nil + } + rekorClient, err := rekor.NewClient(ko.RekorURL) + if err != nil { + return err + } + var pubBytes []byte + if pubKey != nil { + pubBytes, err = sigs.PublicKeyPem(pubKey, signatureoptions.WithContext(ctx)) + if err != nil { + return err } - uuid, index, err := cosign.FindTlogEntry(ctx, rekorClient, b64sig, blobBytes, pubBytes) + } + if cert != nil { + pubBytes, err = cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return err } - fmt.Fprintf(os.Stderr, "tlog entry verified with uuid: %q index: %d\n", uuid, index) - return nil } - + uuid, index, err := cosign.FindTlogEntry(ctx, rekorClient, b64sig, blobBytes, pubBytes) + if err != nil { + return err + } + fmt.Fprintf(os.Stderr, "tlog entry verified with uuid: %q index: %d\n", uuid, index) return nil } diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go new file mode 100644 index 00000000000..0fceca06fdf --- /dev/null +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -0,0 +1,59 @@ +// Copyright 2022 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 verify + +import ( + "testing" +) + +func TestSignatures(t *testing.T) { + sig := "a==" + b64sig := "YT09" + tests := []struct { + description string + sigRef string + shouldErr bool + }{ + { + description: "raw sig", + sigRef: sig, + }, + { + description: "encoded sig", + sigRef: b64sig, + }, { + description: "empty ref", + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + gotSig, gotb64Sig, err := signatures(test.sigRef) + if test.shouldErr && err != nil { + return + } + if test.shouldErr { + t.Fatal("should have received an error") + } + if gotSig != sig { + t.Fatalf("unexpected signature, expected: %s got: %s", sig, gotSig) + } + if gotb64Sig != b64sig { + t.Fatalf("unexpected encoded signature, expected: %s got: %s", b64sig, gotb64Sig) + } + }) + } +}