Skip to content

Commit

Permalink
lints: cabf_br lint to verify .onion addresses are well-formed (#450)
Browse files Browse the repository at this point in the history
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
sleevi authored Jun 12, 2020
1 parent 84a8a20 commit d4acbba
Show file tree
Hide file tree
Showing 5 changed files with 414 additions and 0 deletions.
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

0 comments on commit d4acbba

Please sign in to comment.