Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for intermediate certificates when verifiying #1631

Merged
merged 2 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type CheckOpts struct {

// RootCerts are the root CA certs used to verify a signature's chained certificate.
RootCerts *x509.CertPool
// IntermediateCerts are the optional intermediate CA certs used to verify a certificate chain.
IntermediateCerts *x509.CertPool
// CertEmail is the email expected for a certificate to be valid. The empty string means any certificate can be valid.
CertEmail string
// CertOidcIssuer is the OIDC issuer expected for a certificate to be valid. The empty string means any certificate can be valid.
Expand Down Expand Up @@ -149,7 +151,7 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver
}

// Now verify the cert, then the signature.
if err := TrustedCert(cert, co.RootCerts); err != nil {
if err := TrustedCert(cert, co.RootCerts, co.IntermediateCerts); err != nil {
return nil, err
}
if co.CertEmail != "" {
Expand Down Expand Up @@ -350,6 +352,21 @@ 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 the chain annotation is not present or there is only a root
if chain == nil || len(chain) <= 1 {
co.IntermediateCerts = nil
} else {
pool := x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
co.IntermediateCerts = pool
}
verifier, err = ValidateAndUnpackCert(cert, co)
if err != nil {
return bundleVerified, err
Expand Down Expand Up @@ -513,6 +530,21 @@ 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 the chain annotation is not present or there is only a root
if chain == nil || len(chain) <= 1 {
co.IntermediateCerts = nil
} else {
pool := x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
co.IntermediateCerts = pool
}
verifier, err = ValidateAndUnpackCert(cert, co)
if err != nil {
return err
Expand Down Expand Up @@ -783,13 +815,14 @@ func VerifySET(bundlePayload cbundle.RekorPayload, signature []byte, pub *ecdsa.
return nil
}

func TrustedCert(cert *x509.Certificate, roots *x509.CertPool) error {
func TrustedCert(cert *x509.Certificate, roots *x509.CertPool, intermediates *x509.CertPool) error {
if _, err := cert.Verify(x509.VerifyOptions{
// THIS IS IMPORTANT: WE DO NOT CHECK TIMES HERE
// THE CERTIFICATE IS TREATED AS TRUSTED FOREVER
// WE CHECK THAT THE SIGNATURES WERE CREATED DURING THIS WINDOW
CurrentTime: cert.NotBefore,
Roots: roots,
CurrentTime: cert.NotBefore,
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageCodeSigning,
},
Expand Down
195 changes: 195 additions & 0 deletions pkg/cosign/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -63,6 +68,15 @@ func (m *mockAttestation) Annotations() (map[string]string, error) {
func (m *mockAttestation) Payload() ([]byte, error) {
return json.Marshal(m.payload)
}

func appendSlices(slices [][]byte) []byte {
var tmp []byte
for _, s := range slices {
tmp = append(tmp, s...)
}
return tmp
}

func Test_verifyOCIAttestation(t *testing.T) {
stmt, err := json.Marshal(in_toto.ProvenanceStatement{})
if err != nil {
Expand Down Expand Up @@ -94,6 +108,140 @@ 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, appendSlices([][]byte{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)

ociSig, _ := static.NewSignature(payload,
base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub3, pemSub2, pemSub1, 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 TestVerifyImageSignatureWithNoChain(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey)
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, []byte{}))
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"
Expand Down Expand Up @@ -234,3 +382,50 @@ func TestCompareSigs(t *testing.T) {
})
}
}

func TestTrustedCertSuccess(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey)
leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey)

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)
subPool := x509.NewCertPool()
subPool.AddCert(subCert)

err := TrustedCert(leafCert, rootPool, subPool)
if err != nil {
t.Fatalf("expected no error verifying certificate, got %v", err)
}
}

func TestTrustedCertSuccessNoIntermediates(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey)

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

err := TrustedCert(leafCert, rootPool, nil)
if err != nil {
t.Fatalf("expected no error verifying certificate, got %v", err)
}
}

// Tests that verification succeeds if both a root and subordinate pool are
// present, but a chain is built with only the leaf and root certificates.
func TestTrustedCertSuccessChainFromRoot(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey)
subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootKey)

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)
subPool := x509.NewCertPool()
subPool.AddCert(subCert)

err := TrustedCert(leafCert, rootPool, subPool)
if err != nil {
t.Fatalf("expected no error verifying certificate, got %v", err)
}
}