mirror of
https://github.com/slsa-framework/slsa-verifier.git
synced 2026-05-16 13:36:33 +00:00
verify reamining fields, print attestations
Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
This commit is contained in:
4
cli/slsa-verifier/testdata/vsa/gce/v1/vsa_signing_public_key.pem
vendored
Normal file
4
cli/slsa-verifier/testdata/vsa/gce/v1/vsa_signing_public_key.pem
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeGa6ZCZn0q6WpaUwJrSk+PPYEsca
|
||||
3Xkk3UrxvbQtoZzTmq0zIYq+4QQl0YBedSyy+XcwAMaUWTouTrB05WhYtg==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -200,6 +200,9 @@ func verifyVSACmd() *cobra.Command {
|
||||
ResourceUri: &o.ResourceUri,
|
||||
VerifiedLevels: &o.VerifiedLevels,
|
||||
PrintAttestations: &o.PrintAttestations,
|
||||
PublicKeyPath: &o.PublicKeyPath,
|
||||
PublicKeyID: &o.PublicKeyID,
|
||||
SignatureHashAlgo: &o.SignatureHashAlgo,
|
||||
}
|
||||
if _, err := v.Exec(cmd.Context()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", FAILURE, err)
|
||||
|
||||
@@ -134,6 +134,9 @@ type VerifyVSAOptions struct {
|
||||
VerifierID string
|
||||
ResourceUri string
|
||||
VerifiedLevels []string
|
||||
PublicKeyPath string
|
||||
PublicKeyID string
|
||||
SignatureHashAlgo string
|
||||
PrintAttestations bool
|
||||
}
|
||||
|
||||
@@ -159,11 +162,22 @@ func (o *VerifyVSAOptions) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVar(&o.PrintAttestations, "print-attestations", false,
|
||||
"[optional] print the verified attestations to stdout")
|
||||
|
||||
cmd.Flags().StringVar(&o.PublicKeyPath, "public-key-path", "",
|
||||
"path to a public key file")
|
||||
|
||||
cmd.Flags().StringVar(&o.PublicKeyID, "public-key-id", "",
|
||||
"the ID of the public key")
|
||||
|
||||
cmd.Flags().StringVar(&o.SignatureHashAlgo, "public-key-hash-algo", "SHA256",
|
||||
"the hash algorithm used to hash the public key, one of SHA256, SHA384, or SHA512")
|
||||
|
||||
cmd.MarkFlagRequired("subject-digests")
|
||||
cmd.MarkFlagRequired("attestations-path")
|
||||
cmd.MarkFlagRequired("verifier-id")
|
||||
cmd.MarkFlagRequired("resource-uri")
|
||||
cmd.MarkFlagRequired("verified-levels")
|
||||
cmd.MarkFlagRequired("public-key-path")
|
||||
// public-key-id" and "public-key-hash-algo" are optional since they have useful defaults
|
||||
}
|
||||
|
||||
type workflowInputs struct {
|
||||
|
||||
@@ -16,10 +16,13 @@ package verify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/sigstore/sigstore/pkg/cryptoutils"
|
||||
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
|
||||
"github.com/slsa-framework/slsa-verifier/v2/options"
|
||||
"github.com/slsa-framework/slsa-verifier/v2/verifiers"
|
||||
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
|
||||
@@ -33,6 +36,16 @@ type VerifyVSACommand struct {
|
||||
ResourceUri *string
|
||||
VerifiedLevels *[]string
|
||||
PrintAttestations *bool
|
||||
PublicKeyPath *string
|
||||
PublicKeyID *string
|
||||
SignatureHashAlgo *string
|
||||
}
|
||||
|
||||
var hashAlgos = map[string]crypto.Hash{
|
||||
"": crypto.SHA256, // default to SHA256
|
||||
"SHA256": crypto.SHA256,
|
||||
"SHA384": crypto.SHA384,
|
||||
"SHA512": crypto.SHA512,
|
||||
}
|
||||
|
||||
// Exec executes the verifiers.VerifyVSA
|
||||
@@ -48,12 +61,34 @@ func (c *VerifyVSACommand) Exec(ctx context.Context) (*utils.TrustedAttesterID,
|
||||
ExpectedResourceURI: *c.ResourceUri,
|
||||
ExpectedVerifiedLevels: *c.VerifiedLevels,
|
||||
}
|
||||
pubKeyBytes, err := os.ReadFile(*c.PublicKeyPath)
|
||||
if err != nil {
|
||||
printFailed(err)
|
||||
return nil, err
|
||||
}
|
||||
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w: %w", serrors.ErrorInvalidPublicKey, err)
|
||||
printFailed(err)
|
||||
return nil, err
|
||||
}
|
||||
hashHalgo, ok := hashAlgos[*c.SignatureHashAlgo]
|
||||
if !ok {
|
||||
err := fmt.Errorf("%w: %s", serrors.ErrorInvalidHashAlgo, *c.SignatureHashAlgo)
|
||||
printFailed(err)
|
||||
return nil, err
|
||||
}
|
||||
VerificationOpts := &options.VerificationOpts{
|
||||
PublicKey: pubKey,
|
||||
PublicKeyID: *c.PublicKeyID,
|
||||
SignatureHashAlgo: hashHalgo,
|
||||
}
|
||||
attestations, err := os.ReadFile(*c.AttestationsPath)
|
||||
if err != nil {
|
||||
printFailed(err)
|
||||
return nil, err
|
||||
}
|
||||
verifiedProvenance, outProducerID, err := verifiers.VerifyVSA(ctx, attestations, vsaOpts)
|
||||
verifiedProvenance, outProducerID, err := verifiers.VerifyVSA(ctx, attestations, vsaOpts, VerificationOpts)
|
||||
if err != nil {
|
||||
printFailed(err)
|
||||
return nil, err
|
||||
|
||||
@@ -44,4 +44,5 @@ var (
|
||||
ErrorInvalidHash = errors.New("invalid hash")
|
||||
ErrorNotPresent = errors.New("not present")
|
||||
ErrorInvalidPublicKey = errors.New("invalid public key")
|
||||
ErrorInvalidHashAlgo = errors.New("unsupported hash algorithm")
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package options
|
||||
|
||||
import "crypto"
|
||||
|
||||
// ProvenanceOpts are the options for checking provenance information.
|
||||
type ProvenanceOpts struct {
|
||||
// ExpectedBranch is the expected branch (github_ref or github_base_ref) in
|
||||
@@ -52,3 +54,14 @@ type VSAOpts struct {
|
||||
// ExpectedVerifiedLevels is the levels of verification that are passed from user and not verified
|
||||
ExpectedVerifiedLevels []string
|
||||
}
|
||||
|
||||
type VerificationOpts struct {
|
||||
// PublicKey is the public key used to verify the signature on the Envelope
|
||||
PublicKey crypto.PublicKey
|
||||
|
||||
// PublicKeyID is the ID of the public key
|
||||
PublicKeyID string
|
||||
|
||||
// SignatureHashAlgo is the hash algorithm used to hash the signature
|
||||
SignatureHashAlgo crypto.Hash
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -2,18 +2,15 @@ package vsa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
sigstoreBundle "github.com/sigstore/sigstore-go/pkg/bundle"
|
||||
sigstoreCryptoUtils "github.com/sigstore/sigstore/pkg/cryptoutils"
|
||||
sigstoreSignature "github.com/sigstore/sigstore/pkg/signature"
|
||||
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"
|
||||
)
|
||||
@@ -22,6 +19,7 @@ import (
|
||||
func VerifyVSA(ctx context.Context,
|
||||
attestations []byte,
|
||||
vsaOpts *options.VSAOpts,
|
||||
verificationOpts *options.VerificationOpts,
|
||||
) ([]byte, *utils.TrustedAttesterID, error) {
|
||||
// parse the envelope
|
||||
envelope, err := utils.EnvelopeFromBytes(attestations)
|
||||
@@ -32,63 +30,58 @@ func VerifyVSA(ctx context.Context,
|
||||
Envelope: envelope,
|
||||
}
|
||||
|
||||
// verify the envelope. signature
|
||||
err = verifyEnvelopeSignature(ctx, &sigstoreEnvelope)
|
||||
// 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, &sigstoreEnvelope, verificationOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// verify the metadata
|
||||
statement, err := utils.StatementFromEnvelope(envelope)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// 3. parse the VSA, verifying the predicateType.
|
||||
vsa, err := vsa10.VSAFromStatement(statement)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 2. match the subject digests,
|
||||
// 4. match the verifier ID,
|
||||
// 5. match the expected valuesmatch resourceURI,
|
||||
// 6. confirm the slsaResult is PASSED,
|
||||
// 7. match the verifiedLevels,
|
||||
// no other feields are checked.
|
||||
err = matchExpectedValues(vsa, vsaOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// print the attestation
|
||||
return nil, nil, nil
|
||||
trustedAttesterID, err := utils.TrustedAttesterIDNew(vsa.Predicate.Verifier.ID, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vsaBytes, err := envelope.DecodeB64Payload()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %w", serrors.ErrorInvalidDssePayload, err)
|
||||
}
|
||||
return vsaBytes, trustedAttesterID, 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 {
|
||||
// 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)
|
||||
// verifyEnvelopeSignature verifies the signature of the envelope.
|
||||
func verifyEnvelopeSignature(ctx context.Context, sigstoreEnvelope *sigstoreBundle.Envelope, verificationOpts *options.VerificationOpts) error {
|
||||
signatureVerifier, err := sigstoreSignature.LoadVerifier(verificationOpts.PublicKey, verificationOpts.SignatureHashAlgo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: loading sigstore DSSE envolope verifier %w", serrors.ErrorInvalidPublicKey, err)
|
||||
}
|
||||
// create the envelope verifier with all adapters
|
||||
envelopeVerifier, err := dsse.NewEnvelopeVerifier(verifierAdapters...)
|
||||
envelopeVerifier, err := dsse.NewEnvelopeVerifier(&sigstoreDSSE.VerifierAdapter{
|
||||
SignatureVerifier: signatureVerifier,
|
||||
Pub: verificationOpts.PublicKey,
|
||||
PubKeyID: verificationOpts.PublicKeyID,
|
||||
})
|
||||
if err != nil {
|
||||
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)
|
||||
@@ -98,10 +91,26 @@ func verifyEnvelopeSignature(ctx context.Context, sigstoreEnvelope *sigstoreBund
|
||||
|
||||
// matchExpectedValues checks if the expected values are present in the VSA.
|
||||
func matchExpectedValues(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
|
||||
// 2. match the expected subject digests
|
||||
if err := matchExepectedSubjectDigests(vsa, vsaOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: match other expected values
|
||||
// 4. match the verifier ID
|
||||
if err := matchVerifierID(vsa, vsaOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
// 5. match the expected resourceURI
|
||||
if err := matchResourceURI(vsa, vsaOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
// 6. confirm the slsaResult is Passed
|
||||
if err := conirmSLASResult(vsa); err != nil {
|
||||
return err
|
||||
}
|
||||
// 7. match the verifiedLevels
|
||||
if err := matchVerifiedLevels(vsa, vsaOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -144,3 +153,46 @@ func matchExepectedSubjectDigests(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) erro
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 != vsaOpts.ExpectedVerifierID {
|
||||
return fmt.Errorf("%w: verifier ID mismatch: expected %s, got %s", serrors.ErrorInvalidDssePayload, vsa.Predicate.Verifier.ID, vsa.Predicate.Verifier.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 != vsaOpts.ExpectedResourceURI {
|
||||
return fmt.Errorf("%w: resource URI mismatch: expected %s, got %s", serrors.ErrorInvalidDssePayload, vsa.Predicate.ResourceURI, vsaOpts.ExpectedResourceURI)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// confirmSLASResult confirms the VSA verification result is PASSED.
|
||||
func conirmSLASResult(vsa *vsa10.VSA) error {
|
||||
if normalizeString(vsa.Predicate.VerificationResult) != "PASSED" {
|
||||
return fmt.Errorf("%w: verification result is not Passed: %s", serrors.ErrorInvalidDssePayload, vsa.Predicate.VerificationResult)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchVerifiedLevels checks if the verified levels in the VSA match the expected values.
|
||||
func matchVerifiedLevels(vsa *vsa10.VSA, vsaOpts *options.VSAOpts) error {
|
||||
vsaLevels := make(map[string]bool)
|
||||
for _, level := range vsa.Predicate.VerifiedLevels {
|
||||
vsaLevels[level] = true
|
||||
}
|
||||
for _, expectedLevel := range vsaOpts.ExpectedVerifiedLevels {
|
||||
if _, ok := vsaLevels[normalizeString(expectedLevel)]; !ok {
|
||||
return fmt.Errorf("%w: expected verified level not found: %s", serrors.ErrorInvalidDssePayload, expectedLevel)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeString normalizes a string by trimming whitespace and converting to uppercase.
|
||||
func normalizeString(s string) string {
|
||||
return strings.TrimSpace(strings.ToUpper(s))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,23 @@ import (
|
||||
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
|
||||
)
|
||||
|
||||
// TrustedAttesterID represents an identifer that has been explicitly trusted.
|
||||
type TrustedAttesterID struct {
|
||||
TrustedBuilderID
|
||||
}
|
||||
|
||||
// TrustedAttesterIDNew creates a new AttesterID structure.
|
||||
func TrustedAttesterIDNew(attesterID string, needVersion bool) (*TrustedAttesterID, error) {
|
||||
builderID, err := TrustedBuilderIDNew(attesterID, needVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustedAttesterID := &TrustedAttesterID{
|
||||
TrustedBuilderID: *builderID,
|
||||
}
|
||||
return trustedAttesterID, nil
|
||||
}
|
||||
|
||||
// TrustedBuilderID represents a builder ID that has been explicitly trusted.
|
||||
type TrustedBuilderID struct {
|
||||
name, version string
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package utils
|
||||
|
||||
// TrustedAttesterID represents an identifer that has been explicitly trusted.
|
||||
type TrustedAttesterID struct {
|
||||
name, version string
|
||||
}
|
||||
@@ -80,6 +80,7 @@ func VerifyNpmPackage(ctx context.Context,
|
||||
func VerifyVSA(ctx context.Context,
|
||||
attestations []byte,
|
||||
vsaOpts *options.VSAOpts,
|
||||
verificationOpts *options.VerificationOpts,
|
||||
) ([]byte, *utils.TrustedAttesterID, error) {
|
||||
return vsa.VerifyVSA(ctx, attestations, vsaOpts)
|
||||
return vsa.VerifyVSA(ctx, attestations, vsaOpts, verificationOpts)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user