go.crypto: initial code

Manual edits to README.
Moved from main Go repository, deleted Makefiles, ran gofix -r go1rename.

Tested with: go test code.google.com/p/go.crypto/...

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5564059
This commit is contained in:
Russ Cox
2012-01-25 15:31:12 -05:00
parent f61fbb80d2
commit 470549d502
79 changed files with 15447 additions and 15 deletions
+318
View File
@@ -0,0 +1,318 @@
// 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 (
"errors"
"io"
"sync"
)
// A Channel is an ordered, reliable, duplex stream that is multiplexed over an
// SSH connection.
type Channel interface {
// Accept accepts the channel creation request.
Accept() error
// Reject rejects the channel creation request. After calling this, no
// other methods on the Channel may be called. If they are then the
// peer is likely to signal a protocol error and drop the connection.
Reject(reason RejectionReason, message string) error
// Read may return a ChannelRequest as an error.
Read(data []byte) (int, error)
Write(data []byte) (int, error)
Close() error
// AckRequest either sends an ack or nack to the channel request.
AckRequest(ok bool) error
// ChannelType returns the type of the channel, as supplied by the
// client.
ChannelType() string
// ExtraData returns the arbitary payload for this channel, as supplied
// by the client. This data is specific to the channel type.
ExtraData() []byte
}
// ChannelRequest represents a request sent on a channel, outside of the normal
// stream of bytes. It may result from calling Read on a Channel.
type ChannelRequest struct {
Request string
WantReply bool
Payload []byte
}
func (c ChannelRequest) Error() string {
return "channel request received"
}
// RejectionReason is an enumeration used when rejecting channel creation
// requests. See RFC 4254, section 5.1.
type RejectionReason int
const (
Prohibited RejectionReason = iota + 1
ConnectionFailed
UnknownChannelType
ResourceShortage
)
type channel struct {
// immutable once created
chanType string
extraData []byte
theyClosed bool
theySentEOF bool
weClosed bool
dead bool
serverConn *ServerConn
myId, theirId uint32
myWindow, theirWindow uint32
maxPacketSize uint32
err error
pendingRequests []ChannelRequest
pendingData []byte
head, length int
// This lock is inferior to serverConn.lock
lock sync.Mutex
cond *sync.Cond
}
func (c *channel) Accept() error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
confirm := channelOpenConfirmMsg{
PeersId: c.theirId,
MyId: c.myId,
MyWindow: c.myWindow,
MaxPacketSize: c.maxPacketSize,
}
return c.serverConn.writePacket(marshal(msgChannelOpenConfirm, confirm))
}
func (c *channel) Reject(reason RejectionReason, message string) error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
reject := channelOpenFailureMsg{
PeersId: c.theirId,
Reason: uint32(reason),
Message: message,
Language: "en",
}
return c.serverConn.writePacket(marshal(msgChannelOpenFailure, reject))
}
func (c *channel) handlePacket(packet interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
switch packet := packet.(type) {
case *channelRequestMsg:
req := ChannelRequest{
Request: packet.Request,
WantReply: packet.WantReply,
Payload: packet.RequestSpecificData,
}
c.pendingRequests = append(c.pendingRequests, req)
c.cond.Signal()
case *channelCloseMsg:
c.theyClosed = true
c.cond.Signal()
case *channelEOFMsg:
c.theySentEOF = true
c.cond.Signal()
default:
panic("unknown packet type")
}
}
func (c *channel) handleData(data []byte) {
c.lock.Lock()
defer c.lock.Unlock()
// The other side should never send us more than our window.
if len(data)+c.length > len(c.pendingData) {
// TODO(agl): we should tear down the channel with a protocol
// error.
return
}
c.myWindow -= uint32(len(data))
for i := 0; i < 2; i++ {
tail := c.head + c.length
if tail > len(c.pendingData) {
tail -= len(c.pendingData)
}
n := copy(c.pendingData[tail:], data)
data = data[n:]
c.length += n
}
c.cond.Signal()
}
func (c *channel) Read(data []byte) (n int, err error) {
c.lock.Lock()
defer c.lock.Unlock()
if c.err != nil {
return 0, c.err
}
if c.myWindow <= uint32(len(c.pendingData))/2 {
packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{
PeersId: c.theirId,
AdditionalBytes: uint32(len(c.pendingData)) - c.myWindow,
})
if err := c.serverConn.writePacket(packet); err != nil {
return 0, err
}
}
for {
if c.theySentEOF || c.theyClosed || c.dead {
return 0, io.EOF
}
if len(c.pendingRequests) > 0 {
req := c.pendingRequests[0]
if len(c.pendingRequests) == 1 {
c.pendingRequests = nil
} else {
oldPendingRequests := c.pendingRequests
c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1)
copy(c.pendingRequests, oldPendingRequests[1:])
}
return 0, req
}
if c.length > 0 {
tail := c.head + c.length
if tail > len(c.pendingData) {
tail -= len(c.pendingData)
}
n = copy(data, c.pendingData[c.head:tail])
c.head += n
c.length -= n
if c.head == len(c.pendingData) {
c.head = 0
}
return
}
c.cond.Wait()
}
panic("unreachable")
}
func (c *channel) Write(data []byte) (n int, err error) {
for len(data) > 0 {
c.lock.Lock()
if c.dead || c.weClosed {
return 0, io.EOF
}
if c.theirWindow == 0 {
c.cond.Wait()
continue
}
c.lock.Unlock()
todo := data
if uint32(len(todo)) > c.theirWindow {
todo = todo[:c.theirWindow]
}
packet := make([]byte, 1+4+4+len(todo))
packet[0] = msgChannelData
packet[1] = byte(c.theirId >> 24)
packet[2] = byte(c.theirId >> 16)
packet[3] = byte(c.theirId >> 8)
packet[4] = byte(c.theirId)
packet[5] = byte(len(todo) >> 24)
packet[6] = byte(len(todo) >> 16)
packet[7] = byte(len(todo) >> 8)
packet[8] = byte(len(todo))
copy(packet[9:], todo)
c.serverConn.lock.Lock()
if err = c.serverConn.writePacket(packet); err != nil {
c.serverConn.lock.Unlock()
return
}
c.serverConn.lock.Unlock()
n += len(todo)
data = data[len(todo):]
}
return
}
func (c *channel) Close() error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
if c.weClosed {
return errors.New("ssh: channel already closed")
}
c.weClosed = true
closeMsg := channelCloseMsg{
PeersId: c.theirId,
}
return c.serverConn.writePacket(marshal(msgChannelClose, closeMsg))
}
func (c *channel) AckRequest(ok bool) error {
c.serverConn.lock.Lock()
defer c.serverConn.lock.Unlock()
if c.serverConn.err != nil {
return c.serverConn.err
}
if ok {
ack := channelRequestSuccessMsg{
PeersId: c.theirId,
}
return c.serverConn.writePacket(marshal(msgChannelSuccess, ack))
} else {
ack := channelRequestFailureMsg{
PeersId: c.theirId,
}
return c.serverConn.writePacket(marshal(msgChannelFailure, ack))
}
panic("unreachable")
}
func (c *channel) ChannelType() string {
return c.chanType
}
func (c *channel) ExtraData() []byte {
return c.extraData
}
+88
View File
@@ -0,0 +1,88 @@
// 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/aes"
"crypto/cipher"
"crypto/rc4"
)
// streamDump is used to dump the initial keystream for stream ciphers. It is a
// a write-only buffer, and not intended for reading so do not require a mutex.
var streamDump [512]byte
// noneCipher implements cipher.Stream and provides no encryption. It is used
// by the transport before the first key-exchange.
type noneCipher struct{}
func (c noneCipher) XORKeyStream(dst, src []byte) {
copy(dst, src)
}
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(c, iv), nil
}
func newRC4(key, iv []byte) (cipher.Stream, error) {
return rc4.NewCipher(key)
}
type cipherMode struct {
keySize int
ivSize int
skip int
createFn func(key, iv []byte) (cipher.Stream, error)
}
func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
if len(key) < c.keySize {
panic("ssh: key length too small for cipher")
}
if len(iv) < c.ivSize {
panic("ssh: iv too small for cipher")
}
stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
if err != nil {
return nil, err
}
for remainingToDump := c.skip; remainingToDump > 0; {
dumpThisTime := remainingToDump
if dumpThisTime > len(streamDump) {
dumpThisTime = len(streamDump)
}
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
remainingToDump -= dumpThisTime
}
return stream, nil
}
// Specifies a default set of ciphers and a preference order. This is based on
// OpenSSH's default client preference order, minus algorithms that are not
// implemented.
var DefaultCipherOrder = []string{
"aes128-ctr", "aes192-ctr", "aes256-ctr",
"arcfour256", "arcfour128",
}
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, 1536, newRC4},
"arcfour256": {32, 0, 1536, newRC4},
}
+62
View File
@@ -0,0 +1,62 @@
// 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 (
"bytes"
"testing"
)
// TestCipherReversal tests that each cipher factory produces ciphers that can
// encrypt and decrypt some data successfully.
func TestCipherReversal(t *testing.T) {
testData := []byte("abcdefghijklmnopqrstuvwxyz012345")
testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345")
testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa")
cryptBuffer := make([]byte, 32)
for name, cipherMode := range cipherModes {
encrypter, err := cipherMode.createCipher(testKey, testIv)
if err != nil {
t.Errorf("failed to create encrypter for %q: %s", name, err)
continue
}
decrypter, err := cipherMode.createCipher(testKey, testIv)
if err != nil {
t.Errorf("failed to create decrypter for %q: %s", name, err)
continue
}
copy(cryptBuffer, testData)
encrypter.XORKeyStream(cryptBuffer, cryptBuffer)
if name == "none" {
if !bytes.Equal(cryptBuffer, testData) {
t.Errorf("encryption made change with 'none' cipher")
continue
}
} else {
if bytes.Equal(cryptBuffer, testData) {
t.Errorf("encryption made no change with %q", name)
continue
}
}
decrypter.XORKeyStream(cryptBuffer, cryptBuffer)
if !bytes.Equal(cryptBuffer, testData) {
t.Errorf("decrypted bytes not equal to input with %q", name)
continue
}
}
}
func TestDefaultCiphersExist(t *testing.T) {
for _, cipherAlgo := range DefaultCipherOrder {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("default cipher %q is unknown", cipherAlgo)
}
}
}
+505
View File
@@ -0,0 +1,505 @@
// 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"
"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
}
// 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,
}
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: supportedMACs,
MACsServerClient: supportedMACs,
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)
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 = new(kexDHReplyMsg)
if err = unmarshal(kexDHReply, packet, msgKexDHReply); err != nil {
return nil, nil, err
}
if kexDHReply.Y.Sign() == 0 || kexDHReply.Y.Cmp(group.p) >= 0 {
return nil, nil, errors.New("server DH parameter out of bounds")
}
kInt := new(big.Int).Exp(kexDHReply.Y, x, group.p)
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() {
// TODO(dfc) signal the underlying close to all channels
defer c.Close()
for {
packet, err := c.readPacket()
if err != nil {
break
}
// TODO(dfc) A note on blocking channel use.
// The msg, win, 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
break
}
peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
packet = packet[9:]
c.getChan(peersId).stdout.handleData(packet[:length])
}
case msgChannelExtendedData:
if len(packet) < 13 {
// malformed data packet
break
}
peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
datatype := uint32(packet[5])<<24 | uint32(packet[6])<<16 | uint32(packet[7])<<8 | uint32(packet[8])
if length := int(packet[9])<<24 | int(packet[10])<<16 | int(packet[11])<<8 | int(packet[12]); length > 0 {
packet = packet[13:]
// 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 {
c.getChan(peersId).stderr.handleData(packet[:length])
}
}
default:
switch msg := decode(packet).(type) {
case *channelOpenMsg:
c.getChan(msg.PeersId).msg <- msg
case *channelOpenConfirmMsg:
c.getChan(msg.PeersId).msg <- msg
case *channelOpenFailureMsg:
c.getChan(msg.PeersId).msg <- msg
case *channelCloseMsg:
ch := c.getChan(msg.PeersId)
ch.theyClosed = true
close(ch.stdin.win)
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 := c.getChan(msg.PeersId)
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:
c.getChan(msg.PeersId).msg <- msg
case *channelRequestFailureMsg:
c.getChan(msg.PeersId).msg <- msg
case *channelRequestMsg:
c.getChan(msg.PeersId).msg <- msg
case *windowAdjustMsg:
c.getChan(msg.PeersId).stdin.win <- int(msg.AdditionalBytes)
case *disconnectMsg:
break
default:
fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
}
}
}
}
// 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 {
packetWriter
id, peersId uint32
stdin *chanWriter // receives window adjustments
stdout *chanReader // receives the payload of channelData messages
stderr *chanReader // receives the payload of channelExtendedData messages
msg chan interface{} // incoming messages
theyClosed bool // indicates the close msg has been received from the remote side
weClosed bool // incidates the close msg has been sent from our side
}
// newClientChan returns a partially constructed *clientChan
// using the local id provided. To be usable clientChan.peersId
// needs to be assigned once known.
func newClientChan(t *transport, id uint32) *clientChan {
c := &clientChan{
packetWriter: t,
id: id,
msg: make(chan interface{}, 16),
}
c.stdin = &chanWriter{
win: make(chan int, 16),
clientChan: c,
}
c.stdout = &chanReader{
data: make(chan []byte, 16),
clientChan: c,
}
c.stderr = &chanReader{
data: make(chan []byte, 16),
clientChan: c,
}
return c
}
// waitForChannelOpenResponse, if successful, fills out
// the peerId and records any initial window advertisement.
func (c *clientChan) waitForChannelOpenResponse() error {
switch msg := (<-c.msg).(type) {
case *channelOpenConfirmMsg:
// fixup peersId field
c.peersId = msg.MyId
c.stdin.win <- int(msg.MyWindow)
return nil
case *channelOpenFailureMsg:
return errors.New(safeString(msg.Message))
}
return errors.New("unexpected packet")
}
// sendEOF sends EOF to the server. RFC 4254 Section 5.3
func (c *clientChan) sendEOF() error {
return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{
PeersId: c.peersId,
}))
}
// sendClose signals the intent to close the channel.
func (c *clientChan) sendClose() error {
return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
PeersId: c.peersId,
}))
}
// 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.id.
// 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 {
c.Lock()
defer c.Unlock()
return c.chans[int(id)]
}
func (c *chanlist) remove(id uint32) {
c.Lock()
defer c.Unlock()
c.chans[int(id)] = nil
}
// A chanWriter represents the stdin of a remote process.
type chanWriter struct {
win chan int // receives window adjustments
rwin int // current rwin size
clientChan *clientChan // the channel backing this writer
}
// Write writes data to the remote process's standard input.
func (w *chanWriter) Write(data []byte) (written int, err error) {
for len(data) > 0 {
for w.rwin < 1 {
win, ok := <-w.win
if !ok {
return 0, io.EOF
}
w.rwin += win
}
n := min(len(data), w.rwin)
peersId := w.clientChan.peersId
packet := []byte{
msgChannelData,
byte(peersId >> 24), byte(peersId >> 16), byte(peersId >> 8), byte(peersId),
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
}
if err = w.clientChan.writePacket(append(packet, data[:n]...)); err != nil {
break
}
data = data[n:]
w.rwin -= n
written += n
}
return
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (w *chanWriter) Close() error {
return w.clientChan.sendEOF()
}
// A chanReader represents stdout or stderr of a remote process.
type chanReader struct {
// TODO(dfc) a fixed size channel may not be the right data structure.
// If writes to this channel block, they will block mainLoop, making
// it unable to receive new messages from the remote side.
data chan []byte // receives data from remote
dataClosed bool // protects data from being closed twice
clientChan *clientChan // the channel backing this reader
buf []byte
}
// eof signals to the consumer that there is no more data to be received.
func (r *chanReader) eof() {
if !r.dataClosed {
r.dataClosed = true
close(r.data)
}
}
// handleData sends buf to the reader's consumer. If r.data is closed
// the data will be silently discarded
func (r *chanReader) handleData(buf []byte) {
if !r.dataClosed {
r.data <- buf
}
}
// Read reads data from the remote process's stdout or stderr.
func (r *chanReader) Read(data []byte) (int, error) {
var ok bool
for {
if len(r.buf) > 0 {
n := copy(data, r.buf)
r.buf = r.buf[n:]
msg := windowAdjustMsg{
PeersId: r.clientChan.peersId,
AdditionalBytes: uint32(n),
}
return n, r.clientChan.writePacket(marshal(msgChannelWindowAdjust, msg))
}
r.buf, ok = <-r.data
if !ok {
return 0, io.EOF
}
}
panic("unreachable")
}
+316
View File
@@ -0,0 +1,316 @@
// 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 (
"errors"
"io"
)
// authenticate authenticates with the remote server. See RFC 4252.
func (c *ClientConn) authenticate(session []byte) error {
// initiate user auth session
if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
return err
}
packet, err := c.readPacket()
if err != nil {
return err
}
var serviceAccept serviceAcceptMsg
if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
return err
}
// during the authentication phase the client first attempts the "none" method
// then any untried methods suggested by the server.
tried, remain := make(map[string]bool), make(map[string]bool)
for auth := ClientAuth(new(noneAuth)); auth != nil; {
ok, methods, err := auth.auth(session, c.config.User, c.transport, c.config.rand())
if err != nil {
return err
}
if ok {
// success
return nil
}
tried[auth.method()] = true
delete(remain, auth.method())
for _, meth := range methods {
if tried[meth] {
// if we've tried meth already, skip it.
continue
}
remain[meth] = true
}
auth = nil
for _, a := range c.config.Auth {
if remain[a.method()] {
auth = a
break
}
}
}
return errors.New("ssh: unable to authenticate, no supported methods remain")
}
// A ClientAuth represents an instance of an RFC 4252 authentication method.
type ClientAuth interface {
// auth authenticates user over transport t.
// Returns true if authentication is successful.
// If authentication is not successful, a []string of alternative
// method names is returned.
auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error)
// method returns the RFC 4252 method name.
method() string
}
// "none" authentication, RFC 4252 section 5.2.
type noneAuth int
func (n *noneAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: "none",
})); err != nil {
return false, nil, err
}
return handleAuthResponse(t)
}
func (n *noneAuth) method() string {
return "none"
}
// "password" authentication, RFC 4252 Section 8.
type passwordAuth struct {
ClientPassword
}
func (p *passwordAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
type passwordAuthMsg struct {
User string
Service string
Method string
Reply bool
Password string
}
pw, err := p.Password(user)
if err != nil {
return false, nil, err
}
if err := t.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
User: user,
Service: serviceSSH,
Method: "password",
Reply: false,
Password: pw,
})); err != nil {
return false, nil, err
}
return handleAuthResponse(t)
}
func (p *passwordAuth) method() string {
return "password"
}
// A ClientPassword implements access to a client's passwords.
type ClientPassword interface {
// Password returns the password to use for user.
Password(user string) (password string, err error)
}
// ClientAuthPassword returns a ClientAuth using password authentication.
func ClientAuthPassword(impl ClientPassword) ClientAuth {
return &passwordAuth{impl}
}
// ClientKeyring implements access to a client key ring.
type ClientKeyring interface {
// Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
// no key exists at i.
Key(i int) (key interface{}, err error)
// Sign returns a signature of the given data using the i'th key
// and the supplied random source.
Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
}
// "publickey" authentication, RFC 4252 Section 7.
type publickeyAuth struct {
ClientKeyring
}
type publickeyAuthMsg struct {
User string
Service string
Method string
// HasSig indicates to the reciver packet that the auth request is signed and
// should be used for authentication of the request.
HasSig bool
Algoname string
Pubkey string
// Sig is defined as []byte so marshal will exclude it during validateKey
Sig []byte `ssh:"rest"`
}
func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an
// enquiry to test if each key is acceptable to the remote. The second
// stage attempts to authenticate with the valid keys obtained in the
// first stage.
var index int
// a map of public keys to their index in the keyring
validKeys := make(map[int]interface{})
for {
key, err := p.Key(index)
if err != nil {
return false, nil, err
}
if key == nil {
// no more keys in the keyring
break
}
if ok, err := p.validateKey(key, user, t); ok {
validKeys[index] = key
} else {
if err != nil {
return false, nil, err
}
}
index++
}
// methods that may continue if this auth is not successful.
var methods []string
for i, key := range validKeys {
pubkey := serializePublickey(key)
algoname := algoName(key)
sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
}, []byte(algoname), pubkey))
if err != nil {
return false, nil, err
}
// manually wrap the serialized signature in a string
s := serializeSignature(algoname, sign)
sig := make([]byte, stringLength(s))
marshalString(sig, s)
msg := publickeyAuthMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
HasSig: true,
Algoname: algoname,
Pubkey: string(pubkey),
Sig: sig,
}
p := marshal(msgUserAuthRequest, msg)
if err := t.writePacket(p); err != nil {
return false, nil, err
}
success, methods, err := handleAuthResponse(t)
if err != nil {
return false, nil, err
}
if success {
return success, methods, err
}
}
return false, methods, nil
}
// validateKey validates the key provided it is acceptable to the server.
func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) (bool, error) {
pubkey := serializePublickey(key)
algoname := algoName(key)
msg := publickeyAuthMsg{
User: user,
Service: serviceSSH,
Method: p.method(),
HasSig: false,
Algoname: algoname,
Pubkey: string(pubkey),
}
if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
return false, err
}
return p.confirmKeyAck(key, t)
}
func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, error) {
pubkey := serializePublickey(key)
algoname := algoName(key)
for {
packet, err := t.readPacket()
if err != nil {
return false, err
}
switch packet[0] {
case msgUserAuthBanner:
// TODO(gpaul): add callback to present the banner to the user
case msgUserAuthPubKeyOk:
msg := decode(packet).(*userAuthPubKeyOkMsg)
if msg.Algo != algoname || msg.PubKey != string(pubkey) {
return false, nil
}
return true, nil
case msgUserAuthFailure:
return false, nil
default:
return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
}
panic("unreachable")
}
func (p *publickeyAuth) method() string {
return "publickey"
}
// ClientAuthKeyring returns a ClientAuth using public key authentication.
func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
return &publickeyAuth{impl}
}
// handleAuthResponse returns whether the preceding authentication request succeeded
// along with a list of remaining authentication methods to try next and
// an error if an unexpected response was received.
func handleAuthResponse(t *transport) (bool, []string, error) {
for {
packet, err := t.readPacket()
if err != nil {
return false, nil, err
}
switch packet[0] {
case msgUserAuthBanner:
// TODO: add callback to present the banner to the user
case msgUserAuthFailure:
msg := decode(packet).(*userAuthFailureMsg)
return false, msg.Methods, nil
case msgUserAuthSuccess:
return true, nil, nil
case msgDisconnect:
return false, nil, io.EOF
default:
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
}
panic("unreachable")
}
+257
View File
@@ -0,0 +1,257 @@
// 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 (
"bytes"
"crypto"
"crypto/dsa"
"crypto/rsa"
_ "crypto/sha1"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"io/ioutil"
"math/big"
"testing"
)
// private key for mock server
const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
+IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
-----END RSA PRIVATE KEY-----`
const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld
r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ
tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC
nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW
2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB
y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr
rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg==
-----END RSA PRIVATE KEY-----`
// keychain implements the ClientPublickey interface
type keychain struct {
keys []interface{}
}
func (k *keychain) Key(i int) (interface{}, error) {
if i < 0 || i >= len(k.keys) {
return nil, nil
}
switch key := k.keys[i].(type) {
case *rsa.PrivateKey:
return key.PublicKey, nil
case *dsa.PrivateKey:
return key.PublicKey, nil
}
panic("unknown key type")
}
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
hashFunc := crypto.SHA1
h := hashFunc.New()
h.Write(data)
digest := h.Sum(nil)
switch key := k.keys[i].(type) {
case *rsa.PrivateKey:
return rsa.SignPKCS1v15(rand, key, hashFunc, digest)
}
return nil, errors.New("unknown key type")
}
func (k *keychain) loadPEM(file string) error {
buf, err := ioutil.ReadFile(file)
if err != nil {
return err
}
block, _ := pem.Decode(buf)
if block == nil {
return errors.New("ssh: no key found")
}
r, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
k.keys = append(k.keys, r)
return nil
}
// password implements the ClientPassword interface
type password string
func (p password) Password(user string) (string, error) {
return string(p), nil
}
// reused internally by tests
var (
rsakey *rsa.PrivateKey
dsakey *dsa.PrivateKey
clientKeychain = new(keychain)
clientPassword = password("tiger")
serverConfig = &ServerConfig{
PasswordCallback: func(user, pass string) bool {
return user == "testuser" && pass == string(clientPassword)
},
PublicKeyCallback: func(user, algo string, pubkey []byte) bool {
key := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey
expected := []byte(serializePublickey(key))
algoname := algoName(key)
return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
},
}
)
func init() {
if err := serverConfig.SetRSAPrivateKey([]byte(testServerPrivateKey)); err != nil {
panic("unable to set private key: " + err.Error())
}
block, _ := pem.Decode([]byte(testClientPrivateKey))
rsakey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
clientKeychain.keys = append(clientKeychain.keys, rsakey)
dsakey = new(dsa.PrivateKey)
// taken from crypto/dsa/dsa_test.go
dsakey.P, _ = new(big.Int).SetString("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF", 16)
dsakey.Q, _ = new(big.Int).SetString("E1D3391245933D68A0714ED34BBCB7A1F422B9C1", 16)
dsakey.G, _ = new(big.Int).SetString("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA", 16)
dsakey.Y, _ = new(big.Int).SetString("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2", 16)
dsakey.X, _ = new(big.Int).SetString("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A", 16)
}
// newMockAuthServer creates a new Server bound to
// the loopback interface. The server exits after
// processing one handshake.
func newMockAuthServer(t *testing.T) string {
l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
if err != nil {
t.Fatalf("unable to newMockAuthServer: %s", err)
}
go func() {
defer l.Close()
c, err := l.Accept()
defer c.Close()
if err != nil {
t.Errorf("Unable to accept incoming connection: %v", err)
return
}
if err := c.Handshake(); err != nil {
// not Errorf because this is expected to
// fail for some tests.
t.Logf("Handshaking error: %v", err)
return
}
}()
return l.Addr().String()
}
func TestClientAuthPublickey(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(clientKeychain),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthPassword(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(clientPassword),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
func TestClientAuthWrongPassword(t *testing.T) {
wrongPw := password("wrong")
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(wrongPw),
ClientAuthKeyring(clientKeychain),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
c.Close()
}
// the mock server will only authenticate ssh-rsa keys
func TestClientAuthInvalidPublickey(t *testing.T) {
kc := new(keychain)
kc.keys = append(kc.keys, dsakey)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err == nil {
c.Close()
t.Fatalf("dsa private key should not have authenticated with rsa public key")
}
}
// the client should authenticate with the second key
func TestClientAuthRSAandDSA(t *testing.T) {
kc := new(keychain)
kc.keys = append(kc.keys, dsakey, rsakey)
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
}
c, err := Dial("tcp", newMockAuthServer(t), config)
if err != nil {
t.Fatalf("client could not authenticate with rsa key: %v", err)
}
c.Close()
}
+61
View File
@@ -0,0 +1,61 @@
// 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
// ClientConn functional tests.
// These tests require a running ssh server listening on port 22
// on the local host. Functional tests will be skipped unless
// -ssh.user and -ssh.pass must be passed to gotest.
import (
"flag"
"testing"
)
var (
sshuser = flag.String("ssh.user", "", "ssh username")
sshpass = flag.String("ssh.pass", "", "ssh password")
sshprivkey = flag.String("ssh.privkey", "", "ssh privkey file")
)
func TestFuncPasswordAuth(t *testing.T) {
if *sshuser == "" {
t.Log("ssh.user not defined, skipping test")
return
}
config := &ClientConfig{
User: *sshuser,
Auth: []ClientAuth{
ClientAuthPassword(password(*sshpass)),
},
}
conn, err := Dial("tcp", "localhost:22", config)
if err != nil {
t.Fatalf("Unable to connect: %s", err)
}
defer conn.Close()
}
func TestFuncPublickeyAuth(t *testing.T) {
if *sshuser == "" {
t.Log("ssh.user not defined, skipping test")
return
}
kc := new(keychain)
if err := kc.loadPEM(*sshprivkey); err != nil {
t.Fatalf("unable to load private key: %s", err)
}
config := &ClientConfig{
User: *sshuser,
Auth: []ClientAuth{
ClientAuthKeyring(kc),
},
}
conn, err := Dial("tcp", "localhost:22", config)
if err != nil {
t.Fatalf("unable to connect: %s", err)
}
defer conn.Close()
}
+239
View File
@@ -0,0 +1,239 @@
// 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/dsa"
"crypto/rsa"
"math/big"
"strconv"
"sync"
)
// These are string constants in the SSH protocol.
const (
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
hostAlgoRSA = "ssh-rsa"
macSHA196 = "hmac-sha1-96"
compressionNone = "none"
serviceUserAuth = "ssh-userauth"
serviceSSH = "ssh-connection"
)
var supportedKexAlgos = []string{kexAlgoDH14SHA1}
var supportedHostKeyAlgos = []string{hostAlgoRSA}
var supportedMACs = []string{macSHA196}
var supportedCompressions = []string{compressionNone}
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
type dhGroup struct {
g, p *big.Int
}
// dhGroup14 is the group called diffie-hellman-group14-sha1 in RFC 4253 and
// Oakley Group 14 in RFC 3526.
var dhGroup14 *dhGroup
var dhGroup14Once sync.Once
func initDHGroup14() {
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
dhGroup14 = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
}
}
// UnexpectedMessageError results when the SSH message that we received didn't
// match what we wanted.
type UnexpectedMessageError struct {
expected, got uint8
}
func (u UnexpectedMessageError) Error() string {
return "ssh: unexpected message type " + strconv.Itoa(int(u.got)) + " (expected " + strconv.Itoa(int(u.expected)) + ")"
}
// ParseError results from a malformed SSH message.
type ParseError struct {
msgType uint8
}
func (p ParseError) Error() string {
return "ssh: parse error in message type " + strconv.Itoa(int(p.msgType))
}
type handshakeMagics struct {
clientVersion, serverVersion []byte
clientKexInit, serverKexInit []byte
}
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
for _, clientAlgo := range clientAlgos {
for _, serverAlgo := range serverAlgos {
if clientAlgo == serverAlgo {
return clientAlgo, true
}
}
}
return
}
func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *kexInitMsg) (kexAlgo, hostKeyAlgo string, ok bool) {
kexAlgo, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
if !ok {
return
}
hostKeyAlgo, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
if !ok {
return
}
transport.writer.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
if !ok {
return
}
transport.reader.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
if !ok {
return
}
transport.writer.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if !ok {
return
}
transport.reader.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if !ok {
return
}
transport.writer.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
if !ok {
return
}
transport.reader.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
if !ok {
return
}
ok = true
return
}
// Cryptographic configuration common to both ServerConfig and ClientConfig.
type CryptoConfig struct {
// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
// used.
Ciphers []string
}
func (c *CryptoConfig) ciphers() []string {
if c.Ciphers == nil {
return DefaultCipherOrder
}
return c.Ciphers
}
// serialize a signed slice according to RFC 4254 6.6.
func serializeSignature(algoname string, sig []byte) []byte {
length := stringLength([]byte(algoname))
length += stringLength(sig)
ret := make([]byte, length)
r := marshalString(ret, []byte(algoname))
r = marshalString(r, sig)
return ret
}
// serialize an rsa.PublicKey or dsa.PublicKey according to RFC 4253 6.6.
func serializePublickey(key interface{}) []byte {
algoname := algoName(key)
switch key := key.(type) {
case rsa.PublicKey:
e := new(big.Int).SetInt64(int64(key.E))
length := stringLength([]byte(algoname))
length += intLength(e)
length += intLength(key.N)
ret := make([]byte, length)
r := marshalString(ret, []byte(algoname))
r = marshalInt(r, e)
marshalInt(r, key.N)
return ret
case dsa.PublicKey:
length := stringLength([]byte(algoname))
length += intLength(key.P)
length += intLength(key.Q)
length += intLength(key.G)
length += intLength(key.Y)
ret := make([]byte, length)
r := marshalString(ret, []byte(algoname))
r = marshalInt(r, key.P)
r = marshalInt(r, key.Q)
r = marshalInt(r, key.G)
marshalInt(r, key.Y)
return ret
}
panic("unexpected key type")
}
func algoName(key interface{}) string {
switch key.(type) {
case rsa.PublicKey:
return "ssh-rsa"
case dsa.PublicKey:
return "ssh-dss"
}
panic("unexpected key type")
}
// buildDataSignedForAuth returns the data that is signed in order to prove
// posession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
user := []byte(req.User)
service := []byte(req.Service)
method := []byte(req.Method)
length := stringLength(sessionId)
length += 1
length += stringLength(user)
length += stringLength(service)
length += stringLength(method)
length += 1
length += stringLength(algo)
length += stringLength(pubKey)
ret := make([]byte, length)
r := marshalString(ret, sessionId)
r[0] = msgUserAuthRequest
r = r[1:]
r = marshalString(r, user)
r = marshalString(r, service)
r = marshalString(r, method)
r[0] = 1
r = r[1:]
r = marshalString(r, algo)
r = marshalString(r, pubKey)
return ret
}
// safeString sanitises s according to RFC 4251, section 9.2.
// All control characters except tab, carriage return and newline are
// replaced by 0x20.
func safeString(s string) string {
out := []byte(s)
for i, c := range out {
if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
out[i] = 0x20
}
}
return string(out)
}
+26
View File
@@ -0,0 +1,26 @@
// 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 (
"testing"
)
func TestSafeString(t *testing.T) {
strings := map[string]string{
"\x20\x0d\x0a": "\x20\x0d\x0a",
"flibble": "flibble",
"new\x20line": "new\x20line",
"123456\x07789": "123456 789",
"\t\t\x10\r\n": "\t\t \r\n",
}
for s, expected := range strings {
actual := safeString(s)
if expected != actual {
t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
}
}
}
+127
View File
@@ -0,0 +1,127 @@
// 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 implements an SSH client and server.
SSH is a transport security protocol, an authentication protocol and a
family of application protocols. The most typical application level
protocol is a remote shell and this is specifically implemented. However,
the multiplexed nature of SSH is exposed to users that wish to support
others.
An SSH server is represented by a ServerConfig, which holds certificate
details and handles authentication of ServerConns.
config := new(ssh.ServerConfig)
config.PubKeyCallback = pubKeyAuth
config.PasswordCallback = passwordAuth
pemBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
panic("Failed to load private key")
}
err = config.SetRSAPrivateKey(pemBytes)
if err != nil {
panic("Failed to parse private key")
}
Once a ServerConfig has been configured, connections can be accepted.
listener := Listen("tcp", "0.0.0.0:2022", config)
sConn, err := listener.Accept()
if err != nil {
panic("failed to accept incoming connection")
}
if err := sConn.Handshake(conn); err != nil {
panic("failed to handshake")
}
An SSH connection multiplexes several channels, which must be accepted themselves:
for {
channel, err := sConn.Accept()
if err != nil {
panic("error from Accept")
}
...
}
Accept reads from the connection, demultiplexes packets to their corresponding
channels and returns when a new channel request is seen. Some goroutine must
always be calling Accept; otherwise no messages will be forwarded to the
channels.
Channels have a type, depending on the application level protocol intended. In
the case of a shell, the type is "session" and ServerShell may be used to
present a simple terminal interface.
if channel.ChannelType() != "session" {
channel.Reject(UnknownChannelType, "unknown channel type")
return
}
channel.Accept()
term := terminal.NewTerminal(channel, "> ")
serverTerm := &ssh.ServerTerminal{
Term: term,
Channel: channel,
}
go func() {
defer channel.Close()
for {
line, err := serverTerm.ReadLine()
if err != nil {
break
}
println(line)
}
return
}()
To authenticate with the remote server you must pass at least one implementation of
ClientAuth via the Auth field in ClientConfig.
// password implements the ClientPassword interface
type password string
func (p password) Password(user string) (string, error) {
return string(p), nil
}
config := &ssh.ClientConfig {
User: "username",
Auth: []ClientAuth {
// ClientAuthPassword wraps a ClientPassword implementation
// in a type that implements ClientAuth.
ClientAuthPassword(password("yourpassword")),
}
}
An SSH client is represented with a ClientConn. Currently only the "password"
authentication method is supported.
config := &ClientConfig{
User: "username",
Auth: []ClientAuth{ ... },
}
client, err := Dial("yourserver.com:22", config)
Each ClientConn can support multiple interactive sessions, represented by a Session.
session, err := client.NewSession()
Once a Session is created, you can execute a single command on the remote side
using the Exec method.
b := bytes.NewBuffer()
session.Stdin = b
if err := session.Run("/usr/bin/whoami"); err != nil {
panic("Failed to exec: " + err.String())
}
fmt.Println(bytes.String())
session.Close()
*/
package ssh
+640
View File
@@ -0,0 +1,640 @@
// 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 (
"bytes"
"io"
"math/big"
"reflect"
)
// These are SSH message type numbers. They are scattered around several
// documents but many were taken from
// http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
const (
msgDisconnect = 1
msgIgnore = 2
msgUnimplemented = 3
msgDebug = 4
msgServiceRequest = 5
msgServiceAccept = 6
msgKexInit = 20
msgNewKeys = 21
msgKexDHInit = 30
msgKexDHReply = 31
msgUserAuthRequest = 50
msgUserAuthFailure = 51
msgUserAuthSuccess = 52
msgUserAuthBanner = 53
msgUserAuthPubKeyOk = 60
msgGlobalRequest = 80
msgRequestSuccess = 81
msgRequestFailure = 82
msgChannelOpen = 90
msgChannelOpenConfirm = 91
msgChannelOpenFailure = 92
msgChannelWindowAdjust = 93
msgChannelData = 94
msgChannelExtendedData = 95
msgChannelEOF = 96
msgChannelClose = 97
msgChannelRequest = 98
msgChannelSuccess = 99
msgChannelFailure = 100
)
// SSH messages:
//
// These structures mirror the wire format of the corresponding SSH messages.
// They are marshaled using reflection with the marshal and unmarshal functions
// in this file. The only wrinkle is that a final member of type []byte with a
// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
// See RFC 4253, section 11.1.
type disconnectMsg struct {
Reason uint32
Message string
Language string
}
// See RFC 4253, section 7.1.
type kexInitMsg struct {
Cookie [16]byte
KexAlgos []string
ServerHostKeyAlgos []string
CiphersClientServer []string
CiphersServerClient []string
MACsClientServer []string
MACsServerClient []string
CompressionClientServer []string
CompressionServerClient []string
LanguagesClientServer []string
LanguagesServerClient []string
FirstKexFollows bool
Reserved uint32
}
// See RFC 4253, section 8.
type kexDHInitMsg struct {
X *big.Int
}
type kexDHReplyMsg struct {
HostKey []byte
Y *big.Int
Signature []byte
}
// See RFC 4253, section 10.
type serviceRequestMsg struct {
Service string
}
// See RFC 4253, section 10.
type serviceAcceptMsg struct {
Service string
}
// See RFC 4252, section 5.
type userAuthRequestMsg struct {
User string
Service string
Method string
Payload []byte `ssh:"rest"`
}
// See RFC 4252, section 5.1
type userAuthFailureMsg struct {
Methods []string
PartialSuccess bool
}
// See RFC 4254, section 5.1.
type channelOpenMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
type channelOpenConfirmMsg struct {
PeersId uint32
MyId uint32
MyWindow uint32
MaxPacketSize uint32
TypeSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.1.
type channelOpenFailureMsg struct {
PeersId uint32
Reason uint32
Message string
Language string
}
type channelRequestMsg struct {
PeersId uint32
Request string
WantReply bool
RequestSpecificData []byte `ssh:"rest"`
}
// See RFC 4254, section 5.4.
type channelRequestSuccessMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.4.
type channelRequestFailureMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.3
type channelCloseMsg struct {
PeersId uint32
}
// See RFC 4254, section 5.3
type channelEOFMsg struct {
PeersId uint32
}
// See RFC 4254, section 4
type globalRequestMsg struct {
Type string
WantReply bool
}
// See RFC 4254, section 5.2
type windowAdjustMsg struct {
PeersId uint32
AdditionalBytes uint32
}
// See RFC 4252, section 7
type userAuthPubKeyOkMsg struct {
Algo string
PubKey string
}
// unmarshal parses the SSH wire data in packet into out using reflection.
// expectedType is the expected SSH message type. It either returns nil on
// success, or a ParseError or UnexpectedMessageError on error.
func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
if len(packet) == 0 {
return ParseError{expectedType}
}
if packet[0] != expectedType {
return UnexpectedMessageError{expectedType, packet[0]}
}
packet = packet[1:]
v := reflect.ValueOf(out).Elem()
structType := v.Type()
var ok bool
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
t := field.Type()
switch t.Kind() {
case reflect.Bool:
if len(packet) < 1 {
return ParseError{expectedType}
}
field.SetBool(packet[0] != 0)
packet = packet[1:]
case reflect.Array:
if t.Elem().Kind() != reflect.Uint8 {
panic("array of non-uint8")
}
if len(packet) < t.Len() {
return ParseError{expectedType}
}
for j := 0; j < t.Len(); j++ {
field.Index(j).Set(reflect.ValueOf(packet[j]))
}
packet = packet[t.Len():]
case reflect.Uint32:
var u32 uint32
if u32, packet, ok = parseUint32(packet); !ok {
return ParseError{expectedType}
}
field.SetUint(uint64(u32))
case reflect.String:
var s []byte
if s, packet, ok = parseString(packet); !ok {
return ParseError{expectedType}
}
field.SetString(string(s))
case reflect.Slice:
switch t.Elem().Kind() {
case reflect.Uint8:
if structType.Field(i).Tag.Get("ssh") == "rest" {
field.Set(reflect.ValueOf(packet))
packet = nil
} else {
var s []byte
if s, packet, ok = parseString(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(s))
}
case reflect.String:
var nl []string
if nl, packet, ok = parseNameList(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(nl))
default:
panic("slice of unknown type")
}
case reflect.Ptr:
if t == bigIntType {
var n *big.Int
if n, packet, ok = parseInt(packet); !ok {
return ParseError{expectedType}
}
field.Set(reflect.ValueOf(n))
} else {
panic("pointer to unknown type")
}
default:
panic("unknown type")
}
}
if len(packet) != 0 {
return ParseError{expectedType}
}
return nil
}
// marshal serializes the message in msg, using the given message type.
func marshal(msgType uint8, msg interface{}) []byte {
var out []byte
out = append(out, msgType)
v := reflect.ValueOf(msg)
structType := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
t := field.Type()
switch t.Kind() {
case reflect.Bool:
var v uint8
if field.Bool() {
v = 1
}
out = append(out, v)
case reflect.Array:
if t.Elem().Kind() != reflect.Uint8 {
panic("array of non-uint8")
}
for j := 0; j < t.Len(); j++ {
out = append(out, byte(field.Index(j).Uint()))
}
case reflect.Uint32:
u32 := uint32(field.Uint())
out = append(out, byte(u32>>24))
out = append(out, byte(u32>>16))
out = append(out, byte(u32>>8))
out = append(out, byte(u32))
case reflect.String:
s := field.String()
out = append(out, byte(len(s)>>24))
out = append(out, byte(len(s)>>16))
out = append(out, byte(len(s)>>8))
out = append(out, byte(len(s)))
out = append(out, s...)
case reflect.Slice:
switch t.Elem().Kind() {
case reflect.Uint8:
length := field.Len()
if structType.Field(i).Tag.Get("ssh") != "rest" {
out = append(out, byte(length>>24))
out = append(out, byte(length>>16))
out = append(out, byte(length>>8))
out = append(out, byte(length))
}
for j := 0; j < length; j++ {
out = append(out, byte(field.Index(j).Uint()))
}
case reflect.String:
var length int
for j := 0; j < field.Len(); j++ {
if j != 0 {
length++ /* comma */
}
length += len(field.Index(j).String())
}
out = append(out, byte(length>>24))
out = append(out, byte(length>>16))
out = append(out, byte(length>>8))
out = append(out, byte(length))
for j := 0; j < field.Len(); j++ {
if j != 0 {
out = append(out, ',')
}
out = append(out, field.Index(j).String()...)
}
default:
panic("slice of unknown type")
}
case reflect.Ptr:
if t == bigIntType {
var n *big.Int
nValue := reflect.ValueOf(&n)
nValue.Elem().Set(field)
needed := intLength(n)
oldLength := len(out)
if cap(out)-len(out) < needed {
newOut := make([]byte, len(out), 2*(len(out)+needed))
copy(newOut, out)
out = newOut
}
out = out[:oldLength+needed]
marshalInt(out[oldLength:], n)
} else {
panic("pointer to unknown type")
}
}
}
return out
}
var bigOne = big.NewInt(1)
func parseString(in []byte) (out, rest []byte, ok bool) {
if len(in) < 4 {
return
}
length := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
if uint32(len(in)) < 4+length {
return
}
out = in[4 : 4+length]
rest = in[4+length:]
ok = true
return
}
var (
comma = []byte{','}
emptyNameList = []string{}
)
func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
contents, rest, ok := parseString(in)
if !ok {
return
}
if len(contents) == 0 {
out = emptyNameList
return
}
parts := bytes.Split(contents, comma)
out = make([]string, len(parts))
for i, part := range parts {
out[i] = string(part)
}
return
}
func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
contents, rest, ok := parseString(in)
if !ok {
return
}
out = new(big.Int)
if len(contents) > 0 && contents[0]&0x80 == 0x80 {
// This is a negative number
notBytes := make([]byte, len(contents))
for i := range notBytes {
notBytes[i] = ^contents[i]
}
out.SetBytes(notBytes)
out.Add(out, bigOne)
out.Neg(out)
} else {
// Positive number
out.SetBytes(contents)
}
ok = true
return
}
func parseUint32(in []byte) (out uint32, rest []byte, ok bool) {
if len(in) < 4 {
return
}
out = uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
rest = in[4:]
ok = true
return
}
func nameListLength(namelist []string) int {
length := 4 /* uint32 length prefix */
for i, name := range namelist {
if i != 0 {
length++ /* comma */
}
length += len(name)
}
return length
}
func intLength(n *big.Int) int {
length := 4 /* length bytes */
if n.Sign() < 0 {
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bitLen := nMinus1.BitLen()
if bitLen%8 == 0 {
// The number will need 0xff padding
length++
}
length += (bitLen + 7) / 8
} else if n.Sign() == 0 {
// A zero is the zero length string
} else {
bitLen := n.BitLen()
if bitLen%8 == 0 {
// The number will need 0x00 padding
length++
}
length += (bitLen + 7) / 8
}
return length
}
func marshalUint32(to []byte, n uint32) []byte {
to[0] = byte(n >> 24)
to[1] = byte(n >> 16)
to[2] = byte(n >> 8)
to[3] = byte(n)
return to[4:]
}
func marshalUint64(to []byte, n uint64) []byte {
to[0] = byte(n >> 56)
to[1] = byte(n >> 48)
to[2] = byte(n >> 40)
to[3] = byte(n >> 32)
to[4] = byte(n >> 24)
to[5] = byte(n >> 16)
to[6] = byte(n >> 8)
to[7] = byte(n)
return to[8:]
}
func marshalInt(to []byte, n *big.Int) []byte {
lengthBytes := to
to = to[4:]
length := 0
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement
// form. So we'll subtract 1 and invert. If the
// most-significant-bit isn't set then we'll need to pad the
// beginning with 0xff in order to keep the number negative.
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bytes := nMinus1.Bytes()
for i := range bytes {
bytes[i] ^= 0xff
}
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
to[0] = 0xff
to = to[1:]
length++
}
nBytes := copy(to, bytes)
to = to[nBytes:]
length += nBytes
} else if n.Sign() == 0 {
// A zero is the zero length string
} else {
bytes := n.Bytes()
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
// We'll have to pad this with a 0x00 in order to
// stop it looking like a negative number.
to[0] = 0
to = to[1:]
length++
}
nBytes := copy(to, bytes)
to = to[nBytes:]
length += nBytes
}
lengthBytes[0] = byte(length >> 24)
lengthBytes[1] = byte(length >> 16)
lengthBytes[2] = byte(length >> 8)
lengthBytes[3] = byte(length)
return to
}
func writeInt(w io.Writer, n *big.Int) {
length := intLength(n)
buf := make([]byte, length)
marshalInt(buf, n)
w.Write(buf)
}
func writeString(w io.Writer, s []byte) {
var lengthBytes [4]byte
lengthBytes[0] = byte(len(s) >> 24)
lengthBytes[1] = byte(len(s) >> 16)
lengthBytes[2] = byte(len(s) >> 8)
lengthBytes[3] = byte(len(s))
w.Write(lengthBytes[:])
w.Write(s)
}
func stringLength(s []byte) int {
return 4 + len(s)
}
func marshalString(to []byte, s []byte) []byte {
to[0] = byte(len(s) >> 24)
to[1] = byte(len(s) >> 16)
to[2] = byte(len(s) >> 8)
to[3] = byte(len(s))
to = to[4:]
copy(to, s)
return to[len(s):]
}
var bigIntType = reflect.TypeOf((*big.Int)(nil))
// Decode a packet into it's corresponding message.
func decode(packet []byte) interface{} {
var msg interface{}
switch packet[0] {
case msgDisconnect:
msg = new(disconnectMsg)
case msgServiceRequest:
msg = new(serviceRequestMsg)
case msgServiceAccept:
msg = new(serviceAcceptMsg)
case msgKexInit:
msg = new(kexInitMsg)
case msgKexDHInit:
msg = new(kexDHInitMsg)
case msgKexDHReply:
msg = new(kexDHReplyMsg)
case msgUserAuthRequest:
msg = new(userAuthRequestMsg)
case msgUserAuthFailure:
msg = new(userAuthFailureMsg)
case msgUserAuthPubKeyOk:
msg = new(userAuthPubKeyOkMsg)
case msgGlobalRequest:
msg = new(globalRequestMsg)
case msgRequestSuccess:
msg = new(channelRequestSuccessMsg)
case msgRequestFailure:
msg = new(channelRequestFailureMsg)
case msgChannelOpen:
msg = new(channelOpenMsg)
case msgChannelOpenConfirm:
msg = new(channelOpenConfirmMsg)
case msgChannelOpenFailure:
msg = new(channelOpenFailureMsg)
case msgChannelWindowAdjust:
msg = new(windowAdjustMsg)
case msgChannelEOF:
msg = new(channelEOFMsg)
case msgChannelClose:
msg = new(channelCloseMsg)
case msgChannelRequest:
msg = new(channelRequestMsg)
case msgChannelSuccess:
msg = new(channelRequestSuccessMsg)
case msgChannelFailure:
msg = new(channelRequestFailureMsg)
default:
return UnexpectedMessageError{0, packet[0]}
}
if err := unmarshal(msg, packet, packet[0]); err != nil {
return err
}
return msg
}
+125
View File
@@ -0,0 +1,125 @@
// 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 (
"math/big"
"math/rand"
"reflect"
"testing"
"testing/quick"
)
var intLengthTests = []struct {
val, length int
}{
{0, 4 + 0},
{1, 4 + 1},
{127, 4 + 1},
{128, 4 + 2},
{-1, 4 + 1},
}
func TestIntLength(t *testing.T) {
for _, test := range intLengthTests {
v := new(big.Int).SetInt64(int64(test.val))
length := intLength(v)
if length != test.length {
t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
}
}
}
var messageTypes = []interface{}{
&kexInitMsg{},
&kexDHInitMsg{},
&serviceRequestMsg{},
&serviceAcceptMsg{},
&userAuthRequestMsg{},
&channelOpenMsg{},
&channelOpenConfirmMsg{},
&channelRequestMsg{},
&channelRequestSuccessMsg{},
}
func TestMarshalUnmarshal(t *testing.T) {
rand := rand.New(rand.NewSource(0))
for i, iface := range messageTypes {
ty := reflect.ValueOf(iface).Type()
n := 100
if testing.Short() {
n = 5
}
for j := 0; j < n; j++ {
v, ok := quick.Value(ty, rand)
if !ok {
t.Errorf("#%d: failed to create value", i)
break
}
m1 := v.Elem().Interface()
m2 := iface
marshaled := marshal(msgIgnore, m1)
if err := unmarshal(m2, marshaled, msgIgnore); err != nil {
t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err)
break
}
if !reflect.DeepEqual(v.Interface(), m2) {
t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled)
break
}
}
}
}
func randomBytes(out []byte, rand *rand.Rand) {
for i := 0; i < len(out); i++ {
out[i] = byte(rand.Int31())
}
}
func randomNameList(rand *rand.Rand) []string {
ret := make([]string, rand.Int31()&15)
for i := range ret {
s := make([]byte, 1+(rand.Int31()&15))
for j := range s {
s[j] = 'a' + uint8(rand.Int31()&15)
}
ret[i] = string(s)
}
return ret
}
func randomInt(rand *rand.Rand) *big.Int {
return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
}
func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
ki := &kexInitMsg{}
randomBytes(ki.Cookie[:], rand)
ki.KexAlgos = randomNameList(rand)
ki.ServerHostKeyAlgos = randomNameList(rand)
ki.CiphersClientServer = randomNameList(rand)
ki.CiphersServerClient = randomNameList(rand)
ki.MACsClientServer = randomNameList(rand)
ki.MACsServerClient = randomNameList(rand)
ki.CompressionClientServer = randomNameList(rand)
ki.CompressionServerClient = randomNameList(rand)
ki.LanguagesClientServer = randomNameList(rand)
ki.LanguagesServerClient = randomNameList(rand)
if rand.Int31()&1 == 1 {
ki.FirstKexFollows = true
}
return reflect.ValueOf(ki)
}
func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
dhi := &kexDHInitMsg{}
dhi.X = randomInt(rand)
return reflect.ValueOf(dhi)
}
+676
View File
@@ -0,0 +1,676 @@
// 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 (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"math/big"
"net"
"sync"
)
type ServerConfig struct {
rsa *rsa.PrivateKey
rsaSerialized []byte
// 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
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
NoClientAuth bool
// PasswordCallback, if non-nil, is called when a user attempts to
// authenticate using a password. It may be called concurrently from
// several goroutines.
PasswordCallback func(user, password string) bool
// PublicKeyCallback, if non-nil, is called when a client attempts public
// key authentication. It must return true iff the given public key is
// valid for the given user.
PublicKeyCallback func(user, algo string, pubkey []byte) bool
// Cryptographic-related configuration.
Crypto CryptoConfig
}
func (c *ServerConfig) rand() io.Reader {
if c.Rand == nil {
return rand.Reader
}
return c.Rand
}
// SetRSAPrivateKey sets the private key for a Server. A Server must have a
// private key configured in order to accept connections. The private key must
// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
// typically contains such a key.
func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
block, _ := pem.Decode(pemBytes)
if block == nil {
return errors.New("ssh: no key found")
}
var err error
s.rsa, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
s.rsaSerialized = marshalRSA(s.rsa)
return nil
}
// marshalRSA serializes an RSA private key according to RFC 4256, section 6.6.
func marshalRSA(priv *rsa.PrivateKey) []byte {
e := new(big.Int).SetInt64(int64(priv.E))
length := stringLength([]byte(hostAlgoRSA))
length += intLength(e)
length += intLength(priv.N)
ret := make([]byte, length)
r := marshalString(ret, []byte(hostAlgoRSA))
r = marshalInt(r, e)
r = marshalInt(r, priv.N)
return ret
}
// parseRSA parses an RSA key according to RFC 4256, section 6.6.
func parseRSA(in []byte) (pubKey *rsa.PublicKey, ok bool) {
algo, in, ok := parseString(in)
if !ok || string(algo) != hostAlgoRSA {
return nil, false
}
bigE, in, ok := parseInt(in)
if !ok || bigE.BitLen() > 24 {
return nil, false
}
e := bigE.Int64()
if e < 3 || e&1 == 0 {
return nil, false
}
N, in, ok := parseInt(in)
if !ok || len(in) > 0 {
return nil, false
}
return &rsa.PublicKey{
N: N,
E: int(e),
}, true
}
func parseRSASig(in []byte) (sig []byte, ok bool) {
algo, in, ok := parseString(in)
if !ok || string(algo) != hostAlgoRSA {
return nil, false
}
sig, in, ok = parseString(in)
if len(in) > 0 {
ok = false
}
return
}
// cachedPubKey contains the results of querying whether a public key is
// acceptable for a user. The cache only applies to a single ServerConn.
type cachedPubKey struct {
user, algo string
pubKey []byte
result bool
}
const maxCachedPubKeys = 16
// A ServerConn represents an incomming connection.
type ServerConn struct {
*transport
config *ServerConfig
channels map[uint32]*channel
nextChanId uint32
// lock protects err and also allows Channels to serialise their writes
// to out.
lock sync.RWMutex
err error
// cachedPubKeys contains the cache results of tests for public keys.
// Since SSH clients will query whether a public key is acceptable
// before attempting to authenticate with it, we end up with duplicate
// queries for public key validity.
cachedPubKeys []cachedPubKey
}
// Server returns a new SSH server connection
// using c as the underlying transport.
func Server(c net.Conn, config *ServerConfig) *ServerConn {
conn := &ServerConn{
transport: newTransport(c, config.rand()),
channels: make(map[uint32]*channel),
config: config,
}
return conn
}
// kexDH performs Diffie-Hellman key agreement on a ServerConnection. The
// returned values are given the same names as in RFC 4253, section 8.
func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err error) {
packet, err := s.readPacket()
if err != nil {
return
}
var kexDHInit kexDHInitMsg
if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil {
return
}
if kexDHInit.X.Sign() == 0 || kexDHInit.X.Cmp(group.p) >= 0 {
return nil, nil, errors.New("client DH parameter out of bounds")
}
y, err := rand.Int(s.config.rand(), group.p)
if err != nil {
return
}
Y := new(big.Int).Exp(group.g, y, group.p)
kInt := new(big.Int).Exp(kexDHInit.X, y, group.p)
var serializedHostKey []byte
switch hostKeyAlgo {
case hostAlgoRSA:
serializedHostKey = s.config.rsaSerialized
default:
return nil, nil, errors.New("internal error")
}
h := hashFunc.New()
writeString(h, magics.clientVersion)
writeString(h, magics.serverVersion)
writeString(h, magics.clientKexInit)
writeString(h, magics.serverKexInit)
writeString(h, serializedHostKey)
writeInt(h, kexDHInit.X)
writeInt(h, Y)
K = make([]byte, intLength(kInt))
marshalInt(K, kInt)
h.Write(K)
H = h.Sum(nil)
h.Reset()
h.Write(H)
hh := h.Sum(nil)
var sig []byte
switch hostKeyAlgo {
case hostAlgoRSA:
sig, err = rsa.SignPKCS1v15(s.config.rand(), s.config.rsa, hashFunc, hh)
if err != nil {
return
}
default:
return nil, nil, errors.New("internal error")
}
serializedSig := serializeSignature(hostAlgoRSA, sig)
kexDHReply := kexDHReplyMsg{
HostKey: serializedHostKey,
Y: Y,
Signature: serializedSig,
}
packet = marshal(msgKexDHReply, kexDHReply)
err = s.writePacket(packet)
return
}
// serverVersion is the fixed identification string that Server will use.
var serverVersion = []byte("SSH-2.0-Go\r\n")
// Handshake performs an SSH transport and client authentication on the given ServerConn.
func (s *ServerConn) Handshake() error {
var magics handshakeMagics
if _, err := s.Write(serverVersion); err != nil {
return err
}
if err := s.Flush(); err != nil {
return err
}
magics.serverVersion = serverVersion[:len(serverVersion)-2]
version, err := readVersion(s)
if err != nil {
return err
}
magics.clientVersion = version
serverKexInit := kexInitMsg{
KexAlgos: supportedKexAlgos,
ServerHostKeyAlgos: supportedHostKeyAlgos,
CiphersClientServer: s.config.Crypto.ciphers(),
CiphersServerClient: s.config.Crypto.ciphers(),
MACsClientServer: supportedMACs,
MACsServerClient: supportedMACs,
CompressionClientServer: supportedCompressions,
CompressionServerClient: supportedCompressions,
}
kexInitPacket := marshal(msgKexInit, serverKexInit)
magics.serverKexInit = kexInitPacket
if err := s.writePacket(kexInitPacket); err != nil {
return err
}
packet, err := s.readPacket()
if err != nil {
return err
}
magics.clientKexInit = packet
var clientKexInit kexInitMsg
if err = unmarshal(&clientKexInit, packet, msgKexInit); err != nil {
return err
}
kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(s.transport, &clientKexInit, &serverKexInit)
if !ok {
return errors.New("ssh: no common algorithms")
}
if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] {
// The client sent a Kex message for the wrong algorithm,
// which we have to ignore.
if _, err := s.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 = s.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
default:
err = errors.New("ssh: unexpected key exchange algorithm " + kexAlgo)
}
if err != nil {
return err
}
if err = s.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
if err = s.transport.writer.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
return err
}
if packet, err = s.readPacket(); err != nil {
return err
}
if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]}
}
if err = s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
return err
}
if packet, err = s.readPacket(); err != nil {
return err
}
var serviceRequest serviceRequestMsg
if err = unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil {
return err
}
if serviceRequest.Service != serviceUserAuth {
return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
}
serviceAccept := serviceAcceptMsg{
Service: serviceUserAuth,
}
if err = s.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
return err
}
if err = s.authenticate(H); err != nil {
return err
}
return nil
}
func isAcceptableAlgo(algo string) bool {
return algo == hostAlgoRSA
}
// testPubKey returns true if the given public key is acceptable for the user.
func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) {
return false
}
for _, c := range s.cachedPubKeys {
if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) {
return c.result
}
}
result := s.config.PublicKeyCallback(user, algo, pubKey)
if len(s.cachedPubKeys) < maxCachedPubKeys {
c := cachedPubKey{
user: user,
algo: algo,
pubKey: make([]byte, len(pubKey)),
result: result,
}
copy(c.pubKey, pubKey)
s.cachedPubKeys = append(s.cachedPubKeys, c)
}
return result
}
func (s *ServerConn) authenticate(H []byte) error {
var userAuthReq userAuthRequestMsg
var err error
var packet []byte
userAuthLoop:
for {
if packet, err = s.readPacket(); err != nil {
return err
}
if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil {
return err
}
if userAuthReq.Service != serviceSSH {
return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
}
switch userAuthReq.Method {
case "none":
if s.config.NoClientAuth {
break userAuthLoop
}
case "password":
if s.config.PasswordCallback == nil {
break
}
payload := userAuthReq.Payload
if len(payload) < 1 || payload[0] != 0 {
return ParseError{msgUserAuthRequest}
}
payload = payload[1:]
password, payload, ok := parseString(payload)
if !ok || len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
if s.config.PasswordCallback(userAuthReq.User, string(password)) {
break userAuthLoop
}
case "publickey":
if s.config.PublicKeyCallback == nil {
break
}
payload := userAuthReq.Payload
if len(payload) < 1 {
return ParseError{msgUserAuthRequest}
}
isQuery := payload[0] == 0
payload = payload[1:]
algoBytes, payload, ok := parseString(payload)
if !ok {
return ParseError{msgUserAuthRequest}
}
algo := string(algoBytes)
pubKey, payload, ok := parseString(payload)
if !ok {
return ParseError{msgUserAuthRequest}
}
if isQuery {
// The client can query if the given public key
// would be ok.
if len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
if s.testPubKey(userAuthReq.User, algo, pubKey) {
okMsg := userAuthPubKeyOkMsg{
Algo: algo,
PubKey: string(pubKey),
}
if err = s.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil {
return err
}
continue userAuthLoop
}
} else {
sig, payload, ok := parseString(payload)
if !ok || len(payload) > 0 {
return ParseError{msgUserAuthRequest}
}
if !isAcceptableAlgo(algo) {
break
}
rsaSig, ok := parseRSASig(sig)
if !ok {
return ParseError{msgUserAuthRequest}
}
signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
switch algo {
case hostAlgoRSA:
hashFunc := crypto.SHA1
h := hashFunc.New()
h.Write(signedData)
digest := h.Sum(nil)
rsaKey, ok := parseRSA(pubKey)
if !ok {
return ParseError{msgUserAuthRequest}
}
if rsa.VerifyPKCS1v15(rsaKey, hashFunc, digest, rsaSig) != nil {
return ParseError{msgUserAuthRequest}
}
default:
return errors.New("ssh: isAcceptableAlgo incorrect")
}
if s.testPubKey(userAuthReq.User, algo, pubKey) {
break userAuthLoop
}
}
}
var failureMsg userAuthFailureMsg
if s.config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password")
}
if s.config.PublicKeyCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "publickey")
}
if len(failureMsg.Methods) == 0 {
return errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
}
if err = s.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil {
return err
}
}
packet = []byte{msgUserAuthSuccess}
if err = s.writePacket(packet); err != nil {
return err
}
return nil
}
const defaultWindowSize = 32768
// Accept reads and processes messages on a ServerConn. It must be called
// in order to demultiplex messages to any resulting Channels.
func (s *ServerConn) Accept() (Channel, error) {
if s.err != nil {
return nil, s.err
}
for {
packet, err := s.readPacket()
if err != nil {
s.lock.Lock()
s.err = err
s.lock.Unlock()
for _, c := range s.channels {
c.dead = true
c.handleData(nil)
}
return nil, err
}
switch packet[0] {
case msgChannelData:
if len(packet) < 9 {
// malformed data packet
return nil, ParseError{msgChannelData}
}
peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
s.lock.Lock()
c, ok := s.channels[peersId]
if !ok {
s.lock.Unlock()
continue
}
if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
packet = packet[9:]
c.handleData(packet[:length])
}
s.lock.Unlock()
default:
switch msg := decode(packet).(type) {
case *channelOpenMsg:
c := new(channel)
c.chanType = msg.ChanType
c.theirId = msg.PeersId
c.theirWindow = msg.PeersWindow
c.maxPacketSize = msg.MaxPacketSize
c.extraData = msg.TypeSpecificData
c.myWindow = defaultWindowSize
c.serverConn = s
c.cond = sync.NewCond(&c.lock)
c.pendingData = make([]byte, c.myWindow)
s.lock.Lock()
c.myId = s.nextChanId
s.nextChanId++
s.channels[c.myId] = c
s.lock.Unlock()
return c, nil
case *channelRequestMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *channelEOFMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *channelCloseMsg:
s.lock.Lock()
c, ok := s.channels[msg.PeersId]
if !ok {
s.lock.Unlock()
continue
}
c.handlePacket(msg)
s.lock.Unlock()
case *globalRequestMsg:
if msg.WantReply {
if err := s.writePacket([]byte{msgRequestFailure}); err != nil {
return nil, err
}
}
case UnexpectedMessageError:
return nil, msg
case *disconnectMsg:
return nil, io.EOF
default:
// Unknown message. Ignore.
}
}
}
panic("unreachable")
}
// A Listener implements a network listener (net.Listener) for SSH connections.
type Listener struct {
listener net.Listener
config *ServerConfig
}
// Accept waits for and returns the next incoming SSH connection.
// The receiver should call Handshake() in another goroutine
// to avoid blocking the accepter.
func (l *Listener) Accept() (*ServerConn, error) {
c, err := l.listener.Accept()
if err != nil {
return nil, err
}
conn := Server(c, l.config)
return conn, nil
}
// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
return l.listener.Addr()
}
// Close closes the listener.
func (l *Listener) Close() error {
return l.listener.Close()
}
// Listen creates an SSH listener accepting connections on
// the given network address using net.Listen.
func Listen(network, addr string, config *ServerConfig) (*Listener, error) {
l, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
return &Listener{
l,
config,
}, nil
}
+81
View File
@@ -0,0 +1,81 @@
// 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
// A Terminal is capable of parsing and generating virtual terminal
// data from an SSH client.
type Terminal interface {
ReadLine() (line string, err error)
SetSize(x, y int)
Write([]byte) (int, error)
}
// ServerTerminal contains the state for running a terminal that is capable of
// reading lines of input.
type ServerTerminal struct {
Term Terminal
Channel Channel
}
// parsePtyRequest parses the payload of the pty-req message and extracts the
// dimensions of the terminal. See RFC 4254, section 6.2.
func parsePtyRequest(s []byte) (width, height int, ok bool) {
_, s, ok = parseString(s)
if !ok {
return
}
width32, s, ok := parseUint32(s)
if !ok {
return
}
height32, _, ok := parseUint32(s)
width = int(width32)
height = int(height32)
if width < 1 {
ok = false
}
if height < 1 {
ok = false
}
return
}
func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
return ss.Term.Write(buf)
}
// ReadLine returns a line of input from the terminal.
func (ss *ServerTerminal) ReadLine() (line string, err error) {
for {
if line, err = ss.Term.ReadLine(); err == nil {
return
}
req, ok := err.(ChannelRequest)
if !ok {
return
}
ok = false
switch req.Request {
case "pty-req":
var width, height int
width, height, ok = parsePtyRequest(req.Payload)
ss.Term.SetSize(width, height)
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any commands, only the default shell.
ok = false
}
case "env":
ok = true
}
if req.WantReply {
ss.Channel.AckRequest(ok)
}
}
panic("unreachable")
}
+494
View File
@@ -0,0 +1,494 @@
// 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
// Session implements an interactive session described in
// "RFC 4254, section 6".
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
)
type Signal string
// POSIX signals as listed in RFC 4254 Section 6.10.
const (
SIGABRT Signal = "ABRT"
SIGALRM Signal = "ALRM"
SIGFPE Signal = "FPE"
SIGHUP Signal = "HUP"
SIGILL Signal = "ILL"
SIGINT Signal = "INT"
SIGKILL Signal = "KILL"
SIGPIPE Signal = "PIPE"
SIGQUIT Signal = "QUIT"
SIGSEGV Signal = "SEGV"
SIGTERM Signal = "TERM"
SIGUSR1 Signal = "USR1"
SIGUSR2 Signal = "USR2"
)
var signals = map[Signal]int{
SIGABRT: 6,
SIGALRM: 14,
SIGFPE: 8,
SIGHUP: 1,
SIGILL: 4,
SIGINT: 2,
SIGKILL: 9,
SIGPIPE: 13,
SIGQUIT: 3,
SIGSEGV: 11,
SIGTERM: 15,
}
// A Session represents a connection to a remote command or shell.
type Session struct {
// Stdin specifies the remote process's standard input.
// If Stdin is nil, the remote process reads from an empty
// bytes.Buffer.
Stdin io.Reader
// Stdout and Stderr specify the remote process's standard
// output and error.
//
// If either is nil, Run connects the corresponding file
// descriptor to an instance of ioutil.Discard. There is a
// fixed amount of buffering that is shared for the two streams.
// If either blocks it may eventually cause the remote
// command to block.
Stdout io.Writer
Stderr io.Writer
*clientChan // the channel backing this session
started bool // true once Start, Run or Shell is invoked.
copyFuncs []func() error
errors chan error // one send per copyFunc
// true if pipe method is active
stdinpipe, stdoutpipe, stderrpipe bool
}
// RFC 4254 Section 6.4.
type setenvRequest struct {
PeersId uint32
Request string
WantReply bool
Name string
Value string
}
// Setenv sets an environment variable that will be applied to any
// command executed by Shell or Run.
func (s *Session) Setenv(name, value string) error {
req := setenvRequest{
PeersId: s.peersId,
Request: "env",
WantReply: true,
Name: name,
Value: value,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
return s.waitForResponse()
}
// An empty mode list, see RFC 4254 Section 8.
var emptyModelist = "\x00"
// RFC 4254 Section 6.2.
type ptyRequestMsg struct {
PeersId uint32
Request string
WantReply bool
Term string
Columns uint32
Rows uint32
Width uint32
Height uint32
Modelist string
}
// RequestPty requests the association of a pty with the session on the remote host.
func (s *Session) RequestPty(term string, h, w int) error {
req := ptyRequestMsg{
PeersId: s.peersId,
Request: "pty-req",
WantReply: true,
Term: term,
Columns: uint32(w),
Rows: uint32(h),
Width: uint32(w * 8),
Height: uint32(h * 8),
Modelist: emptyModelist,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
return s.waitForResponse()
}
// RFC 4254 Section 6.9.
type signalMsg struct {
PeersId uint32
Request string
WantReply bool
Signal string
}
// Signal sends the given signal to the remote process.
// sig is one of the SIG* constants.
func (s *Session) Signal(sig Signal) error {
req := signalMsg{
PeersId: s.peersId,
Request: "signal",
WantReply: false,
Signal: string(sig),
}
return s.writePacket(marshal(msgChannelRequest, req))
}
// RFC 4254 Section 6.5.
type execMsg struct {
PeersId uint32
Request string
WantReply bool
Command string
}
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
func (s *Session) Start(cmd string) error {
if s.started {
return errors.New("ssh: session already started")
}
req := execMsg{
PeersId: s.peersId,
Request: "exec",
WantReply: true,
Command: cmd,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
if err := s.waitForResponse(); err != nil {
return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
}
return s.start()
}
// Run runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (s *Session) Run(cmd string) error {
err := s.Start(cmd)
if err != nil {
return err
}
return s.Wait()
}
// Shell starts a login shell on the remote host. A Session only
// accepts one call to Run, Start or Shell.
func (s *Session) Shell() error {
if s.started {
return errors.New("ssh: session already started")
}
req := channelRequestMsg{
PeersId: s.peersId,
Request: "shell",
WantReply: true,
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
}
if err := s.waitForResponse(); err != nil {
return fmt.Errorf("ssh: cound not execute shell: %v", err)
}
return s.start()
}
func (s *Session) waitForResponse() error {
msg := <-s.msg
switch msg.(type) {
case *channelRequestSuccessMsg:
return nil
case *channelRequestFailureMsg:
return errors.New("request failed")
}
return fmt.Errorf("unknown packet %T received: %v", msg, msg)
}
func (s *Session) start() error {
s.started = true
type F func(*Session)
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
setupFd(s)
}
s.errors = make(chan error, len(s.copyFuncs))
for _, fn := range s.copyFuncs {
go func(fn func() error) {
s.errors <- fn()
}(fn)
}
return nil
}
// Wait waits for the remote command to exit.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (s *Session) Wait() error {
if !s.started {
return errors.New("ssh: session not started")
}
waitErr := s.wait()
var copyError error
for _ = range s.copyFuncs {
if err := <-s.errors; err != nil && copyError == nil {
copyError = err
}
}
if waitErr != nil {
return waitErr
}
return copyError
}
func (s *Session) wait() error {
wm := Waitmsg{status: -1}
// Wait for msg channel to be closed before returning.
for msg := range s.msg {
switch msg := msg.(type) {
case *channelRequestMsg:
switch msg.Request {
case "exit-status":
d := msg.RequestSpecificData
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
case "exit-signal":
signal, rest, ok := parseString(msg.RequestSpecificData)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.signal = safeString(string(signal))
// skip coreDumped bool
if len(rest) == 0 {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
rest = rest[1:]
errmsg, rest, ok := parseString(rest)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.msg = safeString(string(errmsg))
lang, _, ok := parseString(rest)
if !ok {
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
}
wm.lang = safeString(string(lang))
default:
return fmt.Errorf("wait: unexpected channel request: %v", msg)
}
default:
return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
}
}
if wm.status == 0 {
return nil
}
if wm.status == -1 {
// exit-status was never sent from server
if wm.signal == "" {
return errors.New("wait: remote command exited without exit status or exit signal")
}
wm.status = 128
if _, ok := signals[Signal(wm.signal)]; ok {
wm.status += signals[Signal(wm.signal)]
}
}
return &ExitError{wm}
}
func (s *Session) stdin() {
if s.stdinpipe {
return
}
if s.Stdin == nil {
s.Stdin = new(bytes.Buffer)
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.clientChan.stdin, s.Stdin)
if err1 := s.clientChan.stdin.Close(); err == nil {
err = err1
}
return err
})
}
func (s *Session) stdout() {
if s.stdoutpipe {
return
}
if s.Stdout == nil {
s.Stdout = ioutil.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stdout, s.clientChan.stdout)
return err
})
}
func (s *Session) stderr() {
if s.stderrpipe {
return
}
if s.Stderr == nil {
s.Stderr = ioutil.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stderr, s.clientChan.stderr)
return err
})
}
// StdinPipe returns a pipe that will be connected to the
// remote command's standard input when the command starts.
func (s *Session) StdinPipe() (io.WriteCloser, error) {
if s.Stdin != nil {
return nil, errors.New("ssh: Stdin already set")
}
if s.started {
return nil, errors.New("ssh: StdinPipe after process started")
}
s.stdinpipe = true
return s.clientChan.stdin, nil
}
// StdoutPipe returns a pipe that will be connected to the
// remote command's standard output when the command starts.
// There is a fixed amount of buffering that is shared between
// stdout and stderr streams. If the StdoutPipe reader is
// not serviced fast enought it may eventually cause the
// remote command to block.
func (s *Session) StdoutPipe() (io.Reader, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
if s.started {
return nil, errors.New("ssh: StdoutPipe after process started")
}
s.stdoutpipe = true
return s.clientChan.stdout, nil
}
// StderrPipe returns a pipe that will be connected to the
// remote command's standard error when the command starts.
// There is a fixed amount of buffering that is shared between
// stdout and stderr streams. If the StderrPipe reader is
// not serviced fast enought it may eventually cause the
// remote command to block.
func (s *Session) StderrPipe() (io.Reader, error) {
if s.Stderr != nil {
return nil, errors.New("ssh: Stderr already set")
}
if s.started {
return nil, errors.New("ssh: StderrPipe after process started")
}
s.stderrpipe = true
return s.clientChan.stderr, nil
}
// TODO(dfc) add Output and CombinedOutput helpers
// NewSession returns a new interactive session on the remote host.
func (c *ClientConn) NewSession() (*Session, error) {
ch := c.newChan(c.transport)
if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
ChanType: "session",
PeersId: ch.id,
PeersWindow: 1 << 14,
MaxPacketSize: 1 << 15, // RFC 4253 6.1
})); err != nil {
c.chanlist.remove(ch.id)
return nil, err
}
if err := ch.waitForChannelOpenResponse(); err != nil {
c.chanlist.remove(ch.id)
return nil, fmt.Errorf("ssh: unable to open session: %v", err)
}
return &Session{
clientChan: ch,
}, nil
}
// An ExitError reports unsuccessful completion of a remote command.
type ExitError struct {
Waitmsg
}
func (e *ExitError) Error() string {
return e.Waitmsg.String()
}
// Waitmsg stores the information about an exited remote command
// as reported by Wait.
type Waitmsg struct {
status int
signal string
msg string
lang string
}
// ExitStatus returns the exit status of the remote command.
func (w Waitmsg) ExitStatus() int {
return w.status
}
// Signal returns the exit signal of the remote command if
// it was terminated violently.
func (w Waitmsg) Signal() string {
return w.signal
}
// Msg returns the exit message given by the remote command
func (w Waitmsg) Msg() string {
return w.msg
}
// Lang returns the language tag. See RFC 3066
func (w Waitmsg) Lang() string {
return w.lang
}
func (w Waitmsg) String() string {
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
}
+374
View File
@@ -0,0 +1,374 @@
// 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
// Session tests.
import (
"bytes"
"exp/terminal"
"io"
"testing"
)
type serverType func(*channel)
// dial constructs a new test server and returns a *ClientConn.
func dial(handler serverType, t *testing.T) *ClientConn {
pw := password("tiger")
serverConfig.PasswordCallback = func(user, pass string) bool {
return user == "testuser" && pass == string(pw)
}
serverConfig.PublicKeyCallback = nil
l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
if err != nil {
t.Fatalf("unable to listen: %s", err)
}
go func() {
defer l.Close()
conn, err := l.Accept()
if err != nil {
t.Errorf("Unable to accept: %v", err)
return
}
defer conn.Close()
if err := conn.Handshake(); err != nil {
t.Errorf("Unable to handshake: %v", err)
return
}
for {
ch, err := conn.Accept()
if err == io.EOF {
return
}
if err != nil {
t.Errorf("Unable to accept incoming channel request: %v", err)
return
}
if ch.ChannelType() != "session" {
ch.Reject(UnknownChannelType, "unknown channel type")
continue
}
ch.Accept()
go handler(ch.(*channel))
}
t.Log("done")
}()
config := &ClientConfig{
User: "testuser",
Auth: []ClientAuth{
ClientAuthPassword(pw),
},
}
c, err := Dial("tcp", l.Addr().String(), config)
if err != nil {
t.Fatalf("unable to dial remote side: %s", err)
}
return c
}
// Test a simple string is returned to session.Stdout.
func TestSessionShell(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
stdout := new(bytes.Buffer)
session.Stdout = stdout
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %s", err)
}
actual := stdout.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
// Test a simple string is returned via StdoutPipe.
func TestSessionStdoutPipe(t *testing.T) {
conn := dial(shellHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
t.Fatalf("Unable to request StdoutPipe(): %v", err)
}
var buf bytes.Buffer
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
done := make(chan bool, 1)
go func() {
if _, err := io.Copy(&buf, stdout); err != nil {
t.Errorf("Copy of stdout failed: %v", err)
}
done <- true
}()
if err := session.Wait(); err != nil {
t.Fatalf("Remote command did not exit cleanly: %s", err)
}
<-done
actual := buf.String()
if actual != "golang" {
t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
}
}
// Test non-0 exit status is returned correctly.
func TestExitStatusNonZero(t *testing.T) {
conn := dial(exitStatusNonZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with 15 but got %s", e.ExitStatus())
}
}
// Test 0 exit status is returned correctly.
func TestExitStatusZero(t *testing.T) {
conn := dial(exitStatusZeroHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err != nil {
t.Fatalf("expected nil but got %s", err)
}
}
// Test exit signal and status are both returned correctly.
func TestExitSignalAndStatus(t *testing.T) {
conn := dial(exitSignalAndStatusHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 15 {
t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestKnownExitSignalOnly(t *testing.T) {
conn := dial(exitSignalHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "TERM" || e.ExitStatus() != 143 {
t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test exit signal and status are both returned correctly.
func TestUnknownExitSignal(t *testing.T) {
conn := dial(exitSignalUnknownHandler, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
e, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *ExitError but got %T", err)
}
if e.Signal() != "SYS" || e.ExitStatus() != 128 {
t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
}
}
// Test WaitMsg is not returned if the channel closes abruptly.
func TestExitWithoutStatusOrSignal(t *testing.T) {
conn := dial(exitWithoutSignalOrStatus, t)
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
t.Fatalf("Unable to request new session: %s", err)
}
defer session.Close()
if err := session.Shell(); err != nil {
t.Fatalf("Unable to execute command: %s", err)
}
err = session.Wait()
if err == nil {
t.Fatalf("expected command to fail but it didn't")
}
_, ok := err.(*ExitError)
if ok {
// you can't actually test for errors.errorString
// because it's not exported.
t.Fatalf("expected *errorString but got %T", err)
}
}
type exitStatusMsg struct {
PeersId uint32
Request string
WantReply bool
Status uint32
}
type exitSignalMsg struct {
PeersId uint32
Request string
WantReply bool
Signal string
CoreDumped bool
Errmsg string
Lang string
}
func newServerShell(ch *channel, prompt string) *ServerTerminal {
term := terminal.NewTerminal(ch, prompt)
return &ServerTerminal{
Term: term,
Channel: ch,
}
}
func exitStatusZeroHandler(ch *channel) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, "> ")
shell.ReadLine()
sendStatus(0, ch)
}
func exitStatusNonZeroHandler(ch *channel) {
defer ch.Close()
shell := newServerShell(ch, "> ")
shell.ReadLine()
sendStatus(15, ch)
}
func exitSignalAndStatusHandler(ch *channel) {
defer ch.Close()
shell := newServerShell(ch, "> ")
shell.ReadLine()
sendStatus(15, ch)
sendSignal("TERM", ch)
}
func exitSignalHandler(ch *channel) {
defer ch.Close()
shell := newServerShell(ch, "> ")
shell.ReadLine()
sendSignal("TERM", ch)
}
func exitSignalUnknownHandler(ch *channel) {
defer ch.Close()
shell := newServerShell(ch, "> ")
shell.ReadLine()
sendSignal("SYS", ch)
}
func exitWithoutSignalOrStatus(ch *channel) {
defer ch.Close()
shell := newServerShell(ch, "> ")
shell.ReadLine()
}
func shellHandler(ch *channel) {
defer ch.Close()
// this string is returned to stdout
shell := newServerShell(ch, "golang")
shell.ReadLine()
sendStatus(0, ch)
}
func sendStatus(status uint32, ch *channel) {
msg := exitStatusMsg{
PeersId: ch.theirId,
Request: "exit-status",
WantReply: false,
Status: status,
}
ch.serverConn.writePacket(marshal(msgChannelRequest, msg))
}
func sendSignal(signal string, ch *channel) {
sig := exitSignalMsg{
PeersId: ch.theirId,
Request: "exit-signal",
WantReply: false,
Signal: signal,
CoreDumped: false,
Errmsg: "Process terminated",
Lang: "en-GB-oed",
}
ch.serverConn.writePacket(marshal(msgChannelRequest, sig))
}
+132
View File
@@ -0,0 +1,132 @@
// 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 (
"errors"
"fmt"
"io"
"net"
"time"
)
// Dial initiates a connection to the addr from the remote host.
// addr is resolved using net.ResolveTCPAddr before connection.
// This could allow an observer to observe the DNS name of the
// remote host. Consider using ssh.DialTCP to avoid this.
func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
raddr, err := net.ResolveTCPAddr(n, addr)
if err != nil {
return nil, err
}
return c.DialTCP(n, nil, raddr)
}
// DialTCP connects to the remote address raddr on the network net,
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
// as the local address for the connection.
func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
if laddr == nil {
laddr = &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
}
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
if err != nil {
return nil, err
}
return &tcpchanconn{
tcpchan: ch,
laddr: laddr,
raddr: raddr,
}, nil
}
// RFC 4254 7.2
type channelOpenDirectMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
raddr string
rport uint32
laddr string
lport uint32
}
// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
// strings and are expected to be resolveable at the remote end.
func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error) {
ch := c.newChan(c.transport)
if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
ChanType: "direct-tcpip",
PeersId: ch.id,
PeersWindow: 1 << 14,
MaxPacketSize: 1 << 15, // RFC 4253 6.1
raddr: raddr,
rport: uint32(rport),
laddr: laddr,
lport: uint32(lport),
})); err != nil {
c.chanlist.remove(ch.id)
return nil, err
}
if err := ch.waitForChannelOpenResponse(); err != nil {
c.chanlist.remove(ch.id)
return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
}
return &tcpchan{
clientChan: ch,
Reader: ch.stdout,
Writer: ch.stdin,
}, nil
}
type tcpchan struct {
*clientChan // the backing channel
io.Reader
io.Writer
}
// tcpchanconn fulfills the net.Conn interface without
// the tcpchan having to hold laddr or raddr directly.
type tcpchanconn struct {
*tcpchan
laddr, raddr net.Addr
}
// LocalAddr returns the local network address.
func (t *tcpchanconn) LocalAddr() net.Addr {
return t.laddr
}
// RemoteAddr returns the remote network address.
func (t *tcpchanconn) RemoteAddr() net.Addr {
return t.raddr
}
// SetDeadline sets the read and write deadlines associated
// with the connection.
func (t *tcpchanconn) SetDeadline(deadline time.Time) error {
if err := t.SetReadDeadline(deadline); err != nil {
return err
}
return t.SetWriteDeadline(deadline)
}
// SetReadDeadline sets the read deadline.
// A zero value for t means Read will not time out.
// After the deadline, the error from Read will implement net.Error
// with Timeout() == true.
func (t *tcpchanconn) SetReadDeadline(deadline time.Time) error {
return errors.New("ssh: tcpchan: deadline not supported")
}
// SetWriteDeadline exists to satisfy the net.Conn interface
// but is not implemented by this type. It always returns an error.
func (t *tcpchanconn) SetWriteDeadline(deadline time.Time) error {
return errors.New("ssh: tcpchan: deadline not supported")
}
+59
View File
@@ -0,0 +1,59 @@
// 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
// direct-tcpip functional tests
import (
"net"
"net/http"
"testing"
)
func TestTCPIPHTTP(t *testing.T) {
if *sshuser == "" {
t.Log("ssh.user not defined, skipping test")
return
}
// google.com will generate at least one redirect, possibly three
// depending on your location.
doTest(t, "http://google.com")
}
func TestTCPIPHTTPS(t *testing.T) {
if *sshuser == "" {
t.Log("ssh.user not defined, skipping test")
return
}
doTest(t, "https://encrypted.google.com/")
}
func doTest(t *testing.T, url string) {
config := &ClientConfig{
User: *sshuser,
Auth: []ClientAuth{
ClientAuthPassword(password(*sshpass)),
},
}
conn, err := Dial("tcp", "localhost:22", config)
if err != nil {
t.Fatalf("Unable to connect: %s", err)
}
defer conn.Close()
tr := &http.Transport{
Dial: func(n, addr string) (net.Conn, error) {
return conn.Dial(n, addr)
},
}
client := &http.Client{
Transport: tr,
}
resp, err := client.Get(url)
if err != nil {
t.Fatalf("unable to proxy: %s", err)
}
// got a body without error
t.Log(resp)
}
+369
View File
@@ -0,0 +1,369 @@
// 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 (
"bufio"
"crypto"
"crypto/cipher"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"errors"
"hash"
"io"
"net"
"sync"
)
const (
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
minPacketSize = 16
maxPacketSize = 36000
minPaddingSize = 4 // TODO(huin) should this be configurable?
)
// filteredConn reduces the set of methods exposed when embeddeding
// a net.Conn inside ssh.transport.
// TODO(dfc) suggestions for a better name will be warmly received.
type filteredConn interface {
// Close closes the connection.
Close() error
// LocalAddr returns the local network address.
LocalAddr() net.Addr
// RemoteAddr returns the remote network address.
RemoteAddr() net.Addr
}
// Types implementing packetWriter provide the ability to send packets to
// an SSH peer.
type packetWriter interface {
// Encrypt and send a packet of data to the remote peer.
writePacket(packet []byte) error
}
// transport represents the SSH connection to the remote peer.
type transport struct {
reader
writer
filteredConn
}
// reader represents the incoming connection state.
type reader struct {
io.Reader
common
}
// writer represnts the outgoing connection state.
type writer struct {
*sync.Mutex // protects writer.Writer from concurrent writes
*bufio.Writer
rand io.Reader
common
}
// common represents the cipher state needed to process messages in a single
// direction.
type common struct {
seqNum uint32
mac hash.Hash
cipher cipher.Stream
cipherAlgo string
macAlgo string
compressionAlgo string
}
// Read and decrypt a single packet from the remote peer.
func (r *reader) readOnePacket() ([]byte, error) {
var lengthBytes = make([]byte, 5)
var macSize uint32
if _, err := io.ReadFull(r, lengthBytes); err != nil {
return nil, err
}
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
if r.mac != nil {
r.mac.Reset()
seqNumBytes := []byte{
byte(r.seqNum >> 24),
byte(r.seqNum >> 16),
byte(r.seqNum >> 8),
byte(r.seqNum),
}
r.mac.Write(seqNumBytes)
r.mac.Write(lengthBytes)
macSize = uint32(r.mac.Size())
}
length := uint32(lengthBytes[0])<<24 | uint32(lengthBytes[1])<<16 | uint32(lengthBytes[2])<<8 | uint32(lengthBytes[3])
paddingLength := uint32(lengthBytes[4])
if length <= paddingLength+1 {
return nil, errors.New("invalid packet length")
}
if length > maxPacketSize {
return nil, errors.New("packet too large")
}
packet := make([]byte, length-1+macSize)
if _, err := io.ReadFull(r, packet); err != nil {
return nil, err
}
mac := packet[length-1:]
r.cipher.XORKeyStream(packet, packet[:length-1])
if r.mac != nil {
r.mac.Write(packet[:length-1])
if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
return nil, errors.New("ssh: MAC failure")
}
}
r.seqNum++
return packet[:length-paddingLength-1], nil
}
// Read and decrypt next packet discarding debug and noop messages.
func (t *transport) readPacket() ([]byte, error) {
for {
packet, err := t.readOnePacket()
if err != nil {
return nil, err
}
if packet[0] != msgIgnore && packet[0] != msgDebug {
return packet, nil
}
}
panic("unreachable")
}
// Encrypt and send a packet of data to the remote peer.
func (w *writer) writePacket(packet []byte) error {
w.Mutex.Lock()
defer w.Mutex.Unlock()
paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
if paddingLength < 4 {
paddingLength += packetSizeMultiple
}
length := len(packet) + 1 + paddingLength
lengthBytes := []byte{
byte(length >> 24),
byte(length >> 16),
byte(length >> 8),
byte(length),
byte(paddingLength),
}
padding := make([]byte, paddingLength)
_, err := io.ReadFull(w.rand, padding)
if err != nil {
return err
}
if w.mac != nil {
w.mac.Reset()
seqNumBytes := []byte{
byte(w.seqNum >> 24),
byte(w.seqNum >> 16),
byte(w.seqNum >> 8),
byte(w.seqNum),
}
w.mac.Write(seqNumBytes)
w.mac.Write(lengthBytes)
w.mac.Write(packet)
w.mac.Write(padding)
}
// TODO(dfc) lengthBytes, packet and padding should be
// subslices of a single buffer
w.cipher.XORKeyStream(lengthBytes, lengthBytes)
w.cipher.XORKeyStream(packet, packet)
w.cipher.XORKeyStream(padding, padding)
if _, err := w.Write(lengthBytes); err != nil {
return err
}
if _, err := w.Write(packet); err != nil {
return err
}
if _, err := w.Write(padding); err != nil {
return err
}
if w.mac != nil {
if _, err := w.Write(w.mac.Sum(nil)); err != nil {
return err
}
}
if err := w.Flush(); err != nil {
return err
}
w.seqNum++
return err
}
// Send a message to the remote peer
func (t *transport) sendMessage(typ uint8, msg interface{}) error {
packet := marshal(typ, msg)
return t.writePacket(packet)
}
func newTransport(conn net.Conn, rand io.Reader) *transport {
return &transport{
reader: reader{
Reader: bufio.NewReader(conn),
common: common{
cipher: noneCipher{},
},
},
writer: writer{
Writer: bufio.NewWriter(conn),
rand: rand,
Mutex: new(sync.Mutex),
common: common{
cipher: noneCipher{},
},
},
filteredConn: conn,
}
}
type direction struct {
ivTag []byte
keyTag []byte
macKeyTag []byte
}
// TODO(dfc) can this be made a constant ?
var (
serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
)
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
// described in RFC 4253, section 6.4. direction should either be serverKeys
// (to setup server->client keys) or clientKeys (for client->server keys).
func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
cipherMode := cipherModes[c.cipherAlgo]
macKeySize := 20
iv := make([]byte, cipherMode.ivSize)
key := make([]byte, cipherMode.keySize)
macKey := make([]byte, macKeySize)
h := hashFunc.New()
generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
c.mac = truncatingMAC{12, hmac.New(sha1.New, macKey)}
cipher, err := cipherMode.createCipher(key, iv)
if err != nil {
return err
}
c.cipher = cipher
return nil
}
// generateKeyMaterial fills out with key material generated from tag, K, H
// and sessionId, as specified in RFC 4253, section 7.2.
func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
var digestsSoFar []byte
for len(out) > 0 {
h.Reset()
h.Write(K)
h.Write(H)
if len(digestsSoFar) == 0 {
h.Write(tag)
h.Write(sessionId)
} else {
h.Write(digestsSoFar)
}
digest := h.Sum(nil)
n := copy(out, digest)
out = out[n:]
if len(out) > 0 {
digestsSoFar = append(digestsSoFar, digest...)
}
}
}
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
// a given size.
type truncatingMAC struct {
length int
hmac hash.Hash
}
func (t truncatingMAC) Write(data []byte) (int, error) {
return t.hmac.Write(data)
}
func (t truncatingMAC) Sum(in []byte) []byte {
out := t.hmac.Sum(in)
return out[:len(in)+t.length]
}
func (t truncatingMAC) Reset() {
t.hmac.Reset()
}
func (t truncatingMAC) Size() int {
return t.length
}
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
// maxVersionStringBytes is the maximum number of bytes that we'll accept as a
// version string. In the event that the client is talking a different protocol
// we need to set a limit otherwise we will keep using more and more memory
// while searching for the end of the version handshake.
const maxVersionStringBytes = 1024
// Read version string as specified by RFC 4253, section 4.2.
func readVersion(r io.Reader) ([]byte, error) {
versionString := make([]byte, 0, 64)
var ok bool
var buf [1]byte
forEachByte:
for len(versionString) < maxVersionStringBytes {
_, err := io.ReadFull(r, buf[:])
if err != nil {
return nil, err
}
// The RFC says that the version should be terminated with \r\n
// but several SSH servers actually only send a \n.
if buf[0] == '\n' {
ok = true
break forEachByte
}
versionString = append(versionString, buf[0])
}
if !ok {
return nil, errors.New("ssh: failed to read version string")
}
// There might be a '\r' on the end which we should remove.
if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
versionString = versionString[:len(versionString)-1]
}
return versionString, nil
}
+51
View File
@@ -0,0 +1,51 @@
// 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 (
"bufio"
"bytes"
"testing"
)
func TestReadVersion(t *testing.T) {
buf := serverVersion
result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
if err != nil {
t.Errorf("readVersion didn't read version correctly: %s", err)
}
if !bytes.Equal(buf[:len(buf)-2], result) {
t.Error("version read did not match expected")
}
}
func TestReadVersionWithJustLF(t *testing.T) {
var buf []byte
buf = append(buf, serverVersion...)
buf = buf[:len(buf)-1]
buf[len(buf)-1] = '\n'
result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
if err != nil {
t.Error("readVersion failed to handle just a \n")
}
if !bytes.Equal(buf[:len(buf)-1], result) {
t.Errorf("version read did not match expected: got %x, want %x", result, buf[:len(buf)-1])
}
}
func TestReadVersionTooLong(t *testing.T) {
buf := make([]byte, maxVersionStringBytes+1)
if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
t.Errorf("readVersion consumed %d bytes without error", len(buf))
}
}
func TestReadVersionWithoutCRLF(t *testing.T) {
buf := serverVersion
buf = buf[:len(buf)-1]
if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
t.Error("readVersion did not notice \\n was missing")
}
}