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

attest-blob: add functionality for keyless signing #2515

Merged
merged 6 commits into from
Jan 4, 2023
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
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)
asraa marked this conversation as resolved.
Show resolved Hide resolved
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)
asraa marked this conversation as resolved.
Show resolved Hide resolved
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