diff --git a/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid.go b/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid.go index ffb3f3fc2..ae11908cc 100644 --- a/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid.go +++ b/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid.go @@ -24,8 +24,6 @@ import ( "github.com/zmap/zlint/v2/util" ) -const onionTLD = ".onion" - type torServiceDescHashInvalid struct{} func (l *torServiceDescHashInvalid) Initialize() error { @@ -33,10 +31,14 @@ func (l *torServiceDescHashInvalid) Initialize() error { return nil } -// CheckApplies returns true if the certificate is a subscriber certificate that -// contains a subject name ending in `.onion`. +// CheckApplies returns true if the TorServiceDescriptor extension is present +// or if the certificate is an EV subscriber certificate with one or more +// subject names ending in `.onion`. func (l *torServiceDescHashInvalid) CheckApplies(c *x509.Certificate) bool { - return util.IsSubscriberCert(c) && util.CertificateSubjInTLD(c, onionTLD) + ext := util.GetExtFromCert(c, util.BRTorServiceDescriptor) + return ext != nil || (util.IsSubscriberCert(c) && + util.CertificateSubjInTLD(c, util.OnionTLD) && + util.IsEV(c.PolicyIdentifiers)) } // failResult is a small utility function for creating a failed lint result. @@ -86,7 +88,7 @@ func lintOnionURL(onion string) *lint.LintResult { // Execute will lint the provided certificate. An lint.Error lint.LintResult will be // returned if: // -// 1) There is no TorServiceDescriptor extension present. +// 1) There is no TorServiceDescriptor extension present and it's required // 2) There were no TorServiceDescriptors parsed by zcrypto // 3) There are TorServiceDescriptorHash entries with an invalid Onion URL. // 4) There are TorServiceDescriptorHash entries with an unknown hash @@ -94,17 +96,17 @@ func lintOnionURL(onion string) *lint.LintResult { // 5) There is a TorServiceDescriptorHash entry that doesn't correspond to // an onion subject in the cert. // 6) There is an onion subject in the cert that doesn't correspond to -// a TorServiceDescriptorHash. +// a TorServiceDescriptorHash, if required. func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *lint.LintResult { - // If the BRTorServiceDescriptor extension is missing return a lint error. We - // know the cert contains one or more `.onion` subjects because of - // `CheckApplies` and all such certs are expected to have this extension after - // util.CABV201Date. + // If the certificate is EV, the BRTorServiceDescriptor extension is required. + // We know that `CheckApplies` will only apply if the certificate has the + // extension or that it's required, so this will only fail when it's + // required. if ext := util.GetExtFromCert(c, util.BRTorServiceDescriptor); ext == nil { return failResult( "certificate contained a %s domain but is missing a TorServiceDescriptor "+ "extension (oid %s)", - onionTLD, util.BRTorServiceDescriptor.String()) + util.OnionTLD, util.BRTorServiceDescriptor.String()) } // The certificate should have at least one TorServiceDescriptorHash in the @@ -114,21 +116,21 @@ func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *lint.LintResul return failResult( "certificate contained a %s domain but TorServiceDescriptor "+ "extension (oid %s) had no TorServiceDescriptorHash objects", - onionTLD, util.BRTorServiceDescriptor.String()) + util.OnionTLD, util.BRTorServiceDescriptor.String()) } // Build a map of all the eTLD+1 onion subjects in the cert to compare against // the service descriptors. onionETLDPlusOneMap := make(map[string]string) for _, subj := range append(c.DNSNames, c.Subject.CommonName) { - if !strings.HasSuffix(subj, onionTLD) { + if !strings.HasSuffix(subj, util.OnionTLD) { continue } labels := strings.Split(subj, ".") if len(labels) < 2 { return failResult("certificate contained a %s domain with too few "+ "labels: %q", - onionTLD, subj) + util.OnionTLD, subj) } eTLDPlusOne := strings.Join(labels[len(labels)-2:], ".") onionETLDPlusOneMap[eTLDPlusOne] = subj @@ -184,14 +186,19 @@ func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *lint.LintResul descriptorMap[hostname] = descriptor } - // Check if any of the onion subjects in the certificate don't have - // a TorServiceDescriptorHash for the eTLD+1 in the descriptorMap. - for eTLDPlusOne, subjDomain := range onionETLDPlusOneMap { - if _, found := descriptorMap[eTLDPlusOne]; !found { - return failResult( - "%s subject domain name %q does not have a corresponding "+ - "TorServiceDescriptorHash for its eTLD+1", - onionTLD, subjDomain) + // For EV certificates, every `.onion` name is required to have a + // TorServiceDescriptorHash, so check if any of the onion subjects in the + // certificate don't have a TorServiceDescriptorHash for the eTLD+1 in the + // descriptorMap. + // See also https://github.com/cabforum/documents/issues/190 + if util.IsEV(c.PolicyIdentifiers) { + for eTLDPlusOne, subjDomain := range onionETLDPlusOneMap { + if _, found := descriptorMap[eTLDPlusOne]; !found { + return failResult( + "%s subject domain name %q does not have a corresponding "+ + "TorServiceDescriptorHash for its eTLD+1", + util.OnionTLD, subjDomain) + } } } @@ -204,8 +211,8 @@ func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *lint.LintResul func init() { lint.RegisterLint(&lint.Lint{ Name: "e_ext_tor_service_descriptor_hash_invalid", - Description: "certificates with .onion names need valid TorServiceDescriptors in extension", - Citation: "BRS: Ballot 201", + Description: "certificates with v2 .onion names need valid TorServiceDescriptors in extension", + Citation: "BRs: Ballot 201, Ballot SC27", Source: lint.CABFBaselineRequirements, EffectiveDate: util.CABV201Date, Lint: &torServiceDescHashInvalid{}, diff --git a/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid_test.go b/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid_test.go index b4ea636e2..ebace75dc 100644 --- a/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid_test.go +++ b/v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid_test.go @@ -16,7 +16,7 @@ func TestTorDescHashInvalid(t *testing.T) { }{ { Name: "Onion subject, no service descriptor extension, before util.CABV201Date", - InputFilename: "dnsNameOnionTLD.pem", + InputFilename: "onionSANEVBefore201.pem", ExpectedResult: lint.NE, }, { diff --git a/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert.go b/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert.go index 1792be931..8693ff09f 100644 --- a/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert.go +++ b/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert.go @@ -24,15 +24,16 @@ import ( type onionNotEV struct{} -// Initialize for an onionNotEV linter is a NOP. func (l *onionNotEV) Initialize() error { return nil } -// CheckApplies returns true if the certificate is a subscriber certificate that -// contains a subject name ending in `.onion`. +// This lint only applies for certificates issued before CA/Browser Forum +// Ballot SC27, which permitted .onion within non-EV certificates func (l *onionNotEV) CheckApplies(c *x509.Certificate) bool { - return util.IsSubscriberCert(c) && util.CertificateSubjInTLD(c, util.OnionTLD) + return c.NotBefore.Before(util.CABFBRs_1_6_9_Date) && + util.IsSubscriberCert(c) && + util.CertificateSubjInTLD(c, util.OnionTLD) } // Execute returns an lint.Error lint.LintResult if the certificate is not an EV diff --git a/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert_test.go b/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert_test.go index af5270347..041a6fdab 100644 --- a/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert_test.go +++ b/v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert_test.go @@ -30,6 +30,11 @@ func TestOnionNotEV(t *testing.T) { InputFilename: "onionSANEV.pem", ExpectedResult: lint.Pass, }, + { + Name: "Onion subject, non EV cert, after util.CABF_BRs_1_6_9_Date", + InputFilename: "onionSANv3Name.pem", + ExpectedResult: lint.NA, + }, } for _, tc := range testCases { diff --git a/v2/testdata/onionSANEVBefore201.pem b/v2/testdata/onionSANEVBefore201.pem new file mode 100644 index 000000000..2b8c89587 --- /dev/null +++ b/v2/testdata/onionSANEVBefore201.pem @@ -0,0 +1,46 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 31337 (0x7a69) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = Zmap Onion CA + Validity + Not Before: Jun 2 15:17:12 2017 GMT + Not After : Mar 2 15:17:12 2018 GMT + Subject: CN = zmap.io + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (512 bit) + Modulus: + 00:e7:b5:d2:75:b1:04:c6:24:e7:b2:1f:b1:22:2b: + 30:35:e9:ae:d8:b4:40:a2:34:19:01:80:a4:2e:a8: + 0a:de:43:49:3d:70:a2:22:0a:a8:51:bd:9b:13:fb: + 6e:cc:60:65:88:32:fc:33:21:06:4d:a3:27:fe:b0: + 75:80:cc:d4:df + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Alternative Name: + DNS:zmap.io, DNS:zmap.onion + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.36305.2 + + Signature Algorithm: sha256WithRSAEncryption + 30:f7:da:b6:a8:15:e3:d9:3a:aa:56:9f:88:06:ea:ae:5e:75: + 58:d5:7c:ea:31:b7:f2:a5:fe:e8:9c:68:f8:0a:6f:64:d1:f3: + 10:53:48:56:55:c6:5c:20:04:bf:b1:44:6a:69:1d:d5:fb:8e: + 57:99:2a:87:1f:b0:d7:ae:a8:20 +-----BEGIN CERTIFICATE----- +MIIBgzCCAS2gAwIBAgICemkwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAxMNWm1h +cCBPbmlvbiBDQTAeFw0xNzA2MDIxNTE3MTJaFw0xODAzMDIxNTE3MTJaMBIxEDAO +BgNVBAMTB3ptYXAuaW8wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA57XSdbEExiTn +sh+xIiswNemu2LRAojQZAYCkLqgK3kNJPXCiIgqoUb2bE/tuzGBliDL8MyEGTaMn +/rB1gMzU3wIDAQABo2cwZTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADAeBgNVHREEFzAVggd6bWFwLmlvggp6bWFwLm9uaW9uMBYG +A1UdIAQPMA0wCwYJKwYBBAGCm1ECMA0GCSqGSIb3DQEBCwUAA0EAMPfatqgV49k6 +qlafiAbqrl51WNV86jG38qX+6Jxo+ApvZNHzEFNIVlXGXCAEv7FEamkd1fuOV5kq +hx+w166oIA== +-----END CERTIFICATE----- diff --git a/v2/testdata/onionSANMissingServDescHash.pem b/v2/testdata/onionSANMissingServDescHash.pem index fac256804..52ccdfb9f 100644 --- a/v2/testdata/onionSANMissingServDescHash.pem +++ b/v2/testdata/onionSANMissingServDescHash.pem @@ -25,22 +25,26 @@ Certificate: CA:FALSE X509v3 Subject Alternative Name: DNS:zmap.io, DNS:zmap.onion, DNS:missing.onion + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.36305.2 + 2.23.140.1.31: 0F0D..https://zmap.onion0...`.H.e.....!..I..I..e\..?.>.{{}.G*.bx0q.9.f8\ Signature Algorithm: sha256WithRSAEncryption - 34:7a:85:96:cb:61:a1:04:78:17:42:e5:f9:b1:e6:0a:33:f7: - 09:4a:d3:43:d7:56:e7:97:d7:9b:ad:78:e2:16:80:66:1b:06: - 19:d9:bc:db:8d:f8:87:6b:98:5a:ef:6a:8c:4f:b1:64:e9:eb: - c3:72:f5:30:7a:79:ac:1d:2a:06 + 09:9b:05:52:98:a3:c1:38:97:46:e9:64:71:26:5d:4c:9b:8f: + 28:64:58:c6:c6:dd:2e:c2:ba:23:dd:67:a9:1e:bc:2b:08:25: + cd:d8:f5:da:90:02:2a:b4:45:fd:19:02:51:99:27:2e:ad:dd: + f2:e4:32:b4:26:19:a2:d3:1f:76 -----BEGIN CERTIFICATE----- -MIIBzzCCAXmgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAxMNWm1h +MIIB5zCCAZGgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAxMNWm1h cCBPbmlvbiBDQTAeFw0xOTAzMDIyMDU0NDBaFw0yMDAzMDIyMDU0NDBaMBIxEDAO BgNVBAMTB3ptYXAuaW8wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAyhMFSI9h3qP7 DR/luYGBrqeBTmTl4pvs45tjx5I9PkZjNB+Cc+qHChHgl15Rh/f2J0fn+RVx53bE -btTumyx8awIDAQABo4GyMIGvMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +btTumyx8awIDAQABo4HKMIHHMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjAMBgNVHRMBAf8EAjAAMC0GA1UdEQQmMCSCB3ptYXAuaW+CCnptYXAub25pb26C -DW1pc3Npbmcub25pb24wUQYFZ4EMAR8ESDBGMEQMEmh0dHBzOi8vem1hcC5vbmlv -bjALBglghkgBZQMEAgEDIQDHSfWySZyPZVwZsz/5PgN7e32+RyqsYngwcbA5uGY4 -XDANBgkqhkiG9w0BAQsFAANBADR6hZbLYaEEeBdC5fmx5goz9wlK00PXVueX15ut -eOIWgGYbBhnZvNuN+IdrmFrvaoxPsWTp68Ny9TB6eawdKgY= +DW1pc3Npbmcub25pb24wFgYDVR0gBA8wDTALBgkrBgEEAYKbUQIwUQYFZ4EMAR8E +SDBGMEQMEmh0dHBzOi8vem1hcC5vbmlvbjALBglghkgBZQMEAgEDIQDHSfWySZyP +ZVwZsz/5PgN7e32+RyqsYngwcbA5uGY4XDANBgkqhkiG9w0BAQsFAANBAAmbBVKY +o8E4l0bpZHEmXUybjyhkWMbG3S7CuiPdZ6kevCsIJc3Y9dqQAiq0Rf0ZAlGZJy6t +3fLkMrQmGaLTH3Y= -----END CERTIFICATE----- diff --git a/v2/testdata/onionSANv3Name.pem b/v2/testdata/onionSANv3Name.pem new file mode 100644 index 000000000..f9f7c68c8 --- /dev/null +++ b/v2/testdata/onionSANv3Name.pem @@ -0,0 +1,78 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2050924719016116738 (0x1c76592a6a060202) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = zlint test 6fb5f7 + Validity + Not Before: Mar 28 00:00:00 2020 GMT + Not After : Mar 28 00:00:00 2021 GMT + Subject: CN = example.test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d3:cf:55:71:96:a8:51:60:82:3d:12:84:61:82: + 01:67:64:d8:38:07:b7:93:7b:d1:40:c3:67:cd:dd: + b0:bc:84:67:38:65:5c:69:91:33:30:84:6c:38:ae: + 65:c5:5f:02:39:7a:38:f1:55:9d:79:57:b8:75:47: + 07:55:63:9e:ff:21:a7:56:8b:be:9c:99:88:86:f9: + 36:64:2b:ac:a1:d8:7c:31:ad:c5:59:1e:c1:b3:06: + 53:d5:77:27:39:d6:68:a3:c6:5c:65:c3:d8:90:2d: + 2b:bd:9d:c4:39:9c:3f:53:53:af:1b:9c:6b:0f:3e: + 04:96:dd:40:7a:21:29:eb:76:e8:2c:95:7b:73:da: + 65:d0:cc:a4:51:cc:f7:6d:4c:d7:8c:e6:d8:bf:20: + d9:01:a6:a4:b3:35:60:ac:c2:04:d4:02:d7:1c:8d: + 71:62:76:a5:10:4c:36:bf:16:c2:be:1d:71:45:95: + 66:17:32:d0:06:94:67:36:90:db:20:53:36:c4:55: + 5c:bb:cb:9c:68:29:43:b6:76:11:da:6e:c2:6c:da: + ae:1c:57:c6:13:a9:2e:c0:cb:8d:de:2f:19:24:79: + d8:28:83:27:5d:29:e9:4a:f7:3b:04:5a:6c:db:c9: + bb:00:e1:30:e0:8e:a1:cf:92:1c:87:77:ab:82:29: + 66:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Alternative Name: + DNS:l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad.onion, DNS:example.test + Signature Algorithm: sha256WithRSAEncryption + aa:ea:24:45:7a:f2:84:6f:bd:0f:43:63:0d:d0:6f:56:cb:43: + 1a:81:b3:38:fa:79:28:f7:16:1b:a7:6a:79:6d:05:98:46:3f: + 27:fa:21:8a:0d:2d:8c:43:ba:6c:e9:4f:7a:60:fd:fa:9d:e7: + cf:f4:63:e6:ce:25:76:64:59:d8:49:29:50:d1:88:90:fb:3d: + 06:77:de:4c:25:e5:3a:87:ff:1e:80:c6:18:11:ca:69:c5:6b: + eb:d4:e7:a7:76:ca:45:5c:77:ec:46:ea:c9:55:6f:4b:69:cb: + 71:9d:90:24:c7:3f:42:13:97:54:5e:ef:aa:d6:87:89:97:1b: + 6e:cb:c3:53:61:b0:1c:1b:5e:7c:82:5f:2f:bc:d5:4b:b5:a9: + 5b:db:36:05:99:7a:26:2b:7d:88:12:a1:6a:29:28:84:86:62: + df:dd:92:eb:eb:5e:28:a1:47:8a:a2:f1:8e:a4:50:20:d4:21: + 81:e1:93:e1:b4:7a:2c:0f:96:ac:d8:07:d8:cc:39:c9:93:11: + 7f:95:c5:9a:91:b8:09:cb:06:7f:2d:24:6f:53:14:43:68:d8: + 3b:4d:31:2f:68:cd:8a:34:12:6d:d5:57:02:61:e4:4b:72:31: + d1:2c:f1:3c:db:85:4e:6b:f6:32:8c:88:1a:22:a0:b2:11:0e: + 25:4d:be:7e +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIIHHZZKmoGAgIwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UE +AxMRemxpbnQgdGVzdCA2ZmI1ZjcwHhcNMjAwMzI4MDAwMDAwWhcNMjEwMzI4MDAw +MDAwWjAXMRUwEwYDVQQDEwxleGFtcGxlLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDTz1VxlqhRYII9EoRhggFnZNg4B7eTe9FAw2fN3bC8hGc4 +ZVxpkTMwhGw4rmXFXwI5ejjxVZ15V7h1RwdVY57/IadWi76cmYiG+TZkK6yh2Hwx +rcVZHsGzBlPVdyc51mijxlxlw9iQLSu9ncQ5nD9TU68bnGsPPgSW3UB6ISnrdugs +lXtz2mXQzKRRzPdtTNeM5ti/INkBpqSzNWCswgTUAtccjXFidqUQTDa/FsK+HXFF +lWYXMtAGlGc2kNsgUzbEVVy7y5xoKUO2dhHabsJs2q4cV8YTqS7Ay43eLxkkedgo +gyddKelK9zsEWmzbybsA4TDgjqHPkhyHd6uCKWbxAgMBAAGjgZkwgZYwDgYDVR0P +AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB +Af8EAjAAMFcGA1UdEQRQME6CPmw1c2F0amd1ZDZndWNyeWF6Y3l2eXZodXhocjc0 +dTZ5Z2lnaXV5aXhlM2E2eXNpczY3b3JvcmFkLm9uaW9uggxleGFtcGxlLnRlc3Qw +DQYJKoZIhvcNAQELBQADggEBAKrqJEV68oRvvQ9DYw3Qb1bLQxqBszj6eSj3Fhun +anltBZhGPyf6IYoNLYxDumzpT3pg/fqd58/0Y+bOJXZkWdhJKVDRiJD7PQZ33kwl +5TqH/x6AxhgRymnFa+vU56d2ykVcd+xG6slVb0tpy3GdkCTHP0ITl1Re76rWh4mX +G27Lw1NhsBwbXnyCXy+81Uu1qVvbNgWZeiYrfYgSoWopKISGYt/dkuvrXiihR4qi +8Y6kUCDUIYHhk+G0eiwPlqzYB9jMOcmTEX+VxZqRuAnLBn8tJG9TFENo2DtNMS9o +zYo0Em3VVwJh5EtyMdEs8TzbhU5r9jKMiBoioLIRDiVNvn4= +-----END CERTIFICATE----- diff --git a/v2/util/time.go b/v2/util/time.go index 413e6de63..b58c56a77 100644 --- a/v2/util/time.go +++ b/v2/util/time.go @@ -57,6 +57,7 @@ var ( MozillaPolicy22Date = time.Date(2013, time.July, 26, 0, 0, 0, 0, time.UTC) MozillaPolicy24Date = time.Date(2017, time.February, 28, 0, 0, 0, 0, time.UTC) MozillaPolicy27Date = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC) + CABFBRs_1_6_9_Date = time.Date(2020, time.March, 27, 0, 0, 0, 0, time.UTC) AppleReducedLifetimeDate = time.Date(2020, time.September, 1, 0, 0, 0, 0, time.UTC) )