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.
17 authFailure authResult = iota
22 // clientAuthenticate authenticates with the remote server. See RFC 4252.
23 func (c *connection) clientAuthenticate(config *ClientConfig) error {
24 // initiate user auth session
25 if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
28 packet, err := c.transport.readPacket()
32 var serviceAccept serviceAcceptMsg
33 if err := Unmarshal(packet, &serviceAccept); err != nil {
37 // during the authentication phase the client first attempts the "none" method
38 // then any untried methods suggested by the server.
39 tried := make(map[string]bool)
40 var lastMethods []string
42 sessionID := c.transport.getSessionID()
43 for auth := AuthMethod(new(noneAuth)); auth != nil; {
44 ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
48 if ok == authSuccess {
51 } else if ok == authFailure {
52 tried[auth.method()] = true
62 for _, a := range config.Auth {
63 candidateMethod := a.method()
64 if tried[candidateMethod] {
67 for _, meth := range methods {
68 if meth == candidateMethod {
75 return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
78 func keys(m map[string]bool) []string {
79 s := make([]string, 0, len(m))
87 // An AuthMethod represents an instance of an RFC 4252 authentication method.
88 type AuthMethod interface {
89 // auth authenticates user over transport t.
90 // Returns true if authentication is successful.
91 // If authentication is not successful, a []string of alternative
92 // method names is returned. If the slice is nil, it will be ignored
93 // and the previous set of possible methods will be reused.
94 auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
96 // method returns the RFC 4252 method name.
100 // "none" authentication, RFC 4252 section 5.2.
103 func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
104 if err := c.writePacket(Marshal(&userAuthRequestMsg{
109 return authFailure, nil, err
112 return handleAuthResponse(c)
115 func (n *noneAuth) method() string {
119 // passwordCallback is an AuthMethod that fetches the password through
120 // a function call, e.g. by prompting the user.
121 type passwordCallback func() (password string, err error)
123 func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
124 type passwordAuthMsg struct {
125 User string `sshtype:"50"`
133 // REVIEW NOTE: is there a need to support skipping a password attempt?
134 // The program may only find out that the user doesn't have a password
137 return authFailure, nil, err
140 if err := c.writePacket(Marshal(&passwordAuthMsg{
147 return authFailure, nil, err
150 return handleAuthResponse(c)
153 func (cb passwordCallback) method() string {
157 // Password returns an AuthMethod using the given password.
158 func Password(secret string) AuthMethod {
159 return passwordCallback(func() (string, error) { return secret, nil })
162 // PasswordCallback returns an AuthMethod that uses a callback for
163 // fetching a password.
164 func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
165 return passwordCallback(prompt)
168 type publickeyAuthMsg struct {
169 User string `sshtype:"50"`
172 // HasSig indicates to the receiver packet that the auth request is signed and
173 // should be used for authentication of the request.
177 // Sig is tagged with "rest" so Marshal will exclude it during
179 Sig []byte `ssh:"rest"`
182 // publicKeyCallback is an AuthMethod that uses a set of key
183 // pairs for authentication.
184 type publicKeyCallback func() ([]Signer, error)
186 func (cb publicKeyCallback) method() string {
190 func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
191 // Authentication is performed by sending an enquiry to test if a key is
192 // acceptable to the remote. If the key is acceptable, the client will
193 // attempt to authenticate with the valid key. If not the client will repeat
194 // the process with the remaining keys.
198 return authFailure, nil, err
201 for _, signer := range signers {
202 ok, err := validateKey(signer.PublicKey(), user, c)
204 return authFailure, nil, err
210 pub := signer.PublicKey()
211 pubKey := pub.Marshal()
212 sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
216 }, []byte(pub.Type()), pubKey))
218 return authFailure, nil, err
221 // manually wrap the serialized signature in a string
223 sig := make([]byte, stringLength(len(s)))
224 marshalString(sig, s)
225 msg := publickeyAuthMsg{
230 Algoname: pub.Type(),
235 if err := c.writePacket(p); err != nil {
236 return authFailure, nil, err
238 var success authResult
239 success, methods, err = handleAuthResponse(c)
241 return authFailure, nil, err
244 // If authentication succeeds or the list of available methods does not
245 // contain the "publickey" method, do not attempt to authenticate with any
246 // other keys. According to RFC 4252 Section 7, the latter can occur when
247 // additional authentication methods are required.
248 if success == authSuccess || !containsMethod(methods, cb.method()) {
249 return success, methods, err
253 return authFailure, methods, nil
256 func containsMethod(methods []string, method string) bool {
257 for _, m := range methods {
266 // validateKey validates the key provided is acceptable to the server.
267 func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
268 pubKey := key.Marshal()
269 msg := publickeyAuthMsg{
274 Algoname: key.Type(),
277 if err := c.writePacket(Marshal(&msg)); err != nil {
281 return confirmKeyAck(key, c)
284 func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
285 pubKey := key.Marshal()
286 algoname := key.Type()
289 packet, err := c.readPacket()
294 case msgUserAuthBanner:
295 if err := handleBannerResponse(c, packet); err != nil {
298 case msgUserAuthPubKeyOk:
299 var msg userAuthPubKeyOkMsg
300 if err := Unmarshal(packet, &msg); err != nil {
303 if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
307 case msgUserAuthFailure:
310 return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
315 // PublicKeys returns an AuthMethod that uses the given key
317 func PublicKeys(signers ...Signer) AuthMethod {
318 return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
321 // PublicKeysCallback returns an AuthMethod that runs the given
322 // function to obtain a list of key pairs.
323 func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
324 return publicKeyCallback(getSigners)
327 // handleAuthResponse returns whether the preceding authentication request succeeded
328 // along with a list of remaining authentication methods to try next and
329 // an error if an unexpected response was received.
330 func handleAuthResponse(c packetConn) (authResult, []string, error) {
332 packet, err := c.readPacket()
334 return authFailure, nil, err
338 case msgUserAuthBanner:
339 if err := handleBannerResponse(c, packet); err != nil {
340 return authFailure, nil, err
342 case msgUserAuthFailure:
343 var msg userAuthFailureMsg
344 if err := Unmarshal(packet, &msg); err != nil {
345 return authFailure, nil, err
347 if msg.PartialSuccess {
348 return authPartialSuccess, msg.Methods, nil
350 return authFailure, msg.Methods, nil
351 case msgUserAuthSuccess:
352 return authSuccess, nil, nil
354 return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
359 func handleBannerResponse(c packetConn, packet []byte) error {
360 var msg userAuthBannerMsg
361 if err := Unmarshal(packet, &msg); err != nil {
365 transport, ok := c.(*handshakeTransport)
370 if transport.bannerCallback != nil {
371 return transport.bannerCallback(msg.Message)
377 // KeyboardInteractiveChallenge should print questions, optionally
378 // disabling echoing (e.g. for passwords), and return all the answers.
379 // Challenge may be called multiple times in a single session. After
380 // successful authentication, the server may send a challenge with no
381 // questions, for which the user and instruction messages should be
382 // printed. RFC 4256 section 3.3 details how the UI should behave for
383 // both CLI and GUI environments.
384 type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
386 // KeyboardInteractive returns an AuthMethod using a prompt/response
387 // sequence controlled by the server.
388 func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
392 func (cb KeyboardInteractiveChallenge) method() string {
393 return "keyboard-interactive"
396 func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
397 type initiateMsg struct {
398 User string `sshtype:"50"`
405 if err := c.writePacket(Marshal(&initiateMsg{
408 Method: "keyboard-interactive",
410 return authFailure, nil, err
414 packet, err := c.readPacket()
416 return authFailure, nil, err
419 // like handleAuthResponse, but with less options.
421 case msgUserAuthBanner:
422 if err := handleBannerResponse(c, packet); err != nil {
423 return authFailure, nil, err
426 case msgUserAuthInfoRequest:
428 case msgUserAuthFailure:
429 var msg userAuthFailureMsg
430 if err := Unmarshal(packet, &msg); err != nil {
431 return authFailure, nil, err
433 if msg.PartialSuccess {
434 return authPartialSuccess, msg.Methods, nil
436 return authFailure, msg.Methods, nil
437 case msgUserAuthSuccess:
438 return authSuccess, nil, nil
440 return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
443 var msg userAuthInfoRequestMsg
444 if err := Unmarshal(packet, &msg); err != nil {
445 return authFailure, nil, err
448 // Manually unpack the prompt/echo pairs.
452 for i := 0; i < int(msg.NumPrompts); i++ {
453 prompt, r, ok := parseString(rest)
454 if !ok || len(r) == 0 {
455 return authFailure, nil, errors.New("ssh: prompt format error")
457 prompts = append(prompts, string(prompt))
458 echos = append(echos, r[0] != 0)
463 return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
466 answers, err := cb(msg.User, msg.Instruction, prompts, echos)
468 return authFailure, nil, err
471 if len(answers) != len(prompts) {
472 return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
474 responseLength := 1 + 4
475 for _, a := range answers {
476 responseLength += stringLength(len(a))
478 serialized := make([]byte, responseLength)
480 p[0] = msgUserAuthInfoResponse
482 p = marshalUint32(p, uint32(len(answers)))
483 for _, a := range answers {
484 p = marshalString(p, []byte(a))
487 if err := c.writePacket(serialized); err != nil {
488 return authFailure, nil, err
493 type retryableAuthMethod struct {
494 authMethod AuthMethod
498 func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
499 for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
500 ok, methods, err = r.authMethod.auth(session, user, c, rand)
501 if ok != authFailure || err != nil { // either success, partial success or error terminate
502 return ok, methods, err
505 return ok, methods, err
508 func (r *retryableAuthMethod) method() string {
509 return r.authMethod.method()
512 // RetryableAuthMethod is a decorator for other auth methods enabling them to
513 // be retried up to maxTries before considering that AuthMethod itself failed.
514 // If maxTries is <= 0, will retry indefinitely
516 // This is useful for interactive clients using challenge/response type
517 // authentication (e.g. Keyboard-Interactive, Password, etc) where the user
518 // could mistype their response resulting in the server issuing a
519 // SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
520 // [keyboard-interactive]); Without this decorator, the non-retryable
521 // AuthMethod would be removed from future consideration, and never tried again
522 // (and so the user would never be able to retry their entry).
523 func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
524 return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}