diff options
| author | Russ Cox <rsc@golang.org> | 2012-01-25 15:31:12 -0500 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2012-01-25 15:31:12 -0500 |
| commit | 470549d5029af744f2f91c2c66a10efc5bc871f8 (patch) | |
| tree | 3c25ee1ef3ee3355419b275ecec84cf848771b7e /ssh/client_auth.go | |
| parent | f61fbb80d2fc0f581f66365e8907f07a4d7506b0 (diff) | |
| download | go-x-crypto-470549d5029af744f2f91c2c66a10efc5bc871f8.tar.xz | |
go.crypto: initial code
Manual edits to README.
Moved from main Go repository, deleted Makefiles, ran gofix -r go1rename.
Tested with: go test code.google.com/p/go.crypto/...
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5564059
Diffstat (limited to 'ssh/client_auth.go')
| -rw-r--r-- | ssh/client_auth.go | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/ssh/client_auth.go b/ssh/client_auth.go new file mode 100644 index 0000000..3a7e9fb --- /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") +} |
