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

Delta Between Two Patch Sets: ssh/client_auth.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 | « ssh/client.go ('k') | ssh/common.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 "errors"
9 "fmt"
10 "io"
11 "net"
12 )
13
14 // authenticate authenticates with the remote server. See RFC 4252.
15 func (c *ClientConn) authenticate() error {
16 // initiate user auth session
17 if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequ estMsg{serviceUserAuth})); err != nil {
18 return err
19 }
20 packet, err := c.transport.readPacket()
21 if err != nil {
22 return err
23 }
24 var serviceAccept serviceAcceptMsg
25 if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != ni l {
26 return err
27 }
28
29 // during the authentication phase the client first attempts the "none" method
1 // then any untried methods suggested by the server. 30 // then any untried methods suggested by the server.
2 tried, remain := make(map[string]bool), make(map[string]bool) 31 tried, remain := make(map[string]bool), make(map[string]bool)
3 for auth := ClientAuth(new(noneAuth)); auth != nil; { 32 for auth := ClientAuth(new(noneAuth)); auth != nil; {
4 » » ok, methods, err := auth.auth(session, c.config.User, c.transpor t, c.config.rand()) 33 » » ok, methods, err := auth.auth(c.transport.getSessionID(), c.conf ig.User, c.transport, c.config.rand())
5 if err != nil { 34 if err != nil {
6 return err 35 return err
7 } 36 }
37 if ok {
38 // success
39 return nil
40 }
41 tried[auth.method()] = true
42 delete(remain, auth.method())
43 for _, meth := range methods {
44 if tried[meth] {
45 // if we've tried meth already, skip it.
46 continue
47 }
48 remain[meth] = true
49 }
50 auth = nil
51 for _, a := range c.config.Auth {
52 if remain[a.method()] {
53 auth = a
54 break
55 }
56 }
57 }
58 return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
59 }
60
61 func keys(m map[string]bool) (s []string) {
62 for k := range m {
63 s = append(s, k)
64 }
65 return
66 }
67
68 // HostKeyChecker represents a database of known server host keys.
69 type HostKeyChecker interface {
70 // Check is called during the handshake to check server's
71 // public key for unexpected changes. The hostKey argument is
72 // in SSH wire format. It can be parsed using
73 // ssh.ParsePublicKey. The address before DNS resolution is
74 // passed in the addr argument, so the key can also be checked
75 // against the hostname.
76 Check(addr string, remote net.Addr, algorithm string, hostKey []byte) er ror
77 }
78
79 // A ClientAuth represents an instance of an RFC 4252 authentication method.
80 type ClientAuth interface {
81 // auth authenticates user over transport t.
82 // Returns true if authentication is successful.
83 // If authentication is not successful, a []string of alternative
84 // method names is returned.
85 auth(session []byte, user string, p packetConn, rand io.Reader) (bool, [ ]string, error)
86
87 // method returns the RFC 4252 method name.
88 method() string
89 }
90
91 // "none" authentication, RFC 4252 section 5.2.
92 type noneAuth int
93
94 func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reade r) (bool, []string, error) {
95 if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
96 User: user,
97 Service: serviceSSH,
98 Method: "none",
99 })); err != nil {
100 return false, nil, err
101 }
102
103 return handleAuthResponse(c)
104 }
105
106 func (n *noneAuth) method() string {
107 return "none"
108 }
109
110 // "password" authentication, RFC 4252 Section 8.
111 type passwordAuth struct {
112 ClientPassword
113 }
114
115 func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.R eader) (bool, []string, error) {
116 type passwordAuthMsg struct {
117 User string
118 Service string
119 Method string
120 Reply bool
121 Password string
122 }
123
124 pw, err := p.Password(user)
125 if err != nil {
126 return false, nil, err
127 }
128
129 if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
130 User: user,
131 Service: serviceSSH,
132 Method: "password",
133 Reply: false,
134 Password: pw,
135 })); err != nil {
136 return false, nil, err
137 }
138
139 return handleAuthResponse(c)
140 }
141
142 func (p *passwordAuth) method() string {
143 return "password"
144 }
145
146 // A ClientPassword implements access to a client's passwords.
147 type ClientPassword interface {
148 // Password returns the password to use for user.
149 Password(user string) (password string, err error)
150 }
151
152 // ClientAuthPassword returns a ClientAuth using password authentication.
153 func ClientAuthPassword(impl ClientPassword) ClientAuth {
154 return &passwordAuth{impl}
155 }
156
157 // ClientKeyring implements access to a client key ring.
158 type ClientKeyring interface {
159 // Key returns the i'th Publickey, or nil if no key exists at i.
160 Key(i int) (key PublicKey, err error)
161
162 // Sign returns a signature of the given data using the i'th key
163 // and the supplied random source.
164 Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
165 }
166
167 // "publickey" authentication, RFC 4252 Section 7.
168 type publickeyAuth struct {
169 ClientKeyring
170 }
171
172 type publickeyAuthMsg struct {
173 User string
174 Service string
175 Method string
176 // HasSig indicates to the receiver packet that the auth request is sign ed and
177 // should be used for authentication of the request.
178 HasSig bool
179 Algoname string
180 Pubkey string
181 // Sig is defined as []byte so marshal will exclude it during validateKe y
182 Sig []byte `ssh:"rest"`
183 }
184
185 func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io. Reader) (bool, []string, error) {
186 // Authentication is performed in two stages. The first stage sends an
187 // enquiry to test if each key is acceptable to the remote. The second
188 // stage attempts to authenticate with the valid keys obtained in the
189 // first stage.
190
191 var index int
192 // a map of public keys to their index in the keyring
193 validKeys := make(map[int]PublicKey)
194 for {
195 key, err := p.Key(index)
196 if err != nil {
197 return false, nil, err
198 }
199 if key == nil {
200 // no more keys in the keyring
201 break
202 }
203
204 if ok, err := p.validateKey(key, user, c); ok {
205 validKeys[index] = key
206 } else {
207 if err != nil {
208 return false, nil, err
209 }
210 }
211 index++
212 }
213
214 // methods that may continue if this auth is not successful.
215 var methods []string
216 for i, key := range validKeys {
217 pubkey := MarshalPublicKey(key)
218 algoname := key.PublicKeyAlgo()
219 data := buildDataSignedForAuth(session, userAuthRequestMsg{
220 User: user,
221 Service: serviceSSH,
222 Method: p.method(),
223 }, []byte(algoname), pubkey)
224 sigBlob, err := p.Sign(i, rand, data)
225 if err != nil {
226 return false, nil, err
227 }
228 // manually wrap the serialized signature in a string
229 s := serializeSignature(key.PublicKeyAlgo(), sigBlob)
230 sig := make([]byte, stringLength(len(s)))
231 marshalString(sig, s)
232 msg := publickeyAuthMsg{
233 User: user,
234 Service: serviceSSH,
235 Method: p.method(),
236 HasSig: true,
237 Algoname: algoname,
238 Pubkey: string(pubkey),
239 Sig: sig,
240 }
241 p := marshal(msgUserAuthRequest, msg)
242 if err := c.writePacket(p); err != nil {
243 return false, nil, err
244 }
245 success, methods, err := handleAuthResponse(c)
246 if err != nil {
247 return false, nil, err
248 }
249 if success {
250 return success, methods, err
251 }
252 }
253 return false, methods, nil
254 }
255
256 // validateKey validates the key provided it is acceptable to the server.
257 func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (b ool, error) {
258 pubkey := MarshalPublicKey(key)
259 algoname := key.PublicKeyAlgo()
260 msg := publickeyAuthMsg{
261 User: user,
262 Service: serviceSSH,
263 Method: p.method(),
264 HasSig: false,
265 Algoname: algoname,
266 Pubkey: string(pubkey),
267 }
268 if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
269 return false, err
270 }
271
272 return p.confirmKeyAck(key, c)
273 }
274
275 func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
276 pubkey := MarshalPublicKey(key)
277 algoname := key.PublicKeyAlgo()
278
279 for {
280 packet, err := c.readPacket()
281 if err != nil {
282 return false, err
283 }
284 switch packet[0] {
285 case msgUserAuthBanner:
286 // TODO(gpaul): add callback to present the banner to th e user
287 case msgUserAuthPubKeyOk:
288 msg := userAuthPubKeyOkMsg{}
289 if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil {
290 return false, err
291 }
292 if msg.Algo != algoname || msg.PubKey != string(pubkey) {
293 return false, nil
294 }
295 return true, nil
296 case msgUserAuthFailure:
297 return false, nil
298 default:
299 return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
300 }
301 }
302 panic("unreachable")
303 }
304
305 func (p *publickeyAuth) method() string {
306 return "publickey"
307 }
308
309 // ClientAuthKeyring returns a ClientAuth using public key authentication.
310 func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
311 return &publickeyAuth{impl}
312 }
313
314 // handleAuthResponse returns whether the preceding authentication request succe eded
315 // along with a list of remaining authentication methods to try next and
316 // an error if an unexpected response was received.
317 func handleAuthResponse(c packetConn) (bool, []string, error) {
318 for {
319 packet, err := c.readPacket()
320 if err != nil {
321 return false, nil, err
322 }
323
324 switch packet[0] {
325 case msgUserAuthBanner:
326 // TODO: add callback to present the banner to the user
327 case msgUserAuthFailure:
328 msg := userAuthFailureMsg{}
329 if err := unmarshal(&msg, packet, msgUserAuthFailure); e rr != nil {
330 return false, nil, err
331 }
332 return false, msg.Methods, nil
333 case msgUserAuthSuccess:
334 return true, nil, nil
335 case msgDisconnect:
336 return false, nil, io.EOF
337 default:
338 return false, nil, UnexpectedMessageError{msgUserAuthSuc cess, packet[0]}
339 }
340 }
341 panic("unreachable")
342 }
343
344 // ClientAuthAgent returns a ClientAuth using public key authentication via
345 // an agent.
346 func ClientAuthAgent(agent *AgentClient) ClientAuth {
347 return ClientAuthKeyring(&agentKeyring{agent: agent})
348 }
349
350 // agentKeyring implements ClientKeyring.
351 type agentKeyring struct {
352 agent *AgentClient
353 keys []*AgentKey
354 }
355
356 func (kr *agentKeyring) Key(i int) (key PublicKey, err error) {
357 if kr.keys == nil {
358 if kr.keys, err = kr.agent.RequestIdentities(); err != nil {
359 return
360 }
361 }
362 if i >= len(kr.keys) {
363 return
364 }
365 return kr.keys[i].Key()
366 }
367
368 func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, er r error) {
369 var key PublicKey
370 if key, err = kr.Key(i); err != nil {
371 return
372 }
373 if key == nil {
374 return nil, errors.New("ssh: key index out of range")
375 }
376 if sig, err = kr.agent.SignRequest(key, data); err != nil {
377 return
378 }
379
380 // Unmarshal the signature.
381
382 var ok bool
383 if _, sig, ok = parseString(sig); !ok {
384 return nil, errors.New("ssh: malformed signature response from a gent")
385 }
386 if sig, _, ok = parseString(sig); !ok {
387 return nil, errors.New("ssh: malformed signature response from a gent")
388 }
389 return sig, nil
390 }
391
392 // ClientKeyboardInteractive should prompt the user for the given
393 // questions.
394 type ClientKeyboardInteractive interface {
395 // Challenge should print the questions, optionally disabling
396 // echoing (eg. for passwords), and return all the answers.
397 // Challenge may be called multiple times in a single
398 // session. After successful authentication, the server may
399 // send a challenge with no questions, for which the user and
400 // instruction messages should be printed. RFC 4256 section
401 // 3.3 details how the UI should behave for both CLI and
402 // GUI environments.
403 Challenge(user, instruction string, questions []string, echos []bool) ([ ]string, error)
404 }
405
406 // ClientAuthKeyboardInteractive returns a ClientAuth using a
407 // prompt/response sequence controlled by the server.
408 func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
409 return &keyboardInteractiveAuth{impl}
410 }
411
412 type keyboardInteractiveAuth struct {
413 ClientKeyboardInteractive
414 }
415
416 func (k *keyboardInteractiveAuth) method() string {
417 return "keyboard-interactive"
418 }
419
420 func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn , rand io.Reader) (bool, []string, error) {
421 type initiateMsg struct {
422 User string
423 Service string
424 Method string
425 Language string
426 Submethods string
427 }
428
429 if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{
430 User: user,
431 Service: serviceSSH,
432 Method: "keyboard-interactive",
433 })); err != nil {
434 return false, nil, err
435 }
436
437 for {
438 packet, err := c.readPacket()
439 if err != nil {
440 return false, nil, err
441 }
442
443 // like handleAuthResponse, but with less options.
444 switch packet[0] {
445 case msgUserAuthBanner:
446 // TODO: Print banners during userauth.
447 continue
448 case msgUserAuthInfoRequest:
449 // OK
450 case msgUserAuthFailure:
451 var msg userAuthFailureMsg
452 if err := unmarshal(&msg, packet, msgUserAuthFailure); e rr != nil {
453 return false, nil, err
454 }
455 return false, msg.Methods, nil
456 case msgUserAuthSuccess:
457 return true, nil, nil
458 default:
459 return false, nil, UnexpectedMessageError{msgUserAuthInf oRequest, packet[0]}
460 }
461
462 var msg userAuthInfoRequestMsg
463 if err := unmarshal(&msg, packet, packet[0]); err != nil {
464 return false, nil, err
465 }
466
467 // Manually unpack the prompt/echo pairs.
468 rest := msg.Prompts
469 var prompts []string
470 var echos []bool
471 for i := 0; i < int(msg.NumPrompts); i++ {
472 prompt, r, ok := parseString(rest)
473 if !ok || len(r) == 0 {
474 return false, nil, errors.New("ssh: prompt forma t error")
475 }
476 prompts = append(prompts, string(prompt))
477 echos = append(echos, r[0] != 0)
478 rest = r[1:]
479 }
480
481 if len(rest) != 0 {
482 return false, nil, fmt.Errorf("ssh: junk following messa ge %q", rest)
483 }
484
485 answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos)
486 if err != nil {
487 return false, nil, err
488 }
489
490 if len(answers) != len(prompts) {
491 return false, nil, errors.New("ssh: not enough answers f rom keyboard-interactive callback")
492 }
493 responseLength := 1 + 4
494 for _, a := range answers {
495 responseLength += stringLength(len(a))
496 }
497 serialized := make([]byte, responseLength)
498 p := serialized
499 p[0] = msgUserAuthInfoResponse
500 p = p[1:]
501 p = marshalUint32(p, uint32(len(answers)))
502 for _, a := range answers {
503 p = marshalString(p, []byte(a))
504 }
505
506 if err := c.writePacket(serialized); err != nil {
507 return false, nil, err
508 }
509 }
510 }
LEFTRIGHT

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