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