From 7e10dc61f6405751ceb6a9dd5dfa02fbdbdb11a8 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 11 May 2022 23:03:37 +0000 Subject: [PATCH] Check certificate policy flags with only a certificate Flags such as cert-email and cert-oidc-issuer were only checked when a certificate and its chain were passed to Cosign, or when the certificate is fetched from either the OCI annotation or from Rekor (for verify-blob). This adds support for checking certificate policy flags when only a certificate is passed to Cosign. Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/verify/verify.go | 4 ++ cmd/cosign/cli/verify/verify_attestation.go | 4 ++ cmd/cosign/cli/verify/verify_blob.go | 14 +++-- pkg/cosign/verify.go | 59 +++++++++++++-------- pkg/cosign/verify_test.go | 23 ++++++++ 5 files changed, 76 insertions(+), 28 deletions(-) diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index fb9595fd54a..7c2f8f526cb 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -144,6 +144,10 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { return err } if c.CertChain == "" { + err = cosign.CheckCertificatePolicy(cert, co) + if err != nil { + return err + } pubKey, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { return err diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 93867633976..c32fc120095 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -123,6 +123,10 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return errors.Wrap(err, "loading certificate from reference") } if c.CertChain == "" { + err = cosign.CheckCertificatePolicy(cert, co) + if err != nil { + return err + } co.SigVerifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { return errors.Wrap(err, "creating certificate verifier") diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 77bef0e92e1..ccdc27b3d99 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -105,7 +105,16 @@ func VerifyBlobCmd(ctx context.Context, ko options.KeyOpts, certRef, certEmail, if err != nil { return err } + co := &cosign.CheckOpts{ + CertEmail: certEmail, + CertOidcIssuer: certOidcIssuer, + EnforceSCT: enforceSCT, + } if certChain == "" { + err = cosign.CheckCertificatePolicy(cert, co) + if err != nil { + return err + } verifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { return err @@ -116,11 +125,6 @@ func VerifyBlobCmd(ctx context.Context, ko options.KeyOpts, certRef, certEmail, if err != nil { return err } - co := &cosign.CheckOpts{ - CertEmail: certEmail, - CertOidcIssuer: certOidcIssuer, - EnforceSCT: enforceSCT, - } verifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) if err != nil { return err diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index e3b63413dde..b212ef6ba8b 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -172,6 +172,35 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver if err != nil { return nil, err } + + err = CheckCertificatePolicy(cert, co) + if err != nil { + return nil, err + } + + contains, err := ctl.ContainsSCT(cert.Raw) + if err != nil { + return nil, err + } + if co.EnforceSCT && !contains { + return nil, errors.New("certificate does not include required embedded SCT") + } + if contains { + // handle if chains has more than one chain - grab first and print message + if len(chains) > 1 { + fmt.Fprintf(os.Stderr, "**Info** Multiple valid certificate chains found. Selecting the first to verify the SCT.\n") + } + if err := ctl.VerifyEmbeddedSCT(context.Background(), chains[0]); err != nil { + return nil, err + } + } + + return verifier, nil +} + +// CheckCertificatePolicy checks that the certificate subject and issuer match +// the expected values. +func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error { if co.CertEmail != "" { emailVerified := false for _, em := range cert.EmailAddresses { @@ -181,12 +210,12 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver } } if !emailVerified { - return nil, errors.New("expected email not found in certificate") + return errors.New("expected email not found in certificate") } } if co.CertOidcIssuer != "" { if getIssuer(cert) != co.CertOidcIssuer { - return nil, errors.New("expected oidc issuer not found in certificate") + return errors.New("expected oidc issuer not found in certificate") } } // If there are identities given, go through them and if one of them @@ -198,7 +227,7 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver if identity.Issuer != "" { issuer := getIssuer(cert) if regex, err := regexp.Compile(identity.Issuer); err != nil { - return nil, fmt.Errorf("malformed issuer in identity: %s : %w", identity.Issuer, err) + return fmt.Errorf("malformed issuer in identity: %s : %w", identity.Issuer, err) } else if regex.MatchString(issuer) { issuerMatches = true } @@ -212,7 +241,7 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver if identity.Subject != "" { regex, err := regexp.Compile(identity.Subject) if err != nil { - return nil, fmt.Errorf("malformed subject in identity: %s : %w", identity.Subject, err) + return fmt.Errorf("malformed subject in identity: %s : %w", identity.Subject, err) } for _, san := range getSubjectAlternateNames(cert) { if regex.MatchString(san) { @@ -226,28 +255,12 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver } if subjectMatches && issuerMatches { // If both issuer / subject match, return verifier - return verifier, nil + return nil } } - return nil, errors.New("none of the expected identities matched what was in the certificate") + return errors.New("none of the expected identities matched what was in the certificate") } - contains, err := ctl.ContainsSCT(cert.Raw) - if err != nil { - return nil, err - } - if co.EnforceSCT && !contains { - return nil, errors.New("certificate does not include required embedded SCT") - } - if contains { - // handle if chains has more than one chain - grab first and print message - if len(chains) > 1 { - fmt.Fprintf(os.Stderr, "**Info** Multiple valid certificate chains found. Selecting the first to verify the SCT.\n") - } - if err := ctl.VerifyEmbeddedSCT(context.Background(), chains[0]); err != nil { - return nil, err - } - } - return verifier, nil + return nil } // getSubjectAlternateNames returns all of the following for a Certificate. diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index c8c2f7d5464..771ff3e0ac0 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -402,6 +402,10 @@ func TestValidateAndUnpackCertSuccess(t *testing.T) { if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } + err = CheckCertificatePolicy(leafCert, co) + if err != nil { + t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) + } } func TestValidateAndUnpackCertSuccessAllowAllValues(t *testing.T) { @@ -422,6 +426,10 @@ func TestValidateAndUnpackCertSuccessAllowAllValues(t *testing.T) { if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } + err = CheckCertificatePolicy(leafCert, co) + if err != nil { + t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) + } } func TestValidateAndUnpackCertWithSCT(t *testing.T) { @@ -522,6 +530,8 @@ func TestValidateAndUnpackCertInvalidOidcIssuer(t *testing.T) { _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected oidc issuer not found in certificate") + err = CheckCertificatePolicy(leafCert, co) + require.Contains(t, err.Error(), "expected oidc issuer not found in certificate") } func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { @@ -542,6 +552,8 @@ func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected email not found in certificate") + err = CheckCertificatePolicy(leafCert, co) + require.Contains(t, err.Error(), "expected email not found in certificate") } func TestValidateAndUnpackCertWithChainSuccess(t *testing.T) { @@ -705,6 +717,17 @@ func TestValidateAndUnpackCertWithIdentities(t *testing.T) { t.Errorf("Did not get the expected error %s, got err = %v", tc.wantErrSubstring, err) } } + // Test CheckCertificatePolicy + err = CheckCertificatePolicy(leafCert, co) + if err == nil && tc.wantErrSubstring != "" { + t.Errorf("Expected error %s got none", tc.wantErrSubstring) + } else if err != nil { + if tc.wantErrSubstring == "" { + t.Errorf("Did not expect an error, got err = %v", err) + } else if !strings.Contains(err.Error(), tc.wantErrSubstring) { + t.Errorf("Did not get the expected error %s, got err = %v", tc.wantErrSubstring, err) + } + } } } func TestCompareSigs(t *testing.T) {