123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- // Copyright 2014 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package ssh
- import (
- "bytes"
- "crypto/dsa"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "encoding/base64"
- "encoding/pem"
- "fmt"
- "io"
- "reflect"
- "strings"
- "testing"
- "golang.org/x/crypto/ed25519"
- "golang.org/x/crypto/ssh/testdata"
- )
- func rawKey(pub PublicKey) interface{} {
- switch k := pub.(type) {
- case *rsaPublicKey:
- return (*rsa.PublicKey)(k)
- case *dsaPublicKey:
- return (*dsa.PublicKey)(k)
- case *ecdsaPublicKey:
- return (*ecdsa.PublicKey)(k)
- case ed25519PublicKey:
- return (ed25519.PublicKey)(k)
- case *Certificate:
- return k
- }
- panic("unknown key type")
- }
- func TestKeyMarshalParse(t *testing.T) {
- for _, priv := range testSigners {
- pub := priv.PublicKey()
- roundtrip, err := ParsePublicKey(pub.Marshal())
- if err != nil {
- t.Errorf("ParsePublicKey(%T): %v", pub, err)
- }
- k1 := rawKey(pub)
- k2 := rawKey(roundtrip)
- if !reflect.DeepEqual(k1, k2) {
- t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
- }
- }
- }
- func TestUnsupportedCurves(t *testing.T) {
- raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
- if err != nil {
- t.Fatalf("GenerateKey: %v", err)
- }
- if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
- t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
- }
- if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
- t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
- }
- }
- func TestNewPublicKey(t *testing.T) {
- for _, k := range testSigners {
- raw := rawKey(k.PublicKey())
- // Skip certificates, as NewPublicKey does not support them.
- if _, ok := raw.(*Certificate); ok {
- continue
- }
- pub, err := NewPublicKey(raw)
- if err != nil {
- t.Errorf("NewPublicKey(%#v): %v", raw, err)
- }
- if !reflect.DeepEqual(k.PublicKey(), pub) {
- t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
- }
- }
- }
- func TestKeySignVerify(t *testing.T) {
- for _, priv := range testSigners {
- pub := priv.PublicKey()
- data := []byte("sign me")
- sig, err := priv.Sign(rand.Reader, data)
- if err != nil {
- t.Fatalf("Sign(%T): %v", priv, err)
- }
- if err := pub.Verify(data, sig); err != nil {
- t.Errorf("publicKey.Verify(%T): %v", priv, err)
- }
- sig.Blob[5]++
- if err := pub.Verify(data, sig); err == nil {
- t.Errorf("publicKey.Verify on broken sig did not fail")
- }
- }
- }
- func TestKeySignWithAlgorithmVerify(t *testing.T) {
- for _, priv := range testSigners {
- if algorithmSigner, ok := priv.(AlgorithmSigner); !ok {
- t.Errorf("Signers constructed by ssh package should always implement the AlgorithmSigner interface: %T", priv)
- } else {
- pub := priv.PublicKey()
- data := []byte("sign me")
- signWithAlgTestCase := func(algorithm string, expectedAlg string) {
- sig, err := algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm)
- if err != nil {
- t.Fatalf("Sign(%T): %v", priv, err)
- }
- if sig.Format != expectedAlg {
- t.Errorf("signature format did not match requested signature algorithm: %s != %s", sig.Format, expectedAlg)
- }
- if err := pub.Verify(data, sig); err != nil {
- t.Errorf("publicKey.Verify(%T): %v", priv, err)
- }
- sig.Blob[5]++
- if err := pub.Verify(data, sig); err == nil {
- t.Errorf("publicKey.Verify on broken sig did not fail")
- }
- }
- // Using the empty string as the algorithm name should result in the same signature format as the algorithm-free Sign method.
- defaultSig, err := priv.Sign(rand.Reader, data)
- if err != nil {
- t.Fatalf("Sign(%T): %v", priv, err)
- }
- signWithAlgTestCase("", defaultSig.Format)
- // RSA keys are the only ones which currently support more than one signing algorithm
- if pub.Type() == KeyAlgoRSA {
- for _, algorithm := range []string{SigAlgoRSA, SigAlgoRSASHA2256, SigAlgoRSASHA2512} {
- signWithAlgTestCase(algorithm, algorithm)
- }
- }
- }
- }
- }
- func TestParseRSAPrivateKey(t *testing.T) {
- key := testPrivateKeys["rsa"]
- rsa, ok := key.(*rsa.PrivateKey)
- if !ok {
- t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
- }
- if err := rsa.Validate(); err != nil {
- t.Errorf("Validate: %v", err)
- }
- }
- func TestParseECPrivateKey(t *testing.T) {
- key := testPrivateKeys["ecdsa"]
- ecKey, ok := key.(*ecdsa.PrivateKey)
- if !ok {
- t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
- }
- if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
- t.Fatalf("public key does not validate.")
- }
- }
- // See Issue https://github.com/golang/go/issues/6650.
- func TestParseEncryptedPrivateKeysFails(t *testing.T) {
- const wantSubstring = "encrypted"
- for i, tt := range testdata.PEMEncryptedKeys {
- _, err := ParsePrivateKey(tt.PEMBytes)
- if err == nil {
- t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name)
- continue
- }
- if !strings.Contains(err.Error(), wantSubstring) {
- t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring)
- }
- }
- }
- // Parse encrypted private keys with passphrase
- func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
- data := []byte("sign me")
- for _, tt := range testdata.PEMEncryptedKeys {
- s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
- if err != nil {
- t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
- continue
- }
- sig, err := s.Sign(rand.Reader, data)
- if err != nil {
- t.Fatalf("dsa.Sign: %v", err)
- }
- if err := s.PublicKey().Verify(data, sig); err != nil {
- t.Errorf("Verify failed: %v", err)
- }
- }
- tt := testdata.PEMEncryptedKeys[0]
- _, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
- if err != x509.IncorrectPasswordError {
- t.Fatalf("got %v want IncorrectPasswordError", err)
- }
- }
- func TestParseDSA(t *testing.T) {
- // We actually exercise the ParsePrivateKey codepath here, as opposed to
- // using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
- // uses.
- s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
- if err != nil {
- t.Fatalf("ParsePrivateKey returned error: %s", err)
- }
- data := []byte("sign me")
- sig, err := s.Sign(rand.Reader, data)
- if err != nil {
- t.Fatalf("dsa.Sign: %v", err)
- }
- if err := s.PublicKey().Verify(data, sig); err != nil {
- t.Errorf("Verify failed: %v", err)
- }
- }
- // Tests for authorized_keys parsing.
- // getTestKey returns a public key, and its base64 encoding.
- func getTestKey() (PublicKey, string) {
- k := testPublicKeys["rsa"]
- b := &bytes.Buffer{}
- e := base64.NewEncoder(base64.StdEncoding, b)
- e.Write(k.Marshal())
- e.Close()
- return k, b.String()
- }
- func TestMarshalParsePublicKey(t *testing.T) {
- pub, pubSerialized := getTestKey()
- line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
- authKeys := MarshalAuthorizedKey(pub)
- actualFields := strings.Fields(string(authKeys))
- if len(actualFields) == 0 {
- t.Fatalf("failed authKeys: %v", authKeys)
- }
- // drop the comment
- expectedFields := strings.Fields(line)[0:2]
- if !reflect.DeepEqual(actualFields, expectedFields) {
- t.Errorf("got %v, expected %v", actualFields, expectedFields)
- }
- actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
- if err != nil {
- t.Fatalf("cannot parse %v: %v", line, err)
- }
- if !reflect.DeepEqual(actPub, pub) {
- t.Errorf("got %v, expected %v", actPub, pub)
- }
- }
- type testAuthResult struct {
- pubKey PublicKey
- options []string
- comments string
- rest string
- ok bool
- }
- func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) {
- rest := authKeys
- var values []testAuthResult
- for len(rest) > 0 {
- var r testAuthResult
- var err error
- r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
- r.ok = (err == nil)
- t.Log(err)
- r.rest = string(rest)
- values = append(values, r)
- }
- if !reflect.DeepEqual(values, expected) {
- t.Errorf("got %#v, expected %#v", values, expected)
- }
- }
- func TestAuthorizedKeyBasic(t *testing.T) {
- pub, pubSerialized := getTestKey()
- line := "ssh-rsa " + pubSerialized + " user@host"
- testAuthorizedKeys(t, []byte(line),
- []testAuthResult{
- {pub, nil, "user@host", "", true},
- })
- }
- func TestAuth(t *testing.T) {
- pub, pubSerialized := getTestKey()
- authWithOptions := []string{
- `# comments to ignore before any keys...`,
- ``,
- `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
- `# comments to ignore, along with a blank line`,
- ``,
- `env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
- ``,
- `# more comments, plus a invalid entry`,
- `ssh-rsa data-that-will-not-parse user@host3`,
- }
- for _, eol := range []string{"\n", "\r\n"} {
- authOptions := strings.Join(authWithOptions, eol)
- rest2 := strings.Join(authWithOptions[3:], eol)
- rest3 := strings.Join(authWithOptions[6:], eol)
- testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{
- {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
- {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
- {nil, nil, "", "", false},
- })
- }
- }
- func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
- pub, pubSerialized := getTestKey()
- authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
- testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{
- {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
- })
- }
- func TestAuthWithQuotedCommaInEnv(t *testing.T) {
- pub, pubSerialized := getTestKey()
- authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
- testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{
- {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
- })
- }
- func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
- pub, pubSerialized := getTestKey()
- authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
- authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
- testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{
- {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
- })
- testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{
- {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
- })
- }
- func TestAuthWithInvalidSpace(t *testing.T) {
- _, pubSerialized := getTestKey()
- authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
- #more to follow but still no valid keys`)
- testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{
- {nil, nil, "", "", false},
- })
- }
- func TestAuthWithMissingQuote(t *testing.T) {
- pub, pubSerialized := getTestKey()
- authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
- env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
- testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{
- {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
- })
- }
- func TestInvalidEntry(t *testing.T) {
- authInvalid := []byte(`ssh-rsa`)
- _, _, _, _, err := ParseAuthorizedKey(authInvalid)
- if err == nil {
- t.Errorf("got valid entry for %q", authInvalid)
- }
- }
- var knownHostsParseTests = []struct {
- input string
- err string
- marker string
- comment string
- hosts []string
- rest string
- }{
- {
- "",
- "EOF",
- "", "", nil, "",
- },
- {
- "# Just a comment",
- "EOF",
- "", "", nil, "",
- },
- {
- " \t ",
- "EOF",
- "", "", nil, "",
- },
- {
- "localhost ssh-rsa {RSAPUB}",
- "",
- "", "", []string{"localhost"}, "",
- },
- {
- "localhost\tssh-rsa {RSAPUB}",
- "",
- "", "", []string{"localhost"}, "",
- },
- {
- "localhost\tssh-rsa {RSAPUB}\tcomment comment",
- "",
- "", "comment comment", []string{"localhost"}, "",
- },
- {
- "localhost\tssh-rsa {RSAPUB}\tcomment comment\n",
- "",
- "", "comment comment", []string{"localhost"}, "",
- },
- {
- "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n",
- "",
- "", "comment comment", []string{"localhost"}, "",
- },
- {
- "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line",
- "",
- "", "comment comment", []string{"localhost"}, "next line",
- },
- {
- "localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
- "",
- "", "comment comment", []string{"localhost", "[host2:123]"}, "",
- },
- {
- "@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
- "",
- "marker", "", []string{"localhost", "[host2:123]"}, "",
- },
- {
- "@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
- "short read",
- "", "", nil, "",
- },
- }
- func TestKnownHostsParsing(t *testing.T) {
- rsaPub, rsaPubSerialized := getTestKey()
- for i, test := range knownHostsParseTests {
- var expectedKey PublicKey
- const rsaKeyToken = "{RSAPUB}"
- input := test.input
- if strings.Contains(input, rsaKeyToken) {
- expectedKey = rsaPub
- input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
- }
- marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
- if err != nil {
- if len(test.err) == 0 {
- t.Errorf("#%d: unexpectedly failed with %q", i, err)
- } else if !strings.Contains(err.Error(), test.err) {
- t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err)
- }
- continue
- } else if len(test.err) != 0 {
- t.Errorf("#%d: succeeded but expected error including %q", i, test.err)
- continue
- }
- if !reflect.DeepEqual(expectedKey, pubKey) {
- t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey)
- }
- if marker != test.marker {
- t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker)
- }
- if comment != test.comment {
- t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment)
- }
- if !reflect.DeepEqual(test.hosts, hosts) {
- t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts)
- }
- if rest := string(rest); rest != test.rest {
- t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest)
- }
- }
- }
- func TestFingerprintLegacyMD5(t *testing.T) {
- pub, _ := getTestKey()
- fingerprint := FingerprintLegacyMD5(pub)
- want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa
- if fingerprint != want {
- t.Errorf("got fingerprint %q want %q", fingerprint, want)
- }
- }
- func TestFingerprintSHA256(t *testing.T) {
- pub, _ := getTestKey()
- fingerprint := FingerprintSHA256(pub)
- want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa
- if fingerprint != want {
- t.Errorf("got fingerprint %q want %q", fingerprint, want)
- }
- }
- func TestInvalidKeys(t *testing.T) {
- keyTypes := []string{
- "RSA PRIVATE KEY",
- "PRIVATE KEY",
- "EC PRIVATE KEY",
- "DSA PRIVATE KEY",
- "OPENSSH PRIVATE KEY",
- }
- for _, keyType := range keyTypes {
- for _, dataLen := range []int{0, 1, 2, 5, 10, 20} {
- data := make([]byte, dataLen)
- if _, err := io.ReadFull(rand.Reader, data); err != nil {
- t.Fatal(err)
- }
- var buf bytes.Buffer
- pem.Encode(&buf, &pem.Block{
- Type: keyType,
- Bytes: data,
- })
- // This test is just to ensure that the function
- // doesn't panic so the return value is ignored.
- ParseRawPrivateKey(buf.Bytes())
- }
- }
- }
|