// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file. package ssh import ( "crypto/aes" "crypto/cipher" "crypto/rc4" "crypto/subtle" "encoding/binary" "errors" "fmt" "hash" "io") const ( packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC // indicates implementations SHOULD be able to handle larger packet sizes, but then // waffles on about reasonable limits. // // OpenSSH caps their maxPacket at 256kB so we choose to do // the same. maxPacket is also used to ensure that uint32 // length fields do not overflow, so it should remain well // below 4G. maxPacket = 256 * 1024) // noneCipher implements cipher.Stream and provides no encryption. It is used// by the transport before the first key-exchange.struct noneCipher {} func (c noneCipher) XORKeyStream(dst, src []byte) { copy(dst, src)} func newAESCTR(key, iv []byte) (cipher.Stream, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewCTR(c, iv), nil} func newRC4(key, iv []byte) (cipher.Stream, error) { return rc4.NewCipher(key)} struct streamCipherMode { keySize int ivSize int skip int createFunc func(key, iv []byte) (cipher.Stream, error)} func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) { if len(key) < c.keySize { panic("ssh: key length too small for cipher") } if len(iv) < c.ivSize { panic("ssh: iv too small for cipher") } stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize]) if err != nil { return nil, err } var streamDump []byte if c.skip > 0 { streamDump = make([]byte, 512) } for remainingToDump := c.skip; remainingToDump > 0; { dumpThisTime := remainingToDump if dumpThisTime > len(streamDump) { dumpThisTime = len(streamDump) } stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) remainingToDump -= dumpThisTime } return stream, nil} // cipherModes documents properties of supported ciphers. Ciphers not included// are not supported and will not be negotiated, even if explicitly requested in// ClientConfig.Crypto.Ciphers.var cipherModes = map[string]*streamCipherMode{ // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms // are defined in the order specified in the RFC. "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR}, "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR}, "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR}, // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. // They are defined in the order specified in the RFC. "arcfour128": {16, 0, 1536, newRC4}, "arcfour256": {32, 0, 1536, newRC4}, // Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol. // Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and // RC4) has problems with weak keys, and should be used with caution." // RFC4345 introduces improved versions of Arcfour. "arcfour": {16, 0, 0, newRC4}, // AES-GCM is not a stream cipher, so it is constructed with a // special case. If we add any more non-stream ciphers, we // should invest a cleaner way to do this. gcmCipherID: {16, 12, 0, nil},} // prefixLen is the length of the packet prefix that contains the packet length// and number of padding bytes.const prefixLen = 5 // streamPacketCipher is a packetCipher using a stream cipher.struct streamPacketCipher { mac hash.Hash cipher cipher.Stream // The following members are to avoid per-packet allocations. prefix [prefixLen]byte seqNumBytes [4]byte padding [2 * packetSizeMultiple]byte packetData []byte macResult []byte} // readPacket reads and decrypt a single packet from the reader argument.func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { if _, err := io.ReadFull(r, s.prefix[:]); err != nil { return nil, err } s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) length := binary.BigEndian.Uint32(s.prefix[0:4]) paddingLength := uint32(s.prefix[4]) var macSize uint32 if s.mac != nil { s.mac.Reset() binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) s.mac.Write(s.seqNumBytes[:]) s.mac.Write(s.prefix[:]) macSize = uint32(s.mac.Size()) } if length <= paddingLength+1 { return nil, errors.New("ssh: invalid packet length, packet too small") } if length > maxPacket { return nil, errors.New("ssh: invalid packet length, packet too large") } // the maxPacket check above ensures that length-1+macSize // does not overflow. if uint32(cap(s.packetData)) < length-1+macSize { s.packetData = make([]byte, length-1+macSize) } else { s.packetData = s.packetData[:length-1+macSize] } if _, err := io.ReadFull(r, s.packetData); err != nil { return nil, err } mac := s.packetData[length-1:] data := s.packetData[:length-1] s.cipher.XORKeyStream(data, data) if s.mac != nil { s.mac.Write(data) s.macResult = s.mac.Sum(s.macResult[:0]) if subtle.ConstantTimeCompare(s.macResult, mac) != 1 { return nil, errors.New("ssh: MAC failure") } } return s.packetData[:length-paddingLength-1], nil} // writePacket encrypts and sends a packet of data to the writer argumentfunc (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { if len(packet) > maxPacket { return errors.New("ssh: packet too large") } paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple if paddingLength < 4 { paddingLength += packetSizeMultiple } length := len(packet) + 1 + paddingLength binary.BigEndian.PutUint32(s.prefix[:], uint32(length)) s.prefix[4] = byte(paddingLength) padding := s.padding[:paddingLength] if _, err := io.ReadFull(rand, padding); err != nil { return err } if s.mac != nil { s.mac.Reset() binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) s.mac.Write(s.seqNumBytes[:]) s.mac.Write(s.prefix[:]) s.mac.Write(packet) s.mac.Write(padding) } s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) s.cipher.XORKeyStream(packet, packet) s.cipher.XORKeyStream(padding, padding) if _, err := w.Write(s.prefix[:]); err != nil { return err } if _, err := w.Write(packet); err != nil { return err } if _, err := w.Write(padding); err != nil { return err } if s.mac != nil { s.macResult = s.mac.Sum(s.macResult[:0]) if _, err := w.Write(s.macResult); err != nil { return err } } return nil} struct gcmCipher { aead cipher.AEAD prefix [4]byte iv []byte buf []byte} func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } aead, err := cipher.NewGCM(c) if err != nil { return nil, err } return &gcmCipher{ aead: aead, iv: iv, }, nil} const gcmTagSize = 16 func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { // Pad out to multiple of 16 bytes. This is different from the // stream cipher because that encrypts the length too. padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple) if padding < 4 { padding += packetSizeMultiple } length := uint32(len(packet) + int(padding) + 1) binary.BigEndian.PutUint32(c.prefix[:], length) if _, err := w.Write(c.prefix[:]); err != nil { return err } if cap(c.buf) < int(length) { c.buf = make([]byte, length) } else { c.buf = c.buf[:length] } c.buf[0] = padding copy(c.buf[1:], packet) if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil { return err } c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:]) if _, err := w.Write(c.buf); err != nil { return err } c.incIV() return nil} func (c *gcmCipher) incIV() { for i := 4 + 7; i >= 4; i-- { c.iv[i]++ if c.iv[i] != 0 { break } }} func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { if _, err := io.ReadFull(r, c.prefix[:]); err != nil { return nil, err } length := binary.BigEndian.Uint32(c.prefix[:]) if length > maxPacket { return nil, errors.New("ssh: max packet length exceeded.") } if cap(c.buf) < int(length+gcmTagSize) { c.buf = make([]byte, length+gcmTagSize) } else { c.buf = c.buf[:length+gcmTagSize] } if _, err := io.ReadFull(r, c.buf); err != nil { return nil, err } plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:]) if err != nil { return nil, err } c.incIV() padding := plain[0] if padding < 4 || padding >= 20 { return nil, fmt.Errorf("ssh: illegal padding %d", padding) } if int(padding+1) >= len(plain) { return nil, fmt.Errorf("ssh: padding %d too large", padding) } plain = plain[1 : length-uint32(padding)] return plain, nil}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"crypto/aes"
"crypto/cipher"
"crypto/rc4"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
)
const (
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
// indicates implementations SHOULD be able to handle larger packet sizes, but then
// waffles on about reasonable limits.
//
// OpenSSH caps their maxPacket at 256kB so we choose to do
// the same. maxPacket is also used to ensure that uint32
// length fields do not overflow, so it should remain well
// below 4G.
maxPacket = 256 * 1024
)
// noneCipher implements cipher.Stream and provides no encryption. It is used
// by the transport before the first key-exchange.
struct noneCipher {}
func (c noneCipher) XORKeyStream(dst, src []byte) {
copy(dst, src)
}
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(c, iv), nil
}
func newRC4(key, iv []byte) (cipher.Stream, error) {
return rc4.NewCipher(key)
}
struct streamCipherMode {
keySize int
ivSize int
skip int
createFunc func(key, iv []byte) (cipher.Stream, error)
}
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
if len(key) < c.keySize {
panic("ssh: key length too small for cipher")
}
if len(iv) < c.ivSize {
panic("ssh: iv too small for cipher")
}
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
if err != nil {
return nil, err
}
var streamDump []byte
if c.skip > 0 {
streamDump = make([]byte, 512)
}
for remainingToDump := c.skip; remainingToDump > 0; {
dumpThisTime := remainingToDump
if dumpThisTime > len(streamDump) {
dumpThisTime = len(streamDump)
}
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
remainingToDump -= dumpThisTime
}
return stream, nil
}
// cipherModes documents properties of supported ciphers. Ciphers not included
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*streamCipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, 1536, newRC4},
"arcfour256": {32, 0, 1536, newRC4},
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
// RC4) has problems with weak keys, and should be used with caution."
// RFC4345 introduces improved versions of Arcfour.
"arcfour": {16, 0, 0, newRC4},
// AES-GCM is not a stream cipher, so it is constructed with a
// special case. If we add any more non-stream ciphers, we
// should invest a cleaner way to do this.
gcmCipherID: {16, 12, 0, nil},
}
// prefixLen is the length of the packet prefix that contains the packet length
// and number of padding bytes.
const prefixLen = 5
// streamPacketCipher is a packetCipher using a stream cipher.
struct streamPacketCipher {
mac hash.Hash
cipher cipher.Stream
// The following members are to avoid per-packet allocations.
prefix [prefixLen]byte
seqNumBytes [4]byte
padding [2 * packetSizeMultiple]byte
packetData []byte
macResult []byte
}
// readPacket reads and decrypt a single packet from the reader argument.
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
return nil, err
}
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
length := binary.BigEndian.Uint32(s.prefix[0:4])
paddingLength := uint32(s.prefix[4])
var macSize uint32
if s.mac != nil {
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
s.mac.Write(s.prefix[:])
macSize = uint32(s.mac.Size())
}
if length <= paddingLength+1 {
return nil, errors.New("ssh: invalid packet length, packet too small")
}
if length > maxPacket {
return nil, errors.New("ssh: invalid packet length, packet too large")
}
// the maxPacket check above ensures that length-1+macSize
// does not overflow.
if uint32(cap(s.packetData)) < length-1+macSize {
s.packetData = make([]byte, length-1+macSize)
} else {
s.packetData = s.packetData[:length-1+macSize]
}
if _, err := io.ReadFull(r, s.packetData); err != nil {
return nil, err
}
mac := s.packetData[length-1:]
data := s.packetData[:length-1]
s.cipher.XORKeyStream(data, data)
if s.mac != nil {
s.mac.Write(data)
s.macResult = s.mac.Sum(s.macResult[:0])
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
return nil, errors.New("ssh: MAC failure")
}
}
return s.packetData[:length-paddingLength-1], nil
}
// writePacket encrypts and sends a packet of data to the writer argument
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
if len(packet) > maxPacket {
return errors.New("ssh: packet too large")
}
paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
if paddingLength < 4 {
paddingLength += packetSizeMultiple
}
length := len(packet) + 1 + paddingLength
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
s.prefix[4] = byte(paddingLength)
padding := s.padding[:paddingLength]
if _, err := io.ReadFull(rand, padding); err != nil {
return err
}
if s.mac != nil {
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
s.mac.Write(s.prefix[:])
s.mac.Write(packet)
s.mac.Write(padding)
}
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
s.cipher.XORKeyStream(packet, packet)
s.cipher.XORKeyStream(padding, padding)
if _, err := w.Write(s.prefix[:]); err != nil {
return err
}
if _, err := w.Write(packet); err != nil {
return err
}
if _, err := w.Write(padding); err != nil {
return err
}
if s.mac != nil {
s.macResult = s.mac.Sum(s.macResult[:0])
if _, err := w.Write(s.macResult); err != nil {
return err
}
}
return nil
}
struct gcmCipher {
aead cipher.AEAD
prefix [4]byte
iv []byte
buf []byte
}
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
return &gcmCipher{
aead: aead,
iv: iv,
}, nil
}
const gcmTagSize = 16
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
// Pad out to multiple of 16 bytes. This is different from the
// stream cipher because that encrypts the length too.
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
if padding < 4 {
padding += packetSizeMultiple
}
length := uint32(len(packet) + int(padding) + 1)
binary.BigEndian.PutUint32(c.prefix[:], length)
if _, err := w.Write(c.prefix[:]); err != nil {
return err
}
if cap(c.buf) < int(length) {
c.buf = make([]byte, length)
} else {
c.buf = c.buf[:length]
}
c.buf[0] = padding
copy(c.buf[1:], packet)
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
return err
}
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
if _, err := w.Write(c.buf); err != nil {
return err
}
c.incIV()
return nil
}
func (c *gcmCipher) incIV() {
for i := 4 + 7; i >= 4; i-- {
c.iv[i]++
if c.iv[i] != 0 {
break
}
}
}
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
return nil, err
}
length := binary.BigEndian.Uint32(c.prefix[:])
if length > maxPacket {
return nil, errors.New("ssh: max packet length exceeded.")
}
if cap(c.buf) < int(length+gcmTagSize) {
c.buf = make([]byte, length+gcmTagSize)
} else {
c.buf = c.buf[:length+gcmTagSize]
}
if _, err := io.ReadFull(r, c.buf); err != nil {
return nil, err
}
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
if err != nil {
return nil, err
}
c.incIV()
padding := plain[0]
if padding < 4 || padding >= 20 {
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
}
if int(padding+1) >= len(plain) {
return nil, fmt.Errorf("ssh: padding %d too large", padding)
}
plain = plain[1 : length-uint32(padding)]
return plain, nil
}