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

Side by Side Diff: ssh/transport.go

Issue 5564059: code review 5564059: go.crypto: initial code (Closed)
Patch Set: diff -r b50a7fb49394 https://code.google.com/p/go.crypto Created 12 years, 2 months ago
Left:
Right:
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 unified diff | Download patch
« no previous file with comments | « ssh/tcpip_func_test.go ('k') | ssh/transport_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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 "crypto"
10 "crypto/cipher"
11 "crypto/hmac"
12 "crypto/sha1"
13 "crypto/subtle"
14 "errors"
15 "hash"
16 "io"
17 "net"
18 "sync"
19 )
20
21 const (
22 packetSizeMultiple = 16 // TODO(huin) this should be determined by the c ipher.
23 minPacketSize = 16
24 maxPacketSize = 36000
25 minPaddingSize = 4 // TODO(huin) should this be configurable?
26 )
27
28 // filteredConn reduces the set of methods exposed when embeddeding
29 // a net.Conn inside ssh.transport.
30 // TODO(dfc) suggestions for a better name will be warmly received.
31 type filteredConn interface {
32 // Close closes the connection.
33 Close() error
34
35 // LocalAddr returns the local network address.
36 LocalAddr() net.Addr
37
38 // RemoteAddr returns the remote network address.
39 RemoteAddr() net.Addr
40 }
41
42 // Types implementing packetWriter provide the ability to send packets to
43 // an SSH peer.
44 type packetWriter interface {
45 // Encrypt and send a packet of data to the remote peer.
46 writePacket(packet []byte) error
47 }
48
49 // transport represents the SSH connection to the remote peer.
50 type transport struct {
51 reader
52 writer
53
54 filteredConn
55 }
56
57 // reader represents the incoming connection state.
58 type reader struct {
59 io.Reader
60 common
61 }
62
63 // writer represnts the outgoing connection state.
64 type writer struct {
65 *sync.Mutex // protects writer.Writer from concurrent writes
66 *bufio.Writer
67 rand io.Reader
68 common
69 }
70
71 // common represents the cipher state needed to process messages in a single
72 // direction.
73 type common struct {
74 seqNum uint32
75 mac hash.Hash
76 cipher cipher.Stream
77
78 cipherAlgo string
79 macAlgo string
80 compressionAlgo string
81 }
82
83 // Read and decrypt a single packet from the remote peer.
84 func (r *reader) readOnePacket() ([]byte, error) {
85 var lengthBytes = make([]byte, 5)
86 var macSize uint32
87 if _, err := io.ReadFull(r, lengthBytes); err != nil {
88 return nil, err
89 }
90
91 r.cipher.XORKeyStream(lengthBytes, lengthBytes)
92
93 if r.mac != nil {
94 r.mac.Reset()
95 seqNumBytes := []byte{
96 byte(r.seqNum >> 24),
97 byte(r.seqNum >> 16),
98 byte(r.seqNum >> 8),
99 byte(r.seqNum),
100 }
101 r.mac.Write(seqNumBytes)
102 r.mac.Write(lengthBytes)
103 macSize = uint32(r.mac.Size())
104 }
105
106 length := uint32(lengthBytes[0])<<24 | uint32(lengthBytes[1])<<16 | uint 32(lengthBytes[2])<<8 | uint32(lengthBytes[3])
107 paddingLength := uint32(lengthBytes[4])
108
109 if length <= paddingLength+1 {
110 return nil, errors.New("invalid packet length")
111 }
112 if length > maxPacketSize {
113 return nil, errors.New("packet too large")
114 }
115
116 packet := make([]byte, length-1+macSize)
117 if _, err := io.ReadFull(r, packet); err != nil {
118 return nil, err
119 }
120 mac := packet[length-1:]
121 r.cipher.XORKeyStream(packet, packet[:length-1])
122
123 if r.mac != nil {
124 r.mac.Write(packet[:length-1])
125 if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
126 return nil, errors.New("ssh: MAC failure")
127 }
128 }
129
130 r.seqNum++
131 return packet[:length-paddingLength-1], nil
132 }
133
134 // Read and decrypt next packet discarding debug and noop messages.
135 func (t *transport) readPacket() ([]byte, error) {
136 for {
137 packet, err := t.readOnePacket()
138 if err != nil {
139 return nil, err
140 }
141 if packet[0] != msgIgnore && packet[0] != msgDebug {
142 return packet, nil
143 }
144 }
145 panic("unreachable")
146 }
147
148 // Encrypt and send a packet of data to the remote peer.
149 func (w *writer) writePacket(packet []byte) error {
150 w.Mutex.Lock()
151 defer w.Mutex.Unlock()
152
153 paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
154 if paddingLength < 4 {
155 paddingLength += packetSizeMultiple
156 }
157
158 length := len(packet) + 1 + paddingLength
159 lengthBytes := []byte{
160 byte(length >> 24),
161 byte(length >> 16),
162 byte(length >> 8),
163 byte(length),
164 byte(paddingLength),
165 }
166 padding := make([]byte, paddingLength)
167 _, err := io.ReadFull(w.rand, padding)
168 if err != nil {
169 return err
170 }
171
172 if w.mac != nil {
173 w.mac.Reset()
174 seqNumBytes := []byte{
175 byte(w.seqNum >> 24),
176 byte(w.seqNum >> 16),
177 byte(w.seqNum >> 8),
178 byte(w.seqNum),
179 }
180 w.mac.Write(seqNumBytes)
181 w.mac.Write(lengthBytes)
182 w.mac.Write(packet)
183 w.mac.Write(padding)
184 }
185
186 // TODO(dfc) lengthBytes, packet and padding should be
187 // subslices of a single buffer
188 w.cipher.XORKeyStream(lengthBytes, lengthBytes)
189 w.cipher.XORKeyStream(packet, packet)
190 w.cipher.XORKeyStream(padding, padding)
191
192 if _, err := w.Write(lengthBytes); err != nil {
193 return err
194 }
195 if _, err := w.Write(packet); err != nil {
196 return err
197 }
198 if _, err := w.Write(padding); err != nil {
199 return err
200 }
201
202 if w.mac != nil {
203 if _, err := w.Write(w.mac.Sum(nil)); err != nil {
204 return err
205 }
206 }
207
208 if err := w.Flush(); err != nil {
209 return err
210 }
211 w.seqNum++
212 return err
213 }
214
215 // Send a message to the remote peer
216 func (t *transport) sendMessage(typ uint8, msg interface{}) error {
217 packet := marshal(typ, msg)
218 return t.writePacket(packet)
219 }
220
221 func newTransport(conn net.Conn, rand io.Reader) *transport {
222 return &transport{
223 reader: reader{
224 Reader: bufio.NewReader(conn),
225 common: common{
226 cipher: noneCipher{},
227 },
228 },
229 writer: writer{
230 Writer: bufio.NewWriter(conn),
231 rand: rand,
232 Mutex: new(sync.Mutex),
233 common: common{
234 cipher: noneCipher{},
235 },
236 },
237 filteredConn: conn,
238 }
239 }
240
241 type direction struct {
242 ivTag []byte
243 keyTag []byte
244 macKeyTag []byte
245 }
246
247 // TODO(dfc) can this be made a constant ?
248 var (
249 serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
250 clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
251 )
252
253 // setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
254 // described in RFC 4253, section 6.4. direction should either be serverKeys
255 // (to setup server->client keys) or clientKeys (for client->server keys).
256 func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto. Hash) error {
257 cipherMode := cipherModes[c.cipherAlgo]
258
259 macKeySize := 20
260
261 iv := make([]byte, cipherMode.ivSize)
262 key := make([]byte, cipherMode.keySize)
263 macKey := make([]byte, macKeySize)
264
265 h := hashFunc.New()
266 generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
267 generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
268 generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
269
270 c.mac = truncatingMAC{12, hmac.New(sha1.New, macKey)}
271
272 cipher, err := cipherMode.createCipher(key, iv)
273 if err != nil {
274 return err
275 }
276
277 c.cipher = cipher
278
279 return nil
280 }
281
282 // generateKeyMaterial fills out with key material generated from tag, K, H
283 // and sessionId, as specified in RFC 4253, section 7.2.
284 func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
285 var digestsSoFar []byte
286
287 for len(out) > 0 {
288 h.Reset()
289 h.Write(K)
290 h.Write(H)
291
292 if len(digestsSoFar) == 0 {
293 h.Write(tag)
294 h.Write(sessionId)
295 } else {
296 h.Write(digestsSoFar)
297 }
298
299 digest := h.Sum(nil)
300 n := copy(out, digest)
301 out = out[n:]
302 if len(out) > 0 {
303 digestsSoFar = append(digestsSoFar, digest...)
304 }
305 }
306 }
307
308 // truncatingMAC wraps around a hash.Hash and truncates the output digest to
309 // a given size.
310 type truncatingMAC struct {
311 length int
312 hmac hash.Hash
313 }
314
315 func (t truncatingMAC) Write(data []byte) (int, error) {
316 return t.hmac.Write(data)
317 }
318
319 func (t truncatingMAC) Sum(in []byte) []byte {
320 out := t.hmac.Sum(in)
321 return out[:len(in)+t.length]
322 }
323
324 func (t truncatingMAC) Reset() {
325 t.hmac.Reset()
326 }
327
328 func (t truncatingMAC) Size() int {
329 return t.length
330 }
331
332 func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
333
334 // maxVersionStringBytes is the maximum number of bytes that we'll accept as a
335 // version string. In the event that the client is talking a different protocol
336 // we need to set a limit otherwise we will keep using more and more memory
337 // while searching for the end of the version handshake.
338 const maxVersionStringBytes = 1024
339
340 // Read version string as specified by RFC 4253, section 4.2.
341 func readVersion(r io.Reader) ([]byte, error) {
342 versionString := make([]byte, 0, 64)
343 var ok bool
344 var buf [1]byte
345 forEachByte:
346 for len(versionString) < maxVersionStringBytes {
347 _, err := io.ReadFull(r, buf[:])
348 if err != nil {
349 return nil, err
350 }
351 // The RFC says that the version should be terminated with \r\n
352 // but several SSH servers actually only send a \n.
353 if buf[0] == '\n' {
354 ok = true
355 break forEachByte
356 }
357 versionString = append(versionString, buf[0])
358 }
359
360 if !ok {
361 return nil, errors.New("ssh: failed to read version string")
362 }
363
364 // There might be a '\r' on the end which we should remove.
365 if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
366 versionString = versionString[:len(versionString)-1]
367 }
368 return versionString, nil
369 }
OLDNEW
« no previous file with comments | « ssh/tcpip_func_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