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

Added support for attaching Time stamp authority Response in attach command #3001

Merged
merged 9 commits into from
Jun 6, 2023
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func attachSignature() *cobra.Command {
PersistentPreRun: options.BindViper,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return attach.SignatureCmd(cmd.Context(), o.Registry, o.Signature, o.Payload, o.Cert, o.CertChain, args[0])
return attach.SignatureCmd(cmd.Context(), o.Registry, o.Signature, o.Payload, o.Cert, o.CertChain, o.TimeStampedSig, args[0])
},
}

Expand Down
15 changes: 12 additions & 3 deletions cmd/cosign/cli/attach/sig.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import (
"path/filepath"

"github.com/google/go-containerregistry/pkg/name"

"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/cosign/v2/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
"github.com/sigstore/cosign/v2/pkg/oci/static"
)

func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, imageRef string) error {
func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, timeStampedSigRef, imageRef string) error {
b64SigBytes, err := signatureBytes(sigRef)
if err != nil {
return err
Expand Down Expand Up @@ -73,6 +73,7 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef,

var cert []byte
var certChain []byte
var timeStampedSig []byte

if certRef != "" {
cert, err = os.ReadFile(filepath.Clean(certRef))
Expand All @@ -88,7 +89,15 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef,
}
}

newSig, err := mutate.Signature(sig, mutate.WithCertChain(cert, certChain))
if timeStampedSigRef != "" {
timeStampedSig, err = os.ReadFile(filepath.Clean(timeStampedSigRef))
if err != nil {
return err
}
}
bundle := bundle.TimestampToRFC3161Timestamp(timeStampedSig)

newSig, err := mutate.Signature(sig, mutate.WithCertChain(cert, certChain), mutate.WithRFC3161Timestamp(bundle))
if err != nil {
return err
}
Expand Down
13 changes: 8 additions & 5 deletions cmd/cosign/cli/options/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import (

// AttachSignatureOptions is the top level wrapper for the attach signature command.
type AttachSignatureOptions struct {
Signature string
Payload string
Cert string
CertChain string
Registry RegistryOptions
Signature string
Payload string
Cert string
CertChain string
TimeStampedSig string
Registry RegistryOptions
}

var _ Interface = (*AttachSignatureOptions)(nil)
Expand All @@ -54,6 +55,8 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) {
"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().StringVar(&o.TimeStampedSig, "tsr", "",
"path to the Time Stamped Signature Response from RFC3161 compliant TSA")
}

// AttachSBOMOptions is the top level wrapper for the attach sbom command.
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_attach_signature.md

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

104 changes: 102 additions & 2 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -55,6 +59,8 @@ import (
"github.com/sigstore/cosign/v2/cmd/cosign/cli/publickey"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify"
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/cosign/v2/pkg/cosign/env"
Expand Down Expand Up @@ -112,6 +118,23 @@ var verifyTSA = func(keyRef, imageRef string, checkClaims bool, annotations map[
return cmd.Exec(context.Background(), args)
}

var verifyKeylessTSA = func(imageRef string, tsaCertChain string, skipSCT bool, skipTlogVerify bool) error {
cmd := cliverify.VerifyCommand{
CertVerifyOptions: options.CertVerifyOptions{
CertOidcIssuerRegexp: ".*",
CertIdentityRegexp: ".*",
},
HashAlgorithm: crypto.SHA256,
TSACertChainPath: tsaCertChain,
IgnoreSCT: skipSCT,
IgnoreTlog: skipTlogVerify,
}

args := []string{imageRef}

return cmd.Exec(context.Background(), args)
}

// Used to verify local images stored on disk
var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
cmd := cliverify.VerifyCommand{
Expand Down Expand Up @@ -809,6 +832,84 @@ func TestAttestationRFC3161Timestamp(t *testing.T) {
must(verifyAttestation.Exec(ctx, []string{imgName}), t)
}

func TestAttachWithRFC3161Timestamp(t *testing.T) {
ctx := context.Background()
// TSA server needed to create timestamp
viper.Set("timestamp-signer", "memory")
apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, 10*time.Second, 10*time.Second)
server := httptest.NewServer(apiServer.GetHandler())
t.Cleanup(server.Close)

repo, stop := reg(t)
defer stop()
td := t.TempDir()

imgName := path.Join(repo, "cosign-attach-timestamp-e2e")

_, _, cleanup := mkimage(t, imgName)
defer cleanup()

b := bytes.Buffer{}
must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t)

rootCert, rootKey, _ := GenerateRootCa()
subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey)
leafCert, privKey, _ := GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey)
pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

payloadref := mkfile(b.String(), td, t)

h := sha256.Sum256(b.Bytes())
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)
b64signature := base64.StdEncoding.EncodeToString([]byte(signature))
sigRef := mkfile(b64signature, td, t)
pemleafRef := mkfile(string(pemLeaf), td, t)
pemrootRef := mkfile(string(pemRoot), td, t)

certchainRef := mkfile(string(append(pemSub[:], pemRoot[:]...)), td, t)

t.Setenv("SIGSTORE_ROOT_FILE", pemrootRef)

tsclient, err := tsaclient.GetTimestampClient(server.URL)
if err != nil {
t.Error(err)
}

chain, err := tsclient.Timestamp.GetTimestampCertChain(nil)
if err != nil {
t.Fatalf("unexpected error getting timestamp chain: %v", err)
}

file, err := os.CreateTemp(os.TempDir(), "tempfile")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString(chain.Payload)
if err != nil {
t.Fatalf("error writing chain payload to temp file: %v", err)
}

tsBytes, err := tsa.GetTimestampedSignature(signature, client.NewTSAClient(server.URL+"/api/v1/timestamp"))
if err != nil {
t.Fatalf("unexpected error creating timestamp: %v", err)
}
rfc3161TSRef := mkfile(string(tsBytes), td, t)

// Upload it!
err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, rfc3161TSRef, imgName)
if err != nil {
t.Fatal(err)
}

must(verifyKeylessTSA(imgName, file.Name(), true, true), t)
}

func TestRekorBundle(t *testing.T) {
// turn on the tlog
defer setenv(t, env.VariableExperimental.String(), "1")()
Expand Down Expand Up @@ -1490,9 +1591,8 @@ func TestUploadDownload(t *testing.T) {
} else {
sigRef = signature
}

// Upload it!
err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, "", "", imgName)
err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, "", "", "", imgName)
if testCase.expectedErr {
mustErr(err, t)
} else {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e_test_attach.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ crane manifest $(./cosign triangulate $IMAGE_URI_DIGEST) | grep -q "dev.sigstore
## Verify Signature, payload, cert and cert-chain using SIGSTORE_ROOT_FILE

export SIGSTORE_ROOT_FILE=./rootcert.pem
./cosign verify $IMAGE_URI_DIGEST --insecure-ignore-sct --insecure-skip-tlog-verify --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*'
./cosign verify $IMAGE_URI_DIGEST --insecure-ignore-sct --insecure-ignore-tlog --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*'


# clean up a bit
Expand Down