exp/ssh: improved client authentication support

This CL adds an API for handling the various SSH
authenticaton methods. None and password continue
to be the only supported methods.

R=bradfitz, agl, n13m3y3r, rsc, cw
CC=golang-dev
https://golang.org/cl/5328045
This commit is contained in:
Dave Cheney 2011-11-07 12:37:05 -05:00 committed by Adam Langley
parent a1c622dfea
commit 1170a6460f
4 changed files with 162 additions and 53 deletions

View file

@ -8,6 +8,7 @@ TARG=exp/ssh
GOFILES=\
channel.go\
client.go\
client_auth.go\
common.go\
messages.go\
transport.go\

View file

@ -131,56 +131,6 @@ func (c *ClientConn) handshake() error {
return c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc)
}
// authenticate authenticates with the remote server. See RFC 4252.
// Only "password" authentication is supported.
func (c *ClientConn) authenticate() error {
if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
return err
}
packet, err := c.readPacket()
if err != nil {
return err
}
var serviceAccept serviceAcceptMsg
if err = unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
return err
}
// TODO(dfc) support proper authentication method negotation
method := "none"
if c.config.Password != "" {
method = "password"
}
if err := c.sendUserAuthReq(method); err != nil {
return err
}
if packet, err = c.readPacket(); err != nil {
return err
}
if packet[0] != msgUserAuthSuccess {
return UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
return nil
}
func (c *ClientConn) sendUserAuthReq(method string) error {
length := stringLength([]byte(c.config.Password)) + 1
payload := make([]byte, length)
// always false for password auth, see RFC 4252 Section 8.
payload[0] = 0
marshalString(payload[1:], []byte(c.config.Password))
return c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
User: c.config.User,
Service: serviceSSH,
Method: method,
Payload: payload,
}))
}
// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
// returned values are given the same names as in RFC 4253, section 8.
func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, error) {
@ -348,8 +298,9 @@ type ClientConfig struct {
// The username to authenticate.
User string
// Used for "password" method authentication.
Password string
// A slice of ClientAuth methods. Only the first instance
// of a particular RFC 4252 method will be used during authentication.
Auth []ClientAuth
}
func (c *ClientConfig) rand() io.Reader {

View file

@ -0,0 +1,157 @@
// 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 (
"errors"
)
// authenticate authenticates with the remote server. See RFC 4252.
func (c *ClientConn) authenticate() error {
// initiate user auth session
if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
return err
}
packet, err := c.readPacket()
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; {
ok, methods, err := auth.auth(c.config.User, c.transport)
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
}
}
}
return errors.New("ssh: unable to authenticate, no supported methods remain")
}
// 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.
auth(user string, t *transport) (bool, []string, error)
// method returns the RFC 4252 method name.
method() string
}
// "none" authentication, RFC 4252 section 5.2.
type noneAuth int
func (n *noneAuth) auth(user string, t *transport) (bool, []string, error) {
if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
User: user,
Service: serviceSSH,
Method: "none",
})); err != nil {
return false, nil, err
}
packet, err := t.readPacket()
if err != nil {
return false, nil, err
}
switch packet[0] {
case msgUserAuthSuccess:
return true, nil, nil
case msgUserAuthFailure:
msg := decode(packet).(*userAuthFailureMsg)
return false, msg.Methods, nil
}
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
func (n *noneAuth) method() string {
return "none"
}
// "password" authentication, RFC 4252 Section 8.
type passwordAuth struct {
ClientPassword
}
func (p *passwordAuth) auth(user string, t *transport) (bool, []string, error) {
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
}
if err := t.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
User: user,
Service: serviceSSH,
Method: "password",
Reply: false,
Password: pw,
})); err != nil {
return false, nil, err
}
packet, err := t.readPacket()
if err != nil {
return false, nil, err
}
switch packet[0] {
case msgUserAuthSuccess:
return true, nil, nil
case msgUserAuthFailure:
msg := decode(packet).(*userAuthFailureMsg)
return false, msg.Methods, nil
}
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
}
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}
}

View file

@ -83,7 +83,7 @@ authentication method is supported.
config := &ClientConfig{
User: "username",
Password: "123456",
Auth: []ClientAuth{ ... },
}
client, err := Dial("yourserver.com:22", config)