client_auth.go raw

   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  	"slices"
  13  	"strings"
  14  )
  15  
  16  type authResult int
  17  
  18  const (
  19  	authFailure authResult = iota
  20  	authPartialSuccess
  21  	authSuccess
  22  )
  23  
  24  // clientAuthenticate authenticates with the remote server. See RFC 4252.
  25  func (c *connection) clientAuthenticate(config *ClientConfig) error {
  26  	// initiate user auth session
  27  	if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
  28  		return err
  29  	}
  30  	packet, err := c.transport.readPacket()
  31  	if err != nil {
  32  		return err
  33  	}
  34  	// The server may choose to send a SSH_MSG_EXT_INFO at this point (if we
  35  	// advertised willingness to receive one, which we always do) or not. See
  36  	// RFC 8308, Section 2.4.
  37  	extensions := make(map[string][]byte)
  38  	if len(packet) > 0 && packet[0] == msgExtInfo {
  39  		var extInfo extInfoMsg
  40  		if err := Unmarshal(packet, &extInfo); err != nil {
  41  			return err
  42  		}
  43  		payload := extInfo.Payload
  44  		for i := uint32(0); i < extInfo.NumExtensions; i++ {
  45  			name, rest, ok := parseString(payload)
  46  			if !ok {
  47  				return parseError(msgExtInfo)
  48  			}
  49  			value, rest, ok := parseString(rest)
  50  			if !ok {
  51  				return parseError(msgExtInfo)
  52  			}
  53  			extensions[string(name)] = value
  54  			payload = rest
  55  		}
  56  		packet, err = c.transport.readPacket()
  57  		if err != nil {
  58  			return err
  59  		}
  60  	}
  61  	var serviceAccept serviceAcceptMsg
  62  	if err := Unmarshal(packet, &serviceAccept); err != nil {
  63  		return err
  64  	}
  65  
  66  	// during the authentication phase the client first attempts the "none" method
  67  	// then any untried methods suggested by the server.
  68  	var tried []string
  69  	var lastMethods []string
  70  
  71  	sessionID := c.transport.getSessionID()
  72  	for auth := AuthMethod(new(noneAuth)); auth != nil; {
  73  		ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
  74  		if err != nil {
  75  			// On disconnect, return error immediately
  76  			if _, ok := err.(*disconnectMsg); ok {
  77  				return err
  78  			}
  79  			// We return the error later if there is no other method left to
  80  			// try.
  81  			ok = authFailure
  82  		}
  83  		if ok == authSuccess {
  84  			// success
  85  			return nil
  86  		} else if ok == authFailure {
  87  			if m := auth.method(); !slices.Contains(tried, m) {
  88  				tried = append(tried, m)
  89  			}
  90  		}
  91  		if methods == nil {
  92  			methods = lastMethods
  93  		}
  94  		lastMethods = methods
  95  
  96  		auth = nil
  97  
  98  	findNext:
  99  		for _, a := range config.Auth {
 100  			candidateMethod := a.method()
 101  			if slices.Contains(tried, candidateMethod) {
 102  				continue
 103  			}
 104  			for _, meth := range methods {
 105  				if meth == candidateMethod {
 106  					auth = a
 107  					break findNext
 108  				}
 109  			}
 110  		}
 111  
 112  		if auth == nil && err != nil {
 113  			// We have an error and there are no other authentication methods to
 114  			// try, so we return it.
 115  			return err
 116  		}
 117  	}
 118  	return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
 119  }
 120  
 121  // An AuthMethod represents an instance of an RFC 4252 authentication method.
 122  type AuthMethod interface {
 123  	// auth authenticates user over transport t.
 124  	// Returns true if authentication is successful.
 125  	// If authentication is not successful, a []string of alternative
 126  	// method names is returned. If the slice is nil, it will be ignored
 127  	// and the previous set of possible methods will be reused.
 128  	auth(session []byte, user string, p packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error)
 129  
 130  	// method returns the RFC 4252 method name.
 131  	method() string
 132  }
 133  
 134  // "none" authentication, RFC 4252 section 5.2.
 135  type noneAuth int
 136  
 137  func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
 138  	if err := c.writePacket(Marshal(&userAuthRequestMsg{
 139  		User:    user,
 140  		Service: serviceSSH,
 141  		Method:  "none",
 142  	})); err != nil {
 143  		return authFailure, nil, err
 144  	}
 145  
 146  	return handleAuthResponse(c)
 147  }
 148  
 149  func (n *noneAuth) method() string {
 150  	return "none"
 151  }
 152  
 153  // passwordCallback is an AuthMethod that fetches the password through
 154  // a function call, e.g. by prompting the user.
 155  type passwordCallback func() (password string, err error)
 156  
 157  func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
 158  	type passwordAuthMsg struct {
 159  		User     string `sshtype:"50"`
 160  		Service  string
 161  		Method   string
 162  		Reply    bool
 163  		Password string
 164  	}
 165  
 166  	pw, err := cb()
 167  	// REVIEW NOTE: is there a need to support skipping a password attempt?
 168  	// The program may only find out that the user doesn't have a password
 169  	// when prompting.
 170  	if err != nil {
 171  		return authFailure, nil, err
 172  	}
 173  
 174  	if err := c.writePacket(Marshal(&passwordAuthMsg{
 175  		User:     user,
 176  		Service:  serviceSSH,
 177  		Method:   cb.method(),
 178  		Reply:    false,
 179  		Password: pw,
 180  	})); err != nil {
 181  		return authFailure, nil, err
 182  	}
 183  
 184  	return handleAuthResponse(c)
 185  }
 186  
 187  func (cb passwordCallback) method() string {
 188  	return "password"
 189  }
 190  
 191  // Password returns an AuthMethod using the given password.
 192  func Password(secret string) AuthMethod {
 193  	return passwordCallback(func() (string, error) { return secret, nil })
 194  }
 195  
 196  // PasswordCallback returns an AuthMethod that uses a callback for
 197  // fetching a password.
 198  func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
 199  	return passwordCallback(prompt)
 200  }
 201  
 202  type publickeyAuthMsg struct {
 203  	User    string `sshtype:"50"`
 204  	Service string
 205  	Method  string
 206  	// HasSig indicates to the receiver packet that the auth request is signed and
 207  	// should be used for authentication of the request.
 208  	HasSig   bool
 209  	Algoname string
 210  	PubKey   []byte
 211  	// Sig is tagged with "rest" so Marshal will exclude it during
 212  	// validateKey
 213  	Sig []byte `ssh:"rest"`
 214  }
 215  
 216  // publicKeyCallback is an AuthMethod that uses a set of key
 217  // pairs for authentication.
 218  type publicKeyCallback func() ([]Signer, error)
 219  
 220  func (cb publicKeyCallback) method() string {
 221  	return "publickey"
 222  }
 223  
 224  func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiAlgorithmSigner, string, error) {
 225  	var as MultiAlgorithmSigner
 226  	keyFormat := signer.PublicKey().Type()
 227  
 228  	// If the signer implements MultiAlgorithmSigner we use the algorithms it
 229  	// support, if it implements AlgorithmSigner we assume it supports all
 230  	// algorithms, otherwise only the key format one.
 231  	switch s := signer.(type) {
 232  	case MultiAlgorithmSigner:
 233  		as = s
 234  	case AlgorithmSigner:
 235  		as = &multiAlgorithmSigner{
 236  			AlgorithmSigner:     s,
 237  			supportedAlgorithms: algorithmsForKeyFormat(underlyingAlgo(keyFormat)),
 238  		}
 239  	default:
 240  		as = &multiAlgorithmSigner{
 241  			AlgorithmSigner:     algorithmSignerWrapper{signer},
 242  			supportedAlgorithms: []string{underlyingAlgo(keyFormat)},
 243  		}
 244  	}
 245  
 246  	getFallbackAlgo := func() (string, error) {
 247  		// Fallback to use if there is no "server-sig-algs" extension or a
 248  		// common algorithm cannot be found. We use the public key format if the
 249  		// MultiAlgorithmSigner supports it, otherwise we return an error.
 250  		if !slices.Contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
 251  			return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v",
 252  				underlyingAlgo(keyFormat), keyFormat, as.Algorithms())
 253  		}
 254  		return keyFormat, nil
 255  	}
 256  
 257  	extPayload, ok := extensions["server-sig-algs"]
 258  	if !ok {
 259  		// If there is no "server-sig-algs" extension use the fallback
 260  		// algorithm.
 261  		algo, err := getFallbackAlgo()
 262  		return as, algo, err
 263  	}
 264  
 265  	// The server-sig-algs extension only carries underlying signature
 266  	// algorithm, but we are trying to select a protocol-level public key
 267  	// algorithm, which might be a certificate type. Extend the list of server
 268  	// supported algorithms to include the corresponding certificate algorithms.
 269  	serverAlgos := strings.Split(string(extPayload), ",")
 270  	for _, algo := range serverAlgos {
 271  		if certAlgo, ok := certificateAlgo(algo); ok {
 272  			serverAlgos = append(serverAlgos, certAlgo)
 273  		}
 274  	}
 275  
 276  	// Filter algorithms based on those supported by MultiAlgorithmSigner.
 277  	var keyAlgos []string
 278  	for _, algo := range algorithmsForKeyFormat(keyFormat) {
 279  		if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
 280  			keyAlgos = append(keyAlgos, algo)
 281  		}
 282  	}
 283  
 284  	algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos, true)
 285  	if err != nil {
 286  		// If there is no overlap, return the fallback algorithm to support
 287  		// servers that fail to list all supported algorithms.
 288  		algo, err := getFallbackAlgo()
 289  		return as, algo, err
 290  	}
 291  	return as, algo, nil
 292  }
 293  
 294  func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
 295  	// Authentication is performed by sending an enquiry to test if a key is
 296  	// acceptable to the remote. If the key is acceptable, the client will
 297  	// attempt to authenticate with the valid key.  If not the client will repeat
 298  	// the process with the remaining keys.
 299  
 300  	signers, err := cb()
 301  	if err != nil {
 302  		return authFailure, nil, err
 303  	}
 304  	var methods []string
 305  	var errSigAlgo error
 306  
 307  	origSignersLen := len(signers)
 308  	for idx := 0; idx < len(signers); idx++ {
 309  		signer := signers[idx]
 310  		pub := signer.PublicKey()
 311  		as, algo, err := pickSignatureAlgorithm(signer, extensions)
 312  		if err != nil && errSigAlgo == nil {
 313  			// If we cannot negotiate a signature algorithm store the first
 314  			// error so we can return it to provide a more meaningful message if
 315  			// no other signers work.
 316  			errSigAlgo = err
 317  			continue
 318  		}
 319  		ok, err := validateKey(pub, algo, user, c)
 320  		if err != nil {
 321  			return authFailure, nil, err
 322  		}
 323  		// OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512
 324  		// in the "server-sig-algs" extension but doesn't support these
 325  		// algorithms for certificate authentication, so if the server rejects
 326  		// the key try to use the obtained algorithm as if "server-sig-algs" had
 327  		// not been implemented if supported from the algorithm signer.
 328  		if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
 329  			if slices.Contains(as.Algorithms(), KeyAlgoRSA) {
 330  				// We retry using the compat algorithm after all signers have
 331  				// been tried normally.
 332  				signers = append(signers, &multiAlgorithmSigner{
 333  					AlgorithmSigner:     as,
 334  					supportedAlgorithms: []string{KeyAlgoRSA},
 335  				})
 336  			}
 337  		}
 338  		if !ok {
 339  			continue
 340  		}
 341  
 342  		pubKey := pub.Marshal()
 343  		data := buildDataSignedForAuth(session, userAuthRequestMsg{
 344  			User:    user,
 345  			Service: serviceSSH,
 346  			Method:  cb.method(),
 347  		}, algo, pubKey)
 348  		sign, err := as.SignWithAlgorithm(rand, data, underlyingAlgo(algo))
 349  		if err != nil {
 350  			return authFailure, nil, err
 351  		}
 352  
 353  		// manually wrap the serialized signature in a string
 354  		s := Marshal(sign)
 355  		sig := make([]byte, stringLength(len(s)))
 356  		marshalString(sig, s)
 357  		msg := publickeyAuthMsg{
 358  			User:     user,
 359  			Service:  serviceSSH,
 360  			Method:   cb.method(),
 361  			HasSig:   true,
 362  			Algoname: algo,
 363  			PubKey:   pubKey,
 364  			Sig:      sig,
 365  		}
 366  		p := Marshal(&msg)
 367  		if err := c.writePacket(p); err != nil {
 368  			return authFailure, nil, err
 369  		}
 370  		var success authResult
 371  		success, methods, err = handleAuthResponse(c)
 372  		if err != nil {
 373  			return authFailure, nil, err
 374  		}
 375  
 376  		// If authentication succeeds or the list of available methods does not
 377  		// contain the "publickey" method, do not attempt to authenticate with any
 378  		// other keys.  According to RFC 4252 Section 7, the latter can occur when
 379  		// additional authentication methods are required.
 380  		if success == authSuccess || !slices.Contains(methods, cb.method()) {
 381  			return success, methods, err
 382  		}
 383  	}
 384  
 385  	return authFailure, methods, errSigAlgo
 386  }
 387  
 388  // validateKey validates the key provided is acceptable to the server.
 389  func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, error) {
 390  	pubKey := key.Marshal()
 391  	msg := publickeyAuthMsg{
 392  		User:     user,
 393  		Service:  serviceSSH,
 394  		Method:   "publickey",
 395  		HasSig:   false,
 396  		Algoname: algo,
 397  		PubKey:   pubKey,
 398  	}
 399  	if err := c.writePacket(Marshal(&msg)); err != nil {
 400  		return false, err
 401  	}
 402  
 403  	return confirmKeyAck(key, c)
 404  }
 405  
 406  func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
 407  	pubKey := key.Marshal()
 408  
 409  	for {
 410  		packet, err := c.readPacket()
 411  		if err != nil {
 412  			return false, err
 413  		}
 414  		switch packet[0] {
 415  		case msgUserAuthBanner:
 416  			if err := handleBannerResponse(c, packet); err != nil {
 417  				return false, err
 418  			}
 419  		case msgUserAuthPubKeyOk:
 420  			var msg userAuthPubKeyOkMsg
 421  			if err := Unmarshal(packet, &msg); err != nil {
 422  				return false, err
 423  			}
 424  			// According to RFC 4252 Section 7 the algorithm in
 425  			// SSH_MSG_USERAUTH_PK_OK should match that of the request but some
 426  			// servers send the key type instead. OpenSSH allows any algorithm
 427  			// that matches the public key, so we do the same.
 428  			// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
 429  			if !slices.Contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
 430  				return false, nil
 431  			}
 432  			if !bytes.Equal(msg.PubKey, pubKey) {
 433  				return false, nil
 434  			}
 435  			return true, nil
 436  		case msgUserAuthFailure:
 437  			return false, nil
 438  		default:
 439  			return false, unexpectedMessageError(msgUserAuthPubKeyOk, packet[0])
 440  		}
 441  	}
 442  }
 443  
 444  // PublicKeys returns an AuthMethod that uses the given key
 445  // pairs.
 446  func PublicKeys(signers ...Signer) AuthMethod {
 447  	return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
 448  }
 449  
 450  // PublicKeysCallback returns an AuthMethod that runs the given
 451  // function to obtain a list of key pairs.
 452  func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
 453  	return publicKeyCallback(getSigners)
 454  }
 455  
 456  // handleAuthResponse returns whether the preceding authentication request succeeded
 457  // along with a list of remaining authentication methods to try next and
 458  // an error if an unexpected response was received.
 459  func handleAuthResponse(c packetConn) (authResult, []string, error) {
 460  	gotMsgExtInfo := false
 461  	for {
 462  		packet, err := c.readPacket()
 463  		if err != nil {
 464  			return authFailure, nil, err
 465  		}
 466  
 467  		switch packet[0] {
 468  		case msgUserAuthBanner:
 469  			if err := handleBannerResponse(c, packet); err != nil {
 470  				return authFailure, nil, err
 471  			}
 472  		case msgExtInfo:
 473  			// Ignore post-authentication RFC 8308 extensions, once.
 474  			if gotMsgExtInfo {
 475  				return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
 476  			}
 477  			gotMsgExtInfo = true
 478  		case msgUserAuthFailure:
 479  			var msg userAuthFailureMsg
 480  			if err := Unmarshal(packet, &msg); err != nil {
 481  				return authFailure, nil, err
 482  			}
 483  			if msg.PartialSuccess {
 484  				return authPartialSuccess, msg.Methods, nil
 485  			}
 486  			return authFailure, msg.Methods, nil
 487  		case msgUserAuthSuccess:
 488  			return authSuccess, nil, nil
 489  		default:
 490  			return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
 491  		}
 492  	}
 493  }
 494  
 495  func handleBannerResponse(c packetConn, packet []byte) error {
 496  	var msg userAuthBannerMsg
 497  	if err := Unmarshal(packet, &msg); err != nil {
 498  		return err
 499  	}
 500  
 501  	transport, ok := c.(*handshakeTransport)
 502  	if !ok {
 503  		return nil
 504  	}
 505  
 506  	if transport.bannerCallback != nil {
 507  		return transport.bannerCallback(msg.Message)
 508  	}
 509  
 510  	return nil
 511  }
 512  
 513  // KeyboardInteractiveChallenge should print questions, optionally
 514  // disabling echoing (e.g. for passwords), and return all the answers.
 515  // Challenge may be called multiple times in a single session. After
 516  // successful authentication, the server may send a challenge with no
 517  // questions, for which the name and instruction messages should be
 518  // printed.  RFC 4256 section 3.3 details how the UI should behave for
 519  // both CLI and GUI environments.
 520  type KeyboardInteractiveChallenge func(name, instruction string, questions []string, echos []bool) (answers []string, err error)
 521  
 522  // KeyboardInteractive returns an AuthMethod using a prompt/response
 523  // sequence controlled by the server.
 524  func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
 525  	return challenge
 526  }
 527  
 528  func (cb KeyboardInteractiveChallenge) method() string {
 529  	return "keyboard-interactive"
 530  }
 531  
 532  func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
 533  	type initiateMsg struct {
 534  		User       string `sshtype:"50"`
 535  		Service    string
 536  		Method     string
 537  		Language   string
 538  		Submethods string
 539  	}
 540  
 541  	if err := c.writePacket(Marshal(&initiateMsg{
 542  		User:    user,
 543  		Service: serviceSSH,
 544  		Method:  "keyboard-interactive",
 545  	})); err != nil {
 546  		return authFailure, nil, err
 547  	}
 548  
 549  	gotMsgExtInfo := false
 550  	gotUserAuthInfoRequest := false
 551  	for {
 552  		packet, err := c.readPacket()
 553  		if err != nil {
 554  			return authFailure, nil, err
 555  		}
 556  
 557  		// like handleAuthResponse, but with less options.
 558  		switch packet[0] {
 559  		case msgUserAuthBanner:
 560  			if err := handleBannerResponse(c, packet); err != nil {
 561  				return authFailure, nil, err
 562  			}
 563  			continue
 564  		case msgExtInfo:
 565  			// Ignore post-authentication RFC 8308 extensions, once.
 566  			if gotMsgExtInfo {
 567  				return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
 568  			}
 569  			gotMsgExtInfo = true
 570  			continue
 571  		case msgUserAuthInfoRequest:
 572  			// OK
 573  		case msgUserAuthFailure:
 574  			var msg userAuthFailureMsg
 575  			if err := Unmarshal(packet, &msg); err != nil {
 576  				return authFailure, nil, err
 577  			}
 578  			if msg.PartialSuccess {
 579  				return authPartialSuccess, msg.Methods, nil
 580  			}
 581  			if !gotUserAuthInfoRequest {
 582  				return authFailure, msg.Methods, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
 583  			}
 584  			return authFailure, msg.Methods, nil
 585  		case msgUserAuthSuccess:
 586  			return authSuccess, nil, nil
 587  		default:
 588  			return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
 589  		}
 590  
 591  		var msg userAuthInfoRequestMsg
 592  		if err := Unmarshal(packet, &msg); err != nil {
 593  			return authFailure, nil, err
 594  		}
 595  		gotUserAuthInfoRequest = true
 596  
 597  		// Manually unpack the prompt/echo pairs.
 598  		rest := msg.Prompts
 599  		var prompts []string
 600  		var echos []bool
 601  		for i := 0; i < int(msg.NumPrompts); i++ {
 602  			prompt, r, ok := parseString(rest)
 603  			if !ok || len(r) == 0 {
 604  				return authFailure, nil, errors.New("ssh: prompt format error")
 605  			}
 606  			prompts = append(prompts, string(prompt))
 607  			echos = append(echos, r[0] != 0)
 608  			rest = r[1:]
 609  		}
 610  
 611  		if len(rest) != 0 {
 612  			return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
 613  		}
 614  
 615  		answers, err := cb(msg.Name, msg.Instruction, prompts, echos)
 616  		if err != nil {
 617  			return authFailure, nil, err
 618  		}
 619  
 620  		if len(answers) != len(prompts) {
 621  			return authFailure, nil, fmt.Errorf("ssh: incorrect number of answers from keyboard-interactive callback %d (expected %d)", len(answers), len(prompts))
 622  		}
 623  		responseLength := 1 + 4
 624  		for _, a := range answers {
 625  			responseLength += stringLength(len(a))
 626  		}
 627  		serialized := make([]byte, responseLength)
 628  		p := serialized
 629  		p[0] = msgUserAuthInfoResponse
 630  		p = p[1:]
 631  		p = marshalUint32(p, uint32(len(answers)))
 632  		for _, a := range answers {
 633  			p = marshalString(p, []byte(a))
 634  		}
 635  
 636  		if err := c.writePacket(serialized); err != nil {
 637  			return authFailure, nil, err
 638  		}
 639  	}
 640  }
 641  
 642  type retryableAuthMethod struct {
 643  	authMethod AuthMethod
 644  	maxTries   int
 645  }
 646  
 647  func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (ok authResult, methods []string, err error) {
 648  	for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
 649  		ok, methods, err = r.authMethod.auth(session, user, c, rand, extensions)
 650  		if ok != authFailure || err != nil { // either success, partial success or error terminate
 651  			return ok, methods, err
 652  		}
 653  	}
 654  	return ok, methods, err
 655  }
 656  
 657  func (r *retryableAuthMethod) method() string {
 658  	return r.authMethod.method()
 659  }
 660  
 661  // RetryableAuthMethod is a decorator for other auth methods enabling them to
 662  // be retried up to maxTries before considering that AuthMethod itself failed.
 663  // If maxTries is <= 0, will retry indefinitely
 664  //
 665  // This is useful for interactive clients using challenge/response type
 666  // authentication (e.g. Keyboard-Interactive, Password, etc) where the user
 667  // could mistype their response resulting in the server issuing a
 668  // SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
 669  // [keyboard-interactive]); Without this decorator, the non-retryable
 670  // AuthMethod would be removed from future consideration, and never tried again
 671  // (and so the user would never be able to retry their entry).
 672  func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
 673  	return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
 674  }
 675  
 676  // GSSAPIWithMICAuthMethod is an AuthMethod with "gssapi-with-mic" authentication.
 677  // See RFC 4462 section 3
 678  // gssAPIClient is implementation of the GSSAPIClient interface, see the definition of the interface for details.
 679  // target is the server host you want to log in to.
 680  func GSSAPIWithMICAuthMethod(gssAPIClient GSSAPIClient, target string) AuthMethod {
 681  	if gssAPIClient == nil {
 682  		panic("gss-api client must be not nil with enable gssapi-with-mic")
 683  	}
 684  	return &gssAPIWithMICCallback{gssAPIClient: gssAPIClient, target: target}
 685  }
 686  
 687  type gssAPIWithMICCallback struct {
 688  	gssAPIClient GSSAPIClient
 689  	target       string
 690  }
 691  
 692  func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) {
 693  	m := &userAuthRequestMsg{
 694  		User:    user,
 695  		Service: serviceSSH,
 696  		Method:  g.method(),
 697  	}
 698  	// The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST.
 699  	// See RFC 4462 section 3.2.
 700  	m.Payload = appendU32(m.Payload, 1)
 701  	m.Payload = appendString(m.Payload, string(krb5OID))
 702  	if err := c.writePacket(Marshal(m)); err != nil {
 703  		return authFailure, nil, err
 704  	}
 705  	// The server responds to the SSH_MSG_USERAUTH_REQUEST with either an
 706  	// SSH_MSG_USERAUTH_FAILURE if none of the mechanisms are supported or
 707  	// with an SSH_MSG_USERAUTH_GSSAPI_RESPONSE.
 708  	// See RFC 4462 section 3.3.
 709  	// OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,so I don't want to check
 710  	// selected mech if it is valid.
 711  	packet, err := c.readPacket()
 712  	if err != nil {
 713  		return authFailure, nil, err
 714  	}
 715  	userAuthGSSAPIResp := &userAuthGSSAPIResponse{}
 716  	if err := Unmarshal(packet, userAuthGSSAPIResp); err != nil {
 717  		return authFailure, nil, err
 718  	}
 719  	// Start the loop into the exchange token.
 720  	// See RFC 4462 section 3.4.
 721  	var token []byte
 722  	defer g.gssAPIClient.DeleteSecContext()
 723  	for {
 724  		// Initiates the establishment of a security context between the application and a remote peer.
 725  		nextToken, needContinue, err := g.gssAPIClient.InitSecContext("host@"+g.target, token, false)
 726  		if err != nil {
 727  			return authFailure, nil, err
 728  		}
 729  		if len(nextToken) > 0 {
 730  			if err := c.writePacket(Marshal(&userAuthGSSAPIToken{
 731  				Token: nextToken,
 732  			})); err != nil {
 733  				return authFailure, nil, err
 734  			}
 735  		}
 736  		if !needContinue {
 737  			break
 738  		}
 739  		packet, err = c.readPacket()
 740  		if err != nil {
 741  			return authFailure, nil, err
 742  		}
 743  		switch packet[0] {
 744  		case msgUserAuthFailure:
 745  			var msg userAuthFailureMsg
 746  			if err := Unmarshal(packet, &msg); err != nil {
 747  				return authFailure, nil, err
 748  			}
 749  			if msg.PartialSuccess {
 750  				return authPartialSuccess, msg.Methods, nil
 751  			}
 752  			return authFailure, msg.Methods, nil
 753  		case msgUserAuthGSSAPIError:
 754  			userAuthGSSAPIErrorResp := &userAuthGSSAPIError{}
 755  			if err := Unmarshal(packet, userAuthGSSAPIErrorResp); err != nil {
 756  				return authFailure, nil, err
 757  			}
 758  			return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+
 759  				"Major Status: %d\n"+
 760  				"Minor Status: %d\n"+
 761  				"Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
 762  				userAuthGSSAPIErrorResp.Message)
 763  		case msgUserAuthGSSAPIToken:
 764  			userAuthGSSAPITokenReq := &userAuthGSSAPIToken{}
 765  			if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil {
 766  				return authFailure, nil, err
 767  			}
 768  			token = userAuthGSSAPITokenReq.Token
 769  		}
 770  	}
 771  	// Binding Encryption Keys.
 772  	// See RFC 4462 section 3.5.
 773  	micField := buildMIC(string(session), user, "ssh-connection", "gssapi-with-mic")
 774  	micToken, err := g.gssAPIClient.GetMIC(micField)
 775  	if err != nil {
 776  		return authFailure, nil, err
 777  	}
 778  	if err := c.writePacket(Marshal(&userAuthGSSAPIMIC{
 779  		MIC: micToken,
 780  	})); err != nil {
 781  		return authFailure, nil, err
 782  	}
 783  	return handleAuthResponse(c)
 784  }
 785  
 786  func (g *gssAPIWithMICCallback) method() string {
 787  	return "gssapi-with-mic"
 788  }
 789