This uses a copy of testenv.Command copied from the main repo, with light edits to allow the testenv helpers to build with Go 1.19. The testenv helper revealed an exec.Command leak in TestCertLogin, so we also fix that leak and simplify server cleanup using testing.T.Cleanup. For golang/go#60099. Fixes golang/go#60343. Change-Id: I7f79fcdb559498b987ee7689972ac53b83870aaf Reviewed-on: https://go-review.googlesource.com/c/crypto/+/496935 Auto-Submit: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> Run-TryBot: Bryan Mills <bcmills@google.com>
145 lines
4.4 KiB
Go
145 lines
4.4 KiB
Go
// 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.
|
|
|
|
//go:build linux
|
|
// +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, ",")})
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|