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:
Kevin Halk
2023-03-06 16:02:30 +00:00
committed by GitHub
parent 9f57e6add9
commit 47495c7d5b
7 changed files with 453 additions and 19 deletions
+50 -1
View File
@@ -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-----
+35 -16
View File
@@ -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)
+18 -2
View File
@@ -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",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long