diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb192f8a..32b75446 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,5 +11,5 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: "1.23.4" + go-version: "1.23.7" - run: go build ./... diff --git a/UPSTREAM b/UPSTREAM index 44d6016d..07d88dbe 100644 --- a/UPSTREAM +++ b/UPSTREAM @@ -1 +1 @@ -v3.25.0 +v3.26.0 diff --git a/go.mod b/go.mod index ec5b8a68..a9eded1f 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,11 @@ module github.com/ooni/probe-engine go 1.23.0 -toolchain go1.23.4 +toolchain go1.23.7 require ( filippo.io/age v1.2.1 - github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250319154633-ceb78316d06e + github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250604135210-85a7bd92a4ba github.com/apex/log v1.9.0 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/cloudflare/circl v1.5.0 @@ -24,7 +24,7 @@ require ( github.com/montanaflynn/stats v0.7.1 github.com/ooni/minivpn v0.0.7 github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8 - github.com/ooni/probe-assets v0.26.0 + github.com/ooni/probe-assets v0.27.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/pborman/getopt/v2 v2.1.0 github.com/pion/stun v0.6.1 @@ -56,7 +56,7 @@ require ( github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 // indirect github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 // indirect github.com/Psiphon-Labs/psiphon-tls v0.0.0-20250318183125-2a2fae2db378 // indirect - github.com/Psiphon-Labs/quic-go v0.0.0-20250318213212-301924cbe026 // indirect + github.com/Psiphon-Labs/quic-go v0.0.0-20250527153145-79fe45fb83b1 // indirect github.com/Psiphon-Labs/utls v1.1.1-0.20241107183331-b18909f8ccaa // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.1 // indirect @@ -170,7 +170,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect - github.com/wlynxg/anet v0.0.3 // indirect + github.com/wlynxg/anet v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xtaci/kcp-go/v5 v5.6.8 // indirect github.com/xtaci/smux v1.5.31 // indirect diff --git a/go.sum b/go.sum index 11b4b319..cb0a3ada 100644 --- a/go.sum +++ b/go.sum @@ -35,10 +35,10 @@ github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFX github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0= github.com/Psiphon-Labs/psiphon-tls v0.0.0-20250318183125-2a2fae2db378 h1:LqI8cxnYxgUKLLvv+XZKpxZAQcov6xhEKgC82FdvG/k= github.com/Psiphon-Labs/psiphon-tls v0.0.0-20250318183125-2a2fae2db378/go.mod h1:7ZUnPnWT5z8J8hxfsVjKHYK77Zme/Y0If1b/zeziiJs= -github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250319154633-ceb78316d06e h1:J47d+9jsigy2T+763M1vdRuq5aqMUZ56IRXU8MdpW/M= -github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250319154633-ceb78316d06e/go.mod h1:HvJvOtzWZ9ESc5T9OK0Db49BX8vfdgOzxHVqirHfOLY= -github.com/Psiphon-Labs/quic-go v0.0.0-20250318213212-301924cbe026 h1:HT5a/8JUfI1H2eA9g5LPyOZis1wa+gzFdXMgWYWB88I= -github.com/Psiphon-Labs/quic-go v0.0.0-20250318213212-301924cbe026/go.mod h1:rONdWgPMbFjyyBai7gB1IBF4pT9r4l0GyiDst5XR1SY= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250604135210-85a7bd92a4ba h1:WPAeM6Dn0GmLNaRLlEdKRSeFZ7tV8kM3s5+lTQJ4JQ8= +github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20250604135210-85a7bd92a4ba/go.mod h1:1BflUn6FnGX/FlC9GFp5fFFV7ePyVUtnj3lTxZDCXwA= +github.com/Psiphon-Labs/quic-go v0.0.0-20250527153145-79fe45fb83b1 h1:zD7JvZCV8gjvtI0AZmE81Ffc/v7A+qwU1/YfUmN/Flk= +github.com/Psiphon-Labs/quic-go v0.0.0-20250527153145-79fe45fb83b1/go.mod h1:rONdWgPMbFjyyBai7gB1IBF4pT9r4l0GyiDst5XR1SY= github.com/Psiphon-Labs/utls v1.1.1-0.20241107183331-b18909f8ccaa h1:5FszHIhxb7yO267qt47tTfJOtD31k7R80L88EwNm4tc= github.com/Psiphon-Labs/utls v1.1.1-0.20241107183331-b18909f8ccaa/go.mod h1:dxmztdV9lf59cq44YY8r21m3b+xSjhg98cgZW8WK1p0= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= @@ -387,8 +387,8 @@ github.com/ooni/minivpn v0.0.7 h1:fRL6lOivKM+Q23HcN/FFiBftbKTAtz7U8r6cOypBAeM= github.com/ooni/minivpn v0.0.7/go.mod h1:0KNwmK2Wg9lDbk936XjtxvCq4tPNbK4C3IJvyLwIMrE= github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8 h1:kJ2wn19lIP/y9ng85BbFRdWKHK6Er116Bbt5uhqHVD4= github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8/go.mod h1:b/wAvTR5n92Vk2b0SBmuMU0xO4ZGVrsXtU7zjTby7vw= -github.com/ooni/probe-assets v0.26.0 h1:pzhUcKOK7S0VNEeSeK2H6o5+KhuihNTQRtfXC7R7Spk= -github.com/ooni/probe-assets v0.26.0/go.mod h1:m0k2FFzcLfFm7dhgyYkLCUR3R0CoRPr0jcjctDS2+gU= +github.com/ooni/probe-assets v0.27.0 h1:aTQTlsUle7u7uKafYDKEBTR/W90oq7jjmjl+dOoqmDo= +github.com/ooni/probe-assets v0.27.0/go.mod h1:m0k2FFzcLfFm7dhgyYkLCUR3R0CoRPr0jcjctDS2+gU= github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= @@ -595,8 +595,9 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 h1:9sreu9e9KOihf2Y0NbpyfWhd1XFDcL4GTkPYL4IvMrg= github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78/go.mod h1:HazXTRLhXFyq80TQp7PUXi6BKE6mS+ydEdzEqNBKopQ= -github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI= diff --git a/pkg/cmd/buildtool/android_test.go b/pkg/cmd/buildtool/android_test.go index 84d19bd6..97a35af2 100644 --- a/pkg/cmd/buildtool/android_test.go +++ b/pkg/cmd/buildtool/android_test.go @@ -702,12 +702,12 @@ func TestAndroidBuildCdepsOpenSSL(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -757,12 +757,12 @@ func TestAndroidBuildCdepsOpenSSL(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -812,12 +812,12 @@ func TestAndroidBuildCdepsOpenSSL(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -867,12 +867,12 @@ func TestAndroidBuildCdepsOpenSSL(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -1738,12 +1738,12 @@ func TestAndroidBuildCdepsTor(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, @@ -1827,12 +1827,12 @@ func TestAndroidBuildCdepsTor(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, @@ -1916,12 +1916,12 @@ func TestAndroidBuildCdepsTor(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, @@ -2005,12 +2005,12 @@ func TestAndroidBuildCdepsTor(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, diff --git a/pkg/cmd/buildtool/cdepsopenssl.go b/pkg/cmd/buildtool/cdepsopenssl.go index fec6196f..abb14765 100644 --- a/pkg/cmd/buildtool/cdepsopenssl.go +++ b/pkg/cmd/buildtool/cdepsopenssl.go @@ -27,13 +27,13 @@ func cdepsOpenSSLBuildMain(globalEnv *cBuildEnv, deps buildtoolmodel.Dependencie defer restore() // See https://github.com/Homebrew/homebrew-core/blob/master/Formula/o/openssl@3.rb - cdepsMustFetch("https://www.openssl.org/source/openssl-3.4.0.tar.gz") + cdepsMustFetch("https://www.openssl.org/source/openssl-3.5.0.tar.gz") deps.VerifySHA256( // must be mockable - "e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf", - "openssl-3.4.0.tar.gz", + "344d0a79f1a9b08029b0744e2cc401a43f9c90acd1044d09a530b4885a8e9fc0", + "openssl-3.5.0.tar.gz", ) - must.Run(log.Log, "tar", "-xf", "openssl-3.4.0.tar.gz") - _ = deps.MustChdir("openssl-3.4.0") // must be mockable + must.Run(log.Log, "tar", "-xf", "openssl-3.5.0.tar.gz") + _ = deps.MustChdir("openssl-3.5.0") // must be mockable mydir := filepath.Join(topdir, "CDEPS", "openssl") for _, patch := range cdepsMustListPatches(mydir) { diff --git a/pkg/cmd/buildtool/cdepstor.go b/pkg/cmd/buildtool/cdepstor.go index b18555dd..e00ce15e 100644 --- a/pkg/cmd/buildtool/cdepstor.go +++ b/pkg/cmd/buildtool/cdepstor.go @@ -27,13 +27,13 @@ func cdepsTorBuildMain(globalEnv *cBuildEnv, deps buildtoolmodel.Dependencies) { defer restore() // See https://github.com/Homebrew/homebrew-core/blob/master/Formula/t/tor.rb - cdepsMustFetch("https://www.torproject.org/dist/tor-0.4.8.14.tar.gz") + cdepsMustFetch("https://www.torproject.org/dist/tor-0.4.8.16.tar.gz") deps.VerifySHA256( // must be mockable - "5047e1ded12d9aac4eb858f7634a627714dd58ce99053d517691a4b304a66d10", - "tor-0.4.8.14.tar.gz", + "6540dd377a120fb8e7d27530aa3b7ff72a0fa5b4f670fe1d64c987c1cfd390cb", + "tor-0.4.8.16.tar.gz", ) - must.Run(log.Log, "tar", "-xf", "tor-0.4.8.14.tar.gz") - _ = deps.MustChdir("tor-0.4.8.14") // must be mockable + must.Run(log.Log, "tar", "-xf", "tor-0.4.8.16.tar.gz") + _ = deps.MustChdir("tor-0.4.8.16") // must be mockable mydir := filepath.Join(topdir, "CDEPS", "tor") for _, patch := range cdepsMustListPatches(mydir) { diff --git a/pkg/cmd/buildtool/gomobile.go b/pkg/cmd/buildtool/gomobile.go index aba10904..32fcebee 100644 --- a/pkg/cmd/buildtool/gomobile.go +++ b/pkg/cmd/buildtool/gomobile.go @@ -64,3 +64,30 @@ func gomobileBuild(config *gomobileConfig) { runtimex.Try0(shellx.RunEx(defaultShellxConfig(), argv, config.envp)) } + +// oomobileBuild runs a build based on oomobile +func oomobileBuild(config *gomobileConfig) { + // Undoes the effects of go-getting golang.org/x/mobile/cmd/gomobile + defer must.Run(log.Log, "go", "mod", "tidy") + + must.Run(log.Log, "go", "install", "github.com/ooni/oomobile/cmd/gomobile@latest") + must.Run(log.Log, "go", "install", "github.com/ooni/oomobile/cmd/gobind@latest") + + gopath := config.deps.GOPATH() + gomobile := filepath.Join(gopath, "bin", "gomobile") + must.Run(log.Log, gomobile, "init") + + // Adding gomobile to go.mod as documented by golang.org/wiki/Mobile + must.Run(log.Log, "go", "get", "-d", "github.com/ooni/oomobile/cmd/gomobile") + + argv := runtimex.Try1(shellx.NewArgv(gomobile, "bind")) + argv.Append("-target", config.target) + argv.Append("-o", config.output) + for _, entry := range config.extraFlags { + argv.Append(entry) + } + + argv.Append("./pkg/oonimkall") + + runtimex.Try0(shellx.RunEx(defaultShellxConfig(), argv, config.envp)) +} diff --git a/pkg/cmd/buildtool/ios_test.go b/pkg/cmd/buildtool/ios_test.go index eba4d6cb..5a05b885 100644 --- a/pkg/cmd/buildtool/ios_test.go +++ b/pkg/cmd/buildtool/ios_test.go @@ -349,12 +349,12 @@ func TestIOSBuildCdepsOpenSSL(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -399,12 +399,12 @@ func TestIOSBuildCdepsOpenSSL(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -449,12 +449,12 @@ func TestIOSBuildCdepsOpenSSL(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -1142,12 +1142,12 @@ func TestIOSBuildCdepsTor(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, @@ -1232,12 +1232,12 @@ func TestIOSBuildCdepsTor(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, @@ -1322,12 +1322,12 @@ func TestIOSBuildCdepsTor(t *testing.T) { }, { Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, diff --git a/pkg/cmd/buildtool/linuxcdeps_test.go b/pkg/cmd/buildtool/linuxcdeps_test.go index eabf2ed6..3918d5d7 100644 --- a/pkg/cmd/buildtool/linuxcdeps_test.go +++ b/pkg/cmd/buildtool/linuxcdeps_test.go @@ -92,12 +92,12 @@ func TestLinuxCdepsBuildMain(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.4.0.tar.gz", + "curl", "-fsSLO", "https://www.openssl.org/source/openssl-3.5.0.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "openssl-3.4.0.tar.gz", + "tar", "-xf", "openssl-3.5.0.tar.gz", }, }, { Env: []string{}, @@ -322,12 +322,12 @@ func TestLinuxCdepsBuildMain(t *testing.T) { expect: []buildtooltest.ExecExpectations{{ Env: []string{}, Argv: []string{ - "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.14.tar.gz", + "curl", "-fsSLO", "https://www.torproject.org/dist/tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, Argv: []string{ - "tar", "-xf", "tor-0.4.8.14.tar.gz", + "tar", "-xf", "tor-0.4.8.16.tar.gz", }, }, { Env: []string{}, diff --git a/pkg/cmd/buildtool/main.go b/pkg/cmd/buildtool/main.go index 82f7bae5..d009a966 100644 --- a/pkg/cmd/buildtool/main.go +++ b/pkg/cmd/buildtool/main.go @@ -21,6 +21,7 @@ func main() { root.AddCommand(darwinSubcommand()) root.AddCommand(genericSubcommand()) root.AddCommand(iosSubcommand()) + root.AddCommand(desktopSubcommand()) root.AddCommand(linuxSubcommand()) root.AddCommand(oohelperdSubcommand()) root.AddCommand(windowsSubcommand()) diff --git a/pkg/experiment/echcheck/generate.go b/pkg/experiment/echcheck/generate.go index acf70d47..a4cce2af 100644 --- a/pkg/experiment/echcheck/generate.go +++ b/pkg/experiment/echcheck/generate.go @@ -4,91 +4,74 @@ package echcheck // ietf.org/archive/id/draft-ietf-tls-esni-14.html import ( - "fmt" + "io" + "github.com/cloudflare/circl/hpke" "golang.org/x/crypto/cryptobyte" - "io" ) -const clientHelloOuter uint8 = 0 - -// echExtension is the Encrypted Client Hello extension that is part of -// ClientHelloOuter as specified in: -// ietf.org/archive/id/draft-ietf-tls-esni-14.html#section-5 -type echExtension struct { - kdfID uint16 - aeadID uint16 - configID uint8 - enc []byte - payload []byte -} - -func (ech *echExtension) marshal() []byte { - var b cryptobyte.Builder - b.AddUint8(clientHelloOuter) - b.AddUint16(ech.kdfID) - b.AddUint16(ech.aeadID) - b.AddUint8(ech.configID) - b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(ech.enc) - }) - b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(ech.payload) - }) - return b.BytesOrPanic() -} - -// generateGreaseExtension generates an ECH extension with random values as -// specified in ietf.org/archive/id/draft-ietf-tls-esni-14.html#section-6.2 -func generateGreaseExtension(rand io.Reader) ([]byte, error) { - // initialize HPKE suite parameters - kem := hpke.KEM(uint16(hpke.KEM_X25519_HKDF_SHA256)) - kdf := hpke.KDF(uint16(hpke.KDF_HKDF_SHA256)) - aead := hpke.AEAD(uint16(hpke.AEAD_AES128GCM)) - - if !kem.IsValid() || !kdf.IsValid() || !aead.IsValid() { - return nil, fmt.Errorf("required parameters not supported") +// ECH Config List per: +// https://www.ietf.org/archive/id/draft-ietf-tls-esni-22.html#name-encrypted-clienthello-confi +func generateGreaseyECHConfigList(rand io.Reader, publicName string) ([]byte, error) { + // Start ECHConfig + var c cryptobyte.Builder + version := uint16(0xfe0d) + c.AddUint16(version) + + // Start ECHConfigContents + var ecc cryptobyte.Builder + // Start HpkeKeyConfig + randConfigId := make([]byte, 1) + if _, err := io.ReadFull(rand, randConfigId); err != nil { + return nil, err } - - defaultHPKESuite := hpke.NewSuite(kem, kdf, aead) - - // generate a public key to place in 'enc' field + ecc.AddUint8(randConfigId[0]) + ecc.AddUint16(uint16(hpke.KEM_X25519_HKDF_SHA256)) + // Generate a public key + kem := hpke.KEM(uint16(hpke.KEM_X25519_HKDF_SHA256)) publicKey, _, err := kem.Scheme().GenerateKeyPair() - if err != nil { - return nil, fmt.Errorf("failed to generate key pair: %s", err) - } - - // initiate HPKE Sender - sender, err := defaultHPKESuite.NewSender(publicKey, nil) - if err != nil { - return nil, fmt.Errorf("failed to create sender: %s", err) - } - - // Set ECH Extension Fields - var ech echExtension - - ech.kdfID = uint16(kdf) - ech.aeadID = uint16(aead) - - randomByte := make([]byte, 1) - _, err = io.ReadFull(rand, randomByte) if err != nil { return nil, err } - ech.configID = randomByte[0] - - ech.enc, _, err = sender.Setup(rand) + publicKeyBytes, err := publicKey.MarshalBinary() if err != nil { return nil, err } + ecc.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(publicKeyBytes) + }) + // Start HpkeSymmetricCipherSuite + kdf := hpke.KDF(uint16(hpke.KDF_HKDF_SHA256)) + aead := hpke.AEAD(uint16(hpke.AEAD_AES128GCM)) + var cs cryptobyte.Builder + cs.AddUint16(uint16(kdf)) + cs.AddUint16(uint16(aead)) + ecc.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(cs.BytesOrPanic()) + }) + // End HpkeSymmetricCipherSuite + // End HpkeKeyConfig + maxNameLength := uint8(42) + ecc.AddUint8(maxNameLength) + publicNameBytes := []byte(publicName) + ecc.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(publicNameBytes) + }) + // Start ECHConfigExtension + var ece cryptobyte.Builder + ecc.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(ece.BytesOrPanic()) + }) + // End ECHConfigExtension + // End ECHConfigContents + c.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(ecc.BytesOrPanic()) + }) + // End ECHConfig + var l cryptobyte.Builder + l.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(c.BytesOrPanic()) + }) - // TODO: compute this correctly as per https://www.ietf.org/archive/id/draft-ietf-tls-esni-14.html#name-recommended-padding-scheme - randomEncodedClientHelloInnerLen := 100 - cipherLen := int(aead.CipherLen(uint(randomEncodedClientHelloInnerLen))) - ech.payload = make([]byte, randomEncodedClientHelloInnerLen+cipherLen) - if _, err = io.ReadFull(rand, ech.payload); err != nil { - return nil, err - } - - return ech.marshal(), nil + return l.BytesOrPanic(), nil } diff --git a/pkg/experiment/echcheck/handshake.go b/pkg/experiment/echcheck/handshake.go index 9eb4e722..4225b7e5 100644 --- a/pkg/experiment/echcheck/handshake.go +++ b/pkg/experiment/echcheck/handshake.go @@ -2,125 +2,126 @@ package echcheck import ( "context" - "crypto/rand" "crypto/tls" - "net" + "crypto/x509" + "encoding/base64" + "fmt" + "net/url" "time" - "github.com/apex/log" "github.com/ooni/probe-engine/pkg/logx" "github.com/ooni/probe-engine/pkg/measurexlite" "github.com/ooni/probe-engine/pkg/model" "github.com/ooni/probe-engine/pkg/netxlite" - utls "gitlab.com/yawning/utls.git" ) -const echExtensionType uint16 = 0xfe0d - -func connectAndHandshake( - ctx context.Context, - startTime time.Time, - address string, sni string, outerSni string, - logger model.Logger) (chan model.ArchivalTLSOrQUICHandshakeResult, error) { - - channel := make(chan model.ArchivalTLSOrQUICHandshakeResult) - - ol := logx.NewOperationLogger(logger, "echcheck: TCPConnect %s", address) - var dialer net.Dialer - conn, err := dialer.DialContext(ctx, "tcp", address) - ol.Stop(err) +// We can't see which outerservername go std lib selects, so we preemptively +// make sure it's unambiguous for a given ECH Config List. If the ecl is +// empty, return an empty string. +func getUnambiguousOuterServerName(ecl []byte) (string, error) { + if len(ecl) == 0 { + return "", nil + } + configs, err := parseECHConfigList(ecl) if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + return "", fmt.Errorf("failed to parse ECH config: %w", err) } - - go func() { - var res *model.ArchivalTLSOrQUICHandshakeResult - if outerSni == "" { - res = handshake( - ctx, - conn, - startTime, - address, - sni, - logger, - ) - } else { - res = handshakeWithEch( - ctx, - conn, - startTime, - address, - outerSni, - logger, - ) - // We need to set this explicitly because otherwise it will get - // overridden with the outerSni in the case of ECH - res.ServerName = sni + outerServerName := string(configs[0].PublicName) + for _, ec := range configs { + if string(ec.PublicName) != outerServerName { + // It's perfectly valid to have multiple ECH configs with different + // `PublicName`s. But, since we can't see which one is selected by + // go's tls package, we can't accurately record OuterServerName. + return "", fmt.Errorf("ambigious OuterServerName for config") } - channel <- *res - }() - - return channel, nil + } + return outerServerName, nil } -func handshake(ctx context.Context, conn net.Conn, zeroTime time.Time, - address string, sni string, logger model.Logger) *model.ArchivalTLSOrQUICHandshakeResult { - return handshakeWithExtension(ctx, conn, zeroTime, address, sni, []utls.TLSExtension{}, logger) -} +func startHandshake(ctx context.Context, echConfigList []byte, isGrease bool, startTime time.Time, address string, target *url.URL, logger model.Logger, testOnlyRootCAs *x509.CertPool) (chan TestKeys, error) { -func handshakeWithEch(ctx context.Context, conn net.Conn, zeroTime time.Time, - address string, sni string, logger model.Logger) *model.ArchivalTLSOrQUICHandshakeResult { - payload, err := generateGreaseExtension(rand.Reader) - if err != nil { - panic("failed to generate grease ECH: " + err.Error()) - } + channel := make(chan TestKeys) - var utlsEchExtension utls.GenericExtension + tlsConfig := genEchTLSConfig(target.Hostname(), echConfigList, testOnlyRootCAs) - utlsEchExtension.Id = echExtensionType - utlsEchExtension.Data = payload + go func() { + tk := TestKeys{} + tk = handshake(ctx, isGrease, startTime, address, logger, tlsConfig, tk) + channel <- tk + }() - hs := handshakeWithExtension(ctx, conn, zeroTime, address, sni, []utls.TLSExtension{&utlsEchExtension}, logger) - hs.ECHConfig = "GREASE" - hs.OuterServerName = sni - return hs + return channel, nil } -func handshakeMaybePrintWithECH(doprint bool) string { - if doprint { - return "WithECH" +// Add to tk TestKeys all events as they occur. May call self recursively using retry_configs. +func handshake(ctx context.Context, isGrease bool, startTime time.Time, address string, logger model.Logger, tlsConfig *tls.Config, tk TestKeys) TestKeys { + var d string + if isGrease { + d = " (GREASE)" + } else if len(tlsConfig.EncryptedClientHelloConfigList) > 0 { + d = " (RealECH)" } - return "" -} - -func handshakeWithExtension(ctx context.Context, conn net.Conn, zeroTime time.Time, address string, sni string, - extensions []utls.TLSExtension, logger model.Logger) *model.ArchivalTLSOrQUICHandshakeResult { - tlsConfig := genTLSConfig(sni) - handshakerConstructor := newHandshakerWithExtensions(extensions) - tracedHandshaker := handshakerConstructor(log.Log, &utls.HelloFirefox_Auto) + ol1 := logx.NewOperationLogger(logger, "echcheck: TCPConnect %s", address) + trace := measurexlite.NewTrace(0, startTime) + dialer := trace.NewDialerWithoutResolver(logger) + conn, err := dialer.DialContext(ctx, "tcp", address) + ol1.Stop(err) + tk.TCPConnects = append(tk.TCPConnects, trace.TCPConnects()...) + tk.NetworkEvents = append(tk.NetworkEvents, trace.NetworkEvents()...) - ol := logx.NewOperationLogger(logger, "echcheck: TLSHandshake%s", handshakeMaybePrintWithECH(len(extensions) > 0)) + ol2 := logx.NewOperationLogger(logger, "echcheck: DialTLS%s", d) start := time.Now() - maybeTLSConn, err := tracedHandshaker.Handshake(ctx, conn, tlsConfig) + maybeTLSConn := tls.Client(conn, tlsConfig) + err = maybeTLSConn.HandshakeContext(ctx) finish := time.Now() - ol.Stop(err) + ol2.Stop(err) + + retryConfigs := []byte{} + if echErr, ok := err.(*tls.ECHRejectionError); ok && isGrease { + if len(echErr.RetryConfigList) > 0 { + retryConfigs = echErr.RetryConfigList + } + // We ignore this error in crafting our TLSOrQUICHandshakeResult + // since the *golang* error is expected and merely indicates we + // didn't get the ECH setup we wanted. It does NOT indicate that + // that the handshake itself was a failure. + // TODO: Can we *confirm* there wasn't a separate TLS failure? This might be ambiguous :-( + // TODO: Confirm above semantics with OONI team. + err = nil + } connState := netxlite.MaybeTLSConnectionState(maybeTLSConn) - return measurexlite.NewArchivalTLSOrQUICHandshakeResult(0, start.Sub(zeroTime), "tcp", address, tlsConfig, - connState, err, finish.Sub(zeroTime)) + hs := measurexlite.NewArchivalTLSOrQUICHandshakeResult(0, start.Sub(startTime), + "tcp", address, tlsConfig, connState, err, finish.Sub(startTime)) + if isGrease { + hs.ECHConfig = "GREASE" + } else { + hs.ECHConfig = base64.StdEncoding.EncodeToString(tlsConfig.EncryptedClientHelloConfigList) + } + osn, err := getUnambiguousOuterServerName(tlsConfig.EncryptedClientHelloConfigList) + if err != nil { + msg := fmt.Sprintf("can't determine OuterServerName: %s", err) + hs.SoError = &msg + } + hs.OuterServerName = osn + tk.TLSHandshakes = append(tk.TLSHandshakes, hs) + + if len(retryConfigs) > 0 { + tlsConfig.EncryptedClientHelloConfigList = retryConfigs + tk = handshake(ctx, false, startTime, address, logger, tlsConfig, tk) + } + + return tk } -// We are creating the pool just once because there is a performance penalty -// when creating it every time. See https://github.com/ooni/probe/issues/2413. -var certpool = netxlite.NewMozillaCertPool() - -// genTLSConfig generates tls.Config from a given SNI -func genTLSConfig(sni string) *tls.Config { - return &tls.Config{ // #nosec G402 - we need to use a large TLS versions range for measuring - RootCAs: certpool, - ServerName: sni, - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, // #nosec G402 - it's fine to skip verify in a nettest +func genEchTLSConfig(host string, echConfigList []byte, testOnlyRootCAs *x509.CertPool) *tls.Config { + c := &tls.Config{ServerName: host} + if len(echConfigList) > 0 { + c.EncryptedClientHelloConfigList = echConfigList + } + if testOnlyRootCAs != nil { + c.RootCAs = testOnlyRootCAs } + return c } diff --git a/pkg/experiment/echcheck/handshake_test.go b/pkg/experiment/echcheck/handshake_test.go index ddfcbc72..7a02fb42 100644 --- a/pkg/experiment/echcheck/handshake_test.go +++ b/pkg/experiment/echcheck/handshake_test.go @@ -2,8 +2,10 @@ package echcheck import ( "context" + "crypto/rand" + "crypto/tls" + "crypto/x509" "fmt" - "net" "net/http" "net/http/httptest" "net/url" @@ -13,38 +15,146 @@ import ( "github.com/ooni/probe-engine/pkg/model" ) -func TestHandshake(t *testing.T) { +type testServerConfig struct { + ts *httptest.Server + url *url.URL + tlsConfig *tls.Config +} + +func setupTest(t *testing.T) testServerConfig { ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "success") })) - defer ts.Close() parsed, err := url.Parse(ts.URL) if err != nil { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var dialer net.Dialer - conn, err := dialer.DialContext(ctx, "tcp", parsed.Host) - if err != nil { - t.Fatal(err) + testPool := x509.NewCertPool() + testPool.AddCert(ts.Certificate()) + tlsConfig := &tls.Config{ + ServerName: parsed.Hostname(), + RootCAs: testPool, } - result := handshakeWithEch(ctx, conn, time.Now(), parsed.Host, "crypto.cloudflare.com", model.DiscardLogger) - if result == nil { - t.Fatal("expected result") + return testServerConfig{ + ts: ts, + url: parsed, + tlsConfig: tlsConfig, } +} - if result.SoError != nil { - t.Fatal("did not expect error, got: ", result.SoError) +func TestHandshake(t *testing.T) { + tests := []struct { + name string + sendGrease bool + useRetryConfigs bool + want func(*testing.T, testServerConfig, TestKeys) + }{ + { + name: "no ECH", + sendGrease: false, + useRetryConfigs: false, + want: func(t *testing.T, testConfig testServerConfig, result TestKeys) { + if len(result.TCPConnects) != 1 { + t.Fatal("expected exactly one TCPConnect, got: ", len(result.TCPConnects)) + } + if len(result.TLSHandshakes) != 1 { + t.Fatal("expected exactly one TLS handshake, got: ", len(result.TLSHandshakes)) + } + if result.TLSHandshakes[0].SoError != nil { + t.Fatal("did not expect soft error, got: ", *result.TLSHandshakes[0].SoError) + } + if result.TLSHandshakes[0].Failure != nil { + t.Fatal("did not expect error, got: ", *result.TLSHandshakes[0].Failure) + } + if result.TLSHandshakes[0].OuterServerName != "" { + t.Fatal("expected OuterServerName to be empty, got: ", result.TLSHandshakes[0].OuterServerName) + } + }, + }, + { + name: "fail to establish ECH handshake", + // We're using a GREASE ECHConfigList, but we'll handle it as if it's a genuine one (isGrease=False) + // Test server doesn't handle ECH yet, so it wouldn't send retry configs anyways. + sendGrease: true, + useRetryConfigs: false, + want: func(t *testing.T, testConfig testServerConfig, result TestKeys) { + if len(result.TCPConnects) != 1 { + t.Fatal("expected exactly one TCPConnect, got: ", len(result.TCPConnects)) + } + if len(result.TLSHandshakes) != 1 { + t.Fatal("expected exactly one TLS handshake, got: ", len(result.TLSHandshakes)) + } + if result.TLSHandshakes[0].ServerName != testConfig.url.Hostname() { + t.Fatal("expected ServerName to be set to ts.URL.Hostname(), got: ", result.TLSHandshakes[0].ServerName) + } + if result.TLSHandshakes[0].SoError != nil { + t.Fatal("did not expect soft error, got: ", *result.TLSHandshakes[0].SoError) + } + if result.TLSHandshakes[0].Failure == nil { + t.Fatal("no GREASE retry so expected Failure to be set") + } + if result.TLSHandshakes[0].OuterServerName != testConfig.url.Hostname() { + t.Fatal("expected OuterServerName to be testserver name, got: ", result.TLSHandshakes[0].OuterServerName) + } + }, + }, + { + name: "GREASEy ECH handshake", + sendGrease: true, + useRetryConfigs: true, + want: func(t *testing.T, testConfig testServerConfig, result TestKeys) { + // Expect 1 TCP Connect and 1 TLS connect because test server does + // not provide retry configs to initiate a second set. Upon golang + // implementing retry_configs, this should become 2 and we should + // expressly check the 2nd one conforms to expectations. + if len(result.TCPConnects) != 1 { + t.Fatal("expected exactly one TCPConnect, got: ", len(result.TCPConnects)) + } + if len(result.TLSHandshakes) != 1 { + t.Fatal("expected exactly one TLS handshake, got: ", len(result.TLSHandshakes)) + } + if result.TLSHandshakes[0].ECHConfig != "GREASE" { + t.Fatal("expected first ECHConfig to be string literal 'GREASE', got: ", result.TLSHandshakes[0].ECHConfig) + } + if result.TLSHandshakes[0].SoError != nil { + t.Fatal("did not expect soft error, got: ", *result.TLSHandshakes[0].SoError) + } + if result.TLSHandshakes[0].Failure != nil { + t.Fatal("did not expect failure, got: ", *result.TLSHandshakes[0].Failure) + } + if result.TLSHandshakes[0].OuterServerName != testConfig.url.Hostname() { + t.Fatal("expected OuterServerName to be testserver name, got: ", result.TLSHandshakes[0].OuterServerName) + } + }, + }, + // TODO: Add a test case with Real ECH once the server-side of crypto/tls supports it. } - if result.Failure != nil { - t.Fatal("did not expect error, got: ", *result.Failure) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testConfig := setupTest(t) + defer testConfig.ts.Close() - conn.Close() + ecl := []byte{} + if test.sendGrease { + grease, err := generateGreaseyECHConfigList(rand.Reader, testConfig.url.Hostname()) + if err != nil { + t.Fatal(err) + } + ecl = grease + } + testConfig.tlsConfig.EncryptedClientHelloConfigList = ecl + + ctx := context.Background() + ch, err := startHandshake(ctx, ecl, test.useRetryConfigs, time.Now(), testConfig.url.Host, testConfig.url, model.DiscardLogger, testConfig.tlsConfig.RootCAs) + if err != nil { + t.Fatal(err) + } + result := <-ch + test.want(t, testConfig, result) + }) + } } diff --git a/pkg/experiment/echcheck/measure.go b/pkg/experiment/echcheck/measure.go index f4d802d0..278dbb0d 100644 --- a/pkg/experiment/echcheck/measure.go +++ b/pkg/experiment/echcheck/measure.go @@ -2,8 +2,10 @@ package echcheck import ( "context" + crand "crypto/rand" "errors" - "math/rand" + "fmt" + "math/rand/v2" "net" "net/url" @@ -15,7 +17,7 @@ import ( const ( testName = "echcheck" - testVersion = "0.2.0" + testVersion = "0.3.0" defaultURL = "https://cloudflare-ech.com/cdn-cgi/trace" ) @@ -29,6 +31,9 @@ var ( // TestKeys contains echcheck test keys. type TestKeys struct { + NetworkEvents []*model.ArchivalNetworkEvent `json:"network_events"` + Queries []*model.ArchivalDNSLookupResult `json:"queries"` + TCPConnects []*model.ArchivalTCPConnectResult `json:"tcp_connects"` TLSHandshakes []*model.ArchivalTLSOrQUICHandshakeResult `json:"tls_handshakes"` } @@ -52,6 +57,7 @@ func (m *Measurer) Run( ctx context.Context, args *model.ExperimentArgs, ) error { + if args.Measurement.Input == "" { args.Measurement.Input = defaultURL } @@ -63,33 +69,60 @@ func (m *Measurer) Run( return errInvalidInputScheme } - // 1. perform a DNSLookup - ol := logx.NewOperationLogger(args.Session.Logger(), "echcheck: DNSLookup[%s] %s", m.config.resolverURL(), parsed.Host) + // DNS Lookups for Address and HTTPS RR + ol := logx.NewOperationLogger(args.Session.Logger(), "echcheck: DNSLookups[%s] %s", m.config.resolverURL(), parsed.Host) trace := measurexlite.NewTrace(0, args.Measurement.MeasurementStartTimeSaved) resolver := trace.NewParallelDNSOverHTTPSResolver(args.Session.Logger(), m.config.resolverURL()) - addrs, err := resolver.LookupHost(ctx, parsed.Host) + // We dial the alias, even when there are hints in the HTTPS record. + addrs, addrsErr := resolver.LookupHost(ctx, parsed.Hostname()) + // Port prefixing per: + // https://www.rfc-editor.org/rfc/rfc9460.html#name-query-names-for-https-rrs + var dnsQueryHost = parsed.Hostname() + if parsed.Port() != "" && parsed.Port() != "443" { + dnsQueryHost = fmt.Sprintf("_%s._https.%s", parsed.Port(), parsed.Hostname()) + } + httpsRr, httpsErr := resolver.LookupHTTPS(ctx, dnsQueryHost) ol.Stop(err) + + if addrsErr != nil { + return addrsErr + } + if httpsErr != nil { + return httpsErr + } + realEchConfig := httpsRr.Ech + grease, err := generateGreaseyECHConfigList(crand.Reader, parsed.Hostname()) if err != nil { - return err + return fmt.Errorf("failed to generate GREASE ECH config: %w", err) } - runtimex.Assert(len(addrs) > 0, "expected at least one entry in addrs") - address := net.JoinHostPort(addrs[0], "443") - handshakes := []func() (chan model.ArchivalTLSOrQUICHandshakeResult, error){ - // handshake with ECH disabled and SNI coming from the URL - func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) { - return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved, - address, parsed.Host, "", args.Session.Logger()) + runtimex.Assert(len(addrs) > 0, "expected at least one entry in addrs") + port := parsed.Port() + if port == "" { + port = "443" + } + address := net.JoinHostPort(addrs[0], port) + + handshakes := []func() (chan TestKeys, error){ + // Handshake with no ECH + func() (chan TestKeys, error) { + return startHandshake(ctx, []byte{}, false, + args.Measurement.MeasurementStartTimeSaved, address, + parsed, args.Session.Logger(), nil) }, - // handshake with ECH enabled and ClientHelloOuter SNI coming from the URL - func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) { - return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved, - address, parsed.Host, parsed.Host, args.Session.Logger()) + + // Handshake with ECH GREASE + func() (chan TestKeys, error) { + return startHandshake(ctx, grease, true, + args.Measurement.MeasurementStartTimeSaved, address, + parsed, args.Session.Logger(), nil) }, - // handshake with ECH enabled and hardcoded different ClientHelloOuter SNI - func() (chan model.ArchivalTLSOrQUICHandshakeResult, error) { - return connectAndHandshake(ctx, args.Measurement.MeasurementStartTimeSaved, - address, parsed.Host, "cloudflare.com", args.Session.Logger()) + + // Handshake with real ECH + func() (chan TestKeys, error) { + return startHandshake(ctx, realEchConfig, false, + args.Measurement.MeasurementStartTimeSaved, address, + parsed, args.Session.Logger(), nil) }, } @@ -99,8 +132,7 @@ func (m *Measurer) Run( handshakes[i], handshakes[j] = handshakes[j], handshakes[i] }) - var channels [3](chan model.ArchivalTLSOrQUICHandshakeResult) - var results [3](model.ArchivalTLSOrQUICHandshakeResult) + var channels [3](chan TestKeys) // Fire the handshakes in parallel // TODO: currently if one of the connects fails we fail the whole result @@ -113,14 +145,23 @@ func (m *Measurer) Run( } } + alltks := TestKeys{ + TLSHandshakes: []*model.ArchivalTLSOrQUICHandshakeResult{}, + NetworkEvents: trace.NetworkEvents(), + Queries: trace.DNSLookupsFromRoundTrip(), + TCPConnects: []*model.ArchivalTCPConnectResult{}, + } + // Wait on each channel for the results to come in - for idx, ch := range channels { - results[idx] = <-ch + for _, ch := range channels { + tk := <-ch + alltks.TLSHandshakes = append(alltks.TLSHandshakes, tk.TLSHandshakes...) + alltks.NetworkEvents = append(alltks.NetworkEvents, tk.NetworkEvents...) + alltks.Queries = append(alltks.Queries, tk.Queries...) + alltks.TCPConnects = append(alltks.TCPConnects, tk.TCPConnects...) } - args.Measurement.TestKeys = TestKeys{TLSHandshakes: []*model.ArchivalTLSOrQUICHandshakeResult{ - &results[0], &results[1], &results[2], - }} + args.Measurement.TestKeys = alltks return nil } diff --git a/pkg/experiment/echcheck/measure_test.go b/pkg/experiment/echcheck/measure_test.go index 46b3404d..381df2e9 100644 --- a/pkg/experiment/echcheck/measure_test.go +++ b/pkg/experiment/echcheck/measure_test.go @@ -2,6 +2,8 @@ package echcheck import ( "context" + "net/url" + "strings" "testing" "github.com/ooni/probe-engine/pkg/mocks" @@ -14,7 +16,7 @@ func TestNewExperimentMeasurer(t *testing.T) { if measurer.ExperimentName() != "echcheck" { t.Fatal("unexpected name") } - if measurer.ExperimentVersion() != "0.2.0" { + if measurer.ExperimentVersion() != "0.3.0" { t.Fatal("unexpected version") } } @@ -114,12 +116,57 @@ func TestMeasurementSuccessRealWorld(t *testing.T) { // check results tk := msrmnt.TestKeys.(TestKeys) + foundA, foundAAAA, foundHTTPS := false, false, false + parsed, err := url.Parse(defaultURL) + if err != nil { + t.Fatal("bad default url:", err) + } + for _, q := range tk.Queries { + aboutHost := q.Hostname == parsed.Hostname() + if (q.Failure == nil) && aboutHost { + switch q.QueryType { + case "A": + foundA = true + case "AAAA": + foundAAAA = true + case "HTTPS": + foundHTTPS = true + default: + // nothing + } + } + } + if !foundA { + t.Fatal("No DNS type A roundtrip reported") + } + if !foundAAAA { + t.Fatal("No DNS type AAAA roundtrip reported") + } + if !foundHTTPS { + t.Fatal("No DNS type HTTPS roundtrip reported") + } + if len(tk.NetworkEvents) == 0 { + t.Fatal("no network events recorded") + } + // NoECH, GREASE, RealECH + if len(tk.TLSHandshakes) != 3 { + t.Fatal("unexpected number of TLS handshakes", len(tk.TLSHandshakes)) + } + if len(tk.TCPConnects) != 3 { + t.Fatal("unexpected number of TCP connections", len(tk.TCPConnects)) + } for _, hs := range tk.TLSHandshakes { if hs.Failure != nil { if hs.ECHConfig == "GREASE" { - t.Fatal("unexpected exp failure:", hs.Failure) + // We expect that this either succeeds (i.e. with a non-ECH server) + // OR that it fails with an EchRejeECHRejectionError + if !strings.Contains(*hs.Failure, "tls: server rejected ECH") { + t.Fatal("unexpected exp (grease) failure:", *hs.Failure) + } + } else if len(hs.ECHConfig) > 0 { + t.Fatal("unexpected exp (ech) failure:", *hs.Failure) } else { - t.Fatal("unexpected ctrl failure:", hs.Failure) + t.Fatal("unexpected ctrl failure:", *hs.Failure) } } } diff --git a/pkg/experiment/echcheck/utls.go b/pkg/experiment/echcheck/utls.go deleted file mode 100644 index d585e9a8..00000000 --- a/pkg/experiment/echcheck/utls.go +++ /dev/null @@ -1,49 +0,0 @@ -package echcheck - -import ( - "context" - "crypto/tls" - "net" - - "github.com/ooni/probe-engine/pkg/model" - "github.com/ooni/probe-engine/pkg/netxlite" - "github.com/ooni/probe-engine/pkg/runtimex" - utls "gitlab.com/yawning/utls.git" -) - -type tlsHandshakerWithExtensions struct { - extensions []utls.TLSExtension - dl model.DebugLogger - id *utls.ClientHelloID -} - -var _ model.TLSHandshaker = &tlsHandshakerWithExtensions{} - -// newHandshakerWithExtensions returns a NewHandshaker function for creating -// tlsHandshakerWithExtensions instances. -func newHandshakerWithExtensions(extensions []utls.TLSExtension) func(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker { - return func(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker { - return &tlsHandshakerWithExtensions{ - extensions: extensions, - dl: dl, - id: id, - } - } -} - -func (t *tlsHandshakerWithExtensions) Handshake( - ctx context.Context, tcpConn net.Conn, tlsConfig *tls.Config) (model.TLSConn, error) { - tlsConn, err := netxlite.NewUTLSConn(tcpConn, tlsConfig, t.id) - runtimex.Assert(err == nil, "unexpected error when creating UTLSConn") - - if t.extensions != nil && len(t.extensions) != 0 { - runtimex.Try0(tlsConn.BuildHandshakeState()) - tlsConn.Extensions = append(tlsConn.Extensions, t.extensions...) - } - - if err := tlsConn.Handshake(); err != nil { - return nil, err - } - - return tlsConn, nil -} diff --git a/pkg/experiment/echcheck/utls_test.go b/pkg/experiment/echcheck/utls_test.go deleted file mode 100644 index 1fd5cfe0..00000000 --- a/pkg/experiment/echcheck/utls_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package echcheck - -import ( - "context" - "crypto/tls" - "errors" - "testing" - - "github.com/ooni/probe-engine/pkg/mocks" - "github.com/ooni/probe-engine/pkg/model" - utls "gitlab.com/yawning/utls.git" -) - -func TestTLSHandshakerWithExtension(t *testing.T) { - t.Run("when the TLS handshake fails", func(t *testing.T) { - thx := &tlsHandshakerWithExtensions{ - extensions: []utls.TLSExtension{}, - dl: model.DiscardLogger, - id: &utls.HelloChrome_70, - } - - expected := errors.New("mocked error") - tcpConn := &mocks.Conn{ - MockWrite: func(b []byte) (int, error) { - return 0, expected - }, - } - - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - - tlsConn, err := thx.Handshake(context.Background(), tcpConn, tlsConfig) - if !errors.Is(err, expected) { - t.Fatal(err) - } - if tlsConn != nil { - t.Fatal("expected nil tls conn") - } - }) -} diff --git a/pkg/model/http.go b/pkg/model/http.go index 258208ef..0fa61004 100644 --- a/pkg/model/http.go +++ b/pkg/model/http.go @@ -15,7 +15,7 @@ const ( // HTTPHeaderUserAgent is the User-Agent header used for measuring. The current header // is 40.98% of the browser population as of 2025-02-09 according to the // https://www.useragents.me/ webpage. - HTTPHeaderUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.3" + HTTPHeaderUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.10 Safari/605.1.1" ) // Additional strings used to report HTTP errors. They're currently only used by diff --git a/pkg/model/netx.go b/pkg/model/netx.go index db16ef87..a0436a2b 100644 --- a/pkg/model/netx.go +++ b/pkg/model/netx.go @@ -183,6 +183,10 @@ type HTTPSSvc struct { // IPv6 contains the IPv6 hints (which may be empty). IPv6 []string + + // Encrypted ClientHello config decoded from base64 to bytes + // (which may be empty). + Ech []byte } // MeasuringNetwork defines the constructors required for implementing OONI experiments. All diff --git a/pkg/netxlite/certifi.go b/pkg/netxlite/certifi.go index 5b832633..5514add6 100644 --- a/pkg/netxlite/certifi.go +++ b/pkg/netxlite/certifi.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2025-02-09 15:06:47.509775 +0530 IST m=+4.059531293 +// 2025-06-12 03:15:08.955569 +0530 IST m=+0.846147542 // https://curl.haxx.se/ca/cacert.pem package netxlite @@ -10,14 +10,14 @@ const pemcerts string = ` ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Dec 31 04:12:05 2024 GMT +## Certificate data from Mozilla as of: Tue May 20 03:12:02 2025 GMT ## ## Find updated versions here: https://curl.se/docs/caextract.html ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: -## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with @@ -25,76 +25,10 @@ const pemcerts string = ` ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: c99d6d3f8d3d4e47719ba2b648992f5b58b150128d3aca3c05c566d8dc98e116 +## SHA256: 8944ec6b572b577daee4fc681a425881f841ec2660e4cb5f0eee727f84620697 ## -GlobalSign Root CA -================== ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx -GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds -b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV -BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD -VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa -DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc -THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb -Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP -c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX -gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF -AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj -Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG -j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH -hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC -X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -Entrust.net Premium 2048 Secure Server CA -========================================= ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u -ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp -bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV -BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx -NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 -d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl -MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u -ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL -Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr -hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW -nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi -VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ -KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy -T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf -zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT -J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e -nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= ------END CERTIFICATE----- - -Baltimore CyberTrust Root -========================= ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE -ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li -ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC -SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs -dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME -uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB -UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C -G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 -XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr -l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI -VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB -BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh -cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 -hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa -Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H -RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- @@ -121,30 +55,6 @@ W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -Comodo AAA Services root -======================== ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS -R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg -TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw -MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl -c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV -BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG -C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs -i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW -Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH -Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK -Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f -BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl -cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz -LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm -7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z -8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C -12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - QuoVadis Root CA 2 ================== -----BEGIN CERTIFICATE----- @@ -211,78 +121,6 @@ vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -XRamp Global CA Root -==================== ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE -BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj -dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx -HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg -U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu -IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx -foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE -zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs -AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry -xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap -oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC -AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc -/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n -nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz -8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -Go Daddy Class 2 CA -=================== ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY -VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG -A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g -RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD -ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv -2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 -qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j -YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY -vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O -BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o -atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu -MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim -PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt -I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI -Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b -vZ8= ------END CERTIFICATE----- - -Starfield Class 2 CA -==================== ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc -U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo -MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG -A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG -SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY -bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ -JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm -epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN -F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF -MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f -hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo -bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g -QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs -afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM -PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD -KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 -QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- @@ -380,37 +218,6 @@ NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- -SwissSign Silver CA - G2 -======================== ------BEGIN CERTIFICATE----- -MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT -BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X -DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 -aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG -9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 -N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm -+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH -6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu -MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h -qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 -FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs -ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc -celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X -CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB -tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 -cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P -4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F -kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L -3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx -/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa -DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP -e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu -WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ -DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub -DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u ------END CERTIFICATE----- - SecureTrust CA ============== -----BEGIN CERTIFICATE----- @@ -3619,4 +3426,66 @@ fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= -----END CERTIFICATE----- +D-TRUST BR Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT +cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z +sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ +WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 +++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL +QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv +x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV +MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX +/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 +CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr +d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv +U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 +nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij +YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff +/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP +pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB +WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ +5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt +n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +D-TRUST EV Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 +sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ +MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf +vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM +lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 +YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 +7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo +nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa +QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF +AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS +Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 +QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD +UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V +4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo +dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi +TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi +S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ +HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L ++KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + ` diff --git a/pkg/netxlite/dnsdecoder.go b/pkg/netxlite/dnsdecoder.go index 9a4326b5..aa46931a 100644 --- a/pkg/netxlite/dnsdecoder.go +++ b/pkg/netxlite/dnsdecoder.go @@ -101,6 +101,7 @@ func (r *dnsResponse) DecodeHTTPS() (*model.HTTPSSvc, error) { ALPN: []string{}, // ensure it's not nil IPv4: []string{}, // ensure it's not nil IPv6: []string{}, // ensure it's not nil + Ech: []byte{}, // ensure it's not nil } for _, answer := range r.msg.Answer { switch avalue := answer.(type) { @@ -117,11 +118,13 @@ func (r *dnsResponse) DecodeHTTPS() (*model.HTTPSSvc, error) { for _, ip := range extv.Hint { out.IPv6 = append(out.IPv6, ip.String()) } + case *dns.SVCBECHConfig: + out.Ech = extv.ECH } } } } - if len(out.IPv4) <= 0 && len(out.IPv6) <= 0 { + if len(out.IPv4) <= 0 && len(out.IPv6) <= 0 && len(out.Ech) <= 0 { return nil, dnsDecoderWrapError(ErrOODNSNoAnswer) } return out, nil diff --git a/pkg/netxlite/errno.go b/pkg/netxlite/errno.go index 6ea3fa69..a0731350 100644 --- a/pkg/netxlite/errno.go +++ b/pkg/netxlite/errno.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.351437 +0530 IST m=+0.348009168 +// Generated: 2025-06-12 03:15:09.826777 +0530 IST m=+0.447703126 package netxlite diff --git a/pkg/netxlite/errno_darwin.go b/pkg/netxlite/errno_darwin.go index edfe65ab..864fca22 100644 --- a/pkg/netxlite/errno_darwin.go +++ b/pkg/netxlite/errno_darwin.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.004347 +0530 IST m=+0.000919585 +// Generated: 2025-06-12 03:15:09.379722 +0530 IST m=+0.000650417 package netxlite diff --git a/pkg/netxlite/errno_darwin_test.go b/pkg/netxlite/errno_darwin_test.go index b54ed3b7..1b726984 100644 --- a/pkg/netxlite/errno_darwin_test.go +++ b/pkg/netxlite/errno_darwin_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.216076 +0530 IST m=+0.212648751 +// Generated: 2025-06-12 03:15:09.693932 +0530 IST m=+0.314858751 package netxlite diff --git a/pkg/netxlite/errno_freebsd.go b/pkg/netxlite/errno_freebsd.go index ef47e9b7..d99bf9a5 100644 --- a/pkg/netxlite/errno_freebsd.go +++ b/pkg/netxlite/errno_freebsd.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.241296 +0530 IST m=+0.237869001 +// Generated: 2025-06-12 03:15:09.711141 +0530 IST m=+0.332067876 package netxlite diff --git a/pkg/netxlite/errno_freebsd_test.go b/pkg/netxlite/errno_freebsd_test.go index 0eff6362..ef5b3be1 100644 --- a/pkg/netxlite/errno_freebsd_test.go +++ b/pkg/netxlite/errno_freebsd_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.25973 +0530 IST m=+0.256302835 +// Generated: 2025-06-12 03:15:09.726329 +0530 IST m=+0.347256042 package netxlite diff --git a/pkg/netxlite/errno_linux.go b/pkg/netxlite/errno_linux.go index 6ac95132..0b240d67 100644 --- a/pkg/netxlite/errno_linux.go +++ b/pkg/netxlite/errno_linux.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.301605 +0530 IST m=+0.298177876 +// Generated: 2025-06-12 03:15:09.769979 +0530 IST m=+0.390905542 package netxlite diff --git a/pkg/netxlite/errno_linux_test.go b/pkg/netxlite/errno_linux_test.go index 5f6517cf..68462585 100644 --- a/pkg/netxlite/errno_linux_test.go +++ b/pkg/netxlite/errno_linux_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.315044 +0530 IST m=+0.311616585 +// Generated: 2025-06-12 03:15:09.785806 +0530 IST m=+0.406732167 package netxlite diff --git a/pkg/netxlite/errno_openbsd.go b/pkg/netxlite/errno_openbsd.go index 26c7d024..76c7b738 100644 --- a/pkg/netxlite/errno_openbsd.go +++ b/pkg/netxlite/errno_openbsd.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.274921 +0530 IST m=+0.271493960 +// Generated: 2025-06-12 03:15:09.74056 +0530 IST m=+0.361487292 package netxlite diff --git a/pkg/netxlite/errno_openbsd_test.go b/pkg/netxlite/errno_openbsd_test.go index 9a674745..fd3e9aa5 100644 --- a/pkg/netxlite/errno_openbsd_test.go +++ b/pkg/netxlite/errno_openbsd_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.289002 +0530 IST m=+0.285574835 +// Generated: 2025-06-12 03:15:09.755749 +0530 IST m=+0.376675917 package netxlite diff --git a/pkg/netxlite/errno_windows.go b/pkg/netxlite/errno_windows.go index a527196f..4a2f9435 100644 --- a/pkg/netxlite/errno_windows.go +++ b/pkg/netxlite/errno_windows.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.327284 +0530 IST m=+0.323856335 +// Generated: 2025-06-12 03:15:09.799972 +0530 IST m=+0.420898917 package netxlite diff --git a/pkg/netxlite/errno_windows_test.go b/pkg/netxlite/errno_windows_test.go index d8e5ebd9..e733d614 100644 --- a/pkg/netxlite/errno_windows_test.go +++ b/pkg/netxlite/errno_windows_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2025-02-09 15:06:48.338773 +0530 IST m=+0.335345085 +// Generated: 2025-06-12 03:15:09.812914 +0530 IST m=+0.433840584 package netxlite diff --git a/pkg/netxlite/resolvercore.go b/pkg/netxlite/resolvercore.go index 7d83e77e..ad4b15d9 100644 --- a/pkg/netxlite/resolvercore.go +++ b/pkg/netxlite/resolvercore.go @@ -10,6 +10,7 @@ import ( "fmt" "net" "net/http" + "strconv" "strings" "time" @@ -234,13 +235,34 @@ func (r *resolverIDNA) LookupHost(ctx context.Context, hostname string) ([]strin return r.Resolver.LookupHost(ctx, host) } +// HTTPS queries may be for either an IDNA-encodable domain or a +// Port Prefix Named domain where the portion following the port +// and service components is an IDNA-encodable domain. func (r *resolverIDNA) LookupHTTPS( ctx context.Context, domain string) (*model.HTTPSSvc, error) { - host, err := idnax.ToASCII(domain) + + idnable := domain + portPrefix := "" + if strings.HasPrefix(domain, "_") { + components := strings.Split(domain, ".") + port, err := strconv.Atoi(strings.TrimPrefix(components[0], "_")) + if err != nil { + return nil, fmt.Errorf("invalid domain: %s", domain) + } + if len(components) >= 2 && components[1] == "_https" && port >= 0 && port <= 65535 { + idnable = strings.Join(components[2:], ".") + portPrefix = components[0] + "." + components[1] + "." + } else { + return nil, fmt.Errorf("invalid domain: %s", domain) + } + } + + ldh, err := idnax.ToASCII(idnable) if err != nil { return nil, err } - return r.Resolver.LookupHTTPS(ctx, host) + query := portPrefix + ldh + return r.Resolver.LookupHTTPS(ctx, query) } func (r *resolverIDNA) Network() string { diff --git a/pkg/netxlite/resolvercore_test.go b/pkg/netxlite/resolvercore_test.go index 2b000815..a3b2d76e 100644 --- a/pkg/netxlite/resolvercore_test.go +++ b/pkg/netxlite/resolvercore_test.go @@ -653,6 +653,74 @@ func TestResolverIDNA(t *testing.T) { t.Fatal("expected no response here") } }) + + t.Run("with underscore prefixed HTTPS SVCB Record", func(t *testing.T) { + expected := &model.HTTPSSvc{ + ALPN: []string{"h3"}, + IPv4: []string{"1.1.1.1"}, + IPv6: []string{}, + } + r := &resolverIDNA{ + Resolver: &mocks.Resolver{ + MockLookupHTTPS: func(ctx context.Context, domain string) (*model.HTTPSSvc, error) { + if domain != "_12345._https.example.org" { + return nil, errors.New("passed invalid domain") + } + return expected, nil + }, + }, + } + ctx := context.Background() + https, err := r.LookupHTTPS(ctx, "_12345._https.example.org") + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(expected, https); diff != "" { + t.Fatal(diff) + } + }) + + t.Run("with underscore prefixed non-LDH HTTPS SVCB Record", func(t *testing.T) { + expected := &model.HTTPSSvc{ + ALPN: []string{"h3"}, + IPv4: []string{"1.1.1.1"}, + IPv6: []string{}, + } + r := &resolverIDNA{ + Resolver: &mocks.Resolver{ + MockLookupHTTPS: func(ctx context.Context, domain string) (*model.HTTPSSvc, error) { + if domain != "_12345._https.xn--d1acpjx3f.xn--p1ai" { + return nil, errors.New("passed invalid domain") + } + return expected, nil + }, + }, + } + ctx := context.Background() + https, err := r.LookupHTTPS(ctx, "_12345._https.яндекс.рф") + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(expected, https); diff != "" { + t.Fatal(diff) + } + }) + + t.Run("with invalid underscore-prefixed domain", func(t *testing.T) { + r := &resolverIDNA{Resolver: &mocks.Resolver{ + MockLookupHTTPS: func(ctx context.Context, domain string) (*model.HTTPSSvc, error) { + return nil, errors.New("should not happen") + }, + }} + ctx := context.Background() + https, err := r.LookupHTTPS(ctx, "_asdf._https.foo.bar") + if err == nil || !strings.HasPrefix(err.Error(), "invalid domain:") { + t.Fatal("not the error we expected") + } + if https != nil { + t.Fatal("expected no response here") + } + }) }) t.Run("Network", func(t *testing.T) { diff --git a/pkg/netxlite/resolverparallel.go b/pkg/netxlite/resolverparallel.go index 9cc9850b..b4cfcd7c 100644 --- a/pkg/netxlite/resolverparallel.go +++ b/pkg/netxlite/resolverparallel.go @@ -79,11 +79,16 @@ func (r *ParallelResolver) LookupHost(ctx context.Context, hostname string) ([]s func (r *ParallelResolver) LookupHTTPS( ctx context.Context, hostname string) (*model.HTTPSSvc, error) { encoder := &DNSEncoderMiekg{} + trace := ContextTraceOrDefault(ctx) query := encoder.Encode(hostname, dns.TypeHTTPS, r.Txp.RequiresPadding()) + started := trace.TimeNow() response, err := r.Txp.RoundTrip(ctx, query) + finished := trace.TimeNow() if err != nil { + trace.OnDNSRoundTripForLookupHost(started, r, query, response, []string{}, err, finished) return nil, err } + trace.OnDNSRoundTripForLookupHost(started, r, query, response, []string{}, err, finished) return response.DecodeHTTPS() } diff --git a/pkg/version/version.go b/pkg/version/version.go index 0684f0aa..1262b362 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -2,4 +2,4 @@ package version // Version is the ooniprobe version. -const Version = "3.25.0" +const Version = "3.26.0"