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 Code Signing lints for EKU, Key Usage, RSA Key Size and CRLDistributionPoints #865

Merged
merged 15 commits into from
Jul 21, 2024
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
3 changes: 3 additions & 0 deletions v3/lint/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ func (l *CertificateLint) Execute(cert *x509.Certificate, config Configuration)
if l.Source == CABFSMIMEBaselineRequirements && !util.IsEmailProtectionCert(cert) {
return &LintResult{Status: NA}
}
if l.Source == CABFCSBaselineRequirements && !util.IsCodeSigning(cert.PolicyIdentifiers) {
return &LintResult{Status: NA}
}
lint := l.Lint()
err := config.MaybeConfigure(lint, l.Name)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions v3/lint/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
RFC6962 LintSource = "RFC6962"
RFC8813 LintSource = "RFC8813"
CABFBaselineRequirements LintSource = "CABF_BR"
CABFCSBaselineRequirements LintSource = "CABF_CS_BR"
CABFSMIMEBaselineRequirements LintSource = "CABF_SMIME_BR"
CABFEVGuidelines LintSource = "CABF_EV"
MozillaRootStorePolicy LintSource = "Mozilla"
Expand Down
62 changes: 62 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_crl_distribution_points.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cabf_cs_br

import (
"strings"

"github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

/*7.1.2.3 b. cRLDistributionPoints
This extension MUST be present. It MUST NOT be marked critical, and it MUST contain the
HTTP URL of the CA’s CRL service*/

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_cs_crl_distribution_points",
Description: "This extension MUST be present. It MUST NOT be marked critical. It MUST contain the HTTP URL of the CA's CRL service",
Citation: "CABF CS BRs 7.1.2.3.b",
Source: lint.CABFCSBaselineRequirements,
EffectiveDate: util.CABF_CS_BRs_1_2_Date,
},
Lint: NewCrlDistributionPoints,
})
}

type crlDistributionPoints struct{}

func NewCrlDistributionPoints() lint.LintInterface {
return &crlDistributionPoints{}
}

func (l *crlDistributionPoints) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c) || util.IsSubCA(c)
}

func (l *crlDistributionPoints) Execute(c *x509.Certificate) *lint.LintResult {
cdp := util.GetExtFromCert(c, util.CrlDistOID)
if cdp == nil {
return &lint.LintResult{
Status: lint.Error,
Details: "The cRLDistributionPoints extension MUST be present."}
}

if cdp.Critical {
return &lint.LintResult{
Status: lint.Error,
Details: "The cRLDistributionPoints MUST NOT be marked critical."}
}

// MUST contain the HTTP URL of the CA’s CRL service
for _, uri := range c.CRLDistributionPoints {
if !strings.HasPrefix(uri, "http://") {
return &lint.LintResult{Status: lint.Error, Details: "cRLDistributionPoints MUST contain the HTTP URL of the CA's CRL service"}
}
}

return &lint.LintResult{
Status: lint.Pass,
}
}
40 changes: 40 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_crl_distribution_points_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cabf_cs_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestCsCrlDistributionPoints(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - code signing certificate with CRLDistributionPoints",
InputFilename: "code_signing/validCodeSigningCertificate.pem",
ExpectedResult: lint.Pass,
},
{
Name: "fail - code signing certificate without CRLDistributionPoints",
InputFilename: "code_signing/noCrldpIncluded.pem",
ExpectedResult: lint.Error,
},
{
Name: "fail - code signing certificate with CRLDistributionPoints without http",
InputFilename: "code_signing/crlDpNoHttp.pem",
ExpectedResult: lint.Error,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_cs_crl_distribution_points", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
87 changes: 87 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_eku_required.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cabf_cs_br

import (
"fmt"

"github.com/zmap/zcrypto/x509"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

/* 7.1.2.3 Code signing and Timestamp Certificate
f. extKeyUsage
If the Certificate is a Code Signing Certificate, then id-kp-codeSigning MUST be present
and the following EKUs MAY be present:
• Lifetime Signing OID (1.3.6.1.4.1.311.10.3.13)
• id-kp-emailProtection
• Document Signing (1.3.6.1.4.1.311.3.10.3.12)

If the Certificate is a Timestamp Certificate, then id-kp-timeStamping MUST be present
and MUST be marked critical.
Additionally, the following EKUs MUST NOT be present:
• anyExtendedKeyUsage
• id-kp-serverAuth

Other values SHOULD NOT be present. If any other value is present, the CA MUST have a
business agreement with a Platform vendor requiring that EKU in order to issue a
Platform‐specific code signing certificate with that EKU.
*/

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_cs_eku_required",
Description: "If the Certificate is a Code Signing Certificate, then id-kp-codeSigning MUST be present. anyExtendedKeyUsage and id-kp-serverAuth MUST NOT be present.",
Citation: "CABF CS BRs 7.1.2.3.f",
Source: lint.CABFCSBaselineRequirements,
EffectiveDate: util.CABF_CS_BRs_1_2_Date,
},
Lint: NewCsEKURequired,
})
}

type csEKURequired struct{}

func NewCsEKURequired() lint.LintInterface {
return &csEKURequired{}
}

func (l *csEKURequired) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c) || util.IsSubCA(c)
}

func (l *csEKURequired) Execute(c *x509.Certificate) *lint.LintResult {
prohibitedEKUs := map[x509.ExtKeyUsage]struct{}{
x509.ExtKeyUsageAny: {},
x509.ExtKeyUsageServerAuth: {},
}

if util.IsSubCA(c) {
prohibitedEKUs[x509.ExtKeyUsageEmailProtection] = struct{}{}
}

hasCodeSigningEKU := false

for _, eku := range c.ExtKeyUsage {
if eku == x509.ExtKeyUsageCodeSigning {
hasCodeSigningEKU = true
}

if _, isProhibited := prohibitedEKUs[eku]; isProhibited {
return &lint.LintResult{
Status: lint.Error,
Details: fmt.Sprintf("Code Signing certificate includes prohibited EKU: %v", eku),
}
}
}

if !hasCodeSigningEKU {
return &lint.LintResult{
Status: lint.Error,
Details: "Code Signing certificate missing required Code Signing EKU",
}
}

return &lint.LintResult{Status: lint.Pass}
}
40 changes: 40 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_eku_required_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cabf_cs_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestCsEKUCheck(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - valid code signing certificate with required EKU",
InputFilename: "code_signing/validCodeSigningCertificate.pem",
ExpectedResult: lint.Pass,
},
{
Name: "fail - code signing certificate without required EKU",
InputFilename: "code_signing/noRequiredCodeSigningEKU.pem",
ExpectedResult: lint.Error,
},
{
Name: "fail - code signing certificate with prohibited EKU",
InputFilename: "code_signing/containsProhibitedEKU.pem",
ExpectedResult: lint.Error,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_cs_eku_required", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
79 changes: 79 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_key_usage_required.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cabf_cs_br

import (
"github.com/zmap/zcrypto/x509"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

/* 7.1.2.3 Code signing and Timestamp Certificate
e. keyUsage
This extension MUST be present and MUST be marked critical.
The bit position for digitalSignature MUST be set. Bit positions for keyCertSign and
cRLSign MUST NOT be set. All other bit positions SHOULD NOT be set.
*/

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_cs_key_usage_required",
Description: "This extension MUST be present and MUST be marked critical. The bit position for digitalSignature MUST be set. The bit positions for keyCertSign and cRLSign MUST NOT be set. All other bit positions SHOULD NOT be set.",
Citation: "CABF CS BRs 7.1.2.3e",
Source: lint.CABFCSBaselineRequirements,
EffectiveDate: util.CABF_CS_BRs_1_2_Date,
},
Lint: NewCsKeyUsageRequired,
})
}

type csKeyUsageRequired struct{}

func NewCsKeyUsageRequired() lint.LintInterface {
return &csKeyUsageRequired{}
}

func (l *csKeyUsageRequired) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c)
}

func (l *csKeyUsageRequired) Execute(c *x509.Certificate) *lint.LintResult {
ku := util.GetExtFromCert(c, util.KeyUsageOID)
if ku == nil {
return &lint.LintResult{
Status: lint.Error,
Details: "Key usage extension MUST be present.",
}
}

if !ku.Critical {
return &lint.LintResult{
Status: lint.Error,
Details: "Key usage extension MUST be marked critical",
}
}

if (c.KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "Code Signing certificate must have digitalSignature key usage",
}
}

// keyCertSign and cRLSign bits MUST NOT be set.
if (c.KeyUsage & (x509.KeyUsageCertSign | x509.KeyUsageCRLSign)) != 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "keyCertSign and cRLSign key usages MUST NOT be set",
}
}

// All other bit positions SHOULD NOT be set.
if c.KeyUsage & ^x509.KeyUsageDigitalSignature != 0 {
return &lint.LintResult{
Status: lint.Warn,
Details: "Only digitalSignature key usage is recommended. Other key usages SHOULD NOT be set."}
}

return &lint.LintResult{Status: lint.Pass}
}
45 changes: 45 additions & 0 deletions v3/lints/cabf_cs_br/lint_cs_key_usage_required_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cabf_cs_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestCsKeyUsageCheck(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - valid code signing certificate with digital signature key usage",
InputFilename: "code_signing/validCodeSigningCertificate.pem",
ExpectedResult: lint.Pass,
},
{
Name: "fail - code signing certificate without required key usage",
InputFilename: "code_signing/noDigitalSignatureKeyUsage.pem",
ExpectedResult: lint.Error,
},
{
Name: "fail - code signing certificate with prohibited key usage",
InputFilename: "code_signing/containsProhibitedKeyUsage.pem",
ExpectedResult: lint.Error,
},
{
Name: "warn - code signing certificate with not recommended key usage",
InputFilename: "code_signing/containsNotRecommendedKeyUsage.pem",
ExpectedResult: lint.Warn,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_cs_key_usage_required", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
Loading
Loading