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