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