Skip to content

Commit d959c83

Browse files
Add lint enforcing the restrictions on subject DN fields for mailbox 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 <[email protected]> * 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 <[email protected]>
1 parent 624744d commit d959c83

16 files changed

+532
-24
lines changed

v3/lint/base.go

+3
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ func (l *CertificateLint) Execute(cert *x509.Certificate, config Configuration)
221221
if l.Source == CABFBaselineRequirements && !util.IsServerAuthCert(cert) {
222222
return &LintResult{Status: NA}
223223
}
224+
if l.Source == CABFSMIMEBaselineRequirements && !util.IsEmailProtectionCert(cert) {
225+
return &LintResult{Status: NA}
226+
}
224227
lint := l.Lint()
225228
err := config.MaybeConfigure(lint, l.Name)
226229
if err != nil {

v3/lint/source.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,19 @@ import (
2727
type LintSource string
2828

2929
const (
30-
UnknownLintSource LintSource = "Unknown"
31-
RFC3279 LintSource = "RFC3279"
32-
RFC5280 LintSource = "RFC5280"
33-
RFC5480 LintSource = "RFC5480"
34-
RFC5891 LintSource = "RFC5891"
35-
RFC8813 LintSource = "RFC8813"
36-
CABFBaselineRequirements LintSource = "CABF_BR"
37-
CABFEVGuidelines LintSource = "CABF_EV"
38-
MozillaRootStorePolicy LintSource = "Mozilla"
39-
AppleRootStorePolicy LintSource = "Apple"
40-
Community LintSource = "Community"
41-
EtsiEsi LintSource = "ETSI_ESI"
30+
UnknownLintSource LintSource = "Unknown"
31+
RFC3279 LintSource = "RFC3279"
32+
RFC5280 LintSource = "RFC5280"
33+
RFC5480 LintSource = "RFC5480"
34+
RFC5891 LintSource = "RFC5891"
35+
RFC8813 LintSource = "RFC8813"
36+
CABFBaselineRequirements LintSource = "CABF_BR"
37+
CABFSMIMEBaselineRequirements LintSource = "CABF_SMIME_BR"
38+
CABFEVGuidelines LintSource = "CABF_EV"
39+
MozillaRootStorePolicy LintSource = "Mozilla"
40+
AppleRootStorePolicy LintSource = "Apple"
41+
Community LintSource = "Community"
42+
EtsiEsi LintSource = "ETSI_ESI"
4243
)
4344

4445
// UnmarshalJSON implements the json.Unmarshaler interface. It ensures that the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package cabf_smime_br
2+
3+
/*
4+
* ZLint Copyright 2021 Regents of the University of Michigan
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
7+
* use this file except in compliance with the License. You may obtain a copy
8+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13+
* implied. See the License for the specific language governing
14+
* permissions and limitations under the License.
15+
*/
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/zmap/zcrypto/x509"
21+
"github.com/zmap/zlint/v3/lint"
22+
"github.com/zmap/zlint/v3/util"
23+
)
24+
25+
// mailboxValidatedEnforceSubjectFieldRestrictions - linter to enforce MAY/SHALL NOT requirements for mailbox validated SMIME certificates
26+
type mailboxValidatedEnforceSubjectFieldRestrictions struct {
27+
forbiddenSubjectFields map[string]string
28+
allowedSubjectFields map[string]string
29+
}
30+
31+
func init() {
32+
lint.RegisterCertificateLint(&lint.CertificateLint{
33+
LintMetadata: lint.LintMetadata{
34+
Name: "e_mailbox_validated_enforce_subject_field_restrictions",
35+
Description: "SMIME certificates complying to mailbox validated profiles MAY only contain commonName, serialNumber or emailAddress attributes in the Subject DN",
36+
Citation: "SMIME BRs: 7.1.4.2.3",
37+
Source: lint.CABFSMIMEBaselineRequirements,
38+
EffectiveDate: util.CABF_SMIME_BRs_1_0_0_Date,
39+
},
40+
Lint: func() lint.CertificateLintInterface {
41+
return NewMailboxValidatedEnforceSubjectFieldRestrictions()
42+
},
43+
})
44+
}
45+
46+
// NewMailboxValidatedEnforceSubjectFieldRestrictions creates a new linter to enforce MAY/SHALL NOT field requirements for mailbox validated SMIME certs
47+
func NewMailboxValidatedEnforceSubjectFieldRestrictions() lint.LintInterface {
48+
return &mailboxValidatedEnforceSubjectFieldRestrictions{
49+
forbiddenSubjectFields: map[string]string{
50+
"0.9.2342.19200300.100.1.25": "subject:domainComponent",
51+
"1.3.6.1.4.1.311.60.2.1.1": "subject:jurisdictionLocality",
52+
"1.3.6.1.4.1.311.60.2.1.2": "subject:jurisdictionProvince",
53+
"1.3.6.1.4.1.311.60.2.1.3": "subject:jurisdictionCountry",
54+
"2.5.4.4": "subject:surname",
55+
"2.5.4.6": "subject:countryName",
56+
"2.5.4.7": "subject:localityName",
57+
"2.5.4.8": "subject:stateOrProvinceName",
58+
"2.5.4.9": "subject:streetAddress",
59+
"2.5.4.10": "subject:organizationName",
60+
"2.5.4.11": "subject:organizationalUnitName",
61+
"2.5.4.12": "subject:title",
62+
"2.5.4.17": "subject:postalCode",
63+
"2.5.4.42": "subject:givenName",
64+
"2.5.4.65": "subject:pseudonym",
65+
"2.5.4.97": "subject:organizationIdentifier",
66+
},
67+
allowedSubjectFields: map[string]string{
68+
"1.2.840.113549.1.9.1": "subject:emailAddress",
69+
"2.5.4.3": "subject:commonName",
70+
"2.5.4.5": "subject:serialNumber",
71+
},
72+
}
73+
}
74+
75+
// CheckApplies returns true if the provided certificate contains one-or-more of the following SMIME BR policy identifiers:
76+
// - Mailbox Validated Legacy
77+
// - Mailbox Validated Multipurpose
78+
// - Mailbox Validated Strict
79+
func (l *mailboxValidatedEnforceSubjectFieldRestrictions) CheckApplies(c *x509.Certificate) bool {
80+
return util.IsMailboxValidatedCertificate(c)
81+
}
82+
83+
// Execute applies the requirements on what fields are allowed for mailbox validated SMIME certificates
84+
func (l *mailboxValidatedEnforceSubjectFieldRestrictions) Execute(c *x509.Certificate) *lint.LintResult {
85+
for _, rdnSeq := range c.Subject.OriginalRDNS {
86+
for _, field := range rdnSeq {
87+
oidStr := field.Type.String()
88+
89+
if _, ok := l.allowedSubjectFields[oidStr]; !ok {
90+
if fieldName, knownField := l.forbiddenSubjectFields[oidStr]; knownField {
91+
return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("subject DN contains forbidden field: %s (%s)", fieldName, oidStr)}
92+
}
93+
return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("subject DN contains forbidden field: %s", oidStr)}
94+
}
95+
}
96+
}
97+
98+
return &lint.LintResult{Status: lint.Pass}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package cabf_smime_br
2+
3+
/*
4+
* ZLint Copyright 2021 Regents of the University of Michigan
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
7+
* use this file except in compliance with the License. You may obtain a copy
8+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13+
* implied. See the License for the specific language governing
14+
* permissions and limitations under the License.
15+
*/
16+
17+
import (
18+
"testing"
19+
20+
"github.com/zmap/zlint/v3/lint"
21+
"github.com/zmap/zlint/v3/test"
22+
)
23+
24+
func TestMailboxValidatedEnforceSubjectFieldRestrictions(t *testing.T) {
25+
testCases := []struct {
26+
Name string
27+
InputFilename string
28+
29+
ExpectedResult lint.LintStatus
30+
ExpectedDetails string
31+
}{
32+
{
33+
Name: "pass - mailbox validated, legacy with commonName",
34+
InputFilename: "smime/mailboxValidatedLegacyWithCommonName.pem",
35+
ExpectedResult: lint.Pass,
36+
},
37+
{
38+
Name: "pass - mailbox validated, multipurpose with commonName",
39+
InputFilename: "smime/mailboxValidatedMultipurposeWithCommonName.pem",
40+
ExpectedResult: lint.Pass,
41+
},
42+
{
43+
Name: "pass - mailbox validated, strict with commonName",
44+
InputFilename: "smime/mailboxValidatedStrictWithCommonName.pem",
45+
ExpectedResult: lint.Pass,
46+
},
47+
{
48+
Name: "na - certificate without mailbox validated policy",
49+
InputFilename: "smime/domainValidatedWithEmailCommonName.pem",
50+
ExpectedResult: lint.NA,
51+
},
52+
{
53+
Name: "ne - certificate with NotBefore before effective date of lint",
54+
InputFilename: "smime/mailboxValidatedLegacyWithCommonNameMay2023.pem",
55+
ExpectedResult: lint.NE,
56+
},
57+
{
58+
Name: "error - certificate with countryName",
59+
InputFilename: "smime/mailboxValidatedLegacyWithCountryName.pem",
60+
ExpectedResult: lint.Error,
61+
ExpectedDetails: "subject DN contains forbidden field: subject:countryName (2.5.4.6)",
62+
},
63+
{
64+
Name: "error - certificate containing nonsense subject field (1.2.3.4.5.6.7.8.9.0)",
65+
InputFilename: "smime/mailboxValidatedMultipurposeWithNonsenseSubjectField.pem",
66+
ExpectedResult: lint.Error,
67+
ExpectedDetails: "subject DN contains forbidden field: 1.2.3.4.5.6.7.8.9.0",
68+
},
69+
}
70+
71+
for _, tc := range testCases {
72+
t.Run(tc.Name, func(t *testing.T) {
73+
result := test.TestLint("e_mailbox_validated_enforce_subject_field_restrictions", tc.InputFilename)
74+
if result.Status != tc.ExpectedResult {
75+
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
76+
}
77+
78+
if tc.ExpectedDetails != "" && tc.ExpectedDetails != result.Details {
79+
t.Errorf("expected details: %s, was %s", tc.ExpectedDetails, result.Details)
80+
}
81+
})
82+
}
83+
}

v3/profiles/profiles_test.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
_ "github.com/zmap/zlint/v3/lints/apple"
2323
_ "github.com/zmap/zlint/v3/lints/cabf_br"
2424
_ "github.com/zmap/zlint/v3/lints/cabf_ev"
25+
_ "github.com/zmap/zlint/v3/lints/cabf_smime_br"
2526
_ "github.com/zmap/zlint/v3/lints/community"
2627
_ "github.com/zmap/zlint/v3/lints/etsi"
2728
_ "github.com/zmap/zlint/v3/lints/mozilla"
@@ -45,13 +46,14 @@ func TestLintsInAllProfilesExist(t *testing.T) {
4546
// lint source in the future that we don't miss importing it into this test file.
4647
func TestNotMissingAnyLintSources(t *testing.T) {
4748
expected := map[string]bool{
48-
"apple": true,
49-
"cabf_br": true,
50-
"cabf_ev": true,
51-
"community": true,
52-
"etsi": true,
53-
"mozilla": true,
54-
"rfc": true,
49+
"apple": true,
50+
"cabf_br": true,
51+
"cabf_ev": true,
52+
"cabf_smime_br": true,
53+
"community": true,
54+
"etsi": true,
55+
"mozilla": true,
56+
"rfc": true,
5557
}
5658
dir, err := ioutil.ReadDir("../lints")
5759
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Certificate:
2+
Data:
3+
Version: 3 (0x2)
4+
Serial Number: 3 (0x3)
5+
Signature Algorithm: ecdsa-with-SHA256
6+
Issuer:
7+
Validity
8+
Not Before: Sep 2 00:00:00 2023 GMT
9+
Not After : Nov 30 00:00:00 9998 GMT
10+
Subject: CN = [email protected]
11+
Subject Public Key Info:
12+
Public Key Algorithm: id-ecPublicKey
13+
Public-Key: (256 bit)
14+
pub:
15+
04:30:eb:57:97:dc:90:9a:27:8f:7f:39:80:fa:21:
16+
aa:3d:48:b1:35:6d:39:97:cf:9e:a4:ca:42:22:0c:
17+
b2:71:67:42:bb:f4:a3:56:4a:51:fc:5e:0f:ec:ed:
18+
98:9e:11:cf:f0:8a:68:62:c4:bf:8f:7b:65:ec:30:
19+
69:d5:64:41:76
20+
ASN1 OID: prime256v1
21+
NIST CURVE: P-256
22+
X509v3 extensions:
23+
X509v3 Certificate Policies:
24+
Policy: 2.23.140.1.2.1
25+
26+
Signature Algorithm: ecdsa-with-SHA256
27+
30:46:02:21:00:ab:fa:9a:25:c9:b9:5f:c3:7c:bf:c1:dd:d2:
28+
dc:4f:00:ad:1d:b7:18:94:0f:a2:37:9d:34:13:b7:cf:7d:a1:
29+
da:02:21:00:f3:20:3b:d8:74:0e:b9:8d:6e:7a:74:d1:00:c8:
30+
72:fb:2c:34:6d:c0:c4:7e:5b:25:ef:04:27:5c:88:22:47:6f
31+
-----BEGIN CERTIFICATE-----
32+
MIIBKDCBzqADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwOTAyMDAwMDAwWhgP
33+
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
34+
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDDrV5fckJonj385gPohqj1IsTVt
35+
OZfPnqTKQiIMsnFnQrv0o1ZKUfxeD+ztmJ4Rz/CKaGLEv497ZewwadVkQXajFzAV
36+
MBMGA1UdIAQMMAowCAYGZ4EMAQIBMAoGCCqGSM49BAMCA0kAMEYCIQCr+polyblf
37+
w3y/wd3S3E8ArR23GJQPojedNBO3z32h2gIhAPMgO9h0DrmNbnp00QDIcvssNG3A
38+
xH5bJe8EJ1yIIkdv
39+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Certificate:
2+
Data:
3+
Version: 3 (0x2)
4+
Serial Number: 3 (0x3)
5+
Signature Algorithm: ecdsa-with-SHA256
6+
Issuer:
7+
Validity
8+
Not Before: Sep 2 00:00:00 2023 GMT
9+
Not After : Nov 30 00:00:00 9998 GMT
10+
Subject: CN = [email protected]
11+
Subject Public Key Info:
12+
Public Key Algorithm: id-ecPublicKey
13+
Public-Key: (256 bit)
14+
pub:
15+
04:a1:ed:8b:dd:62:fc:cc:2d:f4:28:cd:8c:8d:5a:
16+
1d:1f:6c:36:c3:03:81:b4:9f:6e:6d:2d:90:b1:7d:
17+
fa:2f:eb:d6:3c:83:7c:9f:2c:5a:b4:37:3e:ae:56:
18+
57:6b:db:df:6a:1c:db:73:e6:d4:25:b1:15:d6:47:
19+
f2:71:de:51:d0
20+
ASN1 OID: prime256v1
21+
NIST CURVE: P-256
22+
X509v3 extensions:
23+
X509v3 Certificate Policies:
24+
Policy: 2.23.140.1.5.1.1
25+
26+
Signature Algorithm: ecdsa-with-SHA256
27+
30:45:02:20:41:fa:93:51:d2:80:69:a5:5e:4a:cb:85:6a:1e:
28+
47:eb:cb:9b:b3:7b:2b:94:a7:be:a4:b2:55:cc:4a:15:16:f7:
29+
02:21:00:81:0c:18:bd:55:7a:16:6a:0c:84:a9:3b:bf:29:e2:
30+
21:d0:fd:b6:9b:99:14:5b:0b:55:a8:43:b9:64:b6:8e:dc
31+
-----BEGIN CERTIFICATE-----
32+
MIIBKDCBz6ADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwOTAyMDAwMDAwWhgP
33+
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
34+
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKHti91i/Mwt9CjNjI1aHR9sNsMD
35+
gbSfbm0tkLF9+i/r1jyDfJ8sWrQ3Pq5WV2vb32oc23Pm1CWxFdZH8nHeUdCjGDAW
36+
MBQGA1UdIAQNMAswCQYHZ4EMAQUBATAKBggqhkjOPQQDAgNIADBFAiBB+pNR0oBp
37+
pV5Ky4VqHkfry5uzeyuUp76kslXMShUW9wIhAIEMGL1VehZqDISpO78p4iHQ/bab
38+
mRRbC1WoQ7lkto7c
39+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Certificate:
2+
Data:
3+
Version: 3 (0x2)
4+
Serial Number: 3 (0x3)
5+
Signature Algorithm: ecdsa-with-SHA256
6+
Issuer:
7+
Validity
8+
Not Before: May 2 00:00:00 2023 GMT
9+
Not After : Nov 30 00:00:00 9998 GMT
10+
Subject: CN = [email protected]
11+
Subject Public Key Info:
12+
Public Key Algorithm: id-ecPublicKey
13+
Public-Key: (256 bit)
14+
pub:
15+
04:60:a6:a1:36:40:de:33:5a:09:73:86:a9:30:2c:
16+
cb:43:aa:d7:77:f4:77:37:d7:bf:4c:f5:48:24:39:
17+
1b:8f:fc:51:0a:77:81:3a:6e:34:c2:1c:ef:a8:03:
18+
39:42:21:16:2e:1a:f7:ed:8d:0e:38:e0:9f:23:52:
19+
04:3c:9e:9d:c4
20+
ASN1 OID: prime256v1
21+
NIST CURVE: P-256
22+
X509v3 extensions:
23+
X509v3 Certificate Policies:
24+
Policy: 2.23.140.1.5.1.1
25+
26+
Signature Algorithm: ecdsa-with-SHA256
27+
30:46:02:21:00:c8:88:94:49:ba:b0:73:0f:f0:c9:26:0c:5a:
28+
99:a0:36:b4:6b:e0:cf:c1:2f:49:9b:cb:bc:d7:ac:52:97:f0:
29+
ca:02:21:00:a5:14:41:7c:46:dc:dd:af:02:89:0e:3b:79:17:
30+
16:c0:b1:3c:4a:c2:e3:e8:e5:51:9e:e9:9b:a1:69:01:c5:a0
31+
-----BEGIN CERTIFICATE-----
32+
MIIBKTCBz6ADAgECAgEDMAoGCCqGSM49BAMCMAAwIBcNMjMwNTAyMDAwMDAwWhgP
33+
OTk5ODExMzAwMDAwMDBaMCAxHjAcBgNVBAMMFWpvaG5zbWl0aEBleGFtcGxlLmNv
34+
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGCmoTZA3jNaCXOGqTAsy0Oq13f0
35+
dzfXv0z1SCQ5G4/8UQp3gTpuNMIc76gDOUIhFi4a9+2NDjjgnyNSBDyencSjGDAW
36+
MBQGA1UdIAQNMAswCQYHZ4EMAQUBATAKBggqhkjOPQQDAgNJADBGAiEAyIiUSbqw
37+
cw/wySYMWpmgNrRr4M/BL0mby7zXrFKX8MoCIQClFEF8RtzdrwKJDjt5FxbAsTxK
38+
wuPo5VGe6ZuhaQHFoA==
39+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)