-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lints: cabf_br lint to verify .onion addresses are well-formed (#450)
Adds a new lint, identified as `e_san_dns_name_onion_invalid`, that makes sure that the `.onion` addresses present within a certificate are well-formed v2 or v3 addresses, according to the v2 or v3 Rendezvous specifications. Closes #440
- Loading branch information
Showing
5 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* | ||
* ZLint Copyright 2020 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. | ||
*/ | ||
|
||
/******************************************************************* | ||
https://tools.ietf.org/html/rfc7686#section-1 | ||
Note that .onion names are required to conform with DNS name syntax | ||
(as defined in Section 3.5 of [RFC1034] and Section 2.1 of | ||
[RFC1123]), as they will still be exposed to DNS implementations. | ||
See [tor-address] and [tor-rendezvous] for the details of the | ||
creation and use of .onion names. | ||
Baseline Requirements, v1.6.9, Appendix C (Ballot SC27) | ||
The Domain Name MUST contain at least two labels, where the right-most label | ||
is "onion", and the label immediately preceding the right-most "onion" label | ||
is a valid Version 3 Onion Address, as defined in section 6 of the Tor | ||
Rendezvous Specification - Version 3 located at | ||
https://spec.torproject.org/rend-spec-v3. | ||
Explanation: | ||
Since CA/Browser Forum Ballot 144, `.onion` names have been permitted, | ||
predating the ratification of RFC 7686. RFC 7686 introduced a normative | ||
dependency on the Tor address and rendezvous specifications, which describe | ||
v2 addresses. As the EV Guidelines have, since v1.5.3, required that the CA | ||
obtain a demonstration of control from the Applicant, which effectively | ||
requires the `.onion` name to be well-formed, even prior to RFC 7686. | ||
See also https://github.com/cabforum/documents/issues/191 | ||
*******************************************************************/ | ||
|
||
package cabf_br | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/zmap/zcrypto/x509" | ||
"github.com/zmap/zlint/v2/lint" | ||
"github.com/zmap/zlint/v2/util" | ||
) | ||
|
||
var ( | ||
// Per 2.4 of Rendezvous v2: | ||
// Valid onion addresses contain 16 characters in a-z2-7 plus ".onion" | ||
onionV2Len = 16 | ||
|
||
// Per 1.2 of Rendezvous v3: | ||
// A hidden service's name is its long term master identity key. This is | ||
// encoded as a hostname by encoding the entire key in Base 32, including | ||
// a version byte and a checksum, and then appending the string ".onion" | ||
// at the end. The result is a 56-character domain name. | ||
onionV3Len = 56 | ||
|
||
// Per RFC 4648, Section 6, the Base-32 alphabet is A-Z, 2-7, and =. | ||
// Because v2/v3 addresses are always aligned, they should never be padded, | ||
// and so omit = from the character set, as it's also not permitted in a | ||
// domain in the "preferred name syntax". Because `.onion` names appear in | ||
// DNS, which is case insensitive, the alphabet is extended to include a-z, | ||
// as the names are tested for well-formedness prior to normalization to | ||
// uppercase. | ||
base32SubsetRegex = regexp.MustCompile(`^[a-zA-Z2-7]+$`) | ||
) | ||
|
||
type onionNotValid struct{} | ||
|
||
func (l *onionNotValid) Initialize() error { | ||
return nil | ||
} | ||
|
||
// CheckApplies returns true if the certificate contains one or more subject | ||
// names ending in `.onion`. | ||
func (l *onionNotValid) CheckApplies(c *x509.Certificate) bool { | ||
// TODO(sleevi): This should also be extended to support nameConstraints | ||
// in the future. | ||
return util.CertificateSubjInTLD(c, util.OnionTLD) | ||
} | ||
|
||
// Execute will lint the provided certificate. A lint.Error lint.LintResult will | ||
// be returned if: | ||
// | ||
// 1) The certificate contains a Tor Rendezvous Spec v2 address and is not an | ||
// EV certificate (BRs: Appendix C). | ||
// 2) The certificate contains a `.onion` subject name/SAN that is neither a | ||
// Rendezvous Spec v2 or v3 address. | ||
func (l *onionNotValid) Execute(c *x509.Certificate) *lint.LintResult { | ||
for _, subj := range append(c.DNSNames, c.Subject.CommonName) { | ||
if !strings.HasSuffix(subj, util.OnionTLD) { | ||
continue | ||
} | ||
labels := strings.Split(subj, ".") | ||
if len(labels) < 2 { | ||
return &lint.LintResult{ | ||
Status: lint.Error, | ||
Details: fmt.Sprintf("certificate contained a %s domain with too "+ | ||
"few labels: %q", util.OnionTLD, subj), | ||
} | ||
} | ||
onionDomain := labels[len(labels)-2] | ||
if len(onionDomain) == onionV2Len { | ||
// Onion v2 address. These are only permitted for EV, per BRs Appendix C. | ||
if !util.IsEV(c.PolicyIdentifiers) { | ||
return &lint.LintResult{ | ||
Status: lint.Error, | ||
Details: fmt.Sprintf("%q is a v2 address, but the certificate is not "+ | ||
"EV", subj), | ||
} | ||
} | ||
} else if len(onionDomain) == onionV3Len { | ||
// Onion v3 address. Permitted for all certificates by CA/Browser Forum | ||
// Ballot SC27. | ||
} else { | ||
return &lint.LintResult{ | ||
Status: lint.Error, | ||
Details: fmt.Sprintf("%q is not a v2 or v3 Tor address", subj), | ||
} | ||
} | ||
if !base32SubsetRegex.MatchString(onionDomain) { | ||
return &lint.LintResult{ | ||
Status: lint.Error, | ||
Details: fmt.Sprintf("%q contains invalid characters not permitted "+ | ||
"within base-32", subj), | ||
} | ||
} | ||
} | ||
return &lint.LintResult{Status: lint.Pass} | ||
} | ||
|
||
func init() { | ||
lint.RegisterLint(&lint.Lint{ | ||
Name: "e_san_dns_name_onion_invalid", | ||
Description: "certificates with a .onion subject name must be issued in accordance with the Tor address/rendezvous specification", | ||
Citation: "RFC 7686, EVGs v1.7.2: Appendix F, BRs v1.6.9: Appendix C", | ||
Source: lint.CABFBaselineRequirements, | ||
EffectiveDate: util.OnionOnlyEVDate, | ||
Lint: &onionNotValid{}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package cabf_br | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/zmap/zlint/v2/lint" | ||
"github.com/zmap/zlint/v2/test" | ||
) | ||
|
||
func TestOnionNotInvalid(t *testing.T) { | ||
testCases := []struct { | ||
Name string | ||
InputFilename string | ||
ExpectedResult lint.LintStatus | ||
ExpectedDetails string | ||
}{ | ||
{ | ||
Name: "Onion subject, not EV cert, before util.OnionOnlyEVDate", | ||
InputFilename: "dnsNameOnionTLD.pem", | ||
ExpectedResult: lint.NE, | ||
}, | ||
{ | ||
Name: "non-V2/V3 onion subject, non-EV cert", | ||
InputFilename: "onionSANNotEV.pem", | ||
ExpectedResult: lint.Error, | ||
ExpectedDetails: `"zmap.onion" is not a v2 or v3 Tor address`, | ||
}, | ||
{ | ||
Name: "non-V2/V3 onion subject, EV cert", | ||
InputFilename: "onionSANEV.pem", | ||
ExpectedResult: lint.Error, | ||
ExpectedDetails: `"zmap.onion" is not a v2 or v3 Tor address`, | ||
}, | ||
{ | ||
Name: "v2 onion address, non-EV", | ||
InputFilename: "onionSANv2NameNonEV.pem", | ||
ExpectedResult: lint.Error, | ||
ExpectedDetails: `"v2cbb2l4lsnpio4q.onion" is a v2 address, but the certificate is not EV`, | ||
}, | ||
{ | ||
Name: "v2 onion address, EV", | ||
InputFilename: "onionSANv2NameEV.pem", | ||
ExpectedResult: lint.Pass, | ||
}, | ||
{ | ||
Name: "misencoded v2 onion address, EV", | ||
InputFilename: "onionSANv2NameInvalidEV.pem", | ||
ExpectedResult: lint.Error, | ||
ExpectedDetails: `"v2cbb2l-lsnpio4q.onion" contains invalid characters not permitted within base-32`, | ||
}, | ||
{ | ||
Name: "v3 onion address, non-EV", | ||
InputFilename: "onionSANv3Name.pem", | ||
ExpectedResult: lint.Pass, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.Name, func(t *testing.T) { | ||
result := test.TestLint("e_san_dns_name_onion_invalid", tc.InputFilename) | ||
if result.Status != tc.ExpectedResult { | ||
t.Errorf("expected result %v was %v", tc.ExpectedResult, result.Status) | ||
} | ||
if result.Details != tc.ExpectedDetails { | ||
t.Errorf("expected result details %q was %q", tc.ExpectedDetails, result.Details) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
Certificate: | ||
Data: | ||
Version: 3 (0x2) | ||
Serial Number: 2050924719016117252 (0x1c76592a6a060404) | ||
Signature Algorithm: sha256WithRSAEncryption | ||
Issuer: CN = zlint test 6fb5e2 | ||
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:v2cbb2l4lsnpio4q.onion, DNS:example.test | ||
X509v3 Certificate Policies: | ||
Policy: 1.3.6.1.4.1.36305.2 | ||
|
||
Signature Algorithm: sha256WithRSAEncryption | ||
11:33:f2:c1:fc:a3:10:23:bd:36:d7:f0:74:44:b3:1c:3e:9c: | ||
c3:c2:e8:c7:58:12:18:e8:e1:a0:5f:78:45:0e:9e:e5:b9:6b: | ||
4f:31:05:f3:2d:d6:f9:77:a0:b7:e4:81:5d:24:45:81:e8:00: | ||
28:0f:b9:6c:0f:b0:43:eb:8d:c4 | ||
-----BEGIN CERTIFICATE----- | ||
MIICfTCCAiegAwIBAgIIHHZZKmoGBAQwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UE | ||
AxMRemxpbnQgdGVzdCA2ZmI1ZTIwHhcNMjAwMzI4MDAwMDAwWhcNMjEwMzI4MDAw | ||
MDAwWjAXMRUwEwYDVQQDEwxleGFtcGxlLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUA | ||
A4IBDwAwggEKAoIBAQDTz1VxlqhRYII9EoRhggFnZNg4B7eTe9FAw2fN3bC8hGc4 | ||
ZVxpkTMwhGw4rmXFXwI5ejjxVZ15V7h1RwdVY57/IadWi76cmYiG+TZkK6yh2Hwx | ||
rcVZHsGzBlPVdyc51mijxlxlw9iQLSu9ncQ5nD9TU68bnGsPPgSW3UB6ISnrdugs | ||
lXtz2mXQzKRRzPdtTNeM5ti/INkBpqSzNWCswgTUAtccjXFidqUQTDa/FsK+HXFF | ||
lWYXMtAGlGc2kNsgUzbEVVy7y5xoKUO2dhHabsJs2q4cV8YTqS7Ay43eLxkkedgo | ||
gyddKelK9zsEWmzbybsA4TDgjqHPkhyHd6uCKWbxAgMBAAGjgYkwgYYwDgYDVR0P | ||
AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB | ||
Af8EAjAAMC8GA1UdEQQoMCaCFnYyY2JiMmw0bHNucGlvNHEub25pb26CDGV4YW1w | ||
bGUudGVzdDAWBgNVHSAEDzANMAsGCSsGAQQBgptRAjANBgkqhkiG9w0BAQsFAANB | ||
ABEz8sH8oxAjvTbX8HREsxw+nMPC6MdYEhjo4aBfeEUOnuW5a08xBfMt1vl3oLfk | ||
gV0kRYHoACgPuWwPsEPrjcQ= | ||
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
Certificate: | ||
Data: | ||
Version: 3 (0x2) | ||
Serial Number: 2050924719016117509 (0x1c76592a6a060505) | ||
Signature Algorithm: sha256WithRSAEncryption | ||
Issuer: CN = zlint test 6fb5e3 | ||
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:v2cbb2l-lsnpio4q.onion, DNS:example.test | ||
X509v3 Certificate Policies: | ||
Policy: 1.3.6.1.4.1.36305.2 | ||
|
||
Signature Algorithm: sha256WithRSAEncryption | ||
75:00:96:f9:9f:96:d0:94:da:37:95:2e:b6:5a:a1:e6:2b:52: | ||
9a:d0:74:32:26:8a:5a:5c:38:23:3b:ef:c1:69:75:0f:c4:59: | ||
ce:d5:ce:4c:6e:fc:30:8c:eb:1c:95:8a:45:69:0f:45:fb:72: | ||
3e:e0:8e:70:35:53:a0:17:ea:69 | ||
-----BEGIN CERTIFICATE----- | ||
MIICfTCCAiegAwIBAgIIHHZZKmoGBQUwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UE | ||
AxMRemxpbnQgdGVzdCA2ZmI1ZTMwHhcNMjAwMzI4MDAwMDAwWhcNMjEwMzI4MDAw | ||
MDAwWjAXMRUwEwYDVQQDEwxleGFtcGxlLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUA | ||
A4IBDwAwggEKAoIBAQDTz1VxlqhRYII9EoRhggFnZNg4B7eTe9FAw2fN3bC8hGc4 | ||
ZVxpkTMwhGw4rmXFXwI5ejjxVZ15V7h1RwdVY57/IadWi76cmYiG+TZkK6yh2Hwx | ||
rcVZHsGzBlPVdyc51mijxlxlw9iQLSu9ncQ5nD9TU68bnGsPPgSW3UB6ISnrdugs | ||
lXtz2mXQzKRRzPdtTNeM5ti/INkBpqSzNWCswgTUAtccjXFidqUQTDa/FsK+HXFF | ||
lWYXMtAGlGc2kNsgUzbEVVy7y5xoKUO2dhHabsJs2q4cV8YTqS7Ay43eLxkkedgo | ||
gyddKelK9zsEWmzbybsA4TDgjqHPkhyHd6uCKWbxAgMBAAGjgYkwgYYwDgYDVR0P | ||
AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB | ||
Af8EAjAAMC8GA1UdEQQoMCaCFnYyY2JiMmwtbHNucGlvNHEub25pb26CDGV4YW1w | ||
bGUudGVzdDAWBgNVHSAEDzANMAsGCSsGAQQBgptRAjANBgkqhkiG9w0BAQsFAANB | ||
AHUAlvmfltCU2jeVLrZaoeYrUprQdDImilpcOCM778FpdQ/EWc7Vzkxu/DCM6xyV | ||
ikVpD0X7cj7gjnA1U6AX6mk= | ||
-----END CERTIFICATE----- |
Oops, something went wrong.