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>
This commit is contained in:
committed by
Adam Langley
parent
c57d4a7191
commit
5c68cfdf2a
+179
@@ -113,6 +113,10 @@ var cipherModes = map[string]*streamCipherMode{
|
|||||||
// special case. If we add any more non-stream ciphers, we
|
// special case. If we add any more non-stream ciphers, we
|
||||||
// should invest a cleaner way to do this.
|
// should invest a cleaner way to do this.
|
||||||
gcmCipherID: {16, 12, 0, nil},
|
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
|
// 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)]
|
plain = plain[1 : length-uint32(padding)]
|
||||||
return plain, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package ssh
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/aes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -20,6 +21,10 @@ func TestDefaultCiphersExist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPacketCiphers(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 {
|
for cipher := range cipherModes {
|
||||||
kr := &kexResult{Hash: crypto.SHA1}
|
kr := &kexResult{Hash: crypto.SHA1}
|
||||||
algs := directionAlgorithms{
|
algs := directionAlgorithms{
|
||||||
|
|||||||
@@ -206,6 +206,14 @@ func (c *Config) SetDefaults() {
|
|||||||
if c.Ciphers == nil {
|
if c.Ciphers == nil {
|
||||||
c.Ciphers = supportedCiphers
|
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 {
|
if c.KeyExchanges == nil {
|
||||||
c.KeyExchanges = supportedKexAlgos
|
c.KeyExchanges = supportedKexAlgos
|
||||||
|
|||||||
@@ -280,6 +280,9 @@ func TestCiphers(t *testing.T) {
|
|||||||
var config ssh.Config
|
var config ssh.Config
|
||||||
config.SetDefaults()
|
config.SetDefaults()
|
||||||
cipherOrder := config.Ciphers
|
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 {
|
for _, ciph := range cipherOrder {
|
||||||
server := newServer(t)
|
server := newServer(t)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
gcmCipherID = "aes128-gcm@openssh.com"
|
gcmCipherID = "aes128-gcm@openssh.com"
|
||||||
|
aes128cbcID = "aes128-cbc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// packetConn represents a transport that implements packet based
|
// 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)
|
return newGCMCipher(iv, key, macKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if algs.Cipher == aes128cbcID {
|
||||||
|
return newAESCBCCipher(iv, key, macKey, algs)
|
||||||
|
}
|
||||||
|
|
||||||
c := &streamPacketCipher{
|
c := &streamPacketCipher{
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
mac: macModes[algs.MAC].new(macKey),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user