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