client_auth.go 13.5 KB
Newer Older
Russ Cox's avatar
Russ Cox committed
1 2 3 4 5 6 7
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssh

import (
8
	"errors"
9
	"fmt"
Russ Cox's avatar
Russ Cox committed
10
	"io"
11
	"net"
Russ Cox's avatar
Russ Cox committed
12 13 14
)

// authenticate authenticates with the remote server. See RFC 4252.
15
func (c *ClientConn) authenticate() error {
Russ Cox's avatar
Russ Cox committed
16
	// initiate user auth session
17
	if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
Russ Cox's avatar
Russ Cox committed
18 19
		return err
	}
20
	packet, err := c.transport.readPacket()
Russ Cox's avatar
Russ Cox committed
21 22 23 24 25 26 27 28 29 30 31
	if err != nil {
		return err
	}
	var serviceAccept serviceAcceptMsg
	if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
		return err
	}
	// during the authentication phase the client first attempts the "none" method
	// then any untried methods suggested by the server.
	tried, remain := make(map[string]bool), make(map[string]bool)
	for auth := ClientAuth(new(noneAuth)); auth != nil; {
32
		ok, methods, err := auth.auth(c.transport.sessionID, c.config.User, c.transport, c.config.rand())
Russ Cox's avatar
Russ Cox committed
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
		if err != nil {
			return err
		}
		if ok {
			// success
			return nil
		}
		tried[auth.method()] = true
		delete(remain, auth.method())
		for _, meth := range methods {
			if tried[meth] {
				// if we've tried meth already, skip it.
				continue
			}
			remain[meth] = true
		}
		auth = nil
		for _, a := range c.config.Auth {
			if remain[a.method()] {
				auth = a
				break
			}
		}
	}
57 58 59 60
	return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
}

func keys(m map[string]bool) (s []string) {
Mikio Hara's avatar
Mikio Hara committed
61
	for k := range m {
62 63 64
		s = append(s, k)
	}
	return
Russ Cox's avatar
Russ Cox committed
65 66
}

67 68 69 70 71 72 73 74 75 76 77
// HostKeyChecker represents a database of known server host keys.
type HostKeyChecker interface {
	// Check is called during the handshake to check server's
	// public key for unexpected changes. The hostKey argument is
	// in SSH wire format. It can be parsed using
	// ssh.ParsePublicKey. The address before DNS resolution is
	// passed in the addr argument, so the key can also be checked
	// against the hostname.
	Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error
}

Russ Cox's avatar
Russ Cox committed
78 79 80 81 82 83
// A ClientAuth represents an instance of an RFC 4252 authentication method.
type ClientAuth interface {
	// auth authenticates user over transport t.
	// Returns true if authentication is successful.
	// If authentication is not successful, a []string of alternative
	// method names is returned.
84
	auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
Russ Cox's avatar
Russ Cox committed
85 86 87 88 89 90 91 92

	// method returns the RFC 4252 method name.
	method() string
}

// "none" authentication, RFC 4252 section 5.2.
type noneAuth int

93 94
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
	if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
Russ Cox's avatar
Russ Cox committed
95 96 97 98 99 100 101
		User:    user,
		Service: serviceSSH,
		Method:  "none",
	})); err != nil {
		return false, nil, err
	}

102
	return handleAuthResponse(c)
Russ Cox's avatar
Russ Cox committed
103 104 105 106 107 108 109 110 111 112 113
}

func (n *noneAuth) method() string {
	return "none"
}

// "password" authentication, RFC 4252 Section 8.
type passwordAuth struct {
	ClientPassword
}

114
func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
Russ Cox's avatar
Russ Cox committed
115 116 117 118 119 120 121 122 123 124 125 126 127
	type passwordAuthMsg struct {
		User     string
		Service  string
		Method   string
		Reply    bool
		Password string
	}

	pw, err := p.Password(user)
	if err != nil {
		return false, nil, err
	}

128
	if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
Russ Cox's avatar
Russ Cox committed
129 130 131 132 133 134 135 136 137
		User:     user,
		Service:  serviceSSH,
		Method:   "password",
		Reply:    false,
		Password: pw,
	})); err != nil {
		return false, nil, err
	}

138
	return handleAuthResponse(c)
Russ Cox's avatar
Russ Cox committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
}

func (p *passwordAuth) method() string {
	return "password"
}

// A ClientPassword implements access to a client's passwords.
type ClientPassword interface {
	// Password returns the password to use for user.
	Password(user string) (password string, err error)
}

// ClientAuthPassword returns a ClientAuth using password authentication.
func ClientAuthPassword(impl ClientPassword) ClientAuth {
	return &passwordAuth{impl}
}

// ClientKeyring implements access to a client key ring.
type ClientKeyring interface {
158 159
	// Key returns the i'th Publickey, or nil if no key exists at i.
	Key(i int) (key PublicKey, err error)
Russ Cox's avatar
Russ Cox committed
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

	// Sign returns a signature of the given data using the i'th key
	// and the supplied random source.
	Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
}

// "publickey" authentication, RFC 4252 Section 7.
type publickeyAuth struct {
	ClientKeyring
}

type publickeyAuthMsg struct {
	User    string
	Service string
	Method  string
175
	// HasSig indicates to the receiver packet that the auth request is signed and
Russ Cox's avatar
Russ Cox committed
176 177 178 179 180 181 182 183
	// should be used for authentication of the request.
	HasSig   bool
	Algoname string
	Pubkey   string
	// Sig is defined as []byte so marshal will exclude it during validateKey
	Sig []byte `ssh:"rest"`
}

184
func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
Russ Cox's avatar
Russ Cox committed
185 186 187 188 189 190 191
	// Authentication is performed in two stages. The first stage sends an
	// enquiry to test if each key is acceptable to the remote. The second
	// stage attempts to authenticate with the valid keys obtained in the
	// first stage.

	var index int
	// a map of public keys to their index in the keyring
192
	validKeys := make(map[int]PublicKey)
Russ Cox's avatar
Russ Cox committed
193 194 195 196 197 198 199 200 201 202
	for {
		key, err := p.Key(index)
		if err != nil {
			return false, nil, err
		}
		if key == nil {
			// no more keys in the keyring
			break
		}

203
		if ok, err := p.validateKey(key, user, c); ok {
Russ Cox's avatar
Russ Cox committed
204 205 206 207 208 209 210 211 212 213 214 215
			validKeys[index] = key
		} else {
			if err != nil {
				return false, nil, err
			}
		}
		index++
	}

	// methods that may continue if this auth is not successful.
	var methods []string
	for i, key := range validKeys {
216 217
		pubkey := MarshalPublicKey(key)
		algoname := key.PublicKeyAlgo()
218
		data := buildDataSignedForAuth(session, userAuthRequestMsg{
Russ Cox's avatar
Russ Cox committed
219 220 221
			User:    user,
			Service: serviceSSH,
			Method:  p.method(),
222 223
		}, []byte(algoname), pubkey)
		sigBlob, err := p.Sign(i, rand, data)
Russ Cox's avatar
Russ Cox committed
224 225 226 227
		if err != nil {
			return false, nil, err
		}
		// manually wrap the serialized signature in a string
228
		s := serializeSignature(key.PublicKeyAlgo(), sigBlob)
Adam Langley's avatar
Adam Langley committed
229
		sig := make([]byte, stringLength(len(s)))
Russ Cox's avatar
Russ Cox committed
230 231 232 233 234 235 236 237 238 239 240
		marshalString(sig, s)
		msg := publickeyAuthMsg{
			User:     user,
			Service:  serviceSSH,
			Method:   p.method(),
			HasSig:   true,
			Algoname: algoname,
			Pubkey:   string(pubkey),
			Sig:      sig,
		}
		p := marshal(msgUserAuthRequest, msg)
241
		if err := c.writePacket(p); err != nil {
Russ Cox's avatar
Russ Cox committed
242 243
			return false, nil, err
		}
244
		success, methods, err := handleAuthResponse(c)
Russ Cox's avatar
Russ Cox committed
245 246 247 248 249 250 251 252 253 254 255
		if err != nil {
			return false, nil, err
		}
		if success {
			return success, methods, err
		}
	}
	return false, methods, nil
}

// 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 258
	pubkey := MarshalPublicKey(key)
	algoname := key.PublicKeyAlgo()
Russ Cox's avatar
Russ Cox committed
259 260 261 262 263 264 265 266
	msg := publickeyAuthMsg{
		User:     user,
		Service:  serviceSSH,
		Method:   p.method(),
		HasSig:   false,
		Algoname: algoname,
		Pubkey:   string(pubkey),
	}
267
	if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
Russ Cox's avatar
Russ Cox committed
268 269 270
		return false, err
	}

271
	return p.confirmKeyAck(key, c)
Russ Cox's avatar
Russ Cox committed
272 273
}

274
func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
275 276
	pubkey := MarshalPublicKey(key)
	algoname := key.PublicKeyAlgo()
Russ Cox's avatar
Russ Cox committed
277 278

	for {
279
		packet, err := c.readPacket()
Russ Cox's avatar
Russ Cox committed
280 281 282 283 284 285 286
		if err != nil {
			return false, err
		}
		switch packet[0] {
		case msgUserAuthBanner:
			// TODO(gpaul): add callback to present the banner to the user
		case msgUserAuthPubKeyOk:
287 288 289 290
			msg := userAuthPubKeyOkMsg{}
			if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil {
				return false, err
			}
Russ Cox's avatar
Russ Cox committed
291 292 293 294 295 296 297 298 299 300
			if msg.Algo != algoname || msg.PubKey != string(pubkey) {
				return false, nil
			}
			return true, nil
		case msgUserAuthFailure:
			return false, nil
		default:
			return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
		}
	}
301
	panic("unreachable")
Russ Cox's avatar
Russ Cox committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315
}

func (p *publickeyAuth) method() string {
	return "publickey"
}

// ClientAuthKeyring returns a ClientAuth using public key authentication.
func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
	return &publickeyAuth{impl}
}

// handleAuthResponse returns whether the preceding authentication request succeeded
// along with a list of remaining authentication methods to try next and
// an error if an unexpected response was received.
316
func handleAuthResponse(c packetConn) (bool, []string, error) {
Russ Cox's avatar
Russ Cox committed
317
	for {
318
		packet, err := c.readPacket()
Russ Cox's avatar
Russ Cox committed
319 320 321 322 323 324 325 326
		if err != nil {
			return false, nil, err
		}

		switch packet[0] {
		case msgUserAuthBanner:
			// TODO: add callback to present the banner to the user
		case msgUserAuthFailure:
327 328 329 330
			msg := userAuthFailureMsg{}
			if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
				return false, nil, err
			}
Russ Cox's avatar
Russ Cox committed
331 332 333 334 335 336 337 338 339
			return false, msg.Methods, nil
		case msgUserAuthSuccess:
			return true, nil, nil
		case msgDisconnect:
			return false, nil, io.EOF
		default:
			return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
		}
	}
340
	panic("unreachable")
Russ Cox's avatar
Russ Cox committed
341
}
342

343
// ClientAuthAgent returns a ClientAuth using public key authentication via
344 345 346 347 348 349 350 351 352 353 354
// an agent.
func ClientAuthAgent(agent *AgentClient) ClientAuth {
	return ClientAuthKeyring(&agentKeyring{agent: agent})
}

// agentKeyring implements ClientKeyring.
type agentKeyring struct {
	agent *AgentClient
	keys  []*AgentKey
}

355
func (kr *agentKeyring) Key(i int) (key PublicKey, err error) {
356 357 358 359 360 361 362 363 364 365 366 367
	if kr.keys == nil {
		if kr.keys, err = kr.agent.RequestIdentities(); err != nil {
			return
		}
	}
	if i >= len(kr.keys) {
		return
	}
	return kr.keys[i].Key()
}

func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
368
	var key PublicKey
369 370 371 372 373 374 375 376 377 378
	if key, err = kr.Key(i); err != nil {
		return
	}
	if key == nil {
		return nil, errors.New("ssh: key index out of range")
	}
	if sig, err = kr.agent.SignRequest(key, data); err != nil {
		return
	}

Adam Langley's avatar
Adam Langley committed
379
	// Unmarshal the signature.
380 381 382 383 384 385 386 387 388 389

	var ok bool
	if _, sig, ok = parseString(sig); !ok {
		return nil, errors.New("ssh: malformed signature response from agent")
	}
	if sig, _, ok = parseString(sig); !ok {
		return nil, errors.New("ssh: malformed signature response from agent")
	}
	return sig, nil
}
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414

// ClientKeyboardInteractive should prompt the user for the given
// questions.
type ClientKeyboardInteractive interface {
	// Challenge should print the questions, optionally disabling
	// echoing (eg. for passwords), and return all the answers.
	// Challenge may be called multiple times in a single
	// session. After successful authentication, the server may
	// send a challenge with no questions, for which the user and
	// instruction messages should be printed.  RFC 4256 section
	// 3.3 details how the UI should behave for both CLI and
	// GUI environments.
	Challenge(user, instruction string, questions []string, echos []bool) ([]string, error)
}

// ClientAuthKeyboardInteractive returns a ClientAuth using a
// prompt/response sequence controlled by the server.
func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
	return &keyboardInteractiveAuth{impl}
}

type keyboardInteractiveAuth struct {
	ClientKeyboardInteractive
}

415
func (k *keyboardInteractiveAuth) method() string {
416 417 418
	return "keyboard-interactive"
}

419
func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
420 421 422 423 424 425 426 427
	type initiateMsg struct {
		User       string
		Service    string
		Method     string
		Language   string
		Submethods string
	}

428
	if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{
429 430 431 432 433 434 435 436
		User:    user,
		Service: serviceSSH,
		Method:  "keyboard-interactive",
	})); err != nil {
		return false, nil, err
	}

	for {
437
		packet, err := c.readPacket()
438 439 440 441 442 443
		if err != nil {
			return false, nil, err
		}

		// like handleAuthResponse, but with less options.
		switch packet[0] {
444 445 446
		case msgUserAuthBanner:
			// TODO: Print banners during userauth.
			continue
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
		case msgUserAuthInfoRequest:
			// OK
		case msgUserAuthFailure:
			var msg userAuthFailureMsg
			if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
				return false, nil, err
			}
			return false, msg.Methods, nil
		case msgUserAuthSuccess:
			return true, nil, nil
		default:
			return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]}
		}

		var msg userAuthInfoRequestMsg
		if err := unmarshal(&msg, packet, packet[0]); err != nil {
			return false, nil, err
		}

		// Manually unpack the prompt/echo pairs.
		rest := msg.Prompts
		var prompts []string
		var echos []bool
		for i := 0; i < int(msg.NumPrompts); i++ {
			prompt, r, ok := parseString(rest)
			if !ok || len(r) == 0 {
				return false, nil, errors.New("ssh: prompt format error")
			}
			prompts = append(prompts, string(prompt))
			echos = append(echos, r[0] != 0)
			rest = r[1:]
		}

		if len(rest) != 0 {
			return false, nil, fmt.Errorf("ssh: junk following message %q", rest)
		}

484
		answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos)
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
		if err != nil {
			return false, nil, err
		}

		if len(answers) != len(prompts) {
			return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
		}
		responseLength := 1 + 4
		for _, a := range answers {
			responseLength += stringLength(len(a))
		}
		serialized := make([]byte, responseLength)
		p := serialized
		p[0] = msgUserAuthInfoResponse
		p = p[1:]
		p = marshalUint32(p, uint32(len(answers)))
		for _, a := range answers {
			p = marshalString(p, []byte(a))
		}

505
		if err := c.writePacket(serialized); err != nil {
506 507 508 509
			return false, nil, err
		}
	}
}