github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/crypto/ssh/randomized_kex_test.go (about) 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 20 package ssh 21 22 import ( 23 "bytes" 24 "context" 25 "crypto/rand" 26 "crypto/rsa" 27 "net" 28 "testing" 29 30 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 31 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 32 "golang.org/x/sync/errgroup" 33 ) 34 35 func TestRandomizedSSHKEXes(t *testing.T) { 36 37 rsaKey, err := rsa.GenerateKey(rand.Reader, 4096) 38 if err != nil { 39 t.Fatalf("rsa.GenerateKey failed: %s", err) 40 } 41 42 signer, err := NewSignerFromKey(rsaKey) 43 if err != nil { 44 t.Fatalf("NewSignerFromKey failed: %s", err) 45 } 46 47 publicKey := signer.PublicKey() 48 49 username := "username" 50 password := "password" 51 52 for _, doPeerKEXPRNGSeed := range []bool{true, false} { 53 54 failed := false 55 56 for i := 0; i < 1000; i++ { 57 58 clientSeed, err := prng.NewSeed() 59 if err != nil { 60 t.Fatalf("prng.NewSeed failed: %s", err) 61 } 62 63 serverSeed, err := prng.NewSeed() 64 if err != nil { 65 t.Fatalf("prng.NewSeed failed: %s", err) 66 } 67 68 clientConn, serverConn, err := netPipe() 69 if err != nil { 70 t.Fatalf("netPipe failed: %s", err) 71 } 72 73 testGroup, _ := errgroup.WithContext(context.Background()) 74 75 // Client 76 77 testGroup.Go(func() error { 78 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 88 clientConfig := &ClientConfig{ 89 User: username, 90 Auth: []AuthMethod{Password(password)}, 91 HostKeyCallback: certChecker.CheckHostKey, 92 } 93 94 clientConfig.KEXPRNGSeed = clientSeed 95 96 if doPeerKEXPRNGSeed { 97 clientConfig.PeerKEXPRNGSeed = serverSeed 98 } 99 100 clientSSHConn, _, _, err := NewClientConn(clientConn, "", clientConfig) 101 if err != nil { 102 return errors.Trace(err) 103 } 104 105 clientSSHConn.Close() 106 clientConn.Close() 107 return nil 108 }) 109 110 // Server 111 112 testGroup.Go(func() error { 113 114 insecurePasswordCallback := func(c ConnMetadata, pass []byte) (*Permissions, error) { 115 if c.User() == username && string(pass) == password { 116 return nil, nil 117 } 118 return nil, errors.TraceNew("authentication failed") 119 } 120 121 serverConfig := &ServerConfig{ 122 PasswordCallback: insecurePasswordCallback, 123 } 124 serverConfig.AddHostKey(signer) 125 126 serverConfig.KEXPRNGSeed = serverSeed 127 128 serverSSHConn, _, _, err := NewServerConn(serverConn, serverConfig) 129 if err != nil { 130 return errors.Trace(err) 131 } 132 133 serverSSHConn.Close() 134 serverConn.Close() 135 return nil 136 }) 137 138 err = testGroup.Wait() 139 if err != nil { 140 141 // Expect no failure to negotiates when setting PeerKEXPRNGSeed. 142 if doPeerKEXPRNGSeed { 143 t.Fatalf("goroutine failed: %s", err) 144 145 } else { 146 failed = true 147 break 148 } 149 } 150 } 151 152 // Expect at least one failure to negotiate when not setting PeerKEXPRNGSeed. 153 if !doPeerKEXPRNGSeed && !failed { 154 t.Fatalf("unexpected success") 155 } 156 } 157 }