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.
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 {
20 packet, err := c.transport.readPacket()
24 var serviceAccept serviceAcceptMsg
25 if err := Unmarshal(packet, &serviceAccept); err != nil {
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
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)
44 tried[auth.method()] = true
53 for _, a := range config.Auth {
54 candidateMethod := a.method()
55 if tried[candidateMethod] {
58 for _, meth := range methods {
59 if meth == candidateMethod {
66 return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
69 func keys(m map[string]bool) []string {
70 s := make([]string, 0, len(m))
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)
87 // method returns the RFC 4252 method name.
91 // "none" authentication, RFC 4252 section 5.2.
94 func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
95 if err := c.writePacket(Marshal(&userAuthRequestMsg{
100 return false, nil, err
103 return handleAuthResponse(c)
106 func (n *noneAuth) method() string {
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)
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"`
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
128 return false, nil, err
131 if err := c.writePacket(Marshal(&passwordAuthMsg{
138 return false, nil, err
141 return handleAuthResponse(c)
144 func (cb passwordCallback) method() string {
148 // Password returns an AuthMethod using the given password.
149 func Password(secret string) AuthMethod {
150 return passwordCallback(func() (string, error) { return secret, nil })
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)
159 type publickeyAuthMsg struct {
160 User string `sshtype:"50"`
163 // HasSig indicates to the receiver packet that the auth request is signed and
164 // should be used for authentication of the request.
168 // Sig is tagged with "rest" so Marshal will exclude it during
170 Sig []byte `ssh:"rest"`
173 // publicKeyCallback is an AuthMethod that uses a set of key
174 // pairs for authentication.
175 type publicKeyCallback func() ([]Signer, error)
177 func (cb publicKeyCallback) method() string {
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.
189 return false, nil, err
192 for _, signer := range signers {
193 ok, err := validateKey(signer.PublicKey(), user, c)
195 return false, nil, err
201 pub := signer.PublicKey()
202 pubKey := pub.Marshal()
203 sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
207 }, []byte(pub.Type()), pubKey))
209 return false, nil, err
212 // manually wrap the serialized signature in a string
214 sig := make([]byte, stringLength(len(s)))
215 marshalString(sig, s)
216 msg := publickeyAuthMsg{
221 Algoname: pub.Type(),
226 if err := c.writePacket(p); err != nil {
227 return false, nil, err
230 success, methods, err = handleAuthResponse(c)
232 return false, nil, err
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
244 return false, methods, nil
247 func containsMethod(methods []string, method string) bool {
248 for _, m := range methods {
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{
265 Algoname: key.Type(),
268 if err := c.writePacket(Marshal(&msg)); err != nil {
272 return confirmKeyAck(key, c)
275 func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
276 pubKey := key.Marshal()
277 algoname := key.Type()
280 packet, err := c.readPacket()
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 {
292 if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
296 case msgUserAuthFailure:
299 return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
304 // PublicKeys returns an AuthMethod that uses the given key
306 func PublicKeys(signers ...Signer) AuthMethod {
307 return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
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)
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) {
321 packet, err := c.readPacket()
323 return false, nil, err
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
334 return false, msg.Methods, nil
335 case msgUserAuthSuccess:
336 return true, nil, nil
338 return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
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)
352 // KeyboardInteractive returns a AuthMethod using a prompt/response
353 // sequence controlled by the server.
354 func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
358 func (cb KeyboardInteractiveChallenge) method() string {
359 return "keyboard-interactive"
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"`
371 if err := c.writePacket(Marshal(&initiateMsg{
374 Method: "keyboard-interactive",
376 return false, nil, err
380 packet, err := c.readPacket()
382 return false, nil, err
385 // like handleAuthResponse, but with less options.
387 case msgUserAuthBanner:
388 // TODO: Print banners during userauth.
390 case msgUserAuthInfoRequest:
392 case msgUserAuthFailure:
393 var msg userAuthFailureMsg
394 if err := Unmarshal(packet, &msg); err != nil {
395 return false, nil, err
397 return false, msg.Methods, nil
398 case msgUserAuthSuccess:
399 return true, nil, nil
401 return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
404 var msg userAuthInfoRequestMsg
405 if err := Unmarshal(packet, &msg); err != nil {
406 return false, nil, err
409 // Manually unpack the prompt/echo pairs.
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")
418 prompts = append(prompts, string(prompt))
419 echos = append(echos, r[0] != 0)
424 return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
427 answers, err := cb(msg.User, msg.Instruction, prompts, echos)
429 return false, nil, err
432 if len(answers) != len(prompts) {
433 return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
435 responseLength := 1 + 4
436 for _, a := range answers {
437 responseLength += stringLength(len(a))
439 serialized := make([]byte, responseLength)
441 p[0] = msgUserAuthInfoResponse
443 p = marshalUint32(p, uint32(len(answers)))
444 for _, a := range answers {
445 p = marshalString(p, []byte(a))
448 if err := c.writePacket(serialized); err != nil {
449 return false, nil, err
454 type retryableAuthMethod struct {
455 authMethod AuthMethod
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
466 return ok, methods, err
469 func (r *retryableAuthMethod) method() string {
470 return r.authMethod.method()
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
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}