mirror of
https://github.com/golang/go
synced 2024-11-02 11:50:30 +00:00
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:
parent
a1c622dfea
commit
1170a6460f
4 changed files with 162 additions and 53 deletions
|
@ -8,6 +8,7 @@ TARG=exp/ssh
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
channel.go\
|
channel.go\
|
||||||
client.go\
|
client.go\
|
||||||
|
client_auth.go\
|
||||||
common.go\
|
common.go\
|
||||||
messages.go\
|
messages.go\
|
||||||
transport.go\
|
transport.go\
|
||||||
|
|
|
@ -131,56 +131,6 @@ func (c *ClientConn) handshake() error {
|
||||||
return c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc)
|
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
|
// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
|
||||||
// returned values are given the same names as in RFC 4253, section 8.
|
// 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) {
|
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.
|
// The username to authenticate.
|
||||||
User string
|
User string
|
||||||
|
|
||||||
// Used for "password" method authentication.
|
// A slice of ClientAuth methods. Only the first instance
|
||||||
Password string
|
// of a particular RFC 4252 method will be used during authentication.
|
||||||
|
Auth []ClientAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientConfig) rand() io.Reader {
|
func (c *ClientConfig) rand() io.Reader {
|
||||||
|
|
157
src/pkg/exp/ssh/client_auth.go
Normal file
157
src/pkg/exp/ssh/client_auth.go
Normal 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}
|
||||||
|
}
|
|
@ -83,7 +83,7 @@ authentication method is supported.
|
||||||
|
|
||||||
config := &ClientConfig{
|
config := &ClientConfig{
|
||||||
User: "username",
|
User: "username",
|
||||||
Password: "123456",
|
Auth: []ClientAuth{ ... },
|
||||||
}
|
}
|
||||||
client, err := Dial("yourserver.com:22", config)
|
client, err := Dial("yourserver.com:22", config)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue