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