github.com/deis/deis@v1.13.5-0.20170519182049-1d9e59fbdbfc/Godeps/_workspace/src/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  	for auth := AuthMethod(new(noneAuth)); auth != nil; {
    34  		ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand)
    35  		if err != nil {
    36  			return err
    37  		}
    38  		if ok {
    39  			// success
    40  			return nil
    41  		}
    42  		tried[auth.method()] = true
    43  		if methods == nil {
    44  			methods = lastMethods
    45  		}
    46  		lastMethods = methods
    47  
    48  		auth = nil
    49  
    50  	findNext:
    51  		for _, a := range config.Auth {
    52  			candidateMethod := a.method()
    53  			if tried[candidateMethod] {
    54  				continue
    55  			}
    56  			for _, meth := range methods {
    57  				if meth == candidateMethod {
    58  					auth = a
    59  					break findNext
    60  				}
    61  			}
    62  		}
    63  	}
    64  	return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
    65  }
    66  
    67  func keys(m map[string]bool) []string {
    68  	s := make([]string, 0, len(m))
    69  
    70  	for key := range m {
    71  		s = append(s, key)
    72  	}
    73  	return s
    74  }
    75  
    76  // An AuthMethod represents an instance of an RFC 4252 authentication method.
    77  type AuthMethod interface {
    78  	// auth authenticates user over transport t.
    79  	// Returns true if authentication is successful.
    80  	// If authentication is not successful, a []string of alternative
    81  	// method names is returned. If the slice is nil, it will be ignored
    82  	// and the previous set of possible methods will be reused.
    83  	auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
    84  
    85  	// method returns the RFC 4252 method name.
    86  	method() string
    87  }
    88  
    89  // "none" authentication, RFC 4252 section 5.2.
    90  type noneAuth int
    91  
    92  func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
    93  	if err := c.writePacket(Marshal(&userAuthRequestMsg{
    94  		User:    user,
    95  		Service: serviceSSH,
    96  		Method:  "none",
    97  	})); err != nil {
    98  		return false, nil, err
    99  	}
   100  
   101  	return handleAuthResponse(c)
   102  }
   103  
   104  func (n *noneAuth) method() string {
   105  	return "none"
   106  }
   107  
   108  // passwordCallback is an AuthMethod that fetches the password through
   109  // a function call, e.g. by prompting the user.
   110  type passwordCallback func() (password string, err error)
   111  
   112  func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
   113  	type passwordAuthMsg struct {
   114  		User     string `sshtype:"50"`
   115  		Service  string
   116  		Method   string
   117  		Reply    bool
   118  		Password string
   119  	}
   120  
   121  	pw, err := cb()
   122  	// REVIEW NOTE: is there a need to support skipping a password attempt?
   123  	// The program may only find out that the user doesn't have a password
   124  	// when prompting.
   125  	if err != nil {
   126  		return false, nil, err
   127  	}
   128  
   129  	if err := c.writePacket(Marshal(&passwordAuthMsg{
   130  		User:     user,
   131  		Service:  serviceSSH,
   132  		Method:   cb.method(),
   133  		Reply:    false,
   134  		Password: pw,
   135  	})); err != nil {
   136  		return false, nil, err
   137  	}
   138  
   139  	return handleAuthResponse(c)
   140  }
   141  
   142  func (cb passwordCallback) method() string {
   143  	return "password"
   144  }
   145  
   146  // Password returns an AuthMethod using the given password.
   147  func Password(secret string) AuthMethod {
   148  	return passwordCallback(func() (string, error) { return secret, nil })
   149  }
   150  
   151  // PasswordCallback returns an AuthMethod that uses a callback for
   152  // fetching a password.
   153  func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
   154  	return passwordCallback(prompt)
   155  }
   156  
   157  type publickeyAuthMsg struct {
   158  	User    string `sshtype:"50"`
   159  	Service string
   160  	Method  string
   161  	// HasSig indicates to the receiver packet that the auth request is signed and
   162  	// should be used for authentication of the request.
   163  	HasSig   bool
   164  	Algoname string
   165  	PubKey   []byte
   166  	// Sig is tagged with "rest" so Marshal will exclude it during
   167  	// validateKey
   168  	Sig []byte `ssh:"rest"`
   169  }
   170  
   171  // publicKeyCallback is an AuthMethod that uses a set of key
   172  // pairs for authentication.
   173  type publicKeyCallback func() ([]Signer, error)
   174  
   175  func (cb publicKeyCallback) method() string {
   176  	return "publickey"
   177  }
   178  
   179  func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
   180  	// Authentication is performed in two stages. The first stage sends an
   181  	// enquiry to test if each key is acceptable to the remote. The second
   182  	// stage attempts to authenticate with the valid keys obtained in the
   183  	// first stage.
   184  
   185  	signers, err := cb()
   186  	if err != nil {
   187  		return false, nil, err
   188  	}
   189  	var validKeys []Signer
   190  	for _, signer := range signers {
   191  		if ok, err := validateKey(signer.PublicKey(), user, c); ok {
   192  			validKeys = append(validKeys, signer)
   193  		} else {
   194  			if err != nil {
   195  				return false, nil, err
   196  			}
   197  		}
   198  	}
   199  
   200  	// methods that may continue if this auth is not successful.
   201  	var methods []string
   202  	for _, signer := range validKeys {
   203  		pub := signer.PublicKey()
   204  
   205  		pubKey := pub.Marshal()
   206  		sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
   207  			User:    user,
   208  			Service: serviceSSH,
   209  			Method:  cb.method(),
   210  		}, []byte(pub.Type()), pubKey))
   211  		if err != nil {
   212  			return false, nil, err
   213  		}
   214  
   215  		// manually wrap the serialized signature in a string
   216  		s := Marshal(sign)
   217  		sig := make([]byte, stringLength(len(s)))
   218  		marshalString(sig, s)
   219  		msg := publickeyAuthMsg{
   220  			User:     user,
   221  			Service:  serviceSSH,
   222  			Method:   cb.method(),
   223  			HasSig:   true,
   224  			Algoname: pub.Type(),
   225  			PubKey:   pubKey,
   226  			Sig:      sig,
   227  		}
   228  		p := Marshal(&msg)
   229  		if err := c.writePacket(p); err != nil {
   230  			return false, nil, err
   231  		}
   232  		var success bool
   233  		success, methods, err = handleAuthResponse(c)
   234  		if err != nil {
   235  			return false, nil, err
   236  		}
   237  		if success {
   238  			return success, methods, err
   239  		}
   240  	}
   241  	return false, methods, nil
   242  }
   243  
   244  // validateKey validates the key provided is acceptable to the server.
   245  func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
   246  	pubKey := key.Marshal()
   247  	msg := publickeyAuthMsg{
   248  		User:     user,
   249  		Service:  serviceSSH,
   250  		Method:   "publickey",
   251  		HasSig:   false,
   252  		Algoname: key.Type(),
   253  		PubKey:   pubKey,
   254  	}
   255  	if err := c.writePacket(Marshal(&msg)); err != nil {
   256  		return false, err
   257  	}
   258  
   259  	return confirmKeyAck(key, c)
   260  }
   261  
   262  func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
   263  	pubKey := key.Marshal()
   264  	algoname := key.Type()
   265  
   266  	for {
   267  		packet, err := c.readPacket()
   268  		if err != nil {
   269  			return false, err
   270  		}
   271  		switch packet[0] {
   272  		case msgUserAuthBanner:
   273  			// TODO(gpaul): add callback to present the banner to the user
   274  		case msgUserAuthPubKeyOk:
   275  			var msg userAuthPubKeyOkMsg
   276  			if err := Unmarshal(packet, &msg); err != nil {
   277  				return false, err
   278  			}
   279  			if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
   280  				return false, nil
   281  			}
   282  			return true, nil
   283  		case msgUserAuthFailure:
   284  			return false, nil
   285  		default:
   286  			return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
   287  		}
   288  	}
   289  }
   290  
   291  // PublicKeys returns an AuthMethod that uses the given key
   292  // pairs.
   293  func PublicKeys(signers ...Signer) AuthMethod {
   294  	return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
   295  }
   296  
   297  // PublicKeysCallback returns an AuthMethod that runs the given
   298  // function to obtain a list of key pairs.
   299  func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
   300  	return publicKeyCallback(getSigners)
   301  }
   302  
   303  // handleAuthResponse returns whether the preceding authentication request succeeded
   304  // along with a list of remaining authentication methods to try next and
   305  // an error if an unexpected response was received.
   306  func handleAuthResponse(c packetConn) (bool, []string, error) {
   307  	for {
   308  		packet, err := c.readPacket()
   309  		if err != nil {
   310  			return false, nil, err
   311  		}
   312  
   313  		switch packet[0] {
   314  		case msgUserAuthBanner:
   315  			// TODO: add callback to present the banner to the user
   316  		case msgUserAuthFailure:
   317  			var msg userAuthFailureMsg
   318  			if err := Unmarshal(packet, &msg); err != nil {
   319  				return false, nil, err
   320  			}
   321  			return false, msg.Methods, nil
   322  		case msgUserAuthSuccess:
   323  			return true, nil, nil
   324  		case msgDisconnect:
   325  			return false, nil, io.EOF
   326  		default:
   327  			return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
   328  		}
   329  	}
   330  }
   331  
   332  // KeyboardInteractiveChallenge should print questions, optionally
   333  // disabling echoing (e.g. for passwords), and return all the answers.
   334  // Challenge may be called multiple times in a single session. After
   335  // successful authentication, the server may send a challenge with no
   336  // questions, for which the user and instruction messages should be
   337  // printed.  RFC 4256 section 3.3 details how the UI should behave for
   338  // both CLI and GUI environments.
   339  type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
   340  
   341  // KeyboardInteractive returns a AuthMethod using a prompt/response
   342  // sequence controlled by the server.
   343  func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
   344  	return challenge
   345  }
   346  
   347  func (cb KeyboardInteractiveChallenge) method() string {
   348  	return "keyboard-interactive"
   349  }
   350  
   351  func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
   352  	type initiateMsg struct {
   353  		User       string `sshtype:"50"`
   354  		Service    string
   355  		Method     string
   356  		Language   string
   357  		Submethods string
   358  	}
   359  
   360  	if err := c.writePacket(Marshal(&initiateMsg{
   361  		User:    user,
   362  		Service: serviceSSH,
   363  		Method:  "keyboard-interactive",
   364  	})); err != nil {
   365  		return false, nil, err
   366  	}
   367  
   368  	for {
   369  		packet, err := c.readPacket()
   370  		if err != nil {
   371  			return false, nil, err
   372  		}
   373  
   374  		// like handleAuthResponse, but with less options.
   375  		switch packet[0] {
   376  		case msgUserAuthBanner:
   377  			// TODO: Print banners during userauth.
   378  			continue
   379  		case msgUserAuthInfoRequest:
   380  			// OK
   381  		case msgUserAuthFailure:
   382  			var msg userAuthFailureMsg
   383  			if err := Unmarshal(packet, &msg); err != nil {
   384  				return false, nil, err
   385  			}
   386  			return false, msg.Methods, nil
   387  		case msgUserAuthSuccess:
   388  			return true, nil, nil
   389  		default:
   390  			return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
   391  		}
   392  
   393  		var msg userAuthInfoRequestMsg
   394  		if err := Unmarshal(packet, &msg); err != nil {
   395  			return false, nil, err
   396  		}
   397  
   398  		// Manually unpack the prompt/echo pairs.
   399  		rest := msg.Prompts
   400  		var prompts []string
   401  		var echos []bool
   402  		for i := 0; i < int(msg.NumPrompts); i++ {
   403  			prompt, r, ok := parseString(rest)
   404  			if !ok || len(r) == 0 {
   405  				return false, nil, errors.New("ssh: prompt format error")
   406  			}
   407  			prompts = append(prompts, string(prompt))
   408  			echos = append(echos, r[0] != 0)
   409  			rest = r[1:]
   410  		}
   411  
   412  		if len(rest) != 0 {
   413  			return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
   414  		}
   415  
   416  		answers, err := cb(msg.User, msg.Instruction, prompts, echos)
   417  		if err != nil {
   418  			return false, nil, err
   419  		}
   420  
   421  		if len(answers) != len(prompts) {
   422  			return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
   423  		}
   424  		responseLength := 1 + 4
   425  		for _, a := range answers {
   426  			responseLength += stringLength(len(a))
   427  		}
   428  		serialized := make([]byte, responseLength)
   429  		p := serialized
   430  		p[0] = msgUserAuthInfoResponse
   431  		p = p[1:]
   432  		p = marshalUint32(p, uint32(len(answers)))
   433  		for _, a := range answers {
   434  			p = marshalString(p, []byte(a))
   435  		}
   436  
   437  		if err := c.writePacket(serialized); err != nil {
   438  			return false, nil, err
   439  		}
   440  	}
   441  }