diff options
| author | Adam Langley <agl@golang.org> | 2014-04-09 13:57:52 -0700 |
|---|---|---|
| committer | Adam Langley <agl@golang.org> | 2014-04-09 13:57:52 -0700 |
| commit | fa50e7408b9ef89ff2965535b59f1a0010c0770b (patch) | |
| tree | e045a3f48f9ffd3bb712002f8f9f6fd489e8f7ef /ssh/client_auth.go | |
| parent | 8f45c680ceb25c200b8c301d9184532aeb7cb36e (diff) | |
| download | go-x-crypto-fa50e7408b9ef89ff2965535b59f1a0010c0770b.tar.xz | |
go.crypto/ssh: import gosshnew.
See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ
R=hanwen
CC=golang-codereviews
https://golang.org/cl/86190043
Diffstat (limited to 'ssh/client_auth.go')
| -rw-r--r-- | ssh/client_auth.go | 335 |
1 files changed, 129 insertions, 206 deletions
diff --git a/ssh/client_auth.go b/ssh/client_auth.go index 29be0ca..5b7aa30 100644 --- a/ssh/client_auth.go +++ b/ssh/client_auth.go @@ -5,16 +5,16 @@ package ssh import ( + "bytes" "errors" "fmt" "io" - "net" ) -// authenticate authenticates with the remote server. See RFC 4252. -func (c *ClientConn) authenticate() error { +// clientAuthenticate authenticates with the remote server. See RFC 4252. +func (c *connection) clientAuthenticate(config *ClientConfig) error { // initiate user auth session - if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil { + if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil { return err } packet, err := c.transport.readPacket() @@ -22,14 +22,15 @@ func (c *ClientConn) authenticate() error { return err } var serviceAccept serviceAcceptMsg - if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil { + if err := Unmarshal(packet, &serviceAccept); 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.transport.sessionID, c.config.User, c.transport, c.config.rand()) + tried := make(map[string]bool) + for auth := AuthMethod(new(noneAuth)); auth != nil; { + ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand) if err != nil { return err } @@ -38,45 +39,35 @@ func (c *ClientConn) authenticate() error { 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 + for _, a := range config.Auth { + candidateMethod := a.method() + for _, meth := range methods { + if meth != candidateMethod { + continue + } + if !tried[meth] { + 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 -} +func keys(m map[string]bool) []string { + s := make([]string, 0, len(m)) -// HostKeyChecker represents a database of known server host keys. -type HostKeyChecker interface { - // Check is called during the handshake to check server's - // public key for unexpected changes. The hostKey argument is - // in SSH wire format. It can be parsed using - // ssh.ParsePublicKey. The address before DNS resolution is - // passed in the addr argument, so the key can also be checked - // against the hostname. - Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error + for key := range m { + s = append(s, key) + } + return s } -// A ClientAuth represents an instance of an RFC 4252 authentication method. -type ClientAuth interface { +// An AuthMethod represents an instance of an RFC 4252 authentication method. +type AuthMethod interface { // auth authenticates user over transport t. // Returns true if authentication is successful. // If authentication is not successful, a []string of alternative @@ -91,7 +82,7 @@ type ClientAuth interface { type noneAuth int func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{ + if err := c.writePacket(Marshal(&userAuthRequestMsg{ User: user, Service: serviceSSH, Method: "none", @@ -106,29 +97,31 @@ func (n *noneAuth) method() string { return "none" } -// "password" authentication, RFC 4252 Section 8. -type passwordAuth struct { - ClientPassword -} +// passwordCallback is an AuthMethod that fetches the password through +// a function call, e.g. by prompting the user. +type passwordCallback func() (password string, err error) -func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { type passwordAuthMsg struct { - User string + User string `sshtype:"50"` Service string Method string Reply bool Password string } - pw, err := p.Password(user) + pw, err := cb() + // REVIEW NOTE: is there a need to support skipping a password attempt? + // The program may only find out that the user doesn't have a password + // when prompting. if err != nil { return false, nil, err } - if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{ + if err := c.writePacket(Marshal(&passwordAuthMsg{ User: user, Service: serviceSSH, - Method: "password", + Method: cb.method(), Reply: false, Password: pw, })); err != nil { @@ -138,106 +131,93 @@ func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.R return handleAuthResponse(c) } -func (p *passwordAuth) method() string { +func (cb passwordCallback) 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 Publickey, or nil if no key exists at i. - Key(i int) (key PublicKey, 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) +// Password returns an AuthMethod using the given password. +func Password(secret string) AuthMethod { + return passwordCallback(func() (string, error) { return secret, nil }) } -// "publickey" authentication, RFC 4252 Section 7. -type publickeyAuth struct { - ClientKeyring +// PasswordCallback returns an AuthMethod that uses a callback for +// fetching a password. +func PasswordCallback(prompt func() (secret string, err error)) AuthMethod { + return passwordCallback(prompt) } type publickeyAuthMsg struct { - User string + User string `sshtype:"50"` Service string Method string // HasSig indicates to the receiver 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 + PubKey []byte + // Sig is tagged with "rest" so Marshal will exclude it during + // validateKey Sig []byte `ssh:"rest"` } -func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { +// publicKeyCallback is an AuthMethod that uses a set of key +// pairs for authentication. +type publicKeyCallback func() ([]Signer, error) + +func (cb publicKeyCallback) method() string { + return "publickey" +} + +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, 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]PublicKey) - 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, c); ok { - validKeys[index] = key + signers, err := cb() + if err != nil { + return false, nil, err + } + var validKeys []Signer + for _, signer := range signers { + if ok, err := validateKey(signer.PublicKey(), user, c); ok { + validKeys = append(validKeys, signer) } 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 := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() - data := buildDataSignedForAuth(session, userAuthRequestMsg{ + for _, signer := range validKeys { + pub := signer.PublicKey() + + pubKey := pub.Marshal() + sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ User: user, Service: serviceSSH, - Method: p.method(), - }, []byte(algoname), pubkey) - sigBlob, err := p.Sign(i, rand, data) + Method: cb.method(), + }, []byte(pub.Type()), pubKey)) if err != nil { return false, nil, err } + // manually wrap the serialized signature in a string - s := serializeSignature(key.PublicKeyAlgo(), sigBlob) + s := Marshal(sign) sig := make([]byte, stringLength(len(s))) marshalString(sig, s) msg := publickeyAuthMsg{ User: user, Service: serviceSSH, - Method: p.method(), + Method: cb.method(), HasSig: true, - Algoname: algoname, - Pubkey: string(pubkey), + Algoname: pub.Type(), + PubKey: pubKey, Sig: sig, } - p := marshal(msgUserAuthRequest, msg) + p := Marshal(&msg) if err := c.writePacket(p); err != nil { return false, nil, err } @@ -252,28 +232,27 @@ func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io. return false, methods, nil } -// validateKey validates the key provided it is acceptable to the server. -func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (bool, error) { - pubkey := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() +// validateKey validates the key provided is acceptable to the server. +func validateKey(key PublicKey, user string, c packetConn) (bool, error) { + pubKey := key.Marshal() msg := publickeyAuthMsg{ User: user, Service: serviceSSH, - Method: p.method(), + Method: "publickey", HasSig: false, - Algoname: algoname, - Pubkey: string(pubkey), + Algoname: key.Type(), + PubKey: pubKey, } - if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil { + if err := c.writePacket(Marshal(&msg)); err != nil { return false, err } - return p.confirmKeyAck(key, c) + return confirmKeyAck(key, c) } -func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) { - pubkey := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() +func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { + pubKey := key.Marshal() + algoname := key.Type() for { packet, err := c.readPacket() @@ -284,30 +263,32 @@ func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) case msgUserAuthBanner: // TODO(gpaul): add callback to present the banner to the user case msgUserAuthPubKeyOk: - msg := userAuthPubKeyOkMsg{} - if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil { + var msg userAuthPubKeyOkMsg + if err := Unmarshal(packet, &msg); err != nil { return false, err } - if msg.Algo != algoname || msg.PubKey != string(pubkey) { + if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) { return false, nil } return true, nil case msgUserAuthFailure: return false, nil default: - return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]} + return false, unexpectedMessageError(msgUserAuthSuccess, packet[0]) } } - panic("unreachable") } -func (p *publickeyAuth) method() string { - return "publickey" +// PublicKeys returns an AuthMethod that uses the given key +// pairs. +func PublicKeys(signers ...Signer) AuthMethod { + return publicKeyCallback(func() ([]Signer, error) { return signers, nil }) } -// ClientAuthKeyring returns a ClientAuth using public key authentication. -func ClientAuthKeyring(impl ClientKeyring) ClientAuth { - return &publickeyAuth{impl} +// PublicKeysCallback returns an AuthMethod that runs the given +// function to obtain a list of key pairs. +func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod { + return publicKeyCallback(getSigners) } // handleAuthResponse returns whether the preceding authentication request succeeded @@ -324,8 +305,8 @@ func handleAuthResponse(c packetConn) (bool, []string, error) { case msgUserAuthBanner: // TODO: add callback to present the banner to the user case msgUserAuthFailure: - msg := userAuthFailureMsg{} - if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil { + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { return false, nil, err } return false, msg.Methods, nil @@ -334,98 +315,40 @@ func handleAuthResponse(c packetConn) (bool, []string, error) { case msgDisconnect: return false, nil, io.EOF default: - return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]} + return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) } } - panic("unreachable") -} - -// ClientAuthAgent returns a ClientAuth using public key authentication via -// an agent. -func ClientAuthAgent(agent *AgentClient) ClientAuth { - return ClientAuthKeyring(&agentKeyring{agent: agent}) -} - -// agentKeyring implements ClientKeyring. -type agentKeyring struct { - agent *AgentClient - keys []*AgentKey -} - -func (kr *agentKeyring) Key(i int) (key PublicKey, err error) { - if kr.keys == nil { - if kr.keys, err = kr.agent.RequestIdentities(); err != nil { - return - } - } - if i >= len(kr.keys) { - return - } - return kr.keys[i].Key() } -func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { - var key PublicKey - if key, err = kr.Key(i); err != nil { - return - } - if key == nil { - return nil, errors.New("ssh: key index out of range") - } - if sig, err = kr.agent.SignRequest(key, data); err != nil { - return - } - - // Unmarshal the signature. - - var ok bool - if _, sig, ok = parseString(sig); !ok { - return nil, errors.New("ssh: malformed signature response from agent") - } - if sig, _, ok = parseString(sig); !ok { - return nil, errors.New("ssh: malformed signature response from agent") - } - return sig, nil -} - -// ClientKeyboardInteractive should prompt the user for the given -// questions. -type ClientKeyboardInteractive interface { - // Challenge should print the questions, optionally disabling - // echoing (eg. for passwords), and return all the answers. - // Challenge may be called multiple times in a single - // session. After successful authentication, the server may - // send a challenge with no questions, for which the user and - // instruction messages should be printed. RFC 4256 section - // 3.3 details how the UI should behave for both CLI and - // GUI environments. - Challenge(user, instruction string, questions []string, echos []bool) ([]string, error) -} - -// ClientAuthKeyboardInteractive returns a ClientAuth using a -// prompt/response sequence controlled by the server. -func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth { - return &keyboardInteractiveAuth{impl} -} +// KeyboardInteractiveChallenge should print questions, optionally +// disabling echoing (e.g. for passwords), and return all the answers. +// Challenge may be called multiple times in a single session. After +// successful authentication, the server may send a challenge with no +// questions, for which the user and instruction messages should be +// printed. RFC 4256 section 3.3 details how the UI should behave for +// both CLI and GUI environments. +type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) -type keyboardInteractiveAuth struct { - ClientKeyboardInteractive +// KeyboardInteractive returns a AuthMethod using a prompt/response +// sequence controlled by the server. +func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { + return challenge } -func (k *keyboardInteractiveAuth) method() string { +func (cb KeyboardInteractiveChallenge) method() string { return "keyboard-interactive" } -func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { type initiateMsg struct { - User string + User string `sshtype:"50"` Service string Method string Language string Submethods string } - if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{ + if err := c.writePacket(Marshal(&initiateMsg{ User: user, Service: serviceSSH, Method: "keyboard-interactive", @@ -448,18 +371,18 @@ func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn // OK case msgUserAuthFailure: var msg userAuthFailureMsg - if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil { + if err := Unmarshal(packet, &msg); err != nil { return false, nil, err } return false, msg.Methods, nil case msgUserAuthSuccess: return true, nil, nil default: - return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]} + return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) } var msg userAuthInfoRequestMsg - if err := unmarshal(&msg, packet, packet[0]); err != nil { + if err := Unmarshal(packet, &msg); err != nil { return false, nil, err } @@ -478,10 +401,10 @@ func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn } if len(rest) != 0 { - return false, nil, fmt.Errorf("ssh: junk following message %q", rest) + return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs") } - answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos) + answers, err := cb(msg.User, msg.Instruction, prompts, echos) if err != nil { return false, nil, err } |
