1
2
3
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
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
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
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