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

Delta Between Two Patch Sets: ssh/client.go

Issue 14494058: code review 14494058: go.crypto/ssh: support rekeying in both directions. (Closed)
Left Patch Set: diff -r 5ff5636e18c9 https://code.google.com/p/go.crypto Created 10 years, 5 months ago
Right Patch Set: diff -r cd1eea1eb828 https://code.google.com/p/go.crypto Created 10 years, 5 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:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | ssh/client_auth.go » ('j') | ssh/common.go » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 "crypto/rand"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "sync"
15 )
1 16
2 // ClientConn represents the client side of an SSH connection. 17 // ClientConn represents the client side of an SSH connection.
3 type ClientConn struct { 18 type ClientConn struct {
4 » *transport 19 » transport rekeyingTransport
5 » config *ClientConfig 20 » config *ClientConfig
21 » sshConn
6 chanList // channels associated with this connection 22 chanList // channels associated with this connection
7 forwardList // forwarded tcpip connections from the remote side 23 forwardList // forwarded tcpip connections from the remote side
24 globalRequest
25
26 // Address as passed to the Dial function.
27 dialAddress string
28
29 serverVersion string
30 }
31
32 type globalRequest struct {
33 sync.Mutex
34 response chan interface{}
35 }
36
37 // Client returns a new SSH client connection using c as the underlying transpor t.
38 func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
39 return clientWithAddress(c, "", config)
40 }
41
42 func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientCo nn, error) {
43 conn := &ClientConn{
44 config: config,
45 sshConn: sshConn{c, c},
46 globalRequest: globalRequest{response: make(chan interface{}, 1) },
47 dialAddress: addr,
48 }
49
50 if err := conn.handshake(); err != nil {
51 return nil, fmt.Errorf("ssh: handshake failed: %v", err)
52 }
53 go conn.mainLoop()
54 return conn, nil
55 }
56
57 // handshake performs the client side key exchange. See RFC 4253 Section 7.
58 func (c *ClientConn) handshake() error {
59 clientVersion := []byte(packageVersion)
60 if c.config.ClientVersion != "" {
61 clientVersion = []byte(c.config.ClientVersion)
62 }
63
64 serverVersion, err := exchangeVersions(c.sshConn.conn, clientVersion)
65 if err != nil {
66 return err
67 }
68 c.serverVersion = string(serverVersion)
69
70 c.transport = newClientTransport(
71 newTransport(c.sshConn.conn, c.config.rand(), true /* is client */),
72 clientVersion, serverVersion, c.config, c.dialAddress, c.sshConn .RemoteAddr())
73 if err := c.transport.requestKeyChange(); err != nil {
74 return err
75 }
76
77 if packet, err := c.transport.readPacket(); err != nil {
78 return err
79 } else if packet[0] != msgNewKeys {
80 return UnexpectedMessageError{msgNewKeys, packet[0]}
81 }
82 return c.authenticate()
83 }
84
85 // verifyHostKeySignature verifies the host key obtained in the key
86 // exchange.
87 func verifyHostKeySignature(hostKeyAlgo string, result *kexResult) error {
88 hostKey, rest, ok := ParsePublicKey(result.HostKey)
89 if len(rest) > 0 || !ok {
90 return errors.New("ssh: could not parse hostkey")
91 }
92
93 sig, rest, ok := parseSignatureBody(result.Signature)
94 if len(rest) > 0 || !ok {
95 return errors.New("ssh: signature parse error")
96 }
97 if sig.Format != hostKeyAlgo {
98 return fmt.Errorf("ssh: got signature type %q, want %q", sig.For mat, hostKeyAlgo)
99 }
100
101 if !hostKey.Verify(result.H, sig.Blob) {
102 return errors.New("ssh: host key signature error")
103 }
104 return nil
105 }
106
107 // mainLoop reads incoming messages and routes channel messages
108 // to their respective ClientChans.
109 func (c *ClientConn) mainLoop() {
110 defer func() {
111 c.transport.Close()
112 c.chanList.closeAll()
113 c.forwardList.closeAll()
114 }()
115
116 for {
117 packet, err := c.transport.readPacket()
118 if err != nil {
119 break
120 }
121 // TODO(dfc) A note on blocking channel use.
122 // The msg, data and dataExt channels of a clientChan can
123 // cause this loop to block indefinitely if the consumer does
124 // not service them.
125 switch packet[0] {
126 case msgChannelData:
127 if len(packet) < 9 {
128 // malformed data packet
129 return
130 }
131 remoteId := binary.BigEndian.Uint32(packet[1:5])
132 length := binary.BigEndian.Uint32(packet[5:9])
133 packet = packet[9:]
134
135 if length != uint32(len(packet)) {
136 return
137 }
138 ch, ok := c.getChan(remoteId)
139 if !ok {
140 return
141 }
142 ch.stdout.write(packet)
143 case msgChannelExtendedData:
144 if len(packet) < 13 {
145 // malformed data packet
146 return
147 }
148 remoteId := binary.BigEndian.Uint32(packet[1:5])
149 datatype := binary.BigEndian.Uint32(packet[5:9])
150 length := binary.BigEndian.Uint32(packet[9:13])
151 packet = packet[13:]
152
153 if length != uint32(len(packet)) {
154 return
155 }
156 // RFC 4254 5.2 defines data_type_code 1 to be data dest ined
157 // for stderr on interactive sessions. Other data types are
158 // silently discarded.
159 if datatype == 1 {
160 ch, ok := c.getChan(remoteId)
161 if !ok {
162 return
163 }
164 ch.stderr.write(packet)
165 }
166
167 case msgNewKeys:
168 // A rekeying happened.
169 default:
170 decoded, err := decode(packet)
171 if err != nil {
172 if _, ok := err.(UnexpectedMessageError); ok {
173 fmt.Printf("mainLoop: unexpected message : %v\n", err)
174 continue
175 }
176 return
177 }
178 switch msg := decoded.(type) {
179 case *channelOpenMsg:
180 c.handleChanOpen(msg)
181 case *channelOpenConfirmMsg:
182 ch, ok := c.getChan(msg.PeersId)
183 if !ok {
184 return
185 }
186 ch.msg <- msg
187 case *channelOpenFailureMsg:
188 ch, ok := c.getChan(msg.PeersId)
189 if !ok {
190 return
191 }
192 ch.msg <- msg
193 case *channelCloseMsg:
194 ch, ok := c.getChan(msg.PeersId)
195 if !ok {
196 return
197 }
198 ch.Close()
199 close(ch.msg)
200 c.chanList.remove(msg.PeersId)
201 case *channelEOFMsg:
202 ch, ok := c.getChan(msg.PeersId)
203 if !ok {
204 return
205 }
206 ch.stdout.eof()
207 // RFC 4254 is mute on how EOF affects dataExt m essages but
208 // it is logical to signal EOF at the same time.
209 ch.stderr.eof()
210 case *channelRequestSuccessMsg:
211 ch, ok := c.getChan(msg.PeersId)
212 if !ok {
213 return
214 }
215 ch.msg <- msg
216 case *channelRequestFailureMsg:
217 ch, ok := c.getChan(msg.PeersId)
218 if !ok {
219 return
220 }
221 ch.msg <- msg
222 case *channelRequestMsg:
223 ch, ok := c.getChan(msg.PeersId)
224 if !ok {
225 return
226 }
227 ch.msg <- msg
228 case *windowAdjustMsg:
229 ch, ok := c.getChan(msg.PeersId)
230 if !ok {
231 return
232 }
233 if !ch.remoteWin.add(msg.AdditionalBytes) {
234 // invalid window update
235 return
236 }
237 case *globalRequestMsg:
238 // This handles keepalive messages and matches
239 // the behaviour of OpenSSH.
240 if msg.WantReply {
241 c.transport.writePacket(marshal(msgReque stFailure, globalRequestFailureMsg{}))
242 }
243 case *globalRequestSuccessMsg, *globalRequestFailureMsg:
244 c.globalRequest.response <- msg
245 case *disconnectMsg:
246 return
247 default:
248 fmt.Printf("mainLoop: unhandled message %T: %v\n ", msg, msg)
249 }
250 }
251 }
252 }
253
254 // Handle channel open messages from the remote side.
255 func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
256 if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
257 c.sendConnectionFailed(msg.PeersId)
258 }
259
260 switch msg.ChanType {
261 case "forwarded-tcpip":
262 laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData)
263 if !ok {
264 // invalid request
265 c.sendConnectionFailed(msg.PeersId)
266 return
267 }
268
269 l, ok := c.forwardList.lookup(*laddr)
270 if !ok {
271 // TODO: print on a more structured log.
272 fmt.Println("could not find forward list entry for", lad dr)
273 // Section 7.2, implementations MUST reject spurious inc oming
274 // connections.
275 c.sendConnectionFailed(msg.PeersId)
276 return
277 }
278 raddr, rest, ok := parseTCPAddr(rest)
279 if !ok {
280 // invalid request
281 c.sendConnectionFailed(msg.PeersId)
282 return
283 }
284 ch := c.newChan(c.transport)
285 ch.remoteId = msg.PeersId
286 ch.remoteWin.add(msg.PeersWindow)
287 ch.maxPacket = msg.MaxPacketSize
288
289 m := channelOpenConfirmMsg{
290 PeersId: ch.remoteId,
291 MyId: ch.localId,
292 MyWindow: 1 << 14,
293
294 // As per RFC 4253 6.1, 32k is also the minimum.
295 MaxPacketSize: 1 << 15,
296 }
297
298 c.transport.writePacket(marshal(msgChannelOpenConfirm, m))
299 l <- forward{ch, raddr}
300 default:
301 // unknown channel type
302 m := channelOpenFailureMsg{
303 PeersId: msg.PeersId,
304 Reason: UnknownChannelType,
305 Message: fmt.Sprintf("unknown channel type: %v", msg.Ch anType),
306 Language: "en_US.UTF-8",
307 }
308 c.transport.writePacket(marshal(msgChannelOpenFailure, m))
309 }
310 }
311
312 // sendGlobalRequest sends a global request message as specified
313 // in RFC4254 section 4. To correctly synchronise messages, a lock
314 // is held internally until a response is returned.
315 func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) {
316 c.globalRequest.Lock()
317 defer c.globalRequest.Unlock()
318 if err := c.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil {
319 return nil, err
320 }
321 r := <-c.globalRequest.response
322 if r, ok := r.(*globalRequestSuccessMsg); ok {
323 return r, nil
324 }
325 return nil, errors.New("request failed")
326 }
327
328 // sendConnectionFailed rejects an incoming channel identified
329 // by remoteId.
330 func (c *ClientConn) sendConnectionFailed(remoteId uint32) error {
331 m := channelOpenFailureMsg{
332 PeersId: remoteId,
333 Reason: ConnectionFailed,
334 Message: "invalid request",
335 Language: "en_US.UTF-8",
336 }
337 return c.transport.writePacket(marshal(msgChannelOpenFailure, m))
338 }
339
340 // parseTCPAddr parses the originating address from the remote into a *net.TCPAd dr.
341 // RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardli st
342 // requires a valid *net.TCPAddr to operate, so we enforce that restriction here .
343 func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
344 addr, b, ok := parseString(b)
345 if !ok {
346 return nil, b, false
347 }
348 port, b, ok := parseUint32(b)
349 if !ok {
350 return nil, b, false
351 }
352 ip := net.ParseIP(string(addr))
353 if ip == nil {
354 return nil, b, false
355 }
356 return &net.TCPAddr{IP: ip, Port: int(port)}, b, true
357 }
358
359 // Dial connects to the given network address using net.Dial and
360 // then initiates a SSH handshake, returning the resulting client connection.
361 func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
362 conn, err := net.Dial(network, addr)
363 if err != nil {
364 return nil, err
365 }
366 return clientWithAddress(conn, addr, config)
367 }
368
369 // A ClientConfig structure is used to configure a ClientConn. After one has
370 // been passed to an SSH function it must not be modified.
371 type ClientConfig struct {
372 // Rand provides the source of entropy for key exchange. If Rand is
373 // nil, the cryptographic random reader in package crypto/rand will
374 // be used.
375 Rand io.Reader
376
377 // The username to authenticate.
378 User string
379
380 // A slice of ClientAuth methods. Only the first instance
381 // of a particular RFC 4252 method will be used during authentication.
382 Auth []ClientAuth
383
384 // HostKeyChecker, if not nil, is called during the cryptographic
385 // handshake to validate the server's host key. A nil HostKeyChecker
386 // implies that all host keys are accepted.
387 HostKeyChecker HostKeyChecker
388
389 // Cryptographic-related configuration.
390 Crypto CryptoConfig
391
392 // The identification string that will be used for the connection.
393 // If empty, a reasonable default is used.
394 ClientVersion string
395 }
396
397 func (c *ClientConfig) rand() io.Reader {
398 if c.Rand == nil {
399 return rand.Reader
400 }
401 return c.Rand
402 }
403
404 // Thread safe channel list.
405 type chanList struct {
406 // protects concurrent access to chans
407 sync.Mutex
408 // chans are indexed by the local id of the channel, clientChan.localId.
409 // The PeersId value of messages received by ClientConn.mainLoop is
410 // used to locate the right local clientChan in this slice.
411 chans []*clientChan
412 }
413
414 // Allocate a new ClientChan with the next avail local id.
415 func (c *chanList) newChan(p packetConn) *clientChan {
416 c.Lock()
417 defer c.Unlock()
418 for i := range c.chans {
419 if c.chans[i] == nil {
420 ch := newClientChan(p, uint32(i))
421 c.chans[i] = ch
422 return ch
423 }
424 }
425 i := len(c.chans)
426 ch := newClientChan(p, uint32(i))
427 c.chans = append(c.chans, ch)
428 return ch
429 }
430
431 func (c *chanList) getChan(id uint32) (*clientChan, bool) {
432 c.Lock()
433 defer c.Unlock()
434 if id >= uint32(len(c.chans)) {
435 return nil, false
436 }
437 return c.chans[id], true
438 }
439
440 func (c *chanList) remove(id uint32) {
441 c.Lock()
442 defer c.Unlock()
443 c.chans[id] = nil
444 }
445
446 func (c *chanList) closeAll() {
447 c.Lock()
448 defer c.Unlock()
449
450 for _, ch := range c.chans {
451 if ch == nil {
452 continue
453 }
454 ch.Close()
455 close(ch.msg)
456 }
457 }
LEFTRIGHT
« no previous file | ssh/client_auth.go » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

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