github.com/deis/deis@v1.13.5-0.20170519182049-1d9e59fbdbfc/Godeps/_workspace/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 case msgDisconnect: 325 return false, nil, io.EOF 326 default: 327 return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) 328 } 329 } 330 } 331 332 // KeyboardInteractiveChallenge should print questions, optionally 333 // disabling echoing (e.g. for passwords), and return all the answers. 334 // Challenge may be called multiple times in a single session. After 335 // successful authentication, the server may send a challenge with no 336 // questions, for which the user and instruction messages should be 337 // printed. RFC 4256 section 3.3 details how the UI should behave for 338 // both CLI and GUI environments. 339 type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) 340 341 // KeyboardInteractive returns a AuthMethod using a prompt/response 342 // sequence controlled by the server. 343 func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { 344 return challenge 345 } 346 347 func (cb KeyboardInteractiveChallenge) method() string { 348 return "keyboard-interactive" 349 } 350 351 func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { 352 type initiateMsg struct { 353 User string `sshtype:"50"` 354 Service string 355 Method string 356 Language string 357 Submethods string 358 } 359 360 if err := c.writePacket(Marshal(&initiateMsg{ 361 User: user, 362 Service: serviceSSH, 363 Method: "keyboard-interactive", 364 })); err != nil { 365 return false, nil, err 366 } 367 368 for { 369 packet, err := c.readPacket() 370 if err != nil { 371 return false, nil, err 372 } 373 374 // like handleAuthResponse, but with less options. 375 switch packet[0] { 376 case msgUserAuthBanner: 377 // TODO: Print banners during userauth. 378 continue 379 case msgUserAuthInfoRequest: 380 // OK 381 case msgUserAuthFailure: 382 var msg userAuthFailureMsg 383 if err := Unmarshal(packet, &msg); err != nil { 384 return false, nil, err 385 } 386 return false, msg.Methods, nil 387 case msgUserAuthSuccess: 388 return true, nil, nil 389 default: 390 return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) 391 } 392 393 var msg userAuthInfoRequestMsg 394 if err := Unmarshal(packet, &msg); err != nil { 395 return false, nil, err 396 } 397 398 // Manually unpack the prompt/echo pairs. 399 rest := msg.Prompts 400 var prompts []string 401 var echos []bool 402 for i := 0; i < int(msg.NumPrompts); i++ { 403 prompt, r, ok := parseString(rest) 404 if !ok || len(r) == 0 { 405 return false, nil, errors.New("ssh: prompt format error") 406 } 407 prompts = append(prompts, string(prompt)) 408 echos = append(echos, r[0] != 0) 409 rest = r[1:] 410 } 411 412 if len(rest) != 0 { 413 return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs") 414 } 415 416 answers, err := cb(msg.User, msg.Instruction, prompts, echos) 417 if err != nil { 418 return false, nil, err 419 } 420 421 if len(answers) != len(prompts) { 422 return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") 423 } 424 responseLength := 1 + 4 425 for _, a := range answers { 426 responseLength += stringLength(len(a)) 427 } 428 serialized := make([]byte, responseLength) 429 p := serialized 430 p[0] = msgUserAuthInfoResponse 431 p = p[1:] 432 p = marshalUint32(p, uint32(len(answers))) 433 for _, a := range answers { 434 p = marshalString(p, []byte(a)) 435 } 436 437 if err := c.writePacket(serialized); err != nil { 438 return false, nil, err 439 } 440 } 441 }