...

Source file src/golang.org/x/crypto/ssh/transport.go

Documentation: golang.org/x/crypto/ssh

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssh
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"io"
    12  	"log"
    13  )
    14  
    15  // debugTransport if set, will print packet types as they go over the
    16  // wire. No message decoding is done, to minimize the impact on timing.
    17  const debugTransport = false
    18  
    19  const (
    20  	gcm128CipherID = "aes128-gcm@openssh.com"
    21  	gcm256CipherID = "aes256-gcm@openssh.com"
    22  	aes128cbcID    = "aes128-cbc"
    23  	tripledescbcID = "3des-cbc"
    24  )
    25  
    26  // packetConn represents a transport that implements packet based
    27  // operations.
    28  type packetConn interface {
    29  	// Encrypt and send a packet of data to the remote peer.
    30  	writePacket(packet []byte) error
    31  
    32  	// Read a packet from the connection. The read is blocking,
    33  	// i.e. if error is nil, then the returned byte slice is
    34  	// always non-empty.
    35  	readPacket() ([]byte, error)
    36  
    37  	// Close closes the write-side of the connection.
    38  	Close() error
    39  }
    40  
    41  // transport is the keyingTransport that implements the SSH packet
    42  // protocol.
    43  type transport struct {
    44  	reader connectionState
    45  	writer connectionState
    46  
    47  	bufReader *bufio.Reader
    48  	bufWriter *bufio.Writer
    49  	rand      io.Reader
    50  	isClient  bool
    51  	io.Closer
    52  
    53  	strictMode     bool
    54  	initialKEXDone bool
    55  }
    56  
    57  // packetCipher represents a combination of SSH encryption/MAC
    58  // protocol.  A single instance should be used for one direction only.
    59  type packetCipher interface {
    60  	// writeCipherPacket encrypts the packet and writes it to w. The
    61  	// contents of the packet are generally scrambled.
    62  	writeCipherPacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error
    63  
    64  	// readCipherPacket reads and decrypts a packet of data. The
    65  	// returned packet may be overwritten by future calls of
    66  	// readPacket.
    67  	readCipherPacket(seqnum uint32, r io.Reader) ([]byte, error)
    68  }
    69  
    70  // connectionState represents one side (read or write) of the
    71  // connection. This is necessary because each direction has its own
    72  // keys, and can even have its own algorithms
    73  type connectionState struct {
    74  	packetCipher
    75  	seqNum           uint32
    76  	dir              direction
    77  	pendingKeyChange chan packetCipher
    78  }
    79  
    80  func (t *transport) setStrictMode() error {
    81  	if t.reader.seqNum != 1 {
    82  		return errors.New("ssh: sequence number != 1 when strict KEX mode requested")
    83  	}
    84  	t.strictMode = true
    85  	return nil
    86  }
    87  
    88  func (t *transport) setInitialKEXDone() {
    89  	t.initialKEXDone = true
    90  }
    91  
    92  // prepareKeyChange sets up key material for a keychange. The key changes in
    93  // both directions are triggered by reading and writing a msgNewKey packet
    94  // respectively.
    95  func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
    96  	ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	t.reader.pendingKeyChange <- ciph
   101  
   102  	ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	t.writer.pendingKeyChange <- ciph
   107  
   108  	return nil
   109  }
   110  
   111  func (t *transport) printPacket(p []byte, write bool) {
   112  	if len(p) == 0 {
   113  		return
   114  	}
   115  	who := "server"
   116  	if t.isClient {
   117  		who = "client"
   118  	}
   119  	what := "read"
   120  	if write {
   121  		what = "write"
   122  	}
   123  
   124  	log.Println(what, who, p[0])
   125  }
   126  
   127  // Read and decrypt next packet.
   128  func (t *transport) readPacket() (p []byte, err error) {
   129  	for {
   130  		p, err = t.reader.readPacket(t.bufReader, t.strictMode)
   131  		if err != nil {
   132  			break
   133  		}
   134  		// in strict mode we pass through DEBUG and IGNORE packets only during the initial KEX
   135  		if len(p) == 0 || (t.strictMode && !t.initialKEXDone) || (p[0] != msgIgnore && p[0] != msgDebug) {
   136  			break
   137  		}
   138  	}
   139  	if debugTransport {
   140  		t.printPacket(p, false)
   141  	}
   142  
   143  	return p, err
   144  }
   145  
   146  func (s *connectionState) readPacket(r *bufio.Reader, strictMode bool) ([]byte, error) {
   147  	packet, err := s.packetCipher.readCipherPacket(s.seqNum, r)
   148  	s.seqNum++
   149  	if err == nil && len(packet) == 0 {
   150  		err = errors.New("ssh: zero length packet")
   151  	}
   152  
   153  	if len(packet) > 0 {
   154  		switch packet[0] {
   155  		case msgNewKeys:
   156  			select {
   157  			case cipher := <-s.pendingKeyChange:
   158  				s.packetCipher = cipher
   159  				if strictMode {
   160  					s.seqNum = 0
   161  				}
   162  			default:
   163  				return nil, errors.New("ssh: got bogus newkeys message")
   164  			}
   165  
   166  		case msgDisconnect:
   167  			// Transform a disconnect message into an
   168  			// error. Since this is lowest level at which
   169  			// we interpret message types, doing it here
   170  			// ensures that we don't have to handle it
   171  			// elsewhere.
   172  			var msg disconnectMsg
   173  			if err := Unmarshal(packet, &msg); err != nil {
   174  				return nil, err
   175  			}
   176  			return nil, &msg
   177  		}
   178  	}
   179  
   180  	// The packet may point to an internal buffer, so copy the
   181  	// packet out here.
   182  	fresh := make([]byte, len(packet))
   183  	copy(fresh, packet)
   184  
   185  	return fresh, err
   186  }
   187  
   188  func (t *transport) writePacket(packet []byte) error {
   189  	if debugTransport {
   190  		t.printPacket(packet, true)
   191  	}
   192  	return t.writer.writePacket(t.bufWriter, t.rand, packet, t.strictMode)
   193  }
   194  
   195  func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte, strictMode bool) error {
   196  	changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
   197  
   198  	err := s.packetCipher.writeCipherPacket(s.seqNum, w, rand, packet)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	if err = w.Flush(); err != nil {
   203  		return err
   204  	}
   205  	s.seqNum++
   206  	if changeKeys {
   207  		select {
   208  		case cipher := <-s.pendingKeyChange:
   209  			s.packetCipher = cipher
   210  			if strictMode {
   211  				s.seqNum = 0
   212  			}
   213  		default:
   214  			panic("ssh: no key material for msgNewKeys")
   215  		}
   216  	}
   217  	return err
   218  }
   219  
   220  func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport {
   221  	t := &transport{
   222  		bufReader: bufio.NewReader(rwc),
   223  		bufWriter: bufio.NewWriter(rwc),
   224  		rand:      rand,
   225  		reader: connectionState{
   226  			packetCipher:     &streamPacketCipher{cipher: noneCipher{}},
   227  			pendingKeyChange: make(chan packetCipher, 1),
   228  		},
   229  		writer: connectionState{
   230  			packetCipher:     &streamPacketCipher{cipher: noneCipher{}},
   231  			pendingKeyChange: make(chan packetCipher, 1),
   232  		},
   233  		Closer: rwc,
   234  	}
   235  	t.isClient = isClient
   236  
   237  	if isClient {
   238  		t.reader.dir = serverKeys
   239  		t.writer.dir = clientKeys
   240  	} else {
   241  		t.reader.dir = clientKeys
   242  		t.writer.dir = serverKeys
   243  	}
   244  
   245  	return t
   246  }
   247  
   248  type direction struct {
   249  	ivTag     []byte
   250  	keyTag    []byte
   251  	macKeyTag []byte
   252  }
   253  
   254  var (
   255  	serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
   256  	clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
   257  )
   258  
   259  // setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
   260  // described in RFC 4253, section 6.4. direction should either be serverKeys
   261  // (to setup server->client keys) or clientKeys (for client->server keys).
   262  func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
   263  	cipherMode := cipherModes[algs.Cipher]
   264  
   265  	iv := make([]byte, cipherMode.ivSize)
   266  	key := make([]byte, cipherMode.keySize)
   267  
   268  	generateKeyMaterial(iv, d.ivTag, kex)
   269  	generateKeyMaterial(key, d.keyTag, kex)
   270  
   271  	var macKey []byte
   272  	if !aeadCiphers[algs.Cipher] {
   273  		macMode := macModes[algs.MAC]
   274  		macKey = make([]byte, macMode.keySize)
   275  		generateKeyMaterial(macKey, d.macKeyTag, kex)
   276  	}
   277  
   278  	return cipherModes[algs.Cipher].create(key, iv, macKey, algs)
   279  }
   280  
   281  // generateKeyMaterial fills out with key material generated from tag, K, H
   282  // and sessionId, as specified in RFC 4253, section 7.2.
   283  func generateKeyMaterial(out, tag []byte, r *kexResult) {
   284  	var digestsSoFar []byte
   285  
   286  	h := r.Hash.New()
   287  	for len(out) > 0 {
   288  		h.Reset()
   289  		h.Write(r.K)
   290  		h.Write(r.H)
   291  
   292  		if len(digestsSoFar) == 0 {
   293  			h.Write(tag)
   294  			h.Write(r.SessionID)
   295  		} else {
   296  			h.Write(digestsSoFar)
   297  		}
   298  
   299  		digest := h.Sum(nil)
   300  		n := copy(out, digest)
   301  		out = out[n:]
   302  		if len(out) > 0 {
   303  			digestsSoFar = append(digestsSoFar, digest...)
   304  		}
   305  	}
   306  }
   307  
   308  const packageVersion = "SSH-2.0-Go"
   309  
   310  // Sends and receives a version line.  The versionLine string should
   311  // be US ASCII, start with "SSH-2.0-", and should not include a
   312  // newline. exchangeVersions returns the other side's version line.
   313  func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {
   314  	// Contrary to the RFC, we do not ignore lines that don't
   315  	// start with "SSH-2.0-" to make the library usable with
   316  	// nonconforming servers.
   317  	for _, c := range versionLine {
   318  		// The spec disallows non US-ASCII chars, and
   319  		// specifically forbids null chars.
   320  		if c < 32 {
   321  			return nil, errors.New("ssh: junk character in version line")
   322  		}
   323  	}
   324  	if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {
   325  		return
   326  	}
   327  
   328  	them, err = readVersion(rw)
   329  	return them, err
   330  }
   331  
   332  // maxVersionStringBytes is the maximum number of bytes that we'll
   333  // accept as a version string. RFC 4253 section 4.2 limits this at 255
   334  // chars
   335  const maxVersionStringBytes = 255
   336  
   337  // Read version string as specified by RFC 4253, section 4.2.
   338  func readVersion(r io.Reader) ([]byte, error) {
   339  	versionString := make([]byte, 0, 64)
   340  	var ok bool
   341  	var buf [1]byte
   342  
   343  	for length := 0; length < maxVersionStringBytes; length++ {
   344  		_, err := io.ReadFull(r, buf[:])
   345  		if err != nil {
   346  			return nil, err
   347  		}
   348  		// The RFC says that the version should be terminated with \r\n
   349  		// but several SSH servers actually only send a \n.
   350  		if buf[0] == '\n' {
   351  			if !bytes.HasPrefix(versionString, []byte("SSH-")) {
   352  				// RFC 4253 says we need to ignore all version string lines
   353  				// except the one containing the SSH version (provided that
   354  				// all the lines do not exceed 255 bytes in total).
   355  				versionString = versionString[:0]
   356  				continue
   357  			}
   358  			ok = true
   359  			break
   360  		}
   361  
   362  		// non ASCII chars are disallowed, but we are lenient,
   363  		// since Go doesn't use null-terminated strings.
   364  
   365  		// The RFC allows a comment after a space, however,
   366  		// all of it (version and comments) goes into the
   367  		// session hash.
   368  		versionString = append(versionString, buf[0])
   369  	}
   370  
   371  	if !ok {
   372  		return nil, errors.New("ssh: overflow reading version string")
   373  	}
   374  
   375  	// There might be a '\r' on the end which we should remove.
   376  	if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
   377  		versionString = versionString[:len(versionString)-1]
   378  	}
   379  	return versionString, nil
   380  }
   381  

View as plain text