Skip to content

Commit

Permalink
attest-blob: add functionality for keyless signing
Browse files Browse the repository at this point in the history
Signed-off-by: Asra Ali <asraa@google.com>

add check for timestamp path when using ts server

Signed-off-by: Asra Ali <asraa@google.com>

docgen

Signed-off-by: Asra Ali <asraa@google.com>

update

Signed-off-by: Asra Ali <asraa@google.com>
  • Loading branch information
asraa committed Dec 6, 2022
1 parent 6566619 commit d0e2841
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 28 deletions.
114 changes: 97 additions & 17 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -26,42 +27,68 @@ import (
"path"
"path/filepath"
"strings"
"time"

"github.com/pkg/errors"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/internal/pkg/cosign/tsa"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/attestation"
cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
"github.com/sigstore/cosign/pkg/types"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
)

// nolint
type AttestBlobCommand struct {
KeyRef string
options.KeyOpts
CertPath string
CertChainPath string

ArtifactHash string

PredicatePath string
PredicateType string

TlogUpload bool
Timeout time.Duration

OutputSignature string
OutputAttestation string

PassFunc cosign.PassFunc
OutputCertificate string
}

// nolint
func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error {
// TODO: Add in experimental keyless mode
if !options.OneOf(c.KeyRef) {
// We can't have both a key and a security key
if options.NOf(c.KeyRef, c.Sk) > 1 {
return &options.KeyParseError{}
}

if c.Timeout != 0 {
var cancelFn context.CancelFunc
ctx, cancelFn = context.WithTimeout(ctx, c.Timeout)
defer cancelFn()
}

if c.TSAServerURL != "" && c.RFC3161TimestampPath == "" {
return errors.New("expected an rfc3161-timestamp path when using a TSA server")
}

sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
if err != nil {
return fmt.Errorf("getting signer: %w", err)
}
defer sv.Close()

var artifact []byte
var hexDigest string
var err error

if c.ArtifactHash == "" {
if artifactPath == "-" {
Expand All @@ -75,17 +102,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
}
}

ko := options.KeyOpts{
KeyRef: c.KeyRef,
PassFunc: c.PassFunc,
}

sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)
if err != nil {
return errors.Wrap(err, "getting signer")
}
defer sv.Close()

if c.ArtifactHash == "" {
digest, _, err := signature.ComputeDigestForSigning(bytes.NewReader(artifact), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384})
if err != nil {
Expand Down Expand Up @@ -126,6 +142,54 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
return errors.Wrap(err, "signing")
}

signedPayload := cosign.LocalSignedPayload{}
if c.TSAServerURL != "" {
clientTSA, err := tsaclient.GetTimestampClient(c.TSAServerURL)
if err != nil {
return fmt.Errorf("failed to create TSA client: %w", err)
}
rfc3161Timestamp, err := tsa.GetTimestampedSignature(sig, clientTSA)
if err != nil {
return err
}
if err := os.WriteFile(c.RFC3161TimestampPath, rfc3161Timestamp, 0600); err != nil {
return fmt.Errorf("create rfc3161 timestamp file: %w", err)
}
fmt.Printf("RF3161 timestamp bundle wrote in the file %s\n", c.RFC3161TimestampPath)
}

var rekorBytes []byte
if sign.ShouldUploadToTlog(ctx, c.KeyOpts, nil, c.TlogUpload) {
rekorBytes, err = sv.Bytes(ctx)
if err != nil {
return err
}
rekorClient, err := rekor.NewClient(c.RekorURL)
if err != nil {
return err
}
entry, err := cosign.TLogUploadInTotoAttestation(ctx, rekorClient, sig, rekorBytes)
if err != nil {
return err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
signedPayload.Bundle = cbundle.EntryToBundle(entry)
}

if c.BundlePath != "" {
signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig)
signedPayload.Cert = base64.StdEncoding.EncodeToString(rekorBytes)

contents, err := json.Marshal(signedPayload)
if err != nil {
return err
}
if err := os.WriteFile(c.BundlePath, contents, 0600); err != nil {
return fmt.Errorf("create bundle file: %w", err)
}
fmt.Printf("Bundle wrote in the file %s\n", c.BundlePath)
}

if c.OutputSignature != "" {
if err := os.WriteFile(c.OutputSignature, sig, 0600); err != nil {
return fmt.Errorf("create signature file: %w", err)
Expand All @@ -142,5 +206,21 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
fmt.Fprintf(os.Stderr, "Attestation written in %s\n", c.OutputAttestation)
}

if c.OutputCertificate != "" {
signer, err := sv.Bytes(ctx)
if err != nil {
return fmt.Errorf("error getting signer: %w", err)
}
cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer)
// signer is a certificate
if err == nil && len(cert) == 1 {
bts := signer
if err := os.WriteFile(c.OutputCertificate, bts, 0600); err != nil {
return fmt.Errorf("create certificate file: %w", err)
}
fmt.Printf("Certificate wrote in the file %s\n", c.OutputCertificate)
}
}

return nil
}
31 changes: 30 additions & 1 deletion cmd/cosign/cli/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cli

import (
"github.com/sigstore/cosign/cmd/cosign/cli/attest"
"github.com/sigstore/cosign/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -46,13 +47,41 @@ func AttestBlob() *cobra.Command {
Args: cobra.ExactArgs(1),
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error {
oidcClientSecret, err := o.OIDC.ClientSecret()
if err != nil {
return err
}
ko := options.KeyOpts{
KeyRef: o.Key,
PassFunc: generate.GetPass,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
FulcioURL: o.Fulcio.URL,
IDToken: o.Fulcio.IdentityToken,
InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify,
RekorURL: o.Rekor.URL,
OIDCIssuer: o.OIDC.Issuer,
OIDCClientID: o.OIDC.ClientID,
OIDCClientSecret: oidcClientSecret,
OIDCRedirectURL: o.OIDC.RedirectURL,
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
RFC3161TimestampPath: o.RFC3161TimestampPath,
BundlePath: o.BundlePath,
}
v := attest.AttestBlobCommand{
KeyRef: o.Key,
KeyOpts: ko,
CertPath: o.Cert,
CertChainPath: o.CertChain,
ArtifactHash: o.Hash,
TlogUpload: o.TlogUpload,
PredicateType: o.Predicate.Type,
PredicatePath: o.Predicate.Path,
OutputSignature: o.OutputSignature,
OutputAttestation: o.OutputAttestation,
OutputCertificate: o.OutputCertificate,
Timeout: ro.Timeout,
}
return v.Exec(cmd.Context(), args[0])
},
Expand Down
58 changes: 55 additions & 3 deletions cmd/cosign/cli/options/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,53 @@ import (

// AttestOptions is the top level wrapper for the attest command.
type AttestBlobOptions struct {
Key string
Hash string
Key string
Cert string
CertChain string

SkipConfirmation bool
TlogUpload bool
TSAServerURL string
RFC3161TimestampPath string

Hash string
Predicate PredicateLocalOptions

OutputSignature string
OutputAttestation string
OutputCertificate string
BundlePath string

Predicate PredicateLocalOptions
Rekor RekorOptions
Fulcio FulcioOptions
OIDC OIDCOptions
SecurityKey SecurityKeyOptions
}

var _ Interface = (*AttestOptions)(nil)

// AddFlags implements Interface
func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) {
o.Predicate.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.OIDC.AddFlags(cmd)
o.SecurityKey.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.Flags().SetAnnotation("key", cobra.BashCompFilenameExt, []string{"key"})

cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")
_ = cmd.Flags().SetAnnotation("certificate", cobra.BashCompFilenameExt, []string{"cert"})

cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA X.509 certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Included in the OCI Signature")
_ = cmd.Flags().SetAnnotation("certificate-chain", cobra.BashCompFilenameExt, []string{"cert"})

cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "",
"write the signature to FILE")
Expand All @@ -44,6 +75,27 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.OutputAttestation, "output-attestation", "",
"write the attestation to FILE")

cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "",
"write the certificate to FILE")
_ = cmd.Flags().SetAnnotation("key", cobra.BashCompFilenameExt, []string{})

cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"write everything required to verify the blob to a FILE")
_ = cmd.Flags().SetAnnotation("bundle", cobra.BashCompFilenameExt, []string{})

cmd.Flags().StringVar(&o.Hash, "hash", "",
"hash of blob in hexadecimal (base16). Used if you want to sign an artifact stored elsewhere and have the hash")

cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")

cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", false,
"whether or not to upload to the tlog")

cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none")

cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "",
"write everything required to verify the blob to a FILE")
_ = cmd.Flags().SetAnnotation("rfc3161-timestamp-bundle", cobra.BashCompFilenameExt, []string{})
}
34 changes: 27 additions & 7 deletions doc/cosign_attest-blob.md

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

0 comments on commit d0e2841

Please sign in to comment.