Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 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 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 5 | package ssh |
| 6 | |
| 7 | import ( |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 8 | "bytes" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 9 | "crypto/dsa" |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 10 | "crypto/ecdsa" |
| 11 | "crypto/elliptic" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 12 | "crypto/rand" |
| 13 | "crypto/rsa" |
Yasuhiro Matsumoto | 84f24df | 2017-06-23 19:03:27 +0900 | [diff] [blame] | 14 | "crypto/x509" |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 15 | "encoding/base64" |
Sebastian Kinne | 86a7050 | 2019-11-19 16:30:15 -0800 | [diff] [blame] | 16 | "encoding/hex" |
Adam Langley | 0709b30 | 2018-08-31 15:38:59 -0700 | [diff] [blame] | 17 | "encoding/pem" |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 18 | "fmt" |
Adam Langley | 0709b30 | 2018-08-31 15:38:59 -0700 | [diff] [blame] | 19 | "io" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 20 | "reflect" |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 21 | "strings" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 22 | "testing" |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 23 | |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 24 | "golang.org/x/crypto/ed25519" |
Andrew Gerrand | a73c6bb | 2014-11-10 08:50:25 +1100 | [diff] [blame] | 25 | "golang.org/x/crypto/ssh/testdata" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 26 | ) |
| 27 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 28 | func rawKey(pub PublicKey) interface{} { |
| 29 | switch k := pub.(type) { |
| 30 | case *rsaPublicKey: |
| 31 | return (*rsa.PublicKey)(k) |
| 32 | case *dsaPublicKey: |
| 33 | return (*dsa.PublicKey)(k) |
| 34 | case *ecdsaPublicKey: |
| 35 | return (*ecdsa.PublicKey)(k) |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 36 | case ed25519PublicKey: |
| 37 | return (ed25519.PublicKey)(k) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 38 | case *Certificate: |
Jonathan Pittman | 9112f50 | 2013-10-22 15:12:41 -0400 | [diff] [blame] | 39 | return k |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 40 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 41 | panic("unknown key type") |
| 42 | } |
| 43 | |
| 44 | func TestKeyMarshalParse(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 45 | for _, priv := range testSigners { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 46 | pub := priv.PublicKey() |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 47 | roundtrip, err := ParsePublicKey(pub.Marshal()) |
| 48 | if err != nil { |
| 49 | t.Errorf("ParsePublicKey(%T): %v", pub, err) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | k1 := rawKey(pub) |
| 53 | k2 := rawKey(roundtrip) |
| 54 | |
| 55 | if !reflect.DeepEqual(k1, k2) { |
| 56 | t.Errorf("got %#v in roundtrip, want %#v", k2, k1) |
| 57 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 58 | } |
| 59 | } |
| 60 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 61 | func TestUnsupportedCurves(t *testing.T) { |
| 62 | raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 63 | if err != nil { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 64 | t.Fatalf("GenerateKey: %v", err) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 65 | } |
| 66 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 67 | if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") { |
| 68 | t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 69 | } |
| 70 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 71 | if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") { |
| 72 | t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 73 | } |
| 74 | } |
| 75 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 76 | func TestNewPublicKey(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 77 | for _, k := range testSigners { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 78 | raw := rawKey(k.PublicKey()) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 79 | // Skip certificates, as NewPublicKey does not support them. |
| 80 | if _, ok := raw.(*Certificate); ok { |
| 81 | continue |
| 82 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 83 | pub, err := NewPublicKey(raw) |
| 84 | if err != nil { |
| 85 | t.Errorf("NewPublicKey(%#v): %v", raw, err) |
| 86 | } |
| 87 | if !reflect.DeepEqual(k.PublicKey(), pub) { |
| 88 | t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey()) |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | func TestKeySignVerify(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 94 | for _, priv := range testSigners { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 95 | pub := priv.PublicKey() |
| 96 | |
| 97 | data := []byte("sign me") |
| 98 | sig, err := priv.Sign(rand.Reader, data) |
| 99 | if err != nil { |
| 100 | t.Fatalf("Sign(%T): %v", priv, err) |
| 101 | } |
| 102 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 103 | if err := pub.Verify(data, sig); err != nil { |
| 104 | t.Errorf("publicKey.Verify(%T): %v", priv, err) |
| 105 | } |
| 106 | sig.Blob[5]++ |
| 107 | if err := pub.Verify(data, sig); err == nil { |
| 108 | t.Errorf("publicKey.Verify on broken sig did not fail") |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
Ian Haken | dab2b10 | 2018-10-29 02:07:14 +0000 | [diff] [blame] | 113 | func TestKeySignWithAlgorithmVerify(t *testing.T) { |
| 114 | for _, priv := range testSigners { |
| 115 | if algorithmSigner, ok := priv.(AlgorithmSigner); !ok { |
| 116 | t.Errorf("Signers constructed by ssh package should always implement the AlgorithmSigner interface: %T", priv) |
| 117 | } else { |
| 118 | pub := priv.PublicKey() |
| 119 | data := []byte("sign me") |
| 120 | |
| 121 | signWithAlgTestCase := func(algorithm string, expectedAlg string) { |
| 122 | sig, err := algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm) |
| 123 | if err != nil { |
| 124 | t.Fatalf("Sign(%T): %v", priv, err) |
| 125 | } |
| 126 | if sig.Format != expectedAlg { |
| 127 | t.Errorf("signature format did not match requested signature algorithm: %s != %s", sig.Format, expectedAlg) |
| 128 | } |
| 129 | |
| 130 | if err := pub.Verify(data, sig); err != nil { |
| 131 | t.Errorf("publicKey.Verify(%T): %v", priv, err) |
| 132 | } |
| 133 | sig.Blob[5]++ |
| 134 | if err := pub.Verify(data, sig); err == nil { |
| 135 | t.Errorf("publicKey.Verify on broken sig did not fail") |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | // Using the empty string as the algorithm name should result in the same signature format as the algorithm-free Sign method. |
| 140 | defaultSig, err := priv.Sign(rand.Reader, data) |
| 141 | if err != nil { |
| 142 | t.Fatalf("Sign(%T): %v", priv, err) |
| 143 | } |
| 144 | signWithAlgTestCase("", defaultSig.Format) |
| 145 | |
| 146 | // RSA keys are the only ones which currently support more than one signing algorithm |
| 147 | if pub.Type() == KeyAlgoRSA { |
Filippo Valsorda | fcc990c | 2022-03-14 06:24:16 -0400 | [diff] [blame] | 148 | for _, algorithm := range []string{KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512} { |
Ian Haken | dab2b10 | 2018-10-29 02:07:14 +0000 | [diff] [blame] | 149 | signWithAlgTestCase(algorithm, algorithm) |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 156 | func TestParseRSAPrivateKey(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 157 | key := testPrivateKeys["rsa"] |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 158 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 159 | rsa, ok := key.(*rsa.PrivateKey) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 160 | if !ok { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 161 | t.Fatalf("got %T, want *rsa.PrivateKey", rsa) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 162 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 163 | |
| 164 | if err := rsa.Validate(); err != nil { |
| 165 | t.Errorf("Validate: %v", err) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 166 | } |
| 167 | } |
| 168 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 169 | func TestParseECPrivateKey(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 170 | key := testPrivateKeys["ecdsa"] |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 171 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 172 | ecKey, ok := key.(*ecdsa.PrivateKey) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 173 | if !ok { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 174 | t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) { |
| 178 | t.Fatalf("public key does not validate.") |
| 179 | } |
| 180 | } |
| 181 | |
Yasuhiro Matsumoto | fea6c2c | 2017-02-02 15:10:04 +0900 | [diff] [blame] | 182 | func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) { |
| 183 | data := []byte("sign me") |
| 184 | for _, tt := range testdata.PEMEncryptedKeys { |
Filippo Valsorda | 0a08dad | 2019-11-17 18:43:54 -0500 | [diff] [blame] | 185 | t.Run(tt.Name, func(t *testing.T) { |
| 186 | _, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect")) |
| 187 | if err != x509.IncorrectPasswordError { |
| 188 | t.Errorf("got %v want IncorrectPasswordError", err) |
| 189 | } |
Yasuhiro Matsumoto | 84f24df | 2017-06-23 19:03:27 +0900 | [diff] [blame] | 190 | |
Filippo Valsorda | 0a08dad | 2019-11-17 18:43:54 -0500 | [diff] [blame] | 191 | s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey)) |
| 192 | if err != nil { |
| 193 | t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err) |
| 194 | } |
| 195 | |
| 196 | sig, err := s.Sign(rand.Reader, data) |
| 197 | if err != nil { |
| 198 | t.Fatalf("Signer.Sign: %v", err) |
| 199 | } |
| 200 | if err := s.PublicKey().Verify(data, sig); err != nil { |
| 201 | t.Errorf("Verify failed: %v", err) |
| 202 | } |
| 203 | |
| 204 | _, err = ParsePrivateKey(tt.PEMBytes) |
| 205 | if err == nil { |
| 206 | t.Fatalf("ParsePrivateKey succeeded, expected an error") |
| 207 | } |
| 208 | |
| 209 | if err, ok := err.(*PassphraseMissingError); !ok { |
| 210 | t.Errorf("got error %q, want PassphraseMissingError", err) |
| 211 | } else if tt.IncludesPublicKey { |
| 212 | if err.PublicKey == nil { |
| 213 | t.Fatalf("expected PassphraseMissingError.PublicKey not to be nil") |
| 214 | } |
| 215 | got, want := err.PublicKey.Marshal(), s.PublicKey().Marshal() |
| 216 | if !bytes.Equal(got, want) { |
| 217 | t.Errorf("error field %q doesn't match signer public key %q", got, want) |
| 218 | } |
| 219 | } |
| 220 | }) |
Yasuhiro Matsumoto | 84f24df | 2017-06-23 19:03:27 +0900 | [diff] [blame] | 221 | } |
Yasuhiro Matsumoto | fea6c2c | 2017-02-02 15:10:04 +0900 | [diff] [blame] | 222 | } |
| 223 | |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 224 | func TestParseDSA(t *testing.T) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 225 | // We actually exercise the ParsePrivateKey codepath here, as opposed to |
| 226 | // using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go |
| 227 | // uses. |
| 228 | s, err := ParsePrivateKey(testdata.PEMBytes["dsa"]) |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 229 | if err != nil { |
| 230 | t.Fatalf("ParsePrivateKey returned error: %s", err) |
| 231 | } |
| 232 | |
| 233 | data := []byte("sign me") |
| 234 | sig, err := s.Sign(rand.Reader, data) |
| 235 | if err != nil { |
| 236 | t.Fatalf("dsa.Sign: %v", err) |
| 237 | } |
| 238 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 239 | if err := s.PublicKey().Verify(data, sig); err != nil { |
| 240 | t.Errorf("Verify failed: %v", err) |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // Tests for authorized_keys parsing. |
| 245 | |
| 246 | // getTestKey returns a public key, and its base64 encoding. |
| 247 | func getTestKey() (PublicKey, string) { |
| 248 | k := testPublicKeys["rsa"] |
| 249 | |
| 250 | b := &bytes.Buffer{} |
| 251 | e := base64.NewEncoder(base64.StdEncoding, b) |
| 252 | e.Write(k.Marshal()) |
| 253 | e.Close() |
| 254 | |
| 255 | return k, b.String() |
| 256 | } |
| 257 | |
| 258 | func TestMarshalParsePublicKey(t *testing.T) { |
| 259 | pub, pubSerialized := getTestKey() |
| 260 | line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized) |
| 261 | |
| 262 | authKeys := MarshalAuthorizedKey(pub) |
| 263 | actualFields := strings.Fields(string(authKeys)) |
| 264 | if len(actualFields) == 0 { |
| 265 | t.Fatalf("failed authKeys: %v", authKeys) |
| 266 | } |
| 267 | |
| 268 | // drop the comment |
| 269 | expectedFields := strings.Fields(line)[0:2] |
| 270 | |
| 271 | if !reflect.DeepEqual(actualFields, expectedFields) { |
| 272 | t.Errorf("got %v, expected %v", actualFields, expectedFields) |
| 273 | } |
| 274 | |
| 275 | actPub, _, _, _, err := ParseAuthorizedKey([]byte(line)) |
| 276 | if err != nil { |
| 277 | t.Fatalf("cannot parse %v: %v", line, err) |
| 278 | } |
| 279 | if !reflect.DeepEqual(actPub, pub) { |
| 280 | t.Errorf("got %v, expected %v", actPub, pub) |
| 281 | } |
| 282 | } |
| 283 | |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 284 | type testAuthResult struct { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 285 | pubKey PublicKey |
| 286 | options []string |
| 287 | comments string |
| 288 | rest string |
| 289 | ok bool |
| 290 | } |
| 291 | |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 292 | func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 293 | rest := authKeys |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 294 | var values []testAuthResult |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 295 | for len(rest) > 0 { |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 296 | var r testAuthResult |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 297 | var err error |
| 298 | r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest) |
| 299 | r.ok = (err == nil) |
| 300 | t.Log(err) |
| 301 | r.rest = string(rest) |
| 302 | values = append(values, r) |
| 303 | } |
| 304 | |
| 305 | if !reflect.DeepEqual(values, expected) { |
| 306 | t.Errorf("got %#v, expected %#v", values, expected) |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | func TestAuthorizedKeyBasic(t *testing.T) { |
| 311 | pub, pubSerialized := getTestKey() |
| 312 | line := "ssh-rsa " + pubSerialized + " user@host" |
| 313 | testAuthorizedKeys(t, []byte(line), |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 314 | []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 315 | {pub, nil, "user@host", "", true}, |
| 316 | }) |
| 317 | } |
| 318 | |
| 319 | func TestAuth(t *testing.T) { |
| 320 | pub, pubSerialized := getTestKey() |
| 321 | authWithOptions := []string{ |
| 322 | `# comments to ignore before any keys...`, |
| 323 | ``, |
| 324 | `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`, |
| 325 | `# comments to ignore, along with a blank line`, |
| 326 | ``, |
| 327 | `env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`, |
| 328 | ``, |
| 329 | `# more comments, plus a invalid entry`, |
| 330 | `ssh-rsa data-that-will-not-parse user@host3`, |
| 331 | } |
| 332 | for _, eol := range []string{"\n", "\r\n"} { |
| 333 | authOptions := strings.Join(authWithOptions, eol) |
| 334 | rest2 := strings.Join(authWithOptions[3:], eol) |
| 335 | rest3 := strings.Join(authWithOptions[6:], eol) |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 336 | testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 337 | {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, |
| 338 | {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, |
| 339 | {nil, nil, "", "", false}, |
| 340 | }) |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | func TestAuthWithQuotedSpaceInEnv(t *testing.T) { |
| 345 | pub, pubSerialized := getTestKey() |
| 346 | authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 347 | testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 348 | {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true}, |
| 349 | }) |
| 350 | } |
| 351 | |
| 352 | func TestAuthWithQuotedCommaInEnv(t *testing.T) { |
| 353 | pub, pubSerialized := getTestKey() |
| 354 | authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 355 | testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 356 | {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true}, |
| 357 | }) |
| 358 | } |
| 359 | |
| 360 | func TestAuthWithQuotedQuoteInEnv(t *testing.T) { |
| 361 | pub, pubSerialized := getTestKey() |
| 362 | authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`) |
| 363 | authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`) |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 364 | testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 365 | {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true}, |
| 366 | }) |
| 367 | |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 368 | testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 369 | {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true}, |
| 370 | }) |
| 371 | } |
| 372 | |
| 373 | func TestAuthWithInvalidSpace(t *testing.T) { |
| 374 | _, pubSerialized := getTestKey() |
| 375 | authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host |
| 376 | #more to follow but still no valid keys`) |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 377 | testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 378 | {nil, nil, "", "", false}, |
| 379 | }) |
| 380 | } |
| 381 | |
| 382 | func TestAuthWithMissingQuote(t *testing.T) { |
| 383 | pub, pubSerialized := getTestKey() |
| 384 | authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host |
| 385 | env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`) |
| 386 | |
Sami Pönkänen | 9334d73 | 2018-01-17 14:44:42 +0200 | [diff] [blame] | 387 | testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{ |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 388 | {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true}, |
| 389 | }) |
| 390 | } |
| 391 | |
| 392 | func TestInvalidEntry(t *testing.T) { |
| 393 | authInvalid := []byte(`ssh-rsa`) |
| 394 | _, _, _, _, err := ParseAuthorizedKey(authInvalid) |
| 395 | if err == nil { |
| 396 | t.Errorf("got valid entry for %q", authInvalid) |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 397 | } |
| 398 | } |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 399 | |
| 400 | var knownHostsParseTests = []struct { |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 401 | input string |
| 402 | err string |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 403 | |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 404 | marker string |
| 405 | comment string |
| 406 | hosts []string |
| 407 | rest string |
| 408 | }{ |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 409 | { |
| 410 | "", |
| 411 | "EOF", |
| 412 | |
| 413 | "", "", nil, "", |
| 414 | }, |
| 415 | { |
| 416 | "# Just a comment", |
| 417 | "EOF", |
| 418 | |
| 419 | "", "", nil, "", |
| 420 | }, |
| 421 | { |
| 422 | " \t ", |
| 423 | "EOF", |
| 424 | |
| 425 | "", "", nil, "", |
| 426 | }, |
| 427 | { |
| 428 | "localhost ssh-rsa {RSAPUB}", |
| 429 | "", |
| 430 | |
| 431 | "", "", []string{"localhost"}, "", |
| 432 | }, |
| 433 | { |
| 434 | "localhost\tssh-rsa {RSAPUB}", |
| 435 | "", |
| 436 | |
| 437 | "", "", []string{"localhost"}, "", |
| 438 | }, |
| 439 | { |
| 440 | "localhost\tssh-rsa {RSAPUB}\tcomment comment", |
| 441 | "", |
| 442 | |
| 443 | "", "comment comment", []string{"localhost"}, "", |
| 444 | }, |
| 445 | { |
| 446 | "localhost\tssh-rsa {RSAPUB}\tcomment comment\n", |
| 447 | "", |
| 448 | |
| 449 | "", "comment comment", []string{"localhost"}, "", |
| 450 | }, |
| 451 | { |
| 452 | "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n", |
| 453 | "", |
| 454 | |
| 455 | "", "comment comment", []string{"localhost"}, "", |
| 456 | }, |
| 457 | { |
| 458 | "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line", |
| 459 | "", |
| 460 | |
| 461 | "", "comment comment", []string{"localhost"}, "next line", |
| 462 | }, |
| 463 | { |
| 464 | "localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment", |
| 465 | "", |
| 466 | |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 467 | "", "comment comment", []string{"localhost", "[host2:123]"}, "", |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 468 | }, |
| 469 | { |
| 470 | "@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}", |
| 471 | "", |
| 472 | |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 473 | "marker", "", []string{"localhost", "[host2:123]"}, "", |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 474 | }, |
| 475 | { |
| 476 | "@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd", |
| 477 | "short read", |
| 478 | |
| 479 | "", "", nil, "", |
| 480 | }, |
| 481 | } |
| 482 | |
| 483 | func TestKnownHostsParsing(t *testing.T) { |
| 484 | rsaPub, rsaPubSerialized := getTestKey() |
| 485 | |
| 486 | for i, test := range knownHostsParseTests { |
| 487 | var expectedKey PublicKey |
| 488 | const rsaKeyToken = "{RSAPUB}" |
| 489 | |
| 490 | input := test.input |
| 491 | if strings.Contains(input, rsaKeyToken) { |
| 492 | expectedKey = rsaPub |
| 493 | input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1) |
| 494 | } |
| 495 | |
| 496 | marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input)) |
| 497 | if err != nil { |
| 498 | if len(test.err) == 0 { |
| 499 | t.Errorf("#%d: unexpectedly failed with %q", i, err) |
| 500 | } else if !strings.Contains(err.Error(), test.err) { |
| 501 | t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err) |
| 502 | } |
| 503 | continue |
| 504 | } else if len(test.err) != 0 { |
| 505 | t.Errorf("#%d: succeeded but expected error including %q", i, test.err) |
| 506 | continue |
| 507 | } |
| 508 | |
| 509 | if !reflect.DeepEqual(expectedKey, pubKey) { |
| 510 | t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey) |
| 511 | } |
| 512 | |
| 513 | if marker != test.marker { |
| 514 | t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker) |
| 515 | } |
| 516 | |
| 517 | if comment != test.comment { |
| 518 | t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment) |
| 519 | } |
| 520 | |
| 521 | if !reflect.DeepEqual(test.hosts, hosts) { |
| 522 | t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts) |
| 523 | } |
| 524 | |
| 525 | if rest := string(rest); rest != test.rest { |
| 526 | t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest) |
| 527 | } |
| 528 | } |
| 529 | } |
Ryuzo Yamamoto | ede567c | 2016-11-04 15:41:44 -0400 | [diff] [blame] | 530 | |
| 531 | func TestFingerprintLegacyMD5(t *testing.T) { |
| 532 | pub, _ := getTestKey() |
| 533 | fingerprint := FingerprintLegacyMD5(pub) |
| 534 | want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa |
| 535 | if fingerprint != want { |
| 536 | t.Errorf("got fingerprint %q want %q", fingerprint, want) |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | func TestFingerprintSHA256(t *testing.T) { |
| 541 | pub, _ := getTestKey() |
| 542 | fingerprint := FingerprintSHA256(pub) |
| 543 | want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa |
| 544 | if fingerprint != want { |
| 545 | t.Errorf("got fingerprint %q want %q", fingerprint, want) |
| 546 | } |
| 547 | } |
Adam Langley | 0709b30 | 2018-08-31 15:38:59 -0700 | [diff] [blame] | 548 | |
| 549 | func TestInvalidKeys(t *testing.T) { |
| 550 | keyTypes := []string{ |
| 551 | "RSA PRIVATE KEY", |
| 552 | "PRIVATE KEY", |
| 553 | "EC PRIVATE KEY", |
| 554 | "DSA PRIVATE KEY", |
| 555 | "OPENSSH PRIVATE KEY", |
| 556 | } |
| 557 | |
| 558 | for _, keyType := range keyTypes { |
| 559 | for _, dataLen := range []int{0, 1, 2, 5, 10, 20} { |
| 560 | data := make([]byte, dataLen) |
| 561 | if _, err := io.ReadFull(rand.Reader, data); err != nil { |
| 562 | t.Fatal(err) |
| 563 | } |
| 564 | |
| 565 | var buf bytes.Buffer |
| 566 | pem.Encode(&buf, &pem.Block{ |
| 567 | Type: keyType, |
| 568 | Bytes: data, |
| 569 | }) |
| 570 | |
| 571 | // This test is just to ensure that the function |
| 572 | // doesn't panic so the return value is ignored. |
| 573 | ParseRawPrivateKey(buf.Bytes()) |
| 574 | } |
| 575 | } |
| 576 | } |
Sebastian Kinne | 86a7050 | 2019-11-19 16:30:15 -0800 | [diff] [blame] | 577 | |
| 578 | func TestSKKeys(t *testing.T) { |
| 579 | for _, d := range testdata.SKData { |
| 580 | pk, _, _, _, err := ParseAuthorizedKey(d.PubKey) |
| 581 | if err != nil { |
| 582 | t.Fatalf("parseAuthorizedKey returned error: %v", err) |
| 583 | } |
| 584 | |
| 585 | sigBuf := make([]byte, hex.DecodedLen(len(d.HexSignature))) |
| 586 | if _, err := hex.Decode(sigBuf, d.HexSignature); err != nil { |
| 587 | t.Fatalf("hex.Decode() failed: %v", err) |
| 588 | } |
| 589 | |
| 590 | dataBuf := make([]byte, hex.DecodedLen(len(d.HexData))) |
| 591 | if _, err := hex.Decode(dataBuf, d.HexData); err != nil { |
| 592 | t.Fatalf("hex.Decode() failed: %v", err) |
| 593 | } |
| 594 | |
| 595 | sig, _, ok := parseSignature(sigBuf) |
| 596 | if !ok { |
| 597 | t.Fatalf("parseSignature(%v) failed", sigBuf) |
| 598 | } |
| 599 | |
| 600 | // Test that good data and signature pass verification |
| 601 | if err := pk.Verify(dataBuf, sig); err != nil { |
| 602 | t.Errorf("%s: PublicKey.Verify(%v, %v) failed: %v", d.Name, dataBuf, sig, err) |
| 603 | } |
| 604 | |
| 605 | // Invalid data being passed in |
| 606 | invalidData := []byte("INVALID DATA") |
| 607 | if err := pk.Verify(invalidData, sig); err == nil { |
| 608 | t.Errorf("%s with invalid data: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, invalidData, sig) |
| 609 | } |
| 610 | |
| 611 | // Change byte in blob to corrup signature |
| 612 | sig.Blob[5] = byte('A') |
| 613 | // Corrupted data being passed in |
| 614 | if err := pk.Verify(dataBuf, sig); err == nil { |
| 615 | t.Errorf("%s with corrupted signature: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, dataBuf, sig) |
| 616 | } |
| 617 | } |
| 618 | } |