// 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}, // 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. 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 }