1// Copyright 2011 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
5package ssh
6
7// Session tests.
8
9import (
10	"bytes"
11	crypto_rand "crypto/rand"
12	"errors"
13	"io"
14	"io/ioutil"
15	"math/rand"
16	"net"
17	"testing"
18
19	"golang.org/x/crypto/ssh/terminal"
20)
21
22type serverType func(Channel, <-chan *Request, *testing.T)
23
24// dial constructs a new test server and returns a *ClientConn.
25func dial(handler serverType, t *testing.T) *Client {
26	c1, c2, err := netPipe()
27	if err != nil {
28		t.Fatalf("netPipe: %v", err)
29	}
30
31	go func() {
32		defer c1.Close()
33		conf := ServerConfig{
34			NoClientAuth: true,
35		}
36		conf.AddHostKey(testSigners["rsa"])
37
38		_, chans, reqs, err := NewServerConn(c1, &conf)
39		if err != nil {
40			t.Fatalf("Unable to handshake: %v", err)
41		}
42		go DiscardRequests(reqs)
43
44		for newCh := range chans {
45			if newCh.ChannelType() != "session" {
46				newCh.Reject(UnknownChannelType, "unknown channel type")
47				continue
48			}
49
50			ch, inReqs, err := newCh.Accept()
51			if err != nil {
52				t.Errorf("Accept: %v", err)
53				continue
54			}
55			go func() {
56				handler(ch, inReqs, t)
57			}()
58		}
59	}()
60
61	config := &ClientConfig{
62		User:            "testuser",
63		HostKeyCallback: InsecureIgnoreHostKey(),
64	}
65
66	conn, chans, reqs, err := NewClientConn(c2, "", config)
67	if err != nil {
68		t.Fatalf("unable to dial remote side: %v", err)
69	}
70
71	return NewClient(conn, chans, reqs)
72}
73
74// Test a simple string is returned to session.Stdout.
75func TestSessionShell(t *testing.T) {
76	conn := dial(shellHandler, t)
77	defer conn.Close()
78	session, err := conn.NewSession()
79	if err != nil {
80		t.Fatalf("Unable to request new session: %v", err)
81	}
82	defer session.Close()
83	stdout := new(bytes.Buffer)
84	session.Stdout = stdout
85	if err := session.Shell(); err != nil {
86		t.Fatalf("Unable to execute command: %s", err)
87	}
88	if err := session.Wait(); err != nil {
89		t.Fatalf("Remote command did not exit cleanly: %v", err)
90	}
91	actual := stdout.String()
92	if actual != "golang" {
93		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
94	}
95}
96
97// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
98
99// Test a simple string is returned via StdoutPipe.
100func TestSessionStdoutPipe(t *testing.T) {
101	conn := dial(shellHandler, t)
102	defer conn.Close()
103	session, err := conn.NewSession()
104	if err != nil {
105		t.Fatalf("Unable to request new session: %v", err)
106	}
107	defer session.Close()
108	stdout, err := session.StdoutPipe()
109	if err != nil {
110		t.Fatalf("Unable to request StdoutPipe(): %v", err)
111	}
112	var buf bytes.Buffer
113	if err := session.Shell(); err != nil {
114		t.Fatalf("Unable to execute command: %v", err)
115	}
116	done := make(chan bool, 1)
117	go func() {
118		if _, err := io.Copy(&buf, stdout); err != nil {
119			t.Errorf("Copy of stdout failed: %v", err)
120		}
121		done <- true
122	}()
123	if err := session.Wait(); err != nil {
124		t.Fatalf("Remote command did not exit cleanly: %v", err)
125	}
126	<-done
127	actual := buf.String()
128	if actual != "golang" {
129		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
130	}
131}
132
133// Test that a simple string is returned via the Output helper,
134// and that stderr is discarded.
135func TestSessionOutput(t *testing.T) {
136	conn := dial(fixedOutputHandler, t)
137	defer conn.Close()
138	session, err := conn.NewSession()
139	if err != nil {
140		t.Fatalf("Unable to request new session: %v", err)
141	}
142	defer session.Close()
143
144	buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
145	if err != nil {
146		t.Error("Remote command did not exit cleanly:", err)
147	}
148	w := "this-is-stdout."
149	g := string(buf)
150	if g != w {
151		t.Error("Remote command did not return expected string:")
152		t.Logf("want %q", w)
153		t.Logf("got  %q", g)
154	}
155}
156
157// Test that both stdout and stderr are returned
158// via the CombinedOutput helper.
159func TestSessionCombinedOutput(t *testing.T) {
160	conn := dial(fixedOutputHandler, t)
161	defer conn.Close()
162	session, err := conn.NewSession()
163	if err != nil {
164		t.Fatalf("Unable to request new session: %v", err)
165	}
166	defer session.Close()
167
168	buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
169	if err != nil {
170		t.Error("Remote command did not exit cleanly:", err)
171	}
172	const stdout = "this-is-stdout."
173	const stderr = "this-is-stderr."
174	g := string(buf)
175	if g != stdout+stderr && g != stderr+stdout {
176		t.Error("Remote command did not return expected string:")
177		t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
178		t.Logf("got  %q", g)
179	}
180}
181
182// Test non-0 exit status is returned correctly.
183func TestExitStatusNonZero(t *testing.T) {
184	conn := dial(exitStatusNonZeroHandler, t)
185	defer conn.Close()
186	session, err := conn.NewSession()
187	if err != nil {
188		t.Fatalf("Unable to request new session: %v", err)
189	}
190	defer session.Close()
191	if err := session.Shell(); err != nil {
192		t.Fatalf("Unable to execute command: %v", err)
193	}
194	err = session.Wait()
195	if err == nil {
196		t.Fatalf("expected command to fail but it didn't")
197	}
198	e, ok := err.(*ExitError)
199	if !ok {
200		t.Fatalf("expected *ExitError but got %T", err)
201	}
202	if e.ExitStatus() != 15 {
203		t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
204	}
205}
206
207// Test 0 exit status is returned correctly.
208func TestExitStatusZero(t *testing.T) {
209	conn := dial(exitStatusZeroHandler, t)
210	defer conn.Close()
211	session, err := conn.NewSession()
212	if err != nil {
213		t.Fatalf("Unable to request new session: %v", err)
214	}
215	defer session.Close()
216
217	if err := session.Shell(); err != nil {
218		t.Fatalf("Unable to execute command: %v", err)
219	}
220	err = session.Wait()
221	if err != nil {
222		t.Fatalf("expected nil but got %v", err)
223	}
224}
225
226// Test exit signal and status are both returned correctly.
227func TestExitSignalAndStatus(t *testing.T) {
228	conn := dial(exitSignalAndStatusHandler, t)
229	defer conn.Close()
230	session, err := conn.NewSession()
231	if err != nil {
232		t.Fatalf("Unable to request new session: %v", err)
233	}
234	defer session.Close()
235	if err := session.Shell(); err != nil {
236		t.Fatalf("Unable to execute command: %v", err)
237	}
238	err = session.Wait()
239	if err == nil {
240		t.Fatalf("expected command to fail but it didn't")
241	}
242	e, ok := err.(*ExitError)
243	if !ok {
244		t.Fatalf("expected *ExitError but got %T", err)
245	}
246	if e.Signal() != "TERM" || e.ExitStatus() != 15 {
247		t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
248	}
249}
250
251// Test exit signal and status are both returned correctly.
252func TestKnownExitSignalOnly(t *testing.T) {
253	conn := dial(exitSignalHandler, t)
254	defer conn.Close()
255	session, err := conn.NewSession()
256	if err != nil {
257		t.Fatalf("Unable to request new session: %v", err)
258	}
259	defer session.Close()
260	if err := session.Shell(); err != nil {
261		t.Fatalf("Unable to execute command: %v", err)
262	}
263	err = session.Wait()
264	if err == nil {
265		t.Fatalf("expected command to fail but it didn't")
266	}
267	e, ok := err.(*ExitError)
268	if !ok {
269		t.Fatalf("expected *ExitError but got %T", err)
270	}
271	if e.Signal() != "TERM" || e.ExitStatus() != 143 {
272		t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
273	}
274}
275
276// Test exit signal and status are both returned correctly.
277func TestUnknownExitSignal(t *testing.T) {
278	conn := dial(exitSignalUnknownHandler, t)
279	defer conn.Close()
280	session, err := conn.NewSession()
281	if err != nil {
282		t.Fatalf("Unable to request new session: %v", err)
283	}
284	defer session.Close()
285	if err := session.Shell(); err != nil {
286		t.Fatalf("Unable to execute command: %v", err)
287	}
288	err = session.Wait()
289	if err == nil {
290		t.Fatalf("expected command to fail but it didn't")
291	}
292	e, ok := err.(*ExitError)
293	if !ok {
294		t.Fatalf("expected *ExitError but got %T", err)
295	}
296	if e.Signal() != "SYS" || e.ExitStatus() != 128 {
297		t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
298	}
299}
300
301func TestExitWithoutStatusOrSignal(t *testing.T) {
302	conn := dial(exitWithoutSignalOrStatus, t)
303	defer conn.Close()
304	session, err := conn.NewSession()
305	if err != nil {
306		t.Fatalf("Unable to request new session: %v", err)
307	}
308	defer session.Close()
309	if err := session.Shell(); err != nil {
310		t.Fatalf("Unable to execute command: %v", err)
311	}
312	err = session.Wait()
313	if err == nil {
314		t.Fatalf("expected command to fail but it didn't")
315	}
316	if _, ok := err.(*ExitMissingError); !ok {
317		t.Fatalf("got %T want *ExitMissingError", err)
318	}
319}
320
321// windowTestBytes is the number of bytes that we'll send to the SSH server.
322const windowTestBytes = 16000 * 200
323
324// TestServerWindow writes random data to the server. The server is expected to echo
325// the same data back, which is compared against the original.
326func TestServerWindow(t *testing.T) {
327	origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
328	io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
329	origBytes := origBuf.Bytes()
330
331	conn := dial(echoHandler, t)
332	defer conn.Close()
333	session, err := conn.NewSession()
334	if err != nil {
335		t.Fatal(err)
336	}
337	defer session.Close()
338	result := make(chan []byte)
339
340	go func() {
341		defer close(result)
342		echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
343		serverStdout, err := session.StdoutPipe()
344		if err != nil {
345			t.Errorf("StdoutPipe failed: %v", err)
346			return
347		}
348		n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
349		if err != nil && err != io.EOF {
350			t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
351		}
352		result <- echoedBuf.Bytes()
353	}()
354
355	serverStdin, err := session.StdinPipe()
356	if err != nil {
357		t.Fatalf("StdinPipe failed: %v", err)
358	}
359	written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
360	if err != nil {
361		t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
362	}
363	if written != windowTestBytes {
364		t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
365	}
366
367	echoedBytes := <-result
368
369	if !bytes.Equal(origBytes, echoedBytes) {
370		t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
371	}
372}
373
374// Verify the client can handle a keepalive packet from the server.
375func TestClientHandlesKeepalives(t *testing.T) {
376	conn := dial(channelKeepaliveSender, t)
377	defer conn.Close()
378	session, err := conn.NewSession()
379	if err != nil {
380		t.Fatal(err)
381	}
382	defer session.Close()
383	if err := session.Shell(); err != nil {
384		t.Fatalf("Unable to execute command: %v", err)
385	}
386	err = session.Wait()
387	if err != nil {
388		t.Fatalf("expected nil but got: %v", err)
389	}
390}
391
392type exitStatusMsg struct {
393	Status uint32
394}
395
396type exitSignalMsg struct {
397	Signal     string
398	CoreDumped bool
399	Errmsg     string
400	Lang       string
401}
402
403func handleTerminalRequests(in <-chan *Request) {
404	for req := range in {
405		ok := false
406		switch req.Type {
407		case "shell":
408			ok = true
409			if len(req.Payload) > 0 {
410				// We don't accept any commands, only the default shell.
411				ok = false
412			}
413		case "env":
414			ok = true
415		}
416		req.Reply(ok, nil)
417	}
418}
419
420func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
421	term := terminal.NewTerminal(ch, prompt)
422	go handleTerminalRequests(in)
423	return term
424}
425
426func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
427	defer ch.Close()
428	// this string is returned to stdout
429	shell := newServerShell(ch, in, "> ")
430	readLine(shell, t)
431	sendStatus(0, ch, t)
432}
433
434func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
435	defer ch.Close()
436	shell := newServerShell(ch, in, "> ")
437	readLine(shell, t)
438	sendStatus(15, ch, t)
439}
440
441func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
442	defer ch.Close()
443	shell := newServerShell(ch, in, "> ")
444	readLine(shell, t)
445	sendStatus(15, ch, t)
446	sendSignal("TERM", ch, t)
447}
448
449func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
450	defer ch.Close()
451	shell := newServerShell(ch, in, "> ")
452	readLine(shell, t)
453	sendSignal("TERM", ch, t)
454}
455
456func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
457	defer ch.Close()
458	shell := newServerShell(ch, in, "> ")
459	readLine(shell, t)
460	sendSignal("SYS", ch, t)
461}
462
463func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
464	defer ch.Close()
465	shell := newServerShell(ch, in, "> ")
466	readLine(shell, t)
467}
468
469func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
470	defer ch.Close()
471	// this string is returned to stdout
472	shell := newServerShell(ch, in, "golang")
473	readLine(shell, t)
474	sendStatus(0, ch, t)
475}
476
477// Ignores the command, writes fixed strings to stderr and stdout.
478// Strings are "this-is-stdout." and "this-is-stderr.".
479func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
480	defer ch.Close()
481	_, err := ch.Read(nil)
482
483	req, ok := <-in
484	if !ok {
485		t.Fatalf("error: expected channel request, got: %#v", err)
486		return
487	}
488
489	// ignore request, always send some text
490	req.Reply(true, nil)
491
492	_, err = io.WriteString(ch, "this-is-stdout.")
493	if err != nil {
494		t.Fatalf("error writing on server: %v", err)
495	}
496	_, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
497	if err != nil {
498		t.Fatalf("error writing on server: %v", err)
499	}
500	sendStatus(0, ch, t)
501}
502
503func readLine(shell *terminal.Terminal, t *testing.T) {
504	if _, err := shell.ReadLine(); err != nil && err != io.EOF {
505		t.Errorf("unable to read line: %v", err)
506	}
507}
508
509func sendStatus(status uint32, ch Channel, t *testing.T) {
510	msg := exitStatusMsg{
511		Status: status,
512	}
513	if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
514		t.Errorf("unable to send status: %v", err)
515	}
516}
517
518func sendSignal(signal string, ch Channel, t *testing.T) {
519	sig := exitSignalMsg{
520		Signal:     signal,
521		CoreDumped: false,
522		Errmsg:     "Process terminated",
523		Lang:       "en-GB-oed",
524	}
525	if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
526		t.Errorf("unable to send signal: %v", err)
527	}
528}
529
530func discardHandler(ch Channel, t *testing.T) {
531	defer ch.Close()
532	io.Copy(ioutil.Discard, ch)
533}
534
535func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
536	defer ch.Close()
537	if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
538		t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
539	}
540}
541
542// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
543// buffer size to exercise more code paths.
544func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
545	var (
546		buf       = make([]byte, 32*1024)
547		written   int
548		remaining = n
549	)
550	for remaining > 0 {
551		l := rand.Intn(1 << 15)
552		if remaining < l {
553			l = remaining
554		}
555		nr, er := src.Read(buf[:l])
556		nw, ew := dst.Write(buf[:nr])
557		remaining -= nw
558		written += nw
559		if ew != nil {
560			return written, ew
561		}
562		if nr != nw {
563			return written, io.ErrShortWrite
564		}
565		if er != nil && er != io.EOF {
566			return written, er
567		}
568	}
569	return written, nil
570}
571
572func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
573	defer ch.Close()
574	shell := newServerShell(ch, in, "> ")
575	readLine(shell, t)
576	if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
577		t.Errorf("unable to send channel keepalive request: %v", err)
578	}
579	sendStatus(0, ch, t)
580}
581
582func TestClientWriteEOF(t *testing.T) {
583	conn := dial(simpleEchoHandler, t)
584	defer conn.Close()
585
586	session, err := conn.NewSession()
587	if err != nil {
588		t.Fatal(err)
589	}
590	defer session.Close()
591	stdin, err := session.StdinPipe()
592	if err != nil {
593		t.Fatalf("StdinPipe failed: %v", err)
594	}
595	stdout, err := session.StdoutPipe()
596	if err != nil {
597		t.Fatalf("StdoutPipe failed: %v", err)
598	}
599
600	data := []byte(`0000`)
601	_, err = stdin.Write(data)
602	if err != nil {
603		t.Fatalf("Write failed: %v", err)
604	}
605	stdin.Close()
606
607	res, err := ioutil.ReadAll(stdout)
608	if err != nil {
609		t.Fatalf("Read failed: %v", err)
610	}
611
612	if !bytes.Equal(data, res) {
613		t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
614	}
615}
616
617func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
618	defer ch.Close()
619	data, err := ioutil.ReadAll(ch)
620	if err != nil {
621		t.Errorf("handler read error: %v", err)
622	}
623	_, err = ch.Write(data)
624	if err != nil {
625		t.Errorf("handler write error: %v", err)
626	}
627}
628
629func TestSessionID(t *testing.T) {
630	c1, c2, err := netPipe()
631	if err != nil {
632		t.Fatalf("netPipe: %v", err)
633	}
634	defer c1.Close()
635	defer c2.Close()
636
637	serverID := make(chan []byte, 1)
638	clientID := make(chan []byte, 1)
639
640	serverConf := &ServerConfig{
641		NoClientAuth: true,
642	}
643	serverConf.AddHostKey(testSigners["ecdsa"])
644	clientConf := &ClientConfig{
645		HostKeyCallback: InsecureIgnoreHostKey(),
646		User:            "user",
647	}
648
649	go func() {
650		conn, chans, reqs, err := NewServerConn(c1, serverConf)
651		if err != nil {
652			t.Fatalf("server handshake: %v", err)
653		}
654		serverID <- conn.SessionID()
655		go DiscardRequests(reqs)
656		for ch := range chans {
657			ch.Reject(Prohibited, "")
658		}
659	}()
660
661	go func() {
662		conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
663		if err != nil {
664			t.Fatalf("client handshake: %v", err)
665		}
666		clientID <- conn.SessionID()
667		go DiscardRequests(reqs)
668		for ch := range chans {
669			ch.Reject(Prohibited, "")
670		}
671	}()
672
673	s := <-serverID
674	c := <-clientID
675	if bytes.Compare(s, c) != 0 {
676		t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
677	} else if len(s) == 0 {
678		t.Errorf("client and server SessionID were empty.")
679	}
680}
681
682type noReadConn struct {
683	readSeen bool
684	net.Conn
685}
686
687func (c *noReadConn) Close() error {
688	return nil
689}
690
691func (c *noReadConn) Read(b []byte) (int, error) {
692	c.readSeen = true
693	return 0, errors.New("noReadConn error")
694}
695
696func TestInvalidServerConfiguration(t *testing.T) {
697	c1, c2, err := netPipe()
698	if err != nil {
699		t.Fatalf("netPipe: %v", err)
700	}
701	defer c1.Close()
702	defer c2.Close()
703
704	serveConn := noReadConn{Conn: c1}
705	serverConf := &ServerConfig{}
706
707	NewServerConn(&serveConn, serverConf)
708	if serveConn.readSeen {
709		t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
710	}
711
712	serverConf.AddHostKey(testSigners["ecdsa"])
713
714	NewServerConn(&serveConn, serverConf)
715	if serveConn.readSeen {
716		t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
717	}
718}
719
720func TestHostKeyAlgorithms(t *testing.T) {
721	serverConf := &ServerConfig{
722		NoClientAuth: true,
723	}
724	serverConf.AddHostKey(testSigners["rsa"])
725	serverConf.AddHostKey(testSigners["ecdsa"])
726
727	connect := func(clientConf *ClientConfig, want string) {
728		var alg string
729		clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
730			alg = key.Type()
731			return nil
732		}
733		c1, c2, err := netPipe()
734		if err != nil {
735			t.Fatalf("netPipe: %v", err)
736		}
737		defer c1.Close()
738		defer c2.Close()
739
740		go NewServerConn(c1, serverConf)
741		_, _, _, err = NewClientConn(c2, "", clientConf)
742		if err != nil {
743			t.Fatalf("NewClientConn: %v", err)
744		}
745		if alg != want {
746			t.Errorf("selected key algorithm %s, want %s", alg, want)
747		}
748	}
749
750	// By default, we get the preferred algorithm, which is ECDSA 256.
751
752	clientConf := &ClientConfig{
753		HostKeyCallback: InsecureIgnoreHostKey(),
754	}
755	connect(clientConf, KeyAlgoECDSA256)
756
757	// Client asks for RSA explicitly.
758	clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
759	connect(clientConf, KeyAlgoRSA)
760
761	c1, c2, err := netPipe()
762	if err != nil {
763		t.Fatalf("netPipe: %v", err)
764	}
765	defer c1.Close()
766	defer c2.Close()
767
768	go NewServerConn(c1, serverConf)
769	clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
770	_, _, _, err = NewClientConn(c2, "", clientConf)
771	if err == nil {
772		t.Fatal("succeeded connecting with unknown hostkey algorithm")
773	}
774}
775