diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index e27522552852..526fb5b3c654 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -352,6 +352,20 @@ func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co if cert == nil { return bundleVerified, errors.New("no certificate found on signature") } + // Create a certificate pool for intermediate CA certificates, excluding the root + chain, err := sig.Chain() + if err != nil { + return bundleVerified, err + } + if len(chain) > 1 { + pool := x509.NewCertPool() + for _, cert := range chain[:len(chain)-1] { + pool.AddCert(cert) + } + co.IntermediateCerts = pool + } else { + co.IntermediateCerts = nil + } verifier, err = ValidateAndUnpackCert(cert, co) if err != nil { return bundleVerified, err @@ -515,6 +529,20 @@ func verifyImageAttestations(ctx context.Context, atts oci.Signatures, h v1.Hash if cert == nil { return errors.New("no certificate found on attestation") } + // Create a certificate pool for intermediate CA certificates, excluding the root + chain, err := att.Chain() + if err != nil { + return err + } + if len(chain) > 1 { + pool := x509.NewCertPool() + for _, cert := range chain[:len(chain)-1] { + pool.AddCert(cert) + } + co.IntermediateCerts = pool + } else { + co.IntermediateCerts = nil + } verifier, err = ValidateAndUnpackCert(cert, co) if err != nil { return err diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index a1bdb356c1a8..a99c12bf492e 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -17,12 +17,17 @@ package cosign import ( "context" "crypto" + "crypto/rand" + "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" + "encoding/pem" "io" + "strings" "testing" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/pkg/errors" "github.com/secure-systems-lab/go-securesystemslib/dsse" @@ -94,6 +99,115 @@ func Test_verifyOCIAttestation(t *testing.T) { } } +func TestVerifyImageSignature(t *testing.T) { + rootCert, rootKey, _ := test.GenerateRootCa() + subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, append(pemSub, pemRoot...))) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool}) + if err != nil { + t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) + } + // TODO: Create fake bundle and test verification + if verified == true { + t.Fatalf("expected verified=false, got verified=true") + } +} + +func TestVerifyImageSignatureMultipleSubs(t *testing.T) { + rootCert, rootKey, _ := test.GenerateRootCa() + subCert1, subKey1, _ := test.GenerateSubordinateCa(rootCert, rootKey) + subCert2, subKey2, _ := test.GenerateSubordinateCa(subCert1, subKey1) + subCert3, subKey3, _ := test.GenerateSubordinateCa(subCert2, subKey2) + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert3, subKey3) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemSub1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert1.Raw}) + pemSub2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert2.Raw}) + pemSub3 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert3.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + chain := append(pemSub3, append(pemSub2, append(pemSub1, pemRoot...)...)...) + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, chain)) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool}) + if err != nil { + t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) + } + // TODO: Create fake bundle and test verification + if verified == true { + t.Fatalf("expected verified=false, got verified=true") + } +} + +func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool}) + if err != nil { + t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) + } + // TODO: Create fake bundle and test verification + if verified == true { + t.Fatalf("expected verified=false, got verified=true") + } +} + +func TestVerifyImageSignatureWithMissingSub(t *testing.T) { + rootCert, rootKey, _ := test.GenerateRootCa() + subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) + pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool}) + if err == nil { + t.Fatal("expected error while verifying signature") + } + if !strings.Contains(err.Error(), "certificate signed by unknown authority") { + t.Fatal("expected error while verifying signature") + } + // TODO: Create fake bundle and test verification + if verified == true { + t.Fatalf("expected verified=false, got verified=true") + } +} + func TestValidateAndUnpackCertSuccess(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com"