Skip to content

Commit

Permalink
Fix .onion tests to only apply to EV certificates (#449)
Browse files Browse the repository at this point in the history
Before this change, ZLint would reject .onion names in non-EV certs
via the `lint_san_dns_name_onion_not_ev_cert` lint, and if that
was suppressed, then complain about the missing Tor Service
Descriptor extension. As of CA/Browser Forum Ballot SC27, it's
allowed for v3 onion names to appear in DV/OV/IV certificates, and
the Tor Service Descriptor extension is neither required nor
prohibited for these.

This change corrects the Tor Service Descriptor tests to properly
account for it being mandatory for EV, while optional for DV/OV/IV.
This does not introduce new lints to ensure that the address is
itself a well-formed V2 (if EV) or V3 (all types) address, which
will come in a follow-up change.

Closes #440
  • Loading branch information
sleevi authored Jun 10, 2020
1 parent ecf8678 commit 84a8a20
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 40 deletions.
57 changes: 32 additions & 25 deletions v2/lints/cabf_br/lint_ext_tor_service_descriptor_hash_invalid.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@ import (
"github.com/zmap/zlint/v2/util"
)

const onionTLD = ".onion"

type torServiceDescHashInvalid struct{}

func (l *torServiceDescHashInvalid) Initialize() error {
// There is nothing to initialize for a torServiceDescHashInvalid linter.
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.
Expand Down Expand Up @@ -86,25 +88,25 @@ 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
// algorithm or incorrect hash bit length.
// 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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
}

Expand All @@ -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{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
{
Expand Down
9 changes: 5 additions & 4 deletions v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions v2/lints/cabf_br/lint_san_dns_name_onion_not_ev_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
46 changes: 46 additions & 0 deletions v2/testdata/onionSANEVBefore201.pem
Original file line number Diff line number Diff line change
@@ -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-----
24 changes: 14 additions & 10 deletions v2/testdata/onionSANMissingServDescHash.pem
Original file line number Diff line number Diff line change
Expand Up @@ -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-----
78 changes: 78 additions & 0 deletions v2/testdata/onionSANv3Name.pem
Original file line number Diff line number Diff line change
@@ -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-----
1 change: 1 addition & 0 deletions v2/util/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)

Expand Down

0 comments on commit 84a8a20

Please sign in to comment.