From 45a7aa975636a8de271e07904ccd076a953618b0 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Wed, 20 Sep 2023 20:33:42 +0200 Subject: [PATCH] BREAKING CHANGE: fix: pin certificates to hosts (#40) Before writing this diff, netem automatically generated the correct cert on the fly based on the given SNI or local addr. While this behavior has been okay so far, it turns out there is a set of tests we cannot write because of it. Namely, we cannot check whether we can connect to a given host using another SNI, because netem would generate a certificate for the provided SNI, which is not how the internet works. If I'm connecting to www.example.com with www.google.com as the SNI, in the internet the server would return a valid certificate for www.example.com, rather than for www.google.com. This diff rectifies netem's behavior by forcing the programmer to pin the certificate to a set of names and addresses when creating the server that needs such a certificate. Accordingly, we can stop using a forked google/martian/v3/mitm implementation and we can just roll out our own, which is still based on martian (fear not, not reinventing the wheel here!) but allows us to create a certificate with specific addresses and domain names pinned to it. While there, notice that there was code we were not using, such as stdlib.go, and that we also don't need in ooni/probe-cli, so we can definitely kill this piece of code. While there, don't be shy and make a bunch of constructors of the `Must` kind. They will panic on failure, which is fine in the netem context, because this library is meant to only be used when writing tests. Part of https://github.com/ooni/probe/issues/2531, because the tests I was trying to write belong to such an issue. --- ca.go | 210 ++++++++++++++++++++++++ ca_test.go | 142 ++++++++++++++++ cmd/calibrate/topology.go | 6 +- example_dpi_test.go | 15 +- example_loopback_test.go | 5 +- example_star_test.go | 7 +- go.mod | 3 - go.sum | 82 ---------- httpclient.go | 4 +- integration_test.go | 77 +++------ mitmx/mitmx.go | 329 -------------------------------------- mitmx/mitmx_test.go | 205 ------------------------ model.go | 5 +- ndt0.go | 16 +- net.go | 19 +-- stdlib.go | 82 ---------- tlsmitm.go | 80 --------- tlsmitm_test.go | 59 ------- topology.go | 65 +++----- topology_test.go | 7 +- unetstack.go | 35 ++-- 21 files changed, 447 insertions(+), 1006 deletions(-) create mode 100644 ca.go create mode 100644 ca_test.go delete mode 100644 mitmx/mitmx.go delete mode 100644 mitmx/mitmx_test.go delete mode 100644 stdlib.go delete mode 100644 tlsmitm.go delete mode 100644 tlsmitm_test.go 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..2c910c6 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)) @@ -102,12 +102,12 @@ func newTopologyPPP( dnsConfig *netem.DNSConfig, ) (topologyCloser, *netem.UNetStack, *netem.UNetStack) { // create a PPP topology - topology := netem.Must1(netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( clientAddress, serverAddress, log.Log, clientLink, - )) + ) // create DNS server using the server stack _ = netem.Must1(netem.NewDNSServer(log.Log, topology.Server, serverAddress, dnsConfig)) 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..261d3ea 100644 --- a/integration_test.go +++ b/integration_test.go @@ -44,15 +44,12 @@ func TestLinkLatency(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } defer topology.Close() // connect N times and estimate the RTT by sending a SYN and measuring @@ -106,15 +103,12 @@ func TestLinkPLR(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } defer topology.Close() // make sure we have a deadline bound context @@ -133,6 +127,7 @@ func TestLinkPLR(t *testing.T) { ready, serverErrorCh, false, + "ndt0.local", ) // await for the NDT0 server to be listening @@ -203,10 +198,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 +262,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 +308,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 @@ -386,15 +375,12 @@ func TestLinkPCAP(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } // connect N times and estimate the RTT by sending a SYN and measuring // the time required to get back the RST|ACK segment. @@ -512,15 +498,12 @@ func TestDPITCPThrottleForSNI(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } defer topology.Close() // make sure we have a deadline bound context @@ -547,6 +530,8 @@ func TestDPITCPThrottleForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.local", + "ndt0.xyz", ) // await for the NDT0 server to be listening @@ -658,10 +643,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 +687,8 @@ func TestDPITCPResetForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -824,10 +808,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 +852,8 @@ func TestDPITCPCloseConnectionForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -987,10 +970,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 +1014,7 @@ func TestDPITCPCloseConnectionForServerEndpoint(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", ) // await for the NDT0 server to be listening @@ -1161,10 +1142,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 @@ -1293,15 +1271,12 @@ func TestDPITCPDropForSNI(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } defer topology.Close() // make sure we have a deadline bound context @@ -1328,6 +1303,8 @@ func TestDPITCPDropForSNI(t *testing.T) { ready, serverErrorCh, true, + "ndt0.xyz", + "ndt0.local", ) // await for the NDT0 server to be listening @@ -1456,15 +1433,12 @@ func TestDPITCPDropForEndpoint(t *testing.T) { // create a point-to-point topology, which consists of a single // [Link] connecting two userspace network stacks. - topology, err := netem.NewPPPTopology( + topology := netem.MustNewPPPTopology( "10.0.0.2", "10.0.0.1", log.Log, lc, ) - if err != nil { - t.Fatal(err) - } defer topology.Close() // make sure we have a deadline bound context @@ -1623,10 +1597,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..2793f3a 100644 --- a/topology.go +++ b/topology.go @@ -28,8 +28,8 @@ type PPPTopology struct { link *Link } -// NewPPPTopology creates a [PPPTopology]. Use the Close method to -// shutdown the link created by this topology. +// MustNewPPPTopology creates a [PPPTopology]. Use the Close method +// to shutdown the link created by this topology. // // Arguments: // @@ -42,43 +42,33 @@ type PPPTopology struct { // - MTU is the MTU to use (1500 is a good MTU value); // // - lc describes the link characteristics. -func NewPPPTopology( +func MustNewPPPTopology( clientAddress string, serverAddress string, logger Logger, lc *LinkConfig, -) (*PPPTopology, error) { - // create configuration for the MITM - mitmCfg, err := NewTLSMITMConfig() - if err != nil { - return nil, err - } +) *PPPTopology { + // create configuration for the CA + CA := MustNewCA() // create the client TCP/IP userspace stack const MTU = 1500 - client, err := NewUNetStack( + client := Must1(NewUNetStack( logger, MTU, clientAddress, - mitmCfg, + CA, serverAddress, - ) - if err != nil { - return nil, err - } + )) // create the server TCP/IP userspace stack - server, err := NewUNetStack( + server := Must1(NewUNetStack( logger, MTU, serverAddress, - mitmCfg, + CA, "0.0.0.0", - ) - if err != nil { - client.Close() - return nil, err - } + )) // connect the two stacks using a link link := NewLink(logger, client, server, lc) @@ -89,7 +79,7 @@ func NewPPPTopology( closeOnce: sync.Once{}, link: link, } - return t, nil + return t } // Close closes all the hosts and links allocated by the topology @@ -109,6 +99,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 +111,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 +118,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 +159,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 +184,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.