From 9dd133fbed6d9e59d728c4e2706ad46e0cfa5119 Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Thu, 16 Sep 2021 16:12:38 -0700 Subject: [PATCH 1/3] Start to flesh out `Signatures` and `internal/oci/remote` Starting with `cosign.FetchSignaturesForImageDigest`, I am starting to peel out some of the existing logic, and try to define useful interfaces within `internal/oci.Signatures` and implement them in `internal/oci/remote.Signatures`. This is still somewhat rough in parts, so I suspect that as we build it up, we will iterates on parts of it. Signed-off-by: Matt Moore --- cmd/cosign/cli/sign.go | 7 +- internal/oci/remote/remote.go | 134 ++++++++++++++++++++++++++++++++++ internal/oci/signatures.go | 49 ++++++++++++- pkg/cosign/fetch.go | 71 +++++------------- pkg/cosign/keys.go | 3 - pkg/cosign/remote/remote.go | 16 +--- pkg/cosign/tlog.go | 4 +- pkg/cosign/verify.go | 4 +- 8 files changed, 209 insertions(+), 79 deletions(-) create mode 100644 internal/oci/remote/remote.go diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go index e8cb2052e07..293533ad0ab 100644 --- a/cmd/cosign/cli/sign.go +++ b/cmd/cosign/cli/sign.go @@ -39,6 +39,7 @@ import ( "github.com/pkg/errors" "github.com/sigstore/cosign/cmd/cosign/cli/fulcio/fulcioverifier" + "github.com/sigstore/cosign/internal/oci" "github.com/sigstore/cosign/pkg/cosign" "github.com/sigstore/cosign/pkg/cosign/pivkey" cremote "github.com/sigstore/cosign/pkg/cosign/remote" @@ -378,13 +379,13 @@ func SignCmd(ctx context.Context, ko KeyOpts, regOpts RegistryOpts, annotations return nil } -func bundle(entry *models.LogEntryAnon) *cremote.Bundle { +func bundle(entry *models.LogEntryAnon) *oci.Bundle { if entry.Verification == nil { return nil } - return &cremote.Bundle{ + return &oci.Bundle{ SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp, - Payload: cremote.BundlePayload{ + Payload: oci.BundlePayload{ Body: entry.Body, IntegratedTime: *entry.IntegratedTime, LogIndex: *entry.LogIndex, diff --git a/internal/oci/remote/remote.go b/internal/oci/remote/remote.go new file mode 100644 index 00000000000..bf2806cc27a --- /dev/null +++ b/internal/oci/remote/remote.go @@ -0,0 +1,134 @@ +// +// Copyright 2021 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 remote + +import ( + "bytes" + "crypto/x509" + "fmt" + "io/ioutil" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/sigstore/cosign/internal/oci" + "github.com/sigstore/sigstore/pkg/cryptoutils" +) + +const ( + sigkey = "dev.cosignproject.cosign/signature" + certkey = "dev.sigstore.cosign/certificate" + chainkey = "dev.sigstore.cosign/chain" +) + +// Signatures fetches the signatures image represented by the named reference. +func Signatures(ref name.Reference, opts ...remote.Option) (oci.Signatures, error) { + img, err := remote.Image(ref, opts...) + if err != nil { + return nil, err + } + return &sigs{ + Image: img, + }, nil +} + +type sigs struct { + v1.Image +} + +var _ oci.Signatures = (*sigs)(nil) + +// Get implements oci.Signatures +func (s *sigs) Get() ([]oci.Signature, error) { + m, err := s.Manifest() + if err != nil { + return nil, err + } + signatures := make([]oci.Signature, 0, len(m.Layers)) + for _, desc := range m.Layers { + signatures = append(signatures, &sigLayer{ + img: s, + desc: desc, + }) + } + return signatures, nil +} + +type sigLayer struct { + img *sigs + desc v1.Descriptor +} + +var _ oci.Signature = (*sigLayer)(nil) + +// Payload implements oci.Signature +func (s *sigLayer) Payload() ([]byte, error) { + l, err := s.img.LayerByDigest(s.desc.Digest) + if err != nil { + return nil, err + } + + // Compressed is a misnomer here, we just want the raw bytes from the registry. + r, err := l.Compressed() + if err != nil { + return nil, err + } + payload, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return payload, nil +} + +// Base64Signature implements oci.Signature +func (s *sigLayer) Base64Signature() (string, error) { + b64sig, ok := s.desc.Annotations[sigkey] + if !ok { + return "", fmt.Errorf("signature layer %s is missing %q annotation", s.desc.Digest, sigkey) + } + return b64sig, nil +} + +// Cert implements oci.Signature +func (s *sigLayer) Cert() (*x509.Certificate, error) { + certPEM, ok := s.desc.Annotations[certkey] + if !ok { + return nil, nil + } + certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certPEM))) + if err != nil { + return nil, err + } + return certs[0], nil +} + +// Chain implements oci.Signature +func (s *sigLayer) Chain() ([]*x509.Certificate, error) { + chainPEM, ok := s.desc.Annotations[chainkey] + if !ok { + return nil, nil + } + certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(chainPEM))) + if err != nil { + return nil, err + } + return certs, nil +} + +// Bundle implements oci.Signature +func (s *sigLayer) Bundle() (*oci.Bundle, error) { + return nil, nil +} diff --git a/internal/oci/signatures.go b/internal/oci/signatures.go index d3c1972a951..cc1435dac2b 100644 --- a/internal/oci/signatures.go +++ b/internal/oci/signatures.go @@ -15,7 +15,12 @@ package oci -import v1 "github.com/google/go-containerregistry/pkg/v1" +import ( + "crypto/x509" + + "github.com/go-openapi/strfmt" + v1 "github.com/google/go-containerregistry/pkg/v1" +) // Signatures represents a set of signatures that are associated with a particular // v1.Image. @@ -25,4 +30,46 @@ type Signatures interface { // TODO(mattmoor): Accessors that build on `v1.Image` to provide // higher-level accessors for the signature data that is embedded // in the wrapped `v1.Image` + + // Get retrieves the list of signatures stored. + Get() ([]Signature, error) +} + +// Signature holds a single image signature. +type Signature interface { + // Payload fetches the opaque data that is being signed. + // This will always return data when there is no error. + Payload() ([]byte, error) + + // Base64Signature fetches the base64 encoded signature + // of the payload. This will always return data when + // there is no error. + Base64Signature() (string, error) + + // Cert fetches the optional public key from the key pair that + // was used to sign the payload. + Cert() (*x509.Certificate, error) + + // Chain fetches the optional "full certificate chain" rooted + // at a Fulcio CA, the leaf of which was used to sign the + // payload. + Chain() ([]*x509.Certificate, error) + + // Bundle fetches the optional metadata that records the ephemeral + // Fulcio key in the transparency log. + Bundle() (*Bundle, error) +} + +// Bundle holds metadata about recording a Signature's ephemeral key to +// a Rekor transparency log. +type Bundle struct { + SignedEntryTimestamp strfmt.Base64 + Payload BundlePayload +} + +type BundlePayload struct { + Body interface{} `json:"body"` + IntegratedTime int64 `json:"integratedTime"` + LogIndex int64 `json:"logIndex"` + LogID string `json:"logID"` } diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 5efd60610d8..11c88f520b7 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -16,11 +16,8 @@ package cosign import ( - "bytes" "context" "crypto/x509" - "encoding/json" - "io/ioutil" "runtime" "strings" @@ -30,8 +27,8 @@ import ( "github.com/pkg/errors" "knative.dev/pkg/pool" - cremote "github.com/sigstore/cosign/pkg/cosign/remote" - "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/cosign/internal/oci" + ociremote "github.com/sigstore/cosign/internal/oci/remote" ) type SignedPayload struct { @@ -39,7 +36,7 @@ type SignedPayload struct { Payload []byte Cert *x509.Certificate Chain []*x509.Certificate - Bundle *cremote.Bundle + Bundle *oci.Bundle bundleVerified bool } @@ -82,72 +79,38 @@ func FetchSignaturesForImage(ctx context.Context, signedImgRef name.Reference, s func FetchSignaturesForImageDigest(ctx context.Context, signedImageDigest v1.Hash, sigRepo name.Repository, sigTagSuffix string, registryOpts ...remote.Option) ([]SignedPayload, error) { tag := AttachedImageTag(sigRepo, signedImageDigest, sigTagSuffix) - sigImg, err := remote.Image(tag, registryOpts...) + sigs, err := ociremote.Signatures(tag, registryOpts...) if err != nil { return nil, errors.Wrap(err, "remote image") } - - m, err := sigImg.Manifest() + l, err := sigs.Get() if err != nil { - return nil, errors.Wrap(err, "manifest") + return nil, errors.Wrap(err, "fetching signatures") } g := pool.New(runtime.NumCPU()) - signatures := make([]SignedPayload, len(m.Layers)) - for i, desc := range m.Layers { - i, desc := i, desc + signatures := make([]SignedPayload, len(l)) + for i, sig := range l { + i, sig := i, sig g.Go(func() error { - base64sig, ok := desc.Annotations[sigkey] - if !ok { - return nil - } - l, err := sigImg.LayerByDigest(desc.Digest) + signatures[i].Payload, err = sig.Payload() if err != nil { return err } - - // Compressed is a misnomer here, we just want the raw bytes from the registry. - r, err := l.Compressed() + signatures[i].Base64Signature, err = sig.Base64Signature() if err != nil { return err } - payload, err := ioutil.ReadAll(r) + signatures[i].Cert, err = sig.Cert() if err != nil { return err } - sp := SignedPayload{ - Payload: payload, - Base64Signature: base64sig, - } - // We may have a certificate and chain - certPem := desc.Annotations[certkey] - if certPem != "" { - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certPem))) - if err != nil { - return err - } - sp.Cert = certs[0] - } - chainPem := desc.Annotations[chainkey] - if chainPem != "" { - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(chainPem))) - if err != nil { - return err - } - sp.Chain = certs - } - - bundle := desc.Annotations[BundleKey] - if bundle != "" { - var b cremote.Bundle - if err := json.Unmarshal([]byte(bundle), &b); err != nil { - return errors.Wrap(err, "unmarshaling bundle") - } - sp.Bundle = &b + signatures[i].Chain, err = sig.Chain() + if err != nil { + return err } - - signatures[i] = sp - return nil + signatures[i].Bundle, err = sig.Bundle() + return err }) } if err := g.Wait(); err != nil { diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index 173791fc071..9a01090205c 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -35,9 +35,6 @@ import ( const ( PrivakeKeyPemType = "ENCRYPTED COSIGN PRIVATE KEY" - sigkey = "dev.cosignproject.cosign/signature" - certkey = "dev.sigstore.cosign/certificate" - chainkey = "dev.sigstore.cosign/chain" BundleKey = "dev.sigstore.cosign/bundle" ) diff --git a/pkg/cosign/remote/remote.go b/pkg/cosign/remote/remote.go index bffa631b438..aad5b110701 100644 --- a/pkg/cosign/remote/remote.go +++ b/pkg/cosign/remote/remote.go @@ -22,7 +22,6 @@ import ( "io/ioutil" "net/http" - "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -32,6 +31,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pkg/errors" + "github.com/sigstore/cosign/internal/oci" "github.com/sigstore/cosign/internal/oci/empty" ctypes "github.com/sigstore/cosign/pkg/types" "github.com/sigstore/sigstore/pkg/signature" @@ -110,23 +110,11 @@ LayerLoop: return nil, nil } -type BundlePayload struct { - Body interface{} `json:"body"` - IntegratedTime int64 `json:"integratedTime"` - LogIndex int64 `json:"logIndex"` - LogID string `json:"logID"` -} - -type Bundle struct { - SignedEntryTimestamp strfmt.Base64 - Payload BundlePayload -} - type UploadOpts struct { Cert []byte Chain []byte DupeDetector signature.Verifier - Bundle *Bundle + Bundle *oci.Bundle AdditionalAnnotations map[string]string RemoteOpts []remote.Option MediaType string diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 0f935f95513..b4882e370c3 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -31,7 +31,7 @@ import ( "github.com/google/trillian/merkle/rfc6962/hasher" "github.com/pkg/errors" - cremote "github.com/sigstore/cosign/pkg/cosign/remote" + "github.com/sigstore/cosign/internal/oci" "github.com/sigstore/cosign/pkg/cosign/tuf" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" @@ -225,7 +225,7 @@ func verifyTLogEntry(rekorClient *client.Rekor, uuid string) (*models.LogEntryAn return nil, errors.Wrap(err, "rekor public key pem to ecdsa") } - payload := cremote.BundlePayload{ + payload := oci.BundlePayload{ Body: e.Body, IntegratedTime: *e.IntegratedTime, LogIndex: *e.LogIndex, diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 9e10d17bdb1..45141d10d1d 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -33,7 +33,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/pkg/errors" - cremote "github.com/sigstore/cosign/pkg/cosign/remote" + "github.com/sigstore/cosign/internal/oci" rekor "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -288,7 +288,7 @@ func (sp *SignedPayload) VerifyBundle() (bool, error) { return true, nil } -func VerifySET(bundlePayload cremote.BundlePayload, signature []byte, pub *ecdsa.PublicKey) error { +func VerifySET(bundlePayload oci.BundlePayload, signature []byte, pub *ecdsa.PublicKey) error { contents, err := json.Marshal(bundlePayload) if err != nil { return errors.Wrap(err, "marshaling") From 294058639eb13cf9e10f0d642474fb9532f74d2e Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Thu, 16 Sep 2021 18:10:49 -0700 Subject: [PATCH 2/3] Implement Bundle, add tests, fix nits Signed-off-by: Matt Moore --- internal/oci/remote/remote.go | 30 ++- internal/oci/remote/remote_test.go | 329 +++++++++++++++++++++++++++++ pkg/cosign/fetch.go | 2 +- pkg/cosign/keys.go | 3 +- 4 files changed, 354 insertions(+), 10 deletions(-) create mode 100644 internal/oci/remote/remote_test.go diff --git a/internal/oci/remote/remote.go b/internal/oci/remote/remote.go index bf2806cc27a..396eb0d58a6 100644 --- a/internal/oci/remote/remote.go +++ b/internal/oci/remote/remote.go @@ -16,27 +16,33 @@ package remote import ( - "bytes" "crypto/x509" + "encoding/json" "fmt" "io/ioutil" + "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/pkg/errors" "github.com/sigstore/cosign/internal/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) const ( - sigkey = "dev.cosignproject.cosign/signature" - certkey = "dev.sigstore.cosign/certificate" - chainkey = "dev.sigstore.cosign/chain" + sigkey = "dev.cosignproject.cosign/signature" + certkey = "dev.sigstore.cosign/certificate" + chainkey = "dev.sigstore.cosign/chain" + BundleKey = "dev.sigstore.cosign/bundle" ) +// This enables mocking for unit testing without faking an entire registry. +var remoteImage = remote.Image + // Signatures fetches the signatures image represented by the named reference. func Signatures(ref name.Reference, opts ...remote.Option) (oci.Signatures, error) { - img, err := remote.Image(ref, opts...) + img, err := remoteImage(ref, opts...) if err != nil { return nil, err } @@ -108,7 +114,7 @@ func (s *sigLayer) Cert() (*x509.Certificate, error) { if !ok { return nil, nil } - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(certPEM))) + certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(certPEM)) if err != nil { return nil, err } @@ -121,7 +127,7 @@ func (s *sigLayer) Chain() ([]*x509.Certificate, error) { if !ok { return nil, nil } - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader([]byte(chainPEM))) + certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(chainPEM)) if err != nil { return nil, err } @@ -130,5 +136,13 @@ func (s *sigLayer) Chain() ([]*x509.Certificate, error) { // Bundle implements oci.Signature func (s *sigLayer) Bundle() (*oci.Bundle, error) { - return nil, nil + bundle := s.desc.Annotations[BundleKey] + if bundle == "" { + return nil, nil + } + var b oci.Bundle + if err := json.Unmarshal([]byte(bundle), &b); err != nil { + return nil, errors.Wrap(err, "unmarshaling bundle") + } + return &b, nil } diff --git a/internal/oci/remote/remote_test.go b/internal/oci/remote/remote_test.go new file mode 100644 index 00000000000..33a12128b74 --- /dev/null +++ b/internal/oci/remote/remote_test.go @@ -0,0 +1,329 @@ +// +// Copyright 2021 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 remote + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/sigstore/cosign/internal/oci" + "github.com/sigstore/cosign/internal/oci/empty" +) + +func TestSignature(t *testing.T) { + layer, err := random.Layer(300 /* byteSize */, types.DockerLayer) + if err != nil { + t.Fatalf("random.Layer() = %v", err) + } + digest, err := layer.Digest() + if err != nil { + t.Fatalf("Digest() = %v", err) + } + + tests := []struct { + name string + l *sigLayer + wantPayloadErr error + wantSig string + wantSigErr error + wantCert bool + wantCertErr error + wantChain int + wantChainErr error + wantBundle *oci.Bundle + wantBundleErr error + }{{ + name: "just payload and signature", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + }, + }, + }, + wantSig: "blah", + }, { + name: "bad digest", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: v1.Hash{Algorithm: "bad", Hex: "f00d"}, + Annotations: map[string]string{ + sigkey: "blah", + }, + }, + }, + wantPayloadErr: errors.New("unknown blob bad:f00d"), + wantSig: "blah", + }, { + name: "missing signature", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + }, + }, + wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey), + }, { + name: "min plus bad bundle", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + BundleKey: `}`, + }, + }, + }, + wantSig: "blah", + wantBundleErr: errors.New(`unmarshaling bundle: invalid character '}' looking for beginning of value`), + }, { + name: "min plus bad cert", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + certkey: `GARBAGE`, + }, + }, + }, + wantSig: "blah", + wantCertErr: errors.New(`error during PEM decoding`), + }, { + name: "min plus bad chain", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + chainkey: `GARBAGE`, + }, + }, + }, + wantSig: "blah", + wantChainErr: errors.New(`error during PEM decoding`), + }, { + name: "min plus bundle", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. + // The Body has been removed for brevity + BundleKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, + }, + }, + }, + wantSig: "blah", + wantBundle: &oci.Bundle{ + SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), + Payload: oci.BundlePayload{ + Body: "REMOVED", + IntegratedTime: 1631646761, + LogIndex: 693591, + LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + }, + }, + }, { + name: "min plus good cert", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 + certkey: ` +-----BEGIN CERTIFICATE----- +MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt +6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw +EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G +3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG +0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl +Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu +c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j +cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj +ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM +wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx +uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= +-----END CERTIFICATE----- +`, + }, + }, + }, + wantSig: "blah", + wantCert: true, + }, { + name: "min plus bad chain", + l: &sigLayer{ + img: &sigs{ + Image: must(mutate.Append(empty.Image(), mutate.Addendum{Layer: layer})), + }, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 + chainkey: ` +-----BEGIN CERTIFICATE----- +MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu +ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy +A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas +taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm +MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u +Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx +Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup +Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== +-----END CERTIFICATE----- +`, + }, + }, + }, + wantSig: "blah", + wantChain: 1, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + b, err := test.l.Payload() + if (err != nil) != (test.wantPayloadErr != nil) { + t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) + } else if (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error() { + t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) + } else if err == nil { + if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { + t.Errorf("v1.SHA256() = %v", err) + } else if want := digest; want != got { + t.Errorf("v1.SHA256() = %v, wanted %v", got, want) + } + } + + if got, err := test.l.Base64Signature(); (err != nil) != (test.wantSigErr != nil) { + t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) + } else if (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error() { + t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) + } else if got != test.wantSig { + t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) + } + + if got, err := test.l.Cert(); (err != nil) != (test.wantCertErr != nil) { + t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) + } else if (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error() { + t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) + } else if (got != nil) != test.wantCert { + t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) + } + + if got, err := test.l.Chain(); (err != nil) != (test.wantChainErr != nil) { + t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) + } else if (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error() { + t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) + } else if len(got) != test.wantChain { + t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) + } + + if got, err := test.l.Bundle(); (err != nil) != (test.wantBundleErr != nil) { + t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) + } else if (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error() { + t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) + } else if !cmp.Equal(got, test.wantBundle) { + t.Errorf("Bundle() %s", cmp.Diff(got, test.wantBundle)) + } + }) + } +} + +func TestSignatures(t *testing.T) { + ri := remote.Image + t.Cleanup(func() { + remoteImage = ri + }) + wantLayers := int64(27) + remoteImage = func(ref name.Reference, options ...remote.Option) (v1.Image, error) { + return random.Image(300 /* byteSize */, wantLayers) + } + + ref, err := name.ParseReference("gcr.io/distroless/static:nonroot") + if err != nil { + t.Fatalf("ParseRef() = %v", err) + } + + sigs, err := Signatures(ref) + if err != nil { + t.Fatalf("Signatures() = %v", err) + } + + if sl, err := sigs.Get(); err != nil { + t.Errorf("Get() = %v", err) + } else if got := int64(len(sl)); got != wantLayers { + t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) + } +} + +func must(img v1.Image, err error) v1.Image { + if err != nil { + panic(err.Error()) + } + return img +} + +func mustDecode(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err.Error()) + } + return b +} diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 11c88f520b7..c8a1282afd4 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -92,7 +92,7 @@ func FetchSignaturesForImageDigest(ctx context.Context, signedImageDigest v1.Has signatures := make([]SignedPayload, len(l)) for i, sig := range l { i, sig := i, sig - g.Go(func() error { + g.Go(func() (err error) { signatures[i].Payload, err = sig.Payload() if err != nil { return err diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index 9a01090205c..ab88e6a4987 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -28,6 +28,7 @@ import ( "github.com/pkg/errors" "github.com/theupdateframework/go-tuf/encrypted" + "github.com/sigstore/cosign/pkg/cosign/remote" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" ) @@ -35,7 +36,7 @@ import ( const ( PrivakeKeyPemType = "ENCRYPTED COSIGN PRIVATE KEY" - BundleKey = "dev.sigstore.cosign/bundle" + BundleKey = remote.BundleKey ) type PassFunc func(bool) ([]byte, error) From 06b8b7dc34c76e2c547b347b15ea03bbe99e0cd2 Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Thu, 16 Sep 2021 18:20:28 -0700 Subject: [PATCH 3/3] Switch to switch Signed-off-by: Matt Moore --- internal/oci/remote/remote_test.go | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/internal/oci/remote/remote_test.go b/internal/oci/remote/remote_test.go index 33a12128b74..b4195344d00 100644 --- a/internal/oci/remote/remote_test.go +++ b/internal/oci/remote/remote_test.go @@ -239,11 +239,12 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== for _, test := range tests { t.Run(test.name, func(t *testing.T) { b, err := test.l.Payload() - if (err != nil) != (test.wantPayloadErr != nil) { + switch { + case (err != nil) != (test.wantPayloadErr != nil): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) - } else if (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error() { + case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error(): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) - } else if err == nil { + case err == nil: if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { t.Errorf("v1.SHA256() = %v", err) } else if want := digest; want != got { @@ -251,35 +252,39 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== } } - if got, err := test.l.Base64Signature(); (err != nil) != (test.wantSigErr != nil) { + switch got, err := test.l.Base64Signature(); { + case (err != nil) != (test.wantSigErr != nil): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) - } else if (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error() { + case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error(): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) - } else if got != test.wantSig { + case got != test.wantSig: t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) } - if got, err := test.l.Cert(); (err != nil) != (test.wantCertErr != nil) { + switch got, err := test.l.Cert(); { + case (err != nil) != (test.wantCertErr != nil): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) - } else if (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error() { + case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error(): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) - } else if (got != nil) != test.wantCert { + case (got != nil) != test.wantCert: t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) } - if got, err := test.l.Chain(); (err != nil) != (test.wantChainErr != nil) { + switch got, err := test.l.Chain(); { + case (err != nil) != (test.wantChainErr != nil): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) - } else if (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error() { + case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error(): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) - } else if len(got) != test.wantChain { + case len(got) != test.wantChain: t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) } - if got, err := test.l.Bundle(); (err != nil) != (test.wantBundleErr != nil) { + switch got, err := test.l.Bundle(); { + case (err != nil) != (test.wantBundleErr != nil): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) - } else if (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error() { + case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error(): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) - } else if !cmp.Equal(got, test.wantBundle) { + case !cmp.Equal(got, test.wantBundle): t.Errorf("Bundle() %s", cmp.Diff(got, test.wantBundle)) } })