~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/golang.org/x/crypto/ssh/transport.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2011 The Go Authors. All rights reserved.
 
2
// Use of this source code is governed by a BSD-style
 
3
// license that can be found in the LICENSE file.
 
4
 
 
5
package ssh
 
6
 
 
7
import (
 
8
        "bufio"
 
9
        "errors"
 
10
        "io"
 
11
)
 
12
 
 
13
const (
 
14
        gcmCipherID = "aes128-gcm@openssh.com"
 
15
        aes128cbcID = "aes128-cbc"
 
16
)
 
17
 
 
18
// packetConn represents a transport that implements packet based
 
19
// operations.
 
20
type packetConn interface {
 
21
        // Encrypt and send a packet of data to the remote peer.
 
22
        writePacket(packet []byte) error
 
23
 
 
24
        // Read a packet from the connection
 
25
        readPacket() ([]byte, error)
 
26
 
 
27
        // Close closes the write-side of the connection.
 
28
        Close() error
 
29
}
 
30
 
 
31
// transport is the keyingTransport that implements the SSH packet
 
32
// protocol.
 
33
type transport struct {
 
34
        reader connectionState
 
35
        writer connectionState
 
36
 
 
37
        bufReader *bufio.Reader
 
38
        bufWriter *bufio.Writer
 
39
        rand      io.Reader
 
40
 
 
41
        io.Closer
 
42
 
 
43
        // Initial H used for the session ID. Once assigned this does
 
44
        // not change, even during subsequent key exchanges.
 
45
        sessionID []byte
 
46
}
 
47
 
 
48
// getSessionID returns the ID of the SSH connection. The return value
 
49
// should not be modified.
 
50
func (t *transport) getSessionID() []byte {
 
51
        if t.sessionID == nil {
 
52
                panic("session ID not set yet")
 
53
        }
 
54
        return t.sessionID
 
55
}
 
56
 
 
57
// packetCipher represents a combination of SSH encryption/MAC
 
58
// protocol.  A single instance should be used for one direction only.
 
59
type packetCipher interface {
 
60
        // writePacket encrypts the packet and writes it to w. The
 
61
        // contents of the packet are generally scrambled.
 
62
        writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error
 
63
 
 
64
        // readPacket reads and decrypts a packet of data. The
 
65
        // returned packet may be overwritten by future calls of
 
66
        // readPacket.
 
67
        readPacket(seqnum uint32, r io.Reader) ([]byte, error)
 
68
}
 
69
 
 
70
// connectionState represents one side (read or write) of the
 
71
// connection. This is necessary because each direction has its own
 
72
// keys, and can even have its own algorithms
 
73
type connectionState struct {
 
74
        packetCipher
 
75
        seqNum           uint32
 
76
        dir              direction
 
77
        pendingKeyChange chan packetCipher
 
78
}
 
79
 
 
80
// prepareKeyChange sets up key material for a keychange. The key changes in
 
81
// both directions are triggered by reading and writing a msgNewKey packet
 
82
// respectively.
 
83
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
 
84
        if t.sessionID == nil {
 
85
                t.sessionID = kexResult.H
 
86
        }
 
87
 
 
88
        kexResult.SessionID = t.sessionID
 
89
 
 
90
        if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
 
91
                return err
 
92
        } else {
 
93
                t.reader.pendingKeyChange <- ciph
 
94
        }
 
95
 
 
96
        if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil {
 
97
                return err
 
98
        } else {
 
99
                t.writer.pendingKeyChange <- ciph
 
100
        }
 
101
 
 
102
        return nil
 
103
}
 
104
 
 
105
// Read and decrypt next packet.
 
106
func (t *transport) readPacket() ([]byte, error) {
 
107
        return t.reader.readPacket(t.bufReader)
 
108
}
 
109
 
 
110
func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
 
111
        packet, err := s.packetCipher.readPacket(s.seqNum, r)
 
112
        s.seqNum++
 
113
        if err == nil && len(packet) == 0 {
 
114
                err = errors.New("ssh: zero length packet")
 
115
        }
 
116
 
 
117
        if len(packet) > 0 && packet[0] == msgNewKeys {
 
118
                select {
 
119
                case cipher := <-s.pendingKeyChange:
 
120
                        s.packetCipher = cipher
 
121
                default:
 
122
                        return nil, errors.New("ssh: got bogus newkeys message.")
 
123
                }
 
124
        }
 
125
 
 
126
        // The packet may point to an internal buffer, so copy the
 
127
        // packet out here.
 
128
        fresh := make([]byte, len(packet))
 
129
        copy(fresh, packet)
 
130
 
 
131
        return fresh, err
 
132
}
 
133
 
 
134
func (t *transport) writePacket(packet []byte) error {
 
135
        return t.writer.writePacket(t.bufWriter, t.rand, packet)
 
136
}
 
137
 
 
138
func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error {
 
139
        changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
 
140
 
 
141
        err := s.packetCipher.writePacket(s.seqNum, w, rand, packet)
 
142
        if err != nil {
 
143
                return err
 
144
        }
 
145
        if err = w.Flush(); err != nil {
 
146
                return err
 
147
        }
 
148
        s.seqNum++
 
149
        if changeKeys {
 
150
                select {
 
151
                case cipher := <-s.pendingKeyChange:
 
152
                        s.packetCipher = cipher
 
153
                default:
 
154
                        panic("ssh: no key material for msgNewKeys")
 
155
                }
 
156
        }
 
157
        return err
 
158
}
 
159
 
 
160
func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport {
 
161
        t := &transport{
 
162
                bufReader: bufio.NewReader(rwc),
 
163
                bufWriter: bufio.NewWriter(rwc),
 
164
                rand:      rand,
 
165
                reader: connectionState{
 
166
                        packetCipher:     &streamPacketCipher{cipher: noneCipher{}},
 
167
                        pendingKeyChange: make(chan packetCipher, 1),
 
168
                },
 
169
                writer: connectionState{
 
170
                        packetCipher:     &streamPacketCipher{cipher: noneCipher{}},
 
171
                        pendingKeyChange: make(chan packetCipher, 1),
 
172
                },
 
173
                Closer: rwc,
 
174
        }
 
175
        if isClient {
 
176
                t.reader.dir = serverKeys
 
177
                t.writer.dir = clientKeys
 
178
        } else {
 
179
                t.reader.dir = clientKeys
 
180
                t.writer.dir = serverKeys
 
181
        }
 
182
 
 
183
        return t
 
184
}
 
185
 
 
186
type direction struct {
 
187
        ivTag     []byte
 
188
        keyTag    []byte
 
189
        macKeyTag []byte
 
190
}
 
191
 
 
192
var (
 
193
        serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
 
194
        clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
 
195
)
 
196
 
 
197
// generateKeys generates key material for IV, MAC and encryption.
 
198
func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
 
199
        cipherMode := cipherModes[algs.Cipher]
 
200
        macMode := macModes[algs.MAC]
 
201
 
 
202
        iv = make([]byte, cipherMode.ivSize)
 
203
        key = make([]byte, cipherMode.keySize)
 
204
        macKey = make([]byte, macMode.keySize)
 
205
 
 
206
        generateKeyMaterial(iv, d.ivTag, kex)
 
207
        generateKeyMaterial(key, d.keyTag, kex)
 
208
        generateKeyMaterial(macKey, d.macKeyTag, kex)
 
209
        return
 
210
}
 
211
 
 
212
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
 
213
// described in RFC 4253, section 6.4. direction should either be serverKeys
 
214
// (to setup server->client keys) or clientKeys (for client->server keys).
 
215
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
 
216
        iv, key, macKey := generateKeys(d, algs, kex)
 
217
 
 
218
        if algs.Cipher == gcmCipherID {
 
219
                return newGCMCipher(iv, key, macKey)
 
220
        }
 
221
 
 
222
        if algs.Cipher == aes128cbcID {
 
223
                return newAESCBCCipher(iv, key, macKey, algs)
 
224
        }
 
225
 
 
226
        c := &streamPacketCipher{
 
227
                mac: macModes[algs.MAC].new(macKey),
 
228
        }
 
229
        c.macResult = make([]byte, c.mac.Size())
 
230
 
 
231
        var err error
 
232
        c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
 
233
        if err != nil {
 
234
                return nil, err
 
235
        }
 
236
 
 
237
        return c, nil
 
238
}
 
239
 
 
240
// generateKeyMaterial fills out with key material generated from tag, K, H
 
241
// and sessionId, as specified in RFC 4253, section 7.2.
 
242
func generateKeyMaterial(out, tag []byte, r *kexResult) {
 
243
        var digestsSoFar []byte
 
244
 
 
245
        h := r.Hash.New()
 
246
        for len(out) > 0 {
 
247
                h.Reset()
 
248
                h.Write(r.K)
 
249
                h.Write(r.H)
 
250
 
 
251
                if len(digestsSoFar) == 0 {
 
252
                        h.Write(tag)
 
253
                        h.Write(r.SessionID)
 
254
                } else {
 
255
                        h.Write(digestsSoFar)
 
256
                }
 
257
 
 
258
                digest := h.Sum(nil)
 
259
                n := copy(out, digest)
 
260
                out = out[n:]
 
261
                if len(out) > 0 {
 
262
                        digestsSoFar = append(digestsSoFar, digest...)
 
263
                }
 
264
        }
 
265
}
 
266
 
 
267
const packageVersion = "SSH-2.0-Go"
 
268
 
 
269
// Sends and receives a version line.  The versionLine string should
 
270
// be US ASCII, start with "SSH-2.0-", and should not include a
 
271
// newline. exchangeVersions returns the other side's version line.
 
272
func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {
 
273
        // Contrary to the RFC, we do not ignore lines that don't
 
274
        // start with "SSH-2.0-" to make the library usable with
 
275
        // nonconforming servers.
 
276
        for _, c := range versionLine {
 
277
                // The spec disallows non US-ASCII chars, and
 
278
                // specifically forbids null chars.
 
279
                if c < 32 {
 
280
                        return nil, errors.New("ssh: junk character in version line")
 
281
                }
 
282
        }
 
283
        if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {
 
284
                return
 
285
        }
 
286
 
 
287
        them, err = readVersion(rw)
 
288
        return them, err
 
289
}
 
290
 
 
291
// maxVersionStringBytes is the maximum number of bytes that we'll
 
292
// accept as a version string. RFC 4253 section 4.2 limits this at 255
 
293
// chars
 
294
const maxVersionStringBytes = 255
 
295
 
 
296
// Read version string as specified by RFC 4253, section 4.2.
 
297
func readVersion(r io.Reader) ([]byte, error) {
 
298
        versionString := make([]byte, 0, 64)
 
299
        var ok bool
 
300
        var buf [1]byte
 
301
 
 
302
        for len(versionString) < maxVersionStringBytes {
 
303
                _, err := io.ReadFull(r, buf[:])
 
304
                if err != nil {
 
305
                        return nil, err
 
306
                }
 
307
                // The RFC says that the version should be terminated with \r\n
 
308
                // but several SSH servers actually only send a \n.
 
309
                if buf[0] == '\n' {
 
310
                        ok = true
 
311
                        break
 
312
                }
 
313
 
 
314
                // non ASCII chars are disallowed, but we are lenient,
 
315
                // since Go doesn't use null-terminated strings.
 
316
 
 
317
                // The RFC allows a comment after a space, however,
 
318
                // all of it (version and comments) goes into the
 
319
                // session hash.
 
320
                versionString = append(versionString, buf[0])
 
321
        }
 
322
 
 
323
        if !ok {
 
324
                return nil, errors.New("ssh: overflow reading version string")
 
325
        }
 
326
 
 
327
        // There might be a '\r' on the end which we should remove.
 
328
        if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
 
329
                versionString = versionString[:len(versionString)-1]
 
330
        }
 
331
        return versionString, nil
 
332
}