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
5package ssh
6
7import (
8	"bytes"
9	"crypto/dsa"
10	"crypto/ecdsa"
11	"crypto/elliptic"
12	"crypto/rand"
13	"crypto/rsa"
14	"crypto/x509"
15	"encoding/base64"
16	"fmt"
17	"reflect"
18	"strings"
19	"testing"
20
21	"golang.org/x/crypto/ed25519"
22	"golang.org/x/crypto/ssh/testdata"
23)
24
25func rawKey(pub PublicKey) interface{} {
26	switch k := pub.(type) {
27	case *rsaPublicKey:
28		return (*rsa.PublicKey)(k)
29	case *dsaPublicKey:
30		return (*dsa.PublicKey)(k)
31	case *ecdsaPublicKey:
32		return (*ecdsa.PublicKey)(k)
33	case ed25519PublicKey:
34		return (ed25519.PublicKey)(k)
35	case *Certificate:
36		return k
37	}
38	panic("unknown key type")
39}
40
41func TestKeyMarshalParse(t *testing.T) {
42	for _, priv := range testSigners {
43		pub := priv.PublicKey()
44		roundtrip, err := ParsePublicKey(pub.Marshal())
45		if err != nil {
46			t.Errorf("ParsePublicKey(%T): %v", pub, err)
47		}
48
49		k1 := rawKey(pub)
50		k2 := rawKey(roundtrip)
51
52		if !reflect.DeepEqual(k1, k2) {
53			t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
54		}
55	}
56}
57
58func TestUnsupportedCurves(t *testing.T) {
59	raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
60	if err != nil {
61		t.Fatalf("GenerateKey: %v", err)
62	}
63
64	if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
65		t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
66	}
67
68	if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
69		t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
70	}
71}
72
73func TestNewPublicKey(t *testing.T) {
74	for _, k := range testSigners {
75		raw := rawKey(k.PublicKey())
76		// Skip certificates, as NewPublicKey does not support them.
77		if _, ok := raw.(*Certificate); ok {
78			continue
79		}
80		pub, err := NewPublicKey(raw)
81		if err != nil {
82			t.Errorf("NewPublicKey(%#v): %v", raw, err)
83		}
84		if !reflect.DeepEqual(k.PublicKey(), pub) {
85			t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
86		}
87	}
88}
89
90func TestKeySignVerify(t *testing.T) {
91	for _, priv := range testSigners {
92		pub := priv.PublicKey()
93
94		data := []byte("sign me")
95		sig, err := priv.Sign(rand.Reader, data)
96		if err != nil {
97			t.Fatalf("Sign(%T): %v", priv, err)
98		}
99
100		if err := pub.Verify(data, sig); err != nil {
101			t.Errorf("publicKey.Verify(%T): %v", priv, err)
102		}
103		sig.Blob[5]++
104		if err := pub.Verify(data, sig); err == nil {
105			t.Errorf("publicKey.Verify on broken sig did not fail")
106		}
107	}
108}
109
110func TestParseRSAPrivateKey(t *testing.T) {
111	key := testPrivateKeys["rsa"]
112
113	rsa, ok := key.(*rsa.PrivateKey)
114	if !ok {
115		t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
116	}
117
118	if err := rsa.Validate(); err != nil {
119		t.Errorf("Validate: %v", err)
120	}
121}
122
123func TestParseECPrivateKey(t *testing.T) {
124	key := testPrivateKeys["ecdsa"]
125
126	ecKey, ok := key.(*ecdsa.PrivateKey)
127	if !ok {
128		t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
129	}
130
131	if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
132		t.Fatalf("public key does not validate.")
133	}
134}
135
136// See Issue https://github.com/golang/go/issues/6650.
137func TestParseEncryptedPrivateKeysFails(t *testing.T) {
138	const wantSubstring = "encrypted"
139	for i, tt := range testdata.PEMEncryptedKeys {
140		_, err := ParsePrivateKey(tt.PEMBytes)
141		if err == nil {
142			t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name)
143			continue
144		}
145
146		if !strings.Contains(err.Error(), wantSubstring) {
147			t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring)
148		}
149	}
150}
151
152// Parse encrypted private keys with passphrase
153func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
154	data := []byte("sign me")
155	for _, tt := range testdata.PEMEncryptedKeys {
156		s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
157		if err != nil {
158			t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
159			continue
160		}
161		sig, err := s.Sign(rand.Reader, data)
162		if err != nil {
163			t.Fatalf("dsa.Sign: %v", err)
164		}
165		if err := s.PublicKey().Verify(data, sig); err != nil {
166			t.Errorf("Verify failed: %v", err)
167		}
168	}
169
170	tt := testdata.PEMEncryptedKeys[0]
171	_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
172	if err != x509.IncorrectPasswordError {
173		t.Fatalf("got %v want IncorrectPasswordError", err)
174	}
175}
176
177func TestParseDSA(t *testing.T) {
178	// We actually exercise the ParsePrivateKey codepath here, as opposed to
179	// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
180	// uses.
181	s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
182	if err != nil {
183		t.Fatalf("ParsePrivateKey returned error: %s", err)
184	}
185
186	data := []byte("sign me")
187	sig, err := s.Sign(rand.Reader, data)
188	if err != nil {
189		t.Fatalf("dsa.Sign: %v", err)
190	}
191
192	if err := s.PublicKey().Verify(data, sig); err != nil {
193		t.Errorf("Verify failed: %v", err)
194	}
195}
196
197// Tests for authorized_keys parsing.
198
199// getTestKey returns a public key, and its base64 encoding.
200func getTestKey() (PublicKey, string) {
201	k := testPublicKeys["rsa"]
202
203	b := &bytes.Buffer{}
204	e := base64.NewEncoder(base64.StdEncoding, b)
205	e.Write(k.Marshal())
206	e.Close()
207
208	return k, b.String()
209}
210
211func TestMarshalParsePublicKey(t *testing.T) {
212	pub, pubSerialized := getTestKey()
213	line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
214
215	authKeys := MarshalAuthorizedKey(pub)
216	actualFields := strings.Fields(string(authKeys))
217	if len(actualFields) == 0 {
218		t.Fatalf("failed authKeys: %v", authKeys)
219	}
220
221	// drop the comment
222	expectedFields := strings.Fields(line)[0:2]
223
224	if !reflect.DeepEqual(actualFields, expectedFields) {
225		t.Errorf("got %v, expected %v", actualFields, expectedFields)
226	}
227
228	actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
229	if err != nil {
230		t.Fatalf("cannot parse %v: %v", line, err)
231	}
232	if !reflect.DeepEqual(actPub, pub) {
233		t.Errorf("got %v, expected %v", actPub, pub)
234	}
235}
236
237type testAuthResult struct {
238	pubKey   PublicKey
239	options  []string
240	comments string
241	rest     string
242	ok       bool
243}
244
245func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) {
246	rest := authKeys
247	var values []testAuthResult
248	for len(rest) > 0 {
249		var r testAuthResult
250		var err error
251		r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
252		r.ok = (err == nil)
253		t.Log(err)
254		r.rest = string(rest)
255		values = append(values, r)
256	}
257
258	if !reflect.DeepEqual(values, expected) {
259		t.Errorf("got %#v, expected %#v", values, expected)
260	}
261}
262
263func TestAuthorizedKeyBasic(t *testing.T) {
264	pub, pubSerialized := getTestKey()
265	line := "ssh-rsa " + pubSerialized + " user@host"
266	testAuthorizedKeys(t, []byte(line),
267		[]testAuthResult{
268			{pub, nil, "user@host", "", true},
269		})
270}
271
272func TestAuth(t *testing.T) {
273	pub, pubSerialized := getTestKey()
274	authWithOptions := []string{
275		`# comments to ignore before any keys...`,
276		``,
277		`env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
278		`# comments to ignore, along with a blank line`,
279		``,
280		`env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
281		``,
282		`# more comments, plus a invalid entry`,
283		`ssh-rsa data-that-will-not-parse user@host3`,
284	}
285	for _, eol := range []string{"\n", "\r\n"} {
286		authOptions := strings.Join(authWithOptions, eol)
287		rest2 := strings.Join(authWithOptions[3:], eol)
288		rest3 := strings.Join(authWithOptions[6:], eol)
289		testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{
290			{pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
291			{pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
292			{nil, nil, "", "", false},
293		})
294	}
295}
296
297func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
298	pub, pubSerialized := getTestKey()
299	authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
300	testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{
301		{pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
302	})
303}
304
305func TestAuthWithQuotedCommaInEnv(t *testing.T) {
306	pub, pubSerialized := getTestKey()
307	authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + `   user@host`)
308	testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{
309		{pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
310	})
311}
312
313func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
314	pub, pubSerialized := getTestKey()
315	authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + `   user@host`)
316	authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
317	testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{
318		{pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
319	})
320
321	testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{
322		{pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
323	})
324}
325
326func TestAuthWithInvalidSpace(t *testing.T) {
327	_, pubSerialized := getTestKey()
328	authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
329#more to follow but still no valid keys`)
330	testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{
331		{nil, nil, "", "", false},
332	})
333}
334
335func TestAuthWithMissingQuote(t *testing.T) {
336	pub, pubSerialized := getTestKey()
337	authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
338env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
339
340	testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{
341		{pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
342	})
343}
344
345func TestInvalidEntry(t *testing.T) {
346	authInvalid := []byte(`ssh-rsa`)
347	_, _, _, _, err := ParseAuthorizedKey(authInvalid)
348	if err == nil {
349		t.Errorf("got valid entry for %q", authInvalid)
350	}
351}
352
353var knownHostsParseTests = []struct {
354	input string
355	err   string
356
357	marker  string
358	comment string
359	hosts   []string
360	rest    string
361}{
362	{
363		"",
364		"EOF",
365
366		"", "", nil, "",
367	},
368	{
369		"# Just a comment",
370		"EOF",
371
372		"", "", nil, "",
373	},
374	{
375		"   \t   ",
376		"EOF",
377
378		"", "", nil, "",
379	},
380	{
381		"localhost ssh-rsa {RSAPUB}",
382		"",
383
384		"", "", []string{"localhost"}, "",
385	},
386	{
387		"localhost\tssh-rsa {RSAPUB}",
388		"",
389
390		"", "", []string{"localhost"}, "",
391	},
392	{
393		"localhost\tssh-rsa {RSAPUB}\tcomment comment",
394		"",
395
396		"", "comment comment", []string{"localhost"}, "",
397	},
398	{
399		"localhost\tssh-rsa {RSAPUB}\tcomment comment\n",
400		"",
401
402		"", "comment comment", []string{"localhost"}, "",
403	},
404	{
405		"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n",
406		"",
407
408		"", "comment comment", []string{"localhost"}, "",
409	},
410	{
411		"localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line",
412		"",
413
414		"", "comment comment", []string{"localhost"}, "next line",
415	},
416	{
417		"localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
418		"",
419
420		"", "comment comment", []string{"localhost", "[host2:123]"}, "",
421	},
422	{
423		"@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
424		"",
425
426		"marker", "", []string{"localhost", "[host2:123]"}, "",
427	},
428	{
429		"@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
430		"short read",
431
432		"", "", nil, "",
433	},
434}
435
436func TestKnownHostsParsing(t *testing.T) {
437	rsaPub, rsaPubSerialized := getTestKey()
438
439	for i, test := range knownHostsParseTests {
440		var expectedKey PublicKey
441		const rsaKeyToken = "{RSAPUB}"
442
443		input := test.input
444		if strings.Contains(input, rsaKeyToken) {
445			expectedKey = rsaPub
446			input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
447		}
448
449		marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
450		if err != nil {
451			if len(test.err) == 0 {
452				t.Errorf("#%d: unexpectedly failed with %q", i, err)
453			} else if !strings.Contains(err.Error(), test.err) {
454				t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err)
455			}
456			continue
457		} else if len(test.err) != 0 {
458			t.Errorf("#%d: succeeded but expected error including %q", i, test.err)
459			continue
460		}
461
462		if !reflect.DeepEqual(expectedKey, pubKey) {
463			t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey)
464		}
465
466		if marker != test.marker {
467			t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker)
468		}
469
470		if comment != test.comment {
471			t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment)
472		}
473
474		if !reflect.DeepEqual(test.hosts, hosts) {
475			t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts)
476		}
477
478		if rest := string(rest); rest != test.rest {
479			t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest)
480		}
481	}
482}
483
484func TestFingerprintLegacyMD5(t *testing.T) {
485	pub, _ := getTestKey()
486	fingerprint := FingerprintLegacyMD5(pub)
487	want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa
488	if fingerprint != want {
489		t.Errorf("got fingerprint %q want %q", fingerprint, want)
490	}
491}
492
493func TestFingerprintSHA256(t *testing.T) {
494	pub, _ := getTestKey()
495	fingerprint := FingerprintSHA256(pub)
496	want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa
497	if fingerprint != want {
498		t.Errorf("got fingerprint %q want %q", fingerprint, want)
499	}
500}
501