]> Cypherpunks repositories - gostls13.git/commitdiff
exp/ssh: improved client authentication support
authorDave Cheney <dave@cheney.net>
Mon, 7 Nov 2011 17:37:05 +0000 (12:37 -0500)
committerAdam Langley <agl@golang.org>
Mon, 7 Nov 2011 17:37:05 +0000 (12:37 -0500)
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

src/pkg/exp/ssh/Makefile
src/pkg/exp/ssh/client.go
src/pkg/exp/ssh/client_auth.go [new file with mode: 0644]
src/pkg/exp/ssh/doc.go

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