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