summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/crypto/ssh/test
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/crypto/ssh/test')
-rw-r--r--vendor/golang.org/x/crypto/ssh/test/multi_auth_test.go144
-rw-r--r--vendor/golang.org/x/crypto/ssh/test/sshd_test_pw.c173
-rw-r--r--vendor/golang.org/x/crypto/ssh/test/test_unix_test.go73
3 files changed, 385 insertions, 5 deletions
diff --git a/vendor/golang.org/x/crypto/ssh/test/multi_auth_test.go b/vendor/golang.org/x/crypto/ssh/test/multi_auth_test.go
new file mode 100644
index 000000000..f594d36e4
--- /dev/null
+++ b/vendor/golang.org/x/crypto/ssh/test/multi_auth_test.go
@@ -0,0 +1,144 @@
+// Copyright 2017 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.
+
+// Tests for ssh client multi-auth
+//
+// These tests run a simple go ssh client against OpenSSH server
+// over unix domain sockets. The tests use multiple combinations
+// of password, keyboard-interactive and publickey authentication
+// methods.
+//
+// A wrapper library for making sshd PAM authentication use test
+// passwords is required in ./sshd_test_pw.so. If the library does
+// not exist these tests will be skipped. See compile instructions
+// (for linux) in file ./sshd_test_pw.c.
+
+// +build linux
+
+package test
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// test cases
+type multiAuthTestCase struct {
+ authMethods []string
+ expectedPasswordCbs int
+ expectedKbdIntCbs int
+}
+
+// test context
+type multiAuthTestCtx struct {
+ password string
+ numPasswordCbs int
+ numKbdIntCbs int
+}
+
+// create test context
+func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
+ password, err := randomPassword()
+ if err != nil {
+ t.Fatalf("Failed to generate random test password: %s", err.Error())
+ }
+
+ return &multiAuthTestCtx{
+ password: password,
+ }
+}
+
+// password callback
+func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
+ ctx.numPasswordCbs++
+ return ctx.password, nil
+}
+
+// keyboard-interactive callback
+func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
+ if len(questions) == 0 {
+ return nil, nil
+ }
+
+ ctx.numKbdIntCbs++
+ if len(questions) == 1 {
+ return []string{ctx.password}, nil
+ }
+
+ return nil, fmt.Errorf("unsupported keyboard-interactive flow")
+}
+
+// TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
+func TestMultiAuth(t *testing.T) {
+ testCases := []multiAuthTestCase{
+ // Test password,publickey authentication, assert that password callback is called 1 time
+ multiAuthTestCase{
+ authMethods: []string{"password", "publickey"},
+ expectedPasswordCbs: 1,
+ },
+ // Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
+ multiAuthTestCase{
+ authMethods: []string{"keyboard-interactive", "publickey"},
+ expectedKbdIntCbs: 1,
+ },
+ // Test publickey,password authentication, assert that password callback is called 1 time
+ multiAuthTestCase{
+ authMethods: []string{"publickey", "password"},
+ expectedPasswordCbs: 1,
+ },
+ // Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
+ multiAuthTestCase{
+ authMethods: []string{"publickey", "keyboard-interactive"},
+ expectedKbdIntCbs: 1,
+ },
+ // Test password,password authentication, assert that password callback is called 2 times
+ multiAuthTestCase{
+ authMethods: []string{"password", "password"},
+ expectedPasswordCbs: 2,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
+ ctx := newMultiAuthTestCtx(t)
+
+ server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
+ defer server.Shutdown()
+
+ clientConfig := clientConfig()
+ server.setTestPassword(clientConfig.User, ctx.password)
+
+ publicKeyAuthMethod := clientConfig.Auth[0]
+ clientConfig.Auth = nil
+ for _, authMethod := range testCase.authMethods {
+ switch authMethod {
+ case "publickey":
+ clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
+ case "password":
+ clientConfig.Auth = append(clientConfig.Auth,
+ ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
+ case "keyboard-interactive":
+ clientConfig.Auth = append(clientConfig.Auth,
+ ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
+ default:
+ t.Fatalf("Unknown authentication method %s", authMethod)
+ }
+ }
+
+ conn := server.Dial(clientConfig)
+ defer conn.Close()
+
+ if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
+ t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
+ }
+
+ if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
+ t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
+ }
+ })
+ }
+}
diff --git a/vendor/golang.org/x/crypto/ssh/test/sshd_test_pw.c b/vendor/golang.org/x/crypto/ssh/test/sshd_test_pw.c
new file mode 100644
index 000000000..2794a563a
--- /dev/null
+++ b/vendor/golang.org/x/crypto/ssh/test/sshd_test_pw.c
@@ -0,0 +1,173 @@
+// Copyright 2017 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.
+
+// sshd_test_pw.c
+// Wrapper to inject test password data for sshd PAM authentication
+//
+// This wrapper implements custom versions of getpwnam, getpwnam_r,
+// getspnam and getspnam_r. These functions first call their real
+// libc versions, then check if the requested user matches test user
+// specified in env variable TEST_USER and if so replace the password
+// with crypted() value of TEST_PASSWD env variable.
+//
+// Compile:
+// gcc -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
+//
+// Compile with debug:
+// gcc -DVERBOSE -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
+//
+// Run sshd:
+// LD_PRELOAD="sshd_test_pw.so" TEST_USER="..." TEST_PASSWD="..." sshd ...
+
+// +build ignore
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#ifdef VERBOSE
+#define DEBUG(X...) fprintf(stderr, X)
+#else
+#define DEBUG(X...) while (0) { }
+#endif
+
+/* crypt() password */
+static char *
+pwhash(char *passwd) {
+ return strdup(crypt(passwd, "$6$"));
+}
+
+/* Pointers to real functions in libc */
+static struct passwd * (*real_getpwnam)(const char *) = NULL;
+static int (*real_getpwnam_r)(const char *, struct passwd *, char *, size_t, struct passwd **) = NULL;
+static struct spwd * (*real_getspnam)(const char *) = NULL;
+static int (*real_getspnam_r)(const char *, struct spwd *, char *, size_t, struct spwd **) = NULL;
+
+/* Cached test user and test password */
+static char *test_user = NULL;
+static char *test_passwd_hash = NULL;
+
+static void
+init(void) {
+ /* Fetch real libc function pointers */
+ real_getpwnam = dlsym(RTLD_NEXT, "getpwnam");
+ real_getpwnam_r = dlsym(RTLD_NEXT, "getpwnam_r");
+ real_getspnam = dlsym(RTLD_NEXT, "getspnam");
+ real_getspnam_r = dlsym(RTLD_NEXT, "getspnam_r");
+
+ /* abort if env variables are not defined */
+ if (getenv("TEST_USER") == NULL || getenv("TEST_PASSWD") == NULL) {
+ fprintf(stderr, "env variables TEST_USER and TEST_PASSWD are missing\n");
+ abort();
+ }
+
+ /* Fetch test user and test password from env */
+ test_user = strdup(getenv("TEST_USER"));
+ test_passwd_hash = pwhash(getenv("TEST_PASSWD"));
+
+ DEBUG("sshd_test_pw init():\n");
+ DEBUG("\treal_getpwnam: %p\n", real_getpwnam);
+ DEBUG("\treal_getpwnam_r: %p\n", real_getpwnam_r);
+ DEBUG("\treal_getspnam: %p\n", real_getspnam);
+ DEBUG("\treal_getspnam_r: %p\n", real_getspnam_r);
+ DEBUG("\tTEST_USER: '%s'\n", test_user);
+ DEBUG("\tTEST_PASSWD: '%s'\n", getenv("TEST_PASSWD"));
+ DEBUG("\tTEST_PASSWD_HASH: '%s'\n", test_passwd_hash);
+}
+
+static int
+is_test_user(const char *name) {
+ if (test_user != NULL && strcmp(test_user, name) == 0)
+ return 1;
+ return 0;
+}
+
+/* getpwnam */
+
+struct passwd *
+getpwnam(const char *name) {
+ struct passwd *pw;
+
+ DEBUG("sshd_test_pw getpwnam(%s)\n", name);
+
+ if (real_getpwnam == NULL)
+ init();
+ if ((pw = real_getpwnam(name)) == NULL)
+ return NULL;
+
+ if (is_test_user(name))
+ pw->pw_passwd = strdup(test_passwd_hash);
+
+ return pw;
+}
+
+/* getpwnam_r */
+
+int
+getpwnam_r(const char *name,
+ struct passwd *pwd,
+ char *buf,
+ size_t buflen,
+ struct passwd **result) {
+ int r;
+
+ DEBUG("sshd_test_pw getpwnam_r(%s)\n", name);
+
+ if (real_getpwnam_r == NULL)
+ init();
+ if ((r = real_getpwnam_r(name, pwd, buf, buflen, result)) != 0 || *result == NULL)
+ return r;
+
+ if (is_test_user(name))
+ pwd->pw_passwd = strdup(test_passwd_hash);
+
+ return 0;
+}
+
+/* getspnam */
+
+struct spwd *
+getspnam(const char *name) {
+ struct spwd *sp;
+
+ DEBUG("sshd_test_pw getspnam(%s)\n", name);
+
+ if (real_getspnam == NULL)
+ init();
+ if ((sp = real_getspnam(name)) == NULL)
+ return NULL;
+
+ if (is_test_user(name))
+ sp->sp_pwdp = strdup(test_passwd_hash);
+
+ return sp;
+}
+
+/* getspnam_r */
+
+int
+getspnam_r(const char *name,
+ struct spwd *spbuf,
+ char *buf,
+ size_t buflen,
+ struct spwd **spbufp) {
+ int r;
+
+ DEBUG("sshd_test_pw getspnam_r(%s)\n", name);
+
+ if (real_getspnam_r == NULL)
+ init();
+ if ((r = real_getspnam_r(name, spbuf, buf, buflen, spbufp)) != 0)
+ return r;
+
+ if (is_test_user(name))
+ spbuf->sp_pwdp = strdup(test_passwd_hash);
+
+ return r;
+}
diff --git a/vendor/golang.org/x/crypto/ssh/test/test_unix_test.go b/vendor/golang.org/x/crypto/ssh/test/test_unix_test.go
index 15b879d35..39607868c 100644
--- a/vendor/golang.org/x/crypto/ssh/test/test_unix_test.go
+++ b/vendor/golang.org/x/crypto/ssh/test/test_unix_test.go
@@ -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)
}