1
2
3
4
5
6
7 package test
8
9
10
11 import (
12 "bytes"
13 "errors"
14 "fmt"
15 "io"
16 "path/filepath"
17 "regexp"
18 "runtime"
19 "strings"
20 "testing"
21
22 "golang.org/x/crypto/ssh"
23 )
24
25 func skipIfIssue64959(t *testing.T, err error) {
26 if err != nil && runtime.GOOS == "darwin" && strings.Contains(err.Error(), "ssh: unexpected packet in response to channel open: <nil>") {
27 t.Helper()
28 t.Skipf("skipping test broken on some versions of macOS; see https://go.dev/issue/64959")
29 }
30 }
31
32 func TestRunCommandSuccess(t *testing.T) {
33 server := newServer(t)
34 conn := server.Dial(clientConfig())
35 defer conn.Close()
36
37 session, err := conn.NewSession()
38 if err != nil {
39 skipIfIssue64959(t, err)
40 t.Fatalf("session failed: %v", err)
41 }
42 defer session.Close()
43 err = session.Run("true")
44 if err != nil {
45 t.Fatalf("session failed: %v", err)
46 }
47 }
48
49 func TestHostKeyCheck(t *testing.T) {
50 server := newServer(t)
51
52 conf := clientConfig()
53 hostDB := hostKeyDB()
54 conf.HostKeyCallback = hostDB.Check
55
56
57 hostDB.keys[ssh.KeyAlgoRSA][25]++
58 hostDB.keys[ssh.KeyAlgoDSA][25]++
59 hostDB.keys[ssh.KeyAlgoECDSA256][25]++
60
61 conn, err := server.TryDial(conf)
62 if err == nil {
63 conn.Close()
64 t.Fatalf("dial should have failed.")
65 } else if !strings.Contains(err.Error(), "host key mismatch") {
66 t.Fatalf("'host key mismatch' not found in %v", err)
67 }
68 }
69
70 func TestRunCommandStdin(t *testing.T) {
71 server := newServer(t)
72 conn := server.Dial(clientConfig())
73 defer conn.Close()
74
75 session, err := conn.NewSession()
76 if err != nil {
77 skipIfIssue64959(t, err)
78 t.Fatalf("session failed: %v", err)
79 }
80 defer session.Close()
81
82 r, w := io.Pipe()
83 defer r.Close()
84 defer w.Close()
85 session.Stdin = r
86
87 err = session.Run("true")
88 if err != nil {
89 t.Fatalf("session failed: %v", err)
90 }
91 }
92
93 func TestRunCommandStdinError(t *testing.T) {
94 server := newServer(t)
95 conn := server.Dial(clientConfig())
96 defer conn.Close()
97
98 session, err := conn.NewSession()
99 if err != nil {
100 skipIfIssue64959(t, err)
101 t.Fatalf("session failed: %v", err)
102 }
103 defer session.Close()
104
105 r, w := io.Pipe()
106 defer r.Close()
107 session.Stdin = r
108 pipeErr := errors.New("closing write end of pipe")
109 w.CloseWithError(pipeErr)
110
111 err = session.Run("true")
112 if err != pipeErr {
113 t.Fatalf("expected %v, found %v", pipeErr, err)
114 }
115 }
116
117 func TestRunCommandFailed(t *testing.T) {
118 server := newServer(t)
119 conn := server.Dial(clientConfig())
120 defer conn.Close()
121
122 session, err := conn.NewSession()
123 if err != nil {
124 skipIfIssue64959(t, err)
125 t.Fatalf("session failed: %v", err)
126 }
127 defer session.Close()
128 err = session.Run(`bash -c "kill -9 $$"`)
129 if err == nil {
130 t.Fatalf("session succeeded: %v", err)
131 }
132 }
133
134 func TestRunCommandWeClosed(t *testing.T) {
135 server := newServer(t)
136 conn := server.Dial(clientConfig())
137 defer conn.Close()
138
139 session, err := conn.NewSession()
140 if err != nil {
141 skipIfIssue64959(t, err)
142 t.Fatalf("session failed: %v", err)
143 }
144 err = session.Shell()
145 if err != nil {
146 t.Fatalf("shell failed: %v", err)
147 }
148 err = session.Close()
149 if err != nil {
150 t.Fatalf("shell failed: %v", err)
151 }
152 }
153
154 func TestFuncLargeRead(t *testing.T) {
155 server := newServer(t)
156 conn := server.Dial(clientConfig())
157 defer conn.Close()
158
159 session, err := conn.NewSession()
160 if err != nil {
161 skipIfIssue64959(t, err)
162 t.Fatalf("unable to create new session: %s", err)
163 }
164
165 stdout, err := session.StdoutPipe()
166 if err != nil {
167 t.Fatalf("unable to acquire stdout pipe: %s", err)
168 }
169
170 err = session.Start("dd if=/dev/urandom bs=2048 count=1024")
171 if err != nil {
172 t.Fatalf("unable to execute remote command: %s", err)
173 }
174
175 buf := new(bytes.Buffer)
176 n, err := io.Copy(buf, stdout)
177 if err != nil {
178 t.Fatalf("error reading from remote stdout: %s", err)
179 }
180
181 if n != 2048*1024 {
182 t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
183 }
184 }
185
186 func TestKeyChange(t *testing.T) {
187 server := newServer(t)
188 conf := clientConfig()
189 hostDB := hostKeyDB()
190 conf.HostKeyCallback = hostDB.Check
191 conf.RekeyThreshold = 1024
192 conn := server.Dial(conf)
193 defer conn.Close()
194
195 for i := 0; i < 4; i++ {
196 session, err := conn.NewSession()
197 if err != nil {
198 skipIfIssue64959(t, err)
199 t.Fatalf("unable to create new session: %s", err)
200 }
201
202 stdout, err := session.StdoutPipe()
203 if err != nil {
204 t.Fatalf("unable to acquire stdout pipe: %s", err)
205 }
206
207 err = session.Start("dd if=/dev/urandom bs=1024 count=1")
208 if err != nil {
209 t.Fatalf("unable to execute remote command: %s", err)
210 }
211 buf := new(bytes.Buffer)
212 n, err := io.Copy(buf, stdout)
213 if err != nil {
214 t.Fatalf("error reading from remote stdout: %s", err)
215 }
216
217 want := int64(1024)
218 if n != want {
219 t.Fatalf("Expected %d bytes but read only %d from remote command", want, n)
220 }
221 }
222
223 if changes := hostDB.checkCount; changes < 4 {
224 t.Errorf("got %d key changes, want 4", changes)
225 }
226 }
227
228 func TestValidTerminalMode(t *testing.T) {
229 if runtime.GOOS == "aix" {
230
231
232 t.Skipf("skipping on %s", runtime.GOOS)
233 }
234 server := newServer(t)
235 conn := server.Dial(clientConfig())
236 defer conn.Close()
237
238 session, err := conn.NewSession()
239 if err != nil {
240 skipIfIssue64959(t, err)
241 t.Fatalf("session failed: %v", err)
242 }
243 defer session.Close()
244
245 stdout, err := session.StdoutPipe()
246 if err != nil {
247 t.Fatalf("unable to acquire stdout pipe: %s", err)
248 }
249
250 stdin, err := session.StdinPipe()
251 if err != nil {
252 t.Fatalf("unable to acquire stdin pipe: %s", err)
253 }
254
255 tm := ssh.TerminalModes{ssh.ECHO: 0}
256 if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
257 t.Fatalf("req-pty failed: %s", err)
258 }
259
260 err = session.Shell()
261 if err != nil {
262 t.Fatalf("session failed: %s", err)
263 }
264
265 if _, err := io.WriteString(stdin, "echo && echo SHELL $SHELL && stty -a && exit\n"); err != nil {
266 t.Fatal(err)
267 }
268
269 buf := new(strings.Builder)
270 if _, err := io.Copy(buf, stdout); err != nil {
271 t.Fatalf("reading failed: %s", err)
272 }
273
274 if testing.Verbose() {
275 t.Logf("echo && echo SHELL $SHELL && stty -a && exit:\n%s", buf)
276 }
277
278 shellLine := regexp.MustCompile("(?m)^SHELL (.*)$").FindStringSubmatch(buf.String())
279 if len(shellLine) != 2 {
280 t.Fatalf("missing output from echo SHELL $SHELL")
281 }
282 switch shell := filepath.Base(strings.TrimSpace(shellLine[1])); shell {
283 case "sh", "bash":
284 default:
285 t.Skipf("skipping test on non-Bourne shell %q", shell)
286 }
287
288 if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
289 t.Fatal("terminal mode failure: expected -echo in stty output")
290 }
291 }
292
293 func TestWindowChange(t *testing.T) {
294 if runtime.GOOS == "aix" {
295
296
297 t.Skipf("skipping on %s", runtime.GOOS)
298 }
299 server := newServer(t)
300 conn := server.Dial(clientConfig())
301 defer conn.Close()
302
303 session, err := conn.NewSession()
304 if err != nil {
305 skipIfIssue64959(t, err)
306 t.Fatalf("session failed: %v", err)
307 }
308 defer session.Close()
309
310 stdout, err := session.StdoutPipe()
311 if err != nil {
312 t.Fatalf("unable to acquire stdout pipe: %s", err)
313 }
314
315 stdin, err := session.StdinPipe()
316 if err != nil {
317 t.Fatalf("unable to acquire stdin pipe: %s", err)
318 }
319
320 tm := ssh.TerminalModes{ssh.ECHO: 0}
321 if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
322 t.Fatalf("req-pty failed: %s", err)
323 }
324
325 if err := session.WindowChange(100, 100); err != nil {
326 t.Fatalf("window-change failed: %s", err)
327 }
328
329 err = session.Shell()
330 if err != nil {
331 t.Fatalf("session failed: %s", err)
332 }
333
334 stdin.Write([]byte("stty size && exit\n"))
335
336 var buf bytes.Buffer
337 if _, err := io.Copy(&buf, stdout); err != nil {
338 t.Fatalf("reading failed: %s", err)
339 }
340
341 if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "100 100") {
342 t.Fatalf("terminal WindowChange failure: expected \"100 100\" stty output, got %s", sttyOutput)
343 }
344 }
345
346 func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
347 server := newServer(t)
348 conf := clientConfig()
349 conf.Ciphers = []string{cipher}
350
351 conf.Ciphers = append(conf.Ciphers, cipherOrder...)
352 conn, err := server.TryDial(conf)
353 if err != nil {
354 t.Fatalf("TryDial: %v", err)
355 }
356 defer conn.Close()
357
358 numBytes := 4096
359
360
361 if _, _, err := conn.Conn.SendRequest("drop-me", false, make([]byte, numBytes)); err != nil {
362 t.Fatalf("SendRequest: %v", err)
363 }
364
365
366 session, err := conn.NewSession()
367 if err != nil {
368 skipIfIssue64959(t, err)
369 t.Fatalf("NewSession: %v", err)
370 }
371
372 out, err := session.Output(fmt.Sprintf("dd if=/dev/zero bs=%d count=1", numBytes))
373 if err != nil {
374 t.Fatalf("Output: %v", err)
375 }
376
377 if len(out) != numBytes {
378 t.Fatalf("got %d bytes, want %d bytes", len(out), numBytes)
379 }
380 }
381
382 var deprecatedCiphers = []string{
383 "aes128-cbc", "3des-cbc",
384 "arcfour128", "arcfour256",
385 }
386
387 func TestCiphers(t *testing.T) {
388 var config ssh.Config
389 config.SetDefaults()
390 cipherOrder := append(config.Ciphers, deprecatedCiphers...)
391
392 for _, ciph := range cipherOrder {
393 t.Run(ciph, func(t *testing.T) {
394 testOneCipher(t, ciph, cipherOrder)
395 })
396 }
397 }
398
399 func TestMACs(t *testing.T) {
400 var config ssh.Config
401 config.SetDefaults()
402 macOrder := config.MACs
403
404 for _, mac := range macOrder {
405 t.Run(mac, func(t *testing.T) {
406 server := newServer(t)
407 conf := clientConfig()
408 conf.MACs = []string{mac}
409
410 conf.MACs = append(conf.MACs, macOrder...)
411 if conn, err := server.TryDial(conf); err == nil {
412 conn.Close()
413 } else {
414 t.Fatalf("failed for MAC %q", mac)
415 }
416 })
417 }
418 }
419
420 func TestKeyExchanges(t *testing.T) {
421 var config ssh.Config
422 config.SetDefaults()
423 kexOrder := config.KeyExchanges
424
425
426
427
428 kexOrder = append(kexOrder, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
429
430
431 kexOrder = append(kexOrder, "diffie-hellman-group16-sha512")
432 for _, kex := range kexOrder {
433 t.Run(kex, func(t *testing.T) {
434 server := newServer(t)
435 conf := clientConfig()
436
437 conf.KeyExchanges = append([]string{kex}, kexOrder...)
438 conn, err := server.TryDial(conf)
439 if err == nil {
440 conn.Close()
441 } else {
442 t.Errorf("failed for kex %q", kex)
443 }
444 })
445 }
446 }
447
448 func TestClientAuthAlgorithms(t *testing.T) {
449 for _, key := range []string{
450 "rsa",
451 "dsa",
452 "ecdsa",
453 "ed25519",
454 } {
455 t.Run(key, func(t *testing.T) {
456 server := newServer(t)
457 conf := clientConfig()
458 conf.SetDefaults()
459 conf.Auth = []ssh.AuthMethod{
460 ssh.PublicKeys(testSigners[key]),
461 }
462
463 conn, err := server.TryDial(conf)
464 if err == nil {
465 conn.Close()
466 } else {
467 t.Errorf("failed for key %q", key)
468 }
469 })
470 }
471 }
472
View as plain text