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

Attestation/Blob signing and verification using a RFC3161 time-stamping server #2464

Merged
merged 11 commits into from
Nov 23, 2022
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description needs to be updated, this was copied from BundlePath.

_ = 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
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
}

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit for style: if _, err := tsa.GetTimestampedSignature(...); err != nil { ... }

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
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
}
// 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document somewhere that we're generating a timestamp over the base64 signature, not the raw bytes? This was somewhat discussed in sigstore/timestamp-authority#116 - Cosign is being opinionated in using the base64-encoded sig, which I agree with, just think we should be explicit about this.

Update the SPEC docs for the OCI annotations?


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 != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we require that if ko.TSAServerURL is set, ko.RFC3161TimestampPath must also be? Otherwise it's unnecessarily fetching a timestamp without persisting it.

signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig)

contents, err := json.Marshal(signedPayload)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be writing signedPayload.RFC3161Timestamp, not signedPayload.Base64Signature?

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