d5249a6cf7
This diff improves testing and increases coverage inside the ./internal/netxlite and ./internal/tracex packages. See https://github.com/ooni/probe/issues/2121
263 lines
6.2 KiB
Go
263 lines
6.2 KiB
Go
package tracex
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
|
)
|
|
|
|
func TestWrapTLSHandshaker(t *testing.T) {
|
|
var saver *Saver
|
|
thx := &mocks.TLSHandshaker{}
|
|
if saver.WrapTLSHandshaker(thx) != thx {
|
|
t.Fatal("unexpected result")
|
|
}
|
|
}
|
|
|
|
func TestTLSHandshakerSaver(t *testing.T) {
|
|
|
|
t.Run("Handshake", func(t *testing.T) {
|
|
checkStartEventFields := func(t *testing.T, value *EventValue) {
|
|
if value.Address != "8.8.8.8:443" {
|
|
t.Fatal("invalid Address")
|
|
}
|
|
if !value.NoTLSVerify {
|
|
t.Fatal("expected NoTLSVerify to be true")
|
|
}
|
|
if value.Proto != "tcp" {
|
|
t.Fatal("wrong protocol")
|
|
}
|
|
if diff := cmp.Diff(value.TLSNextProtos, []string{"h2"}); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
if value.TLSServerName != "dns.google" {
|
|
t.Fatal("invalid TLSServerName")
|
|
}
|
|
if value.Time.IsZero() {
|
|
t.Fatal("expected non zero time")
|
|
}
|
|
}
|
|
|
|
checkStartedEvent := func(t *testing.T, ev Event) {
|
|
if _, good := ev.(*EventTLSHandshakeStart); !good {
|
|
t.Fatal("invalid event type")
|
|
}
|
|
value := ev.Value()
|
|
checkStartEventFields(t, value)
|
|
}
|
|
|
|
checkDoneEventFieldsSuccess := func(t *testing.T, value *EventValue) {
|
|
if value.Duration <= 0 {
|
|
t.Fatal("expected non-zero duration")
|
|
}
|
|
if value.Err.IsNotNil() {
|
|
t.Fatal("expected no error here")
|
|
}
|
|
if value.TLSCipherSuite != "TLS_RSA_WITH_RC4_128_SHA" {
|
|
t.Fatal("invalid cipher suite")
|
|
}
|
|
if value.TLSNegotiatedProto != "h2" {
|
|
t.Fatal("invalid negotiated protocol")
|
|
}
|
|
if diff := cmp.Diff(value.TLSPeerCerts, [][]byte{{1, 2, 3, 4}}); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
if value.TLSVersion != "TLSv1.3" {
|
|
t.Fatal("invalid TLS version")
|
|
}
|
|
}
|
|
|
|
checkDoneEvent := func(t *testing.T, ev Event, fun func(t *testing.T, value *EventValue)) {
|
|
if _, good := ev.(*EventTLSHandshakeDone); !good {
|
|
t.Fatal("invalid event type")
|
|
}
|
|
value := ev.Value()
|
|
checkStartEventFields(t, value)
|
|
fun(t, value)
|
|
}
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
saver := &Saver{}
|
|
returnedConnState := tls.ConnectionState{
|
|
CipherSuite: tls.TLS_RSA_WITH_RC4_128_SHA,
|
|
NegotiatedProtocol: "h2",
|
|
PeerCertificates: []*x509.Certificate{{
|
|
Raw: []byte{1, 2, 3, 4},
|
|
}},
|
|
Version: tls.VersionTLS13,
|
|
}
|
|
returnedConn := &mocks.TLSConn{
|
|
MockConnectionState: func() tls.ConnectionState {
|
|
return returnedConnState
|
|
},
|
|
}
|
|
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
|
|
MockHandshake: func(ctx context.Context, conn net.Conn,
|
|
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
|
return returnedConn, returnedConnState, nil
|
|
},
|
|
})
|
|
ctx := context.Background()
|
|
tlsConfig := &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
NextProtos: []string{"h2"},
|
|
ServerName: "dns.google",
|
|
}
|
|
tcpConn := &mocks.Conn{
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockString: func() string {
|
|
return "8.8.8.8:443"
|
|
},
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if conn == nil {
|
|
t.Fatal("expected non-nil conn")
|
|
}
|
|
events := saver.Read()
|
|
if len(events) != 2 {
|
|
t.Fatal("expected two events")
|
|
}
|
|
checkStartedEvent(t, events[0])
|
|
checkDoneEvent(t, events[1], checkDoneEventFieldsSuccess)
|
|
})
|
|
|
|
checkDoneEventFieldsFailure := func(t *testing.T, value *EventValue) {
|
|
if value.Duration <= 0 {
|
|
t.Fatal("expected non-zero duration")
|
|
}
|
|
if value.Err.IsNil() {
|
|
t.Fatal("expected non-nil error here")
|
|
}
|
|
if value.TLSCipherSuite != "" {
|
|
t.Fatal("invalid TLS cipher suite")
|
|
}
|
|
if value.TLSNegotiatedProto != "" {
|
|
t.Fatal("invalid negotiated proto")
|
|
}
|
|
if len(value.TLSPeerCerts) > 0 {
|
|
t.Fatal("expected no peer certs")
|
|
}
|
|
if value.TLSVersion != "" {
|
|
t.Fatal("invalid TLS version")
|
|
}
|
|
}
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
saver := &Saver{}
|
|
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
|
|
MockHandshake: func(ctx context.Context, conn net.Conn,
|
|
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
|
return nil, tls.ConnectionState{}, expected
|
|
},
|
|
})
|
|
ctx := context.Background()
|
|
tlsConfig := &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
NextProtos: []string{"h2"},
|
|
ServerName: "dns.google",
|
|
}
|
|
tcpConn := &mocks.Conn{
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockString: func() string {
|
|
return "8.8.8.8:443"
|
|
},
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := saver.Read()
|
|
if len(events) != 2 {
|
|
t.Fatal("expected two events")
|
|
}
|
|
checkStartedEvent(t, events[0])
|
|
checkDoneEvent(t, events[1], checkDoneEventFieldsFailure)
|
|
})
|
|
})
|
|
}
|
|
|
|
func Test_tlsPeerCerts(t *testing.T) {
|
|
cert0 := &x509.Certificate{Raw: []byte{1, 2, 3, 4}}
|
|
type args struct {
|
|
state tls.ConnectionState
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want [][]byte
|
|
}{{
|
|
name: "no error",
|
|
args: args{
|
|
state: tls.ConnectionState{
|
|
PeerCertificates: []*x509.Certificate{cert0},
|
|
},
|
|
},
|
|
want: [][]byte{cert0.Raw},
|
|
}, {
|
|
name: "all empty",
|
|
args: args{},
|
|
want: nil,
|
|
}, {
|
|
name: "x509.HostnameError",
|
|
args: args{
|
|
state: tls.ConnectionState{},
|
|
err: x509.HostnameError{
|
|
Certificate: cert0,
|
|
},
|
|
},
|
|
want: [][]byte{cert0.Raw},
|
|
}, {
|
|
name: "x509.UnknownAuthorityError",
|
|
args: args{
|
|
state: tls.ConnectionState{},
|
|
err: x509.UnknownAuthorityError{
|
|
Cert: cert0,
|
|
},
|
|
},
|
|
want: [][]byte{cert0.Raw},
|
|
}, {
|
|
name: "x509.CertificateInvalidError",
|
|
args: args{
|
|
state: tls.ConnectionState{},
|
|
err: x509.CertificateInvalidError{
|
|
Cert: cert0,
|
|
},
|
|
},
|
|
want: [][]byte{cert0.Raw},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tlsPeerCerts(tt.args.state, tt.args.err)
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|