github.com/maenmax/kairep@v0.0.0-20210218001208-55bf3df36788/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 default: 325 return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) 326 } 327 } 328 } 329 330 // KeyboardInteractiveChallenge should print questions, optionally 331 // disabling echoing (e.g. for passwords), and return all the answers. 332 // Challenge may be called multiple times in a single session. After 333 // successful authentication, the server may send a challenge with no 334 // questions, for which the user and instruction messages should be 335 // printed. RFC 4256 section 3.3 details how the UI should behave for 336 // both CLI and GUI environments. 337 type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) 338 339 // KeyboardInteractive returns a AuthMethod using a prompt/response 340 // sequence controlled by the server. 341 func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { 342 return challenge 343 } 344 345 func (cb KeyboardInteractiveChallenge) method() string { 346 return "keyboard-interactive" 347 } 348 349 func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { 350 type initiateMsg struct { 351 User string `sshtype:"50"` 352 Service string 353 Method string 354 Language string 355 Submethods string 356 } 357 358 if err := c.writePacket(Marshal(&initiateMsg{ 359 User: user, 360 Service: serviceSSH, 361 Method: "keyboard-interactive", 362 })); err != nil { 363 return false, nil, err 364 } 365 366 for { 367 packet, err := c.readPacket() 368 if err != nil { 369 return false, nil, err 370 } 371 372 // like handleAuthResponse, but with less options. 373 switch packet[0] { 374 case msgUserAuthBanner: 375 // TODO: Print banners during userauth. 376 continue 377 case msgUserAuthInfoRequest: 378 // OK 379 case msgUserAuthFailure: 380 var msg userAuthFailureMsg 381 if err := Unmarshal(packet, &msg); err != nil { 382 return false, nil, err 383 } 384 return false, msg.Methods, nil 385 case msgUserAuthSuccess: 386 return true, nil, nil 387 default: 388 return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) 389 } 390 391 var msg userAuthInfoRequestMsg 392 if err := Unmarshal(packet, &msg); err != nil { 393 return false, nil, err 394 } 395 396 // Manually unpack the prompt/echo pairs. 397 rest := msg.Prompts 398 var prompts []string 399 var echos []bool 400 for i := 0; i < int(msg.NumPrompts); i++ { 401 prompt, r, ok := parseString(rest) 402 if !ok || len(r) == 0 { 403 return false, nil, errors.New("ssh: prompt format error") 404 } 405 prompts = append(prompts, string(prompt)) 406 echos = append(echos, r[0] != 0) 407 rest = r[1:] 408 } 409 410 if len(rest) != 0 { 411 return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs") 412 } 413 414 answers, err := cb(msg.User, msg.Instruction, prompts, echos) 415 if err != nil { 416 return false, nil, err 417 } 418 419 if len(answers) != len(prompts) { 420 return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") 421 } 422 responseLength := 1 + 4 423 for _, a := range answers { 424 responseLength += stringLength(len(a)) 425 } 426 serialized := make([]byte, responseLength) 427 p := serialized 428 p[0] = msgUserAuthInfoResponse 429 p = p[1:] 430 p = marshalUint32(p, uint32(len(answers))) 431 for _, a := range answers { 432 p = marshalString(p, []byte(a)) 433 } 434 435 if err := c.writePacket(serialized); err != nil { 436 return false, nil, err 437 } 438 } 439 } 440 441 type retryableAuthMethod struct { 442 authMethod AuthMethod 443 maxTries int 444 } 445 446 func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) { 447 for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ { 448 ok, methods, err = r.authMethod.auth(session, user, c, rand) 449 if ok || err != nil { // either success or error terminate 450 return ok, methods, err 451 } 452 } 453 return ok, methods, err 454 } 455 456 func (r *retryableAuthMethod) method() string { 457 return r.authMethod.method() 458 } 459 460 // RetryableAuthMethod is a decorator for other auth methods enabling them to 461 // be retried up to maxTries before considering that AuthMethod itself failed. 462 // If maxTries is <= 0, will retry indefinitely 463 // 464 // This is useful for interactive clients using challenge/response type 465 // authentication (e.g. Keyboard-Interactive, Password, etc) where the user 466 // could mistype their response resulting in the server issuing a 467 // SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4 468 // [keyboard-interactive]); Without this decorator, the non-retryable 469 // AuthMethod would be removed from future consideration, and never tried again 470 // (and so the user would never be able to retry their entry). 471 func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod { 472 return &retryableAuthMethod{authMethod: auth, maxTries: maxTries} 473 }