go.crypto/ssh: import gosshnew.

See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ

R=hanwen
CC=golang-codereviews
https://golang.org/cl/86190043
This commit is contained in:
Adam Langley
2014-04-09 13:57:52 -07:00
parent 8f45c680ce
commit fa50e7408b
53 changed files with 7391 additions and 3777 deletions
+137 -440
View File
@@ -5,403 +5,158 @@
package ssh
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"sync"
)
// ClientConn represents the client side of an SSH connection.
type ClientConn struct {
transport *transport
config *ClientConfig
chanList // channels associated with this connection
forwardList // forwarded tcpip connections from the remote side
globalRequest
// Client implements a traditional SSH client that supports shells,
// subprocesses, port forwarding and tunneled dialing.
type Client struct {
Conn
// Address as passed to the Dial function.
dialAddress string
serverVersion string
forwards forwardList // forwarded tcpip connections from the remote side
mu sync.Mutex
channelHandlers map[string]chan NewChannel
}
type globalRequest struct {
sync.Mutex
response chan interface{}
// HandleChannelOpen returns a channel on which NewChannel requests
// for the given type are sent. If the type already is being handled,
// nil is returned. The channel is closed when the connection is closed.
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
c.mu.Lock()
defer c.mu.Unlock()
if c.channelHandlers == nil {
// The SSH channel has been closed.
c := make(chan NewChannel)
close(c)
return c
}
ch := c.channelHandlers[channelType]
if ch != nil {
return nil
}
ch = make(chan NewChannel, 16)
c.channelHandlers[channelType] = ch
return ch
}
// Client returns a new SSH client connection using c as the underlying transport.
func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
return clientWithAddress(c, "", config)
// NewClient creates a Client on top of the given connection.
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn := &Client{
Conn: c,
channelHandlers: make(map[string]chan NewChannel, 1),
}
go conn.handleGlobalRequests(reqs)
go conn.handleChannelOpens(chans)
go func() {
conn.Wait()
conn.forwards.closeAll()
}()
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
return conn
}
func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientConn, error) {
conn := &ClientConn{
transport: newTransport(c, config.rand(), true /* is client */),
config: config,
globalRequest: globalRequest{response: make(chan interface{}, 1)},
dialAddress: addr,
// NewClientConn establishes an authenticated SSH connection using c
// as the underlying transport. The Request and NewChannel channels
// must be serviced or the connection will hang.
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config
fullConf.SetDefaults()
conn := &connection{
sshConn: sshConn{conn: c},
}
if err := conn.handshake(); err != nil {
conn.transport.Close()
return nil, fmt.Errorf("handshake failed: %v", err)
if err := conn.clientHandshake(addr, &fullConf); err != nil {
c.Close()
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
}
go conn.mainLoop()
return conn, nil
conn.mux = newMux(conn.transport)
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
}
// Close closes the connection.
func (c *ClientConn) Close() error { return c.transport.Close() }
// LocalAddr returns the local network address.
func (c *ClientConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
// RemoteAddr returns the remote network address.
func (c *ClientConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
// handshake performs the client side key exchange. See RFC 4253 Section 7.
func (c *ClientConn) handshake() error {
clientVersion := []byte(packageVersion)
if c.config.ClientVersion != "" {
clientVersion = []byte(c.config.ClientVersion)
// clientHandshake performs the client side key exchange. See RFC 4253 Section
// 7.
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
c.clientVersion = []byte(packageVersion)
if config.ClientVersion != "" {
c.clientVersion = []byte(config.ClientVersion)
}
serverVersion, err := exchangeVersions(c.transport.Conn, clientVersion)
if err != nil {
return err
}
c.serverVersion = string(serverVersion)
clientKexInit := kexInitMsg{
KexAlgos: c.config.Crypto.kexes(),
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)
if err := c.transport.writePacket(kexInitPacket); err != nil {
return err
}
packet, err := c.transport.readPacket()
var err error
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
if err != nil {
return err
}
var serverKexInit kexInitMsg
if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
c.transport = newClientTransport(
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
if err := c.transport.requestKeyChange(); err != nil {
return err
}
algs := findAgreedAlgorithms(&clientKexInit, &serverKexInit)
if algs == nil {
return errors.New("ssh: no common algorithms")
}
if serverKexInit.FirstKexFollows && algs.kex != serverKexInit.KexAlgos[0] {
// The server sent a Kex message for the wrong algorithm,
// which we have to ignore.
if _, err := c.transport.readPacket(); err != nil {
return err
}
}
kex, ok := kexAlgoMap[algs.kex]
if !ok {
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
}
magics := handshakeMagics{
clientVersion: clientVersion,
serverVersion: serverVersion,
clientKexInit: kexInitPacket,
serverKexInit: packet,
}
result, err := kex.Client(c.transport, c.config.rand(), &magics)
if err != nil {
if packet, err := c.transport.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {
return unexpectedMessageError(msgNewKeys, packet[0])
}
err = verifyHostKeySignature(algs.hostKey, result.HostKey, result.H, result.Signature)
if err != nil {
return err
}
if checker := c.config.HostKeyChecker; checker != nil {
err = checker.Check(c.dialAddress, c.transport.RemoteAddr(), algs.hostKey, result.HostKey)
if err != nil {
return err
}
}
c.transport.prepareKeyChange(algs, result)
if err = c.transport.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
if packet, err = c.transport.readPacket(); err != nil {
return err
}
if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
return c.authenticate()
return c.clientAuthenticate(config)
}
// Verify the host key obtained in the key exchange.
func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte, signature []byte) error {
hostKey, rest, ok := ParsePublicKey(hostKeyBytes)
if len(rest) > 0 || !ok {
return errors.New("ssh: could not parse hostkey")
}
sig, rest, ok := parseSignatureBody(signature)
// verifyHostKeySignature verifies the host key obtained in the key
// exchange.
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
sig, rest, ok := parseSignatureBody(result.Signature)
if len(rest) > 0 || !ok {
return errors.New("ssh: signature parse error")
}
if sig.Format != hostKeyAlgo {
return fmt.Errorf("ssh: unexpected signature type %q", sig.Format)
}
if !hostKey.Verify(data, sig.Blob) {
return errors.New("ssh: host key signature error")
}
return nil
return hostKey.Verify(result.H, sig)
}
// mainLoop reads incoming messages and routes channel messages
// to their respective ClientChans.
func (c *ClientConn) mainLoop() {
defer func() {
c.transport.Close()
c.chanList.closeAll()
c.forwardList.closeAll()
}()
for {
packet, err := c.transport.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 indefinitely 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:
decoded, err := decode(packet)
if err != nil {
if _, ok := err.(UnexpectedMessageError); ok {
fmt.Printf("mainLoop: unexpected message: %v\n", err)
continue
}
return
}
switch msg := decoded.(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.Close()
close(ch.msg)
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 *globalRequestMsg:
// This handles keepalive messages and matches
// the behaviour of OpenSSH.
if msg.WantReply {
c.transport.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{}))
}
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) {
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
c.sendConnectionFailed(msg.PeersId)
}
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 {
// TODO: print on a more structured log.
fmt.Println("could not find forward list entry for", laddr)
// Section 7.2, implementations MUST reject spurious 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)
ch.maxPacket = msg.MaxPacketSize
m := channelOpenConfirmMsg{
PeersId: ch.remoteId,
MyId: ch.localId,
MyWindow: channelWindowSize,
MaxPacketSize: channelMaxPacketSize,
}
c.transport.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.transport.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.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil {
// NewSession opens a new Session for this client. (A session is a remote
// execution of a program.)
func (c *Client) NewSession() (*Session, error) {
ch, in, err := c.OpenChannel("session", nil)
if err != nil {
return nil, err
}
r := <-c.globalRequest.response
if r, ok := r.(*globalRequestSuccessMsg); ok {
return r, nil
}
return nil, errors.New("request failed")
return newSession(ch, in)
}
// 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",
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
for r := range incoming {
// This handles keepalive messages and matches
// the behaviour of OpenSSH.
r.Reply(false, nil)
}
return c.transport.writePacket(marshal(msgChannelOpenFailure, m))
}
// handleChannelOpens channel open messages from the remote side.
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
for ch := range in {
c.mu.Lock()
handler := c.channelHandlers[ch.ChannelType()]
c.mu.Unlock()
if handler != nil {
handler <- ch
} else {
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
}
}
c.mu.Lock()
for _, ch := range c.channelHandlers {
close(ch)
}
c.channelHandlers = nil
c.mu.Unlock()
}
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
@@ -413,7 +168,7 @@ func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
return nil, b, false
}
port, b, ok := parseUint32(b)
if !ok {
if !ok || port == 0 || port > 65535 {
return nil, b, false
}
ip := net.ParseIP(string(addr))
@@ -423,102 +178,44 @@ func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
return &net.TCPAddr{IP: ip, Port: 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) {
// Dial starts a client connection to the given SSH server. It is a
// convenience function that connects to the given network address,
// initiates the SSH handshake, and then sets up a Client. For access
// to incoming channels and requests, use net.Dial with NewClientConn
// instead.
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
return clientWithAddress(conn, addr, config)
c, chans, reqs, err := NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
return NewClient(c, chans, reqs), nil
}
// A ClientConfig structure is used to configure a ClientConn. After one has
// been passed to an SSH function it must not be modified.
// A ClientConfig structure is used to configure a Client. It must not be
// modified after having been passed to an SSH function.
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
// Config contains configuration that is shared between clients and
// servers.
Config
// The username to authenticate.
// User contains the username to authenticate as.
User string
// A slice of ClientAuth methods. Only the first instance
// of a particular RFC 4252 method will be used during authentication.
Auth []ClientAuth
// Auth contains possible authentication methods to use with the
// server. Only the first instance of a particular RFC 4252 method will
// be used during authentication.
Auth []AuthMethod
// HostKeyChecker, if not nil, is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyChecker
// HostKeyCallback, if not nil, is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyCallback
// implies that all host keys are accepted.
HostKeyChecker HostKeyChecker
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
// Cryptographic-related configuration.
Crypto CryptoConfig
// The identification string that will be used for the connection.
// If empty, a reasonable default is used.
// ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used.
ClientVersion string
}
func (c *ClientConfig) rand() io.Reader {
if c.Rand == nil {
return rand.Reader
}
return c.Rand
}
// 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(p packetConn) *clientChan {
c.Lock()
defer c.Unlock()
for i := range c.chans {
if c.chans[i] == nil {
ch := newClientChan(p, uint32(i))
c.chans[i] = ch
return ch
}
}
i := len(c.chans)
ch := newClientChan(p, 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.Close()
close(ch.msg)
}
}