From 857ca58e048e927cad5406dfab196ec704c44003 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 30 May 2023 09:13:58 -0400 Subject: [PATCH 1/3] refactor bundle validation code, add support for DSSE rekor type Signed-off-by: Bob Callaway --- cmd/cosign/cli/verify/verify_blob_test.go | 82 +++++++++- go.mod | 10 +- go.sum | 11 ++ pkg/cosign/tlog.go | 27 ++++ pkg/cosign/verify.go | 182 +++++++--------------- 5 files changed, 180 insertions(+), 132 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index fab70bc36a5..77c803aaf6b 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -48,6 +48,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/rekor/pkg/types" + rekor_dsse "github.com/sigstore/rekor/pkg/types/dsse" "github.com/sigstore/rekor/pkg/types/hashedrekord" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" @@ -861,7 +862,44 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { t.Fatal("expected error due to expired cert, received nil") } }) - t.Run("Attestation", func(t *testing.T) { + t.Run("dsse Attestation", func(t *testing.T) { + identity := "hello@foo.com" + issuer := "issuer" + leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) + + stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` + wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) + signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) + if err != nil { + t.Fatal(err) + } + // intoto sig = json-serialized dsse envelope + sig := signedPayload + + // Create bundle + entry := genRekorEntry(t, rekor_dsse.KIND, "0.0.1", signedPayload, leafPemCert, sig) + b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) + b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) + bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") + blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") + + // Verify command + cmd := VerifyBlobAttestationCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: identity, + CertOidcIssuer: issuer, + }, + CertRef: "", // Cert is fetched from bundle + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SignaturePath: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, + } + if err := cmd.Exec(context.Background(), blobPath); err != nil { + t.Fatal(err) + } + }) + t.Run("intoto Attestation", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) @@ -1192,7 +1230,45 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { t.Fatalf("expected error with mismatched root, got %v", err) } }) - t.Run("Attestation with keyless", func(t *testing.T) { + t.Run("dsse Attestation with keyless", func(t *testing.T) { + identity := "hello@foo.com" + issuer := "issuer" + leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) + + stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` + wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) + signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) + if err != nil { + t.Fatal(err) + } + // intoto sig = json-serialized dsse envelope + sig := signedPayload + + // Create bundle + entry := genRekorEntry(t, rekor_dsse.KIND, "0.0.1", signedPayload, leafPemCert, sig) + b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) + b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) + bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") + blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") + + // Verify command with bundle + cmd := VerifyBlobAttestationCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: identity, + }, + CertRef: "", // Cert is fetched from bundle + CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE + SignaturePath: "", // Sig is fetched from bundle + KeyOpts: options.KeyOpts{BundlePath: bundlePath}, + IgnoreSCT: true, + CheckClaims: false, // Intentionally false. This checks the subject claim. This is tested in verify_blob_attestation_test.go + } + if err := cmd.Exec(context.Background(), blobPath); err != nil { + t.Fatal(err) + } + }) + t.Run("intoto Attestation with keyless", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) @@ -1477,6 +1553,8 @@ func createEntry(ctx context.Context, kind, apiVersion string, blobBytes, certBy props.SignatureBytes = sigBytes case intoto.KIND: props.ArtifactBytes = blobBytes + case rekor_dsse.KIND: + props.ArtifactBytes = blobBytes default: return nil, fmt.Errorf("unexpected entry kind: %s", kind) } diff --git a/go.mod b/go.mod index a5c94f79114..3853c4189a3 100644 --- a/go.mod +++ b/go.mod @@ -28,12 +28,12 @@ require ( github.com/pkg/errors v0.9.1 github.com/secure-systems-lab/go-securesystemslib v0.6.0 github.com/sigstore/fulcio v1.3.1 - github.com/sigstore/rekor v1.2.1 + github.com/sigstore/rekor v1.2.2-0.20230530122220-67cc9e58bd23 github.com/sigstore/sigstore v1.6.4 github.com/sigstore/timestamp-authority v1.1.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.15.0 + github.com/spf13/viper v1.16.0 github.com/spiffe/go-spiffe/v2 v2.1.4 github.com/stretchr/testify v1.8.3 github.com/theupdateframework/go-tuf v0.5.2 @@ -193,7 +193,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/uuid v1.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.15.1 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -211,8 +211,8 @@ require ( github.com/sigstore/protobuf-specs v0.1.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect diff --git a/go.sum b/go.sum index 88b8aa577d9..3b353dd1c7f 100644 --- a/go.sum +++ b/go.sum @@ -321,6 +321,7 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -732,6 +733,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -795,6 +798,8 @@ github.com/sigstore/protobuf-specs v0.1.0 h1:X0l/E2C2c79t/rI/lmSu8WAoKWsQtMqDzAM github.com/sigstore/protobuf-specs v0.1.0/go.mod h1:5shUCxf82hGnjUEFVWiktcxwzdtn6EfeeJssxZ5Q5HE= github.com/sigstore/rekor v1.2.1 h1:cEI4qn9IBvM7EkPQYl3YzCwCw97Mx8O2nHrv02XiI8U= github.com/sigstore/rekor v1.2.1/go.mod h1:zcFO54qIg2G1/i0sE/nvmELUOng/n0MPjTszRYByVPo= +github.com/sigstore/rekor v1.2.2-0.20230530122220-67cc9e58bd23 h1:eZY7mQFcc0VvNr0fiAK3/n7kh73+T06KzBEIUYzFSDQ= +github.com/sigstore/rekor v1.2.2-0.20230530122220-67cc9e58bd23/go.mod h1:h1tOLhldpfILtziWpUDgGBu0vulWk9Kh72t6XzBGJok= github.com/sigstore/sigstore v1.6.4 h1:jH4AzR7qlEH/EWzm+opSpxCfuUcjHL+LJPuQE7h40WE= github.com/sigstore/sigstore v1.6.4/go.mod h1:pjR64lBxnjoSrAr+Ydye/FV73IfrgtoYlAI11a8xMfA= github.com/sigstore/timestamp-authority v1.1.1 h1:EldrdeBED0edNzDMvYZDf5CyWgtSchtR9DKYyksNR8M= @@ -817,10 +822,14 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -835,6 +844,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/spiffe/go-spiffe/v2 v2.1.4 h1:Z31Ycaf2Z5DF38sQGmp+iGKjBhBlSzfAq68bfy67Mxw= github.com/spiffe/go-spiffe/v2 v2.1.4/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 5a75c7fb3d7..1ddd436c432 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -42,6 +42,8 @@ import ( "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/rekor/pkg/types/dsse" + dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" @@ -82,6 +84,21 @@ func GetTransparencyLogID(pub crypto.PublicKey) (string, error) { return hex.EncodeToString(digest[:]), nil } +func dsseEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) { + var pubKeyBytes [][]byte + + if len(pubKey) == 0 { + return nil, errors.New("public key provided has 0 length") + } + + pubKeyBytes = append(pubKeyBytes, pubKey) + + return types.NewProposedEntry(ctx, dsse.KIND, dsse_v001.APIVERSION, types.ArtifactProperties{ + ArtifactBytes: signature, + PublicKeyBytes: pubKeyBytes, + }) +} + func intotoEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) { var pubKeyBytes [][]byte @@ -162,6 +179,16 @@ func TLogUpload(ctx context.Context, rekorClient *client.Rekor, signature []byte return doUpload(ctx, rekorClient, &returnVal) } +// TLogUploadInTotoAttestation will upload and in-toto entry for the signature and public key to the transparency log. +func TLogUploadDSSEEnvelope(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { + e, err := dsseEntry(ctx, signature, pemBytes) + if err != nil { + return nil, err + } + + return doUpload(ctx, rekorClient, e) +} + // TLogUploadInTotoAttestation will upload and in-toto entry for the signature and public key to the transparency log. func TLogUploadInTotoAttestation(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { e, err := intotoEntry(ctx, signature, pemBytes) diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 2d12f74830c..c505a4f4f77 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -34,6 +34,7 @@ import ( "time" "github.com/digitorus/timestamp" + "github.com/go-openapi/runtime" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/sigstore/pkg/tuf" @@ -53,6 +54,12 @@ import ( ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" + rekor_types "github.com/sigstore/rekor/pkg/types" + dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" + hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" + intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" + intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" + rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" @@ -1130,155 +1137,80 @@ func comparePublicKey(bundleBody string, sig oci.Signature, co *CheckOpts) error return nil } -func bundleHash(bundleBody, signature string) (string, string, error) { - var toto models.Intoto - var rekord models.Rekord - var hrekord models.Hashedrekord - var intotoObj models.IntotoV001Schema - var rekordObj models.RekordV001Schema - var hrekordObj models.HashedrekordV001Schema - - bodyDecoded, err := base64.StdEncoding.DecodeString(bundleBody) +func extractEntryImpl(bundleBody string) (rekor_types.EntryImpl, error) { + pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(bundleBody)), runtime.JSONConsumer()) if err != nil { - return "", "", err - } - - // The fact that there's no signature (or empty rather), implies - // that this is an Attestation that we're verifying. - if len(signature) == 0 { - err = json.Unmarshal(bodyDecoded, &toto) - if err != nil { - return "", "", err - } - - specMarshal, err := json.Marshal(toto.Spec) - if err != nil { - return "", "", err - } - err = json.Unmarshal(specMarshal, &intotoObj) - if err != nil { - return "", "", err - } - - return *intotoObj.Content.Hash.Algorithm, *intotoObj.Content.Hash.Value, nil + return nil, err } - if err := json.Unmarshal(bodyDecoded, &rekord); err == nil { - specMarshal, err := json.Marshal(rekord.Spec) - if err != nil { - return "", "", err - } - err = json.Unmarshal(specMarshal, &rekordObj) - if err != nil { - return "", "", err - } - return *rekordObj.Data.Hash.Algorithm, *rekordObj.Data.Hash.Value, nil - } + return rekor_types.UnmarshalEntry(pe) +} - // Try hashedRekordObj - err = json.Unmarshal(bodyDecoded, &hrekord) - if err != nil { - return "", "", err - } - specMarshal, err := json.Marshal(hrekord.Spec) +func bundleHash(bundleBody, _ string) (string, string, error) { + ei, err := extractEntryImpl(bundleBody) if err != nil { return "", "", err } - err = json.Unmarshal(specMarshal, &hrekordObj) - if err != nil { - return "", "", err + + switch entry := ei.(type) { + case *dsse_v001.V001Entry: + return *entry.DSSEObj.EnvelopeHash.Algorithm, *entry.DSSEObj.EnvelopeHash.Value, nil + case *hashedrekord_v001.V001Entry: + return *entry.HashedRekordObj.Data.Hash.Algorithm, *entry.HashedRekordObj.Data.Hash.Value, nil + case *intoto_v001.V001Entry: + return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil + case *intoto_v002.V002Entry: + return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil + case *rekord_v001.V001Entry: + return *entry.RekordObj.Data.Hash.Algorithm, *entry.RekordObj.Data.Hash.Value, nil + default: + return "", "", errors.New("unsupported type") } - return *hrekordObj.Data.Hash.Algorithm, *hrekordObj.Data.Hash.Value, nil } // bundleSig extracts the signature from the rekor bundle body func bundleSig(bundleBody string) (string, error) { - var rekord models.Rekord - var hrekord models.Hashedrekord - var rekordObj models.RekordV001Schema - var hrekordObj models.HashedrekordV001Schema - - bodyDecoded, err := base64.StdEncoding.DecodeString(bundleBody) - if err != nil { - return "", fmt.Errorf("decoding bundleBody: %w", err) - } - - // Try Rekord - if err := json.Unmarshal(bodyDecoded, &rekord); err == nil { - specMarshal, err := json.Marshal(rekord.Spec) - if err != nil { - return "", err - } - if err := json.Unmarshal(specMarshal, &rekordObj); err != nil { - return "", err - } - return rekordObj.Signature.Content.String(), nil - } - - // Try hashedRekordObj - if err := json.Unmarshal(bodyDecoded, &hrekord); err != nil { - return "", err - } - specMarshal, err := json.Marshal(hrekord.Spec) + ei, err := extractEntryImpl(bundleBody) if err != nil { return "", err } - if err := json.Unmarshal(specMarshal, &hrekordObj); err != nil { - return "", err + + switch entry := ei.(type) { + case *dsse_v001.V001Entry: + // TODO: this could have multiple signatures but we're only returning the first here + return *entry.DSSEObj.Signatures[0].Signature, nil + case *hashedrekord_v001.V001Entry: + return entry.HashedRekordObj.Signature.Content.String(), nil + case *rekord_v001.V001Entry: + return entry.RekordObj.Signature.Content.String(), nil + default: + return "", errors.New("unsupported type") } - return hrekordObj.Signature.Content.String(), nil } // bundleKey extracts the key from the rekor bundle body func bundleKey(bundleBody string) (string, error) { - var rekord models.Rekord - var hrekord models.Hashedrekord - var intotod models.Intoto - var rekordObj models.RekordV001Schema - var hrekordObj models.HashedrekordV001Schema - var intotodObj models.IntotoV001Schema - - bodyDecoded, err := base64.StdEncoding.DecodeString(bundleBody) - if err != nil { - return "", fmt.Errorf("decoding bundleBody: %w", err) - } - - // Try Rekord - if err := json.Unmarshal(bodyDecoded, &rekord); err == nil { - specMarshal, err := json.Marshal(rekord.Spec) - if err != nil { - return "", err - } - if err := json.Unmarshal(specMarshal, &rekordObj); err != nil { - return "", err - } - return rekordObj.Signature.PublicKey.Content.String(), nil - } - - // Try hashedRekordObj - if err := json.Unmarshal(bodyDecoded, &hrekord); err == nil { - specMarshal, err := json.Marshal(hrekord.Spec) - if err != nil { - return "", err - } - if err := json.Unmarshal(specMarshal, &hrekordObj); err != nil { - return "", err - } - return hrekordObj.Signature.PublicKey.Content.String(), nil - } - - // Try Intoto - if err := json.Unmarshal(bodyDecoded, &intotod); err != nil { - return "", err - } - specMarshal, err := json.Marshal(intotod.Spec) + ei, err := extractEntryImpl(bundleBody) if err != nil { return "", err } - if err := json.Unmarshal(specMarshal, &intotodObj); err != nil { - return "", err + + switch entry := ei.(type) { + case *dsse_v001.V001Entry: + // TODO: this could have multiple verifiers but we're only returning the first here + return entry.DSSEObj.Signatures[0].Verifier.String(), nil + case *hashedrekord_v001.V001Entry: + return entry.HashedRekordObj.Signature.PublicKey.Content.String(), nil + case *intoto_v001.V001Entry: + return entry.IntotoObj.PublicKey.String(), nil + case *intoto_v002.V002Entry: + // TODO: this could have multiple verifiers but we're only returning the first here + return entry.IntotoObj.Content.Envelope.Signatures[0].PublicKey.String(), nil + case *rekord_v001.V001Entry: + return entry.RekordObj.Signature.PublicKey.Content.String(), nil + default: + return "", errors.New("unsupported type") } - return intotodObj.PublicKey.String(), nil } func VerifySET(bundlePayload cbundle.RekorPayload, signature []byte, pub *ecdsa.PublicKey) error { From f47435aedc7fb63798fedddd83b78695709c8142 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Thu, 1 Jun 2023 07:31:02 -0400 Subject: [PATCH 2/3] address comments Signed-off-by: Bob Callaway --- pkg/cosign/ctlog_test.go | 2 +- pkg/cosign/tlog.go | 4 ++-- pkg/cosign/tlog_test.go | 2 +- pkg/cosign/verify.go | 17 ++++++++++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/cosign/ctlog_test.go b/pkg/cosign/ctlog_test.go index 92ad29418a8..2abb48f6b60 100644 --- a/pkg/cosign/ctlog_test.go +++ b/pkg/cosign/ctlog_test.go @@ -33,7 +33,7 @@ Nmo7M3bN7+dQddw9Ibc2R3SV8tzBZw0rST8FKcn4apJepcKM4qUpYUeNfw== func TestGetCTLogPubKeys(t *testing.T) { keys, err := GetCTLogPubs(context.Background()) if err != nil { - t.Errorf("Unexpected error calling GetCTLogPubs, expected nil: %v", err) + t.Fatalf("Unexpected error calling GetCTLogPubs, expected nil: %v", err) } if len(keys.Keys) == 0 { t.Errorf("expected 1 or more keys, got 0") diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 1ddd436c432..87579dacf6f 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -179,7 +179,7 @@ func TLogUpload(ctx context.Context, rekorClient *client.Rekor, signature []byte return doUpload(ctx, rekorClient, &returnVal) } -// TLogUploadInTotoAttestation will upload and in-toto entry for the signature and public key to the transparency log. +// TLogUploadDSSEEnvelope will upload a DSSE entry for the signature and public key to the Rekor transparency log. func TLogUploadDSSEEnvelope(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { e, err := dsseEntry(ctx, signature, pemBytes) if err != nil { @@ -189,7 +189,7 @@ func TLogUploadDSSEEnvelope(ctx context.Context, rekorClient *client.Rekor, sign return doUpload(ctx, rekorClient, e) } -// TLogUploadInTotoAttestation will upload and in-toto entry for the signature and public key to the transparency log. +// TLogUploadInTotoAttestation will upload an in-toto entry for the signature and public key to the transparency log. func TLogUploadInTotoAttestation(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { e, err := intotoEntry(ctx, signature, pemBytes) if err != nil { diff --git a/pkg/cosign/tlog_test.go b/pkg/cosign/tlog_test.go index 8afa5633911..1034aa1cc75 100644 --- a/pkg/cosign/tlog_test.go +++ b/pkg/cosign/tlog_test.go @@ -38,7 +38,7 @@ var ( func TestGetRekorPubKeys(t *testing.T) { keys, err := GetRekorPubs(context.Background()) if err != nil { - t.Errorf("Unexpected error calling GetRekorPubs, expected nil: %v", err) + t.Fatalf("Unexpected error calling GetRekorPubs, expected nil: %v", err) } if len(keys.Keys) == 0 { t.Errorf("expected 1 or more keys, got 0") diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index c505a4f4f77..6afdac5e568 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1177,10 +1177,17 @@ func bundleSig(bundleBody string) (string, error) { switch entry := ei.(type) { case *dsse_v001.V001Entry: - // TODO: this could have multiple signatures but we're only returning the first here + if len(entry.DSSEObj.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") + } return *entry.DSSEObj.Signatures[0].Signature, nil case *hashedrekord_v001.V001Entry: return entry.HashedRekordObj.Signature.Content.String(), nil + case *intoto_v002.V002Entry: + if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") + } + return entry.IntotoObj.Content.Envelope.Signatures[0].Sig.String(), nil case *rekord_v001.V001Entry: return entry.RekordObj.Signature.Content.String(), nil default: @@ -1197,14 +1204,18 @@ func bundleKey(bundleBody string) (string, error) { switch entry := ei.(type) { case *dsse_v001.V001Entry: - // TODO: this could have multiple verifiers but we're only returning the first here + if len(entry.DSSEObj.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") + } return entry.DSSEObj.Signatures[0].Verifier.String(), nil case *hashedrekord_v001.V001Entry: return entry.HashedRekordObj.Signature.PublicKey.Content.String(), nil case *intoto_v001.V001Entry: return entry.IntotoObj.PublicKey.String(), nil case *intoto_v002.V002Entry: - // TODO: this could have multiple verifiers but we're only returning the first here + if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") + } return entry.IntotoObj.Content.Envelope.Signatures[0].PublicKey.String(), nil case *rekord_v001.V001Entry: return entry.RekordObj.Signature.PublicKey.Content.String(), nil From e4463e57cc0f192ae371428cc6e619ffb258b389 Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 13 Jun 2023 10:14:15 -0400 Subject: [PATCH 3/3] remove duplicated test Signed-off-by: Bob Callaway --- cmd/cosign/cli/verify/verify_blob_test.go | 38 ----------------------- 1 file changed, 38 deletions(-) diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index 77c803aaf6b..05338a812e1 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -1230,44 +1230,6 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { t.Fatalf("expected error with mismatched root, got %v", err) } }) - t.Run("dsse Attestation with keyless", func(t *testing.T) { - identity := "hello@foo.com" - issuer := "issuer" - leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) - - stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` - wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) - signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) - if err != nil { - t.Fatal(err) - } - // intoto sig = json-serialized dsse envelope - sig := signedPayload - - // Create bundle - entry := genRekorEntry(t, rekor_dsse.KIND, "0.0.1", signedPayload, leafPemCert, sig) - b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) - b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) - bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") - blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") - - // Verify command with bundle - cmd := VerifyBlobAttestationCommand{ - CertVerifyOptions: options.CertVerifyOptions{ - CertOidcIssuer: issuer, - CertIdentity: identity, - }, - CertRef: "", // Cert is fetched from bundle - CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE - SignaturePath: "", // Sig is fetched from bundle - KeyOpts: options.KeyOpts{BundlePath: bundlePath}, - IgnoreSCT: true, - CheckClaims: false, // Intentionally false. This checks the subject claim. This is tested in verify_blob_attestation_test.go - } - if err := cmd.Exec(context.Background(), blobPath); err != nil { - t.Fatal(err) - } - }) t.Run("intoto Attestation with keyless", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer"