Skip to content

Commit 4393313

Browse files
authored
Fix bug in attest-blob when using a timestamp authority with new bundles (#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]>
1 parent 081dea1 commit 4393313

File tree

3 files changed

+146
-4
lines changed

3 files changed

+146
-4
lines changed

cmd/cosign/cli/attest/attest.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,14 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
175175
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
176176
}
177177
if c.KeyOpts.TSAServerURL != "" {
178-
// Here we get the response from the timestamped authority server
178+
// TODO - change this when we implement protobuf / new bundle support
179+
//
180+
// Historically, cosign sent the entire JSON DSSE Envelope to the
181+
// timestamp authority. However, when sigstore clients are verifying a
182+
// bundle they will use the DSSE Sig field, so we choose what signature
183+
// to send to the timestamp authority based on our output format.
184+
//
185+
// See cmd/cosign/cli/attest/attest_blob.go
179186
responseBytes, err := tsa.GetTimestampedSignature(signedPayload, tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL))
180187
if err != nil {
181188
return err

cmd/cosign/cli/attest/attest_blob.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,35 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
165165
var rekorEntry *models.LogEntryAnon
166166

167167
if c.TSAServerURL != "" {
168-
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
169-
if err != nil {
170-
return err
168+
// We need to decide what signature to send to the timestamp authority.
169+
//
170+
// Historically, cosign sent `sig`, which is the entire JSON DSSE
171+
// Envelope. However, when sigstore clients are verifying a bundle they
172+
// will use the DSSE Sig field, so we choose what signature to send to
173+
// the timestamp authority based on our output format.
174+
if c.NewBundleFormat {
175+
var envelope dsse.Envelope
176+
err = json.Unmarshal(sig, &envelope)
177+
if err != nil {
178+
return err
179+
}
180+
if len(envelope.Signatures) == 0 {
181+
return fmt.Errorf("envelope has no signatures")
182+
}
183+
envelopeSigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
184+
if err != nil {
185+
return err
186+
}
187+
188+
timestampBytes, err = tsa.GetTimestampedSignature(envelopeSigBytes, client.NewTSAClient(c.TSAServerURL))
189+
if err != nil {
190+
return err
191+
}
192+
} else {
193+
timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(c.TSAServerURL))
194+
if err != nil {
195+
return err
196+
}
171197
}
172198
rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes)
173199
// TODO: Consider uploading RFC3161 TS to Rekor

test/e2e_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import (
6868
"github.com/sigstore/cosign/v2/pkg/cosign/kubernetes"
6969
"github.com/sigstore/cosign/v2/pkg/oci/mutate"
7070
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
71+
"github.com/sigstore/sigstore-go/pkg/root"
7172
"github.com/sigstore/sigstore/pkg/signature/payload"
7273
tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
7374
"github.com/sigstore/timestamp-authority/pkg/server"
@@ -859,6 +860,114 @@ func TestAttestationRFC3161Timestamp(t *testing.T) {
859860
must(verifyAttestation.Exec(ctx, []string{imgName}), t)
860861
}
861862

863+
func TestAttestationBlobRFC3161Timestamp(t *testing.T) {
864+
// TSA server needed to create timestamp
865+
viper.Set("timestamp-signer", "memory")
866+
viper.Set("timestamp-signer-hash", "sha256")
867+
apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
868+
server := httptest.NewServer(apiServer.GetHandler())
869+
t.Cleanup(server.Close)
870+
871+
blob := "someblob"
872+
predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
873+
predicateType := "slsaprovenance"
874+
875+
td := t.TempDir()
876+
t.Cleanup(func() {
877+
os.RemoveAll(td)
878+
})
879+
880+
bp := filepath.Join(td, blob)
881+
if err := os.WriteFile(bp, []byte(blob), 0600); err != nil {
882+
t.Fatal(err)
883+
}
884+
885+
predicatePath := filepath.Join(td, "predicate")
886+
if err := os.WriteFile(predicatePath, []byte(predicate), 0600); err != nil {
887+
t.Fatal(err)
888+
}
889+
890+
bundlePath := filepath.Join(td, "bundle.sigstore.json")
891+
_, privKeyPath, pubKeyPath := keypair(t, td)
892+
893+
ctx := context.Background()
894+
ko := options.KeyOpts{
895+
KeyRef: privKeyPath,
896+
BundlePath: bundlePath,
897+
NewBundleFormat: true,
898+
TSAServerURL: server.URL + "/api/v1/timestamp",
899+
PassFunc: passFunc,
900+
}
901+
902+
attestBlobCmd := attest.AttestBlobCommand{
903+
KeyOpts: ko,
904+
PredicatePath: predicatePath,
905+
PredicateType: predicateType,
906+
Timeout: 30 * time.Second,
907+
TlogUpload: false,
908+
RekorEntryType: "dsse",
909+
}
910+
must(attestBlobCmd.Exec(ctx, bp), t)
911+
912+
client, err := tsaclient.GetTimestampClient(server.URL)
913+
if err != nil {
914+
t.Error(err)
915+
}
916+
917+
chain, err := client.Timestamp.GetTimestampCertChain(nil)
918+
if err != nil {
919+
t.Fatalf("unexpected error getting timestamp chain: %v", err)
920+
}
921+
922+
var certs []*x509.Certificate
923+
for block, contents := pem.Decode([]byte(chain.Payload)); ; block, contents = pem.Decode(contents) {
924+
cert, err := x509.ParseCertificate(block.Bytes)
925+
if err != nil {
926+
t.Error(err)
927+
}
928+
certs = append(certs, cert)
929+
930+
if len(contents) == 0 {
931+
break
932+
}
933+
}
934+
935+
tsaCA := root.CertificateAuthority{
936+
Root: certs[len(certs)-1],
937+
Intermediates: certs[:len(certs)-1],
938+
}
939+
940+
trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, []root.CertificateAuthority{tsaCA}, nil)
941+
if err != nil {
942+
t.Error(err)
943+
}
944+
945+
trustedRootPath := filepath.Join(td, "trustedroot.json")
946+
trustedRootBytes, err := trustedRoot.MarshalJSON()
947+
if err != nil {
948+
t.Error(err)
949+
}
950+
if err := os.WriteFile(trustedRootPath, trustedRootBytes, 0600); err != nil {
951+
t.Fatal(err)
952+
}
953+
954+
ko = options.KeyOpts{
955+
KeyRef: pubKeyPath,
956+
BundlePath: bundlePath,
957+
NewBundleFormat: true,
958+
}
959+
960+
verifyBlobAttestation := cliverify.VerifyBlobAttestationCommand{
961+
KeyOpts: ko,
962+
PredicateType: predicateType,
963+
IgnoreTlog: true,
964+
CheckClaims: true,
965+
TrustedRootPath: trustedRootPath,
966+
}
967+
968+
must(verifyBlobAttestation.Exec(ctx, bp), t)
969+
}
970+
862971
func TestVerifyWithCARoots(t *testing.T) {
863972
ctx := context.Background()
864973
// TSA server needed to create timestamp

0 commit comments

Comments
 (0)