Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
This commit is contained in:
Ramon Petgrave
2024-06-25 22:54:07 +00:00
parent 3d6e498ec5
commit 9560d1a834
8 changed files with 467 additions and 225 deletions

View File

@@ -1795,6 +1795,8 @@ func Test_runVerifyNpmPackage(t *testing.T) {
}
}
// Test_runVerifyVSA tests the CLI inputes of verify-vsa. More extensive tests are in
// slsa-verifier/verifiers/internal/vsa/verifier_test.go
func Test_runVerifyVSA(t *testing.T) {
t.Parallel()
@@ -1855,7 +1857,6 @@ func Test_runVerifyVSA(t *testing.T) {
publicKeyHashAlgo: PointerTo("SHA256"),
err: serrors.ErrorNoValidSignature,
},
// TODO: Add more tests for different scenarios.
}
for _, tt := range tests {

View File

@@ -45,9 +45,10 @@ var (
ErrorNotPresent = errors.New("not present")
ErrorInvalidPublicKey = errors.New("invalid public key")
ErrorInvalidHashAlgo = errors.New("unsupported hash algorithm")
ErrorMismatchSLSAResult = errors.New("SLSA result does not match")
ErrorInvalidVerificationResult = errors.New("verificationResult is not PASSED")
ErrorMismatchVerifiedLevels = errors.New("verified levels do not match")
ErrorMissingSubjectDigest = errors.New("missing subject digest")
ErrorEmptyRequiredField = errors.New("empty value in required field")
ErrorMismatchResourceURI = errors.New("resource URI does not match")
ErrorMismatchVerifierID = errors.New("verifier ID does not match")
)

View File

@@ -1,10 +0,0 @@
{
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi92ZXJpZmljYXRpb25fc3VtbWFyeS92MSIsInByZWRpY2F0ZSI6eyJ0aW1lVmVyaWZpZWQiOiIyMDI0LTA2LTEyVDA3OjI0OjM0LjM1MTYwOFoiLCJ2ZXJpZmllciI6eyJpZCI6Imh0dHBzOi8vYmNpZC5jb3JwLmdvb2dsZS5jb20vdmVyaWZpZXIvYmNpZF9wYWNrYWdlX2VuZm9yY2VyL3YwLjEifSwidmVyaWZpY2F0aW9uUmVzdWx0IjoiUEFTU0VEIiwidmVyaWZpZWRMZXZlbHMiOlsiQkNJRF9MMSIsIlNMU0FfQlVJTERfTEVWRUxfMiJdLCJyZXNvdXJjZVVyaSI6ImdjZV9pbWFnZTovL2drZS1ub2RlLWltYWdlczpna2UtMTI2MTUtZ2tlMTQxODAwMC1jb3MtMTAxLTE3MTYyLTQ2My0yOS1jLWNncHYxLXByZSIsInBvbGljeSI6eyJ1cmkiOiJnb29nbGVmaWxlOi9nb29nbGVfc3JjL2ZpbGVzLzY0MjUxMzE5Mi9kZXBvdC9nb29nbGUzL3Byb2R1Y3Rpb24vc2VjdXJpdHkvYmNpZC9zb2Z0d2FyZS9nY2VfaW1hZ2UvZ2tlL3ZtX2ltYWdlcy5zd19wb2xpY3kudGV4dHByb3RvIn19LCJzdWJqZWN0IjpbeyJuYW1lIjoiXyIsImRpZ2VzdCI6eyJnY2VfaW1hZ2VfaWQiOiI4OTcwMDk1MDA1MzA2MDAwMDUzIn19XX0=",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{
"sig": "bmIy2gfnQt6oYpd0WbpQMtZcMRtmntDmyki+Be+2Z9qkboMVbi2RQAD1b5AWbBs7iAP8NZVJOI4R/4jOVYB/FA==",
"keyid": "keystore://76574:prod:vsa_signing_public_key"
}
]
}

View File

@@ -1,4 +0,0 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca
3Xkk3UrxvbQtoZzTmq0zIYq+4QQl0YBedSyy+XcwAMaUWTouTrB05WhYtg==
-----END PUBLIC KEY-----

View File

@@ -10,7 +10,7 @@ import (
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
)
const vsaPredicateType = "https://slsa.dev/verification_summary/v1"
const PredicateType = "https://slsa.dev/verification_summary/v1"
// VSA is a struct that represents a VSA statement.
// spec: https://slsa.dev/spec/v1.0/verification_summary.
@@ -43,8 +43,8 @@ type Verifier struct {
// VSAFromStatement creates a VSA from a statement.
func VSAFromStatement(statement *intotoGolang.Statement) (*VSA, error) {
if statement.PredicateType != vsaPredicateType {
return nil, fmt.Errorf("%w: expected predicate type %q, got %q", serrors.ErrorInvalidDssePayload, vsaPredicateType, statement.PredicateType)
if statement.PredicateType != PredicateType {
return nil, fmt.Errorf("%w: expected predicate type %q, got %q", serrors.ErrorInvalidDssePayload, PredicateType, statement.PredicateType)
}
vsaBytes, err := json.Marshal(statement)
if err != nil {

View File

@@ -21,7 +21,6 @@ func VerifyVSA(ctx context.Context,
verificationOpts *options.VerificationOpts,
) ([]byte, *utils.TrustedAttesterID, error) {
// following steps in https://slsa.dev/spec/v1.1/verification_summary#how-to-verify
envelope, err := utils.EnvelopeFromBytes(attestation)
if err != nil {
return nil, nil, err
@@ -29,17 +28,8 @@ func VerifyVSA(ctx context.Context,
// 1. verify the envelope signature,
// 4. match the verfier with the public key: implicit because we accept a user-provided public key.
err = verifyEnvelopeSignature(ctx, envelope, verificationOpts)
if err != nil {
return nil, nil, err
}
statement, err := utils.StatementFromEnvelope(envelope)
if err != nil {
return nil, nil, err
}
// 3. parse the VSA, verifying the predicateType.
vsa, err := vsa10.VSAFromStatement(statement)
vsa, err := extractSignedVSA(ctx, envelope, verificationOpts)
if err != nil {
return nil, nil, err
}
@@ -65,11 +55,31 @@ func VerifyVSA(ctx context.Context,
return vsaBytes, trustedAttesterID, nil
}
// extractSignedVSA verifies the envelope signature and type and extracts the VSA from the envelope.
func extractSignedVSA(ctx context.Context, envelope *dsse.Envelope, verificationOpts *options.VerificationOpts) (*vsa10.VSA, error) {
// 1. verify the envelope signature,
// 4. match the verfier with the public key: implicit because we accept a user-provided public key.
err := verifyEnvelopeSignature(ctx, envelope, verificationOpts)
if err != nil {
return nil, err
}
statement, err := utils.StatementFromEnvelope(envelope)
if err != nil {
return nil, err
}
// 3. parse the VSA, verifying the predicateType.
vsa, err := vsa10.VSAFromStatement(statement)
if err != nil {
return nil, err
}
return vsa, nil
}
// verifyEnvelopeSignature verifies the signature of the envelope.
func verifyEnvelopeSignature(ctx context.Context, envelope *dsse.Envelope, verificationOpts *options.VerificationOpts) error {
signatureVerifier, err := sigstoreSignature.LoadVerifier(verificationOpts.PublicKey, verificationOpts.PublicKeyHashAlgo)
if err != nil {
return fmt.Errorf("%w: loading sigstore DSSE envolope verifier %w", serrors.ErrorInvalidPublicKey, err)
return fmt.Errorf("%w: loading sigstore DSSE envolope verifier: %w", serrors.ErrorInvalidPublicKey, err)
}
envelopeVerifier, err := dsse.NewEnvelopeVerifier(&sigstoreDSSE.VerifierAdapter{
SignatureVerifier: signatureVerifier,
@@ -77,11 +87,11 @@ func verifyEnvelopeSignature(ctx context.Context, envelope *dsse.Envelope, verif
PubKeyID: *verificationOpts.PublicKeyID,
})
if err != nil {
return fmt.Errorf("%w: creating sigstore DSSE envelope verifier %w", serrors.ErrorInvalidPublicKey, err)
return fmt.Errorf("%w: creating sigstore DSSE envelope verifier: %w", serrors.ErrorInvalidPublicKey, err)
}
_, err = envelopeVerifier.Verify(ctx, envelope)
if err != nil {
return fmt.Errorf("%w: verifying envelope %w", serrors.ErrorNoValidSignature, err)
return fmt.Errorf("%w: verifying envelope: %w", serrors.ErrorNoValidSignature, err)
}
return nil
}
@@ -114,10 +124,7 @@ func matchExpectedValues(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
// matchExepectedSubjectDigests checks if the expected subject digests are present in the VSA.
func matchExepectedSubjectDigests(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
if len(*vsaOpts.ExpectedDigests) == 0 {
return fmt.Errorf("%w: no subject digests provided", serrors.ErrorInvalidSubject)
}
if len(vsa.Subject) == 0 {
return fmt.Errorf("%w: no subject digests found in the VSA", serrors.ErrorInvalidDssePayload)
return fmt.Errorf("%w: no subject digests provided", serrors.ErrorEmptyRequiredField)
}
// collect all digests from the VSA, so we can efficiently search, e.g.:
// {
@@ -139,6 +146,9 @@ func matchExepectedSubjectDigests(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) erro
allVSASubjectDigests[digestType][digestValue] = true
}
}
if len(allVSASubjectDigests) == 0 {
return fmt.Errorf("%w: no subject digests found in the VSA", serrors.ErrorInvalidDssePayload)
}
// search for the expected digests in the VSA
for _, expectedDigest := range *vsaOpts.ExpectedDigests {
parts := strings.SplitN(expectedDigest, ":", 2)
@@ -159,6 +169,9 @@ func matchExepectedSubjectDigests(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) erro
// matchVerifierID checks if the verifier ID in the VSA matches the expected value.
func matchVerifierID(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
if vsa.Predicate.Verifier.ID == "" {
return fmt.Errorf("%w: no verifierID found in the VSA", serrors.ErrorEmptyRequiredField)
}
if *vsaOpts.ExpectedVerifierID != vsa.Predicate.Verifier.ID {
return fmt.Errorf("%w: verifier ID mismatch: wanted %s, got %s", serrors.ErrorMismatchVerifierID, *vsaOpts.ExpectedVerifierID, vsa.Predicate.Verifier.ID)
}
@@ -167,6 +180,9 @@ func matchVerifierID(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
// matchResourceURI checks if the resource URI in the VSA matches the expected value.
func matchResourceURI(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
if vsa.Predicate.ResourceURI == "" {
return fmt.Errorf("%w: no resourceURI provided", serrors.ErrorEmptyRequiredField)
}
if *vsaOpts.ExpectedResourceURI != vsa.Predicate.ResourceURI {
return fmt.Errorf("%w: resource URI mismatch: wanted %s, got %s", serrors.ErrorMismatchResourceURI, *vsaOpts.ExpectedResourceURI, vsa.Predicate.ResourceURI)
}
@@ -176,7 +192,7 @@ func matchResourceURI(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
// confirmVerificationResult checks that the policy verification result is "PASSED".
func confirmVerificationResult(vsa *vsa10.VSA) error {
if normalizeString(vsa.Predicate.VerificationResult) != "PASSED" {
return fmt.Errorf("%w: verification result is not Passed: %s", serrors.ErrorMismatchSLSAResult, vsa.Predicate.VerificationResult)
return fmt.Errorf("%w: verification result is not Passed: %s", serrors.ErrorInvalidVerificationResult, vsa.Predicate.VerificationResult)
}
return nil
}

View File

@@ -0,0 +1,425 @@
package vsa
import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/json"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
intotoAttestattions "github.com/in-toto/attestation/go/v1"
intotoGolang "github.com/in-toto/in-toto-golang/in_toto"
intotoCommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/sigstore/pkg/cryptoutils"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/options"
vsa10 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/vsa/v1.0"
)
func Test_extractSignedVSA(t *testing.T) {
ctx := context.Background()
t.Parallel()
// goodPayload := base64.StdEncoding.EncodeToString()
goodAttestationString := `
{
"_type": "https://in-toto.io/Statement/v1",
"predicateType": "https://slsa.dev/verification_summary/v1",
"predicate": {
"timeVerified": "2024-06-12T07:24:34.351608Z",
"verifier": {
"id": "https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"
},
"verificationResult": "PASSED",
"verifiedLevels": [
"BCID_L1",
"SLSA_BUILD_LEVEL_2"
],
"resourceUri": "gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre",
"policy": {
"uri": "googlefile:/google_src/files/642513192/depot/google3/production/security/bcid/software/gce_image/gke/vm_images.sw_policy.textproto"
}
},
"subject": [
{
"name": "_",
"digest": {
"gce_image_id": "8970095005306000053"
}
}
]
}
`
goodEnvelope := &dsse.Envelope{
PayloadType: intotoGolang.PayloadType,
Payload: mustEncodeAttestationString(goodAttestationString),
Signatures: []dsse.Signature{
{
KeyID: "keystore://76574:prod:vsa_signing_public_key",
Sig: "bmIy2gfnQt6oYpd0WbpQMtZcMRtmntDmyki+Be+2Z9qkboMVbi2RQAD1b5AWbBs7iAP8NZVJOI4R/4jOVYB/FA==",
},
},
}
goodVSAOpts := &options.VerificationOpts{
PublicKey: mustPublicKeyFromBytes([]byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca
3Xkk3UrxvbQtoZzTmq0zIYq+4QQl0YBedSyy+XcwAMaUWTouTrB05WhYtg==
-----END PUBLIC KEY-----`)),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
}
goodVSA := &vsa10.VSA{
StatementHeader: intotoGolang.StatementHeader{
Type: intotoAttestattions.StatementTypeUri,
PredicateType: vsa10.PredicateType,
Subject: []intotoGolang.Subject{
{
Name: "_",
Digest: map[string]string{
"gce_image_id": "8970095005306000053",
},
},
},
},
Predicate: vsa10.Predicate{
TimeVerified: time.Date(2024, 6, 12, 7, 24, 34, 351608000, time.UTC),
Verifier: vsa10.Verifier{
ID: "https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1",
},
ResourceURI: "gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre",
Policy: intotoCommon.ProvenanceMaterial{
URI: "googlefile:/google_src/files/642513192/depot/google3/production/security/bcid/software/gce_image/gke/vm_images.sw_policy.textproto",
},
VerificationResult: "PASSED",
VerifiedLevels: []string{"BCID_L1", "SLSA_BUILD_LEVEL_2"},
},
}
tests := []struct {
name string
envelope *dsse.Envelope
opts *options.VerificationOpts
expectedVSA *vsa10.VSA
err error
}{
{
name: "success",
envelope: goodEnvelope,
opts: goodVSAOpts,
expectedVSA: goodVSA,
},
{
name: "failure: empty signatures",
envelope: &dsse.Envelope{
PayloadType: goodEnvelope.PayloadType,
Payload: goodEnvelope.Payload,
Signatures: []dsse.Signature{},
},
opts: goodVSAOpts,
expectedVSA: nil,
err: dsse.ErrNoSignature,
},
{
name: "failure: mismatch signature",
envelope: &dsse.Envelope{
PayloadType: goodEnvelope.PayloadType,
Payload: mustEncodeAttestationString("{}"),
Signatures: goodEnvelope.Signatures,
},
opts: goodVSAOpts,
expectedVSA: nil,
err: serrors.ErrorNoValidSignature,
},
{
name: "failure: misatch keyID",
envelope: goodEnvelope,
opts: &options.VerificationOpts{
PublicKey: goodVSAOpts.PublicKey,
PublicKeyID: pointerTo("keystore://76574:prod:another_key_id"),
PublicKeyHashAlgo: crypto.SHA256,
},
expectedVSA: nil,
err: serrors.ErrorNoValidSignature,
},
{
name: "failure: missing needed keyID",
envelope: goodEnvelope,
opts: &options.VerificationOpts{
PublicKey: goodVSAOpts.PublicKey,
PublicKeyID: pointerTo(""),
PublicKeyHashAlgo: crypto.SHA256,
},
expectedVSA: nil,
err: serrors.ErrorNoValidSignature,
},
{
name: "failure: incorrect algorithm",
envelope: goodEnvelope,
opts: &options.VerificationOpts{
PublicKey: goodVSAOpts.PublicKey,
PublicKeyID: pointerTo(""),
PublicKeyHashAlgo: crypto.SHA512,
},
expectedVSA: nil,
err: serrors.ErrorNoValidSignature,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
vsa, err := extractSignedVSA(ctx, tc.envelope, tc.opts)
if diff := cmp.Diff(tc.expectedVSA, vsa, cmpopts.EquateComparable()); diff != "" {
t.Errorf("unexpected VSA (-want +got): \n%s", diff)
}
if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got): \n%s", diff)
}
})
}
}
func Test_matchExpectedValues(t *testing.T) {
t.Parallel()
goodVSA := &vsa10.VSA{
StatementHeader: intotoGolang.StatementHeader{
PredicateType: vsa10.PredicateType,
Subject: []intotoGolang.Subject{
{
Digest: map[string]string{
"gce_image_id": "8970095005306000053",
"sha256": "abc",
},
},
},
},
Predicate: vsa10.Predicate{
Verifier: vsa10.Verifier{
ID: "https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1",
},
ResourceURI: "gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre",
VerificationResult: "PASSED",
VerifiedLevels: []string{"BCID_L1", "SLSA_BUILD_LEVEL_2"},
},
}
goodVSAOpts := &options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"},
}
tests := []struct {
name string
vsa *vsa10.VSA
opts *options.VSAOpts
err error
}{
// success cases
{
name: "success",
vsa: goodVSA,
opts: goodVSAOpts,
},
{
name: "success: empty verifiedLevels",
vsa: &vsa10.VSA{
StatementHeader: goodVSA.StatementHeader,
Predicate: vsa10.Predicate{
Verifier: goodVSA.Predicate.Verifier,
ResourceURI: goodVSA.Predicate.ResourceURI,
VerificationResult: goodVSA.Predicate.VerificationResult,
VerifiedLevels: []string{},
},
},
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: &[]string{},
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
},
{
name: "success: unspecified verifiedLevels",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: &[]string{},
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
},
// failure cases
{
name: "failure empty digests",
vsa: &vsa10.VSA{
StatementHeader: intotoGolang.StatementHeader{
PredicateType: vsa10.PredicateType,
Subject: []intotoGolang.Subject{
{
Digest: map[string]string{},
},
},
},
Predicate: goodVSA.Predicate,
},
opts: goodVSAOpts,
err: serrors.ErrorInvalidDssePayload,
},
{
name: "failure: no supplied digests",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: &[]string{},
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: goodVSAOpts.ExpectedVerifiedLevels,
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
err: serrors.ErrorEmptyRequiredField,
},
{
name: "failure: missing digest",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: &[]string{"zeit:geist"},
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: goodVSAOpts.ExpectedVerifiedLevels,
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
err: serrors.ErrorMissingSubjectDigest,
},
{
name: "failure: empty verifierID",
vsa: &vsa10.VSA{
StatementHeader: goodVSA.StatementHeader,
Predicate: vsa10.Predicate{
Verifier: vsa10.Verifier{},
ResourceURI: goodVSA.Predicate.ResourceURI,
VerificationResult: goodVSA.Predicate.VerificationResult,
VerifiedLevels: goodVSA.Predicate.VerifiedLevels,
},
},
opts: goodVSAOpts,
err: serrors.ErrorEmptyRequiredField,
},
{
name: "failure: mismatch verifierID",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: goodVSAOpts.ExpectedVerifiedLevels,
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.2"),
},
err: serrors.ErrorMismatchVerifierID,
},
{
name: "failure: empty resourceURI",
vsa: &vsa10.VSA{
StatementHeader: goodVSA.StatementHeader,
Predicate: vsa10.Predicate{
Verifier: goodVSA.Predicate.Verifier,
ResourceURI: "",
VerificationResult: goodVSA.Predicate.VerificationResult,
VerifiedLevels: goodVSA.Predicate.VerifiedLevels,
},
},
opts: goodVSAOpts,
err: serrors.ErrorEmptyRequiredField,
},
{
name: "failure: mismatch resourceURI",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-126GGG"),
ExpectedVerifiedLevels: goodVSAOpts.ExpectedVerifiedLevels,
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
err: serrors.ErrorMismatchResourceURI,
},
{
name: "failure: empty verificationResult",
vsa: &vsa10.VSA{
StatementHeader: goodVSA.StatementHeader,
Predicate: vsa10.Predicate{
Verifier: goodVSA.Predicate.Verifier,
ResourceURI: goodVSA.Predicate.ResourceURI,
VerificationResult: "",
VerifiedLevels: goodVSA.Predicate.VerifiedLevels,
},
},
opts: goodVSAOpts,
err: serrors.ErrorInvalidVerificationResult,
},
{
name: "failure: wrong verificationResult",
vsa: &vsa10.VSA{
StatementHeader: goodVSA.StatementHeader,
Predicate: vsa10.Predicate{
Verifier: goodVSA.Predicate.Verifier,
ResourceURI: goodVSA.Predicate.ResourceURI,
VerificationResult: "FAILED",
VerifiedLevels: goodVSA.Predicate.VerifiedLevels,
},
},
opts: goodVSAOpts,
err: serrors.ErrorInvalidVerificationResult,
},
{
name: "failure: missing verifiedLevels",
vsa: goodVSA,
opts: &options.VSAOpts{
ExpectedDigests: goodVSAOpts.ExpectedDigests,
ExpectedResourceURI: goodVSAOpts.ExpectedResourceURI,
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_3"},
ExpectedVerifierID: goodVSAOpts.ExpectedVerifierID,
},
err: serrors.ErrorMismatchVerifiedLevels,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := matchExpectedValues(tc.vsa, tc.opts)
if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got): \n%s", diff)
}
})
}
}
func mustEncodeAttestationString(attestationString string) string {
dst := &bytes.Buffer{}
if err := json.Compact(dst, []byte(attestationString)); err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(dst.Bytes())
}
func mustPublicKeyFromBytes(pubKeyBytes []byte) crypto.PublicKey {
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pubKeyBytes)
if err != nil {
panic(err)
}
return pubKey
}
func pointerTo[K any](object K) *K {
return &object
}

View File

@@ -1,187 +0,0 @@
package vsa
import (
"context"
"crypto"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" // Add this import
"github.com/sigstore/sigstore/pkg/cryptoutils"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/options"
)
const testDir = "./testdata"
func Test_VerifyVSA(t *testing.T) {
t.Parallel()
ctx := context.Background()
tests := []struct {
name string
attestationPath string
vsaOpts *options.VSAOpts
verificationOpts *options.VerificationOpts
err error
}{
{
"success",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
nil,
},
{
"success: unspecified levels",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
nil,
},
{
"failure: missing levels",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{"SLSA_BUILD_LEVEL_3"},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
serrors.ErrorMismatchVerifiedLevels,
},
{
"failure: unspecified subject digests",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
serrors.ErrorInvalidSubject,
},
{
"failure: mismatch subject digests",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"my-giest:123"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
serrors.ErrorMissingSubjectDigest,
},
{
"failure: mismatch resource URI",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
ExpectedResourceURI: pointerTo("my-uri://my/path"),
ExpectedVerifiedLevels: &[]string{},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
serrors.ErrorMismatchResourceURI,
},
{
"failure: msimatch verifier id",
"gce/v1/gke-gce-pre.bcid-vsa.jsonl",
&options.VSAOpts{
ExpectedDigests: &[]string{"gce_image_id:8970095005306000053"},
ExpectedVerifierID: pointerTo("https://celestial-being.gn/gundam"),
ExpectedResourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
ExpectedVerifiedLevels: &[]string{},
},
&options.VerificationOpts{
PublicKey: mustPublicKey(filepath.Clean(filepath.Join(testDir, "gce/v1/vsa_signing_public_key.pem"))),
PublicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
PublicKeyHashAlgo: crypto.SHA256,
},
serrors.ErrorMismatchVerifierID,
},
// TODO: Add more test cases
}
for _, tt := range tests {
// t.Parallel()
attestationPath := filepath.Clean(filepath.Join(testDir, tt.attestationPath))
attestation, err := os.ReadFile(attestationPath)
if err != nil {
t.Errorf("failed to read attestations file: %v", err)
}
_, trustedAttesterID, err := VerifyVSA(ctx, attestation, tt.vsaOpts, tt.verificationOpts)
if err != nil && trustedAttesterID != nil {
t.Errorf("unexpected trustedAttesterID to be nil: %v", trustedAttesterID)
}
if err == nil {
if diff := cmp.Diff(*tt.vsaOpts.ExpectedVerifierID, trustedAttesterID.Name()); diff != "" {
t.Errorf("unexpected trustedAttesterID (-want +got): \n%s", diff)
}
}
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got): \n%s", diff)
}
}
}
func mustPublicKey(path string) crypto.PublicKey {
pubKeyBytes, err := os.ReadFile(path)
if err != nil {
panic(err)
}
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pubKeyBytes)
if err != nil {
panic(err)
}
return pubKey
}
func pointerTo[K any](object K) *K {
return &object
}