Skip to content

Commit

Permalink
Attestation/Blob signing and verification using a RFC3161 time-stampi…
Browse files Browse the repository at this point in the history
…ng server (#2464)

* add support for tsa signing and verification of images

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* address reviewer comments

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* add support for both tsa and rekor verifications

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* signing and verification of blobs using time-stamping

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* adjust code to latest changes

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* add support for attestations

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* set right annotation

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* add test for blob signing/verification

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* ensure b64 signature is time-stamped

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* test: add attestation verify test

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

* refactor functions and flags

Signed-off-by: Hector Fernandez <hector@chainguard.dev>

Signed-off-by: Hector Fernandez <hector@chainguard.dev>
  • Loading branch information
hectorj2f authored Nov 23, 2022
1 parent 5a3c90f commit 7e967e8
Show file tree
Hide file tree
Showing 26 changed files with 548 additions and 54 deletions.
1 change: 1 addition & 0 deletions cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func Attest() *cobra.Command {
OIDCRedirectURL: o.OIDC.RedirectURL,
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
}
attestCommand := attest.AttestCommand{
KeyOpts: ko,
Expand Down
19 changes: 18 additions & 1 deletion cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"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"
Expand All @@ -43,6 +44,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/sigstore/pkg/signature/dsse"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
)

type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)
Expand Down Expand Up @@ -77,6 +79,7 @@ type AttestCommand struct {
Replace bool
Timeout time.Duration
TlogUpload bool
TSAServerURL string
}

// nolint
Expand Down Expand Up @@ -164,9 +167,23 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
if sv.Cert != nil {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}
if c.KeyOpts.TSAServerURL != "" {
clientTSA, err := tsaclient.GetTimestampClient(c.KeyOpts.TSAServerURL)
if err != nil {
return fmt.Errorf("failed to create TSA client: %w", err)
}

// Here we get the response from the timestamped authority server
responseBytes, err := tsa.GetTimestampedSignature(signedPayload, clientTSA)
if err != nil {
return err
}
bundle := cbundle.TimestampToRFC3161Timestamp(responseBytes)

opts = append(opts, static.WithRFC3161Timestamp(bundle))
}
// Check whether we should be uploading to the transparency log
if sign.ShouldUploadToTlog(ctx, c.KeyOpts, digest, c.SkipConfirmation, c.TlogUpload, "") {
if sign.ShouldUploadToTlog(ctx, c.KeyOpts, digest, c.TlogUpload) {
bundle, err := uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b)
})
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type AttestOptions struct {
Replace bool
SkipConfirmation bool
TlogUpload bool
TSAServerURL string

Rekor RekorOptions
Fulcio FulcioOptions
Expand Down Expand Up @@ -78,4 +79,7 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) {

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")
}
2 changes: 2 additions & 0 deletions cmd/cosign/cli/options/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type KeyOpts struct {
BundlePath string
SkipConfirmation bool
TSAServerURL string
RFC3161TimestampPath string
TSACertChainPath string

// FulcioAuthFlow is the auth flow to use when authenticating against
// Fulcio. See https://pkg.go.dev/github.com/sigstore/cosign/cmd/cosign/cli/fulcio#pkg-constants
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type PolicySignOptions struct {
Rekor RekorOptions
SkipConfirmation bool
TlogUpload bool
TSAServerURL string

OIDC OIDCOptions
}
Expand All @@ -84,6 +85,9 @@ func (o *PolicySignOptions) AddFlags(cmd *cobra.Command) {
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")

o.Registry.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
Expand Down
35 changes: 22 additions & 13 deletions cmd/cosign/cli/options/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ import (
// SignBlobOptions is the top level wrapper for the sign-blob command.
// The new output-certificate flag is only in use when COSIGN_EXPERIMENTAL is enabled
type SignBlobOptions struct {
Key string
Base64Output 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
SecurityKey SecurityKeyOptions
Fulcio FulcioOptions
Rekor RekorOptions
OIDC OIDCOptions
Registry RegistryOptions
BundlePath string
SkipConfirmation bool
TlogUpload bool
Key string
Base64Output 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
SecurityKey SecurityKeyOptions
Fulcio FulcioOptions
Rekor RekorOptions
OIDC OIDCOptions
Registry RegistryOptions
BundlePath string
SkipConfirmation bool
TlogUpload bool
TSAServerURL string
RFC3161TimestampPath string
}

var _ Interface = (*SignBlobOptions)(nil)
Expand Down Expand Up @@ -74,4 +76,11 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) {

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{})
}
5 changes: 5 additions & 0 deletions cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ type VerifyBlobOptions struct {
Rekor RekorOptions
Registry RegistryOptions
CommonVerifyOptions CommonVerifyOptions

RFC3161TimestampPath string
}

var _ Interface = (*VerifyBlobOptions)(nil)
Expand All @@ -161,6 +163,9 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to bundle FILE")

cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "",
"path to timestamp bundle FILE")
}

// VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command.
Expand Down
17 changes: 16 additions & 1 deletion cmd/cosign/cli/policy_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/cmd/cosign/cli/upload"
"github.com/sigstore/cosign/internal/pkg/cosign/tsa"
"github.com/sigstore/sigstore/pkg/cryptoutils"
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"

"github.com/sigstore/cosign/pkg/cosign"
cremote "github.com/sigstore/cosign/pkg/cosign/remote"
Expand Down Expand Up @@ -192,6 +194,7 @@ func signPolicy() *cobra.Command {
OIDCRedirectURL: o.OIDC.RedirectURL,
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
}
sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)

Expand Down Expand Up @@ -260,8 +263,20 @@ func signPolicy() *cobra.Command {
return err
}

if o.TSAServerURL != "" {
clientTSA, err := tsaclient.GetTimestampClient(o.TSAServerURL)
if err != nil {
return fmt.Errorf("failed to create TSA client: %w", err)
}
// Here we get the response from the timestamped authority server
_, err = tsa.GetTimestampedSignature(signed.Signed, clientTSA)
if err != nil {
return err
}
}

// Upload to rekor
if sign.ShouldUploadToTlog(ctx, ko, ref, ko.SkipConfirmation, o.TlogUpload, "") {
if sign.ShouldUploadToTlog(ctx, ko, ref, o.TlogUpload) {
// TODO: Refactor with sign.go
rekorBytes := sv.Cert
rekorClient, err := rekor.NewClient(o.Rekor.URL)
Expand Down
11 changes: 5 additions & 6 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import (
"github.com/sigstore/sigstore/pkg/signature"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"

tsaclient "github.com/sigstore/timestamp-authority/pkg/client"

// Loads OIDC providers
Expand All @@ -69,17 +68,18 @@ const TagReferenceMessage string = `WARNING: Image reference %s uses a tag, not
images by tag will be removed in a future release.
`

func ShouldUploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Reference, skipConfirmation, tlogUpload bool, tsaServerURL string) bool {
func ShouldUploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Reference, tlogUpload bool) bool {
// Check if TSA signing is enabled and tlog upload is disabled
if !tlogUpload && tsaServerURL != "" {
if !tlogUpload && ko.TSAServerURL != "" {
fmt.Fprintln(os.Stderr, "\nWARNING: skipping transparency log upload")
return false
}
// If we aren't using keyless signing and --tlog-upload=false, return
if !keylessSigning(ko) && !tlogUpload {
return false
}

if skipConfirmation {
if ko.SkipConfirmation {
return true
}

Expand Down Expand Up @@ -204,7 +204,6 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO
return fmt.Errorf("computing digest: %w", err)
}
digest := ref.Context().Digest(d.String())

err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se)
if err != nil {
return fmt.Errorf("signing digest: %w", err)
Expand Down Expand Up @@ -247,7 +246,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti

s = tsa.NewSigner(s, clientTSA)
}
if ShouldUploadToTlog(ctx, ko, digest, ko.SkipConfirmation, tlogUpload, ko.TSAServerURL) {
if ShouldUploadToTlog(ctx, ko, digest, tlogUpload) {
rClient, err := rekor.NewClient(ko.RekorURL)
if err != nil {
return err
Expand Down
32 changes: 31 additions & 1 deletion cmd/cosign/cli/sign/sign_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import (
"os"
"path/filepath"

"github.com/sigstore/cosign/internal/pkg/cosign/tsa"
cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"

"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
Expand Down Expand Up @@ -65,7 +67,21 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.Re

signedPayload := cosign.LocalSignedPayload{}

if ShouldUploadToTlog(ctx, ko, nil, ko.SkipConfirmation, tlogUpload, "") {
if ko.TSAServerURL != "" {
clientTSA, err := tsaclient.GetTimestampClient(ko.TSAServerURL)
if err != nil {
return nil, fmt.Errorf("failed to create TSA client: %w", err)
}
b64Sig := []byte(base64.StdEncoding.EncodeToString(sig))

respBytes, err := tsa.GetTimestampedSignature(b64Sig, clientTSA)
if err != nil {
return nil, err
}

signedPayload.RFC3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes)
}
if ShouldUploadToTlog(ctx, ko, nil, tlogUpload) {
rekorBytes, err = sv.Bytes(ctx)
if err != nil {
return nil, err
Expand All @@ -82,6 +98,20 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.Re
signedPayload.Bundle = cbundle.EntryToBundle(entry)
}

// if bundle is specified, just do that and ignore the rest
if ko.RFC3161TimestampPath != "" {
signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig)

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

// if bundle is specified, just do that and ignore the rest
if ko.BundlePath != "" {
signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig)
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,17 @@ func SignBlob() *cobra.Command {
OIDCDisableProviders: o.OIDC.DisableAmbientProviders,
BundlePath: o.BundlePath,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
RFC3161TimestampPath: o.RFC3161TimestampPath,
}

for _, blob := range args {
// TODO: remove when the output flag has been deprecated
if o.Output != "" {
fmt.Fprintln(os.Stderr, "WARNING: the '--output' flag is deprecated and will be removed in the future. Use '--output-signature'")
o.OutputSignature = o.Output
}

if _, err := sign.SignBlobCmd(ro, ko, o.Registry, blob, o.Base64Output, o.OutputSignature, o.OutputCertificate, o.TlogUpload); err != nil {
return fmt.Errorf("signing %s: %w", blob, err)
}
Expand Down
14 changes: 9 additions & 5 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ against the transparency log.`,
LocalImage: o.LocalImage,
NameOptions: o.Registry.NameOptions(),
Offline: o.CommonVerifyOptions.Offline,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
SkipTlogVerify: o.CommonVerifyOptions.SkipTlogVerify,
}

return v.Exec(cmd.Context(), args)
Expand Down Expand Up @@ -275,11 +277,13 @@ The blob may be specified as a path to a file or - for stdin.`,
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error {
ko := options.KeyOpts{
KeyRef: o.Key,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
RekorURL: o.Rekor.URL,
BundlePath: o.BundlePath,
KeyRef: o.Key,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
RekorURL: o.Rekor.URL,
BundlePath: o.BundlePath,
RFC3161TimestampPath: o.RFC3161TimestampPath,
TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath,
}
verifyBlobCmd := &verify.VerifyBlobCmd{
KeyOpts: ko,
Expand Down
1 change: 0 additions & 1 deletion cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ type VerifyCommand struct {
LocalImage bool
NameOptions []name.Option
Offline bool
TSAServerURL string
TSACertChainPath string
SkipTlogVerify bool
}
Expand Down
Loading

0 comments on commit 7e967e8

Please sign in to comment.