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