~ubuntu-branches/ubuntu/utopic/hockeypuck/utopic-proposed

« back to all changes in this revision

Viewing changes to build/src/code.google.com/p/go.crypto/ssh/client_auth.go

  • Committer: Package Import Robot
  • Author(s): Casey Marshall
  • Date: 2014-04-13 20:06:01 UTC
  • Revision ID: package-import@ubuntu.com-20140413200601-oxdlqn1gy0x8m55u
Tags: 1.0~rel20140413+7a1892a~trusty
Hockeypuck 1.0 release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "errors"
 
9
        "fmt"
 
10
        "io"
 
11
        "net"
 
12
)
 
13
 
 
14
// authenticate authenticates with the remote server. See RFC 4252.
 
15
func (c *ClientConn) authenticate() error {
 
16
        // initiate user auth session
 
17
        if err := c.transport.writePacket(marshal(msgServiceRequest, 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(&serviceAccept, packet, msgServiceAccept); err != nil {
 
26
                return err
 
27
        }
 
28
        // during the authentication phase the client first attempts the "none" method
 
29
        // then any untried methods suggested by the server.
 
30
        tried, remain := make(map[string]bool), make(map[string]bool)
 
31
        for auth := ClientAuth(new(noneAuth)); auth != nil; {
 
32
                ok, methods, err := auth.auth(c.transport.sessionID, c.config.User, c.transport, c.config.rand())
 
33
                if err != nil {
 
34
                        return err
 
35
                }
 
36
                if ok {
 
37
                        // success
 
38
                        return nil
 
39
                }
 
40
                tried[auth.method()] = true
 
41
                delete(remain, auth.method())
 
42
                for _, meth := range methods {
 
43
                        if tried[meth] {
 
44
                                // if we've tried meth already, skip it.
 
45
                                continue
 
46
                        }
 
47
                        remain[meth] = true
 
48
                }
 
49
                auth = nil
 
50
                for _, a := range c.config.Auth {
 
51
                        if remain[a.method()] {
 
52
                                auth = a
 
53
                                break
 
54
                        }
 
55
                }
 
56
        }
 
57
        return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
 
58
}
 
59
 
 
60
func keys(m map[string]bool) (s []string) {
 
61
        for k := range m {
 
62
                s = append(s, k)
 
63
        }
 
64
        return
 
65
}
 
66
 
 
67
// HostKeyChecker represents a database of known server host keys.
 
68
type HostKeyChecker interface {
 
69
        // Check is called during the handshake to check server's
 
70
        // public key for unexpected changes. The hostKey argument is
 
71
        // in SSH wire format. It can be parsed using
 
72
        // ssh.ParsePublicKey. The address before DNS resolution is
 
73
        // passed in the addr argument, so the key can also be checked
 
74
        // against the hostname.
 
75
        Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error
 
76
}
 
77
 
 
78
// A ClientAuth represents an instance of an RFC 4252 authentication method.
 
79
type ClientAuth 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.
 
84
        auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
 
85
 
 
86
        // method returns the RFC 4252 method name.
 
87
        method() string
 
88
}
 
89
 
 
90
// "none" authentication, RFC 4252 section 5.2.
 
91
type noneAuth int
 
92
 
 
93
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
 
94
        if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
 
95
                User:    user,
 
96
                Service: serviceSSH,
 
97
                Method:  "none",
 
98
        })); err != nil {
 
99
                return false, nil, err
 
100
        }
 
101
 
 
102
        return handleAuthResponse(c)
 
103
}
 
104
 
 
105
func (n *noneAuth) method() string {
 
106
        return "none"
 
107
}
 
108
 
 
109
// "password" authentication, RFC 4252 Section 8.
 
110
type passwordAuth struct {
 
111
        ClientPassword
 
112
}
 
113
 
 
114
func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
 
115
        type passwordAuthMsg struct {
 
116
                User     string
 
117
                Service  string
 
118
                Method   string
 
119
                Reply    bool
 
120
                Password string
 
121
        }
 
122
 
 
123
        pw, err := p.Password(user)
 
124
        if err != nil {
 
125
                return false, nil, err
 
126
        }
 
127
 
 
128
        if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
 
129
                User:     user,
 
130
                Service:  serviceSSH,
 
131
                Method:   "password",
 
132
                Reply:    false,
 
133
                Password: pw,
 
134
        })); err != nil {
 
135
                return false, nil, err
 
136
        }
 
137
 
 
138
        return handleAuthResponse(c)
 
139
}
 
140
 
 
141
func (p *passwordAuth) method() string {
 
142
        return "password"
 
143
}
 
144
 
 
145
// A ClientPassword implements access to a client's passwords.
 
146
type ClientPassword interface {
 
147
        // Password returns the password to use for user.
 
148
        Password(user string) (password string, err error)
 
149
}
 
150
 
 
151
// ClientAuthPassword returns a ClientAuth using password authentication.
 
152
func ClientAuthPassword(impl ClientPassword) ClientAuth {
 
153
        return &passwordAuth{impl}
 
154
}
 
155
 
 
156
// ClientKeyring implements access to a client key ring.
 
157
type ClientKeyring interface {
 
158
        // Key returns the i'th Publickey, or nil if no key exists at i.
 
159
        Key(i int) (key PublicKey, err error)
 
160
 
 
161
        // Sign returns a signature of the given data using the i'th key
 
162
        // and the supplied random source.
 
163
        Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
 
164
}
 
165
 
 
166
// "publickey" authentication, RFC 4252 Section 7.
 
167
type publickeyAuth struct {
 
168
        ClientKeyring
 
169
}
 
170
 
 
171
type publickeyAuthMsg struct {
 
172
        User    string
 
173
        Service string
 
174
        Method  string
 
175
        // HasSig indicates to the receiver packet that the auth request is signed and
 
176
        // should be used for authentication of the request.
 
177
        HasSig   bool
 
178
        Algoname string
 
179
        Pubkey   string
 
180
        // Sig is defined as []byte so marshal will exclude it during validateKey
 
181
        Sig []byte `ssh:"rest"`
 
182
}
 
183
 
 
184
func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
 
185
        // Authentication is performed in two stages. The first stage sends an
 
186
        // enquiry to test if each key is acceptable to the remote. The second
 
187
        // stage attempts to authenticate with the valid keys obtained in the
 
188
        // first stage.
 
189
 
 
190
        var index int
 
191
        // a map of public keys to their index in the keyring
 
192
        validKeys := make(map[int]PublicKey)
 
193
        for {
 
194
                key, err := p.Key(index)
 
195
                if err != nil {
 
196
                        return false, nil, err
 
197
                }
 
198
                if key == nil {
 
199
                        // no more keys in the keyring
 
200
                        break
 
201
                }
 
202
 
 
203
                if ok, err := p.validateKey(key, user, c); ok {
 
204
                        validKeys[index] = key
 
205
                } else {
 
206
                        if err != nil {
 
207
                                return false, nil, err
 
208
                        }
 
209
                }
 
210
                index++
 
211
        }
 
212
 
 
213
        // methods that may continue if this auth is not successful.
 
214
        var methods []string
 
215
        for i, key := range validKeys {
 
216
                pubkey := MarshalPublicKey(key)
 
217
                algoname := key.PublicKeyAlgo()
 
218
                data := buildDataSignedForAuth(session, userAuthRequestMsg{
 
219
                        User:    user,
 
220
                        Service: serviceSSH,
 
221
                        Method:  p.method(),
 
222
                }, []byte(algoname), pubkey)
 
223
                sigBlob, err := p.Sign(i, rand, data)
 
224
                if err != nil {
 
225
                        return false, nil, err
 
226
                }
 
227
                // manually wrap the serialized signature in a string
 
228
                s := serializeSignature(key.PublicKeyAlgo(), sigBlob)
 
229
                sig := make([]byte, stringLength(len(s)))
 
230
                marshalString(sig, s)
 
231
                msg := publickeyAuthMsg{
 
232
                        User:     user,
 
233
                        Service:  serviceSSH,
 
234
                        Method:   p.method(),
 
235
                        HasSig:   true,
 
236
                        Algoname: algoname,
 
237
                        Pubkey:   string(pubkey),
 
238
                        Sig:      sig,
 
239
                }
 
240
                p := marshal(msgUserAuthRequest, msg)
 
241
                if err := c.writePacket(p); err != nil {
 
242
                        return false, nil, err
 
243
                }
 
244
                success, methods, err := handleAuthResponse(c)
 
245
                if err != nil {
 
246
                        return false, nil, err
 
247
                }
 
248
                if success {
 
249
                        return success, methods, err
 
250
                }
 
251
        }
 
252
        return false, methods, nil
 
253
}
 
254
 
 
255
// validateKey validates the key provided it is acceptable to the server.
 
256
func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (bool, error) {
 
257
        pubkey := MarshalPublicKey(key)
 
258
        algoname := key.PublicKeyAlgo()
 
259
        msg := publickeyAuthMsg{
 
260
                User:     user,
 
261
                Service:  serviceSSH,
 
262
                Method:   p.method(),
 
263
                HasSig:   false,
 
264
                Algoname: algoname,
 
265
                Pubkey:   string(pubkey),
 
266
        }
 
267
        if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
 
268
                return false, err
 
269
        }
 
270
 
 
271
        return p.confirmKeyAck(key, c)
 
272
}
 
273
 
 
274
func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
 
275
        pubkey := MarshalPublicKey(key)
 
276
        algoname := key.PublicKeyAlgo()
 
277
 
 
278
        for {
 
279
                packet, err := c.readPacket()
 
280
                if err != nil {
 
281
                        return false, err
 
282
                }
 
283
                switch packet[0] {
 
284
                case msgUserAuthBanner:
 
285
                        // TODO(gpaul): add callback to present the banner to the user
 
286
                case msgUserAuthPubKeyOk:
 
287
                        msg := userAuthPubKeyOkMsg{}
 
288
                        if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil {
 
289
                                return false, err
 
290
                        }
 
291
                        if msg.Algo != algoname || msg.PubKey != string(pubkey) {
 
292
                                return false, nil
 
293
                        }
 
294
                        return true, nil
 
295
                case msgUserAuthFailure:
 
296
                        return false, nil
 
297
                default:
 
298
                        return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
 
299
                }
 
300
        }
 
301
        panic("unreachable")
 
302
}
 
303
 
 
304
func (p *publickeyAuth) method() string {
 
305
        return "publickey"
 
306
}
 
307
 
 
308
// ClientAuthKeyring returns a ClientAuth using public key authentication.
 
309
func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
 
310
        return &publickeyAuth{impl}
 
311
}
 
312
 
 
313
// handleAuthResponse returns whether the preceding authentication request succeeded
 
314
// along with a list of remaining authentication methods to try next and
 
315
// an error if an unexpected response was received.
 
316
func handleAuthResponse(c packetConn) (bool, []string, error) {
 
317
        for {
 
318
                packet, err := c.readPacket()
 
319
                if err != nil {
 
320
                        return false, nil, err
 
321
                }
 
322
 
 
323
                switch packet[0] {
 
324
                case msgUserAuthBanner:
 
325
                        // TODO: add callback to present the banner to the user
 
326
                case msgUserAuthFailure:
 
327
                        msg := userAuthFailureMsg{}
 
328
                        if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
 
329
                                return false, nil, err
 
330
                        }
 
331
                        return false, msg.Methods, nil
 
332
                case msgUserAuthSuccess:
 
333
                        return true, nil, nil
 
334
                case msgDisconnect:
 
335
                        return false, nil, io.EOF
 
336
                default:
 
337
                        return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
 
338
                }
 
339
        }
 
340
        panic("unreachable")
 
341
}
 
342
 
 
343
// ClientAuthAgent returns a ClientAuth using public key authentication via
 
344
// an agent.
 
345
func ClientAuthAgent(agent *AgentClient) ClientAuth {
 
346
        return ClientAuthKeyring(&agentKeyring{agent: agent})
 
347
}
 
348
 
 
349
// agentKeyring implements ClientKeyring.
 
350
type agentKeyring struct {
 
351
        agent *AgentClient
 
352
        keys  []*AgentKey
 
353
}
 
354
 
 
355
func (kr *agentKeyring) Key(i int) (key PublicKey, err error) {
 
356
        if kr.keys == nil {
 
357
                if kr.keys, err = kr.agent.RequestIdentities(); err != nil {
 
358
                        return
 
359
                }
 
360
        }
 
361
        if i >= len(kr.keys) {
 
362
                return
 
363
        }
 
364
        return kr.keys[i].Key()
 
365
}
 
366
 
 
367
func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
 
368
        var key PublicKey
 
369
        if key, err = kr.Key(i); err != nil {
 
370
                return
 
371
        }
 
372
        if key == nil {
 
373
                return nil, errors.New("ssh: key index out of range")
 
374
        }
 
375
        if sig, err = kr.agent.SignRequest(key, data); err != nil {
 
376
                return
 
377
        }
 
378
 
 
379
        // Unmarshal the signature.
 
380
 
 
381
        var ok bool
 
382
        if _, sig, ok = parseString(sig); !ok {
 
383
                return nil, errors.New("ssh: malformed signature response from agent")
 
384
        }
 
385
        if sig, _, ok = parseString(sig); !ok {
 
386
                return nil, errors.New("ssh: malformed signature response from agent")
 
387
        }
 
388
        return sig, nil
 
389
}
 
390
 
 
391
// ClientKeyboardInteractive should prompt the user for the given
 
392
// questions.
 
393
type ClientKeyboardInteractive interface {
 
394
        // Challenge should print the questions, optionally disabling
 
395
        // echoing (eg. for passwords), and return all the answers.
 
396
        // Challenge may be called multiple times in a single
 
397
        // session. After successful authentication, the server may
 
398
        // send a challenge with no questions, for which the user and
 
399
        // instruction messages should be printed.  RFC 4256 section
 
400
        // 3.3 details how the UI should behave for both CLI and
 
401
        // GUI environments.
 
402
        Challenge(user, instruction string, questions []string, echos []bool) ([]string, error)
 
403
}
 
404
 
 
405
// ClientAuthKeyboardInteractive returns a ClientAuth using a
 
406
// prompt/response sequence controlled by the server.
 
407
func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
 
408
        return &keyboardInteractiveAuth{impl}
 
409
}
 
410
 
 
411
type keyboardInteractiveAuth struct {
 
412
        ClientKeyboardInteractive
 
413
}
 
414
 
 
415
func (k *keyboardInteractiveAuth) method() string {
 
416
        return "keyboard-interactive"
 
417
}
 
418
 
 
419
func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
 
420
        type initiateMsg struct {
 
421
                User       string
 
422
                Service    string
 
423
                Method     string
 
424
                Language   string
 
425
                Submethods string
 
426
        }
 
427
 
 
428
        if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{
 
429
                User:    user,
 
430
                Service: serviceSSH,
 
431
                Method:  "keyboard-interactive",
 
432
        })); err != nil {
 
433
                return false, nil, err
 
434
        }
 
435
 
 
436
        for {
 
437
                packet, err := c.readPacket()
 
438
                if err != nil {
 
439
                        return false, nil, err
 
440
                }
 
441
 
 
442
                // like handleAuthResponse, but with less options.
 
443
                switch packet[0] {
 
444
                case msgUserAuthBanner:
 
445
                        // TODO: Print banners during userauth.
 
446
                        continue
 
447
                case msgUserAuthInfoRequest:
 
448
                        // OK
 
449
                case msgUserAuthFailure:
 
450
                        var msg userAuthFailureMsg
 
451
                        if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
 
452
                                return false, nil, err
 
453
                        }
 
454
                        return false, msg.Methods, nil
 
455
                case msgUserAuthSuccess:
 
456
                        return true, nil, nil
 
457
                default:
 
458
                        return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]}
 
459
                }
 
460
 
 
461
                var msg userAuthInfoRequestMsg
 
462
                if err := unmarshal(&msg, packet, packet[0]); err != nil {
 
463
                        return false, nil, err
 
464
                }
 
465
 
 
466
                // Manually unpack the prompt/echo pairs.
 
467
                rest := msg.Prompts
 
468
                var prompts []string
 
469
                var echos []bool
 
470
                for i := 0; i < int(msg.NumPrompts); i++ {
 
471
                        prompt, r, ok := parseString(rest)
 
472
                        if !ok || len(r) == 0 {
 
473
                                return false, nil, errors.New("ssh: prompt format error")
 
474
                        }
 
475
                        prompts = append(prompts, string(prompt))
 
476
                        echos = append(echos, r[0] != 0)
 
477
                        rest = r[1:]
 
478
                }
 
479
 
 
480
                if len(rest) != 0 {
 
481
                        return false, nil, fmt.Errorf("ssh: junk following message %q", rest)
 
482
                }
 
483
 
 
484
                answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos)
 
485
                if err != nil {
 
486
                        return false, nil, err
 
487
                }
 
488
 
 
489
                if len(answers) != len(prompts) {
 
490
                        return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
 
491
                }
 
492
                responseLength := 1 + 4
 
493
                for _, a := range answers {
 
494
                        responseLength += stringLength(len(a))
 
495
                }
 
496
                serialized := make([]byte, responseLength)
 
497
                p := serialized
 
498
                p[0] = msgUserAuthInfoResponse
 
499
                p = p[1:]
 
500
                p = marshalUint32(p, uint32(len(answers)))
 
501
                for _, a := range answers {
 
502
                        p = marshalString(p, []byte(a))
 
503
                }
 
504
 
 
505
                if err := c.writePacket(serialized); err != nil {
 
506
                        return false, nil, err
 
507
                }
 
508
        }
 
509
}