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

Skip checking for a Tor Descriptor Hash if the provided cert contains a V3 Onion address. #669

Merged
merged 2 commits into from
Mar 27, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func (l *torServiceDescHashInvalid) CheckApplies(c *x509.Certificate) bool {
ext := util.GetExtFromCert(c, util.BRTorServiceDescriptor)
return ext != nil || (util.IsSubscriberCert(c) &&
util.CertificateSubjInTLD(c, util.OnionTLD) &&
util.IsEV(c.PolicyIdentifiers))
util.IsEV(c.PolicyIdentifiers)) &&
!util.IsOnionV3Cert(c)
}

// failResult is a small utility function for creating a failed lint result.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func TestTorDescHashInvalid(t *testing.T) {
InputFilename: "onionSANGoodServDesc.pem",
ExpectedResult: lint.Pass,
},
{
Name: "V3 address does not require TorServiceDescriptorHash",
InputFilename: "facebookOnionV3Address.pem",
ExpectedResult: lint.NA,
},
}

for _, tc := range testCases {
Expand Down
146 changes: 146 additions & 0 deletions v3/testdata/facebookOnionV3Address.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
05:c8:f6:08:3e:f0:0e:ee:97:f9:dc:0d:14:ca:fe:25
Signature Algorithm: ecdsa-with-SHA384
Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert ECC Extended Validation Server CA
Validity
Not Before: Mar 10 00:00:00 2022 GMT
Not After : May 21 23:59:59 2022 GMT
Subject: jurisdictionC = US, jurisdictionST = Delaware, businessCategory = Private Organization, serialNumber = 3835815, C = US, ST = California, L = Menlo Park, O = "Facebook, Inc.", CN = *.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:0d:a8:c5:66:cd:32:9d:e2:96:e3:ed:07:e4:bf:
54:cf:51:cc:09:07:88:a0:95:82:4d:52:28:7f:05:
ee:3d:93:06:65:29:99:8d:e1:e1:ae:d5:4b:c2:3e:
3a:40:6b:5a:57:e9:a3:0f:df:44:e9:1d:18:d1:b9:
4a:47:0c:c4:94
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Authority Key Identifier:
keyid:F8:25:D9:A6:39:C7:C3:81:87:25:3E:30:54:91:18:21:40:9B:17:9D

X509v3 Subject Key Identifier:
34:B9:92:66:05:94:E0:82:1B:58:47:6F:29:2C:05:EA:7E:78:CE:5C
X509v3 Subject Alternative Name:
DNS:*.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion, DNS:*.facebookcooa4ldbat4g7iacswl3p2zrf5nuylvnhxn6kqolvojixwid.onion, DNS:*.facebooksg4bc7ddneq44pf4miux7o7oqdn2agstg5v3d45odhyu4sqd.onion, DNS:*.m.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion, DNS:*.xx.facebookcooa4ldbat4g7iacswl3p2zrf5nuylvnhxn6kqolvojixwid.onion, DNS:*.xy.facebookcooa4ldbat4g7iacswl3p2zrf5nuylvnhxn6kqolvojixwid.onion, DNS:*.xz.facebookcooa4ldbat4g7iacswl3p2zrf5nuylvnhxn6kqolvojixwid.onion, DNS:facebookcooa4ldbat4g7iacswl3p2zrf5nuylvnhxn6kqolvojixwid.onion, DNS:facebooksg4bc7ddneq44pf4miux7o7oqdn2agstg5v3d45odhyu4sqd.onion, DNS:facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 CRL Distribution Points:

Full Name:
URI:http://crl3.digicert.com/DigiCertECCExtendedValidationServerCA.crl

Full Name:
URI:http://crl4.digicert.com/DigiCertECCExtendedValidationServerCA.crl

X509v3 Certificate Policies:
Policy: 2.16.840.1.114412.2.1
Policy: 2.23.140.1.1
CPS: http://www.digicert.com/CPS

Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/DigiCertECCExtendedValidationServerCA.crt

X509v3 Basic Constraints:
CA:FALSE
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 29:79:BE:F0:9E:39:39:21:F0:56:73:9F:63:A5:77:E5:
BE:57:7D:9C:60:0A:F8:F9:4D:5D:26:5C:25:5D:C7:84
Timestamp : Mar 10 22:34:29.487 2022 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:21:00:F1:00:D1:80:2B:E1:BE:F5:CB:9B:A9:
45:23:A1:CC:66:D7:F3:F9:AE:D0:83:F3:2C:61:0D:0C:
F5:32:DC:40:6D:02:20:53:7F:78:2B:3A:B6:9B:6C:A2:
87:A1:E8:BE:25:38:B0:3A:95:24:11:F0:A3:B3:86:9F:
23:23:B8:E3:C4:BF:60
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 51:A3:B0:F5:FD:01:79:9C:56:6D:B8:37:78:8F:0C:A4:
7A:CC:1B:27:CB:F7:9E:88:42:9A:0D:FE:D4:8B:05:E5
Timestamp : Mar 10 22:34:29.564 2022 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:21:00:95:60:F6:64:E8:98:FA:C3:C2:EE:49:
A1:F7:37:CB:D7:CA:19:7F:C5:F6:44:79:36:A4:E3:C4:
18:C1:B1:0A:C0:02:20:0A:A8:02:53:12:FB:03:B2:5E:
AA:4B:B8:49:91:43:A7:11:9E:8C:33:EB:C2:DA:F6:39:
EA:E4:90:4B:E6:E8:D7
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 41:C8:CA:B1:DF:22:46:4A:10:C6:A1:3A:09:42:87:5E:
4E:31:8B:1B:03:EB:EB:4B:C7:68:F0:90:62:96:06:F6
Timestamp : Mar 10 22:34:29.550 2022 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:46:02:21:00:F5:AC:90:E3:83:9C:A8:E2:9B:5C:D5:
25:26:0D:FD:5A:40:D1:9E:9A:DB:93:D8:9F:35:DB:BB:
41:E9:86:E4:CC:02:21:00:F6:EB:D8:A0:87:C4:80:74:
8D:3D:92:6D:EF:B1:1B:FC:CC:CB:78:61:B2:3B:26:E6:
CB:45:49:53:EF:DB:8C:6C
Signature Algorithm: ecdsa-with-SHA384
30:65:02:30:02:3c:bd:85:48:9d:8c:fa:56:4d:90:d6:a9:b9:
1c:8e:84:2c:8b:e0:44:63:be:ff:fc:89:d4:34:88:8c:64:d8:
40:ec:3c:26:05:c5:14:ad:f2:28:41:a2:53:1d:0d:1a:02:31:
00:c3:75:d4:d4:47:c9:cd:88:95:44:ed:28:bc:40:fa:d3:6b:
38:80:c4:e5:c8:ed:7e:64:6e:c3:1a:5b:7f:0d:c2:54:25:bd:
1b:7a:47:1b:a2:33:57:12:dc:af:36:6c:89
-----BEGIN CERTIFICATE-----
MIIItDCCCDqgAwIBAgIQBcj2CD7wDu6X+dwNFMr+JTAKBggqhkjOPQQDAzB0MQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMTMwMQYDVQQDEypEaWdpQ2VydCBFQ0MgRXh0ZW5kZWQgVmFs
aWRhdGlvbiBTZXJ2ZXIgQ0EwHhcNMjIwMzEwMDAwMDAwWhcNMjIwNTIxMjM1OTU5
WjCB/DETMBEGCysGAQQBgjc8AgEDEwJVUzEZMBcGCysGAQQBgjc8AgECEwhEZWxh
d2FyZTEdMBsGA1UEDwwUUHJpdmF0ZSBPcmdhbml6YXRpb24xEDAOBgNVBAUTBzM4
MzU4MTUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRMwEQYDVQQH
EwpNZW5sbyBQYXJrMRcwFQYDVQQKEw5GYWNlYm9vaywgSW5jLjFJMEcGA1UEAwxA
Ki5mYWNlYm9va3draHBpbG5lbXhqN2FzYW5pdTd2bmpqYmlsdHhqcWh5ZTNtaGJz
aGc3a3g1dGZ5ZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABA2oxWbN
Mp3iluPtB+S/VM9RzAkHiKCVgk1SKH8F7j2TBmUpmY3h4a7VS8I+OkBrWlfpow/f
ROkdGNG5SkcMxJSjggYjMIIGHzAfBgNVHSMEGDAWgBT4JdmmOcfDgYclPjBUkRgh
QJsXnTAdBgNVHQ4EFgQUNLmSZgWU4IIbWEdvKSwF6n54zlwwggKmBgNVHREEggKd
MIICmYJAKi5mYWNlYm9va3draHBpbG5lbXhqN2FzYW5pdTd2bmpqYmlsdHhqcWh5
ZTNtaGJzaGc3a3g1dGZ5ZC5vbmlvboJAKi5mYWNlYm9va2Nvb2E0bGRiYXQ0Zzdp
YWNzd2wzcDJ6cmY1bnV5bHZuaHhuNmtxb2x2b2ppeHdpZC5vbmlvboJAKi5mYWNl
Ym9va3NnNGJjN2RkbmVxNDRwZjRtaXV4N283b3FkbjJhZ3N0ZzV2M2Q0NW9kaHl1
NHNxZC5vbmlvboJCKi5tLmZhY2Vib29rd2tocGlsbmVteGo3YXNhbml1N3Zuampi
aWx0eGpxaHllM21oYnNoZzdreDV0ZnlkLm9uaW9ugkMqLnh4LmZhY2Vib29rY29v
YTRsZGJhdDRnN2lhY3N3bDNwMnpyZjVudXlsdm5oeG42a3FvbHZvaml4d2lkLm9u
aW9ugkMqLnh5LmZhY2Vib29rY29vYTRsZGJhdDRnN2lhY3N3bDNwMnpyZjVudXls
dm5oeG42a3FvbHZvaml4d2lkLm9uaW9ugkMqLnh6LmZhY2Vib29rY29vYTRsZGJh
dDRnN2lhY3N3bDNwMnpyZjVudXlsdm5oeG42a3FvbHZvaml4d2lkLm9uaW9ugj5m
YWNlYm9va2Nvb2E0bGRiYXQ0ZzdpYWNzd2wzcDJ6cmY1bnV5bHZuaHhuNmtxb2x2
b2ppeHdpZC5vbmlvboI+ZmFjZWJvb2tzZzRiYzdkZG5lcTQ0cGY0bWl1eDdvN29x
ZG4yYWdzdGc1djNkNDVvZGh5dTRzcWQub25pb26CPmZhY2Vib29rd2tocGlsbmVt
eGo3YXNhbml1N3ZuampiaWx0eGpxaHllM21oYnNoZzdreDV0ZnlkLm9uaW9uMA4G
A1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgZ8G
A1UdHwSBlzCBlDBIoEagRIZCaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lD
ZXJ0RUNDRXh0ZW5kZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3JsMEigRqBEhkJodHRw
Oi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRFQ0NFeHRlbmRlZFZhbGlkYXRp
b25TZXJ2ZXJDQS5jcmwwSgYDVR0gBEMwQTALBglghkgBhv1sAgEwMgYFZ4EMAQEw
KTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGHBggr
BgEFBQcBAQR7MHkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
bTBRBggrBgEFBQcwAoZFaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
ZXJ0RUNDRXh0ZW5kZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAkGA1UdEwQCMAAw
ggF/BgorBgEEAdZ5AgQCBIIBbwSCAWsBaQB2ACl5vvCeOTkh8FZzn2Old+W+V32c
YAr4+U1dJlwlXceEAAABf3X4Hu8AAAQDAEcwRQIhAPEA0YAr4b71y5upRSOhzGbX
8/mu0IPzLGENDPUy3EBtAiBTf3grOrabbKKHoei+JTiwOpUkEfCjs4afIyO448S/
YAB2AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXlAAABf3X4HzwAAAQD
AEcwRQIhAJVg9mTomPrDwu5Jofc3y9fKGX/F9kR5NqTjxBjBsQrAAiAKqAJTEvsD
sl6qS7hJkUOnEZ6MM+vC2vY56uSQS+bo1wB3AEHIyrHfIkZKEMahOglCh15OMYsb
A+vrS8do8JBilgb2AAABf3X4Hy4AAAQDAEgwRgIhAPWskOODnKjim1zVJSYN/VpA
0Z6a25PYnzXbu0HphuTMAiEA9uvYoIfEgHSNPZJt77Eb/MzLeGGyOybmy0VJU+/b
jGwwCgYIKoZIzj0EAwMDaAAwZQIwAjy9hUidjPpWTZDWqbkcjoQsi+BEY77//InU
NIiMZNhA7DwmBcUUrfIoQaJTHQ0aAjEAw3XU1EfJzYiVRO0ovED602s4gMTlyO1+
ZG7DGlt/DcJUJb0bekcbojNXEtyvNmyJ
-----END CERTIFICATE-----
54 changes: 54 additions & 0 deletions v3/util/onion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package util

import (
"encoding/base32"
"strings"

"github.com/zmap/zcrypto/x509"
)

// An onion V3 address is base32 encoded, however Tor believes that the standard base32 encoding
// is lowercase while the Go standard library believes that the standard base32 encoding is uppercase.
//
// onionV3Base32Encoding is simply base32.StdEncoding but lowercase instead of uppercase in order
// to work with the above mismatch.
var onionV3Base32Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")

// IsOnionV3 returns whether or not the provided DNS name is an Onion V3 encoded address.
//
// In order to be an Onion V3 encoded address, the DNS name must satisfy the following:
// 1. Contain at least two labels.
// 2. The right most label MUST be "onion".
// 3. The second to the right most label MUST be exactly 56 characters long.
// 4. The second to the right most label MUST be base32 encoded against the lowercase standard encoding.
// 5. The final byte of the decoded result from #4 MUST be equal to 0x03.
func IsOnionV3(dnsName string) bool {
labels := strings.Split(dnsName, ".")
if len(labels) < 2 || labels[len(labels)-1] != "onion" {
return false
}
address := labels[len(labels)-2]
if len(address) != 56 {
return false
}
raw, err := onionV3Base32Encoding.DecodeString(address)
if err != nil {
return false
}
return raw[len(raw)-1] == 0x03
}

// AllAreOnionV3 returns whether-or-not EVERY name provided conforms to IsOnionV3
func AllAreOnionV3(names []string) bool {
isV3 := !(len(names) == 0)
for _, name := range names {
isV3 = isV3 && IsOnionV3(name)
}
return isV3
}

// IsOnionV3Cert returns whether-or-not the provided certificates' subject common name and
// ALL subject alternative DNS names are version 3 Onion addresses.
func IsOnionV3Cert(c *x509.Certificate) bool {
return AllAreOnionV3(append(c.DNSNames, c.Subject.CommonName))
}
119 changes: 119 additions & 0 deletions v3/util/onion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package util

import "testing"

func TestIsOnionV3(t *testing.T) {
data := []struct {
in string
want bool
}{
{
"*.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion",
true,
},
{
"*.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.com",
false,
},
{
// Tricky to spot, but different final byte (e instead of d)
"*.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfye.onion",
false,
},
{
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
true,
},

{
"sp3k262uwy4r2k3ycr5awluarykdpag6a7y33jxop4cs2lu5uz5sseqd.onion",
true,
},

{
"xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
true,
},
{
"facebook.onion",
false,
},
{
// Trigger bad base32 decoding with the leading #
"#a4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
false,
},
}
for _, test := range data {
test := test
t.Run(test.in, func(t *testing.T) {
got := IsOnionV3(test.in)
if got != test.want {
t.Errorf("expected %v got %v", test.want, got)
}
})
}
}

func TestAllAreOnionV3(t *testing.T) {
data := []struct {
in []string
want bool
}{
{
[]string{"*.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"},
true,
},
{
[]string{},
false,
},
{
[]string{
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"sp3k262uwy4r2k3ycr5awluarykdpag6a7y33jxop4cs2lu5uz5sseqd.onion",
"xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
},
true,
},
{
[]string{
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"facebook.com",
"xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
},
false,
},
{
[]string{
"facebook.com",
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
},
false,
},
{
[]string{
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion",
"facebook.com",
},
false,
},
}
for _, test := range data {
test := test
var name string
if len(test.in) == 0 {
name = "empty"
} else {
name = test.in[0]
}
t.Run(name, func(t *testing.T) {
got := AllAreOnionV3(test.in)
if got != test.want {
t.Errorf("expected %v got %v", test.want, got)
}
})
}
}