Skip to content

Commit

Permalink
Add lint enforcing the restrictions on subject DN fields for mailbox …
Browse files Browse the repository at this point in the history
…validated SMIME certificates (#713)

* Add lint enforcing the restrictions on subject DN fields for mailbox validated SMIME certificates

* Add zlint copyright text to new files.

* Add cabf_smime_br lint source to TestNotMissingAnyLintSources

* refactor lint to add lists of allowed and forbidden fields into the lint struct

* rename mailboxValidatedEnforceSubjectFieldRestrictions lint to no longer export the underlying struct as per other lints in zlint

* Update mailbox lint to use new certificatelint interface

* fix mailbox validated field lint unit tests, reorganise smime testdata, remove unused test certificates

* Update v3/lints/cabf_smime_br/mailbox_validated_enforce_subject_field_restrictions.go comment to list relevant policy OIDs

Co-authored-by: Christopher Henderson <chris@chenderson.org>

* attempt to address lint complaint with comment describing CheckApplies of mailbox field presence lint

* Add explanatory comment to IsEmailProtectionCert

* Fix styling in time.go

---------

Co-authored-by: Christopher Henderson <chris@chenderson.org>
  • Loading branch information
robplee and christopher-henderson committed Aug 13, 2023
1 parent 624744d commit d959c83
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 24 deletions.
3 changes: 3 additions & 0 deletions v3/lint/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ func (l *CertificateLint) Execute(cert *x509.Certificate, config Configuration)
if l.Source == CABFBaselineRequirements && !util.IsServerAuthCert(cert) {
return &LintResult{Status: NA}
}
if l.Source == CABFSMIMEBaselineRequirements && !util.IsEmailProtectionCert(cert) {
return &LintResult{Status: NA}
}
lint := l.Lint()
err := config.MaybeConfigure(lint, l.Name)
if err != nil {
Expand Down
25 changes: 13 additions & 12 deletions v3/lint/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,19 @@ import (
type LintSource string

const (
UnknownLintSource LintSource = "Unknown"
RFC3279 LintSource = "RFC3279"
RFC5280 LintSource = "RFC5280"
RFC5480 LintSource = "RFC5480"
RFC5891 LintSource = "RFC5891"
RFC8813 LintSource = "RFC8813"
CABFBaselineRequirements LintSource = "CABF_BR"
CABFEVGuidelines LintSource = "CABF_EV"
MozillaRootStorePolicy LintSource = "Mozilla"
AppleRootStorePolicy LintSource = "Apple"
Community LintSource = "Community"
EtsiEsi LintSource = "ETSI_ESI"
UnknownLintSource LintSource = "Unknown"
RFC3279 LintSource = "RFC3279"
RFC5280 LintSource = "RFC5280"
RFC5480 LintSource = "RFC5480"
RFC5891 LintSource = "RFC5891"
RFC8813 LintSource = "RFC8813"
CABFBaselineRequirements LintSource = "CABF_BR"
CABFSMIMEBaselineRequirements LintSource = "CABF_SMIME_BR"
CABFEVGuidelines LintSource = "CABF_EV"
MozillaRootStorePolicy LintSource = "Mozilla"
AppleRootStorePolicy LintSource = "Apple"
Community LintSource = "Community"
EtsiEsi LintSource = "ETSI_ESI"
)

// UnmarshalJSON implements the json.Unmarshaler interface. It ensures that the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cabf_smime_br

/*
* ZLint Copyright 2021 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import (
"fmt"

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

// mailboxValidatedEnforceSubjectFieldRestrictions - linter to enforce MAY/SHALL NOT requirements for mailbox validated SMIME certificates
type mailboxValidatedEnforceSubjectFieldRestrictions struct {
forbiddenSubjectFields map[string]string
allowedSubjectFields map[string]string
}

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_mailbox_validated_enforce_subject_field_restrictions",
Description: "SMIME certificates complying to mailbox validated profiles MAY only contain commonName, serialNumber or emailAddress attributes in the Subject DN",
Citation: "SMIME BRs: 7.1.4.2.3",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABF_SMIME_BRs_1_0_0_Date,
},
Lint: func() lint.CertificateLintInterface {
return NewMailboxValidatedEnforceSubjectFieldRestrictions()
},
})
}

// NewMailboxValidatedEnforceSubjectFieldRestrictions creates a new linter to enforce MAY/SHALL NOT field requirements for mailbox validated SMIME certs
func NewMailboxValidatedEnforceSubjectFieldRestrictions() lint.LintInterface {
return &mailboxValidatedEnforceSubjectFieldRestrictions{
forbiddenSubjectFields: map[string]string{
"0.9.2342.19200300.100.1.25": "subject:domainComponent",
"1.3.6.1.4.1.311.60.2.1.1": "subject:jurisdictionLocality",
"1.3.6.1.4.1.311.60.2.1.2": "subject:jurisdictionProvince",
"1.3.6.1.4.1.311.60.2.1.3": "subject:jurisdictionCountry",
"2.5.4.4": "subject:surname",
"2.5.4.6": "subject:countryName",
"2.5.4.7": "subject:localityName",
"2.5.4.8": "subject:stateOrProvinceName",
"2.5.4.9": "subject:streetAddress",
"2.5.4.10": "subject:organizationName",
"2.5.4.11": "subject:organizationalUnitName",
"2.5.4.12": "subject:title",
"2.5.4.17": "subject:postalCode",
"2.5.4.42": "subject:givenName",
"2.5.4.65": "subject:pseudonym",
"2.5.4.97": "subject:organizationIdentifier",
},
allowedSubjectFields: map[string]string{
"1.2.840.113549.1.9.1": "subject:emailAddress",
"2.5.4.3": "subject:commonName",
"2.5.4.5": "subject:serialNumber",
},
}
}

// CheckApplies returns true if the provided certificate contains one-or-more of the following SMIME BR policy identifiers:
// - Mailbox Validated Legacy
// - Mailbox Validated Multipurpose
// - Mailbox Validated Strict
func (l *mailboxValidatedEnforceSubjectFieldRestrictions) CheckApplies(c *x509.Certificate) bool {
return util.IsMailboxValidatedCertificate(c)
}

// Execute applies the requirements on what fields are allowed for mailbox validated SMIME certificates
func (l *mailboxValidatedEnforceSubjectFieldRestrictions) Execute(c *x509.Certificate) *lint.LintResult {
for _, rdnSeq := range c.Subject.OriginalRDNS {
for _, field := range rdnSeq {
oidStr := field.Type.String()

if _, ok := l.allowedSubjectFields[oidStr]; !ok {
if fieldName, knownField := l.forbiddenSubjectFields[oidStr]; knownField {
return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("subject DN contains forbidden field: %s (%s)", fieldName, oidStr)}
}
return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("subject DN contains forbidden field: %s", oidStr)}
}
}
}

return &lint.LintResult{Status: lint.Pass}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cabf_smime_br

/*
* ZLint Copyright 2021 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import (
"testing"

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

func TestMailboxValidatedEnforceSubjectFieldRestrictions(t *testing.T) {
testCases := []struct {
Name string
InputFilename string

ExpectedResult lint.LintStatus
ExpectedDetails string
}{
{
Name: "pass - mailbox validated, legacy with commonName",
InputFilename: "smime/mailboxValidatedLegacyWithCommonName.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - mailbox validated, multipurpose with commonName",
InputFilename: "smime/mailboxValidatedMultipurposeWithCommonName.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - mailbox validated, strict with commonName",
InputFilename: "smime/mailboxValidatedStrictWithCommonName.pem",
ExpectedResult: lint.Pass,
},
{
Name: "na - certificate without mailbox validated policy",
InputFilename: "smime/domainValidatedWithEmailCommonName.pem",
ExpectedResult: lint.NA,
},
{
Name: "ne - certificate with NotBefore before effective date of lint",
InputFilename: "smime/mailboxValidatedLegacyWithCommonNameMay2023.pem",
ExpectedResult: lint.NE,
},
{
Name: "error - certificate with countryName",
InputFilename: "smime/mailboxValidatedLegacyWithCountryName.pem",
ExpectedResult: lint.Error,
ExpectedDetails: "subject DN contains forbidden field: subject:countryName (2.5.4.6)",
},
{
Name: "error - certificate containing nonsense subject field (1.2.3.4.5.6.7.8.9.0)",
InputFilename: "smime/mailboxValidatedMultipurposeWithNonsenseSubjectField.pem",
ExpectedResult: lint.Error,
ExpectedDetails: "subject DN contains forbidden field: 1.2.3.4.5.6.7.8.9.0",
},
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_mailbox_validated_enforce_subject_field_restrictions", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}

if tc.ExpectedDetails != "" && tc.ExpectedDetails != result.Details {
t.Errorf("expected details: %s, was %s", tc.ExpectedDetails, result.Details)
}
})
}
}
16 changes: 9 additions & 7 deletions v3/profiles/profiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
_ "github.com/zmap/zlint/v3/lints/apple"
_ "github.com/zmap/zlint/v3/lints/cabf_br"
_ "github.com/zmap/zlint/v3/lints/cabf_ev"
_ "github.com/zmap/zlint/v3/lints/cabf_smime_br"
_ "github.com/zmap/zlint/v3/lints/community"
_ "github.com/zmap/zlint/v3/lints/etsi"
_ "github.com/zmap/zlint/v3/lints/mozilla"
Expand All @@ -45,13 +46,14 @@ func TestLintsInAllProfilesExist(t *testing.T) {
// lint source in the future that we don't miss importing it into this test file.
func TestNotMissingAnyLintSources(t *testing.T) {
expected := map[string]bool{
"apple": true,
"cabf_br": true,
"cabf_ev": true,
"community": true,
"etsi": true,
"mozilla": true,
"rfc": true,
"apple": true,
"cabf_br": true,
"cabf_ev": true,
"cabf_smime_br": true,
"community": true,
"etsi": true,
"mozilla": true,
"rfc": true,
}
dir, err := ioutil.ReadDir("../lints")
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions v3/testdata/smime/domainValidatedWithEmailCommonName.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: Sep 2 00:00:00 2023 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject: CN = johnsmith@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:30:eb:57:97:dc:90:9a:27:8f:7f:39:80:fa:21:
aa:3d:48:b1:35:6d:39:97:cf:9e:a4:ca:42:22:0c:
b2:71:67:42:bb:f4:a3:56:4a:51:fc:5e:0f:ec:ed:
98:9e:11:cf:f0:8a:68:62:c4:bf:8f:7b:65:ec:30:
69:d5:64:41:76
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1

Signature Algorithm: ecdsa-with-SHA256
30:46:02:21:00:ab:fa:9a:25:c9:b9:5f:c3:7c:bf:c1:dd:d2:
dc:4f:00:ad:1d:b7:18:94:0f:a2:37:9d:34:13:b7:cf:7d:a1:
da:02:21:00:f3:20:3b:d8:74:0e:b9:8d:6e:7a:74:d1:00:c8:
72:fb:2c:34:6d:c0:c4:7e:5b:25:ef:04:27:5c:88:22:47:6f
-----BEGIN CERTIFICATE-----
MIIBKDCBzqADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwOTAyMDAwMDAwWhgP
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDDrV5fckJonj385gPohqj1IsTVt
OZfPnqTKQiIMsnFnQrv0o1ZKUfxeD+ztmJ4Rz/CKaGLEv497ZewwadVkQXajFzAV
MBMGA1UdIAQMMAowCAYGZ4EMAQIBMAoGCCqGSM49BAMCA0kAMEYCIQCr+polyblf
w3y/wd3S3E8ArR23GJQPojedNBO3z32h2gIhAPMgO9h0DrmNbnp00QDIcvssNG3A
xH5bJe8EJ1yIIkdv
-----END CERTIFICATE-----
39 changes: 39 additions & 0 deletions v3/testdata/smime/mailboxValidatedLegacyWithCommonName.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: Sep 2 00:00:00 2023 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject: CN = johnsmith@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:a1:ed:8b:dd:62:fc:cc:2d:f4:28:cd:8c:8d:5a:
1d:1f:6c:36:c3:03:81:b4:9f:6e:6d:2d:90:b1:7d:
fa:2f:eb:d6:3c:83:7c:9f:2c:5a:b4:37:3e:ae:56:
57:6b:db:df:6a:1c:db:73:e6:d4:25:b1:15:d6:47:
f2:71:de:51:d0
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Certificate Policies:
Policy: 2.23.140.1.5.1.1

Signature Algorithm: ecdsa-with-SHA256
30:45:02:20:41:fa:93:51:d2:80:69:a5:5e:4a:cb:85:6a:1e:
47:eb:cb:9b:b3:7b:2b:94:a7:be:a4:b2:55:cc:4a:15:16:f7:
02:21:00:81:0c:18:bd:55:7a:16:6a:0c:84:a9:3b:bf:29:e2:
21:d0:fd:b6:9b:99:14:5b:0b:55:a8:43:b9:64:b6:8e:dc
-----BEGIN CERTIFICATE-----
MIIBKDCBz6ADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwOTAyMDAwMDAwWhgP
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKHti91i/Mwt9CjNjI1aHR9sNsMD
gbSfbm0tkLF9+i/r1jyDfJ8sWrQ3Pq5WV2vb32oc23Pm1CWxFdZH8nHeUdCjGDAW
MBQGA1UdIAQNMAswCQYHZ4EMAQUBATAKBggqhkjOPQQDAgNIADBFAiBB+pNR0oBp
pV5Ky4VqHkfry5uzeyuUp76kslXMShUW9wIhAIEMGL1VehZqDISpO78p4iHQ/bab
mRRbC1WoQ7lkto7c
-----END CERTIFICATE-----
39 changes: 39 additions & 0 deletions v3/testdata/smime/mailboxValidatedLegacyWithCommonNameMay2023.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: May 2 00:00:00 2023 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject: CN = johnsmith@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:60:a6:a1:36:40:de:33:5a:09:73:86:a9:30:2c:
cb:43:aa:d7:77:f4:77:37:d7:bf:4c:f5:48:24:39:
1b:8f:fc:51:0a:77:81:3a:6e:34:c2:1c:ef:a8:03:
39:42:21:16:2e:1a:f7:ed:8d:0e:38:e0:9f:23:52:
04:3c:9e:9d:c4
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Certificate Policies:
Policy: 2.23.140.1.5.1.1

Signature Algorithm: ecdsa-with-SHA256
30:46:02:21:00:c8:88:94:49:ba:b0:73:0f:f0:c9:26:0c:5a:
99:a0:36:b4:6b:e0:cf:c1:2f:49:9b:cb:bc:d7:ac:52:97:f0:
ca:02:21:00:a5:14:41:7c:46:dc:dd:af:02:89:0e:3b:79:17:
16:c0:b1:3c:4a:c2:e3:e8:e5:51:9e:e9:9b:a1:69:01:c5:a0
-----BEGIN CERTIFICATE-----
MIIBKTCBz6ADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwNTAyMDAwMDAwWhgP
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGCmoTZA3jNaCXOGqTAsy0Oq13f0
dzfXv0z1SCQ5G4/8UQp3gTpuNMIc76gDOUIhFi4a9+2NDjjgnyNSBDyencSjGDAW
MBQGA1UdIAQNMAswCQYHZ4EMAQUBATAKBggqhkjOPQQDAgNJADBGAiEAyIiUSbqw
cw/wySYMWpmgNrRr4M/BL0mby7zXrFKX8MoCIQClFEF8RtzdrwKJDjt5FxbAsTxK
wuPo5VGe6ZuhaQHFoA==
-----END CERTIFICATE-----
Loading

0 comments on commit d959c83

Please sign in to comment.