x/crypto/ssh: add 3des-cbc as a non-default cipher

3des-cbc is an insecure cipher. As such, you must explictly add it to
Config in order to use it.

Change-Id: Ifd15cde46a9908eefef1c75bae7e97b05767361d
Reviewed-on: https://go-review.googlesource.com/22770
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
diff --git a/ssh/cipher.go b/ssh/cipher.go
index 2732963..34d3917 100644
--- a/ssh/cipher.go
+++ b/ssh/cipher.go
@@ -7,6 +7,7 @@
 import (
 	"crypto/aes"
 	"crypto/cipher"
+	"crypto/des"
 	"crypto/rc4"
 	"crypto/subtle"
 	"encoding/binary"
@@ -121,6 +122,9 @@
 	// You should expect that an active attacker can recover plaintext if
 	// you do.
 	aes128cbcID: {16, aes.BlockSize, 0, nil},
+
+	// 3des-cbc is insecure and is disabled by default.
+	tripledescbcID: {24, des.BlockSize, 0, nil},
 }
 
 // prefixLen is the length of the packet prefix that contains the packet length
@@ -368,12 +372,7 @@
 	oracleCamouflage uint32
 }
 
-func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
-	c, err := aes.NewCipher(key)
-	if err != nil {
-		return nil, err
-	}
-
+func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
 	cbc := &cbcCipher{
 		mac:        macModes[algs.MAC].new(macKey),
 		decrypter:  cipher.NewCBCDecrypter(c, iv),
@@ -387,6 +386,34 @@
 	return cbc, nil
 }
 
+func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
+	c, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	cbc, err := newCBCCipher(c, iv, key, macKey, algs)
+	if err != nil {
+		return nil, err
+	}
+
+	return cbc, nil
+}
+
+func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
+	c, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	cbc, err := newCBCCipher(c, iv, key, macKey, algs)
+	if err != nil {
+		return nil, err
+	}
+
+	return cbc, nil
+}
+
 func maxUInt32(a, b int) uint32 {
 	if a > b {
 		return uint32(a)
diff --git a/ssh/test/session_test.go b/ssh/test/session_test.go
index d27ade7..faf2d2b 100644
--- a/ssh/test/session_test.go
+++ b/ssh/test/session_test.go
@@ -280,9 +280,9 @@
 	var config ssh.Config
 	config.SetDefaults()
 	cipherOrder := config.Ciphers
-	// This cipher will not be tested when commented out in cipher.go it will
+	// These ciphers will not be tested when commented out in cipher.go it will
 	// fallback to the next available as per line 292.
-	cipherOrder = append(cipherOrder, "aes128-cbc")
+	cipherOrder = append(cipherOrder, "aes128-cbc", "3des-cbc")
 
 	for _, ciph := range cipherOrder {
 		server := newServer(t)
diff --git a/ssh/transport.go b/ssh/transport.go
index bf7dd61..62fba62 100644
--- a/ssh/transport.go
+++ b/ssh/transport.go
@@ -11,8 +11,9 @@
 )
 
 const (
-	gcmCipherID = "aes128-gcm@openssh.com"
-	aes128cbcID = "aes128-cbc"
+	gcmCipherID    = "aes128-gcm@openssh.com"
+	aes128cbcID    = "aes128-cbc"
+	tripledescbcID = "3des-cbc"
 )
 
 // packetConn represents a transport that implements packet based
@@ -219,6 +220,10 @@
 		return newAESCBCCipher(iv, key, macKey, algs)
 	}
 
+	if algs.Cipher == tripledescbcID {
+		return newTripleDESCBCCipher(iv, key, macKey, algs)
+	}
+
 	c := &streamPacketCipher{
 		mac: macModes[algs.MAC].new(macKey),
 	}