From 88446f9658da8fdbb5c6d8031492a9a5f633650a Mon Sep 17 00:00:00 2001 From: Rob Stradling Date: Tue, 4 Aug 2026 10:14:44 +0100 Subject: [PATCH 1/2] Fix e_org_validated_invalid_cn to not flag absent Subject CN The S/MIME BRs say the Subject CN field is optional for Organization Validated certificates. The lint was treating an absent CN (empty string) as invalid, returning Error instead of Pass. Also guard against a potential panic when Subject.Organization is empty. Fixes issue reported after merging of #1069. --- .../lint_org_validated_invalid_cn.go | 7 +- .../lint_org_validated_invalid_cn_test.go | 5 + .../smime/sub1_sm1_ov1_cn_absent_eff1.pem | 115 ++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 v3/testdata/smime/sub1_sm1_ov1_cn_absent_eff1.pem diff --git a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go index d53662dbd..aed34b577 100644 --- a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go +++ b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go @@ -55,8 +55,13 @@ func isEmail(s string) bool { func (l *OrgValidatedInvalidCN) Execute(c *x509.Certificate) *lint.LintResult { + // CN is optional per the S/MIME BRs. + if c.Subject.CommonName == "" { + return &lint.LintResult{Status: lint.Pass} + } + if isEmail(c.Subject.CommonName) || - c.Subject.CommonName == c.Subject.Organization[0] { + (len(c.Subject.Organization) > 0 && c.Subject.CommonName == c.Subject.Organization[0]) { return &lint.LintResult{Status: lint.Pass} } diff --git a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go index 9eb2f5f54..e311b1e93 100644 --- a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go +++ b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go @@ -63,6 +63,11 @@ func TestOrgValidatedInvalidCN(t *testing.T) { path: "smime/sub1_sm1_ov1_cne0_cno0_eff1.pem", want: lint.Error, }, + { + desc: "OV S/MIME certificate with no CN", + path: "smime/sub1_sm1_ov1_cn_absent_eff1.pem", + want: lint.Pass, + }, } for _, tc := range testCases { diff --git a/v3/testdata/smime/sub1_sm1_ov1_cn_absent_eff1.pem b/v3/testdata/smime/sub1_sm1_ov1_cn_absent_eff1.pem new file mode 100644 index 000000000..0fbb7d2de --- /dev/null +++ b/v3/testdata/smime/sub1_sm1_ov1_cn_absent_eff1.pem @@ -0,0 +1,115 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1311768467294899695 (0x1234567890abcdef) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XX, O=Some CA, CN=Fake CA for Zlint testing + Validity + Not Before: Aug 4 09:11:28 2026 GMT + Not After : Aug 4 09:11:28 2027 GMT + Subject: C=ES, L=Some location, O=Alguna Compania + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:aa:f4:ae:b0:70:30:0b:90:90:58:16:74:cf:9e: + 78:12:68:cd:2c:22:0c:92:9a:3c:c2:b2:4a:94:f7: + 2b:17:00:2c:f5:f9:94:58:18:72:d5:bf:40:f6:24: + 51:1f:98:2e:4f:bb:f0:15:df:10:22:cc:9b:ef:0e: + 1d:93:14:fa:3b:cf:c5:92:35:ed:60:72:03:42:54: + f8:b9:1a:13:7c:d3:2d:f9:6e:cc:e5:3b:3f:39:08: + 59:7a:41:6d:ef:d7:b9:c4:b7:9e:d0:95:71:c0:98: + 7d:5c:75:b3:34:0e:d4:9b:7a:e2:49:bb:ab:38:16: + c9:67:2d:5d:ca:c8:8e:cc:68:b6:dc:79:8d:96:0f: + a0:c6:c5:e7:26:d5:a2:09:b3:a0:ac:94:ca:c7:7e: + 46:e6:0c:0d:7d:5f:d9:80:33:44:51:09:f0:66:65: + 78:d7:34:72:9f:b5:50:2d:fd:f3:c8:1f:cd:90:50: + 7c:94:75:2d:e3:97:d9:37:80:44:23:a1:c9:37:cb: + b7:a3:1c:5d:6c:59:f2:b8:7c:f1:93:8c:3c:e0:e5: + 39:87:d6:8e:4e:5c:8d:11:c3:76:4e:e9:0e:fb:d9: + 40:2d:61:2c:51:0e:13:f4:cc:ca:30:ab:6d:9c:13: + 7b:87:19:fb:53:c8:48:ee:1d:9b:da:2b:21:33:0c: + bc:e9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, E-mail Protection + X509v3 Subject Key Identifier: + D1:A0:A3:F9:B2:DA:9A:C0:E8:95:ED:C9:B2:BD:34:89:D6:EB:A3:74 + X509v3 Authority Key Identifier: + 26:93:01:2A:0A:CD:4A:E6:8F:67:BF:0B:CF:B0:E1:35:AF:E0:48:10 + X509v3 Subject Alternative Name: + email:Leon.Mandrake@example.com + X509v3 Certificate Policies: + Policy: 2.23.140.1.5.2.2 + X509v3 CRL Distribution Points: + Full Name: + URI:http://ca.someca-inc.com/crl + + Authority Information Access: + OCSP - URI:http://ca.someca-inc.com/ocsp + CA Issuers - URI:http://ca.someca-inc.com/root + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 05:21:55:91:a2:ef:0a:cb:0f:70:99:65:a9:77:2b:14:ab:6f: + 3e:91:03:70:07:6d:f2:46:08:c7:c8:8d:8b:d5:32:75:6e:21: + a0:48:a8:ed:23:0e:a5:9f:d5:70:14:82:42:e9:fa:02:7c:a4: + 85:a4:75:bb:56:63:27:a0:43:91:ec:31:6e:4f:cc:54:e5:99: + e0:78:35:21:cb:d7:93:86:fa:e1:f2:f7:f3:b7:da:92:77:7d: + a1:6c:c9:59:5c:fb:27:95:17:d4:cb:e7:97:85:d3:ce:b1:f1: + e4:3f:b4:a0:df:ae:06:ac:9c:df:6b:2e:b0:44:dd:c0:7c:ae: + ad:57:59:20:60:a9:df:96:d1:80:90:93:04:1f:9c:82:64:e1: + 1d:9c:09:42:6e:9a:2e:50:1d:7f:bd:94:fe:96:9b:14:3d:2d: + 17:1b:59:8e:29:12:d2:a1:fa:43:70:f8:92:29:f3:5e:b6:84: + 4b:53:45:21:f5:88:dc:ae:1e:da:2a:60:8e:b1:36:1c:a5:b3: + 4e:1e:4f:ae:66:d8:ed:1b:da:a2:e9:be:1c:66:ee:45:cb:85: + da:9f:00:a0:af:38:fb:39:2c:2d:ff:c7:26:a5:88:b8:f4:56: + 1e:c2:8e:c1:23:68:18:ba:2d:b4:73:14:38:41:00:a7:11:c5: + 67:e1:0d:d8:59:ad:72:33:73:11:38:56:23:54:53:1d:2c:26: + 1c:d7:8c:2e:db:ad:d4:aa:2b:81:ad:98:83:9a:af:92:fe:99: + a0:2d:ae:81:52:a1:1e:d0:d5:24:32:8c:9b:05:c9:b0:f8:10: + 6d:fe:75:2b:2b:9b:3b:39:3f:ae:37:2e:68:b2:73:29:9b:2a: + 85:ba:8c:c9:20:88:c6:39:c4:ff:ef:3f:2c:56:3e:a0:b9:a8: + 3b:7d:a1:22:ed:82:92:d1:9e:0c:44:f8:8c:aa:fd:8e:7e:33: + 0c:1b:b5:91:a4:e4:f1:6f:45:31:42:b8:2a:b5:65:e6:d3:52: + 7b:03:16:4d:22:92:55:c3:99:e5:0a:3b:39:ad:65:58:8c:1a: + 37:1f:25:1e:8a:b6:3d:19:fd:dd:2b:21:99:b8:8e:86:18:bf: + 96:ce:96:8b:5b:09:d8:1f:c6:c5:42:74:f6:b8:32:42:39:dd: + 66:c5:6a:c9:e6:27:9b:87:39:b7:98:5e:35:33:34:d6:ec:6e: + e3:01:e6:f8:15:01:a9:47:03:9d:3a:dd:cb:5f:d7:3a:fd:cc: + a5:6c:45:0f:ec:45:be:0f:ee:b5:dd:2a:be:ef:37:6e:f3:af: + a0:ac:5b:bd:c5:d5:ae:8a:24:b4:25:de:e2:d1:ea:0f:81:ab: + 99:7c:a5:3a:36:4d:b3:e3 +-----BEGIN CERTIFICATE----- +MIIFSjCCAzKgAwIBAgIIEjRWeJCrze8wDQYJKoZIhvcNAQELBQAwQzELMAkGA1UE +BhMCWFgxEDAOBgNVBAoMB1NvbWUgQ0ExIjAgBgNVBAMMGUZha2UgQ0EgZm9yIFps +aW50IHRlc3RpbmcwHhcNMjYwODA0MDkxMTI4WhcNMjcwODA0MDkxMTI4WjA/MQsw +CQYDVQQGEwJFUzEWMBQGA1UEBwwNU29tZSBsb2NhdGlvbjEYMBYGA1UECgwPQWxn +dW5hIENvbXBhbmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqvSu +sHAwC5CQWBZ0z554EmjNLCIMkpo8wrJKlPcrFwAs9fmUWBhy1b9A9iRRH5guT7vw +Fd8QIsyb7w4dkxT6O8/FkjXtYHIDQlT4uRoTfNMt+W7M5Ts/OQhZekFt79e5xLee +0JVxwJh9XHWzNA7Um3riSburOBbJZy1dysiOzGi23HmNlg+gxsXnJtWiCbOgrJTK +x35G5gwNfV/ZgDNEUQnwZmV41zRyn7VQLf3zyB/NkFB8lHUt45fZN4BEI6HJN8u3 +oxxdbFnyuHzxk4w84OU5h9aOTlyNEcN2TukO+9lALWEsUQ4T9MzKMKttnBN7hxn7 +U8hI7h2b2ishMwy86QIDAQABo4IBRDCCAUAwDgYDVR0PAQH/BAQDAgWgMB0GA1Ud +JQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAdBgNVHQ4EFgQU0aCj+bLamsDole3J +sr00idbro3QwHwYDVR0jBBgwFoAUJpMBKgrNSuaPZ78Lz7DhNa/gSBAwJAYDVR0R +BB0wG4EZTGVvbi5NYW5kcmFrZUBleGFtcGxlLmNvbTAUBgNVHSAEDTALMAkGB2eB +DAEFAgIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cDovL2NhLnNvbWVjYS1pbmMuY29t +L2NybDBkBggrBgEFBQcBAQRYMFYwKQYIKwYBBQUHMAGGHWh0dHA6Ly9jYS5zb21l +Y2EtaW5jLmNvbS9vY3NwMCkGCCsGAQUFBzAChh1odHRwOi8vY2Euc29tZWNhLWlu +Yy5jb20vcm9vdDANBgkqhkiG9w0BAQsFAAOCAgEABSFVkaLvCssPcJllqXcrFKtv +PpEDcAdt8kYIx8iNi9UydW4hoEio7SMOpZ/VcBSCQun6AnykhaR1u1ZjJ6BDkewx +bk/MVOWZ4Hg1IcvXk4b64fL387faknd9oWzJWVz7J5UX1Mvnl4XTzrHx5D+0oN+u +Bqyc32susETdwHyurVdZIGCp35bRgJCTBB+cgmThHZwJQm6aLlAdf72U/pabFD0t +FxtZjikS0qH6Q3D4kinzXraES1NFIfWI3K4e2ipgjrE2HKWzTh5PrmbY7Rvaoum+ +HGbuRcuF2p8AoK84+zksLf/HJqWIuPRWHsKOwSNoGLottHMUOEEApxHFZ+EN2Fmt +cjNzEThWI1RTHSwmHNeMLtut1Korga2Yg5qvkv6ZoC2ugVKhHtDVJDKMmwXJsPgQ +bf51KyubOzk/rjcuaLJzKZsqhbqMySCIxjnE/+8/LFY+oLmoO32hIu2CktGeDET4 +jKr9jn4zDBu1kaTk8W9FMUK4KrVl5tNSewMWTSKSVcOZ5Qo7Oa1lWIwaNx8lHoq2 +PRn93SshmbiOhhi/ls6Wi1sJ2B/GxUJ09rgyQjndZsVqyeYnm4c5t5heNTM01uxu +4wHm+BUBqUcDnTrdy1/XOv3MpWxFD+xFvg/utd0qvu83bvOvoKxbvcXVrooktCXe +4tHqD4GrmXylOjZNs+M= +-----END CERTIFICATE----- From afaff75faeeb1032dbfa58b5f7e14825a95a2b00 Mon Sep 17 00:00:00 2001 From: Rob Stradling Date: Tue, 4 Aug 2026 10:49:38 +0100 Subject: [PATCH 2/2] e_org_validated_invalid_cn: skip lint when CN is absent The S/MIME BRs say the Subject CN field is optional. Move the empty-CN check from Execute into CheckApplies so the lint returns NA (not applicable) rather than Error when no CN is present. --- v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go | 7 +------ .../cabf_smime_br/lint_org_validated_invalid_cn_test.go | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go index aed34b577..50bae6325 100644 --- a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go +++ b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn.go @@ -42,7 +42,7 @@ func NewOrgValidatedInvalidCN() lint.LintInterface { } func (l *OrgValidatedInvalidCN) CheckApplies(c *x509.Certificate) bool { - return util.IsSubscriberCert(c) && util.IsOrganizationValidatedCertificate(c) + return util.IsSubscriberCert(c) && util.IsOrganizationValidatedCertificate(c) && c.Subject.CommonName != "" } func isEmail(s string) bool { @@ -55,11 +55,6 @@ func isEmail(s string) bool { func (l *OrgValidatedInvalidCN) Execute(c *x509.Certificate) *lint.LintResult { - // CN is optional per the S/MIME BRs. - if c.Subject.CommonName == "" { - return &lint.LintResult{Status: lint.Pass} - } - if isEmail(c.Subject.CommonName) || (len(c.Subject.Organization) > 0 && c.Subject.CommonName == c.Subject.Organization[0]) { return &lint.LintResult{Status: lint.Pass} diff --git a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go index e311b1e93..73a8e5de8 100644 --- a/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go +++ b/v3/lints/cabf_smime_br/lint_org_validated_invalid_cn_test.go @@ -66,7 +66,7 @@ func TestOrgValidatedInvalidCN(t *testing.T) { { desc: "OV S/MIME certificate with no CN", path: "smime/sub1_sm1_ov1_cn_absent_eff1.pem", - want: lint.Pass, + want: lint.NA, }, }