Skip to content

Commit

Permalink
attest-blob: add functionality for keyless signing (#2515)
Browse files Browse the repository at this point in the history
* attest-blob: add support for keyless

Signed-off-by: Asra Ali <asraa@google.com>
  • Loading branch information
asraa committed Jan 4, 2023
1 parent 631a26f commit 0081e1a
Show file tree
Hide file tree
Showing 6 changed files with 478 additions and 31 deletions.
136 changes: 119 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,39 +27,60 @@ import (
"path"
"path/filepath"
"strings"
"time"

"github.com/pkg/errors"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/cosign/attestation"
cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/cosign/v2/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")
}

var artifact []byte
var hexDigest string
var err error
Expand All @@ -75,17 +97,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 All @@ -95,7 +106,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
} else {
hexDigest = c.ArtifactHash
}
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)

fmt.Fprintln(os.Stderr, "Using predicate from:", c.PredicatePath)
predicate, err := os.Open(c.PredicatePath)
Expand All @@ -104,6 +114,13 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
}
defer predicate.Close()

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

base := path.Base(artifactPath)

sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Expand All @@ -126,6 +143,68 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
return errors.Wrap(err, "signing")
}

var rfc3161Timestamp *cbundle.RFC3161Timestamp
if c.TSAServerURL != "" {
clientTSA, err := tsaclient.GetTimestampClient(c.TSAServerURL)
if err != nil {
return fmt.Errorf("failed to create TSA client: %w", err)
}
respBytes, err := tsa.GetTimestampedSignature(sig, clientTSA)
if err != nil {
return err
}
rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes)
// TODO: Consider uploading RFC3161 TS to Rekor

if rfc3161Timestamp == nil {
return fmt.Errorf("rfc3161 timestamp is nil")
}
ts, err := json.Marshal(rfc3161Timestamp)
if err != nil {
return err
}
if err := os.WriteFile(c.RFC3161TimestampPath, ts, 0600); err != nil {
return fmt.Errorf("create RFC3161 timestamp file: %w", err)
}
fmt.Fprintln(os.Stderr, "RFC3161 timestamp bundle written to file ", c.RFC3161TimestampPath)
}

rekorBytes, err := sv.Bytes(ctx)
if err != nil {
return err
}
shouldUpload, err := sign.ShouldUploadToTlog(ctx, c.KeyOpts, nil, c.TlogUpload)
if err != nil {
return fmt.Errorf("upload to tlog: %w", err)
}
signedPayload := cosign.LocalSignedPayload{}
if shouldUpload {
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.Fprintln(os.Stderr, "Bundle wrote in the file ", 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 +221,28 @@ 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 {
fmt.Fprintln(os.Stderr, "Could not output signer certificate. Was a certificate used? ", err)
return nil

}
if len(cert) != 1 {
fmt.Fprintln(os.Stderr, "Could not output signer certificate. Expected a single certificate")
return nil
}
bts := signer
if err := os.WriteFile(c.OutputCertificate, bts, 0600); err != nil {
return fmt.Errorf("create certificate file: %w", err)
}
fmt.Fprintln(os.Stderr, "Certificate written to file ", c.OutputCertificate)
}

return nil
}
Loading

0 comments on commit 0081e1a

Please sign in to comment.