github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/crypto/ssh/transport.go (about)

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