ssh: support chacha20-poly1305 cipher

chacha20-poly1305 is an AEAD which performs well without hardware
support. It is recommended as a replacement for the aging arcfour128
and arcfour256 ciphers.

Fixes golang/go#9489

Change-Id: I5d5a4620a435e65997f0ba7e683a34c29d9a396b
Reviewed-on: https://go-review.googlesource.com/87077
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Adam Langley <agl@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/ssh/cipher.go b/ssh/cipher.go
index e67c5e0..9944139 100644
--- a/ssh/cipher.go
+++ b/ssh/cipher.go
@@ -16,6 +16,9 @@
 	"hash"
 	"io"
 	"io/ioutil"
+
+	"golang.org/x/crypto/internal/chacha20"
+	"golang.org/x/crypto/poly1305"
 )
 
 const (
@@ -111,10 +114,10 @@
 	// RFC4345 introduces improved versions of Arcfour.
 	"arcfour": {16, 0, 0, newRC4},
 
-	// AES-GCM is not a stream cipher, so it is constructed with a
-	// special case. If we add any more non-stream ciphers, we
-	// should invest a cleaner way to do this.
-	gcmCipherID: {16, 12, 0, nil},
+	// AEAD ciphers are special cased. If we add any more non-stream
+	// ciphers, we should create a cleaner way to do this.
+	gcmCipherID:        {16, 12, 0, nil},
+	chacha20Poly1305ID: {64, 0, 0, nil},
 
 	// CBC mode is insecure and so is not included in the default config.
 	// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
@@ -627,3 +630,142 @@
 
 	return nil
 }
+
+const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
+
+// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com
+// AEAD, which is described here:
+//
+//   https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
+//
+// the methods here also implement padding, which RFC4253 Section 6
+// also requires of stream ciphers.
+type chacha20Poly1305Cipher struct {
+	lengthKey  [32]byte
+	contentKey [32]byte
+	buf        []byte
+}
+
+func newChaCha20Cipher(key []byte) (packetCipher, error) {
+	if len(key) != 64 {
+		panic("key length")
+	}
+
+	c := &chacha20Poly1305Cipher{
+		buf: make([]byte, 256),
+	}
+
+	copy(c.contentKey[:], key[:32])
+	copy(c.lengthKey[:], key[32:])
+	return c, nil
+}
+
+// The Poly1305 key is obtained by encrypting 32 0-bytes.
+var chacha20PolyKeyInput [32]byte
+
+func (c *chacha20Poly1305Cipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
+	var counter [16]byte
+	binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
+
+	var polyKey [32]byte
+	chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
+
+	encryptedLength := c.buf[:4]
+	if _, err := r.Read(encryptedLength); err != nil {
+		return nil, err
+	}
+
+	var lenBytes [4]byte
+	chacha20.XORKeyStream(lenBytes[:], encryptedLength, &counter, &c.lengthKey)
+
+	length := binary.BigEndian.Uint32(lenBytes[:])
+	if length > maxPacket {
+		return nil, errors.New("ssh: invalid packet length, packet too large")
+	}
+
+	contentEnd := 4 + length
+	packetEnd := contentEnd + poly1305.TagSize
+	if uint32(cap(c.buf)) < packetEnd {
+		c.buf = make([]byte, packetEnd)
+		copy(c.buf[:], encryptedLength)
+	} else {
+		c.buf = c.buf[:packetEnd]
+	}
+
+	if _, err := r.Read(c.buf[4:packetEnd]); err != nil {
+		return nil, err
+	}
+
+	var mac [poly1305.TagSize]byte
+	copy(mac[:], c.buf[contentEnd:packetEnd])
+
+	if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) {
+		return nil, errors.New("ssh: MAC failure")
+	}
+
+	counter[0] = 1
+
+	plain := c.buf[4:contentEnd]
+	chacha20.XORKeyStream(plain, plain, &counter, &c.contentKey)
+
+	padding := plain[0]
+	if padding < 4 {
+		// padding is a byte, so it automatically satisfies
+		// the maximum size, which is 255.
+		return nil, fmt.Errorf("ssh: illegal padding %d", padding)
+	}
+
+	if int(padding)+1 >= len(plain) {
+		return nil, fmt.Errorf("ssh: padding %d too large", padding)
+	}
+
+	plain = plain[1 : len(plain)-int(padding)]
+	return plain, nil
+}
+
+func (c *chacha20Poly1305Cipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error {
+	var counter [16]byte
+	binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
+
+	var polyKey [32]byte
+	chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
+
+	// There is no blocksize, so fall back to multiple of 8 byte
+	// padding, as described in RFC 4253, Sec 6.
+	const packetSizeMultiple = 8
+
+	padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple
+	if padding < 4 {
+		padding += packetSizeMultiple
+	}
+
+	// size (4 bytes), padding (1), payload, padding, tag.
+	totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize
+	if cap(c.buf) < totalLength {
+		c.buf = make([]byte, totalLength)
+	} else {
+		c.buf = c.buf[:totalLength]
+	}
+
+	binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding))
+	chacha20.XORKeyStream(c.buf, c.buf[:4], &counter, &c.lengthKey)
+	c.buf[4] = byte(padding)
+	copy(c.buf[5:], payload)
+	packetEnd := 5 + len(payload) + padding
+	if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil {
+		return err
+	}
+
+	counter[0] = 1
+	chacha20.XORKeyStream(c.buf[4:], c.buf[4:packetEnd], &counter, &c.contentKey)
+
+	var mac [poly1305.TagSize]byte
+	poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey)
+
+	copy(c.buf[packetEnd:], mac[:])
+
+	if _, err := w.Write(c.buf); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/ssh/common.go b/ssh/common.go
index 135b4ed..77f9c5b 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -28,6 +28,7 @@
 var supportedCiphers = []string{
 	"aes128-ctr", "aes192-ctr", "aes256-ctr",
 	"aes128-gcm@openssh.com",
+	chacha20Poly1305ID,
 	"arcfour256", "arcfour128",
 }
 
diff --git a/ssh/transport.go b/ssh/transport.go
index 82da0d7..5a56753 100644
--- a/ssh/transport.go
+++ b/ssh/transport.go
@@ -254,15 +254,14 @@
 func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
 	iv, key, macKey := generateKeys(d, algs, kex)
 
-	if algs.Cipher == gcmCipherID {
+	switch algs.Cipher {
+	case chacha20Poly1305ID:
+		return newChaCha20Cipher(key)
+	case gcmCipherID:
 		return newGCMCipher(iv, key)
-	}
-
-	if algs.Cipher == aes128cbcID {
+	case aes128cbcID:
 		return newAESCBCCipher(iv, key, macKey, algs)
-	}
-
-	if algs.Cipher == tripledescbcID {
+	case tripledescbcID:
 		return newTripleDESCBCCipher(iv, key, macKey, algs)
 	}