ssh: fix support for partial success authentication responses in client
The existing client side authentication does not handle correctly the partial success flag in SSH_MSG_USERAUTH_FAILURE authentication responses. This commit fixes two problems in ssh library: 1) RetryableAuthMethod() now breaks out from the retry loop and returns when underlying auth method fails with partial success set to true. 2) Book keeping of tried (and failed) auth methods in clientAuthenticate() does not mark an auth method failed if it fails with partial success set to true. Fixes golang/go#23461 Change-Id: Ib2e1a1d54bfe2549496199bb2f66ebbce58d130d Reviewed-on: https://go-review.googlesource.com/88035 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
This commit is contained in:
committed by
Han-Wen Nienhuys
parent
d9133f5469
commit
9334d73e5f
@@ -10,6 +10,8 @@ package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -25,7 +27,8 @@ import (
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
const sshdConfig = `
|
||||
const (
|
||||
defaultSshdConfig = `
|
||||
Protocol 2
|
||||
Banner {{.Dir}}/banner
|
||||
HostKey {{.Dir}}/id_rsa
|
||||
@@ -50,8 +53,17 @@ RhostsRSAAuthentication no
|
||||
HostbasedAuthentication no
|
||||
PubkeyAcceptedKeyTypes=*
|
||||
`
|
||||
multiAuthSshdConfigTail = `
|
||||
UsePAM yes
|
||||
PasswordAuthentication yes
|
||||
ChallengeResponseAuthentication yes
|
||||
AuthenticationMethods {{.AuthMethods}}
|
||||
`
|
||||
)
|
||||
|
||||
var configTmpl = template.Must(template.New("").Parse(sshdConfig))
|
||||
var configTmpl = map[string]*template.Template{
|
||||
"default": template.Must(template.New("").Parse(defaultSshdConfig)),
|
||||
"MultiAuth": template.Must(template.New("").Parse(defaultSshdConfig + multiAuthSshdConfigTail))}
|
||||
|
||||
type server struct {
|
||||
t *testing.T
|
||||
@@ -60,6 +72,10 @@ type server struct {
|
||||
cmd *exec.Cmd
|
||||
output bytes.Buffer // holds stderr from sshd process
|
||||
|
||||
testUser string // test username for sshd
|
||||
testPasswd string // test password for sshd
|
||||
sshdTestPwSo string // dynamic library to inject a custom password into sshd
|
||||
|
||||
// Client half of the network connection.
|
||||
clientConn net.Conn
|
||||
}
|
||||
@@ -186,6 +202,20 @@ func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Cl
|
||||
s.cmd.Stdin = f
|
||||
s.cmd.Stdout = f
|
||||
s.cmd.Stderr = &s.output
|
||||
|
||||
if s.sshdTestPwSo != "" {
|
||||
if s.testUser == "" {
|
||||
s.t.Fatal("user missing from sshd_test_pw.so config")
|
||||
}
|
||||
if s.testPasswd == "" {
|
||||
s.t.Fatal("password missing from sshd_test_pw.so config")
|
||||
}
|
||||
s.cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
|
||||
fmt.Sprintf("TEST_USER=%s", s.testUser),
|
||||
fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
|
||||
}
|
||||
|
||||
if err := s.cmd.Start(); err != nil {
|
||||
s.t.Fail()
|
||||
s.Shutdown()
|
||||
@@ -236,8 +266,39 @@ func writeFile(path string, contents []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// generate random password
|
||||
func randomPassword() (string, error) {
|
||||
b := make([]byte, 12)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// setTestPassword is used for setting user and password data for sshd_test_pw.so
|
||||
// This function also checks that ./sshd_test_pw.so exists and if not calls s.t.Skip()
|
||||
func (s *server) setTestPassword(user, passwd string) error {
|
||||
wd, _ := os.Getwd()
|
||||
wrapper := filepath.Join(wd, "sshd_test_pw.so")
|
||||
if _, err := os.Stat(wrapper); err != nil {
|
||||
s.t.Skip(fmt.Errorf("sshd_test_pw.so is not available"))
|
||||
return err
|
||||
}
|
||||
|
||||
s.sshdTestPwSo = wrapper
|
||||
s.testUser = user
|
||||
s.testPasswd = passwd
|
||||
return nil
|
||||
}
|
||||
|
||||
// newServer returns a new mock ssh server.
|
||||
func newServer(t *testing.T) *server {
|
||||
return newServerForConfig(t, "default", map[string]string{})
|
||||
}
|
||||
|
||||
// newServerForConfig returns a new mock ssh server.
|
||||
func newServerForConfig(t *testing.T, config string, configVars map[string]string) *server {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
@@ -249,9 +310,11 @@ func newServer(t *testing.T) *server {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = configTmpl.Execute(f, map[string]string{
|
||||
"Dir": dir,
|
||||
})
|
||||
if _, ok := configTmpl[config]; ok == false {
|
||||
t.Fatal(fmt.Errorf("Invalid server config '%s'", config))
|
||||
}
|
||||
configVars["Dir"] = dir
|
||||
err = configTmpl[config].Execute(f, configVars)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user