Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(136)

Unified Diff: ssh/transport.go

Issue 86190043: code review 86190043: go.crypto/ssh: import gosshnew. (Closed)
Patch Set: diff -r 2990fc550b9f https://code.google.com/p/go.crypto Created 10 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ssh/testdata_test.go ('k') | ssh/transport_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ssh/transport.go
===================================================================
--- a/ssh/transport.go
+++ b/ssh/transport.go
@@ -6,26 +6,12 @@
import (
"bufio"
- "crypto/cipher"
- "crypto/subtle"
- "encoding/binary"
"errors"
- "hash"
"io"
- "net"
- "sync"
)
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 = 256 * 1024
+ gcmCipherID = "aes128-gcm@openssh.com"
)
// packetConn represents a transport that implements packet based
@@ -41,225 +27,128 @@
Close() error
}
-// transport represents the SSH connection to the remote peer.
+// transport is the keyingTransport that implements the SSH packet
+// protocol.
type transport struct {
- reader
- writer
+ reader connectionState
+ writer connectionState
- net.Conn
+ bufReader *bufio.Reader
+ bufWriter *bufio.Writer
+ rand io.Reader
+
+ io.Closer
// Initial H used for the session ID. Once assigned this does
// not change, even during subsequent key exchanges.
sessionID []byte
}
-// reader represents the incoming connection state.
-type reader struct {
- io.Reader
- common
+func (t *transport) getSessionID() []byte {
+ if t.sessionID == nil {
+ panic("session ID not set yet")
+ }
+ s := make([]byte, len(t.sessionID))
+ copy(s, t.sessionID)
+ return s
}
-// writer represents the outgoing connection state.
-type writer struct {
- sync.Mutex // protects writer.Writer from concurrent writes
- *bufio.Writer
- rand io.Reader
- common
+// packetCipher represents a combination of SSH encryption/MAC
+// protocol. A single instance should be used for one direction only.
+type packetCipher interface {
+ // writePacket encrypts the packet and writes it to w. The
+ // contents of the packet are generally scrambled.
+ writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error
+
+ // readPacket reads and decrypts a packet of data. The
+ // returned packet may be overwritten by future calls of
+ // readPacket.
+ readPacket(seqnum uint32, r io.Reader) ([]byte, error)
+}
+
+// connectionState represents one side (read or write) of the
+// connection. This is necessary because each direction has its own
+// keys, and can even have its own algorithms
+type connectionState struct {
+ packetCipher
+ seqNum uint32
+ dir direction
+ pendingKeyChange chan packetCipher
}
// prepareKeyChange sets up key material for a keychange. The key changes in
// both directions are triggered by reading and writing a msgNewKey packet
// respectively.
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
- t.writer.cipherAlgo = algs.wCipher
- t.writer.macAlgo = algs.wMAC
- t.writer.compressionAlgo = algs.wCompression
-
- t.reader.cipherAlgo = algs.rCipher
- t.reader.macAlgo = algs.rMAC
- t.reader.compressionAlgo = algs.rCompression
-
if t.sessionID == nil {
t.sessionID = kexResult.H
}
kexResult.SessionID = t.sessionID
- t.reader.pendingKeyChange <- kexResult
- t.writer.pendingKeyChange <- kexResult
+
+ if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
+ return err
+ } else {
+ t.reader.pendingKeyChange <- ciph
+ }
+
+ if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
+ return err
+ } else {
+ t.writer.pendingKeyChange <- ciph
+ }
+
return nil
}
-// common represents the cipher state needed to process messages in a single
-// direction.
-type common struct {
- seqNum uint32
- mac hash.Hash
- cipher cipher.Stream
-
- cipherAlgo string
- macAlgo string
- compressionAlgo string
-
- dir direction
- pendingKeyChange chan *kexResult
+// Read and decrypt next packet.
+func (t *transport) readPacket() ([]byte, error) {
+ return t.reader.readPacket(t.bufReader)
}
-// Read and decrypt a single packet from the remote peer.
-func (r *reader) readPacket() ([]byte, error) {
- var lengthBytes = make([]byte, 5)
- var macSize uint32
- if _, err := io.ReadFull(r, lengthBytes); err != nil {
- return nil, err
+func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
+ packet, err := s.packetCipher.readPacket(s.seqNum, r)
+ s.seqNum++
+ if err == nil && len(packet) == 0 {
+ err = errors.New("ssh: zero length packet")
}
- r.cipher.XORKeyStream(lengthBytes, lengthBytes)
-
- if r.mac != nil {
- r.mac.Reset()
- seqNumBytes := []byte{
- byte(r.seqNum >> 24),
- byte(r.seqNum >> 16),
- byte(r.seqNum >> 8),
- byte(r.seqNum),
- }
- r.mac.Write(seqNumBytes)
- r.mac.Write(lengthBytes)
- macSize = uint32(r.mac.Size())
- }
-
- length := binary.BigEndian.Uint32(lengthBytes[0:4])
- paddingLength := uint32(lengthBytes[4])
-
- 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")
- }
-
- packet := make([]byte, length-1+macSize)
- if _, err := io.ReadFull(r, packet); err != nil {
- return nil, err
- }
- mac := packet[length-1:]
- r.cipher.XORKeyStream(packet, packet[:length-1])
-
- if r.mac != nil {
- r.mac.Write(packet[:length-1])
- if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
- return nil, errors.New("ssh: MAC failure")
- }
- }
-
- r.seqNum++
- packet = packet[:length-paddingLength-1]
-
if len(packet) > 0 && packet[0] == msgNewKeys {
select {
- case k := <-r.pendingKeyChange:
- if err := r.setupKeys(r.dir, k); err != nil {
- return nil, err
- }
+ case cipher := <-s.pendingKeyChange:
+ s.packetCipher = cipher
default:
return nil, errors.New("ssh: got bogus newkeys message.")
}
}
- return packet, nil
+
+ // The packet may point to an internal buffer, so copy the
+ // packet out here.
+ fresh := make([]byte, len(packet))
+ copy(fresh, packet)
+
+ return fresh, err
}
-// Read and decrypt next packet discarding debug and noop messages.
-func (t *transport) readPacket() ([]byte, error) {
- for {
- packet, err := t.reader.readPacket()
- if err != nil {
- return nil, err
- }
- if len(packet) == 0 {
- return nil, errors.New("ssh: zero length packet")
- }
-
- if packet[0] != msgIgnore && packet[0] != msgDebug {
- return packet, nil
- }
- }
- panic("unreachable")
+func (t *transport) writePacket(packet []byte) error {
+ return t.writer.writePacket(t.bufWriter, t.rand, packet)
}
-// Encrypt and send a packet of data to the remote peer.
-func (w *writer) writePacket(packet []byte) error {
+func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error {
changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
- if len(packet) > maxPacket {
- return errors.New("ssh: packet too large")
- }
- w.Mutex.Lock()
- defer w.Mutex.Unlock()
-
- paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
- if paddingLength < 4 {
- paddingLength += packetSizeMultiple
- }
-
- length := len(packet) + 1 + paddingLength
- lengthBytes := []byte{
- byte(length >> 24),
- byte(length >> 16),
- byte(length >> 8),
- byte(length),
- byte(paddingLength),
- }
- padding := make([]byte, paddingLength)
- _, err := io.ReadFull(w.rand, padding)
+ err := s.packetCipher.writePacket(s.seqNum, w, rand, packet)
if err != nil {
return err
}
-
- if w.mac != nil {
- w.mac.Reset()
- seqNumBytes := []byte{
- byte(w.seqNum >> 24),
- byte(w.seqNum >> 16),
- byte(w.seqNum >> 8),
- byte(w.seqNum),
- }
- w.mac.Write(seqNumBytes)
- w.mac.Write(lengthBytes)
- w.mac.Write(packet)
- w.mac.Write(padding)
- }
-
- // TODO(dfc) lengthBytes, packet and padding should be
- // subslices of a single buffer
- w.cipher.XORKeyStream(lengthBytes, lengthBytes)
- w.cipher.XORKeyStream(packet, packet)
- w.cipher.XORKeyStream(padding, padding)
-
- if _, err := w.Write(lengthBytes); err != nil {
- return err
- }
- if _, err := w.Write(packet); err != nil {
- return err
- }
- if _, err := w.Write(padding); err != nil {
- return err
- }
-
- if w.mac != nil {
- if _, err := w.Write(w.mac.Sum(nil)); err != nil {
- return err
- }
- }
-
- w.seqNum++
if err = w.Flush(); err != nil {
return err
}
-
+ s.seqNum++
if changeKeys {
select {
- case k := <-w.pendingKeyChange:
- err = w.setupKeys(w.dir, k)
+ case cipher := <-s.pendingKeyChange:
+ s.packetCipher = cipher
default:
panic("ssh: no key material for msgNewKeys")
}
@@ -267,24 +156,20 @@
return err
}
-func newTransport(conn net.Conn, rand io.Reader, isClient bool) *transport {
+func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport {
t := &transport{
- reader: reader{
- Reader: bufio.NewReader(conn),
- common: common{
- cipher: noneCipher{},
- pendingKeyChange: make(chan *kexResult, 1),
- },
+ bufReader: bufio.NewReader(rwc),
+ bufWriter: bufio.NewWriter(rwc),
+ rand: rand,
+ reader: connectionState{
+ packetCipher: &streamPacketCipher{cipher: noneCipher{}},
+ pendingKeyChange: make(chan packetCipher, 1),
},
- writer: writer{
- Writer: bufio.NewWriter(conn),
- rand: rand,
- common: common{
- cipher: noneCipher{},
- pendingKeyChange: make(chan *kexResult, 1),
- },
+ writer: connectionState{
+ packetCipher: &streamPacketCipher{cipher: noneCipher{}},
+ pendingKeyChange: make(chan packetCipher, 1),
},
- Conn: conn,
+ Closer: rwc,
}
if isClient {
t.reader.dir = serverKeys
@@ -303,48 +188,64 @@
macKeyTag []byte
}
-// TODO(dfc) can this be made a constant ?
var (
serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
)
+// generateKeys generates key material for IV, MAC and encryption.
+func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
+ cipherMode := cipherModes[algs.Cipher]
+ macMode := macModes[algs.MAC]
+
+ iv = make([]byte, cipherMode.ivSize)
+ key = make([]byte, cipherMode.keySize)
+ macKey = make([]byte, macMode.keySize)
+
+ generateKeyMaterial(iv, d.ivTag, kex)
+ generateKeyMaterial(key, d.keyTag, kex)
+ generateKeyMaterial(macKey, d.macKeyTag, kex)
+ return
+}
+
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
// described in RFC 4253, section 6.4. direction should either be serverKeys
// (to setup server->client keys) or clientKeys (for client->server keys).
-func (c *common) setupKeys(d direction, r *kexResult) error {
- cipherMode := cipherModes[c.cipherAlgo]
- macMode := macModes[c.macAlgo]
+func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
+ iv, key, macKey := generateKeys(d, algs, kex)
- iv := make([]byte, cipherMode.ivSize)
- key := make([]byte, cipherMode.keySize)
- macKey := make([]byte, macMode.keySize)
+ if algs.Cipher == gcmCipherID {
+ return newGCMCipher(iv, key, macKey)
+ }
- h := r.Hash.New()
- generateKeyMaterial(iv, d.ivTag, r.K, r.H, r.SessionID, h)
- generateKeyMaterial(key, d.keyTag, r.K, r.H, r.SessionID, h)
- generateKeyMaterial(macKey, d.macKeyTag, r.K, r.H, r.SessionID, h)
-
- c.mac = macMode.new(macKey)
+ c := &streamPacketCipher{
+ mac: macModes[algs.MAC].new(macKey),
+ }
+ c.macResult = make([]byte, c.mac.Size())
var err error
- c.cipher, err = cipherMode.createCipher(key, iv)
- return err
+ c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
+ if err != nil {
+ return nil, err
+ }
+
+ return c, nil
}
// generateKeyMaterial fills out with key material generated from tag, K, H
// and sessionId, as specified in RFC 4253, section 7.2.
-func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
+func generateKeyMaterial(out, tag []byte, r *kexResult) {
var digestsSoFar []byte
+ h := r.Hash.New()
for len(out) > 0 {
h.Reset()
- h.Write(K)
- h.Write(H)
+ h.Write(r.K)
+ h.Write(r.H)
if len(digestsSoFar) == 0 {
h.Write(tag)
- h.Write(sessionId)
+ h.Write(r.SessionID)
} else {
h.Write(digestsSoFar)
}
« no previous file with comments | « ssh/testdata_test.go ('k') | ssh/transport_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b