diff --git a/src/pkg/exp/ssh/Makefile b/src/pkg/exp/ssh/Makefile index 8e007a4b216..0db0b6f53fa 100644 --- a/src/pkg/exp/ssh/Makefile +++ b/src/pkg/exp/ssh/Makefile @@ -8,6 +8,7 @@ TARG=exp/ssh GOFILES=\ channel.go\ client.go\ + client_auth.go\ common.go\ messages.go\ transport.go\ diff --git a/src/pkg/exp/ssh/client.go b/src/pkg/exp/ssh/client.go index 345e707b336..9a2c0c59770 100644 --- a/src/pkg/exp/ssh/client.go +++ b/src/pkg/exp/ssh/client.go @@ -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 { diff --git a/src/pkg/exp/ssh/client_auth.go b/src/pkg/exp/ssh/client_auth.go new file mode 100644 index 00000000000..0089d0c769e --- /dev/null +++ b/src/pkg/exp/ssh/client_auth.go @@ -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} +} diff --git a/src/pkg/exp/ssh/doc.go b/src/pkg/exp/ssh/doc.go index fc842b0c1d2..248b2fec4f8 100644 --- a/src/pkg/exp/ssh/doc.go +++ b/src/pkg/exp/ssh/doc.go @@ -83,7 +83,7 @@ authentication method is supported. config := &ClientConfig{ User: "username", - Password: "123456", + Auth: []ClientAuth{ ... }, } client, err := Dial("yourserver.com:22", config)