package ssh
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
)
const debugTransport = false
type packetConn interface {
writePacket(packet []byte ) error
readPacket() ([]byte , error )
Close() error
}
type transport struct {
reader connectionState
writer connectionState
bufReader *bufio .Reader
bufWriter *bufio .Writer
rand io .Reader
isClient bool
io .Closer
strictMode bool
initialKEXDone bool
}
type packetCipher interface {
writeCipherPacket(seqnum uint32 , w io .Writer , rand io .Reader , packet []byte ) error
readCipherPacket(seqnum uint32 , r io .Reader ) ([]byte , error )
}
type connectionState struct {
packetCipher
seqNum uint32
dir direction
pendingKeyChange chan packetCipher
}
func (t *transport ) setStrictMode () error {
if t .reader .seqNum != 1 {
return errors .New ("ssh: sequence number != 1 when strict KEX mode requested" )
}
t .strictMode = true
return nil
}
func (t *transport ) setInitialKEXDone () {
t .initialKEXDone = true
}
func (t *transport ) prepareKeyChange (algs *NegotiatedAlgorithms , kexResult *kexResult ) error {
ciph , err := newPacketCipher (t .reader .dir , algs .Read , kexResult )
if err != nil {
return err
}
t .reader .pendingKeyChange <- ciph
ciph , err = newPacketCipher (t .writer .dir , algs .Write , kexResult )
if err != nil {
return err
}
t .writer .pendingKeyChange <- ciph
return nil
}
func (t *transport ) printPacket (p []byte , write bool ) {
if len (p ) == 0 {
return
}
who := "server"
if t .isClient {
who = "client"
}
what := "read"
if write {
what = "write"
}
log .Println (what , who , p [0 ])
}
func (t *transport ) readPacket () (p []byte , err error ) {
for {
p , err = t .reader .readPacket (t .bufReader , t .strictMode )
if err != nil {
break
}
if len (p ) == 0 || (t .strictMode && !t .initialKEXDone ) || (p [0 ] != msgIgnore && p [0 ] != msgDebug ) {
break
}
}
if debugTransport {
t .printPacket (p , false )
}
return p , err
}
func (s *connectionState ) readPacket (r *bufio .Reader , strictMode bool ) ([]byte , error ) {
packet , err := s .packetCipher .readCipherPacket (s .seqNum , r )
s .seqNum ++
if err == nil && len (packet ) == 0 {
err = errors .New ("ssh: zero length packet" )
}
if len (packet ) > 0 {
switch packet [0 ] {
case msgNewKeys :
select {
case cipher := <- s .pendingKeyChange :
s .packetCipher = cipher
if strictMode {
s .seqNum = 0
}
default :
return nil , errors .New ("ssh: got bogus newkeys message" )
}
case msgDisconnect :
var msg disconnectMsg
if err := Unmarshal (packet , &msg ); err != nil {
return nil , err
}
return nil , &msg
}
}
fresh := make ([]byte , len (packet ))
copy (fresh , packet )
return fresh , err
}
func (t *transport ) writePacket (packet []byte ) error {
if debugTransport {
t .printPacket (packet , true )
}
return t .writer .writePacket (t .bufWriter , t .rand , packet , t .strictMode )
}
func (s *connectionState ) writePacket (w *bufio .Writer , rand io .Reader , packet []byte , strictMode bool ) error {
changeKeys := len (packet ) > 0 && packet [0 ] == msgNewKeys
err := s .packetCipher .writeCipherPacket (s .seqNum , w , rand , packet )
if err != nil {
return err
}
if err = w .Flush (); err != nil {
return err
}
s .seqNum ++
if changeKeys {
select {
case cipher := <- s .pendingKeyChange :
s .packetCipher = cipher
if strictMode {
s .seqNum = 0
}
default :
panic ("ssh: no key material for msgNewKeys" )
}
}
return err
}
func newTransport(rwc io .ReadWriteCloser , rand io .Reader , isClient bool ) *transport {
t := &transport {
bufReader : bufio .NewReader (rwc ),
bufWriter : bufio .NewWriter (rwc ),
rand : rand ,
reader : connectionState {
packetCipher : &streamPacketCipher {cipher : noneCipher {}},
pendingKeyChange : make (chan packetCipher , 1 ),
},
writer : connectionState {
packetCipher : &streamPacketCipher {cipher : noneCipher {}},
pendingKeyChange : make (chan packetCipher , 1 ),
},
Closer : rwc ,
}
t .isClient = isClient
if isClient {
t .reader .dir = serverKeys
t .writer .dir = clientKeys
} else {
t .reader .dir = clientKeys
t .writer .dir = serverKeys
}
return t
}
type direction struct {
ivTag []byte
keyTag []byte
macKeyTag []byte
}
var (
serverKeys = direction {[]byte {'B' }, []byte {'D' }, []byte {'F' }}
clientKeys = direction {[]byte {'A' }, []byte {'C' }, []byte {'E' }}
)
func newPacketCipher(d direction , algs DirectionAlgorithms , kex *kexResult ) (packetCipher , error ) {
cipherMode := cipherModes [algs .Cipher ]
if cipherMode == nil {
return nil , fmt .Errorf ("ssh: unsupported cipher %v" , algs .Cipher )
}
iv := make ([]byte , cipherMode .ivSize )
key := make ([]byte , cipherMode .keySize )
generateKeyMaterial (iv , d .ivTag , kex )
generateKeyMaterial (key , d .keyTag , kex )
var macKey []byte
if !aeadCiphers [algs .Cipher ] {
macMode := macModes [algs .MAC ]
macKey = make ([]byte , macMode .keySize )
generateKeyMaterial (macKey , d .macKeyTag , kex )
}
return cipherModes [algs .Cipher ].create (key , iv , macKey , algs )
}
func generateKeyMaterial(out , tag []byte , r *kexResult ) {
var digestsSoFar []byte
h := r .Hash .New ()
for len (out ) > 0 {
h .Reset ()
h .Write (r .K )
h .Write (r .H )
if len (digestsSoFar ) == 0 {
h .Write (tag )
h .Write (r .SessionID )
} else {
h .Write (digestsSoFar )
}
digest := h .Sum (nil )
n := copy (out , digest )
out = out [n :]
if len (out ) > 0 {
digestsSoFar = append (digestsSoFar , digest ...)
}
}
}
const packageVersion = "SSH-2.0-Go"
func exchangeVersions(rw io .ReadWriter , versionLine []byte ) (them []byte , err error ) {
for _ , c := range versionLine {
if c < 32 {
return nil , errors .New ("ssh: junk character in version line" )
}
}
if _, err = rw .Write (append (versionLine , '\r' , '\n' )); err != nil {
return
}
them , err = readVersion (rw )
return them , err
}
const maxVersionStringBytes = 255
func readVersion(r io .Reader ) ([]byte , error ) {
versionString := make ([]byte , 0 , 64 )
var ok bool
var buf [1 ]byte
for length := 0 ; length < maxVersionStringBytes ; length ++ {
_ , err := io .ReadFull (r , buf [:])
if err != nil {
return nil , err
}
if buf [0 ] == '\n' {
if !bytes .HasPrefix (versionString , []byte ("SSH-" )) {
versionString = versionString [:0 ]
continue
}
ok = true
break
}
versionString = append (versionString , buf [0 ])
}
if !ok {
return nil , errors .New ("ssh: overflow reading version string" )
}
if len (versionString ) > 0 && versionString [len (versionString )-1 ] == '\r' {
versionString = versionString [:len (versionString )-1 ]
}
return versionString , nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .