]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/golang.org/x/crypto/ssh/client_auth.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / golang.org / x / crypto / ssh / client_auth.go
CommitLineData
bae9f6d2
JC
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
5package ssh
6
7import (
8 "bytes"
9 "errors"
10 "fmt"
11 "io"
12)
13
14// clientAuthenticate authenticates with the remote server. See RFC 4252.
15func (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
69func 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.
79type 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.
92type noneAuth int
93
94func (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
106func (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.
112type passwordCallback func() (password string, err error)
113
114func (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
144func (cb passwordCallback) method() string {
145 return "password"
146}
147
148// Password returns an AuthMethod using the given password.
149func 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.
155func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
156 return passwordCallback(prompt)
157}
158
159type 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.
175type publicKeyCallback func() ([]Signer, error)
176
177func (cb publicKeyCallback) method() string {
178 return "publickey"
179}
180
181func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
182 // Authentication is performed in two stages. The first stage sends an
183 // enquiry to test if each key is acceptable to the remote. The second
184 // stage attempts to authenticate with the valid keys obtained in the
185 // first stage.
186
187 signers, err := cb()
188 if err != nil {
189 return false, nil, err
190 }
191 var validKeys []Signer
192 for _, signer := range signers {
193 if ok, err := validateKey(signer.PublicKey(), user, c); ok {
194 validKeys = append(validKeys, signer)
195 } else {
196 if err != nil {
197 return false, nil, err
198 }
199 }
200 }
201
202 // methods that may continue if this auth is not successful.
203 var methods []string
204 for _, signer := range validKeys {
205 pub := signer.PublicKey()
206
207 pubKey := pub.Marshal()
208 sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
209 User: user,
210 Service: serviceSSH,
211 Method: cb.method(),
212 }, []byte(pub.Type()), pubKey))
213 if err != nil {
214 return false, nil, err
215 }
216
217 // manually wrap the serialized signature in a string
218 s := Marshal(sign)
219 sig := make([]byte, stringLength(len(s)))
220 marshalString(sig, s)
221 msg := publickeyAuthMsg{
222 User: user,
223 Service: serviceSSH,
224 Method: cb.method(),
225 HasSig: true,
226 Algoname: pub.Type(),
227 PubKey: pubKey,
228 Sig: sig,
229 }
230 p := Marshal(&msg)
231 if err := c.writePacket(p); err != nil {
232 return false, nil, err
233 }
234 var success bool
235 success, methods, err = handleAuthResponse(c)
236 if err != nil {
237 return false, nil, err
238 }
239 if success {
240 return success, methods, err
241 }
242 }
243 return false, methods, nil
244}
245
246// validateKey validates the key provided is acceptable to the server.
247func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
248 pubKey := key.Marshal()
249 msg := publickeyAuthMsg{
250 User: user,
251 Service: serviceSSH,
252 Method: "publickey",
253 HasSig: false,
254 Algoname: key.Type(),
255 PubKey: pubKey,
256 }
257 if err := c.writePacket(Marshal(&msg)); err != nil {
258 return false, err
259 }
260
261 return confirmKeyAck(key, c)
262}
263
264func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
265 pubKey := key.Marshal()
266 algoname := key.Type()
267
268 for {
269 packet, err := c.readPacket()
270 if err != nil {
271 return false, err
272 }
273 switch packet[0] {
274 case msgUserAuthBanner:
275 // TODO(gpaul): add callback to present the banner to the user
276 case msgUserAuthPubKeyOk:
277 var msg userAuthPubKeyOkMsg
278 if err := Unmarshal(packet, &msg); err != nil {
279 return false, err
280 }
281 if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
282 return false, nil
283 }
284 return true, nil
285 case msgUserAuthFailure:
286 return false, nil
287 default:
288 return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
289 }
290 }
291}
292
293// PublicKeys returns an AuthMethod that uses the given key
294// pairs.
295func PublicKeys(signers ...Signer) AuthMethod {
296 return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
297}
298
299// PublicKeysCallback returns an AuthMethod that runs the given
300// function to obtain a list of key pairs.
301func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
302 return publicKeyCallback(getSigners)
303}
304
305// handleAuthResponse returns whether the preceding authentication request succeeded
306// along with a list of remaining authentication methods to try next and
307// an error if an unexpected response was received.
308func handleAuthResponse(c packetConn) (bool, []string, error) {
309 for {
310 packet, err := c.readPacket()
311 if err != nil {
312 return false, nil, err
313 }
314
315 switch packet[0] {
316 case msgUserAuthBanner:
317 // TODO: add callback to present the banner to the user
318 case msgUserAuthFailure:
319 var msg userAuthFailureMsg
320 if err := Unmarshal(packet, &msg); err != nil {
321 return false, nil, err
322 }
323 return false, msg.Methods, nil
324 case msgUserAuthSuccess:
325 return true, nil, nil
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.
339type 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.
343func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
344 return challenge
345}
346
347func (cb KeyboardInteractiveChallenge) method() string {
348 return "keyboard-interactive"
349}
350
351func (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}
442
443type retryableAuthMethod struct {
444 authMethod AuthMethod
445 maxTries int
446}
447
448func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
449 for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
450 ok, methods, err = r.authMethod.auth(session, user, c, rand)
451 if ok || err != nil { // either success or error terminate
452 return ok, methods, err
453 }
454 }
455 return ok, methods, err
456}
457
458func (r *retryableAuthMethod) method() string {
459 return r.authMethod.method()
460}
461
462// RetryableAuthMethod is a decorator for other auth methods enabling them to
463// be retried up to maxTries before considering that AuthMethod itself failed.
464// If maxTries is <= 0, will retry indefinitely
465//
466// This is useful for interactive clients using challenge/response type
467// authentication (e.g. Keyboard-Interactive, Password, etc) where the user
468// could mistype their response resulting in the server issuing a
469// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
470// [keyboard-interactive]); Without this decorator, the non-retryable
471// AuthMethod would be removed from future consideration, and never tried again
472// (and so the user would never be able to retry their entry).
473func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
474 return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
475}