Skip to content

Commit

Permalink
Use function to validate certificate chain
Browse files Browse the repository at this point in the history
This also checks if the certificate matches a subject provided via flag.

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Mar 25, 2022
1 parent 4f0cc3d commit 7950fa2
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 45 deletions.
22 changes: 8 additions & 14 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,28 +141,22 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
if err != nil {
return err
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if c.CertChain != "" {
certs, err := loadCertChainFromFileOrURL(c.CertChain)
if c.CertChain == "" {
pubKey, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
} else {
// Verify certificate with chain
chain, err := loadCertChainFromFileOrURL(c.CertChain)
if err != nil {
return err
}
err = cosign.TrustedCert(cert, rootPool, subPool)
pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
if err != nil {
return err
}
}
pubKey, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
}
co.SigVerifier = pubKey

Expand Down
27 changes: 10 additions & 17 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"flag"
Expand Down Expand Up @@ -123,27 +122,21 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if err != nil {
return errors.Wrap(err, "loading certificate from reference")
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if c.CertChain != "" {
certs, err := loadCertChainFromFileOrURL(c.CertChain)
if c.CertChain == "" {
co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
return errors.Wrap(err, "creating certificate verifier")
}
err = cosign.TrustedCert(cert, rootPool, subPool)
} else {
// Verify certificate with chain
chain, err := loadCertChainFromFileOrURL(c.CertChain)
if err != nil {
return err
}
}
co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return errors.Wrap(err, "creating certificate verifier")
co.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
if err != nil {
return errors.Wrap(err, "creating certificate verifier")
}
}
}

Expand Down
26 changes: 12 additions & 14 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,26 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, cer
if err != nil {
return err
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if certChain != "" {
certs, err := loadCertChainFromFileOrURL(certChain)
if certChain == "" {
verifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
} else {
// Verify certificate with chain
chain, err := loadCertChainFromFileOrURL(certChain)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
co := &cosign.CheckOpts{
CertEmail: certEmail,
CertOidcIssuer: certOidcIssuer,
}
err = cosign.TrustedCert(cert, rootPool, subPool)
verifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
if err != nil {
return err
}
}
verifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
}
case ko.BundlePath != "":
b, err := cosign.FetchLocalSignedPayloadFromPath(ko.BundlePath)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,26 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver
return verifier, nil
}

// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Veries that the certificate
// chains up to the provided root. Chain should start with the parent of the certificate and end with the root.
// Optionally verifies the subject of the certificate.
func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) {
if len(chain) == 0 {
return nil, errors.New("no chain provided to validate certificate")
}
rootPool := x509.NewCertPool()
rootPool.AddCert(chain[len(chain)-1])
co.RootCerts = rootPool

subPool := x509.NewCertPool()
for _, c := range chain[:len(chain)-1] {
subPool.AddCert(c)
}
co.IntermediateCerts = subPool

return ValidateAndUnpackCert(cert, co)
}

func tlogValidatePublicKey(ctx context.Context, rekorClient *client.Rekor, pub crypto.PublicKey, sig oci.Signature) error {
pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pub)
if err != nil {
Expand Down
74 changes: 74 additions & 0 deletions pkg/cosign/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,80 @@ func TestValidateAndUnpackCertInvalidEmail(t *testing.T) {
require.Contains(t, err.Error(), "expected email not found in certificate")
}

func TestValidateAndUnpackCertWithChainSuccess(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"

rootCert, rootKey, _ := test.GenerateRootCa()
subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey)
leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, subCert, subKey)

co := &CheckOpts{
CertEmail: subject,
CertOidcIssuer: oidcIssuer,
}

_, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{subCert, leafCert}, co)
if err != nil {
t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err)
}
}

func TestValidateAndUnpackCertWithChainSuccessWithRoot(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"

rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey)

co := &CheckOpts{
CertEmail: subject,
CertOidcIssuer: oidcIssuer,
}

_, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCert}, co)
if err != nil {
t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err)
}
}

func TestValidateAndUnpackCertWithChainFailsWithoutChain(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"

rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey)

co := &CheckOpts{
CertEmail: subject,
CertOidcIssuer: oidcIssuer,
}

_, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{}, co)
if err == nil || err.Error() != "no chain provided to validate certificate" {
t.Errorf("expected error without chain, got %v", err)
}
}

func TestValidateAndUnpackCertWithChainFailsWithInvalidChain(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"

rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey)
rootCertOther, _, _ := test.GenerateRootCa()

co := &CheckOpts{
CertEmail: subject,
CertOidcIssuer: oidcIssuer,
}

_, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCertOther}, co)
if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") {
t.Errorf("expected error without valid chain, got %v", err)
}
}

func TestCompareSigs(t *testing.T) {
// TODO(nsmith5): Add test cases for invalid signature, missing signature etc
tests := []struct {
Expand Down

0 comments on commit 7950fa2

Please sign in to comment.