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: sign --output-certificate and verify --cert #1095

Merged
merged 3 commits into from
Dec 2, 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
25 changes: 17 additions & 8 deletions cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ import (

// SignOptions is the top level wrapper for the sign command.
type SignOptions struct {
Key string
Cert string
Upload bool
Output string
PayloadPath string
Force bool
Recursive bool
Attachment string
Key string
Cert string
Upload bool
Output string // deprecated: TODO remove when the output flag is fully deprecated
OutputSignature string // TODO: this should be the root output file arg.
OutputCertificate string
PayloadPath string
Force bool
Recursive bool
Attachment string

Rekor RekorOptions
Fulcio FulcioOptions
Expand Down Expand Up @@ -58,9 +60,16 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.Upload, "upload", true,
"whether to upload the signature")

// TODO: remove when output flag is fully deprecated
cmd.Flags().StringVar(&o.Output, "output", "",
"write the signature to FILE")

cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "",
"write the signature to FILE")

cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "",
"write the certificate to FILE")

cmd.Flags().StringVar(&o.PayloadPath, "payload", "",
"path to a payload file to use rather than generating one")

Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
// VerifyOptions is the top level wrapper for the `verify` command.
type VerifyOptions struct {
Key string
Cert string
CertEmail string // TODO: merge into fulcio option as read mode?
CheckClaims bool
Attachment string
Expand Down Expand Up @@ -49,6 +50,9 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")

cmd.Flags().StringVar(&o.Cert, "cert", "",
"path to the public certificate")

cmd.Flags().StringVar(&o.CertEmail, "cert-email", "",
"the email expected in a valid fulcio cert")

Expand Down
4 changes: 3 additions & 1 deletion cmd/cosign/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/sigstore/cosign/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
Expand All @@ -28,6 +29,7 @@ import (

func Sign() *cobra.Command {
o := &options.SignOptions{}
viper.RegisterAlias("output", "output-signature")

cmd := &cobra.Command{
Use: "sign",
Expand Down Expand Up @@ -89,7 +91,7 @@ func Sign() *cobra.Command {
if err != nil {
return err
}
if err := sign.SignCmd(cmd.Context(), ko, o.Registry, annotationsMap.Annotations, args, o.Cert, o.Upload, o.Output, o.PayloadPath, o.Force, o.Recursive, o.Attachment); err != nil {
if err := sign.SignCmd(cmd.Context(), ko, o.Registry, annotationsMap.Annotations, args, o.Cert, o.Upload, o.OutputSignature, o.OutputCertificate, o.PayloadPath, o.Force, o.Recursive, o.Attachment); err != nil {
if o.Attachment == "" {
return errors.Wrapf(err, "signing %v", args)
}
Expand Down
38 changes: 32 additions & 6 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
rekorClient "github.com/sigstore/rekor/pkg/client"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"
)

Expand Down Expand Up @@ -97,7 +98,7 @@ func GetAttachedImageRef(ref name.Reference, attachment string, opts ...ociremot

// nolint
func SignCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOptions, annotations map[string]interface{},
imgs []string, certPath string, upload bool, output string, payloadPath string, force bool, recursive bool, attachment string) error {
imgs []string, certPath string, upload bool, outputSignature, outputCertificate string, payloadPath string, force bool, recursive bool, attachment string) error {
if options.EnableExperimental() {
if options.NOf(ko.KeyRef, ko.Sk) > 1 {
return &options.KeyParseError{}
Expand Down Expand Up @@ -156,7 +157,7 @@ func SignCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOptions, a
if err != nil {
return errors.Wrap(err, "accessing image")
}
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, output, force, dd, sv, se)
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, outputSignature, outputCertificate, force, dd, sv, se)
if err != nil {
return errors.Wrap(err, "signing digest")
}
Expand All @@ -176,7 +177,7 @@ func SignCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOptions, a
}
digest := ref.Context().Digest(d.String())

err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, output, force, dd, sv, se)
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, outputSignature, outputCertificate, force, dd, sv, se)
if err != nil {
return errors.Wrap(err, "signing digest")
}
Expand All @@ -190,7 +191,7 @@ func SignCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOptions, a
}

func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyOpts,
regOpts options.RegistryOptions, annotations map[string]interface{}, upload bool, output string, force bool,
regOpts options.RegistryOptions, annotations map[string]interface{}, upload bool, outputSignature, outputCertificate string, force bool,
dd mutate.DupeDetector, sv *SignerVerifier, se oci.SignedEntity) error {
var err error
// The payload can be passed to skip generation.
Expand Down Expand Up @@ -225,8 +226,8 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO
return err
}

if output != "" {
out, err := os.Create(output)
if outputSignature != "" {
out, err := os.Create(outputSignature)
if err != nil {
return errors.Wrap(err, "create signature file")
}
Expand Down Expand Up @@ -260,6 +261,18 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO
return err
}

if outputCertificate != "" {
rekorBytes, err := sv.Bytes(ctx)
if err != nil {
return err
}

if err := os.WriteFile(outputCertificate, rekorBytes, 0600); err != nil {
return err
}
// TODO: maybe accept a --b64 flag as well?
}

return nil
}

Expand Down Expand Up @@ -432,3 +445,16 @@ func (c *SignerVerifier) Close() {
c.close()
}
}

func (c *SignerVerifier) Bytes(ctx context.Context) ([]byte, error) {
if c.Cert != nil {
fmt.Fprintf(os.Stderr, "using ephemeral certificate:\n%s\n", string(c.Cert))
return c.Cert, nil
}

pemBytes, err := sigs.PublicKeyPem(c, signatureoptions.WithContext(ctx))
if err != nil {
return nil, err
}
return pemBytes, nil
}
14 changes: 3 additions & 11 deletions cmd/cosign/cli/sign/sign_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (

"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/signature"
rekorClient "github.com/sigstore/rekor/pkg/client"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
)
Expand Down Expand Up @@ -84,16 +83,9 @@ func SignBlobCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOption
}

if options.EnableExperimental() {
// TODO: Refactor with sign.go
if sv.Cert != nil {
fmt.Fprintf(os.Stderr, "signing with ephemeral certificate:\n%s\n", string(sv.Cert))
rekorBytes = sv.Cert
} else {
pemBytes, err := signature.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
if err != nil {
return nil, err
}
rekorBytes = pemBytes
rekorBytes, err := sv.Bytes(ctx)
if err != nil {
return nil, err
}
rekorClient, err := rekorClient.GetRekorClient(ko.RekorURL)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/sign/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestSignCmdLocalKeyAndSk(t *testing.T) {
Sk: true,
},
} {
err := SignCmd(ctx, ko, options.RegistryOptions{}, nil, nil, "", false, "", "", false, false, "")
err := SignCmd(ctx, ko, options.RegistryOptions{}, nil, nil, "", false, "", "", "", false, false, "")
if (errors.Is(err, &options.KeyParseError{}) == false) {
t.Fatal("expected KeyParseError")
}
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ against the transparency log.`,
RegistryOptions: o.Registry,
CheckClaims: o.CheckClaims,
KeyRef: o.Key,
CertRef: o.Cert,
CertEmail: o.CertEmail,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Expand Down
41 changes: 38 additions & 3 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package verify
import (
"context"
"crypto"
"crypto/ecdsa"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
Expand All @@ -34,6 +36,7 @@ import (
"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
"github.com/sigstore/cosign/pkg/oci"
sigs "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/payload"
)
Expand All @@ -44,6 +47,7 @@ type VerifyCommand struct {
options.RegistryOptions
CheckClaims bool
KeyRef string
CertRef string
CertEmail string
Sk bool
Slot string
Expand Down Expand Up @@ -73,7 +77,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
c.HashAlgorithm = crypto.SHA256
}

if !options.OneOf(c.KeyRef, c.Sk) && !options.EnableExperimental() {
if !options.OneOf(c.KeyRef, c.CertRef, c.Sk) && !options.EnableExperimental() {
return &options.KeyParseError{}
}
ociremoteOpts, err := c.ClientOpts(ctx)
Expand All @@ -94,10 +98,12 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
co.RootCerts = fulcio.GetRoots()
}
keyRef := c.KeyRef
certRef := c.CertRef

// Keys are optional!
var pubKey signature.Verifier
if keyRef != "" {
switch {
case keyRef != "":
pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm)
if err != nil {
return errors.Wrap(err, "loading public key")
Expand All @@ -106,7 +112,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
if ok {
defer pkcs11Key.Close()
}
} else if c.Sk {
case c.Sk:
sk, err := pivkey.GetKeyWithSlot(c.Slot)
if err != nil {
return errors.Wrap(err, "opening piv token")
Expand All @@ -116,6 +122,11 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
if err != nil {
return errors.Wrap(err, "initializing piv token verifier")
}
case certRef != "":
pubKey, err = loadCertFromFile(c.CertRef)
if err != nil {
return err
}
}
co.SigVerifier = pubKey

Expand Down Expand Up @@ -225,3 +236,27 @@ func PrintVerification(imgRef string, verified []oci.Signature, output string) {
fmt.Printf("\n%s\n", string(b))
}
}

func loadCertFromFile(path string) (*signature.ECDSAVerifier, error) {
pems, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var out []byte
out, err = base64.StdEncoding.DecodeString(string(pems))
if err != nil {
// not a base64
out = pems
}

certs, err := cryptoutils.UnmarshalCertificatesFromPEM(out)
if err != nil {
return nil, err
}
if len(certs) == 0 {
return nil, errors.New("no certs found in pem file")
}
cert := certs[0]
return signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
}
22 changes: 1 addition & 21 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,27 +113,7 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, sigRef, blobRe
return errors.Wrap(err, "loading public key from token")
}
case certRef != "":
pems, err := os.ReadFile(certRef)
if err != nil {
return err
}

var out []byte
out, err = base64.StdEncoding.DecodeString(string(pems))
if err != nil {
// not a base64
out = pems
}

certs, err := cryptoutils.UnmarshalCertificatesFromPEM(out)
if err != nil {
return err
}
if len(certs) == 0 {
return errors.New("no certs found in pem file")
}
cert = certs[0]
pubKey, err = sigstoresigs.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
pubKey, err = loadCertFromFile(certRef)
if err != nil {
return err
}
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.

2 changes: 2 additions & 0 deletions doc/cosign_sign.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.

Loading