randomized_kex_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. * Copyright (c) 2019, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package ssh
  20. import (
  21. "bytes"
  22. "context"
  23. "crypto/rand"
  24. "crypto/rsa"
  25. "net"
  26. "testing"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  29. "golang.org/x/sync/errgroup"
  30. )
  31. func TestRandomizedSSHKEXes(t *testing.T) {
  32. err := runTestRandomizedSSHKEXes(false)
  33. if err != nil {
  34. t.Errorf("runTestRandomizedSSHKEXes failed: %s", err)
  35. return
  36. }
  37. }
  38. func TestLegacyRandomizedSSHKEXes(t *testing.T) {
  39. err := runTestRandomizedSSHKEXes(true)
  40. if err != nil {
  41. t.Errorf("runTestRandomizedSSHKEXes failed: %s", err)
  42. return
  43. }
  44. }
  45. func runTestRandomizedSSHKEXes(legacyClient bool) error {
  46. rsaKey, err := rsa.GenerateKey(rand.Reader, 4096)
  47. if err != nil {
  48. return errors.Trace(err)
  49. }
  50. signer, err := NewSignerFromKey(rsaKey)
  51. if err != nil {
  52. return errors.Trace(err)
  53. }
  54. publicKey := signer.PublicKey()
  55. username := "username"
  56. password := "password"
  57. testLegacyClient = legacyClient
  58. defer func() {
  59. testLegacyClient = false
  60. }()
  61. for _, doPeerKEXPRNGSeed := range []bool{true, false} {
  62. failed := false
  63. for i := 0; i < 1000; i++ {
  64. clientSeed, err := prng.NewSeed()
  65. if err != nil {
  66. return errors.Trace(err)
  67. }
  68. serverSeed, err := prng.NewSeed()
  69. if err != nil {
  70. return errors.Trace(err)
  71. }
  72. clientConn, serverConn, err := netPipe()
  73. if err != nil {
  74. return errors.Trace(err)
  75. }
  76. testGroup, _ := errgroup.WithContext(context.Background())
  77. // Client
  78. testGroup.Go(func() error {
  79. certChecker := &CertChecker{
  80. HostKeyFallback: func(addr string, remote net.Addr, key PublicKey) error {
  81. if !bytes.Equal(publicKey.Marshal(), key.Marshal()) {
  82. return errors.TraceNew("unexpected host public key")
  83. }
  84. return nil
  85. },
  86. }
  87. clientConfig := &ClientConfig{
  88. User: username,
  89. Auth: []AuthMethod{Password(password)},
  90. HostKeyCallback: certChecker.CheckHostKey,
  91. }
  92. clientConfig.KEXPRNGSeed = clientSeed
  93. if doPeerKEXPRNGSeed {
  94. clientConfig.PeerKEXPRNGSeed = serverSeed
  95. }
  96. clientSSHConn, _, _, err := NewClientConn(clientConn, "", clientConfig)
  97. if err != nil {
  98. return errors.Trace(err)
  99. }
  100. if !legacyClient {
  101. // Ensure weak MAC is not negotiated
  102. for _, p := range []packetCipher{
  103. clientSSHConn.(*connection).transport.conn.(*transport).reader.packetCipher,
  104. clientSSHConn.(*connection).transport.conn.(*transport).writer.packetCipher} {
  105. switch c := p.(type) {
  106. case *gcmCipher, *chacha20Poly1305Cipher:
  107. // No weak MAC.
  108. case *streamPacketCipher:
  109. // The only weak MAC, "hmac-sha1-96", is also the only truncatingMAC.
  110. if _, ok := c.mac.(truncatingMAC); ok {
  111. return errors.TraceNew("weak MAC negotiated")
  112. }
  113. }
  114. }
  115. }
  116. clientSSHConn.Close()
  117. clientConn.Close()
  118. return nil
  119. })
  120. // Server
  121. testGroup.Go(func() error {
  122. insecurePasswordCallback := func(c ConnMetadata, pass []byte) (*Permissions, error) {
  123. if c.User() == username && string(pass) == password {
  124. return nil, nil
  125. }
  126. return nil, errors.TraceNew("authentication failed")
  127. }
  128. serverConfig := &ServerConfig{
  129. PasswordCallback: insecurePasswordCallback,
  130. }
  131. serverConfig.AddHostKey(signer)
  132. serverConfig.KEXPRNGSeed = serverSeed
  133. serverSSHConn, _, _, err := NewServerConn(serverConn, serverConfig)
  134. if err != nil {
  135. return errors.Trace(err)
  136. }
  137. serverSSHConn.Close()
  138. serverConn.Close()
  139. return nil
  140. })
  141. err = testGroup.Wait()
  142. if err != nil {
  143. // Expect no failure to negotiates when setting PeerKEXPRNGSeed.
  144. if doPeerKEXPRNGSeed {
  145. return errors.Tracef("unexpected failure to negotiate: %v", err)
  146. } else {
  147. failed = true
  148. break
  149. }
  150. }
  151. }
  152. // Expect at least one failure to negotiate when not setting PeerKEXPRNGSeed.
  153. if !doPeerKEXPRNGSeed && !failed {
  154. errors.TraceNew("unexpected success")
  155. }
  156. }
  157. return nil
  158. }