Skip to content

Commit

Permalink
Add support for intermediate certificates when verifiying (sigstore#1631
Browse files Browse the repository at this point in the history
)

* Add support for intermediate certificates when verifiying

This adds an intermediate CA certificate pool to CheckOpts, allowing for
those using the Cosign library to pass intermediate CA certificates to
validate a certificate chain.

Signed-off-by: Hayden Blauzvern <[email protected]>

* Populate intermediate certs from the OCI chain annotation

This adds support for verifying OCI signatures with chains
that include more than one certificate in the chain, a root and
subordinate.

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper authored and mlieberman85 committed May 6, 2022
1 parent c74232b commit 8709a62
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 4 deletions.
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)
}
}

0 comments on commit 8709a62

Please sign in to comment.