...

Source file src/golang.org/x/crypto/ssh/client_test.go

Documentation: golang.org/x/crypto/ssh

     1  // Copyright 2014 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  package ssh
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"errors"
    11  	"fmt"
    12  	"net"
    13  	"strings"
    14  	"testing"
    15  )
    16  
    17  func TestClientVersion(t *testing.T) {
    18  	for _, tt := range []struct {
    19  		name      string
    20  		version   string
    21  		multiLine string
    22  		wantErr   bool
    23  	}{
    24  		{
    25  			name:    "default version",
    26  			version: packageVersion,
    27  		},
    28  		{
    29  			name:    "custom version",
    30  			version: "SSH-2.0-CustomClientVersionString",
    31  		},
    32  		{
    33  			name:      "good multi line version",
    34  			version:   packageVersion,
    35  			multiLine: strings.Repeat("ignored\r\n", 20),
    36  		},
    37  		{
    38  			name:      "bad multi line version",
    39  			version:   packageVersion,
    40  			multiLine: "bad multi line version",
    41  			wantErr:   true,
    42  		},
    43  		{
    44  			name:      "long multi line version",
    45  			version:   packageVersion,
    46  			multiLine: strings.Repeat("long multi line version\r\n", 50)[:256],
    47  			wantErr:   true,
    48  		},
    49  	} {
    50  		t.Run(tt.name, func(t *testing.T) {
    51  			c1, c2, err := netPipe()
    52  			if err != nil {
    53  				t.Fatalf("netPipe: %v", err)
    54  			}
    55  			defer c1.Close()
    56  			defer c2.Close()
    57  			go func() {
    58  				if tt.multiLine != "" {
    59  					c1.Write([]byte(tt.multiLine))
    60  				}
    61  				NewClientConn(c1, "", &ClientConfig{
    62  					ClientVersion:   tt.version,
    63  					HostKeyCallback: InsecureIgnoreHostKey(),
    64  				})
    65  				c1.Close()
    66  			}()
    67  			conf := &ServerConfig{NoClientAuth: true}
    68  			conf.AddHostKey(testSigners["rsa"])
    69  			conn, _, _, err := NewServerConn(c2, conf)
    70  			if err == nil == tt.wantErr {
    71  				t.Fatalf("got err %v; wantErr %t", err, tt.wantErr)
    72  			}
    73  			if tt.wantErr {
    74  				// Don't verify the version on an expected error.
    75  				return
    76  			}
    77  			if got := string(conn.ClientVersion()); got != tt.version {
    78  				t.Fatalf("got %q; want %q", got, tt.version)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestHostKeyCheck(t *testing.T) {
    85  	for _, tt := range []struct {
    86  		name      string
    87  		wantError string
    88  		key       PublicKey
    89  	}{
    90  		{"no callback", "must specify HostKeyCallback", nil},
    91  		{"correct key", "", testSigners["rsa"].PublicKey()},
    92  		{"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
    93  	} {
    94  		c1, c2, err := netPipe()
    95  		if err != nil {
    96  			t.Fatalf("netPipe: %v", err)
    97  		}
    98  		defer c1.Close()
    99  		defer c2.Close()
   100  		serverConf := &ServerConfig{
   101  			NoClientAuth: true,
   102  		}
   103  		serverConf.AddHostKey(testSigners["rsa"])
   104  
   105  		go NewServerConn(c1, serverConf)
   106  		clientConf := ClientConfig{
   107  			User: "user",
   108  		}
   109  		if tt.key != nil {
   110  			clientConf.HostKeyCallback = FixedHostKey(tt.key)
   111  		}
   112  
   113  		_, _, _, err = NewClientConn(c2, "", &clientConf)
   114  		if err != nil {
   115  			if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
   116  				t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
   117  			}
   118  		} else if tt.wantError != "" {
   119  			t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
   120  		}
   121  	}
   122  }
   123  
   124  func TestVerifyHostKeySignature(t *testing.T) {
   125  	for _, tt := range []struct {
   126  		key        string
   127  		signAlgo   string
   128  		verifyAlgo string
   129  		wantError  string
   130  	}{
   131  		{"rsa", KeyAlgoRSA, KeyAlgoRSA, ""},
   132  		{"rsa", KeyAlgoRSASHA256, KeyAlgoRSASHA256, ""},
   133  		{"rsa", KeyAlgoRSA, KeyAlgoRSASHA512, `ssh: invalid signature algorithm "ssh-rsa", expected "rsa-sha2-512"`},
   134  		{"ed25519", KeyAlgoED25519, KeyAlgoED25519, ""},
   135  	} {
   136  		key := testSigners[tt.key].PublicKey()
   137  		s, ok := testSigners[tt.key].(AlgorithmSigner)
   138  		if !ok {
   139  			t.Fatalf("needed an AlgorithmSigner")
   140  		}
   141  		sig, err := s.SignWithAlgorithm(rand.Reader, []byte("test"), tt.signAlgo)
   142  		if err != nil {
   143  			t.Fatalf("couldn't sign: %q", err)
   144  		}
   145  
   146  		b := bytes.Buffer{}
   147  		writeString(&b, []byte(sig.Format))
   148  		writeString(&b, sig.Blob)
   149  
   150  		result := kexResult{Signature: b.Bytes(), H: []byte("test")}
   151  
   152  		err = verifyHostKeySignature(key, tt.verifyAlgo, &result)
   153  		if err != nil {
   154  			if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
   155  				t.Errorf("got error %q, expecting %q", err.Error(), tt.wantError)
   156  			}
   157  		} else if tt.wantError != "" {
   158  			t.Errorf("succeeded, but want error string %q", tt.wantError)
   159  		}
   160  	}
   161  }
   162  
   163  func TestBannerCallback(t *testing.T) {
   164  	c1, c2, err := netPipe()
   165  	if err != nil {
   166  		t.Fatalf("netPipe: %v", err)
   167  	}
   168  	defer c1.Close()
   169  	defer c2.Close()
   170  
   171  	serverConf := &ServerConfig{
   172  		PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
   173  			return &Permissions{}, nil
   174  		},
   175  		BannerCallback: func(conn ConnMetadata) string {
   176  			return "Hello World"
   177  		},
   178  	}
   179  	serverConf.AddHostKey(testSigners["rsa"])
   180  	go NewServerConn(c1, serverConf)
   181  
   182  	var receivedBanner string
   183  	var bannerCount int
   184  	clientConf := ClientConfig{
   185  		Auth: []AuthMethod{
   186  			Password("123"),
   187  		},
   188  		User:            "user",
   189  		HostKeyCallback: InsecureIgnoreHostKey(),
   190  		BannerCallback: func(message string) error {
   191  			bannerCount++
   192  			receivedBanner = message
   193  			return nil
   194  		},
   195  	}
   196  
   197  	_, _, _, err = NewClientConn(c2, "", &clientConf)
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	if bannerCount != 1 {
   203  		t.Errorf("got %d banners; want 1", bannerCount)
   204  	}
   205  
   206  	expected := "Hello World"
   207  	if receivedBanner != expected {
   208  		t.Fatalf("got %s; want %s", receivedBanner, expected)
   209  	}
   210  }
   211  
   212  func TestNewClientConn(t *testing.T) {
   213  	errHostKeyMismatch := errors.New("host key mismatch")
   214  
   215  	for _, tt := range []struct {
   216  		name                    string
   217  		user                    string
   218  		simulateHostKeyMismatch HostKeyCallback
   219  	}{
   220  		{
   221  			name: "good user field for ConnMetadata",
   222  			user: "testuser",
   223  		},
   224  		{
   225  			name: "empty user field for ConnMetadata",
   226  			user: "",
   227  		},
   228  		{
   229  			name: "host key mismatch",
   230  			user: "testuser",
   231  			simulateHostKeyMismatch: func(hostname string, remote net.Addr, key PublicKey) error {
   232  				return fmt.Errorf("%w: %s", errHostKeyMismatch, bytes.TrimSpace(MarshalAuthorizedKey(key)))
   233  			},
   234  		},
   235  	} {
   236  		t.Run(tt.name, func(t *testing.T) {
   237  			c1, c2, err := netPipe()
   238  			if err != nil {
   239  				t.Fatalf("netPipe: %v", err)
   240  			}
   241  			defer c1.Close()
   242  			defer c2.Close()
   243  
   244  			serverConf := &ServerConfig{
   245  				PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
   246  					return &Permissions{}, nil
   247  				},
   248  			}
   249  			serverConf.AddHostKey(testSigners["rsa"])
   250  			go NewServerConn(c1, serverConf)
   251  
   252  			clientConf := &ClientConfig{
   253  				User: tt.user,
   254  				Auth: []AuthMethod{
   255  					Password("testpw"),
   256  				},
   257  				HostKeyCallback: InsecureIgnoreHostKey(),
   258  			}
   259  
   260  			if tt.simulateHostKeyMismatch != nil {
   261  				clientConf.HostKeyCallback = tt.simulateHostKeyMismatch
   262  			}
   263  
   264  			clientConn, _, _, err := NewClientConn(c2, "", clientConf)
   265  			if err != nil {
   266  				if tt.simulateHostKeyMismatch != nil && errors.Is(err, errHostKeyMismatch) {
   267  					return
   268  				}
   269  				t.Fatal(err)
   270  			}
   271  
   272  			if userGot := clientConn.User(); userGot != tt.user {
   273  				t.Errorf("got user %q; want user %q", userGot, tt.user)
   274  			}
   275  		})
   276  	}
   277  }
   278  
   279  func TestUnsupportedAlgorithm(t *testing.T) {
   280  	for _, tt := range []struct {
   281  		name      string
   282  		config    Config
   283  		wantError string
   284  	}{
   285  		{
   286  			"unsupported KEX",
   287  			Config{
   288  				KeyExchanges: []string{"unsupported"},
   289  			},
   290  			"no common algorithm",
   291  		},
   292  		{
   293  			"unsupported and supported KEXs",
   294  			Config{
   295  				KeyExchanges: []string{"unsupported", kexAlgoCurve25519SHA256},
   296  			},
   297  			"",
   298  		},
   299  		{
   300  			"unsupported cipher",
   301  			Config{
   302  				Ciphers: []string{"unsupported"},
   303  			},
   304  			"no common algorithm",
   305  		},
   306  		{
   307  			"unsupported and supported ciphers",
   308  			Config{
   309  				Ciphers: []string{"unsupported", chacha20Poly1305ID},
   310  			},
   311  			"",
   312  		},
   313  		{
   314  			"unsupported MAC",
   315  			Config{
   316  				MACs: []string{"unsupported"},
   317  				// MAC is used for non AAED ciphers.
   318  				Ciphers: []string{"aes256-ctr"},
   319  			},
   320  			"no common algorithm",
   321  		},
   322  		{
   323  			"unsupported and supported MACs",
   324  			Config{
   325  				MACs: []string{"unsupported", "hmac-sha2-256-etm@openssh.com"},
   326  				// MAC is used for non AAED ciphers.
   327  				Ciphers: []string{"aes256-ctr"},
   328  			},
   329  			"",
   330  		},
   331  	} {
   332  		t.Run(tt.name, func(t *testing.T) {
   333  			c1, c2, err := netPipe()
   334  			if err != nil {
   335  				t.Fatalf("netPipe: %v", err)
   336  			}
   337  			defer c1.Close()
   338  			defer c2.Close()
   339  
   340  			serverConf := &ServerConfig{
   341  				Config: tt.config,
   342  				PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
   343  					return &Permissions{}, nil
   344  				},
   345  			}
   346  			serverConf.AddHostKey(testSigners["rsa"])
   347  			go NewServerConn(c1, serverConf)
   348  
   349  			clientConf := &ClientConfig{
   350  				User:   "testuser",
   351  				Config: tt.config,
   352  				Auth: []AuthMethod{
   353  					Password("testpw"),
   354  				},
   355  				HostKeyCallback: InsecureIgnoreHostKey(),
   356  			}
   357  			_, _, _, err = NewClientConn(c2, "", clientConf)
   358  			if err != nil {
   359  				if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
   360  					t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
   361  				}
   362  			} else if tt.wantError != "" {
   363  				t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
   364  			}
   365  		})
   366  	}
   367  }
   368  

View as plain text