You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

145 lines
4.4 KiB

  1. // Copyright 2017 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Tests for ssh client multi-auth
  5. //
  6. // These tests run a simple go ssh client against OpenSSH server
  7. // over unix domain sockets. The tests use multiple combinations
  8. // of password, keyboard-interactive and publickey authentication
  9. // methods.
  10. //
  11. // A wrapper library for making sshd PAM authentication use test
  12. // passwords is required in ./sshd_test_pw.so. If the library does
  13. // not exist these tests will be skipped. See compile instructions
  14. // (for linux) in file ./sshd_test_pw.c.
  15. // +build linux
  16. package test
  17. import (
  18. "fmt"
  19. "strings"
  20. "testing"
  21. "golang.org/x/crypto/ssh"
  22. )
  23. // test cases
  24. type multiAuthTestCase struct {
  25. authMethods []string
  26. expectedPasswordCbs int
  27. expectedKbdIntCbs int
  28. }
  29. // test context
  30. type multiAuthTestCtx struct {
  31. password string
  32. numPasswordCbs int
  33. numKbdIntCbs int
  34. }
  35. // create test context
  36. func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
  37. password, err := randomPassword()
  38. if err != nil {
  39. t.Fatalf("Failed to generate random test password: %s", err.Error())
  40. }
  41. return &multiAuthTestCtx{
  42. password: password,
  43. }
  44. }
  45. // password callback
  46. func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
  47. ctx.numPasswordCbs++
  48. return ctx.password, nil
  49. }
  50. // keyboard-interactive callback
  51. func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
  52. if len(questions) == 0 {
  53. return nil, nil
  54. }
  55. ctx.numKbdIntCbs++
  56. if len(questions) == 1 {
  57. return []string{ctx.password}, nil
  58. }
  59. return nil, fmt.Errorf("unsupported keyboard-interactive flow")
  60. }
  61. // TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
  62. func TestMultiAuth(t *testing.T) {
  63. testCases := []multiAuthTestCase{
  64. // Test password,publickey authentication, assert that password callback is called 1 time
  65. multiAuthTestCase{
  66. authMethods: []string{"password", "publickey"},
  67. expectedPasswordCbs: 1,
  68. },
  69. // Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
  70. multiAuthTestCase{
  71. authMethods: []string{"keyboard-interactive", "publickey"},
  72. expectedKbdIntCbs: 1,
  73. },
  74. // Test publickey,password authentication, assert that password callback is called 1 time
  75. multiAuthTestCase{
  76. authMethods: []string{"publickey", "password"},
  77. expectedPasswordCbs: 1,
  78. },
  79. // Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
  80. multiAuthTestCase{
  81. authMethods: []string{"publickey", "keyboard-interactive"},
  82. expectedKbdIntCbs: 1,
  83. },
  84. // Test password,password authentication, assert that password callback is called 2 times
  85. multiAuthTestCase{
  86. authMethods: []string{"password", "password"},
  87. expectedPasswordCbs: 2,
  88. },
  89. }
  90. for _, testCase := range testCases {
  91. t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
  92. ctx := newMultiAuthTestCtx(t)
  93. server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
  94. defer server.Shutdown()
  95. clientConfig := clientConfig()
  96. server.setTestPassword(clientConfig.User, ctx.password)
  97. publicKeyAuthMethod := clientConfig.Auth[0]
  98. clientConfig.Auth = nil
  99. for _, authMethod := range testCase.authMethods {
  100. switch authMethod {
  101. case "publickey":
  102. clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
  103. case "password":
  104. clientConfig.Auth = append(clientConfig.Auth,
  105. ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
  106. case "keyboard-interactive":
  107. clientConfig.Auth = append(clientConfig.Auth,
  108. ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
  109. default:
  110. t.Fatalf("Unknown authentication method %s", authMethod)
  111. }
  112. }
  113. conn := server.Dial(clientConfig)
  114. defer conn.Close()
  115. if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
  116. t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
  117. }
  118. if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
  119. t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
  120. }
  121. })
  122. }
  123. }