...

Source file src/golang.org/x/crypto/ssh/test/session_test.go

Documentation: golang.org/x/crypto/ssh/test

     1  // Copyright 2012 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  
     5  //go:build !windows && !js && !wasip1
     6  
     7  package test
     8  
     9  // Session functional tests.
    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  	// change the keys.
    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  		// On AIX, sshd cannot acquire /dev/pts/* if launched as
   231  		// a non-root user.
   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  		// On AIX, sshd cannot acquire /dev/pts/* if launched as
   296  		// a non-root user.
   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  	// Don't fail if sshd doesn't have the cipher.
   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  	// Exercise sending data to the server
   361  	if _, _, err := conn.Conn.SendRequest("drop-me", false, make([]byte, numBytes)); err != nil {
   362  		t.Fatalf("SendRequest: %v", err)
   363  	}
   364  
   365  	// Exercise receiving data from the server
   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  			// Don't fail if sshd doesn't have the MAC.
   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  	// Based on the discussion in #17230, the key exchange algorithms
   425  	// diffie-hellman-group-exchange-sha1 and diffie-hellman-group-exchange-sha256
   426  	// are not included in the default list of supported kex so we have to add them
   427  	// here manually.
   428  	kexOrder = append(kexOrder, "diffie-hellman-group-exchange-sha1", "diffie-hellman-group-exchange-sha256")
   429  	// The key exchange algorithms diffie-hellman-group16-sha512 is disabled by
   430  	// default so we add it here manually.
   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  			// Don't fail if sshd doesn't have the kex.
   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