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") |
+} |