|
|
@@ -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
|
|
|
+}
|