Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(268)

Unified Diff: ssh/client_auth.go

Issue 5564059: code review 5564059: go.crypto: initial code (Closed)
Patch Set: diff -r b50a7fb49394 https://code.google.com/p/go.crypto Created 12 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ssh/client.go ('k') | ssh/client_auth_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ssh/client_auth.go
===================================================================
new file mode 100644
--- /dev/null
+++ b/ssh/client_auth.go
@@ -0,0 +1,316 @@
+// 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"
+ "io"
+)
+
+// authenticate authenticates with the remote server. See RFC 4252.
+func (c *ClientConn) authenticate(session []byte) 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(session, c.config.User, c.transport, c.config.rand())
+ 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(session []byte, user string, t *transport, rand io.Reader) (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(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+ if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: "none",
+ })); err != nil {
+ return false, nil, err
+ }
+
+ return handleAuthResponse(t)
+}
+
+func (n *noneAuth) method() string {
+ return "none"
+}
+
+// "password" authentication, RFC 4252 Section 8.
+type passwordAuth struct {
+ ClientPassword
+}
+
+func (p *passwordAuth) auth(session []byte, user string, t *transport, rand io.Reader) (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
+ }
+
+ return handleAuthResponse(t)
+}
+
+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 {
+ // Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
+ // no key exists at i.
+ Key(i int) (key interface{}, err error)
+
+ // 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
+ // HasSig indicates to the reciver packet that the auth request is signed and
+ // 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"`
+}
+
+func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+
+ // 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
+ validKeys := make(map[int]interface{})
+ for {
+ key, err := p.Key(index)
+ if err != nil {
+ return false, nil, err
+ }
+ if key == nil {
+ // no more keys in the keyring
+ break
+ }
+
+ if ok, err := p.validateKey(key, user, t); ok {
+ 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 {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+ sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, userAuthRequestMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: p.method(),
+ }, []byte(algoname), pubkey))
+ if err != nil {
+ return false, nil, err
+ }
+ // manually wrap the serialized signature in a string
+ s := serializeSignature(algoname, sign)
+ sig := make([]byte, stringLength(s))
+ 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)
+ if err := t.writePacket(p); err != nil {
+ return false, nil, err
+ }
+ success, methods, err := handleAuthResponse(t)
+ 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.
+func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) (bool, error) {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+ msg := publickeyAuthMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: p.method(),
+ HasSig: false,
+ Algoname: algoname,
+ Pubkey: string(pubkey),
+ }
+ if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
+ return false, err
+ }
+
+ return p.confirmKeyAck(key, t)
+}
+
+func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, error) {
+ pubkey := serializePublickey(key)
+ algoname := algoName(key)
+
+ for {
+ packet, err := t.readPacket()
+ if err != nil {
+ return false, err
+ }
+ switch packet[0] {
+ case msgUserAuthBanner:
+ // TODO(gpaul): add callback to present the banner to the user
+ case msgUserAuthPubKeyOk:
+ msg := decode(packet).(*userAuthPubKeyOkMsg)
+ 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]}
+ }
+ }
+ panic("unreachable")
+}
+
+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.
+func handleAuthResponse(t *transport) (bool, []string, error) {
+ for {
+ packet, err := t.readPacket()
+ if err != nil {
+ return false, nil, err
+ }
+
+ switch packet[0] {
+ case msgUserAuthBanner:
+ // TODO: add callback to present the banner to the user
+ case msgUserAuthFailure:
+ msg := decode(packet).(*userAuthFailureMsg)
+ 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]}
+ }
+ }
+ panic("unreachable")
+}
« no previous file with comments | « ssh/client.go ('k') | ssh/client_auth_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b