Since encryption messes up the packets, the wrongly retained packets look like noise and cause application protocol errors or panics in the SSH library. This normally triggers very rarely: the mandatory key exchange doesn't have parallel writes, so this failure condition would be setup on the first key exchange, take effect only after the second key exchange. Fortunately, the tests against openssh exercise this. This change adds also adds a unittest. Fixes #18850. Change-Id: I656c8b94bfb265831daa118f4d614a2f0c65d2af Reviewed-on: https://go-review.googlesource.com/36056 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
528 lines
12 KiB
Go
528 lines
12 KiB
Go
// Copyright 2013 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
type testChecker struct {
|
|
calls []string
|
|
}
|
|
|
|
func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
|
if dialAddr == "bad" {
|
|
return fmt.Errorf("dialAddr is bad")
|
|
}
|
|
|
|
if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil {
|
|
return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr)
|
|
}
|
|
|
|
t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal()))
|
|
|
|
return nil
|
|
}
|
|
|
|
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
|
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
|
// a write.)
|
|
func netPipe() (net.Conn, net.Conn, error) {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer listener.Close()
|
|
c1, err := net.Dial("tcp", listener.Addr().String())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
c2, err := listener.Accept()
|
|
if err != nil {
|
|
c1.Close()
|
|
return nil, nil, err
|
|
}
|
|
|
|
return c1, c2, nil
|
|
}
|
|
|
|
// noiseTransport inserts ignore messages to check that the read loop
|
|
// and the key exchange filters out these messages.
|
|
type noiseTransport struct {
|
|
keyingTransport
|
|
}
|
|
|
|
func (t *noiseTransport) writePacket(p []byte) error {
|
|
ignore := []byte{msgIgnore}
|
|
if err := t.keyingTransport.writePacket(ignore); err != nil {
|
|
return err
|
|
}
|
|
debug := []byte{msgDebug, 1, 2, 3}
|
|
if err := t.keyingTransport.writePacket(debug); err != nil {
|
|
return err
|
|
}
|
|
|
|
return t.keyingTransport.writePacket(p)
|
|
}
|
|
|
|
func addNoiseTransport(t keyingTransport) keyingTransport {
|
|
return &noiseTransport{t}
|
|
}
|
|
|
|
// handshakePair creates two handshakeTransports connected with each
|
|
// other. If the noise argument is true, both transports will try to
|
|
// confuse the other side by sending ignore and debug messages.
|
|
func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) {
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var trC, trS keyingTransport
|
|
|
|
trC = newTransport(a, rand.Reader, true)
|
|
trS = newTransport(b, rand.Reader, false)
|
|
if noise {
|
|
trC = addNoiseTransport(trC)
|
|
trS = addNoiseTransport(trS)
|
|
}
|
|
clientConf.SetDefaults()
|
|
|
|
v := []byte("version")
|
|
client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr())
|
|
|
|
serverConf := &ServerConfig{}
|
|
serverConf.AddHostKey(testSigners["ecdsa"])
|
|
serverConf.AddHostKey(testSigners["rsa"])
|
|
serverConf.SetDefaults()
|
|
server = newServerTransport(trS, v, v, serverConf)
|
|
|
|
if err := server.waitSession(); err != nil {
|
|
return nil, nil, fmt.Errorf("server.waitSession: %v", err)
|
|
}
|
|
if err := client.waitSession(); err != nil {
|
|
return nil, nil, fmt.Errorf("client.waitSession: %v", err)
|
|
}
|
|
|
|
return client, server, nil
|
|
}
|
|
|
|
func TestHandshakeBasic(t *testing.T) {
|
|
if runtime.GOOS == "plan9" {
|
|
t.Skip("see golang.org/issue/7237")
|
|
}
|
|
|
|
checker := &syncChecker{
|
|
waitCall: make(chan int, 10),
|
|
called: make(chan int, 10),
|
|
}
|
|
|
|
checker.waitCall <- 1
|
|
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
|
if err != nil {
|
|
t.Fatalf("handshakePair: %v", err)
|
|
}
|
|
|
|
defer trC.Close()
|
|
defer trS.Close()
|
|
|
|
// Let first kex complete normally.
|
|
<-checker.called
|
|
|
|
clientDone := make(chan int, 0)
|
|
gotHalf := make(chan int, 0)
|
|
const N = 20
|
|
|
|
go func() {
|
|
defer close(clientDone)
|
|
// Client writes a bunch of stuff, and does a key
|
|
// change in the middle. This should not confuse the
|
|
// handshake in progress. We do this twice, so we test
|
|
// that the packet buffer is reset correctly.
|
|
for i := 0; i < N; i++ {
|
|
p := []byte{msgRequestSuccess, byte(i)}
|
|
if err := trC.writePacket(p); err != nil {
|
|
t.Fatalf("sendPacket: %v", err)
|
|
}
|
|
if (i % 10) == 5 {
|
|
<-gotHalf
|
|
// halfway through, we request a key change.
|
|
trC.requestKeyExchange()
|
|
|
|
// Wait until we can be sure the key
|
|
// change has really started before we
|
|
// write more.
|
|
<-checker.called
|
|
}
|
|
if (i % 10) == 7 {
|
|
// write some packets until the kex
|
|
// completes, to test buffering of
|
|
// packets.
|
|
checker.waitCall <- 1
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Server checks that client messages come in cleanly
|
|
i := 0
|
|
err = nil
|
|
for ; i < N; i++ {
|
|
var p []byte
|
|
p, err = trS.readPacket()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if (i % 10) == 5 {
|
|
gotHalf <- 1
|
|
}
|
|
|
|
want := []byte{msgRequestSuccess, byte(i)}
|
|
if bytes.Compare(p, want) != 0 {
|
|
t.Errorf("message %d: got %v, want %v", i, p, want)
|
|
}
|
|
}
|
|
<-clientDone
|
|
if err != nil && err != io.EOF {
|
|
t.Fatalf("server error: %v", err)
|
|
}
|
|
if i != N {
|
|
t.Errorf("received %d messages, want 10.", i)
|
|
}
|
|
|
|
close(checker.called)
|
|
if _, ok := <-checker.called; ok {
|
|
// If all went well, we registered exactly 2 key changes: one
|
|
// that establishes the session, and one that we requested
|
|
// additionally.
|
|
t.Fatalf("got another host key checks after 2 handshakes")
|
|
}
|
|
}
|
|
|
|
func TestForceFirstKex(t *testing.T) {
|
|
// like handshakePair, but must access the keyingTransport.
|
|
checker := &testChecker{}
|
|
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
|
a, b, err := netPipe()
|
|
if err != nil {
|
|
t.Fatalf("netPipe: %v", err)
|
|
}
|
|
|
|
var trC, trS keyingTransport
|
|
|
|
trC = newTransport(a, rand.Reader, true)
|
|
|
|
// This is the disallowed packet:
|
|
trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
|
|
|
|
// Rest of the setup.
|
|
trS = newTransport(b, rand.Reader, false)
|
|
clientConf.SetDefaults()
|
|
|
|
v := []byte("version")
|
|
client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
|
|
|
|
serverConf := &ServerConfig{}
|
|
serverConf.AddHostKey(testSigners["ecdsa"])
|
|
serverConf.AddHostKey(testSigners["rsa"])
|
|
serverConf.SetDefaults()
|
|
server := newServerTransport(trS, v, v, serverConf)
|
|
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
// We setup the initial key exchange, but the remote side
|
|
// tries to send serviceRequestMsg in cleartext, which is
|
|
// disallowed.
|
|
|
|
if err := server.waitSession(); err == nil {
|
|
t.Errorf("server first kex init should reject unexpected packet")
|
|
}
|
|
}
|
|
|
|
func TestHandshakeAutoRekeyWrite(t *testing.T) {
|
|
checker := &syncChecker{
|
|
called: make(chan int, 10),
|
|
waitCall: nil,
|
|
}
|
|
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
|
clientConf.RekeyThreshold = 500
|
|
trC, trS, err := handshakePair(clientConf, "addr", false)
|
|
if err != nil {
|
|
t.Fatalf("handshakePair: %v", err)
|
|
}
|
|
defer trC.Close()
|
|
defer trS.Close()
|
|
|
|
input := make([]byte, 251)
|
|
input[0] = msgRequestSuccess
|
|
|
|
done := make(chan int, 1)
|
|
const numPacket = 5
|
|
go func() {
|
|
defer close(done)
|
|
j := 0
|
|
for ; j < numPacket; j++ {
|
|
if p, err := trS.readPacket(); err != nil {
|
|
break
|
|
} else if !bytes.Equal(input, p) {
|
|
t.Errorf("got packet type %d, want %d", p[0], input[0])
|
|
}
|
|
}
|
|
|
|
if j != numPacket {
|
|
t.Errorf("got %d, want 5 messages", j)
|
|
}
|
|
}()
|
|
|
|
<-checker.called
|
|
|
|
for i := 0; i < numPacket; i++ {
|
|
p := make([]byte, len(input))
|
|
copy(p, input)
|
|
if err := trC.writePacket(p); err != nil {
|
|
t.Errorf("writePacket: %v", err)
|
|
}
|
|
if i == 2 {
|
|
// Make sure the kex is in progress.
|
|
<-checker.called
|
|
}
|
|
|
|
}
|
|
<-done
|
|
}
|
|
|
|
type syncChecker struct {
|
|
waitCall chan int
|
|
called chan int
|
|
}
|
|
|
|
func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
|
c.called <- 1
|
|
if c.waitCall != nil {
|
|
<-c.waitCall
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestHandshakeAutoRekeyRead(t *testing.T) {
|
|
sync := &syncChecker{
|
|
called: make(chan int, 2),
|
|
waitCall: nil,
|
|
}
|
|
clientConf := &ClientConfig{
|
|
HostKeyCallback: sync.Check,
|
|
}
|
|
clientConf.RekeyThreshold = 500
|
|
|
|
trC, trS, err := handshakePair(clientConf, "addr", false)
|
|
if err != nil {
|
|
t.Fatalf("handshakePair: %v", err)
|
|
}
|
|
defer trC.Close()
|
|
defer trS.Close()
|
|
|
|
packet := make([]byte, 501)
|
|
packet[0] = msgRequestSuccess
|
|
if err := trS.writePacket(packet); err != nil {
|
|
t.Fatalf("writePacket: %v", err)
|
|
}
|
|
|
|
// While we read out the packet, a key change will be
|
|
// initiated.
|
|
done := make(chan int, 1)
|
|
go func() {
|
|
defer close(done)
|
|
if _, err := trC.readPacket(); err != nil {
|
|
t.Fatalf("readPacket(client): %v", err)
|
|
}
|
|
|
|
}()
|
|
|
|
<-done
|
|
<-sync.called
|
|
}
|
|
|
|
// errorKeyingTransport generates errors after a given number of
|
|
// read/write operations.
|
|
type errorKeyingTransport struct {
|
|
packetConn
|
|
readLeft, writeLeft int
|
|
}
|
|
|
|
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
|
|
return nil
|
|
}
|
|
|
|
func (n *errorKeyingTransport) getSessionID() []byte {
|
|
return nil
|
|
}
|
|
|
|
func (n *errorKeyingTransport) writePacket(packet []byte) error {
|
|
if n.writeLeft == 0 {
|
|
n.Close()
|
|
return errors.New("barf")
|
|
}
|
|
|
|
n.writeLeft--
|
|
return n.packetConn.writePacket(packet)
|
|
}
|
|
|
|
func (n *errorKeyingTransport) readPacket() ([]byte, error) {
|
|
if n.readLeft == 0 {
|
|
n.Close()
|
|
return nil, errors.New("barf")
|
|
}
|
|
|
|
n.readLeft--
|
|
return n.packetConn.readPacket()
|
|
}
|
|
|
|
func TestHandshakeErrorHandlingRead(t *testing.T) {
|
|
for i := 0; i < 20; i++ {
|
|
testHandshakeErrorHandlingN(t, i, -1, false)
|
|
}
|
|
}
|
|
|
|
func TestHandshakeErrorHandlingWrite(t *testing.T) {
|
|
for i := 0; i < 20; i++ {
|
|
testHandshakeErrorHandlingN(t, -1, i, false)
|
|
}
|
|
}
|
|
|
|
func TestHandshakeErrorHandlingReadCoupled(t *testing.T) {
|
|
for i := 0; i < 20; i++ {
|
|
testHandshakeErrorHandlingN(t, i, -1, true)
|
|
}
|
|
}
|
|
|
|
func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
|
for i := 0; i < 20; i++ {
|
|
testHandshakeErrorHandlingN(t, -1, i, true)
|
|
}
|
|
}
|
|
|
|
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
|
|
// handshakeTransport deadlocks, the go runtime will detect it and
|
|
// panic.
|
|
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
|
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
|
|
|
a, b := memPipe()
|
|
defer a.Close()
|
|
defer b.Close()
|
|
|
|
key := testSigners["ecdsa"]
|
|
serverConf := Config{RekeyThreshold: minRekeyThreshold}
|
|
serverConf.SetDefaults()
|
|
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
|
|
serverConn.hostKeys = []Signer{key}
|
|
go serverConn.readLoop()
|
|
go serverConn.kexLoop()
|
|
|
|
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
|
|
clientConf.SetDefaults()
|
|
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
|
|
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
|
|
go clientConn.readLoop()
|
|
go clientConn.kexLoop()
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, hs := range []packetConn{serverConn, clientConn} {
|
|
if !coupled {
|
|
wg.Add(2)
|
|
go func(c packetConn) {
|
|
for i := 0; ; i++ {
|
|
str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8)
|
|
err := c.writePacket(Marshal(&serviceRequestMsg{str}))
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
wg.Done()
|
|
c.Close()
|
|
}(hs)
|
|
go func(c packetConn) {
|
|
for {
|
|
_, err := c.readPacket()
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
wg.Done()
|
|
}(hs)
|
|
} else {
|
|
wg.Add(1)
|
|
go func(c packetConn) {
|
|
for {
|
|
_, err := c.readPacket()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if err := c.writePacket(msg); err != nil {
|
|
break
|
|
}
|
|
|
|
}
|
|
wg.Done()
|
|
}(hs)
|
|
}
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestDisconnect(t *testing.T) {
|
|
if runtime.GOOS == "plan9" {
|
|
t.Skip("see golang.org/issue/7237")
|
|
}
|
|
checker := &testChecker{}
|
|
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
|
if err != nil {
|
|
t.Fatalf("handshakePair: %v", err)
|
|
}
|
|
|
|
defer trC.Close()
|
|
defer trS.Close()
|
|
|
|
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
|
errMsg := &disconnectMsg{
|
|
Reason: 42,
|
|
Message: "such is life",
|
|
}
|
|
trC.writePacket(Marshal(errMsg))
|
|
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
|
|
|
packet, err := trS.readPacket()
|
|
if err != nil {
|
|
t.Fatalf("readPacket 1: %v", err)
|
|
}
|
|
if packet[0] != msgRequestSuccess {
|
|
t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess)
|
|
}
|
|
|
|
_, err = trS.readPacket()
|
|
if err == nil {
|
|
t.Errorf("readPacket 2 succeeded")
|
|
} else if !reflect.DeepEqual(err, errMsg) {
|
|
t.Errorf("got error %#v, want %#v", err, errMsg)
|
|
}
|
|
|
|
_, err = trS.readPacket()
|
|
if err == nil {
|
|
t.Errorf("readPacket 3 succeeded")
|
|
}
|
|
}
|