diff options
| author | Nathan(yinian) Hu <nathanhu@google.com> | 2015-04-08 12:39:14 +1000 |
|---|---|---|
| committer | Adam Langley <agl@golang.org> | 2015-04-10 21:42:31 +0000 |
| commit | 5c68cfdf2a545b5ff576c075b459d1fc0c606f82 (patch) | |
| tree | bac931fa27b9c6dfc9aead1fe72bcdbf980e4455 /ssh/cipher.go | |
| parent | c57d4a71915a248dbad846d60825145062b4c18e (diff) | |
| download | go-x-crypto-5c68cfdf2a545b5ff576c075b459d1fc0c606f82.tar.xz | |
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>
Diffstat (limited to 'ssh/cipher.go')
| -rw-r--r-- | ssh/cipher.go | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/ssh/cipher.go b/ssh/cipher.go index 642696b..b173183 100644 --- a/ssh/cipher.go +++ b/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 +} |
