| // 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. |
| type noneCipher struct{} |
| |
| 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) |
| } |
| |
| type streamCipherMode struct { |
| 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}, |
| |
| // 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. |
| type streamPacketCipher struct { |
| 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 |
| } |
| |
| type gcmCipher struct { |
| 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 |
| } |