diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index fab70bc36a5..05338a812e1 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,7 @@ 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("intoto Attestation with keyless", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) @@ -1477,6 +1515,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 15511cd8cae..238c224357f 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ 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.7.0 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.7.0 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.7.0 @@ -165,7 +165,6 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/tink/go v1.7.0 // indirect - github.com/google/trillian v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect github.com/googleapis/gax-go/v2 v2.10.0 // indirect @@ -219,7 +218,6 @@ require ( github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - 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.5 // indirect diff --git a/go.sum b/go.sum index de44a1ffc3c..d1d60fff4b1 100644 --- a/go.sum +++ b/go.sum @@ -526,8 +526,6 @@ github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/trillian v1.5.2 h1:roGP6G8aaAch7vP08+oitPkvmZzxjTfIkguozqJ04Ok= -github.com/google/trillian v1.5.2/go.mod h1:H8vOoa2dxd3xCdMzOOwt9kIz/3MSoJhcqLJGG8iRwbg= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -666,7 +664,7 @@ github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GW github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -804,10 +802,8 @@ github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigstore/fulcio v1.3.1 h1:0ntW9VbQbt2JytoSs8BOGB84A65eeyvGSavWteYp29Y= github.com/sigstore/fulcio v1.3.1/go.mod h1:/XfqazOec45ulJZpyL9sq+OsVQ8g2UOVoNVi7abFgqU= -github.com/sigstore/protobuf-specs v0.1.0 h1:X0l/E2C2c79t/rI/lmSu8WAoKWsQtMqDzAMiDdEMGr8= -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.7.0 h1:0jLlzxX68LtirwSTWAwRPMKhulT0aWVLmFU5ofnbtYA= github.com/sigstore/sigstore v1.7.0/go.mod h1:0PmMzfJP2Y9+lugD0wer4e7TihR5tM7NcIs3bQNk5xg= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.7.0 h1:fRv9grFx22NsmXTkfhF8/+UzqkrCND8JI/QfCpYjEnc= 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 5a75c7fb3d7..87579dacf6f 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,7 +179,17 @@ 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 { + return nil, err + } + + return doUpload(ctx, rekorClient, e) +} + +// 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 9d7af1ec828..a0bd77276ee 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -35,6 +35,7 @@ import ( "github.com/pkg/errors" "github.com/digitorus/timestamp" + "github.com/go-openapi/runtime" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/sigstore/pkg/tuf" @@ -54,6 +55,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" @@ -1152,155 +1159,91 @@ 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) +func bundleHash(bundleBody, _ string) (string, string, error) { + ei, err := extractEntryImpl(bundleBody) if err != nil { return "", "", err } - specMarshal, err := json.Marshal(hrekord.Spec) - 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) + ei, err := extractEntryImpl(bundleBody) if err != nil { - return "", fmt.Errorf("decoding bundleBody: %w", err) + return "", err } - // Try Rekord - if err := json.Unmarshal(bodyDecoded, &rekord); err == nil { - specMarshal, err := json.Marshal(rekord.Spec) - if err != nil { - return "", err + switch entry := ei.(type) { + case *dsse_v001.V001Entry: + if len(entry.DSSEObj.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } - if err := json.Unmarshal(specMarshal, &rekordObj); err != nil { - return "", err + 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 rekordObj.Signature.Content.String(), nil - } - - // Try hashedRekordObj - if err := json.Unmarshal(bodyDecoded, &hrekord); err != nil { - return "", err - } - specMarshal, err := json.Marshal(hrekord.Spec) - if err != nil { - return "", err - } - if err := json.Unmarshal(specMarshal, &hrekordObj); err != nil { - return "", err + return entry.IntotoObj.Content.Envelope.Signatures[0].Sig.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) + ei, err := extractEntryImpl(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 + return "", err } - // Try hashedRekordObj - if err := json.Unmarshal(bodyDecoded, &hrekord); err == nil { - specMarshal, err := json.Marshal(hrekord.Spec) - if err != nil { - return "", err + switch entry := ei.(type) { + case *dsse_v001.V001Entry: + if len(entry.DSSEObj.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } - if err := json.Unmarshal(specMarshal, &hrekordObj); err != nil { - return "", err + 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: + if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 { + return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } - 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) - if err != nil { - return "", err - } - if err := json.Unmarshal(specMarshal, &intotodObj); err != nil { - return "", err + 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 {