multi_auth_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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. package test
  16. import (
  17. "fmt"
  18. "strings"
  19. "testing"
  20. "golang.org/x/crypto/ssh"
  21. )
  22. // test cases
  23. type multiAuthTestCase struct {
  24. authMethods []string
  25. expectedPasswordCbs int
  26. expectedKbdIntCbs int
  27. }
  28. // test context
  29. type multiAuthTestCtx struct {
  30. password string
  31. numPasswordCbs int
  32. numKbdIntCbs int
  33. }
  34. // create test context
  35. func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
  36. password, err := randomPassword()
  37. if err != nil {
  38. t.Fatalf("Failed to generate random test password: %s", err.Error())
  39. }
  40. return &multiAuthTestCtx{
  41. password: password,
  42. }
  43. }
  44. // password callback
  45. func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
  46. ctx.numPasswordCbs++
  47. return ctx.password, nil
  48. }
  49. // keyboard-interactive callback
  50. func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
  51. if len(questions) == 0 {
  52. return nil, nil
  53. }
  54. ctx.numKbdIntCbs++
  55. if len(questions) == 1 {
  56. return []string{ctx.password}, nil
  57. }
  58. return nil, fmt.Errorf("unsupported keyboard-interactive flow")
  59. }
  60. // TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
  61. func TestMultiAuth(t *testing.T) {
  62. testCases := []multiAuthTestCase{
  63. // Test password,publickey authentication, assert that password callback is called 1 time
  64. multiAuthTestCase{
  65. authMethods: []string{"password", "publickey"},
  66. expectedPasswordCbs: 1,
  67. },
  68. // Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
  69. multiAuthTestCase{
  70. authMethods: []string{"keyboard-interactive", "publickey"},
  71. expectedKbdIntCbs: 1,
  72. },
  73. // Test publickey,password authentication, assert that password callback is called 1 time
  74. multiAuthTestCase{
  75. authMethods: []string{"publickey", "password"},
  76. expectedPasswordCbs: 1,
  77. },
  78. // Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
  79. multiAuthTestCase{
  80. authMethods: []string{"publickey", "keyboard-interactive"},
  81. expectedKbdIntCbs: 1,
  82. },
  83. // Test password,password authentication, assert that password callback is called 2 times
  84. multiAuthTestCase{
  85. authMethods: []string{"password", "password"},
  86. expectedPasswordCbs: 2,
  87. },
  88. }
  89. for _, testCase := range testCases {
  90. t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
  91. ctx := newMultiAuthTestCtx(t)
  92. server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
  93. defer server.Shutdown()
  94. clientConfig := clientConfig()
  95. server.setTestPassword(clientConfig.User, ctx.password)
  96. publicKeyAuthMethod := clientConfig.Auth[0]
  97. clientConfig.Auth = nil
  98. for _, authMethod := range testCase.authMethods {
  99. switch authMethod {
  100. case "publickey":
  101. clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
  102. case "password":
  103. clientConfig.Auth = append(clientConfig.Auth,
  104. ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
  105. case "keyboard-interactive":
  106. clientConfig.Auth = append(clientConfig.Auth,
  107. ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
  108. default:
  109. t.Fatalf("Unknown authentication method %s", authMethod)
  110. }
  111. }
  112. conn := server.Dial(clientConfig)
  113. defer conn.Close()
  114. if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
  115. t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
  116. }
  117. if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
  118. t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
  119. }
  120. })
  121. }
  122. }