From 13a74b5b4a6fc7036ca1e1d11d92ddcd7fe50a8b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 18 Jun 2024 22:18:25 +0000 Subject: [PATCH] embed the google vsa key, match against all signatures, match the subject digests Signed-off-by: Ramon Petgrave --- .../vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl | 11 ++- verifiers/internal/vsa/keys/static.go | 12 +++ verifiers/internal/vsa/verifier.go | 93 +++++++++++++++---- 3 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 verifiers/internal/vsa/keys/static.go diff --git a/cli/slsa-verifier/testdata/vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl b/cli/slsa-verifier/testdata/vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl index ba750c6..35976a9 100644 --- a/cli/slsa-verifier/testdata/vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl +++ b/cli/slsa-verifier/testdata/vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl @@ -1 +1,10 @@ -{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi92ZXJpZmljYXRpb25fc3VtbWFyeS92MSIsInByZWRpY2F0ZSI6eyJ0aW1lVmVyaWZpZWQiOiIyMDI0LTA2LTEyVDA3OjI0OjM0LjM1MTYwOFoiLCJ2ZXJpZmllciI6eyJpZCI6Imh0dHBzOi8vYmNpZC5jb3JwLmdvb2dsZS5jb20vdmVyaWZpZXIvYmNpZF9wYWNrYWdlX2VuZm9yY2VyL3YwLjEifSwidmVyaWZpY2F0aW9uUmVzdWx0IjoiUEFTU0VEIiwidmVyaWZpZWRMZXZlbHMiOlsiQkNJRF9MMSIsIlNMU0FfQlVJTERfTEVWRUxfMiJdLCJyZXNvdXJjZVVyaSI6ImdjZV9pbWFnZTovL2drZS1ub2RlLWltYWdlczpna2UtMTI2MTUtZ2tlMTQxODAwMC1jb3MtMTAxLTE3MTYyLTQ2My0yOS1jLWNncHYxLXByZSIsInBvbGljeSI6eyJ1cmkiOiJnb29nbGVmaWxlOi9nb29nbGVfc3JjL2ZpbGVzLzY0MjUxMzE5Mi9kZXBvdC9nb29nbGUzL3Byb2R1Y3Rpb24vc2VjdXJpdHkvYmNpZC9zb2Z0d2FyZS9nY2VfaW1hZ2UvZ2tlL3ZtX2ltYWdlcy5zd19wb2xpY3kudGV4dHByb3RvIn19LCJzdWJqZWN0IjpbeyJuYW1lIjoiXyIsImRpZ2VzdCI6eyJnY2VfaW1hZ2VfaWQiOiI4OTcwMDk1MDA1MzA2MDAwMDUzIn19XX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"bmIy2gfnQt6oYpd0WbpQMtZcMRtmntDmyki+Be+2Z9qkboMVbi2RQAD1b5AWbBs7iAP8NZVJOI4R/4jOVYB/FA==","keyid":"keystore://76574:prod:vsa_signing_public_key"}]} \ No newline at end of file +{ + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi92ZXJpZmljYXRpb25fc3VtbWFyeS92MSIsInByZWRpY2F0ZSI6eyJ0aW1lVmVyaWZpZWQiOiIyMDI0LTA2LTEyVDA3OjI0OjM0LjM1MTYwOFoiLCJ2ZXJpZmllciI6eyJpZCI6Imh0dHBzOi8vYmNpZC5jb3JwLmdvb2dsZS5jb20vdmVyaWZpZXIvYmNpZF9wYWNrYWdlX2VuZm9yY2VyL3YwLjEifSwidmVyaWZpY2F0aW9uUmVzdWx0IjoiUEFTU0VEIiwidmVyaWZpZWRMZXZlbHMiOlsiQkNJRF9MMSIsIlNMU0FfQlVJTERfTEVWRUxfMiJdLCJyZXNvdXJjZVVyaSI6ImdjZV9pbWFnZTovL2drZS1ub2RlLWltYWdlczpna2UtMTI2MTUtZ2tlMTQxODAwMC1jb3MtMTAxLTE3MTYyLTQ2My0yOS1jLWNncHYxLXByZSIsInBvbGljeSI6eyJ1cmkiOiJnb29nbGVmaWxlOi9nb29nbGVfc3JjL2ZpbGVzLzY0MjUxMzE5Mi9kZXBvdC9nb29nbGUzL3Byb2R1Y3Rpb24vc2VjdXJpdHkvYmNpZC9zb2Z0d2FyZS9nY2VfaW1hZ2UvZ2tlL3ZtX2ltYWdlcy5zd19wb2xpY3kudGV4dHByb3RvIn19LCJzdWJqZWN0IjpbeyJuYW1lIjoiXyIsImRpZ2VzdCI6eyJnY2VfaW1hZ2VfaWQiOiI4OTcwMDk1MDA1MzA2MDAwMDUzIn19XX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "bmIy2gfnQt6oYpd0WbpQMtZcMRtmntDmyki+Be+2Z9qkboMVbi2RQAD1b5AWbBs7iAP8NZVJOI4R/4jOVYB/FA==", + "keyid": "keystore://76574:prod:vsa_signing_public_key" + } + ] +} \ No newline at end of file diff --git a/verifiers/internal/vsa/keys/static.go b/verifiers/internal/vsa/keys/static.go new file mode 100644 index 0000000..dec3a4f --- /dev/null +++ b/verifiers/internal/vsa/keys/static.go @@ -0,0 +1,12 @@ +package keys + +// GoogleVSASigningPublicKey is the public key used to verify Google VSA signatures. +const GoogleVSASigningPublicKey = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca +3Xkk3UrxvbQtoZzTmq0zIYq+4QQl0YBedSyy+XcwAMaUWTouTrB05WhYtg== +-----END PUBLIC KEY-----` + +// AttestorKeys is a map of Attestor IDs to their public keys. +var AttestorKeys = map[string]string{ + "keystore://76574:prod:vsa_signing_public_key": GoogleVSASigningPublicKey, +} diff --git a/verifiers/internal/vsa/verifier.go b/verifiers/internal/vsa/verifier.go index 7bb97e5..4904ab8 100644 --- a/verifiers/internal/vsa/verifier.go +++ b/verifiers/internal/vsa/verifier.go @@ -4,6 +4,7 @@ import ( "context" "crypto" "fmt" + "strings" "github.com/secure-systems-lab/go-securesystemslib/dsse" sigstoreBundle "github.com/sigstore/sigstore-go/pkg/bundle" @@ -12,6 +13,7 @@ import ( sigstoreDSSE "github.com/sigstore/sigstore/pkg/signature/dsse" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" "github.com/slsa-framework/slsa-verifier/v2/options" + vsaKeys "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/vsa/keys" vsa10 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/vsa/v1.0" "github.com/slsa-framework/slsa-verifier/v2/verifiers/utils" ) @@ -56,28 +58,37 @@ func VerifyVSA(ctx context.Context, return nil, nil, nil } +// verifyEnvelopeSignature verifies the signatures of the envelope, requiring at least one signature to be valid. func verifyEnvelopeSignature(ctx context.Context, sigstoreEnvelope *sigstoreBundle.Envelope) error { - pubKeyBytes := []byte(`-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca -3Xkk3UrxvbQtoZzTmq0zIYq+4QQl0YBedSyy+XcwAMaUWTouTrB05WhYtg== ------END PUBLIC KEY-----`) - keyID := "keystore://76574:prod:vsa_signing_public_key" - pubKey, err := sigstoreCryptoUtils.UnmarshalPEMToPublicKey(pubKeyBytes) - if err != nil { - return fmt.Errorf("%w: %w", serrors.ErrorInvalidPublicKey, err) + // assemble an "adapter" for each of the signatures and their KeyID + var verifierAdapters []dsse.Verifier + for _, signature := range sigstoreEnvelope.Envelope.Signatures { + keyID := signature.KeyID + pubKeyString, ok := vsaKeys.AttestorKeys[keyID] + if !ok { + continue + } + pubKey, err := sigstoreCryptoUtils.UnmarshalPEMToPublicKey([]byte(pubKeyString)) + if err != nil { + return fmt.Errorf("%w: %w", serrors.ErrorInvalidPublicKey, err) + } + signatureVerifier, err := sigstoreSignature.LoadVerifier(pubKey, crypto.SHA256) + if err != nil { + return fmt.Errorf("%w: loading sigstore DSSE envolope verifier %w", serrors.ErrorInvalidPublicKey, err) + } + verifierAdapter := &sigstoreDSSE.VerifierAdapter{ + SignatureVerifier: signatureVerifier, + Pub: pubKey, + PubKeyID: keyID, // "keystore://76574:prod:vsa_signing_public_key" + } + verifierAdapters = append(verifierAdapters, verifierAdapter) } - signatureVerifier, err := sigstoreSignature.LoadVerifier(pubKey, crypto.SHA256) + // create the envelope verifier with all adapters + envelopeVerifier, err := dsse.NewEnvelopeVerifier(verifierAdapters...) if err != nil { - return fmt.Errorf("%w: loading sigstore DSSE envolope verifier %w", serrors.ErrorInvalidPublicKey, err) - } - envelopeVerifier, err := dsse.NewEnvelopeVerifier(&sigstoreDSSE.VerifierAdapter{ - SignatureVerifier: signatureVerifier, - Pub: pubKey, - PubKeyID: keyID, // "keystore://76574:prod:vsa_signing_public_key" - }) - if err != nil { - return fmt.Errorf("%w: creating verifier %w", serrors.ErrorInvalidPublicKey, err) + return fmt.Errorf("%w: creating sigstore DSSE envelope verifier %w", serrors.ErrorInvalidPublicKey, err) } + // verify the envelope _, err = envelopeVerifier.Verify(ctx, sigstoreEnvelope.Envelope) if err != nil { return fmt.Errorf("%w: verifying envelope %w", serrors.ErrorInvalidPublicKey, err) @@ -85,7 +96,51 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca return nil } +// matchExpectedValues checks if the expected values are present in the VSA. func matchExpectedValues(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error { - // TODO: implement this function + if err := matchExepectedSubjectDigests(vsa, vsaOpts); err != nil { + return err + } + // TODO: match other expected values + return nil +} + +// matchExepectedSubjectDigests checks if the expected subject digests are present in the VSA. +func matchExepectedSubjectDigests(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error { + // collect all digests from the VSA, so we can efficiently search, e.g.: + // { + // "sha256": { + // "abc": true, + // "def": true, + // }, + // "gce_image_id": { + // "123": true, + // "456": true, + // } + // } + allVSASubjectDigests := make(map[string]map[string]bool) + for _, subject := range vsa.Subject { + for digestType, digestValue := range subject.Digest { + if _, ok := allVSASubjectDigests[digestType]; !ok { + allVSASubjectDigests[digestType] = make(map[string]bool) + } + allVSASubjectDigests[digestType][digestValue] = true + } + } + // search for the expected digests in the VSA + for _, expectedDigest := range vsaOpts.ExpectedDigests { + parts := strings.SplitN(expectedDigest, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("%w: expected digest %s is not in the format :", serrors.ErrorInvalidDssePayload, expectedDigest) + } + digestType := parts[0] + digestValue := parts[1] + if _, ok := allVSASubjectDigests[digestType]; !ok { + return fmt.Errorf("%w: expected digest not found: %s", serrors.ErrorInvalidDssePayload, expectedDigest) + } + if _, ok := allVSASubjectDigests[digestType][digestValue]; !ok { + return fmt.Errorf("%w: expected digest not found: %s", serrors.ErrorInvalidDssePayload, expectedDigest) + } + } return nil }