github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/crypto/ssh/test/session_test.go (about)

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