github.com/devops-filetransfer/sshego@v7.0.4+incompatible/_vendor/golang.org/x/crypto/ssh/client_auth.go (about)

     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  }