Skip to content

Commit

Permalink
Add lint to enforce SMIME BRs: 7.1.4.2.1 requirement for mailbox addr…
Browse files Browse the repository at this point in the history
…esses

All mailbox addresses appearing in subjectDN or dirName must be repeated
in san:rfc822Name or san:otherName. This lint does its best to detect
mailbox address values in the subjectDN or dirName and if any are
detected ensures they are repeated.
  • Loading branch information
toddgaunt-gs committed Feb 20, 2024
1 parent a4b46ef commit f4b0243
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 1 deletion.
117 changes: 117 additions & 0 deletions v3/lints/cabf_smime_br/mailbox_address_from_san.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package cabf_smime_br

/*
* ZLint Copyright 2024 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 (
"github.com/zmap/zcrypto/encoding/asn1"
"github.com/zmap/zcrypto/x509"
"github.com/zmap/zcrypto/x509/pkix"
"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

// MailboxAddressFromSAN - linter to enforce MAY/SHALL NOT requirements for SMIME certificates
type MailboxAddressFromSAN struct {
}

func init() {
lint.RegisterLint(&lint.Lint{
Name: "e_mailbox_address_shall_contain_an_rfc822_name",
Description: "All Mailbox Addresses in the subject field or entries of type dirName of this extension SHALL be repeated as rfc822Name or otherName values of type id-on-SmtpUTF8Mailbox in this extension",
Citation: "SMIME BRs: 7.1.4.2.1",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.ZeroDate,
Lint: func() lint.LintInterface {
return NewMailboxAddressFromSAN()
},
})
}

// NewMailboxAddressFromSAN creates a new linter to enforce the requirement that all Mailbox Addresses in SMIME BR certificates must be copied from the SAN
func NewMailboxAddressFromSAN() lint.LintInterface {
return &MailboxAddressFromSAN{}
}

// CheckApplies is returns true if the certificate's policies assert that it conforms to the SMIME BRs
func (l *MailboxAddressFromSAN) CheckApplies(c *x509.Certificate) bool {
if util.HasEKU(c, x509.ExtKeyUsageEmailProtection) || util.HasEKU(c, x509.ExtKeyUsageAny) {
return true
}

return util.IsMailboxValidatedCertificate(c) && util.IsSubscriberCert(c)
}

// Execute checks all the places where Mailbox Addresses may be found in an SMIME certificate and confirms that they are present in the SAN rfc822Name or SAN otherName
func (l *MailboxAddressFromSAN) Execute(c *x509.Certificate) *lint.LintResult {
lintErr := &lint.LintResult{
Status: lint.Error,
Details: "all certificate mailbox addresses must be present in san:emailAddresses or san:otherNames in addition to any other field they may appear",
}

// build list of Mailbox addresses from subject:commonName, subject:emailAddress, dirName
toFindMailboxAddresses := []string{}

toFindMailboxAddresses = append(toFindMailboxAddresses, getMailboxAddressesFromDistinguishedName(c.Subject)...)

for _, dirName := range c.DirectoryNames {
toFindMailboxAddresses = append(toFindMailboxAddresses, getMailboxAddressesFromDistinguishedName(dirName)...)
}

sanNames := map[string]bool{}
for _, rfc822Name := range c.EmailAddresses {
sanNames[rfc822Name] = true
}

for _, otherName := range c.OtherNames {
if otherName.TypeID.Equal(util.OidIdOnSmtpUtf8Mailbox) {
// The otherName needs to be specially unmarshalled since it is
// stored as a UTF-8 string rather than what the asn1 package
// describes as a PrintableString.
var otherNameValue string
rest, err := asn1.UnmarshalWithParams(otherName.Value.Bytes, &otherNameValue, "utf8")
if len(rest) > 0 || err != nil {
return lintErr
}

sanNames[otherNameValue] = true
}
}

for _, mailboxAddress := range toFindMailboxAddresses {
if _, found := sanNames[mailboxAddress]; !found {
return lintErr
}
}

return &lint.LintResult{Status: lint.Pass}
}

func getMailboxAddressesFromDistinguishedName(name pkix.Name) []string {
mailboxAddresses := []string{}

for _, commonName := range name.CommonNames {
if util.IsMailboxAddress(commonName) {
mailboxAddresses = append(mailboxAddresses, commonName)
}
}

for _, emailAddress := range name.EmailAddress {
if util.IsMailboxAddress(emailAddress) {
mailboxAddresses = append(mailboxAddresses, emailAddress)
}
}

return mailboxAddresses
}
97 changes: 97 additions & 0 deletions v3/lints/cabf_smime_br/mailbox_address_from_san_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package cabf_smime_br

/*
* ZLint Copyright 2024 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 TestMailboxAddressFromSANLint(t *testing.T) {
testCases := []struct {
Name string
InputFilename string

ExpectedResult lint.LintStatus
ExpectedDetails string
}{
{
Name: "pass - subject:commonName email address matches san:otherName",
InputFilename: "WithOtherNameMatched.pem",

ExpectedResult: lint.Pass,
},
{
Name: "pass - subject:commonName email address matches san:emailAddress",
InputFilename: "WithSANEmailMatched.pem",

ExpectedResult: lint.Pass,
},
{
Name: "pass - only contains one san:emailAddress value",
InputFilename: "WithOnlySANEmail.pem",

ExpectedResult: lint.Pass,
},
{
Name: "pass - only contains one san:otherName value",
InputFilename: "WithOnlySANOtherName.pem",

ExpectedResult: lint.Pass,
},
{
Name: "NA - does not contain smime certificate policy",
InputFilename: "NotApplicable.pem",

ExpectedResult: lint.NA,
},
{
Name: "fail - subject:commonName email address does not match san:otherName",
InputFilename: "WithOtherNameUnmatched.pem",

ExpectedResult: lint.Error,
ExpectedDetails: "all certificate mailbox addresses must be present in san:emailAddresses or san:otherNames in addition to any other field they may appear",
},
{
Name: "fail - subject:commonName email address does not match the email value under san:otherName",
InputFilename: "WithOtherNameAsEmailUnmatched.pem",

ExpectedResult: lint.Error,
ExpectedDetails: "all certificate mailbox addresses must be present in san:emailAddresses or san:otherNames in addition to any other field they may appear",
},
{
Name: "fail - subject:commonName email address does not match san:emailAddress",
InputFilename: "WithSANEmailUnmatched.pem",

ExpectedResult: lint.Error,
ExpectedDetails: "all certificate mailbox addresses must be present in san:emailAddresses or san:otherNames in addition to any other field they may appear",
},
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_mailbox_address_shall_contain_an_rfc822_name", "smime/MailboxAddressFromSAN/"+tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v", tc.ExpectedResult, result.Status)
}

if tc.ExpectedResult == lint.Error && tc.ExpectedDetails != result.Details {
t.Errorf("expected details: %q, was %q", tc.ExpectedDetails, result.Details)
}
})
}
}
39 changes: 39 additions & 0 deletions v3/testdata/smime/MailboxAddressFromSAN/NotApplicable.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: Jan 1 00:00:00 0 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject: CN=test@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:27:76:32:bd:c0:68:3b:e1:18:89:fa:c1:ea:64:
d4:df:07:3d:b5:51:22:85:17:3c:c7:f7:d3:31:3e:
2a:fe:bd:f7:90:ee:5b:85:f6:2e:11:05:cf:7b:e5:
ed:38:ff:de:8a:58:26:93:83:55:67:c4:8b:53:6a:
db:cf:6c:78:8a
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Subject Alternative Name: critical
othername:<unsupported>
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:6a:5c:82:60:82:b4:a4:80:44:83:df:84:91:cc:
c3:c7:41:be:9d:34:03:1e:33:4b:f8:38:0d:e6:b9:5c:64:a3:
02:20:7e:80:91:b2:02:0d:5a:6d:09:ce:6f:2c:52:ef:a0:05:
6f:52:d1:76:46:1d:43:11:70:07:98:bd:62:1d:ba:40
-----BEGIN CERTIFICATE-----
MIIBVTCB/aADAgECAgEDMAoGCCqGSM49BAMCMAAwIhgPMDAwMDAxMDEwMDAwMDBa
GA85OTk4MTEzMDAwMDAwMFowGzEZMBcGA1UEAwwQdGVzdEBleGFtcGxlLmNvbTBZ
MBMGByqGSM49AgEGCCqGSM49AwEHA0IABCd2Mr3AaDvhGIn6wepk1N8HPbVRIoUX
PMf30zE+Kv6995DuW4X2LhEFz3vl7Tj/3opYJpODVWfEi1Nq289seIqjSTBHMEUG
A1UdEQEB/wQ7MDmgNwYIKwYBBQUHCAmgKwwpdGhpcyB2YWx1ZSBkb2VzIG5vdCBt
YXRjaCB0aGUgY29tbW9uIG5hbWUwCgYIKoZIzj0EAwIDRwAwRAIgalyCYIK0pIBE
g9+EkczDx0G+nTQDHjNL+DgN5rlcZKMCIH6AkbICDVptCc5vLFLvoAVvUtF2Rh1D
EXAHmL1iHbpA
-----END CERTIFICATE-----
41 changes: 41 additions & 0 deletions v3/testdata/smime/MailboxAddressFromSAN/WithOnlySANEmail.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: Jan 1 00:00:00 0 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject:
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:f4:05:c9:ed:e7:9f:2f:19:e7:fe:22:a6:b0:76:
55:60:c4:0a:f9:9a:02:a6:e3:92:ea:07:17:b2:69:
29:3a:28:6b:76:4b:06:a7:ab:e3:86:25:d2:37:f0:
58:94:ab:36:71:a0:b8:76:d9:7e:a1:2e:e1:bf:2a:
01:7e:f8:bb:3a
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Subject Alternative Name:
email:test@example.com
X509v3 Certificate Policies:
Policy: 2.23.140.1.5.1.1

Signature Algorithm: ecdsa-with-SHA256
30:45:02:21:00:cf:03:c5:60:bf:25:4d:3f:eb:aa:b9:01:b1:
8f:9e:9e:02:0c:78:b8:b4:48:24:6e:3c:84:7d:00:40:cd:67:
67:02:20:58:5f:1c:6b:29:15:cc:0b:17:42:e7:dc:4b:59:4f:
06:75:b9:ac:89:00:71:b6:97:03:ba:43:2c:77:03:df:51
-----BEGIN CERTIFICATE-----
MIIBJzCBzqADAgECAgEDMAoGCCqGSM49BAMCMAAwIhgPMDAwMDAxMDEwMDAwMDBa
GA85OTk4MTEzMDAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPQF
ye3nny8Z5/4iprB2VWDECvmaAqbjkuoHF7JpKTooa3ZLBqer44Yl0jfwWJSrNnGg
uHbZfqEu4b8qAX74uzqjNTAzMBsGA1UdEQQUMBKBEHRlc3RAZXhhbXBsZS5jb20w
FAYDVR0gBA0wCzAJBgdngQwBBQEBMAoGCCqGSM49BAMCA0gAMEUCIQDPA8VgvyVN
P+uquQGxj56eAgx4uLRIJG48hH0AQM1nZwIgWF8caykVzAsXQufcS1lPBnW5rIkA
cbaXA7pDLHcD31E=
-----END CERTIFICATE-----
42 changes: 42 additions & 0 deletions v3/testdata/smime/MailboxAddressFromSAN/WithOnlySANOtherName.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: Jan 1 00:00:00 0 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject:
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:4b:89:11:9f:71:cd:9f:36:7f:85:e8:43:81:37:
c5:15:ab:81:ff:2a:e7:d7:13:fb:41:a0:1e:94:44:
8d:9c:f0:20:62:51:9c:b2:17:29:ca:87:7a:1f:28:
9c:32:8e:ee:95:bd:a7:c9:dc:58:b1:b9:48:33:f7:
5c:1c:32:ef:cc
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Certificate Policies:
Policy: 2.23.140.1.5.1.1

X509v3 Subject Alternative Name: critical
othername:<unsupported>
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:51:bb:6a:c2:9b:dd:b3:0e:35:90:c4:f5:6d:91:
e8:a7:a3:5c:f5:13:88:67:8d:66:0d:48:86:94:0d:2c:a8:92:
02:20:15:2a:0c:cf:3f:f2:88:d1:39:c1:da:88:2f:c7:24:17:
36:cb:a5:dd:b8:2b:4e:ea:c4:3e:3a:1d:27:6b:ee:22
-----BEGIN CERTIFICATE-----
MIIBUDCB+KADAgECAgEDMAoGCCqGSM49BAMCMAAwIhgPMDAwMDAxMDEwMDAwMDBa
GA85OTk4MTEzMDAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEuJ
EZ9xzZ82f4XoQ4E3xRWrgf8q59cT+0GgHpREjZzwIGJRnLIXKcqHeh8onDKO7pW9
p8ncWLG5SDP3XBwy78yjXzBdMBQGA1UdIAQNMAswCQYHZ4EMAQUBATBFBgNVHREB
Af8EOzA5oDcGCCsGAQUFBwgJoCsMKXRoaXMgdmFsdWUgZG9lcyBub3QgbWF0Y2gg
dGhlIGNvbW1vbiBuYW1lMAoGCCqGSM49BAMCA0cAMEQCIFG7asKb3bMONZDE9W2R
6KejXPUTiGeNZg1IhpQNLKiSAiAVKgzPP/KI0TnB2ogvxyQXNsul3bgrTurEPjod
J2vuIg==
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: ecdsa-with-SHA256
Issuer:
Validity
Not Before: Jan 1 00:00:00 0 GMT
Not After : Nov 30 00:00:00 9998 GMT
Subject: CN=test@example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:ee:d6:0c:34:aa:88:1c:8c:71:a9:7b:8d:3d:e9:
d9:63:18:22:a7:7a:94:fe:0a:74:31:e4:0c:67:37:
15:1d:12:39:e0:32:ab:79:29:8e:14:67:b0:c9:d6:
9d:da:e7:c6:e0:23:4b:4a:60:f2:75:24:f1:3f:1e:
16:fe:e6:e1:cc
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Certificate Policies:
Policy: 2.23.140.1.5.1.1

X509v3 Subject Alternative Name: critical
othername:<unsupported>
Signature Algorithm: ecdsa-with-SHA256
30:45:02:21:00:de:88:2f:09:52:6a:15:ff:6f:de:5d:48:2c:
40:4f:60:00:dd:a1:3c:07:70:49:c3:0c:de:da:15:a7:e1:d2:
82:02:20:36:be:cc:ab:37:e4:0a:75:92:dc:9d:99:d7:5d:b7:
1f:6f:87:db:36:b1:00:60:7b:e9:3a:d4:9a:52:1f:2c:95
-----BEGIN CERTIFICATE-----
MIIBWzCCAQGgAwIBAgIBAzAKBggqhkjOPQQDAjAAMCIYDzAwMDAwMTAxMDAwMDAw
WhgPOTk5ODExMzAwMDAwMDBaMBsxGTAXBgNVBAMMEHRlc3RAZXhhbXBsZS5jb20w
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATu1gw0qogcjHGpe4096dljGCKnepT+
CnQx5AxnNxUdEjngMqt5KY4UZ7DJ1p3a58bgI0tKYPJ1JPE/Hhb+5uHMo00wSzAU
BgNVHSAEDTALMAkGB2eBDAEFAQEwMwYDVR0RAQH/BCkwJ6AlBggrBgEFBQcICaAZ
DBdhbm90aGVydGVzdEBleGFtcGxlLmNvbTAKBggqhkjOPQQDAgNIADBFAiEA3ogv
CVJqFf9v3l1ILEBPYADdoTwHcEnDDN7aFafh0oICIDa+zKs35Ap1ktydmdddtx9v
h9s2sQBge+k61JpSHyyV
-----END CERTIFICATE-----
Loading

0 comments on commit f4b0243

Please sign in to comment.