| // 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 ( |
| "fmt" |
| "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 fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried)) |
| } |
| |
| func keys(m map[string]bool) (s []string) { |
| for k, _ := range m { |
| s = append(s, k) |
| } |
| return |
| } |
| |
| // 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") |
| } |