Skip to content

Commit

Permalink
Fix bug in attest-blob when using a timestamp authority with new bund…
Browse files Browse the repository at this point in the history
…les (#3877)

* Fix bug in #3752

When adding bundles support to `attest-blob`, we sent the wrong data to
the timestamp authority to sign.

Signed-off-by: Zach Steindler <[email protected]>

* Only change timestamp authority signature behavior for new bundles

Also add TODO when we get to updating `cosign attest`

Signed-off-by: Zach Steindler <[email protected]>

* Add happy path e2e test

Signed-off-by: Zach Steindler <[email protected]>

---------

Signed-off-by: Zach Steindler <[email protected]>
  • Loading branch information
steiza authored Sep 18, 2024
1 parent 081dea1 commit 4393313
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 4 deletions.
9 changes: 8 additions & 1 deletion cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,14 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}
if c.KeyOpts.TSAServerURL != "" {
// Here we get the response from the timestamped authority server
// TODO - change this when we implement protobuf / new bundle support
//
// Historically, cosign sent the entire JSON DSSE Envelope to the
// timestamp authority. However, when sigstore clients are verifying a
// bundle they will use the DSSE Sig field, so we choose what signature
// to send to the timestamp authority based on our output format.
//
// See cmd/cosign/cli/attest/attest_blob.go
responseBytes, err := tsa.GetTimestampedSignature(signedPayload, tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL))
if err != nil {
return err
Expand Down
32 changes: 29 additions & 3 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,35 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
var rekorEntry *models.LogEntryAnon

if c.TSAServerURL != "" {
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
if err != nil {
return err
// We need to decide what signature to send to the timestamp authority.
//
// Historically, cosign sent `sig`, which is the entire JSON DSSE
// Envelope. However, when sigstore clients are verifying a bundle they
// will use the DSSE Sig field, so we choose what signature to send to
// the timestamp authority based on our output format.
if c.NewBundleFormat {
var envelope dsse.Envelope
err = json.Unmarshal(sig, &envelope)
if err != nil {
return err
}
if len(envelope.Signatures) == 0 {
return fmt.Errorf("envelope has no signatures")
}
envelopeSigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
if err != nil {
return err
}

timestampBytes, err = tsa.GetTimestampedSignature(envelopeSigBytes, client.NewTSAClient(c.TSAServerURL))
if err != nil {
return err
}
} else {
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
if err != nil {
return err
}
}
rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes)
// TODO: Consider uploading RFC3161 TS to Rekor
Expand Down
109 changes: 109 additions & 0 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import (
"github.com/sigstore/cosign/v2/pkg/cosign/kubernetes"
"github.com/sigstore/cosign/v2/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/signature/payload"
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
"github.com/sigstore/timestamp-authority/pkg/server"
Expand Down Expand Up @@ -859,6 +860,114 @@ func TestAttestationRFC3161Timestamp(t *testing.T) {
must(verifyAttestation.Exec(ctx, []string{imgName}), t)
}

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

blob := "someblob"
predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
predicateType := "slsaprovenance"

td := t.TempDir()
t.Cleanup(func() {
os.RemoveAll(td)
})

bp := filepath.Join(td, blob)
if err := os.WriteFile(bp, []byte(blob), 0600); err != nil {
t.Fatal(err)
}

predicatePath := filepath.Join(td, "predicate")
if err := os.WriteFile(predicatePath, []byte(predicate), 0600); err != nil {
t.Fatal(err)
}

bundlePath := filepath.Join(td, "bundle.sigstore.json")
_, privKeyPath, pubKeyPath := keypair(t, td)

ctx := context.Background()
ko := options.KeyOpts{
KeyRef: privKeyPath,
BundlePath: bundlePath,
NewBundleFormat: true,
TSAServerURL: server.URL + "/api/v1/timestamp",
PassFunc: passFunc,
}

attestBlobCmd := attest.AttestBlobCommand{
KeyOpts: ko,
PredicatePath: predicatePath,
PredicateType: predicateType,
Timeout: 30 * time.Second,
TlogUpload: false,
RekorEntryType: "dsse",
}
must(attestBlobCmd.Exec(ctx, bp), t)

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

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

var certs []*x509.Certificate
for block, contents := pem.Decode([]byte(chain.Payload)); ; block, contents = pem.Decode(contents) {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Error(err)
}
certs = append(certs, cert)

if len(contents) == 0 {
break
}
}

tsaCA := root.CertificateAuthority{
Root: certs[len(certs)-1],
Intermediates: certs[:len(certs)-1],
}

trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, []root.CertificateAuthority{tsaCA}, nil)
if err != nil {
t.Error(err)
}

trustedRootPath := filepath.Join(td, "trustedroot.json")
trustedRootBytes, err := trustedRoot.MarshalJSON()
if err != nil {
t.Error(err)
}
if err := os.WriteFile(trustedRootPath, trustedRootBytes, 0600); err != nil {
t.Fatal(err)
}

ko = options.KeyOpts{
KeyRef: pubKeyPath,
BundlePath: bundlePath,
NewBundleFormat: true,
}

verifyBlobAttestation := cliverify.VerifyBlobAttestationCommand{
KeyOpts: ko,
PredicateType: predicateType,
IgnoreTlog: true,
CheckClaims: true,
TrustedRootPath: trustedRootPath,
}

must(verifyBlobAttestation.Exec(ctx, bp), t)
}

func TestVerifyWithCARoots(t *testing.T) {
ctx := context.Background()
// TSA server needed to create timestamp
Expand Down

0 comments on commit 4393313

Please sign in to comment.