Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint that .onion addresses are well-formed #450

Merged
merged 3 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions v2/lints/cabf_br/lint_san_dns_name_onion_invalid.go
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{},
})
}
69 changes: 69 additions & 0 deletions v2/lints/cabf_br/lint_san_dns_name_onion_invalid_test.go
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)
}
})
}
}
66 changes: 66 additions & 0 deletions v2/testdata/onionSANv2NameEV.pem
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-----
66 changes: 66 additions & 0 deletions v2/testdata/onionSANv2NameInvalidEV.pem
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-----
Loading