Files
crypto/ssh/client.go
T
Dave Cheney f4749cba32 go.crypto/ssh: never send more data than maxpacket
RFC 4254 s5.2 is clear that a client must never send a data
packet larger than the value of maximum packet supplied by the
remote side during channel setup. The client was not honoring
this value, in fact it wasn't even recording it.

Thanks to Albert Strasheim for the bug report.

R=agl, fullung
CC=golang-dev
https://golang.org/cl/6448128
2012-08-09 10:22:00 +10:00

634 lines
16 KiB
Go

// Copyright 2011 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 (
"crypto"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"net"
"sync"
)
// clientVersion is the fixed identification string that the client will use.
var clientVersion = []byte("SSH-2.0-Go\r\n")
// ClientConn represents the client side of an SSH connection.
type ClientConn struct {
*transport
config *ClientConfig
chanList // channels associated with this connection
forwardList // forwarded tcpip connections from the remote side
globalRequest
}
type globalRequest struct {
sync.Mutex
response chan interface{}
}
// Client returns a new SSH client connection using c as the underlying transport.
func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
conn := &ClientConn{
transport: newTransport(c, config.rand()),
config: config,
globalRequest: globalRequest{response: make(chan interface{}, 1)},
}
if err := conn.handshake(); err != nil {
conn.Close()
return nil, err
}
go conn.mainLoop()
return conn, nil
}
// handshake performs the client side key exchange. See RFC 4253 Section 7.
func (c *ClientConn) handshake() error {
var magics handshakeMagics
if _, err := c.Write(clientVersion); err != nil {
return err
}
if err := c.Flush(); err != nil {
return err
}
magics.clientVersion = clientVersion[:len(clientVersion)-2]
// read remote server version
version, err := readVersion(c)
if err != nil {
return err
}
magics.serverVersion = version
clientKexInit := kexInitMsg{
KexAlgos: supportedKexAlgos,
ServerHostKeyAlgos: supportedHostKeyAlgos,
CiphersClientServer: c.config.Crypto.ciphers(),
CiphersServerClient: c.config.Crypto.ciphers(),
MACsClientServer: c.config.Crypto.macs(),
MACsServerClient: c.config.Crypto.macs(),
CompressionClientServer: supportedCompressions,
CompressionServerClient: supportedCompressions,
}
kexInitPacket := marshal(msgKexInit, clientKexInit)
magics.clientKexInit = kexInitPacket
if err := c.writePacket(kexInitPacket); err != nil {
return err
}
packet, err := c.readPacket()
if err != nil {
return err
}
magics.serverKexInit = packet
var serverKexInit kexInitMsg
if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
return err
}
kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(c.transport, &clientKexInit, &serverKexInit)
if !ok {
return errors.New("ssh: no common algorithms")
}
if serverKexInit.FirstKexFollows && kexAlgo != serverKexInit.KexAlgos[0] {
// The server sent a Kex message for the wrong algorithm,
// which we have to ignore.
if _, err := c.readPacket(); err != nil {
return err
}
}
var H, K []byte
var hashFunc crypto.Hash
switch kexAlgo {
case kexAlgoDH14SHA1:
hashFunc = crypto.SHA1
dhGroup14Once.Do(initDHGroup14)
H, K, err = c.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
case keyAlgoDH1SHA1:
hashFunc = crypto.SHA1
dhGroup1Once.Do(initDHGroup1)
H, K, err = c.kexDH(dhGroup1, hashFunc, &magics, hostKeyAlgo)
default:
err = fmt.Errorf("ssh: unexpected key exchange algorithm %v", kexAlgo)
}
if err != nil {
return err
}
if err = c.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
if err = c.transport.writer.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
return err
}
if packet, err = c.readPacket(); err != nil {
return err
}
if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
if err := c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
return err
}
return c.authenticate(H)
}
// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
// returned values are given the same names as in RFC 4253, section 8.
func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, error) {
x, err := rand.Int(c.config.rand(), group.p)
if err != nil {
return nil, nil, err
}
X := new(big.Int).Exp(group.g, x, group.p)
kexDHInit := kexDHInitMsg{
X: X,
}
if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
return nil, nil, err
}
packet, err := c.readPacket()
if err != nil {
return nil, nil, err
}
var kexDHReply kexDHReplyMsg
if err = unmarshal(&kexDHReply, packet, msgKexDHReply); err != nil {
return nil, nil, err
}
kInt, err := group.diffieHellman(kexDHReply.Y, x)
if err != nil {
return nil, nil, err
}
h := hashFunc.New()
writeString(h, magics.clientVersion)
writeString(h, magics.serverVersion)
writeString(h, magics.clientKexInit)
writeString(h, magics.serverKexInit)
writeString(h, kexDHReply.HostKey)
writeInt(h, X)
writeInt(h, kexDHReply.Y)
K := make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
H := h.Sum(nil)
return H, K, nil
}
// mainLoop reads incoming messages and routes channel messages
// to their respective ClientChans.
func (c *ClientConn) mainLoop() {
defer func() {
c.Close()
c.closeAll()
}()
for {
packet, err := c.readPacket()
if err != nil {
break
}
// TODO(dfc) A note on blocking channel use.
// The msg, data and dataExt channels of a clientChan can
// cause this loop to block indefinately if the consumer does
// not service them.
switch packet[0] {
case msgChannelData:
if len(packet) < 9 {
// malformed data packet
return
}
remoteId := binary.BigEndian.Uint32(packet[1:5])
length := binary.BigEndian.Uint32(packet[5:9])
packet = packet[9:]
if length != uint32(len(packet)) {
return
}
ch, ok := c.getChan(remoteId)
if !ok {
return
}
ch.stdout.write(packet)
case msgChannelExtendedData:
if len(packet) < 13 {
// malformed data packet
return
}
remoteId := binary.BigEndian.Uint32(packet[1:5])
datatype := binary.BigEndian.Uint32(packet[5:9])
length := binary.BigEndian.Uint32(packet[9:13])
packet = packet[13:]
if length != uint32(len(packet)) {
return
}
// RFC 4254 5.2 defines data_type_code 1 to be data destined
// for stderr on interactive sessions. Other data types are
// silently discarded.
if datatype == 1 {
ch, ok := c.getChan(remoteId)
if !ok {
return
}
ch.stderr.write(packet)
}
default:
switch msg := decode(packet).(type) {
case *channelOpenMsg:
c.handleChanOpen(msg)
case *channelOpenConfirmMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelOpenFailureMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelCloseMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.theyClosed = true
ch.stdout.eof()
ch.stderr.eof()
close(ch.msg)
if !ch.weClosed {
ch.weClosed = true
ch.sendClose()
}
c.chanList.remove(msg.PeersId)
case *channelEOFMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.stdout.eof()
// RFC 4254 is mute on how EOF affects dataExt messages but
// it is logical to signal EOF at the same time.
ch.stderr.eof()
case *channelRequestSuccessMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelRequestFailureMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *channelRequestMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
ch.msg <- msg
case *windowAdjustMsg:
ch, ok := c.getChan(msg.PeersId)
if !ok {
return
}
if !ch.remoteWin.add(msg.AdditionalBytes) {
// invalid window update
return
}
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
c.globalRequest.response <- msg
case *disconnectMsg:
return
default:
fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
}
}
}
}
// Handle channel open messages from the remote side.
func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
switch msg.ChanType {
case "forwarded-tcpip":
laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData)
if !ok {
// invalid request
c.sendConnectionFailed(msg.PeersId)
return
}
l, ok := c.forwardList.lookup(laddr)
if !ok {
fmt.Println("could not find forward list entry for", laddr)
// Section 7.2, implementations MUST reject suprious incoming
// connections.
c.sendConnectionFailed(msg.PeersId)
return
}
raddr, rest, ok := parseTCPAddr(rest)
if !ok {
// invalid request
c.sendConnectionFailed(msg.PeersId)
return
}
ch := c.newChan(c.transport)
ch.remoteId = msg.PeersId
ch.remoteWin.add(msg.PeersWindow)
m := channelOpenConfirmMsg{
PeersId: ch.remoteId,
MyId: ch.localId,
MyWindow: 1 << 14,
MaxPacketSize: 1 << 15, // RFC 4253 6.1
}
c.writePacket(marshal(msgChannelOpenConfirm, m))
l <- forward{ch, raddr}
default:
// unknown channel type
m := channelOpenFailureMsg{
PeersId: msg.PeersId,
Reason: UnknownChannelType,
Message: fmt.Sprintf("unknown channel type: %v", msg.ChanType),
Language: "en_US.UTF-8",
}
c.writePacket(marshal(msgChannelOpenFailure, m))
}
}
// sendGlobalRequest sends a global request message as specified
// in RFC4254 section 4. To correctly synchronise messages, a lock
// is held internally until a response is returned.
func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) {
c.globalRequest.Lock()
defer c.globalRequest.Unlock()
if err := c.writePacket(marshal(msgGlobalRequest, m)); err != nil {
return nil, err
}
r := <-c.globalRequest.response
if r, ok := r.(*globalRequestSuccessMsg); ok {
return r, nil
}
return nil, errors.New("request failed")
}
// sendConnectionFailed rejects an incoming channel identified
// by remoteId.
func (c *ClientConn) sendConnectionFailed(remoteId uint32) error {
m := channelOpenFailureMsg{
PeersId: remoteId,
Reason: ConnectionFailed,
Message: "invalid request",
Language: "en_US.UTF-8",
}
return c.writePacket(marshal(msgChannelOpenFailure, m))
}
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
// RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardlist
// requires a valid *net.TCPAddr to operate, so we enforce that restriction here.
func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
addr, b, ok := parseString(b)
if !ok {
return nil, b, false
}
port, b, ok := parseUint32(b)
if !ok {
return nil, b, false
}
ip := net.ParseIP(string(addr))
if ip == nil {
return nil, b, false
}
return &net.TCPAddr{ip, int(port)}, b, true
}
// Dial connects to the given network address using net.Dial and
// then initiates a SSH handshake, returning the resulting client connection.
func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
return Client(conn, config)
}
// A ClientConfig structure is used to configure a ClientConn. After one has
// been passed to an SSH function it must not be modified.
type ClientConfig struct {
// Rand provides the source of entropy for key exchange. If Rand is
// nil, the cryptographic random reader in package crypto/rand will
// be used.
Rand io.Reader
// The username to authenticate.
User string
// A slice of ClientAuth methods. Only the first instance
// of a particular RFC 4252 method will be used during authentication.
Auth []ClientAuth
// Cryptographic-related configuration.
Crypto CryptoConfig
}
func (c *ClientConfig) rand() io.Reader {
if c.Rand == nil {
return rand.Reader
}
return c.Rand
}
// A clientChan represents a single RFC 4254 channel that is multiplexed
// over a single SSH connection.
type clientChan struct {
channel
stdin *chanWriter
stdout *chanReader
stderr *chanReader
msg chan interface{}
}
// newClientChan returns a partially constructed *clientChan
// using the local id provided. To be usable clientChan.remoteId
// needs to be assigned once known.
func newClientChan(cc conn, id uint32) *clientChan {
c := &clientChan{
channel: channel{
conn: cc,
localId: id,
remoteWin: window{Cond: newCond()},
},
msg: make(chan interface{}, 16),
}
c.stdin = &chanWriter{
channel: &c.channel,
}
c.stdout = &chanReader{
channel: &c.channel,
buffer: newBuffer(),
}
c.stderr = &chanReader{
channel: &c.channel,
buffer: newBuffer(),
}
return c
}
// waitForChannelOpenResponse, if successful, fills out
// the remoteId and records any initial window advertisement.
func (c *clientChan) waitForChannelOpenResponse() error {
switch msg := (<-c.msg).(type) {
case *channelOpenConfirmMsg:
// fixup remoteId field
c.remoteId = msg.MyId
// TODO(dfc) asset this is < 2^31.
c.maxPacketSize = msg.MaxPacketSize
c.remoteWin.add(msg.MyWindow)
return nil
case *channelOpenFailureMsg:
return errors.New(safeString(msg.Message))
}
return errors.New("ssh: unexpected packet")
}
// Close closes the channel. This does not close the underlying connection.
func (c *clientChan) Close() error {
if !c.weClosed {
c.weClosed = true
return c.sendClose()
}
return nil
}
// Thread safe channel list.
type chanList struct {
// protects concurrent access to chans
sync.Mutex
// chans are indexed by the local id of the channel, clientChan.localId.
// The PeersId value of messages received by ClientConn.mainLoop is
// used to locate the right local clientChan in this slice.
chans []*clientChan
}
// Allocate a new ClientChan with the next avail local id.
func (c *chanList) newChan(t *transport) *clientChan {
c.Lock()
defer c.Unlock()
for i := range c.chans {
if c.chans[i] == nil {
ch := newClientChan(t, uint32(i))
c.chans[i] = ch
return ch
}
}
i := len(c.chans)
ch := newClientChan(t, uint32(i))
c.chans = append(c.chans, ch)
return ch
}
func (c *chanList) getChan(id uint32) (*clientChan, bool) {
c.Lock()
defer c.Unlock()
if id >= uint32(len(c.chans)) {
return nil, false
}
return c.chans[id], true
}
func (c *chanList) remove(id uint32) {
c.Lock()
defer c.Unlock()
c.chans[id] = nil
}
func (c *chanList) closeAll() {
c.Lock()
defer c.Unlock()
for _, ch := range c.chans {
if ch == nil {
continue
}
ch.theyClosed = true
ch.stdout.eof()
ch.stderr.eof()
close(ch.msg)
}
}
// A chanWriter represents the stdin of a remote process.
type chanWriter struct {
*channel
}
// Write writes data to the remote process's standard input.
func (w *chanWriter) Write(data []byte) (written int, err error) {
for len(data) > 0 {
// never send more data than maxPacketSize even if
// there is sufficent window.
n := min(int(w.maxPacketSize), len(data))
n = int(w.remoteWin.reserve(uint32(n)))
remoteId := w.remoteId
packet := []byte{
msgChannelData,
byte(remoteId >> 24), byte(remoteId >> 16), byte(remoteId >> 8), byte(remoteId),
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
}
if err = w.writePacket(append(packet, data[:n]...)); err != nil {
break
}
data = data[n:]
written += n
}
return
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (w *chanWriter) Close() error {
return w.sendEOF()
}
// A chanReader represents stdout or stderr of a remote process.
type chanReader struct {
*channel // the channel backing this reader
*buffer
}
// Read reads data from the remote process's stdout or stderr.
func (r *chanReader) Read(buf []byte) (int, error) {
n, err := r.buffer.Read(buf)
if err != nil {
if err == io.EOF {
return n, err
}
return 0, err
}
return n, r.sendWindowAdj(n)
}