From b145f3605fbecb83e7564f51ee3e81e841e21c91 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 1 Jul 2024 23:57:16 +0000 Subject: [PATCH] remove --public-key-hash-algo, make verified-levels an array Signed-off-by: Ramon Petgrave --- README.md | 19 +++-- cli/slsa-verifier/main_regression_test.go | 91 +++++++++-------------- cli/slsa-verifier/verify.go | 17 ++--- cli/slsa-verifier/verify/options.go | 26 +++---- cli/slsa-verifier/verify/verify_vsa.go | 61 +++++++++------ errors/errors.go | 1 - verifiers/internal/vsa/verifier_test.go | 19 +++++ 7 files changed, 120 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index f79b080..bdc0cbd 100644 --- a/README.md +++ b/README.md @@ -502,16 +502,15 @@ Usage: slsa-verifier verify-vsa [flags] subject-digest [subject-digest...] Flags: - --attestations-path string path to a file containing the attestations - -h, --help help for verify-vsa - --print-attestation [optional] print the contents of attestation to stdout - --public-key-signing-hash-algo string [optional] the hash algorithm used to compute the digest to be signed, one of SHA256 [default], SHA384, or SHA512 - --public-key-id string [optional] the ID of the public key, defaults to the SHA256 digest of the base64-encoded public key - --public-key-path string path to a public key file - --resource-uri string the resource URI to be verified - --subject-digest stringArray the digests to be verified. Pass multiple digests by repeating the flag. e.g. : - --verified-levels strings [optional] the levels of verification to be performed, comma-separated. e.g., 'SLSA_BUILD_LEVEL_2,FEDRAMP_LOW' - --verifier-id string the unique verifier ID who created the attestations + --attestation-path string path to a file containing the attestation + -h, --help help for verify-vsa + --print-attestation [optional] print the contents of attestation to stdout + --public-key-id string [optional] the ID of the public key, defaults to the SHA256 digest of the base64-encoded public key + --public-key-path string path to a public key file + --resource-uri string the resource URI to be verified + --subject-digest stringArray the digests to be verified. Pass multiple digests by repeating the flag. e.g. --subject-digest : --subject-digest : + --verified-level stringArray [optional] the levels of verification to be performed. Pass multiple digests by repeating the flag, e.g., --verified-level SLSA_BUILD_LEVEL_2 --verified-level FEDRAMP_LOW' + --verifier-id string the unique verifier ID who created the attestation ``` To verify VSAs, invoke like this diff --git a/cli/slsa-verifier/main_regression_test.go b/cli/slsa-verifier/main_regression_test.go index af83cb4..67ee7ac 100644 --- a/cli/slsa-verifier/main_regression_test.go +++ b/cli/slsa-verifier/main_regression_test.go @@ -1801,61 +1801,39 @@ func Test_runVerifyVSA(t *testing.T) { t.Parallel() tests := []struct { - name string - attestationPath *string - subjectDigests *[]string - verifierID *string - resourceURI *string - verifiedLevels *[]string - publicKeyPath *string - publicKeyID *string - publicKeyHashAlgo *string - err error + name string + attestationPath *string + subjectDigests *[]string + verifierID *string + resourceURI *string + verifiedLevels *[]string + publicKeyPath *string + publicKeyID *string + err error }{ { - name: "success: gke", - attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), - subjectDigests: pointerTo([]string{"gce_image_id:8970095005306000053"}), - verifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"), - resourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"), - verifiedLevels: pointerTo([]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"}), - publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), - publicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"), - publicKeyHashAlgo: pointerTo("SHA256"), + name: "success: gke", + attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), + subjectDigests: pointerTo([]string{"gce_image_id:8970095005306000053"}), + verifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"), + resourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"), + verifiedLevels: pointerTo([]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"}), + publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), + publicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"), }, { - name: "success: gke, default public key hash algo", - attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), - subjectDigests: pointerTo([]string{"gce_image_id:8970095005306000053"}), - verifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"), - resourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"), - verifiedLevels: pointerTo([]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"}), - publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), - publicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"), - publicKeyHashAlgo: pointerTo(""), + name: "fail: gke, empty public key id", + attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), + publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), + publicKeyID: pointerTo(""), + err: serrors.ErrorNoValidSignature, }, { - name: "fail: gke, unsupported public key hash algo", - attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), - publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), - publicKeyHashAlgo: pointerTo("SHA123"), - err: serrors.ErrorInvalidHashAlgo, - }, - { - name: "fail: gke, wrong public key hash algo", - attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), - publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), - publicKeyID: pointerTo(""), - publicKeyHashAlgo: pointerTo("SHA512"), - err: serrors.ErrorNoValidSignature, - }, - { - name: "fail: gke, wrong key id", - attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), - publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), - publicKeyID: pointerTo("my_key_id"), - publicKeyHashAlgo: pointerTo("SHA256"), - err: serrors.ErrorNoValidSignature, + name: "fail: gke, wrong key id", + attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"), + publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"), + publicKeyID: pointerTo("my_key_id"), + err: serrors.ErrorNoValidSignature, }, } @@ -1868,14 +1846,13 @@ func Test_runVerifyVSA(t *testing.T) { publicKeyPath := filepath.Clean(filepath.Join(TEST_DIR, "vsa", *tt.publicKeyPath)) cmd := verify.VerifyVSACommand{ - AttestationPath: &attestationPath, - SubjectDigests: tt.subjectDigests, - VerifierID: tt.verifierID, - ResourceURI: tt.resourceURI, - VerifiedLevels: tt.verifiedLevels, - PublicKeyPath: &publicKeyPath, - PublicKeyID: tt.publicKeyID, - PublicKeyHashAlgo: tt.publicKeyHashAlgo, + AttestationPath: &attestationPath, + SubjectDigests: tt.subjectDigests, + VerifierID: tt.verifierID, + ResourceURI: tt.resourceURI, + VerifiedLevels: tt.verifiedLevels, + PublicKeyPath: &publicKeyPath, + PublicKeyID: tt.publicKeyID, } err := cmd.Exec(context.Background()) diff --git a/cli/slsa-verifier/verify.go b/cli/slsa-verifier/verify.go index 842289f..84f6a4e 100644 --- a/cli/slsa-verifier/verify.go +++ b/cli/slsa-verifier/verify.go @@ -194,15 +194,14 @@ func verifyVSACmd() *cobra.Command { Short: "Verifies SLSA VSAs for the given subject-digests", Run: func(cmd *cobra.Command, args []string) { v := verify.VerifyVSACommand{ - SubjectDigests: &o.SubjectDigests, - AttestationPath: &o.AttestationPath, - VerifierID: &o.VerifierID, - ResourceURI: &o.ResourceURI, - VerifiedLevels: &o.VerifiedLevels, - PrintAttestation: o.PrintAttestation, - PublicKeyPath: &o.PublicKeyPath, - PublicKeyID: &o.PublicKeyID, - PublicKeyHashAlgo: &o.PublicKeyHashAlgo, + SubjectDigests: &o.SubjectDigests, + AttestationPath: &o.AttestationPath, + VerifierID: &o.VerifierID, + ResourceURI: &o.ResourceURI, + VerifiedLevels: &o.VerifiedLevels, + PrintAttestation: o.PrintAttestation, + PublicKeyPath: &o.PublicKeyPath, + PublicKeyID: &o.PublicKeyID, } if err := v.Exec(cmd.Context()); err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", FAILURE, err) diff --git a/cli/slsa-verifier/verify/options.go b/cli/slsa-verifier/verify/options.go index e853e8c..888338e 100644 --- a/cli/slsa-verifier/verify/options.go +++ b/cli/slsa-verifier/verify/options.go @@ -129,15 +129,14 @@ func (o *VerifyNpmOptions) AddFlags(cmd *cobra.Command) { // VerifyVSAOptions is the top-level options for the `verifyVSA` command. type VerifyVSAOptions struct { - SubjectDigests []string - AttestationPath string - VerifierID string - ResourceURI string - VerifiedLevels []string - PublicKeyPath string - PublicKeyID string - PublicKeyHashAlgo string - PrintAttestation bool + SubjectDigests []string + AttestationPath string + VerifierID string + ResourceURI string + VerifiedLevels []string + PublicKeyPath string + PublicKeyID string + PrintAttestation bool } var _ Interface = (*VerifyVSAOptions)(nil) @@ -145,7 +144,7 @@ var _ Interface = (*VerifyVSAOptions)(nil) // AddFlags implements Interface. func (o *VerifyVSAOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVar(&o.SubjectDigests, "subject-digest", []string{}, - "the digests to be verified. Pass multiple digests by repeating the flag. e.g. :") + "the digests to be verified. Pass multiple digests by repeating the flag. e.g. --subject-digest : --subject-digest :") cmd.Flags().StringVar(&o.AttestationPath, "attestation-path", "", "path to a file containing the attestation") @@ -156,8 +155,8 @@ func (o *VerifyVSAOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ResourceURI, "resource-uri", "", "the resource URI to be verified") - cmd.Flags().StringSliceVar(&o.VerifiedLevels, "verified-levels", []string{}, - "[optional] the levels of verification to be performed, comma-separated. e.g., 'SLSA_BUILD_LEVEL_2,FEDRAMP_LOW'") + cmd.Flags().StringArrayVar(&o.VerifiedLevels, "verified-level", []string{}, + "[optional] the levels of verification to be performed. Pass multiple digests by repeating the flag, e.g., --verified-level SLSA_BUILD_LEVEL_2 --verified-level FEDRAMP_LOW'") cmd.Flags().BoolVar(&o.PrintAttestation, "print-attestation", false, "[optional] print the contents of attestation to stdout") @@ -168,9 +167,6 @@ func (o *VerifyVSAOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.PublicKeyID, "public-key-id", "", "[optional] the ID of the public key, defaults to the SHA256 digest of the base64-encoded public key") - cmd.Flags().StringVar(&o.PublicKeyHashAlgo, "public-key-signing-hash-algo", "SHA256", - "[optional] the hash algorithm used to compute the digest to be signed, one of SHA256 [default], SHA384, or SHA512") - cmd.MarkFlagRequired("subject-digests") cmd.MarkFlagRequired("attestation-path") cmd.MarkFlagRequired("verifier-id") diff --git a/cli/slsa-verifier/verify/verify_vsa.go b/cli/slsa-verifier/verify/verify_vsa.go index e5a11e8..931f94e 100644 --- a/cli/slsa-verifier/verify/verify_vsa.go +++ b/cli/slsa-verifier/verify/verify_vsa.go @@ -17,6 +17,10 @@ package verify import ( "context" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" "fmt" "os" @@ -28,22 +32,14 @@ import ( // VerifyVSACommand contains the parameters for the verify-vsa command. type VerifyVSACommand struct { - SubjectDigests *[]string - AttestationPath *string - VerifierID *string - ResourceURI *string - VerifiedLevels *[]string - PrintAttestation bool - PublicKeyPath *string - PublicKeyID *string - PublicKeyHashAlgo *string -} - -var hashAlgos = map[string]crypto.Hash{ - "": crypto.SHA256, // default to SHA256 - "SHA256": crypto.SHA256, - "SHA384": crypto.SHA384, - "SHA512": crypto.SHA512, + SubjectDigests *[]string + AttestationPath *string + VerifierID *string + ResourceURI *string + VerifiedLevels *[]string + PrintAttestation bool + PublicKeyPath *string + PublicKeyID *string } // Exec executes the verifiers.VerifyVSA. @@ -65,12 +61,7 @@ func (c *VerifyVSACommand) Exec(ctx context.Context) error { printFailed(err) return err } - hashAlgo, ok := hashAlgos[*c.PublicKeyHashAlgo] - if !ok { - err := fmt.Errorf("%w: %s", serrors.ErrorInvalidHashAlgo, *c.PublicKeyHashAlgo) - printFailed(err) - return err - } + hashAlgo := determineSignatureHashAlgo(pubKey) VerificationOpts := &options.VerificationOpts{ PublicKey: pubKey, PublicKeyID: c.PublicKeyID, @@ -98,3 +89,29 @@ func (c *VerifyVSACommand) Exec(ctx context.Context) error { func printFailed(err error) { fmt.Fprintf(os.Stderr, "Verifying VSA: FAILED: %v\n\n", err) } + +// determineSignatureHashAlgo determines the hash algorithm used to compute the digest to be signed, based on the public key. +// some well-known defaults can be determined, otherwise the it returns crypto.SHA256. +func determineSignatureHashAlgo(pubKey crypto.PublicKey) crypto.Hash { + var h crypto.Hash + switch pk := pubKey.(type) { + case *rsa.PublicKey: + h = crypto.SHA256 + case *ecdsa.PublicKey: + switch pk.Curve { + case elliptic.P256(): + h = crypto.SHA256 + case elliptic.P384(): + h = crypto.SHA384 + case elliptic.P521(): + h = crypto.SHA512 + default: + h = crypto.SHA256 + } + case ed25519.PublicKey: + h = crypto.SHA512 + default: + h = crypto.SHA256 + } + return h +} diff --git a/errors/errors.go b/errors/errors.go index 4da797d..cab7185 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -44,7 +44,6 @@ var ( ErrorInvalidHash = errors.New("invalid hash") ErrorNotPresent = errors.New("not present") ErrorInvalidPublicKey = errors.New("invalid public key") - ErrorInvalidHashAlgo = errors.New("unsupported hash algorithm") ErrorInvalidVerificationResult = errors.New("verificationResult is not PASSED") ErrorMismatchVerifiedLevels = errors.New("verified levels do not match") ErrorMissingSubjectDigest = errors.New("missing subject digest") diff --git a/verifiers/internal/vsa/verifier_test.go b/verifiers/internal/vsa/verifier_test.go index 1188da7..98936cc 100644 --- a/verifiers/internal/vsa/verifier_test.go +++ b/verifiers/internal/vsa/verifier_test.go @@ -114,6 +114,25 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca opts: goodVSAOpts, expectedVSA: goodVSA, }, + { + name: "success: sha256 key id in envelope", + envelope: &dsse.Envelope{ + PayloadType: goodEnvelope.PayloadType, + Payload: goodEnvelope.Payload, + Signatures: []dsse.Signature{ + { + KeyID: "SHA256:Zphi7kubaI7RnOrkqPgkRdVhF5a2JOFB4gor/Zajiiw", + Sig: goodEnvelope.Signatures[0].Sig, + }, + }, + }, + opts: &options.VerificationOpts{ + PublicKey: goodVSAOpts.PublicKey, + PublicKeyID: pointerTo(""), + PublicKeyHashAlgo: crypto.SHA256, + }, + expectedVSA: goodVSA, + }, { name: "failure: empty signatures", envelope: &dsse.Envelope{