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

feat: support signature file in verify cmd #1068

Merged
merged 2 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 9 additions & 5 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import (

// VerifyOptions is the top level wrapper for the `verify` command.
type VerifyOptions struct {
Key string
CertEmail string // TODO: merge into fulcio option as read mode?
CheckClaims bool
Attachment string
Output string
Key string
CertEmail string // TODO: merge into fulcio option as read mode?
CheckClaims bool
Attachment string
Output string
SignatureRef string

SecurityKey SecurityKeyOptions
Rekor RekorOptions
Expand Down Expand Up @@ -59,6 +60,9 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVarP(&o.Output, "output", "o", "json",
"output format for the signing image information (json|text)")

cmd.Flags().StringVar(&o.SignatureRef, "signature", "",
"signature content or path or remote URL")
}

// VerifyAttestationOptions is the top level wrapper for the `verify attestation` command.
Expand Down
4 changes: 2 additions & 2 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ against the transparency log.`,
RekorURL: o.Rekor.URL,
Attachment: o.Attachment,
Annotations: annotations,

HashAlgorithm: hashAlgorithm,
HashAlgorithm: hashAlgorithm,
SignatureRef: o.SignatureRef,
}

return v.Exec(cmd.Context(), args)
Expand Down
21 changes: 11 additions & 10 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ import (
// nolint
type VerifyCommand struct {
options.RegistryOptions
CheckClaims bool
KeyRef string
CertEmail string
Sk bool
Slot string
Output string
RekorURL string
Attachment string
Annotations sigs.AnnotationsMap

CheckClaims bool
KeyRef string
CertEmail string
Sk bool
Slot string
Output string
RekorURL string
Attachment string
Annotations sigs.AnnotationsMap
SignatureRef string
HashAlgorithm crypto.Hash
}

Expand Down Expand Up @@ -84,6 +84,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
Annotations: c.Annotations.Annotations,
RegistryClientOpts: ociremoteOpts,
CertEmail: c.CertEmail,
SignatureRef: c.SignatureRef,
}
if c.CheckClaims {
co.ClaimVerifier = cosign.SimpleClaimVerifier
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_dockerfile_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_manifest_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 66 additions & 5 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/sigstore/cosign/pkg/blob"
"github.com/sigstore/cosign/pkg/oci/static"

"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
Expand All @@ -43,6 +47,7 @@ import (
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/sigstore/sigstore/pkg/signature/options"
sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"
)

// CheckOpts are the options for checking signatures.
Expand All @@ -67,6 +72,9 @@ type CheckOpts struct {
RootCerts *x509.CertPool
// CertEmail is the email expected for a certificate to be valid. The empty string means any certificate can be valid.
CertEmail string

// SignatureRef is the reference to the signature file
SignatureRef string
}

func getSignedEntity(signedImgRef name.Reference, regClientOpts []ociremote.Option) (oci.SignedEntity, v1.Hash, error) {
Expand Down Expand Up @@ -186,6 +194,15 @@ func tlogValidateCertificate(ctx context.Context, rekorClient *client.Rekor, sig
return checkExpiry(cert, time.Unix(*e.IntegratedTime, 0))
}

type fakeOCISignatures struct {
oci.Signatures
signatures []oci.Signature
}

func (fos *fakeOCISignatures) Get() ([]oci.Signature, error) {
return fos.signatures, nil
}

// 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 VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
Expand All @@ -196,15 +213,54 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co

// TODO(mattmoor): We could implement recursive verification if we just wrapped
// most of the logic below here in a call to mutate.Map

se, h, err := getSignedEntity(signedImgRef, co.RegistryClientOpts)
if err != nil {
return nil, false, err
}
sigs, err := se.Signatures()
if err != nil {
return nil, false, err

var sigs oci.Signatures
sigRef := co.SignatureRef
if sigRef == "" {
sigs, err = se.Signatures()
if err != nil {
return nil, false, err
}
} else {
developer-guy marked this conversation as resolved.
Show resolved Hide resolved
var b64sig string
targetSig, err := blob.LoadFileOrURL(sigRef)
if err != nil {
if !os.IsNotExist(err) {
return nil, false, err
}
targetSig = []byte(sigRef)
}

if isb64(targetSig) {
b64sig = string(targetSig)
} else {
b64sig = base64.StdEncoding.EncodeToString(targetSig)
}

digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}

payload, err := (&sigPayload.Cosign{Image: digest}).MarshalJSON()

if err != nil {
return nil, false, err
}

sig, err := static.NewSignature(payload, b64sig)
if err != nil {
return nil, false, err
}
sigs = &fakeOCISignatures{
signatures: []oci.Signature{sig},
}
}

sl, err := sigs.Get()
if err != nil {
return nil, false, err
Expand All @@ -230,7 +286,7 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co
return err
}
if cert == nil {
return errors.New("no certificate found on signature")
return errors.New("no certificate found on sigRef")
developer-guy marked this conversation as resolved.
Show resolved Hide resolved
}
verifier, err = validateAndUnpackCert(cert, co)
if err != nil {
Expand Down Expand Up @@ -281,6 +337,11 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co
return checkedSignatures, bundleVerified, nil
}

func isb64(data []byte) bool {
developer-guy marked this conversation as resolved.
Show resolved Hide resolved
_, err := base64.StdEncoding.DecodeString(string(data))
return err == nil
}

// VerifyAttestations does all the main cosign checks in a loop, returning the verified attestations.
// If there were no valid attestations, we return an error.
func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
Expand Down