376 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			376 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | // 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" | ||
|  | 	"errors" | ||
|  | 	"io" | ||
|  | 	"log" | ||
|  | ) | ||
|  | 
 | ||
|  | // debugTransport if set, will print packet types as they go over the | ||
|  | // wire. No message decoding is done, to minimize the impact on timing. | ||
|  | const debugTransport = false | ||
|  | 
 | ||
|  | const ( | ||
|  | 	gcmCipherID    = "aes128-gcm@openssh.com" | ||
|  | 	aes128cbcID    = "aes128-cbc" | ||
|  | 	tripledescbcID = "3des-cbc" | ||
|  | ) | ||
|  | 
 | ||
|  | // packetConn represents a transport that implements packet based | ||
|  | // operations. | ||
|  | type packetConn interface { | ||
|  | 	// Encrypt and send a packet of data to the remote peer. | ||
|  | 	writePacket(packet []byte) error | ||
|  | 
 | ||
|  | 	// Read a packet from the connection. The read is blocking, | ||
|  | 	// i.e. if error is nil, then the returned byte slice is | ||
|  | 	// always non-empty. | ||
|  | 	readPacket() ([]byte, error) | ||
|  | 
 | ||
|  | 	// Close closes the write-side of the connection. | ||
|  | 	Close() error | ||
|  | } | ||
|  | 
 | ||
|  | // transport is the keyingTransport that implements the SSH packet | ||
|  | // protocol. | ||
|  | type transport struct { | ||
|  | 	reader connectionState | ||
|  | 	writer connectionState | ||
|  | 
 | ||
|  | 	bufReader *bufio.Reader | ||
|  | 	bufWriter *bufio.Writer | ||
|  | 	rand      io.Reader | ||
|  | 	isClient  bool | ||
|  | 	io.Closer | ||
|  | } | ||
|  | 
 | ||
|  | // packetCipher represents a combination of SSH encryption/MAC | ||
|  | // protocol.  A single instance should be used for one direction only. | ||
|  | type packetCipher interface { | ||
|  | 	// writePacket encrypts the packet and writes it to w. The | ||
|  | 	// contents of the packet are generally scrambled. | ||
|  | 	writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error | ||
|  | 
 | ||
|  | 	// readPacket reads and decrypts a packet of data. The | ||
|  | 	// returned packet may be overwritten by future calls of | ||
|  | 	// readPacket. | ||
|  | 	readPacket(seqnum uint32, r io.Reader) ([]byte, error) | ||
|  | } | ||
|  | 
 | ||
|  | // connectionState represents one side (read or write) of the | ||
|  | // connection. This is necessary because each direction has its own | ||
|  | // keys, and can even have its own algorithms | ||
|  | type connectionState struct { | ||
|  | 	packetCipher | ||
|  | 	seqNum           uint32 | ||
|  | 	dir              direction | ||
|  | 	pendingKeyChange chan packetCipher | ||
|  | } | ||
|  | 
 | ||
|  | // prepareKeyChange sets up key material for a keychange. The key changes in | ||
|  | // both directions are triggered by reading and writing a msgNewKey packet | ||
|  | // respectively. | ||
|  | func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { | ||
|  | 	if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil { | ||
|  | 		return err | ||
|  | 	} else { | ||
|  | 		t.reader.pendingKeyChange <- ciph | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil { | ||
|  | 		return err | ||
|  | 	} else { | ||
|  | 		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]) | ||
|  | } | ||
|  | 
 | ||
|  | // Read and decrypt next packet. | ||
|  | func (t *transport) readPacket() (p []byte, err error) { | ||
|  | 	for { | ||
|  | 		p, err = t.reader.readPacket(t.bufReader) | ||
|  | 		if err != nil { | ||
|  | 			break | ||
|  | 		} | ||
|  | 		if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) { | ||
|  | 			break | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if debugTransport { | ||
|  | 		t.printPacket(p, false) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return p, err | ||
|  | } | ||
|  | 
 | ||
|  | func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { | ||
|  | 	packet, err := s.packetCipher.readPacket(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 | ||
|  | 			default: | ||
|  | 				return nil, errors.New("ssh: got bogus newkeys message.") | ||
|  | 			} | ||
|  | 
 | ||
|  | 		case msgDisconnect: | ||
|  | 			// Transform a disconnect message into an | ||
|  | 			// error. Since this is lowest level at which | ||
|  | 			// we interpret message types, doing it here | ||
|  | 			// ensures that we don't have to handle it | ||
|  | 			// elsewhere. | ||
|  | 			var msg disconnectMsg | ||
|  | 			if err := Unmarshal(packet, &msg); err != nil { | ||
|  | 				return nil, err | ||
|  | 			} | ||
|  | 			return nil, &msg | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// The packet may point to an internal buffer, so copy the | ||
|  | 	// packet out here. | ||
|  | 	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) | ||
|  | } | ||
|  | 
 | ||
|  | func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error { | ||
|  | 	changeKeys := len(packet) > 0 && packet[0] == msgNewKeys | ||
|  | 
 | ||
|  | 	err := s.packetCipher.writePacket(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 | ||
|  | 		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'}} | ||
|  | ) | ||
|  | 
 | ||
|  | // generateKeys generates key material for IV, MAC and encryption. | ||
|  | func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) { | ||
|  | 	cipherMode := cipherModes[algs.Cipher] | ||
|  | 	macMode := macModes[algs.MAC] | ||
|  | 
 | ||
|  | 	iv = make([]byte, cipherMode.ivSize) | ||
|  | 	key = make([]byte, cipherMode.keySize) | ||
|  | 	macKey = make([]byte, macMode.keySize) | ||
|  | 
 | ||
|  | 	generateKeyMaterial(iv, d.ivTag, kex) | ||
|  | 	generateKeyMaterial(key, d.keyTag, kex) | ||
|  | 	generateKeyMaterial(macKey, d.macKeyTag, kex) | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | // 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 newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { | ||
|  | 	iv, key, macKey := generateKeys(d, algs, kex) | ||
|  | 
 | ||
|  | 	if algs.Cipher == gcmCipherID { | ||
|  | 		return newGCMCipher(iv, key, macKey) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if algs.Cipher == aes128cbcID { | ||
|  | 		return newAESCBCCipher(iv, key, macKey, algs) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if algs.Cipher == tripledescbcID { | ||
|  | 		return newTripleDESCBCCipher(iv, key, macKey, algs) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	c := &streamPacketCipher{ | ||
|  | 		mac: macModes[algs.MAC].new(macKey), | ||
|  | 		etm: macModes[algs.MAC].etm, | ||
|  | 	} | ||
|  | 	c.macResult = make([]byte, c.mac.Size()) | ||
|  | 
 | ||
|  | 	var err error | ||
|  | 	c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return c, 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, 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" | ||
|  | 
 | ||
|  | // Sends and receives a version line.  The versionLine string should | ||
|  | // be US ASCII, start with "SSH-2.0-", and should not include a | ||
|  | // newline. exchangeVersions returns the other side's version line. | ||
|  | func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { | ||
|  | 	// Contrary to the RFC, we do not ignore lines that don't | ||
|  | 	// start with "SSH-2.0-" to make the library usable with | ||
|  | 	// nonconforming servers. | ||
|  | 	for _, c := range versionLine { | ||
|  | 		// The spec disallows non US-ASCII chars, and | ||
|  | 		// specifically forbids null chars. | ||
|  | 		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 | ||
|  | } | ||
|  | 
 | ||
|  | // maxVersionStringBytes is the maximum number of bytes that we'll | ||
|  | // accept as a version string. RFC 4253 section 4.2 limits this at 255 | ||
|  | // chars | ||
|  | const maxVersionStringBytes = 255 | ||
|  | 
 | ||
|  | // 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 | ||
|  | 
 | ||
|  | 	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 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// non ASCII chars are disallowed, but we are lenient, | ||
|  | 		// since Go doesn't use null-terminated strings. | ||
|  | 
 | ||
|  | 		// The RFC allows a comment after a space, however, | ||
|  | 		// all of it (version and comments) goes into the | ||
|  | 		// session hash. | ||
|  | 		versionString = append(versionString, buf[0]) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if !ok { | ||
|  | 		return nil, errors.New("ssh: overflow reading 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 | ||
|  | } |