mirror of
https://github.com/slsa-framework/slsa-verifier.git
synced 2026-08-18 02:56:31 +00:00
feat: Update SLSA verifier to support a global signing key for GCB V1 which… (#509)
* Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> * Update SLSA verifier to support a global signing key for GCB V1 which creates the signature in a DSSE-conformant PAE format - new public key for "global PAE signing key" - test data and unit tests Signed-off-by: Kevin Halk <khalk@google.com> --------- Signed-off-by: Kevin Halk <khalk@google.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"embed"
|
||||
"encoding/pem"
|
||||
@@ -9,12 +11,16 @@ import (
|
||||
"io/fs"
|
||||
"path"
|
||||
|
||||
dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
|
||||
)
|
||||
|
||||
//go:embed materials/*
|
||||
var publicKeys embed.FS
|
||||
|
||||
const GlobalPAEKeyID = "projects/verified-builder/locations/global/keyRings/attestor/cryptoKeys/provenanceSigner/cryptoKeyVersions/1"
|
||||
const GlobalPAEPublicKeyName = "global-pae"
|
||||
|
||||
type PublicKey struct {
|
||||
value []byte
|
||||
pubKey *ecdsa.PublicKey
|
||||
@@ -22,7 +28,7 @@ type PublicKey struct {
|
||||
// TODO: key type and size
|
||||
}
|
||||
|
||||
func PublicKeyNew(region string) (*PublicKey, error) {
|
||||
func NewPublicKey(region string) (*PublicKey, error) {
|
||||
content, err := fs.ReadFile(publicKeys, path.Join("materials", region+".key"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: cannot read key materials", err)
|
||||
@@ -61,3 +67,46 @@ func (p *PublicKey) VerifySignature(digest [32]byte, sig []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type GlobalPAEKey struct {
|
||||
publicKey *PublicKey
|
||||
Verifier *dsselib.EnvelopeVerifier
|
||||
}
|
||||
|
||||
func NewGlobalPAEKey() (*GlobalPAEKey, error) {
|
||||
publicKey, err := NewPublicKey(GlobalPAEPublicKeyName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create public key for Global PAE key: %w", err)
|
||||
}
|
||||
|
||||
globalPaeKey := &GlobalPAEKey{publicKey: publicKey}
|
||||
envVerifier, err := dsselib.NewEnvelopeVerifier(globalPaeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
globalPaeKey.Verifier = envVerifier
|
||||
return globalPaeKey, nil
|
||||
}
|
||||
|
||||
func (v *GlobalPAEKey) VerifyPAESignature(envelope *dsselib.Envelope) error {
|
||||
_, err := v.Verifier.Verify(envelope)
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify implements dsse.Verifier.Verify. It verifies
|
||||
// a signature formatted in DSSE-conformant PAE.
|
||||
func (v *GlobalPAEKey) Verify(data, sig []byte) error {
|
||||
// Verify the signature.
|
||||
digest := sha256.Sum256(data)
|
||||
return v.publicKey.VerifySignature(digest, sig)
|
||||
}
|
||||
|
||||
// KeyID implements dsse.Verifier.KeyID.
|
||||
func (v *GlobalPAEKey) KeyID() (string, error) {
|
||||
return GlobalPAEKeyID, nil
|
||||
}
|
||||
|
||||
// Public implements dsse.Verifier.Public.
|
||||
func (v *GlobalPAEKey) Public() crypto.PublicKey {
|
||||
return v.publicKey
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdMcJUyKbmarf6dydhfmAjgmK6c42
|
||||
oCCNRR1se3Bi3VO65KcGk6qyci6/bsu2s4u+dLKWrsUQomEw4v3FtVctoA==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -25,6 +25,8 @@ var GCBBuilderIDs = []string{
|
||||
"https://cloudbuild.googleapis.com/GoogleHostedWorker@v0.3",
|
||||
}
|
||||
|
||||
var regionalKeyRegex = regexp.MustCompile(`^projects\/verified-builder\/locations\/(.*)\/keyRings\/attestor\/cryptoKeys\/builtByGCB\/cryptoKeyVersions\/1$`)
|
||||
|
||||
type v01IntotoStatement struct {
|
||||
intoto.StatementHeader
|
||||
Predicate ProvenancePredicate `json:"predicate"`
|
||||
@@ -416,20 +418,35 @@ func (p *Provenance) verifySignatures(prov *provenance) error {
|
||||
}
|
||||
|
||||
payloadHash := sha256.Sum256(payload)
|
||||
|
||||
// Verify the signatures.
|
||||
if len(prov.Envelope.Signatures) == 0 {
|
||||
return fmt.Errorf("%w: no signatures found in envelope", serrors.ErrorNoValidSignature)
|
||||
}
|
||||
|
||||
var errs []error
|
||||
regex := regexp.MustCompile(`^projects\/verified-builder\/locations\/(.*)\/keyRings\/attestor\/cryptoKeys\/builtByGCB\/cryptoKeyVersions\/1$`)
|
||||
|
||||
for _, sig := range prov.Envelope.Signatures {
|
||||
match := regex.FindStringSubmatch(sig.KeyID)
|
||||
if len(match) == 2 {
|
||||
// Create a public key instance for this region.
|
||||
region := match[1]
|
||||
pubKey, err := keys.PublicKeyNew(region)
|
||||
var region string
|
||||
if sig.KeyID == keys.GlobalPAEKeyID {
|
||||
// If the signature is signed with the global PAE key, use a DSSE verifier
|
||||
// to verify the DSSE/PAE-encoded signature.
|
||||
region = keys.GlobalPAEPublicKeyName
|
||||
globalPaeKey, err := keys.NewGlobalPAEKey()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = globalPaeKey.VerifyPAESignature(&prov.Envelope)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
} else if match := regionalKeyRegex.FindStringSubmatch(sig.KeyID); len(match) == 2 {
|
||||
// If the signature is signed with a regional key, verify the legacy
|
||||
// signing which is over the envelope (not PAE-encoded).
|
||||
region = match[1]
|
||||
pubKey, err := keys.NewPublicKey(region)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
@@ -448,16 +465,18 @@ func (p *Provenance) verifySignatures(prov *provenance) error {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var statement v01IntotoStatement
|
||||
if err := json.Unmarshal(payload, &statement); err != nil {
|
||||
return fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err.Error())
|
||||
}
|
||||
p.verifiedIntotoStatement = &statement
|
||||
p.verifiedProvenance = prov
|
||||
fmt.Fprintf(os.Stderr, "Verification succeeded with region key '%s'\n", region)
|
||||
return nil
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
var statement v01IntotoStatement
|
||||
if err := json.Unmarshal(payload, &statement); err != nil {
|
||||
return fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err.Error())
|
||||
}
|
||||
p.verifiedIntotoStatement = &statement
|
||||
p.verifiedProvenance = prov
|
||||
fmt.Fprintf(os.Stderr, "Verification succeeded with region key '%s'\n", region)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %v", serrors.ErrorNoValidSignature, errs)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
|
||||
)
|
||||
|
||||
// This function sets the statement of the proveannce, as if
|
||||
// This function sets the statement of the provenance, as if
|
||||
// it had been verified. This is necessary because individual functions
|
||||
// expect this statement to be populated; and this is done only
|
||||
// after the signature is verified.
|
||||
@@ -506,6 +506,10 @@ func Test_VerifySignature(t *testing.T) {
|
||||
name: "valid gcb provenance",
|
||||
path: "./testdata/gcloud-container-github.json",
|
||||
},
|
||||
{
|
||||
name: "global gcb signing key",
|
||||
path: "./testdata/gcloud-container-global-pae-signing-key-successful.json",
|
||||
},
|
||||
{
|
||||
name: "invalid signature",
|
||||
path: "./testdata/gcloud-container-invalid-signature.json",
|
||||
@@ -516,6 +520,11 @@ func Test_VerifySignature(t *testing.T) {
|
||||
path: "./testdata/gcloud-container-invalid-signature-payloadtype.json",
|
||||
expected: serrors.ErrorNoValidSignature,
|
||||
},
|
||||
{
|
||||
name: "invalid signature - global PAE key",
|
||||
path: "./testdata/gcloud-container-invalid-signature-global-pae-key.json",
|
||||
expected: serrors.ErrorNoValidSignature,
|
||||
},
|
||||
{
|
||||
name: "invalid signature empty",
|
||||
path: "./testdata/gcloud-container-empty-signature.json",
|
||||
@@ -559,6 +568,10 @@ func Test_VerifySignature(t *testing.T) {
|
||||
name: "signature multiple 3rd valid",
|
||||
path: "./testdata/gcloud-container-multiple-signatures-3rdvalid.json",
|
||||
},
|
||||
{
|
||||
name: "signature multiple global pae valid",
|
||||
path: "./testdata/gcloud-container-multiple-signatures-global-pae-valid.json",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
@@ -578,7 +591,6 @@ func Test_VerifySignature(t *testing.T) {
|
||||
if err := setStatement(prov); err != nil {
|
||||
panic(fmt.Errorf("setStatement: %w", err))
|
||||
}
|
||||
|
||||
err = prov.VerifySignature()
|
||||
if !cmp.Equal(err, tt.expected, cmpopts.EquateErrors()) {
|
||||
t.Errorf(cmp.Diff(err, tt.expected, cmpopts.EquateErrors()))
|
||||
@@ -799,6 +811,10 @@ func Test_VerifyTextProvenance(t *testing.T) {
|
||||
name: "valid gcb provenance",
|
||||
path: "./testdata/gcloud-container-github.json",
|
||||
},
|
||||
{
|
||||
name: "valid gcb provenance with global signing key",
|
||||
path: "./testdata/gcloud-container-global-pae-signing-key-successful.json",
|
||||
},
|
||||
{
|
||||
name: "mismatch everything",
|
||||
path: "./testdata/gcloud-container-github.json",
|
||||
|
||||
+114
File diff suppressed because one or more lines are too long
+114
File diff suppressed because one or more lines are too long
+118
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user