瀏覽代碼

crypto/ssh: add support for aes128-cbc cipher.

The aes128cbc cipher is commented out in cipher.go on purpose, anyone wants to
use the cipher needs to uncomment line 119 in cipher.go

Fixes #4274.

Change-Id: I4bbc88ab884bda821c5f155dcf495bb7235c8605
Reviewed-on: https://go-review.googlesource.com/8396
Reviewed-by: Adam Langley <agl@golang.org>
Nathan(yinian) Hu 10 年之前
父節點
當前提交
5c68cfdf2a
共有 5 個文件被更改,包括 200 次插入0 次删除
  1. 179 0
      ssh/cipher.go
  2. 5 0
      ssh/cipher_test.go
  3. 8 0
      ssh/common.go
  4. 3 0
      ssh/test/session_test.go
  5. 5 0
      ssh/transport.go

+ 179 - 0
ssh/cipher.go

@@ -113,6 +113,10 @@ var cipherModes = map[string]*streamCipherMode{
 	// special case. If we add any more non-stream ciphers, we
 	// should invest a cleaner way to do this.
 	gcmCipherID: {16, 12, 0, nil},
+
+	// insecure cipher, see http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf
+	// uncomment below to enable it.
+	// aes128cbcID: {16, aes.BlockSize, 0, nil},
 }
 
 // prefixLen is the length of the packet prefix that contains the packet length
@@ -342,3 +346,178 @@ func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
 	plain = plain[1 : length-uint32(padding)]
 	return plain, nil
 }
+
+// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
+type cbcCipher struct {
+	mac       hash.Hash
+	decrypter cipher.BlockMode
+	encrypter cipher.BlockMode
+
+	// The following members are to avoid per-packet allocations.
+	seqNumBytes [4]byte
+	packetData  []byte
+	macResult   []byte
+}
+
+func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
+	c, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	return &cbcCipher{
+		mac:        macModes[algs.MAC].new(macKey),
+		decrypter:  cipher.NewCBCDecrypter(c, iv),
+		encrypter:  cipher.NewCBCEncrypter(c, iv),
+		packetData: make([]byte, 1024),
+	}, nil
+}
+
+func maxUInt32(a, b int) uint32 {
+	if a > b {
+		return uint32(a)
+	}
+	return uint32(b)
+}
+
+const (
+	cbcMinPacketSizeMultiple = 8
+	cbcMinPacketSize         = 16
+	cbcMinPaddingSize        = 4
+)
+
+func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
+	blockSize := c.decrypter.BlockSize()
+
+	// Read the header, which will include some of the subsequent data in the
+	// case of block ciphers - this is copied back to the payload later.
+	// How many bytes of payload/padding will be read with this first read.
+	firstBlockLength := (prefixLen + blockSize - 1) / blockSize * blockSize
+	firstBlock := c.packetData[:firstBlockLength]
+	if _, err := io.ReadFull(r, firstBlock); err != nil {
+		return nil, err
+	}
+
+	c.decrypter.CryptBlocks(firstBlock, firstBlock)
+	length := binary.BigEndian.Uint32(firstBlock[:4])
+	if length > maxPacket {
+		return nil, errors.New("ssh: packet too large")
+	}
+	if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
+		// The minimum size of a packet is 16 (or the cipher block size, whichever
+		// is larger) bytes.
+		return nil, errors.New("ssh: packet too small")
+	}
+	// The length of the packet (including the length field but not the MAC) must
+	// be a multiple of the block size or 8, whichever is larger.
+	if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
+		return nil, errors.New("ssh: invalid packet length multiple")
+	}
+
+	paddingLength := uint32(firstBlock[4])
+	if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
+		return nil, errors.New("ssh: invalid packet length")
+	}
+
+	var macSize uint32
+	if c.mac != nil {
+		macSize = uint32(c.mac.Size())
+	}
+
+	// Positions within the c.packetData buffer:
+	macStart := 4 + length
+	paddingStart := macStart - paddingLength
+
+	// Entire packet size, starting before length, ending at end of mac.
+	entirePacketSize := macStart + macSize
+
+	// Ensure c.packetData is large enough for the entire packet data.
+	if uint32(cap(c.packetData)) < entirePacketSize {
+		// Still need to upsize and copy, but this should be rare at runtime, only
+		// on upsizing the packetData buffer.
+		c.packetData = make([]byte, entirePacketSize)
+		copy(c.packetData, firstBlock)
+	} else {
+		c.packetData = c.packetData[:entirePacketSize]
+	}
+
+	if _, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
+		return nil, err
+	}
+
+	remainingCrypted := c.packetData[firstBlockLength:macStart]
+	c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
+
+	mac := c.packetData[macStart:]
+	if c.mac != nil {
+		c.mac.Reset()
+		binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
+		c.mac.Write(c.seqNumBytes[:])
+		c.mac.Write(c.packetData[:macStart])
+		c.macResult = c.mac.Sum(c.macResult[:0])
+		if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
+			return nil, errors.New("ssh: MAC failure")
+		}
+	}
+
+	return c.packetData[prefixLen:paddingStart], nil
+}
+
+func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
+	effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
+
+	// Length of encrypted portion of the packet (header, payload, padding).
+	// Enforce minimum padding and packet size.
+	encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
+	// Enforce block size.
+	encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
+
+	length := encLength - 4
+	paddingLength := int(length) - (1 + len(packet))
+
+	var macSize uint32
+	if c.mac != nil {
+		macSize = uint32(c.mac.Size())
+	}
+	// Overall buffer contains: header, payload, padding, mac.
+	// Space for the MAC is reserved in the capacity but not the slice length.
+	bufferSize := encLength + macSize
+	if uint32(cap(c.packetData)) < bufferSize {
+		c.packetData = make([]byte, encLength, bufferSize)
+	} else {
+		c.packetData = c.packetData[:encLength]
+	}
+
+	p := c.packetData
+
+	// Packet header.
+	binary.BigEndian.PutUint32(p, length)
+	p = p[4:]
+	p[0] = byte(paddingLength)
+
+	// Payload.
+	p = p[1:]
+	copy(p, packet)
+
+	// Padding.
+	p = p[len(packet):]
+	if _, err := io.ReadFull(rand, p); err != nil {
+		return err
+	}
+
+	if c.mac != nil {
+		c.mac.Reset()
+		binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
+		c.mac.Write(c.seqNumBytes[:])
+		c.mac.Write(c.packetData)
+		// The MAC is now appended into the capacity reserved for it earlier.
+		c.packetData = c.mac.Sum(c.packetData)
+	}
+
+	c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
+
+	if _, err := w.Write(c.packetData); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 5 - 0
ssh/cipher_test.go

@@ -7,6 +7,7 @@ package ssh
 import (
 	"bytes"
 	"crypto"
+	"crypto/aes"
 	"crypto/rand"
 	"testing"
 )
@@ -20,6 +21,10 @@ func TestDefaultCiphersExist(t *testing.T) {
 }
 
 func TestPacketCiphers(t *testing.T) {
+	// Still test aes128cbc cipher althought it's commented out.
+	cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
+	defer delete(cipherModes, aes128cbcID)
+
 	for cipher := range cipherModes {
 		kr := &kexResult{Hash: crypto.SHA1}
 		algs := directionAlgorithms{

+ 8 - 0
ssh/common.go

@@ -206,6 +206,14 @@ func (c *Config) SetDefaults() {
 	if c.Ciphers == nil {
 		c.Ciphers = supportedCiphers
 	}
+	var ciphers []string
+	for _, c := range c.Ciphers {
+		if cipherModes[c] != nil {
+			// reject the cipher if we have no cipherModes definition
+			ciphers = append(ciphers, c)
+		}
+	}
+	c.Ciphers = ciphers
 
 	if c.KeyExchanges == nil {
 		c.KeyExchanges = supportedKexAlgos

+ 3 - 0
ssh/test/session_test.go

@@ -280,6 +280,9 @@ func TestCiphers(t *testing.T) {
 	var config ssh.Config
 	config.SetDefaults()
 	cipherOrder := config.Ciphers
+	// This cipher 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")
 
 	for _, ciph := range cipherOrder {
 		server := newServer(t)

+ 5 - 0
ssh/transport.go

@@ -12,6 +12,7 @@ import (
 
 const (
 	gcmCipherID = "aes128-gcm@openssh.com"
+	aes128cbcID = "aes128-cbc"
 )
 
 // packetConn represents a transport that implements packet based
@@ -218,6 +219,10 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac
 		return newGCMCipher(iv, key, macKey)
 	}
 
+	if algs.Cipher == aes128cbcID {
+		return newAESCBCCipher(iv, key, macKey, algs)
+	}
+
 	c := &streamPacketCipher{
 		mac: macModes[algs.MAC].new(macKey),
 	}