diff --git a/ca.go b/ca.go new file mode 100644 index 0000000..644eab2 --- /dev/null +++ b/ca.go @@ -0,0 +1,210 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package netem + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "net" + "time" +) + +// caMaxSerialNumber is the upper boundary that is used to create unique serial +// numbers for the certificate. This can be any unsigned integer up to 20 +// bytes (2^(8*20)-1). +var caMaxSerialNumber = big.NewInt(0).SetBytes(bytes.Repeat([]byte{255}, 20)) + +// caMustNewAuthority creates a new CA certificate and associated private key or PANICS. +// +// This code is derived from github.com/google/martian/v3. +// +// SPDX-License-Identifier: Apache-2.0. +func caMustNewAuthority(name, organization string, validity time.Duration, + timeNow func() time.Time) (*x509.Certificate, *rsa.PrivateKey) { + priv := Must1(rsa.GenerateKey(rand.Reader, 2048)) + pub := priv.Public() + + // Subject Key Identifier support for end entity certificate. + // https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2) + pkixpub := Must1(x509.MarshalPKIXPublicKey(pub)) + h := sha1.New() + h.Write(pkixpub) + keyID := h.Sum(nil) + + // TODO(bassosimone): keep a map of used serial numbers to avoid potentially + // reusing a serial multiple times. + serial := Must1(rand.Int(rand.Reader, caMaxSerialNumber)) + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: name, + Organization: []string{organization}, + }, + SubjectKeyId: keyID, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + NotBefore: timeNow().Add(-validity), + NotAfter: timeNow().Add(validity), + DNSNames: []string{name}, + IsCA: true, + } + + raw := Must1(x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)) + + // Parse certificate bytes so that we have a leaf certificate. + x509c := Must1(x509.ParseCertificate(raw)) + + return x509c, priv +} + +// CA is a certification authority. +// +// The zero value is invalid, please use [NewCA] to construct. +// +// This code is derived from github.com/google/martian/v3. +// +// SPDX-License-Identifier: Apache-2.0. +type CA struct { + ca *x509.Certificate + capriv any + keyID []byte + org string + priv *rsa.PrivateKey + validity time.Duration +} + +// NewCA creates a new certification authority. +func MustNewCA() *CA { + return MustNewCAWithTimeNow(time.Now) +} + +// MustNewCA is like [NewCA] but uses a custom [time.Now] func. +// +// This code is derived from github.com/google/martian/v3. +// +// SPDX-License-Identifier: Apache-2.0. +func MustNewCAWithTimeNow(timeNow func() time.Time) *CA { + ca, privateKey := caMustNewAuthority("jafar", "OONI", 24*time.Hour, timeNow) + + roots := x509.NewCertPool() + roots.AddCert(ca) + + priv := Must1(rsa.GenerateKey(rand.Reader, 2048)) + pub := priv.Public() + + // Subject Key Identifier support for end entity certificate. + // https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2) + pkixpub := Must1(x509.MarshalPKIXPublicKey(pub)) + h := sha1.New() + h.Write(pkixpub) + keyID := h.Sum(nil) + + return &CA{ + ca: ca, + capriv: privateKey, + priv: priv, + keyID: keyID, + validity: time.Hour, + org: "OONI Netem CA", + } +} + +// CertPool returns an [x509.CertPool] using the given [*CA]. +func (c *CA) CertPool() *x509.CertPool { + pool := x509.NewCertPool() + pool.AddCert(c.ca) + return pool +} + +// MustNewCert creates a new certificate for the given common name or PANICS. +// +// The common name and the extra names could contain domain names or IP addresses. +// +// For example: +// +// - www.example.com +// +// - 10.0.0.1 +// +// - ::1 +// +// are all valid values you can pass as common name or extra names. +func (c *CA) MustNewCert(commonName string, extraNames ...string) *tls.Certificate { + return c.MustNewCertWithTimeNow(time.Now, commonName, extraNames...) +} + +// MustNewCertWithTimeNow is like [MustNewCert] but uses a custom [time.Now] func. +// +// This code is derived from github.com/google/martian/v3. +// +// SPDX-License-Identifier: Apache-2.0. +func (c *CA) MustNewCertWithTimeNow(timeNow func() time.Time, commonName string, extraNames ...string) *tls.Certificate { + serial := Must1(rand.Int(rand.Reader, caMaxSerialNumber)) + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: commonName, + Organization: []string{c.org}, + }, + SubjectKeyId: c.keyID, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + NotBefore: timeNow().Add(-c.validity), + NotAfter: timeNow().Add(c.validity), + } + + allNames := []string{commonName} + allNames = append(allNames, extraNames...) + for _, name := range allNames { + if ip := net.ParseIP(name); ip != nil { + tmpl.IPAddresses = append(tmpl.IPAddresses, ip) + } else { + tmpl.DNSNames = append(tmpl.DNSNames, name) + } + } + + raw := Must1(x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.priv.Public(), c.capriv)) + + // Parse certificate bytes so that we have a leaf certificate. + x509c := Must1(x509.ParseCertificate(raw)) + + tlsc := &tls.Certificate{ + Certificate: [][]byte{raw, c.ca.Raw}, + PrivateKey: c.priv, + Leaf: x509c, + } + + return tlsc +} + +// MustServerTLSConfig generates a server-side [*tls.Config] that uses the given [*CA] and +// a generated certificate for the given common name and extra names. +// +// See [CA.MustNewCert] documentation for more details about what common name and extra names should be. +func (ca *CA) MustServerTLSConfig(commonName string, extraNames ...string) *tls.Config { + return &tls.Config{ + Certificates: []tls.Certificate{*ca.MustNewCert(commonName, extraNames...)}, + } +} diff --git a/ca_test.go b/ca_test.go new file mode 100644 index 0000000..ac7fcac --- /dev/null +++ b/ca_test.go @@ -0,0 +1,142 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package netem + +import ( + "context" + "crypto/tls" + "crypto/x509" + "net" + "net/http" + "reflect" + "strings" + "testing" + "time" + + "github.com/apex/log" + "github.com/google/go-cmp/cmp" +) + +func TestCAMustNewCert(t *testing.T) { + ca := MustNewCA() + + tlsc := ca.MustNewCert("example.com", "www.example.com", "10.0.0.1", "10.0.0.2") + + if tlsc.Certificate == nil { + t.Error("tlsc.Certificate: got nil, want certificate bytes") + } + if tlsc.PrivateKey == nil { + t.Error("tlsc.PrivateKey: got nil, want private key") + } + + x509c := tlsc.Leaf + if x509c == nil { + t.Fatal("x509c: got nil, want *x509.Certificate") + } + + if got := x509c.SerialNumber; got.Cmp(caMaxSerialNumber) >= 0 { + t.Errorf("x509c.SerialNumber: got %v, want <= MaxSerialNumber", got) + } + if got, want := x509c.Subject.CommonName, "example.com"; got != want { + t.Errorf("X509c.Subject.CommonName: got %q, want %q", got, want) + } + if err := x509c.VerifyHostname("example.com"); err != nil { + t.Errorf("x509c.VerifyHostname(%q): got %v, want no error", "example.com", err) + } + + if got, want := x509c.Subject.Organization, []string{"OONI Netem CA"}; !reflect.DeepEqual(got, want) { + t.Errorf("x509c.Subject.Organization: got %v, want %v", got, want) + } + + if got := x509c.SubjectKeyId; got == nil { + t.Error("x509c.SubjectKeyId: got nothing, want key ID") + } + if !x509c.BasicConstraintsValid { + t.Error("x509c.BasicConstraintsValid: got false, want true") + } + + if got, want := x509c.KeyUsage, x509.KeyUsageKeyEncipherment; got&want == 0 { + t.Error("x509c.KeyUsage: got nothing, want to include x509.KeyUsageKeyEncipherment") + } + if got, want := x509c.KeyUsage, x509.KeyUsageDigitalSignature; got&want == 0 { + t.Error("x509c.KeyUsage: got nothing, want to include x509.KeyUsageDigitalSignature") + } + + want := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + if got := x509c.ExtKeyUsage; !reflect.DeepEqual(got, want) { + t.Errorf("x509c.ExtKeyUsage: got %v, want %v", got, want) + } + + if got, want := x509c.DNSNames, []string{"example.com", "www.example.com"}; !reflect.DeepEqual(got, want) { + t.Errorf("x509c.DNSNames: got %v, want %v", got, want) + } + + if diff := cmp.Diff(x509c.IPAddresses, []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("10.0.0.2")}); diff != "" { + t.Errorf(diff) + } + + before := time.Now().Add(-2 * time.Hour) + if got := x509c.NotBefore; before.After(got) { + t.Errorf("x509c.NotBefore: got %v, want after %v", got, before) + } + + after := time.Now().Add(2 * time.Hour) + if got := x509c.NotAfter; !after.After(got) { + t.Errorf("x509c.NotAfter: got %v, want before %v", got, want) + } +} + +func TestCAWeCanGenerateAnExpiredCertificate(t *testing.T) { + topology := MustNewStarTopology(log.Log) + defer topology.Close() + + serverStack := Must1(topology.AddHost("10.0.0.1", "0.0.0.0", &LinkConfig{})) + clientStack := Must1(topology.AddHost("10.0.0.2", "0.0.0.0", &LinkConfig{})) + + serverAddr := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 443} + serverListener := Must1(serverStack.ListenTCP("tcp", serverAddr)) + + serverServer := &http.Server{ + Handler: http.NewServeMux(), + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{ + *serverStack.CA().MustNewCertWithTimeNow(func() time.Time { + return time.Date(2017, time.July, 17, 0, 0, 0, 0, time.UTC) + }, + "www.example.com", + "10.0.0.1", + ), + }, + }, + } + go serverServer.ServeTLS(serverListener, "", "") + defer serverServer.Close() + + tcpConn, err := clientStack.DialContext(context.Background(), "tcp", "10.0.0.1:443") + if err != nil { + t.Fatal(err) + } + defer tcpConn.Close() + + tlsClientConfig := &tls.Config{ + RootCAs: clientStack.DefaultCertPool(), + ServerName: "www.example.com", + } + tlsConn := tls.Client(tcpConn, tlsClientConfig) + err = tlsConn.HandshakeContext(context.Background()) + if err == nil || !strings.Contains(err.Error(), "x509: certificate has expired or is not yet valid") { + t.Fatal("unexpected error", err) + } +} diff --git a/cmd/calibrate/topology.go b/cmd/calibrate/topology.go index 96f728c..f2fa949 100644 --- a/cmd/calibrate/topology.go +++ b/cmd/calibrate/topology.go @@ -64,7 +64,7 @@ func newTopologyStar( dnsConfig *netem.DNSConfig, ) (topologyCloser, *netem.UNetStack, *netem.UNetStack) { // create an empty topology - topology := netem.Must1(netem.NewStarTopology(log.Log)) + topology := netem.MustNewStarTopology(log.Log) // add the client to the topology clientStack := netem.Must1(topology.AddHost(clientAddress, serverAddress, clientLink)) diff --git a/example_dpi_test.go b/example_dpi_test.go index f6dd016..4ce91a8 100644 --- a/example_dpi_test.go +++ b/example_dpi_test.go @@ -16,10 +16,7 @@ import ( // This example shows how to use DPI to provoke an EOF when you see an offending string. func Example_dpiCloseConnectionForString() { // Create a star topology for our hosts. - topology, err := netem.NewStarTopology(&netem.NullLogger{}) - if err != nil { - log.Fatal(err) - } + topology := netem.MustNewStarTopology(&netem.NullLogger{}) defer topology.Close() // Create DPI engine in the client link @@ -150,10 +147,7 @@ func Example_dpiCloseConnectionForString() { // This example shows how to use DPI to drop traffic after you see a given string, func Example_dpiDropTrafficForString() { // Create a star topology for our hosts. - topology, err := netem.NewStarTopology(&netem.NullLogger{}) - if err != nil { - log.Fatal(err) - } + topology := netem.MustNewStarTopology(&netem.NullLogger{}) defer topology.Close() // Create DPI engine in the client link @@ -287,10 +281,7 @@ func Example_dpiDropTrafficForString() { // This example shows how to use DPI to spoof a blockpage for a string func Example_dpiSpoofBlockpageForString() { // Create a star topology for our hosts. - topology, err := netem.NewStarTopology(&netem.NullLogger{}) - if err != nil { - log.Fatal(err) - } + topology := netem.MustNewStarTopology(&netem.NullLogger{}) defer topology.Close() // Create DPI engine in the client link diff --git a/example_loopback_test.go b/example_loopback_test.go index 570083a..2ed3250 100644 --- a/example_loopback_test.go +++ b/example_loopback_test.go @@ -17,10 +17,7 @@ import ( // itself, therefore, the DPI does not have any effect. func Example_dpiDoesNotAffectLoopbackTraffic() { // Create a star topology for our hosts. - topology, err := netem.NewStarTopology(&netem.NullLogger{}) - if err != nil { - log.Fatal(err) - } + topology := netem.MustNewStarTopology(&netem.NullLogger{}) defer topology.Close() // Create DPI engine in the wwwStack link diff --git a/example_star_test.go b/example_star_test.go index cf07c92..0fd3062 100644 --- a/example_star_test.go +++ b/example_star_test.go @@ -15,10 +15,7 @@ import ( // client to fetch a very important message from the server. func Example_starTopologyHTTPSAndDNS() { // Create a star topology for our hosts. - topology, err := netem.NewStarTopology(&netem.NullLogger{}) - if err != nil { - log.Fatal(err) - } + topology := netem.MustNewStarTopology(&netem.NullLogger{}) defer topology.Close() // Add client stack to topology. Note that we don't need to @@ -80,7 +77,7 @@ func Example_starTopologyHTTPSAndDNS() { } httpsServer := &http.Server{ Handler: mux, - TLSConfig: httpsServerStack.ServerTLSConfig(), // allow for TLS MITM + TLSConfig: httpsServerStack.CA().MustServerTLSConfig("tyrell.wellick.name"), } go httpsServer.ServeTLS(httpsListener, "", "") // empty string: use .TLSConfig defer httpsServer.Close() diff --git a/go.mod b/go.mod index f5fc7a7..9d4b7df 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.20 require ( github.com/apex/log v1.9.0 github.com/google/gopacket v1.1.19 - github.com/google/martian/v3 v3.3.2 github.com/miekg/dns v1.1.54 golang.org/x/crypto v0.9.0 gvisor.dev/gvisor v0.0.0-20230603040744-5c9219dedd33 @@ -21,10 +20,8 @@ require ( ) require ( - github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13 // indirect github.com/google/go-cmp v0.5.9 github.com/montanaflynn/stats v0.7.0 golang.org/x/mod v0.10.0 // indirect - golang.org/x/text v0.9.0 // indirect golang.org/x/tools v0.9.3 // indirect ) diff --git a/go.sum b/go.sum index 1bebe0f..f77b80b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= @@ -7,53 +5,21 @@ github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13 h1:yztvEbaW/qZGubeP7+Lug7PXl7NBfilUK6mw3jq25gQ= -github.com/golang/protobuf v1.5.3-0.20210916003710-5d5e8c018a13/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= @@ -77,7 +43,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= @@ -87,7 +52,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -105,30 +69,18 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -137,45 +89,13 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.53.0-dev.0.20230123225046-4075ef07c5d5 h1:qq9WB3Dez2tMAKtZTVtZsZSmTkDgPeXx+FRPt5kLEkM= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -188,5 +108,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20230603040744-5c9219dedd33 h1:64QentohifmKGeTgJCHilDgfmQVuYE45fsaS9psJ3zY= gvisor.dev/gvisor v0.0.0-20230603040744-5c9219dedd33/go.mod h1:sQuqOkxbfJq/GS2uSnqHphtXclHyk/ZrAGhZBxxsq6g= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/httpclient.go b/httpclient.go index 6573842..30e0985 100644 --- a/httpclient.go +++ b/httpclient.go @@ -5,7 +5,6 @@ package netem // import ( - "crypto/tls" "net/http" ) @@ -14,7 +13,6 @@ type HTTPUnderlyingNetwork interface { UnderlyingNetwork IPAddress() string Logger() Logger - ServerTLSConfig() *tls.Config } // NewHTTPTransport creates a new [http.Transport] using an [UnderlyingNetwork]. @@ -23,7 +21,7 @@ type HTTPUnderlyingNetwork interface { // // - DialContext to call a dialing function that will eventually use [stack.DialContext]; // -// - TLSClientConfig to use the stack's [MITMConfig]; +// - DialTLSContext to use the stack's [MITMConfig]; // // - ForceAttemptHTTP2 to force enabling the HTTP/2 protocol. func NewHTTPTransport(stack HTTPUnderlyingNetwork) *http.Transport { diff --git a/integration_test.go b/integration_test.go index a901851..58339ea 100644 --- a/integration_test.go +++ b/integration_test.go @@ -133,6 +133,7 @@ func TestLinkPLR(t *testing.T) { ready, serverErrorCh, false, + "ndt0.local", ) // await for the NDT0 server to be listening @@ -203,10 +204,7 @@ func TestRoutingWorksDNS(t *testing.T) { // create a star topology, which consists of a single // [Router] connected to arbitrary hosts - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // attach a client to the topology @@ -270,10 +268,7 @@ func TestRoutingWorksHTTPS(t *testing.T) { // create a star topology, which consists of a single // [Router] connected to arbitrary hosts - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // attach a client to the topology @@ -319,7 +314,7 @@ func TestRoutingWorksHTTPS(t *testing.T) { t.Fatal(err) } httpServer := &http.Server{ - TLSConfig: clientStack.ServerTLSConfig(), + TLSConfig: serverStack.CA().MustServerTLSConfig("example.local", "10.0.0.1"), Handler: mux, } go httpServer.ServeTLS(listener, "", "") // empty strings mean: use TLSConfig @@ -547,6 +542,8 @@ func TestDPITCPThrottleForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.local", + "ndt0.xyz", ) // await for the NDT0 server to be listening @@ -658,10 +655,7 @@ func TestDPITCPResetForSNI(t *testing.T) { // Create a star topology. We MUST create such a topology because // the rule we're using REQUIRES a router in the path. - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // make sure we add delay to the router<->server link because @@ -705,6 +699,8 @@ func TestDPITCPResetForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -824,10 +820,7 @@ func TestDPITCPCloseConnectionForSNI(t *testing.T) { // Create a star topology. We MUST create such a topology because // the rule we're using REQUIRES a router in the path. - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // make sure we add delay to the router<->server link because @@ -871,6 +864,8 @@ func TestDPITCPCloseConnectionForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -987,10 +982,7 @@ func TestDPITCPCloseConnectionForServerEndpoint(t *testing.T) { // Create a star topology. We MUST create such a topology because // the rule we're using REQUIRES a router in the path. - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // make sure we add delay to the router<->server link because @@ -1034,6 +1026,7 @@ func TestDPITCPCloseConnectionForServerEndpoint(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", ) // await for the NDT0 server to be listening @@ -1161,10 +1154,7 @@ func TestDPISpoofDNSResponse(t *testing.T) { // Create a star topology. We MUST create such a topology because // the rule we're using REQUIRES a router in the path. - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // make sure we add delay to the router<->server link because @@ -1328,6 +1318,8 @@ func TestDPITCPDropForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -1623,10 +1615,7 @@ func TestDPITCPResetForString(t *testing.T) { // create a star topology, required because the router will send // back the spoofed traffic to us - topology, err := netem.NewStarTopology(log.Log) - if err != nil { - t.Fatal(err) - } + topology := netem.MustNewStarTopology(log.Log) defer topology.Close() // create server stack diff --git a/mitmx/mitmx.go b/mitmx/mitmx.go deleted file mode 100644 index c9953e3..0000000 --- a/mitmx/mitmx.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2015 Google Inc. All rights reserved. -// -// 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. - -// Package mitmx provides tooling for MITMing TLS connections. It provides -// tooling to create CA certs and generate TLS configs that can be used to MITM -// a TLS connection with a provided CA certificate. -// -// This package is a fork of the mitm package in github.com/google/martian/v3. -package mitmx - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "errors" - "math/big" - "net" - "net/http" - "sync" - "time" - - "github.com/google/martian/v3/h2" - "github.com/google/martian/v3/log" -) - -// MaxSerialNumber is the upper boundary that is used to create unique serial -// numbers for the certificate. This can be any unsigned integer up to 20 -// bytes (2^(8*20)-1). -var MaxSerialNumber = big.NewInt(0).SetBytes(bytes.Repeat([]byte{255}, 20)) - -// Config is a set of configuration values that are used to build TLS configs -// capable of MITM. -type Config struct { - ca *x509.Certificate - capriv interface{} - priv *rsa.PrivateKey - keyID []byte - validity time.Duration - org string - h2Config *h2.Config - roots *x509.CertPool - skipVerify bool - handshakeErrorCallback func(*http.Request, error) - - certmu sync.RWMutex - certs map[string]*tls.Certificate -} - -// NewAuthority creates a new CA certificate and associated -// private key. -func NewAuthority(name, organization string, validity time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) { - return NewAuthorityWithTimeNow(name, organization, validity, time.Now) -} - -// NewAuthorityWithTimeNow is like NewAuthority but allows to customize the time.Now func -func NewAuthorityWithTimeNow( - name, organization string, validity time.Duration, timeNow func() time.Time) (*x509.Certificate, *rsa.PrivateKey, error) { - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - pub := priv.Public() - - // Subject Key Identifier support for end entity certificate. - // https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2) - pkixpub, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, nil, err - } - h := sha1.New() - h.Write(pkixpub) - keyID := h.Sum(nil) - - // TODO: keep a map of used serial numbers to avoid potentially reusing a - // serial multiple times. - serial, err := rand.Int(rand.Reader, MaxSerialNumber) - if err != nil { - return nil, nil, err - } - - tmpl := &x509.Certificate{ - SerialNumber: serial, - Subject: pkix.Name{ - CommonName: name, - Organization: []string{organization}, - }, - SubjectKeyId: keyID, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - NotBefore: timeNow().Add(-validity), - NotAfter: timeNow().Add(validity), - DNSNames: []string{name}, - IsCA: true, - } - - raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) - if err != nil { - return nil, nil, err - } - - // Parse certificate bytes so that we have a leaf certificate. - x509c, err := x509.ParseCertificate(raw) - if err != nil { - return nil, nil, err - } - - return x509c, priv, nil -} - -// NewConfig creates a MITM config using the CA certificate and -// private key to generate on-the-fly certificates. -func NewConfig(ca *x509.Certificate, privateKey interface{}) (*Config, error) { - roots := x509.NewCertPool() - roots.AddCert(ca) - - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - pub := priv.Public() - - // Subject Key Identifier support for end entity certificate. - // https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2) - pkixpub, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, err - } - h := sha1.New() - h.Write(pkixpub) - keyID := h.Sum(nil) - - return &Config{ - ca: ca, - capriv: privateKey, - priv: priv, - keyID: keyID, - validity: time.Hour, - org: "Martian Proxy", - certs: make(map[string]*tls.Certificate), - roots: roots, - }, nil -} - -// SetValidity sets the validity window around the current time that the -// certificate is valid for. -func (c *Config) SetValidity(validity time.Duration) { - c.validity = validity -} - -// SkipTLSVerify skips the TLS certification verification check. -func (c *Config) SkipTLSVerify(skip bool) { - c.skipVerify = skip -} - -// SetOrganization sets the organization of the certificate. -func (c *Config) SetOrganization(org string) { - c.org = org -} - -// SetH2Config configures processing of HTTP/2 streams. -func (c *Config) SetH2Config(h2Config *h2.Config) { - c.h2Config = h2Config -} - -// H2Config returns the current HTTP/2 configuration. -func (c *Config) H2Config() *h2.Config { - return c.h2Config -} - -// SetHandshakeErrorCallback sets the handshakeErrorCallback function. -func (c *Config) SetHandshakeErrorCallback(cb func(*http.Request, error)) { - c.handshakeErrorCallback = cb -} - -// HandshakeErrorCallback calls the handshakeErrorCallback function in this -// Config, if it is non-nil. Request is the connect request that this handshake -// is being executed through. -func (c *Config) HandshakeErrorCallback(r *http.Request, err error) { - if c.handshakeErrorCallback != nil { - c.handshakeErrorCallback(r, err) - } -} - -// TLS returns a *tls.Config that will generate certificates on-the-fly using -// the SNI extension in the TLS ClientHello. -func (c *Config) TLS() *tls.Config { - return &tls.Config{ - InsecureSkipVerify: c.skipVerify, - GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - if clientHello.ServerName == "" { - return nil, errors.New("mitm: SNI not provided, failed to build certificate") - } - - return c.cert(clientHello.ServerName) - }, - NextProtos: []string{"http/1.1"}, - } -} - -// TLSForHost returns a *tls.Config that will generate certificates on-the-fly -// using SNI from the connection, or fall back to the provided hostname. -func (c *Config) TLSForHost(hostname string) *tls.Config { - nextProtos := []string{"http/1.1"} - if c.h2AllowedHost(hostname) { - nextProtos = []string{"h2", "http/1.1"} - } - return &tls.Config{ - InsecureSkipVerify: c.skipVerify, - GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - host := clientHello.ServerName - if host == "" { - host = hostname - } - - return c.cert(host) - }, - NextProtos: nextProtos, - } -} - -func (c *Config) h2AllowedHost(host string) bool { - return c.h2Config != nil && - c.h2Config.AllowedHostsFilter != nil && - c.h2Config.AllowedHostsFilter(host) -} - -func (c *Config) cert(hostname string) (*tls.Certificate, error) { - // Remove the port if it exists. - host, _, err := net.SplitHostPort(hostname) - if err == nil { - hostname = host - } - - c.certmu.RLock() - tlsc, ok := c.certs[hostname] - c.certmu.RUnlock() - - if ok { - log.Debugf("mitm: cache hit for %s", hostname) - - // Check validity of the certificate for hostname match, expiry, etc. In - // particular, if the cached certificate has expired, create a new one. - if _, err := tlsc.Leaf.Verify(x509.VerifyOptions{ - DNSName: hostname, - Roots: c.roots, - }); err == nil { - return tlsc, nil - } - - log.Debugf("mitm: invalid certificate in cache for %s", hostname) - } - - log.Debugf("mitm: cache miss for %s", hostname) - - tlsc, err = c.NewCertWithoutCacheWithTimeNow(hostname, time.Now) - if err != nil { - return nil, err - } - - c.certmu.Lock() - c.certs[hostname] = tlsc - c.certmu.Unlock() - - return tlsc, nil -} - -// NewCertWithoutCacheWithTimeNow is the most fundamental building block for building a certificate -// that completely bypasses caching and allows for setting a custom time.Now func. -func (c *Config) NewCertWithoutCacheWithTimeNow(hostname string, timeNow func() time.Time) (*tls.Certificate, error) { - serial, err := rand.Int(rand.Reader, MaxSerialNumber) - if err != nil { - return nil, err - } - - tmpl := &x509.Certificate{ - SerialNumber: serial, - Subject: pkix.Name{ - CommonName: hostname, - Organization: []string{c.org}, - }, - SubjectKeyId: c.keyID, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - NotBefore: timeNow().Add(-c.validity), - NotAfter: timeNow().Add(c.validity), - } - - if ip := net.ParseIP(hostname); ip != nil { - tmpl.IPAddresses = []net.IP{ip} - } else { - tmpl.DNSNames = []string{hostname} - } - - raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.priv.Public(), c.capriv) - if err != nil { - return nil, err - } - - // Parse certificate bytes so that we have a leaf certificate. - x509c, err := x509.ParseCertificate(raw) - if err != nil { - return nil, err - } - - tlsc := &tls.Certificate{ - Certificate: [][]byte{raw, c.ca.Raw}, - PrivateKey: c.priv, - Leaf: x509c, - } - - return tlsc, nil -} diff --git a/mitmx/mitmx_test.go b/mitmx/mitmx_test.go deleted file mode 100644 index 0563068..0000000 --- a/mitmx/mitmx_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2015 Google Inc. All rights reserved. -// -// 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. - -package mitmx - -import ( - "crypto/tls" - "crypto/x509" - "net" - "reflect" - "testing" - "time" -) - -func TestMITM(t *testing.T) { - ca, priv, err := NewAuthority("martian.proxy", "Martian Authority", 24*time.Hour) - if err != nil { - t.Fatalf("NewAuthority(): got %v, want no error", err) - } - - c, err := NewConfig(ca, priv) - if err != nil { - t.Fatalf("NewConfig(): got %v, want no error", err) - } - - c.SetValidity(20 * time.Hour) - c.SetOrganization("Test Organization") - - protos := []string{"http/1.1"} - - conf := c.TLS() - if got := conf.NextProtos; !reflect.DeepEqual(got, protos) { - t.Errorf("conf.NextProtos: got %v, want %v", got, protos) - } - if conf.InsecureSkipVerify { - t.Error("conf.InsecureSkipVerify: got true, want false") - } - - // Simulate a TLS connection without SNI. - clientHello := &tls.ClientHelloInfo{ - ServerName: "", - } - - if _, err := conf.GetCertificate(clientHello); err == nil { - t.Fatal("conf.GetCertificate(): got nil, want error") - } - - // Simulate a TLS connection with SNI. - clientHello.ServerName = "example.com" - - tlsc, err := conf.GetCertificate(clientHello) - if err != nil { - t.Fatalf("conf.GetCertificate(): got %v, want no error", err) - } - - x509c := tlsc.Leaf - if got, want := x509c.Subject.CommonName, "example.com"; got != want { - t.Errorf("x509c.Subject.CommonName: got %q, want %q", got, want) - } - - c.SkipTLSVerify(true) - - conf = c.TLSForHost("example.com") - if got := conf.NextProtos; !reflect.DeepEqual(got, protos) { - t.Errorf("conf.NextProtos: got %v, want %v", got, protos) - } - if !conf.InsecureSkipVerify { - t.Error("conf.InsecureSkipVerify: got false, want true") - } - - // Set SNI, takes precedence over host. - clientHello.ServerName = "google.com" - tlsc, err = conf.GetCertificate(clientHello) - if err != nil { - t.Fatalf("conf.GetCertificate(): got %v, want no error", err) - } - - x509c = tlsc.Leaf - if got, want := x509c.Subject.CommonName, "google.com"; got != want { - t.Errorf("x509c.Subject.CommonName: got %q, want %q", got, want) - } - - // Reset SNI to fallback to hostname. - clientHello.ServerName = "" - tlsc, err = conf.GetCertificate(clientHello) - if err != nil { - t.Fatalf("conf.GetCertificate(): got %v, want no error", err) - } - - x509c = tlsc.Leaf - if got, want := x509c.Subject.CommonName, "example.com"; got != want { - t.Errorf("x509c.Subject.CommonName: got %q, want %q", got, want) - } -} - -func TestCert(t *testing.T) { - ca, priv, err := NewAuthority("martian.proxy", "Martian Authority", 24*time.Hour) - if err != nil { - t.Fatalf("NewAuthority(): got %v, want no error", err) - } - - c, err := NewConfig(ca, priv) - if err != nil { - t.Fatalf("NewConfig(): got %v, want no error", err) - } - - tlsc, err := c.cert("example.com") - if err != nil { - t.Fatalf("c.cert(%q): got %v, want no error", "example.com:8080", err) - } - - if tlsc.Certificate == nil { - t.Error("tlsc.Certificate: got nil, want certificate bytes") - } - if tlsc.PrivateKey == nil { - t.Error("tlsc.PrivateKey: got nil, want private key") - } - - x509c := tlsc.Leaf - if x509c == nil { - t.Fatal("x509c: got nil, want *x509.Certificate") - } - - if got := x509c.SerialNumber; got.Cmp(MaxSerialNumber) >= 0 { - t.Errorf("x509c.SerialNumber: got %v, want <= MaxSerialNumber", got) - } - if got, want := x509c.Subject.CommonName, "example.com"; got != want { - t.Errorf("X509c.Subject.CommonName: got %q, want %q", got, want) - } - if err := x509c.VerifyHostname("example.com"); err != nil { - t.Errorf("x509c.VerifyHostname(%q): got %v, want no error", "example.com", err) - } - - if got, want := x509c.Subject.Organization, []string{"Martian Proxy"}; !reflect.DeepEqual(got, want) { - t.Errorf("x509c.Subject.Organization: got %v, want %v", got, want) - } - - if got := x509c.SubjectKeyId; got == nil { - t.Error("x509c.SubjectKeyId: got nothing, want key ID") - } - if !x509c.BasicConstraintsValid { - t.Error("x509c.BasicConstraintsValid: got false, want true") - } - - if got, want := x509c.KeyUsage, x509.KeyUsageKeyEncipherment; got&want == 0 { - t.Error("x509c.KeyUsage: got nothing, want to include x509.KeyUsageKeyEncipherment") - } - if got, want := x509c.KeyUsage, x509.KeyUsageDigitalSignature; got&want == 0 { - t.Error("x509c.KeyUsage: got nothing, want to include x509.KeyUsageDigitalSignature") - } - - want := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} - if got := x509c.ExtKeyUsage; !reflect.DeepEqual(got, want) { - t.Errorf("x509c.ExtKeyUsage: got %v, want %v", got, want) - } - - if got, want := x509c.DNSNames, []string{"example.com"}; !reflect.DeepEqual(got, want) { - t.Errorf("x509c.DNSNames: got %v, want %v", got, want) - } - - before := time.Now().Add(-2 * time.Hour) - if got := x509c.NotBefore; before.After(got) { - t.Errorf("x509c.NotBefore: got %v, want after %v", got, before) - } - - after := time.Now().Add(2 * time.Hour) - if got := x509c.NotAfter; !after.After(got) { - t.Errorf("x509c.NotAfter: got %v, want before %v", got, want) - } - - // Retrieve cached certificate. - tlsc2, err := c.cert("example.com") - if err != nil { - t.Fatalf("c.cert(%q): got %v, want no error", "example.com", err) - } - if tlsc != tlsc2 { - t.Error("tlsc2: got new certificate, want cached certificate") - } - - // TLS certificate for IP. - tlsc, err = c.cert("10.0.0.1:8227") - if err != nil { - t.Fatalf("c.cert(%q): got %v, want no error", "10.0.0.1:8227", err) - } - x509c = tlsc.Leaf - - if got, want := len(x509c.IPAddresses), 1; got != want { - t.Fatalf("len(x509c.IPAddresses): got %d, want %d", got, want) - } - - if got, want := x509c.IPAddresses[0], net.ParseIP("10.0.0.1"); !got.Equal(want) { - t.Fatalf("x509c.IPAddresses: got %v, want %v", got, want) - } -} diff --git a/model.go b/model.go index e2a06dd..974a847 100644 --- a/model.go +++ b/model.go @@ -154,8 +154,11 @@ type UDPLikeConn interface { // UnderlyingNetwork replaces for functions in the [net] package. type UnderlyingNetwork interface { + // CA returns the CA we're using. + CA() *CA + // DefaultCertPool returns the underlying cert pool to be used. - DefaultCertPool() (*x509.CertPool, error) + DefaultCertPool() *x509.CertPool // DialContext dials a TCP or UDP connection. Unlike [net.DialContext], this // function does not implement dialing when address contains a domain. diff --git a/ndt0.go b/ndt0.go index daed7f0..180acc8 100644 --- a/ndt0.go +++ b/ndt0.go @@ -103,7 +103,7 @@ func (ps *NDT0PerformanceSample) CSVRecord(pcapfile string, rtt time.Duration, p // we close when we're done running. func RunNDT0Client( ctx context.Context, - stack NetUnderlyingNetwork, + stack UnderlyingNetwork, serverAddr string, logger Logger, TLS bool, @@ -227,16 +227,19 @@ func RunNDT0Client( // - errorch is where we post the overall result of this function (we // will post a nil value in case of success); // -// - TLS controls whether we should use TLS. +// - TLS controls whether we should use TLS; +// +// - serverNames contains the SNIs to add to the certificate (TLS only). func RunNDT0Server( ctx context.Context, - stack NetUnderlyingNetwork, + stack UnderlyingNetwork, serverIPAddr net.IP, serverPort int, logger Logger, ready chan<- net.Listener, errorch chan<- error, TLS bool, + serverNames ...string, ) { // create buffer with random data buffer := make([]byte, 65535) @@ -245,11 +248,16 @@ func RunNDT0Server( return } + // generate a config for the given SNI and for the given IP addr + tlsConfig := stack.CA().MustServerTLSConfig(serverIPAddr.String(), serverNames...) + // conditionally use TLS ns := &Net{stack} listeners := map[bool]func(network string, addr *net.TCPAddr) (net.Listener, error){ false: ns.ListenTCP, - true: ns.ListenTLS, + true: func(network string, addr *net.TCPAddr) (net.Listener, error) { + return ns.ListenTLS(network, addr, tlsConfig) + }, } // listen for an incoming client connection diff --git a/net.go b/net.go index 0790e49..57faeeb 100644 --- a/net.go +++ b/net.go @@ -14,17 +14,11 @@ import ( "time" ) -// NetUnderlyingNetwork is the [UNetStack] used by a [Net]. -type NetUnderlyingNetwork interface { - UnderlyingNetwork - ServerTLSConfig() *tls.Config -} - // Net is a drop-in replacement for the [net] package. The zero // value is invalid; please init all the MANDATORY fields. type Net struct { // Stack is the MANDATORY underlying stack. - Stack NetUnderlyingNetwork + Stack UnderlyingNetwork } // ErrDial contains all the errors occurred during a [DialContext] operation. @@ -104,7 +98,7 @@ func (n *Net) DialTLSContext(ctx context.Context, network, address string) (net. return nil, err } config := &tls.Config{ - RootCAs: Must1(n.Stack.DefaultCertPool()), + RootCAs: n.Stack.DefaultCertPool(), NextProtos: nil, // TODO(bassosimone): automatically generate the right ALPN ServerName: hostname, } @@ -158,12 +152,13 @@ func (n *Net) ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error) // ListenTLS is a replacement for [tls.Listen] that uses the underlying // stack's TLS MITM capabilities during the TLS handshake. -func (n *Net) ListenTLS(network string, laddr *net.TCPAddr) (net.Listener, error) { +func (n *Net) ListenTLS(network string, laddr *net.TCPAddr, config *tls.Config) (net.Listener, error) { listener, err := n.ListenTCP(network, laddr) if err != nil { return nil, err } lw := &netListenerTLS{ + config: config, listener: listener, stack: n.Stack, } @@ -172,8 +167,9 @@ func (n *Net) ListenTLS(network string, laddr *net.TCPAddr) (net.Listener, error // netListenerTLS is a TLS listener. type netListenerTLS struct { + config *tls.Config listener net.Listener - stack NetUnderlyingNetwork + stack UnderlyingNetwork } var _ net.Listener = &netListenerTLS{} @@ -184,8 +180,7 @@ func (lw *netListenerTLS) Accept() (net.Conn, error) { if err != nil { return nil, err } - config := lw.stack.ServerTLSConfig() - tc := tls.Server(conn, config) + tc := tls.Server(conn, lw.config) // make sure there is a maximum timeout for the handshake ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() diff --git a/stdlib.go b/stdlib.go deleted file mode 100644 index 084a6b4..0000000 --- a/stdlib.go +++ /dev/null @@ -1,82 +0,0 @@ -package netem - -// -// Stdlib-based implementation of [UnderlyingNetwork] -// - -import ( - "context" - "crypto/x509" - "net" -) - -// Stdlib implements [UnderlyingNetwork] using the Go stdlib. The zero -// value of this structure is ready to use. -type Stdlib struct { - // Dialer is the OPTIONAL [net.Dialer] to use. - Dialer *net.Dialer - - // Resolver is the OPTIONAL [net.Resolver] to use. - Resolver *net.Resolver -} - -var _ UnderlyingNetwork = &Stdlib{} - -// DefaultCertPool implements UnderlyingNetwork -func (s *Stdlib) DefaultCertPool() (*x509.CertPool, error) { - return x509.SystemCertPool() -} - -// DialContext implements UnderlyingNetwork -func (s *Stdlib) DialContext(ctx context.Context, network string, address string) (net.Conn, error) { - // Implementation note: reject domain names like our Gvisor - // based counterpart does, so we are consistent. - host, _, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - if net.ParseIP(host) == nil { - return nil, ErrNotIPAddress - } - return s.dialer().DialContext(ctx, network, address) -} - -// GetaddrinfoLookupANY implements UnderlyingNetwork -func (s *Stdlib) GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error) { - addrs, err := s.resolver().LookupHost(ctx, domain) - return addrs, "", err -} - -// GetaddrinfoResolverNetwork implements UnderlyingNetwork -func (s *Stdlib) GetaddrinfoResolverNetwork() string { - return "unknown" -} - -// ListenTCP implements UnderlyingNetwork -func (s *Stdlib) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error) { - return net.ListenTCP(network, addr) -} - -// ListenUDP implements UnderlyingNetwork -func (s *Stdlib) ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error) { - return net.ListenUDP(network, addr) -} - -// DefaultDialer is the default [net.Dialer] used by [Stdlib]. -var DefaultDialer = &net.Dialer{} - -// dialer returns a suitable [net.Dialer]. -func (s *Stdlib) dialer() *net.Dialer { - if s.Dialer != nil { - return s.Dialer - } - return DefaultDialer -} - -// resolver returns a suitable [net.Resolver]. -func (s *Stdlib) resolver() *net.Resolver { - if s.Resolver != nil { - return s.Resolver - } - return net.DefaultResolver -} diff --git a/tlsmitm.go b/tlsmitm.go deleted file mode 100644 index d2de7b3..0000000 --- a/tlsmitm.go +++ /dev/null @@ -1,80 +0,0 @@ -package netem - -// -// TLS: MITM configuration -// - -import ( - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "time" - - mitm "github.com/ooni/netem/mitmx" -) - -// TLSMITMConfig contains configuration for TLS MITM operations. You MUST use the -// [NewMITMConfig] factory to create a new instance. You will need to pass this -// instance to [NewGVisorStack] so that all the [GvisorStack] can communicate with -// each other using the same underlying (fake) root CA pool. -// -// The zero value of this struct is invalid; please, use [NewTLSMITMConfig]. -type TLSMITMConfig struct { - // Cert is the fake CA certificate for MITM. - Cert *x509.Certificate - - // Config is the MITM Config to generate certificates on the fly. - Config *mitm.Config - - // Key is the private Key that signed the mitmCert. - Key *rsa.PrivateKey -} - -// NewTLSMITMConfig creates a new [MITMConfig]. -func NewTLSMITMConfig() (*TLSMITMConfig, error) { - cert, key, err := mitm.NewAuthority("jafar", "OONI", 24*time.Hour) - if err != nil { - return nil, err - } - config, err := mitm.NewConfig(cert, key) - if err != nil { - return nil, err - } - mitmConfig := &TLSMITMConfig{ - Cert: cert, - Config: config, - Key: key, - } - return mitmConfig, nil -} - -// CertPool returns an [x509.CertPool] using the given [MITMConfig]. -func (c *TLSMITMConfig) CertPool() (*x509.CertPool, error) { - pool := x509.NewCertPool() - pool.AddCert(c.Cert) - return pool, nil -} - -// TLSConfig returns a *tls.Config that will generate certificates on-the-fly using -// the SNI extension in the TLS ClientHello, or the remote server's IP as a fallback SNI. -func (c *TLSMITMConfig) TLSConfig() *tls.Config { - return &tls.Config{ - InsecureSkipVerify: false, - GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - martianConfig := c.Config.TLSForHost(tlsAddrFromClientHello(clientHello)) - return martianConfig.GetCertificate(clientHello) - }, - NextProtos: []string{"http/1.1"}, - } -} - -// tlsAddrFromClientHello extracts the server addr from the ClientHelloInfo struct. This fixes -// cases where we have a fake server listening on, say, 8.8.8.8, and the client attempts to -// connect to the https://8.8.8.8/ URL without using any SNI. -func tlsAddrFromClientHello(clientHello *tls.ClientHelloInfo) string { - addr := clientHello.Conn.LocalAddr() - if addr == nil { - return "" - } - return addr.String() -} diff --git a/tlsmitm_test.go b/tlsmitm_test.go deleted file mode 100644 index 8c662d1..0000000 --- a/tlsmitm_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package netem - -import ( - "context" - "crypto/tls" - "net" - "net/http" - "strings" - "testing" - "time" - - "github.com/apex/log" -) - -func TestMITMWeCanGenerateAnExpiredCertificate(t *testing.T) { - topology := Must1(NewStarTopology(log.Log)) - defer topology.Close() - - serverStack := Must1(topology.AddHost("10.0.0.1", "0.0.0.0", &LinkConfig{})) - clientStack := Must1(topology.AddHost("10.0.0.2", "0.0.0.0", &LinkConfig{})) - - serverAddr := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 443} - serverListener := Must1(serverStack.ListenTCP("tcp", serverAddr)) - - serverServer := &http.Server{ - Handler: http.NewServeMux(), - TLSConfig: &tls.Config{ - GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { - // obtain the underlying MITM mechanism and force a long time in the past as - // the certificate generation time for testing - config := topology.TLSMITMConfig() - return config.Config.NewCertWithoutCacheWithTimeNow( - chi.ServerName, - func() time.Time { - return time.Date(2017, time.July, 17, 0, 0, 0, 0, time.UTC) - }, - ) - }, - }, - } - go serverServer.ServeTLS(serverListener, "", "") - defer serverServer.Close() - - tcpConn, err := clientStack.DialContext(context.Background(), "tcp", "10.0.0.1:443") - if err != nil { - t.Fatal(err) - } - defer tcpConn.Close() - - tlsClientConfig := &tls.Config{ - RootCAs: Must1(clientStack.TLSMITMConfig().CertPool()), - ServerName: "www.example.com", - } - tlsConn := tls.Client(tcpConn, tlsClientConfig) - err = tlsConn.HandshakeContext(context.Background()) - if err == nil || !strings.Contains(err.Error(), "x509: certificate has expired or is not yet valid") { - t.Fatal("unexpected error", err) - } -} diff --git a/topology.go b/topology.go index 616d3eb..740ecc0 100644 --- a/topology.go +++ b/topology.go @@ -48,11 +48,8 @@ func NewPPPTopology( logger Logger, lc *LinkConfig, ) (*PPPTopology, error) { - // create configuration for the MITM - mitmCfg, err := NewTLSMITMConfig() - if err != nil { - return nil, err - } + // create configuration for the CA + CA := MustNewCA() // create the client TCP/IP userspace stack const MTU = 1500 @@ -60,7 +57,7 @@ func NewPPPTopology( logger, MTU, clientAddress, - mitmCfg, + CA, serverAddress, ) if err != nil { @@ -72,7 +69,7 @@ func NewPPPTopology( logger, MTU, serverAddress, - mitmCfg, + CA, "0.0.0.0", ) if err != nil { @@ -109,6 +106,9 @@ type StarTopology struct { // addresses tracks the already-added addresses addresses map[string]int + // ca is the CA. + ca *CA + // closeOnce allows to have a "once" semantics for Close closeOnce sync.Once @@ -118,9 +118,6 @@ type StarTopology struct { // logger is the logger to use logger Logger - // mitm is the TLS MITM configuration - mitm *TLSMITMConfig - // mtu is the MTU to use mtu uint32 @@ -128,26 +125,19 @@ type StarTopology struct { router *Router } -// NewStarTopology constructs a new, empty [StarTopology] consisting +// MustNewStarTopology constructs a new, empty [StarTopology] consisting // of a [Router] sitting in the middle. Once you have the [StarTopology] // you can now add hosts using [AddHost], [AddHTTPServer], etc. -func NewStarTopology(logger Logger) (*StarTopology, error) { - mitmCfg, err := NewTLSMITMConfig() - if err != nil { - return nil, err - } - - t := &StarTopology{ +func MustNewStarTopology(logger Logger) *StarTopology { + return &StarTopology{ addresses: map[string]int{}, + ca: MustNewCA(), closeOnce: sync.Once{}, links: []*Link{}, logger: logger, - mitm: mitmCfg, mtu: 1500, router: NewRouter(logger), } - - return t, nil } // ErrDuplicateAddr indicates that an address has already been added to a topology. @@ -176,7 +166,7 @@ func (t *StarTopology) AddHost( if t.addresses[hostAddress] > 0 { return nil, fmt.Errorf("%w: %s", ErrDuplicateAddr, hostAddress) } - host, err := NewUNetStack(t.logger, t.mtu, hostAddress, t.mitm, resolverAddress) + host, err := NewUNetStack(t.logger, t.mtu, hostAddress, t.ca, resolverAddress) if err != nil { return nil, err } @@ -201,7 +191,7 @@ func (t *StarTopology) Close() error { return nil } -// TLSMITMConfig exposes the [TLSMITMConfig]. -func (t *StarTopology) TLSMITMConfig() *TLSMITMConfig { - return t.mitm +// CA exposes the [*CA]. +func (t *StarTopology) CA() *CA { + return t.ca } diff --git a/topology_test.go b/topology_test.go index a7eccf2..5374650 100644 --- a/topology_test.go +++ b/topology_test.go @@ -8,10 +8,7 @@ import ( func TestStartTopology(t *testing.T) { t.Run("AddHost", func(t *testing.T) { t.Run("we cannot add the same address more than once", func(t *testing.T) { - topology, err := NewStarTopology(&NullLogger{}) - if err != nil { - t.Fatal(err) - } + topology := MustNewStarTopology(&NullLogger{}) // it should be possible to add an host once if _, err := topology.AddHost("1.2.3.4", "0.0.0.0", &LinkConfig{}); err != nil { @@ -19,7 +16,7 @@ func TestStartTopology(t *testing.T) { } // the second time, it should fail - _, err = topology.AddHost("1.2.3.4", "0.0.0.0", &LinkConfig{}) + _, err := topology.AddHost("1.2.3.4", "0.0.0.0", &LinkConfig{}) if !errors.Is(err, ErrDuplicateAddr) { t.Fatal("not the error we expected", err) } diff --git a/unetstack.go b/unetstack.go index bab9cb4..bef91d0 100644 --- a/unetstack.go +++ b/unetstack.go @@ -6,7 +6,6 @@ package netem import ( "context" - "crypto/tls" "crypto/x509" "net" "net/netip" @@ -34,12 +33,12 @@ import ( // Use [UNetStack.NIC] to obtain a [NIC] to read and write the [Frames] // produced by using the network stack as the [UnderlyingNetwork]. type UNetStack struct { + // ca is the underlying CA. + ca *CA + // ns is the GVisor network stack. ns *gvisorStack - // mitmConfig allows generating X.509 certificates on the fly. - mitmConfig *TLSMITMConfig - // resoAddr is the resolver IPv4 address. resoAddr netip.Addr } @@ -68,7 +67,7 @@ func NewUNetStack( logger Logger, MTU uint32, stackAddress string, - cfg *TLSMITMConfig, + ca *CA, resolverAddress string, ) (*UNetStack, error) { // parse the stack address @@ -97,21 +96,16 @@ func NewUNetStack( // fill and return the network stack stack := &UNetStack{ - ns: ns, - mitmConfig: cfg, - resoAddr: resolverAddr, + ca: ca, + ns: ns, + resoAddr: resolverAddr, } return stack, nil } -// CACert implements TLSMITMProvider. -func (gs *UNetStack) CACert() *x509.Certificate { - return gs.mitmConfig.Cert -} - -// TLSMITMConfig exposes the underlying [TLSMITMConfig]. -func (gs *UNetStack) TLSMITMConfig() *TLSMITMConfig { - return gs.mitmConfig +// CA implements UnderlyingNetwork. +func (gs *UNetStack) CA() *CA { + return gs.ca } // Logger implements HTTPUnderlyingNetwork. @@ -119,11 +113,6 @@ func (gs *UNetStack) Logger() Logger { return gs.ns.logger } -// ServerTLSConfig returns the [tls.Config] we should use on the server side. -func (gs *UNetStack) ServerTLSConfig() *tls.Config { - return gs.mitmConfig.TLSConfig() -} - // FrameAvailable implements NIC func (gs *UNetStack) FrameAvailable() <-chan any { return gs.ns.FrameAvailable() @@ -160,8 +149,8 @@ func (gs *UNetStack) Close() error { } // DefaultCertPool implements UnderlyingNetwork. -func (gs *UNetStack) DefaultCertPool() (*x509.CertPool, error) { - return gs.mitmConfig.CertPool() +func (gs *UNetStack) DefaultCertPool() *x509.CertPool { + return gs.ca.CertPool() } // DialContext implements UnderlyingNetwork.