Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue 919 #930

Merged
merged 1 commit into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"os"
"path/filepath"

Expand All @@ -37,7 +36,6 @@ import (
"github.com/sigstore/cosign/pkg/cosign/pivkey"
sigs "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
)

// VerifyAttestationCommand verifies a signature on a supplied container image
Expand All @@ -55,17 +53,6 @@ type VerifyAttestationCommand struct {
Policies []string
}

// DSSE messages contain the signature and payload in one object, but our interface expects a signature and payload
// This means we need to use one field and ignore the other. The DSSE verifier upstream uses the signature field and ignores
// The message field, but we want the reverse here.
type reverseDSSEVerifier struct {
signature.Verifier
}

func (w *reverseDSSEVerifier) VerifySignature(s io.Reader, m io.Reader, opts ...signature.VerifyOption) error {
return w.Verifier.VerifySignature(m, nil, opts...)
}

// Exec runs the verification command
func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (err error) {
if len(images) == 0 {
Expand All @@ -80,7 +67,6 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if err != nil {
return errors.Wrap(err, "constructing client options")
}

co := &cosign.CheckOpts{
RegistryClientOpts: ociremoteOpts,
}
Expand Down Expand Up @@ -111,9 +97,11 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
return errors.Wrap(err, "initializing piv token verifier")
}
}

co.SigVerifier = &reverseDSSEVerifier{
Verifier: dsse.WrapVerifier(pubKey),
if pubKey != nil {
// TODO(vaikas): Should this be private and cosign just figures out
// how to wrap things. This would mean we need to pass more context, so
// just making it like this for now.
co.SigVerifier = cosign.NewReverseDSSEVerifier(pubKey)
}

for _, imageRef := range images {
Expand Down
30 changes: 29 additions & 1 deletion pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"strings"
"time"

Expand All @@ -38,6 +39,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/sigstore/sigstore/pkg/signature/options"
)

Expand Down Expand Up @@ -65,6 +67,23 @@ type CheckOpts struct {
CertEmail string
}

// DSSE messages (Attestations) contain the signature and payload in one object, but our interface expects a signature and payload
// This means we need to use one field and ignore the other. The DSSE verifier upstream uses the signature field and ignores
// The message field, but we want the reverse here.
type reverseDSSEVerifier struct {
signature.Verifier
}

func NewReverseDSSEVerifier(v signature.Verifier) signature.Verifier {
return &reverseDSSEVerifier{
Verifier: dsse.WrapVerifier(v),
}
}

func (w *reverseDSSEVerifier) VerifySignature(s io.Reader, m io.Reader, opts ...signature.VerifyOption) error {
return w.Verifier.VerifySignature(m, nil, opts...)
}

// VerifySignatures does all the main cosign checks in a loop, returning the verified signatures.
// If there were no valid signatures, we return an error.
func VerifySignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
Expand Down Expand Up @@ -154,7 +173,8 @@ func Verify(ctx context.Context, signedImgRef name.Reference, accessor Accessor,
if cert == nil {
return errors.New("no certificate found on signature")
}
pub, err := signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
var pub signature.Verifier
pub, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return errors.Wrap(err, "invalid certificate found on signature")
}
Expand All @@ -167,6 +187,14 @@ func Verify(ctx context.Context, signedImgRef name.Reference, accessor Accessor,
if err != nil {
return err
}

// The fact that there's no signature (or empty rather), implies
// that this is an Attestation that we're verifying. So, we need
// to construct a Verifier that grabs the signature from the
// payload instead of the Signatures annotations.
if len(signature) == 0 {
pub = NewReverseDSSEVerifier(pub)
}
if err := pub.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)); err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/oci/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type SignedEntity interface {

// Attestations returns the set of attestations currently associated with this
// entity, or the empty equivalent if none are found.
// Attestations are just like a Signature, but they do not contain
// Base64Signature because it's baked into the payload.
Attestations() (Signatures, error)

// Attachment returns a named entity associated with this entity, or error if not found.
Expand Down
3 changes: 3 additions & 0 deletions pkg/oci/static/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func NewSignature(payload []byte, b64sig string, opts ...Option) (oci.Signature,
}

// NewAttestation constructs a new oci.Signature from the provided options.
// Since Attestation is treated just like a Signature but the actual signature
// is baked into the payload, the Signature does not actually have
// the Base64Signature.
func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) {
return NewSignature(payload, "", opts...)
}
Expand Down