OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / golang.org / x / crypto / ssh / client_auth.go
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         "bytes"
9         "errors"
10         "fmt"
11         "io"
12 )
13
14 // clientAuthenticate authenticates with the remote server. See RFC 4252.
15 func (c *connection) clientAuthenticate(config *ClientConfig) error {
16         // initiate user auth session
17         if err := c.transport.writePacket(Marshal(&serviceRequestMsg{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(packet, &serviceAccept); err != nil {
26                 return err
27         }
28
29         // during the authentication phase the client first attempts the "none" method
30         // then any untried methods suggested by the server.
31         tried := make(map[string]bool)
32         var lastMethods []string
33
34         sessionID := c.transport.getSessionID()
35         for auth := AuthMethod(new(noneAuth)); auth != nil; {
36                 ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
37                 if err != nil {
38                         return err
39                 }
40                 if ok {
41                         // success
42                         return nil
43                 }
44                 tried[auth.method()] = true
45                 if methods == nil {
46                         methods = lastMethods
47                 }
48                 lastMethods = methods
49
50                 auth = nil
51
52         findNext:
53                 for _, a := range config.Auth {
54                         candidateMethod := a.method()
55                         if tried[candidateMethod] {
56                                 continue
57                         }
58                         for _, meth := range methods {
59                                 if meth == candidateMethod {
60                                         auth = a
61                                         break findNext
62                                 }
63                         }
64                 }
65         }
66         return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
67 }
68
69 func keys(m map[string]bool) []string {
70         s := make([]string, 0, len(m))
71
72         for key := range m {
73                 s = append(s, key)
74         }
75         return s
76 }
77
78 // An AuthMethod represents an instance of an RFC 4252 authentication method.
79 type AuthMethod interface {
80         // auth authenticates user over transport t.
81         // Returns true if authentication is successful.
82         // If authentication is not successful, a []string of alternative
83         // method names is returned. If the slice is nil, it will be ignored
84         // and the previous set of possible methods will be reused.
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.Reader) (bool, []string, error) {
95         if err := c.writePacket(Marshal(&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 // passwordCallback is an AuthMethod that fetches the password through
111 // a function call, e.g. by prompting the user.
112 type passwordCallback func() (password string, err error)
113
114 func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
115         type passwordAuthMsg struct {
116                 User     string `sshtype:"50"`
117                 Service  string
118                 Method   string
119                 Reply    bool
120                 Password string
121         }
122
123         pw, err := cb()
124         // REVIEW NOTE: is there a need to support skipping a password attempt?
125         // The program may only find out that the user doesn't have a password
126         // when prompting.
127         if err != nil {
128                 return false, nil, err
129         }
130
131         if err := c.writePacket(Marshal(&passwordAuthMsg{
132                 User:     user,
133                 Service:  serviceSSH,
134                 Method:   cb.method(),
135                 Reply:    false,
136                 Password: pw,
137         })); err != nil {
138                 return false, nil, err
139         }
140
141         return handleAuthResponse(c)
142 }
143
144 func (cb passwordCallback) method() string {
145         return "password"
146 }
147
148 // Password returns an AuthMethod using the given password.
149 func Password(secret string) AuthMethod {
150         return passwordCallback(func() (string, error) { return secret, nil })
151 }
152
153 // PasswordCallback returns an AuthMethod that uses a callback for
154 // fetching a password.
155 func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
156         return passwordCallback(prompt)
157 }
158
159 type publickeyAuthMsg struct {
160         User    string `sshtype:"50"`
161         Service string
162         Method  string
163         // HasSig indicates to the receiver packet that the auth request is signed and
164         // should be used for authentication of the request.
165         HasSig   bool
166         Algoname string
167         PubKey   []byte
168         // Sig is tagged with "rest" so Marshal will exclude it during
169         // validateKey
170         Sig []byte `ssh:"rest"`
171 }
172
173 // publicKeyCallback is an AuthMethod that uses a set of key
174 // pairs for authentication.
175 type publicKeyCallback func() ([]Signer, error)
176
177 func (cb publicKeyCallback) method() string {
178         return "publickey"
179 }
180
181 func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
182         // Authentication is performed by sending an enquiry to test if a key is
183         // acceptable to the remote. If the key is acceptable, the client will
184         // attempt to authenticate with the valid key.  If not the client will repeat
185         // the process with the remaining keys.
186
187         signers, err := cb()
188         if err != nil {
189                 return false, nil, err
190         }
191         var methods []string
192         for _, signer := range signers {
193                 ok, err := validateKey(signer.PublicKey(), user, c)
194                 if err != nil {
195                         return false, nil, err
196                 }
197                 if !ok {
198                         continue
199                 }
200
201                 pub := signer.PublicKey()
202                 pubKey := pub.Marshal()
203                 sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
204                         User:    user,
205                         Service: serviceSSH,
206                         Method:  cb.method(),
207                 }, []byte(pub.Type()), pubKey))
208                 if err != nil {
209                         return false, nil, err
210                 }
211
212                 // manually wrap the serialized signature in a string
213                 s := Marshal(sign)
214                 sig := make([]byte, stringLength(len(s)))
215                 marshalString(sig, s)
216                 msg := publickeyAuthMsg{
217                         User:     user,
218                         Service:  serviceSSH,
219                         Method:   cb.method(),
220                         HasSig:   true,
221                         Algoname: pub.Type(),
222                         PubKey:   pubKey,
223                         Sig:      sig,
224                 }
225                 p := Marshal(&msg)
226                 if err := c.writePacket(p); err != nil {
227                         return false, nil, err
228                 }
229                 var success bool
230                 success, methods, err = handleAuthResponse(c)
231                 if err != nil {
232                         return false, nil, err
233                 }
234
235                 // If authentication succeeds or the list of available methods does not
236                 // contain the "publickey" method, do not attempt to authenticate with any
237                 // other keys.  According to RFC 4252 Section 7, the latter can occur when
238                 // additional authentication methods are required.
239                 if success || !containsMethod(methods, cb.method()) {
240                         return success, methods, err
241                 }
242         }
243
244         return false, methods, nil
245 }
246
247 func containsMethod(methods []string, method string) bool {
248         for _, m := range methods {
249                 if m == method {
250                         return true
251                 }
252         }
253
254         return false
255 }
256
257 // validateKey validates the key provided is acceptable to the server.
258 func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
259         pubKey := key.Marshal()
260         msg := publickeyAuthMsg{
261                 User:     user,
262                 Service:  serviceSSH,
263                 Method:   "publickey",
264                 HasSig:   false,
265                 Algoname: key.Type(),
266                 PubKey:   pubKey,
267         }
268         if err := c.writePacket(Marshal(&msg)); err != nil {
269                 return false, err
270         }
271
272         return confirmKeyAck(key, c)
273 }
274
275 func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
276         pubKey := key.Marshal()
277         algoname := key.Type()
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 the user
287                 case msgUserAuthPubKeyOk:
288                         var msg userAuthPubKeyOkMsg
289                         if err := Unmarshal(packet, &msg); err != nil {
290                                 return false, err
291                         }
292                         if msg.Algo != algoname || !bytes.Equal(msg.PubKey, 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 }
303
304 // PublicKeys returns an AuthMethod that uses the given key
305 // pairs.
306 func PublicKeys(signers ...Signer) AuthMethod {
307         return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
308 }
309
310 // PublicKeysCallback returns an AuthMethod that runs the given
311 // function to obtain a list of key pairs.
312 func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
313         return publicKeyCallback(getSigners)
314 }
315
316 // handleAuthResponse returns whether the preceding authentication request succeeded
317 // along with a list of remaining authentication methods to try next and
318 // an error if an unexpected response was received.
319 func handleAuthResponse(c packetConn) (bool, []string, error) {
320         for {
321                 packet, err := c.readPacket()
322                 if err != nil {
323                         return false, nil, err
324                 }
325
326                 switch packet[0] {
327                 case msgUserAuthBanner:
328                         // TODO: add callback to present the banner to the user
329                 case msgUserAuthFailure:
330                         var msg userAuthFailureMsg
331                         if err := Unmarshal(packet, &msg); err != nil {
332                                 return false, nil, err
333                         }
334                         return false, msg.Methods, nil
335                 case msgUserAuthSuccess:
336                         return true, nil, nil
337                 default:
338                         return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
339                 }
340         }
341 }
342
343 // KeyboardInteractiveChallenge should print questions, optionally
344 // disabling echoing (e.g. for passwords), and return all the answers.
345 // Challenge may be called multiple times in a single session. After
346 // successful authentication, the server may send a challenge with no
347 // questions, for which the user and instruction messages should be
348 // printed.  RFC 4256 section 3.3 details how the UI should behave for
349 // both CLI and GUI environments.
350 type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
351
352 // KeyboardInteractive returns a AuthMethod using a prompt/response
353 // sequence controlled by the server.
354 func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
355         return challenge
356 }
357
358 func (cb KeyboardInteractiveChallenge) method() string {
359         return "keyboard-interactive"
360 }
361
362 func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
363         type initiateMsg struct {
364                 User       string `sshtype:"50"`
365                 Service    string
366                 Method     string
367                 Language   string
368                 Submethods string
369         }
370
371         if err := c.writePacket(Marshal(&initiateMsg{
372                 User:    user,
373                 Service: serviceSSH,
374                 Method:  "keyboard-interactive",
375         })); err != nil {
376                 return false, nil, err
377         }
378
379         for {
380                 packet, err := c.readPacket()
381                 if err != nil {
382                         return false, nil, err
383                 }
384
385                 // like handleAuthResponse, but with less options.
386                 switch packet[0] {
387                 case msgUserAuthBanner:
388                         // TODO: Print banners during userauth.
389                         continue
390                 case msgUserAuthInfoRequest:
391                         // OK
392                 case msgUserAuthFailure:
393                         var msg userAuthFailureMsg
394                         if err := Unmarshal(packet, &msg); err != nil {
395                                 return false, nil, err
396                         }
397                         return false, msg.Methods, nil
398                 case msgUserAuthSuccess:
399                         return true, nil, nil
400                 default:
401                         return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
402                 }
403
404                 var msg userAuthInfoRequestMsg
405                 if err := Unmarshal(packet, &msg); err != nil {
406                         return false, nil, err
407                 }
408
409                 // Manually unpack the prompt/echo pairs.
410                 rest := msg.Prompts
411                 var prompts []string
412                 var echos []bool
413                 for i := 0; i < int(msg.NumPrompts); i++ {
414                         prompt, r, ok := parseString(rest)
415                         if !ok || len(r) == 0 {
416                                 return false, nil, errors.New("ssh: prompt format error")
417                         }
418                         prompts = append(prompts, string(prompt))
419                         echos = append(echos, r[0] != 0)
420                         rest = r[1:]
421                 }
422
423                 if len(rest) != 0 {
424                         return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
425                 }
426
427                 answers, err := cb(msg.User, msg.Instruction, prompts, echos)
428                 if err != nil {
429                         return false, nil, err
430                 }
431
432                 if len(answers) != len(prompts) {
433                         return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
434                 }
435                 responseLength := 1 + 4
436                 for _, a := range answers {
437                         responseLength += stringLength(len(a))
438                 }
439                 serialized := make([]byte, responseLength)
440                 p := serialized
441                 p[0] = msgUserAuthInfoResponse
442                 p = p[1:]
443                 p = marshalUint32(p, uint32(len(answers)))
444                 for _, a := range answers {
445                         p = marshalString(p, []byte(a))
446                 }
447
448                 if err := c.writePacket(serialized); err != nil {
449                         return false, nil, err
450                 }
451         }
452 }
453
454 type retryableAuthMethod struct {
455         authMethod AuthMethod
456         maxTries   int
457 }
458
459 func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
460         for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
461                 ok, methods, err = r.authMethod.auth(session, user, c, rand)
462                 if ok || err != nil { // either success or error terminate
463                         return ok, methods, err
464                 }
465         }
466         return ok, methods, err
467 }
468
469 func (r *retryableAuthMethod) method() string {
470         return r.authMethod.method()
471 }
472
473 // RetryableAuthMethod is a decorator for other auth methods enabling them to
474 // be retried up to maxTries before considering that AuthMethod itself failed.
475 // If maxTries is <= 0, will retry indefinitely
476 //
477 // This is useful for interactive clients using challenge/response type
478 // authentication (e.g. Keyboard-Interactive, Password, etc) where the user
479 // could mistype their response resulting in the server issuing a
480 // SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
481 // [keyboard-interactive]); Without this decorator, the non-retryable
482 // AuthMethod would be removed from future consideration, and never tried again
483 // (and so the user would never be able to retry their entry).
484 func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
485         return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
486 }