ooni-probe-cli/internal/tracex/tls_test.go
Simone Basso d5249a6cf7
chore: improve testing and increase coverage (#794)
This diff improves testing and increases coverage inside the
./internal/netxlite and ./internal/tracex packages.

See https://github.com/ooni/probe/issues/2121
2022-06-04 14:58:48 +02:00

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)
}
})
}
}