Files
crypto/ssh/keys_test.go
T
Nicola Murino 6f39c5226a ssh: limit RSA key size in OpenSSH private key parsing
parseOpenSSHPrivateKey builds an rsa.PrivateKey directly from the
unmarshalled OpenSSH key blob, bypassing parseRSA and its modulus
limit. Validate() and Precompute() then perform several modular
exponentiations whose cost grows with the size of the prime factors;
in particular Precompute() recomputes the CRT coefficient as
q^(p-2) mod p, which is cubic in |p|. A maliciously crafted key with
oversized N and P can keep the CPU busy for hours or days during a
single load.

Mirror the parseRSA validation here: cap the modulus at 8192 bits
(the same limit enforced by crypto/tls), reject exponents larger than
24 bits, and reject invalid exponent values (< 3 or even). In addition,
bound each prime factor at 4096 bits to prevent the CRT computation
from becoming the dominant cost.

This issue was found during a security audit by NCC Group Cryptography
Services, sponsored by Teleport, and was assessed and is being fixed as
a non-security bug.

Change-Id: Ia5991f25dd41a22eddd7cf63a8fc5106de9e9663
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/782420
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-06-02 00:22:37 -07:00

1432 lines
51 KiB
Go

// 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/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"strings"
"testing"
"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 TestParsePublicKeyWithSigningAlgoAsKeyFormat(t *testing.T) {
key := []byte(`rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAEBAJ7qMyjLXEJCCJmRknuCLo0uPi5GrPY5pQYr84lhlN8Gor5KVL2LKYCW4e70r5xzj7SrHHSCft1FMlYg1KDO9xrprJh733kQqAPWETmSuH0EfRtGtcH6EarKyVxk6As076/yNiiMKVBtG0RPa1L7FviTfcYK4vnCCVrbv3RmA5CCzuG5BSMbRLxzVb4Ri3p8jhxYT8N4QGe/2yqvJLys5vQ9szpZR3tcFp3DJIVZhBRfR6LnoY23XZniAAMQaUVBX86dXQ++dNwAwZSXSt9Og+AniOCiBYqhNVa5n3DID/H7YtEtG+CbZr3r2KD3fv8AfSLRar4XOp8rsRdD31h/kr8=`)
_, _, _, _, err := ParseAuthorizedKey(key)
if err == nil {
t.Fatal("parsing a public key using a signature algorithm as the key format succeeded unexpectedly")
}
if !strings.Contains(err.Error(), `signature algorithm "rsa-sha2-256" isn't a key format`) {
t.Errorf(`got %v, expected 'signature algorithm "rsa-sha2-256" isn't a key format'`, err)
}
}
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 k, priv := range testSigners {
if algorithmSigner, ok := priv.(MultiAlgorithmSigner); !ok {
t.Errorf("Signers %q constructed by ssh package should always implement the MultiAlgorithmSigner interface: %T", k, 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{KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512} {
signWithAlgTestCase(algorithm, algorithm)
}
}
}
}
}
func TestKeySignWithShortSignature(t *testing.T) {
signer := testSigners["rsa"].(AlgorithmSigner)
pub := signer.PublicKey()
// Note: data obtained by empirically trying until a result
// starting with 0 appeared
tests := []struct {
algorithm string
data []byte
}{
{
algorithm: KeyAlgoRSA,
data: []byte("sign me92"),
},
{
algorithm: KeyAlgoRSASHA256,
data: []byte("sign me294"),
},
{
algorithm: KeyAlgoRSASHA512,
data: []byte("sign me60"),
},
}
for _, tt := range tests {
sig, err := signer.SignWithAlgorithm(rand.Reader, tt.data, tt.algorithm)
if err != nil {
t.Fatalf("Sign(%T): %v", signer, err)
}
if sig.Blob[0] != 0 {
t.Errorf("%s: Expected signature with a leading 0", tt.algorithm)
}
sig.Blob = sig.Blob[1:]
if err := pub.Verify(tt.data, sig); err != nil {
t.Errorf("publicKey.Verify(%s): %v", tt.algorithm, err)
}
}
}
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 TestParseRSAModulusTooLarge(t *testing.T) {
rsa16384 := "ssh-rsa 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"
_, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384))
if err == nil {
t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
}
expectedError := "rsa modulus too large"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
}
func TestParsePrivateKeyRSAModulusTooLarge(t *testing.T) {
rsa16384 := []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
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
QHAxNnM=
-----END OPENSSH PRIVATE KEY-----
`)
_, err := ParseRawPrivateKey(rsa16384)
if err == nil {
t.Fatal("ParseRawPrivateKey accepted a 16384-bit modulus; expected it to be rejected")
}
expectedError := "rsa modulus too large"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
}
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.")
}
}
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
data := []byte("sign me")
for _, tt := range testdata.PEMEncryptedKeys {
t.Run(tt.Name, func(t *testing.T) {
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
if err != x509.IncorrectPasswordError {
t.Errorf("got %v want IncorrectPasswordError", err)
}
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
if err != nil {
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
}
sig, err := s.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("Signer.Sign: %v", err)
}
if err := s.PublicKey().Verify(data, sig); err != nil {
t.Errorf("Verify failed: %v", err)
}
_, err = ParsePrivateKey(tt.PEMBytes)
if err == nil {
t.Fatalf("ParsePrivateKey succeeded, expected an error")
}
if err, ok := err.(*PassphraseMissingError); !ok {
t.Errorf("got error %q, want PassphraseMissingError", err)
} else if tt.IncludesPublicKey {
if err.PublicKey == nil {
t.Fatalf("expected PassphraseMissingError.PublicKey not to be nil")
}
got, want := err.PublicKey.Marshal(), s.PublicKey().Marshal()
if !bytes.Equal(got, want) {
t.Errorf("error field %q doesn't match signer public key %q", got, want)
}
}
})
}
}
func TestParseEncryptedPrivateKeysWithUnsupportedCiphers(t *testing.T) {
for _, tt := range testdata.UnsupportedCipherData {
t.Run(tt.Name, func(t *testing.T) {
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
if err == nil {
t.Fatalf("expected 'unknown cipher' error for %q, got nil", tt.Name)
// If this cipher is now supported, remove it from testdata.UnsupportedCipherData
}
if !strings.Contains(err.Error(), "unknown cipher") {
t.Errorf("wanted 'unknown cipher' error, got %v", err.Error())
}
})
}
}
func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
pem := testdata.PEMEncryptedKeys[0].PEMBytes
for i := 0; i < 4096; i++ {
_, err := ParseRawPrivateKeyWithPassphrase(pem, []byte(fmt.Sprintf("%d", i)))
if !errors.Is(err, x509.IncorrectPasswordError) {
t.Fatalf("expected error: %v, got: %v", x509.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)
}
}
func TestParseDSAHugeQ(t *testing.T) {
P := new(big.Int).Lsh(big.NewInt(1), 1023)
Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
// G and Y: Dummy values, just needs to be < P to pass that specific check
G := big.NewInt(2)
Y := big.NewInt(5)
rawKey := struct {
P, Q, G, Y *big.Int
}{
P: P,
Q: Q,
G: G,
Y: Y,
}
inputBytes := Marshal(&rawKey)
_, _, err := parseDSA(inputBytes)
if err == nil {
t.Fatal("parseDSA accepted a DSA key with large Q")
}
expectedError := "ssh: unsupported DSA sub-prime size"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
}
func TestParseDSAYOutOfRange(t *testing.T) {
// Valid 1024/160 parameters (values don't need to be a real DSA group,
// they only need to pass the checkDSAParams bit-length checks and the
// G < P / G > 0 checks).
P := new(big.Int).Lsh(big.NewInt(1), 1023)
P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
Q := new(big.Int).Lsh(big.NewInt(1), 159)
Q.SetBit(Q, 0, 1)
G := big.NewInt(2)
for _, tc := range []struct {
name string
Y *big.Int
}{
{"Y_zero", big.NewInt(0)},
{"Y_negative", big.NewInt(-1)},
{"Y_equals_P", new(big.Int).Set(P)},
{"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
{"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
} {
t.Run(tc.name, func(t *testing.T) {
rawKey := struct {
P, Q, G, Y *big.Int
}{P: P, Q: Q, G: G, Y: tc.Y}
_, _, err := parseDSA(Marshal(&rawKey))
if err == nil {
t.Fatalf("parseDSA accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
}
expectedError := "DSA public value Y out of range"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
})
}
}
func TestMarshalPrivateKey(t *testing.T) {
tests := []struct {
name string
}{
{"rsa-openssh-format"},
{"ed25519"},
{"p256-openssh-format"},
{"p384-openssh-format"},
{"p521-openssh-format"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected, ok := testPrivateKeys[tt.name]
if !ok {
t.Fatalf("cannot find key %s", tt.name)
}
block, err := MarshalPrivateKey(expected, "test@golang.org")
if err != nil {
t.Fatalf("cannot marshal %s: %v", tt.name, err)
}
key, err := ParseRawPrivateKey(pem.EncodeToMemory(block))
if err != nil {
t.Fatalf("cannot parse %s: %v", tt.name, err)
}
if !reflect.DeepEqual(expected, key) {
t.Errorf("unexpected marshaled key %s", tt.name)
}
})
}
}
func TestMarshalPrivateKeyWithPassphrase(t *testing.T) {
tests := []struct {
name string
}{
{"rsa-openssh-format"},
{"ed25519"},
{"p256-openssh-format"},
{"p384-openssh-format"},
{"p521-openssh-format"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected, ok := testPrivateKeys[tt.name]
if !ok {
t.Fatalf("cannot find key %s", tt.name)
}
block, err := MarshalPrivateKeyWithPassphrase(expected, "test@golang.org", []byte("test-passphrase"))
if err != nil {
t.Fatalf("cannot marshal %s: %v", tt.name, err)
}
key, err := ParseRawPrivateKeyWithPassphrase(pem.EncodeToMemory(block), []byte("test-passphrase"))
if err != nil {
t.Fatalf("cannot parse %s: %v", tt.name, err)
}
if !reflect.DeepEqual(expected, key) {
t.Errorf("unexpected marshaled key %s", tt.name)
}
})
}
}
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 := "b7:ef:d3:d5:89:29:52:96:9f:df:47:41:4d:15:37:f4" // 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:fi5+D7UmDZDE9Q2sAVvvlpcQSIakN4DERdINgXd2AnE" // 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())
}
}
}
func TestSKKeys(t *testing.T) {
for _, d := range testdata.SKData {
pk, _, _, _, err := ParseAuthorizedKey(d.PubKey)
if err != nil {
t.Fatalf("parseAuthorizedKey returned error: %v", err)
}
sigBuf := make([]byte, hex.DecodedLen(len(d.HexSignature)))
if _, err := hex.Decode(sigBuf, d.HexSignature); err != nil {
t.Fatalf("hex.Decode() failed: %v", err)
}
dataBuf := make([]byte, hex.DecodedLen(len(d.HexData)))
if _, err := hex.Decode(dataBuf, d.HexData); err != nil {
t.Fatalf("hex.Decode() failed: %v", err)
}
sig, _, ok := parseSignature(sigBuf)
if !ok {
t.Fatalf("parseSignature(%v) failed", sigBuf)
}
// Test that good data and signature pass verification
if err := pk.Verify(dataBuf, sig); err != nil {
t.Errorf("%s: PublicKey.Verify(%v, %v) failed: %v", d.Name, dataBuf, sig, err)
}
// Invalid data being passed in
invalidData := []byte("INVALID DATA")
if err := pk.Verify(invalidData, sig); err == nil {
t.Errorf("%s with invalid data: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, invalidData, sig)
}
// Change byte in blob to corrup signature
sig.Blob[5] = byte('A')
// Corrupted data being passed in
if err := pk.Verify(dataBuf, sig); err == nil {
t.Errorf("%s with corrupted signature: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, dataBuf, sig)
}
}
}
// skTestHarness builds SK-formatted signatures over a fixed payload
// using a caller-supplied signing function, letting tests vary the UP
// flag byte without duplicating the wire-format scaffolding.
type skTestHarness struct {
format string
application string
data []byte
// sign takes the SHA-256 digest of the marshalled SK blob and
// returns the value to embed in Signature.Blob. For ECDSA keys the
// helper feeds the digest to ecdsa.Sign; for ed25519 the helper
// feeds the raw marshalled blob to ed25519.Sign.
signDigest func(digest []byte) []byte
signBlob func(blob []byte) []byte
}
func (h skTestHarness) sign(t *testing.T, flags byte) *Signature {
t.Helper()
hsh := sha256.New()
hsh.Write([]byte(h.application))
appDigest := hsh.Sum(nil)
hsh.Reset()
hsh.Write(h.data)
dataDigest := hsh.Sum(nil)
var counter uint32 = 1
blob := struct {
ApplicationDigest []byte `ssh:"rest"`
Flags byte
Counter uint32
MessageDigest []byte `ssh:"rest"`
}{appDigest, flags, counter, dataDigest}
marshalled := Marshal(blob)
var sigBlob []byte
if h.signDigest != nil {
hsh.Reset()
hsh.Write(marshalled)
sigBlob = h.signDigest(hsh.Sum(nil))
} else {
sigBlob = h.signBlob(marshalled)
}
return &Signature{
Format: h.format,
Blob: sigBlob,
Rest: Marshal(struct {
Flags byte
Counter uint32
}{flags, counter}),
}
}
func TestSKUserPresence(t *testing.T) {
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
h := skTestHarness{
format: "sk-ecdsa-sha2-nistp256@openssh.com",
application: "ssh:",
data: []byte("test data"),
signDigest: func(digest []byte) []byte {
r, s, err := ecdsa.Sign(rand.Reader, ecKey, digest)
if err != nil {
t.Fatal(err)
}
return Marshal(struct{ R, S *big.Int }{r, s})
},
}
pk := &skECDSAPublicKey{
application: h.application,
PublicKey: ecKey.PublicKey,
}
// Valid signature with UP=1 should pass.
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
t.Errorf("Verify failed with UP=1: %v", err)
}
// Valid signature with UP=0 should fail with the user-presence sentinel.
sigNoUP := h.sign(t, 0)
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
}
// UV set but UP clear must still fail: we only waive UP, never UV-only.
if err := pk.Verify(h.data, h.sign(t, 0x04)); !errors.Is(err, errSKMissingUserPresence) {
t.Errorf("UV-only (flags=0x04): expected errSKMissingUserPresence, got: %v", err)
}
// With noTouchRequired, UP=0 passes; UP=1+UV=1 also passes.
pk.noTouchRequired = true
if err := pk.Verify(h.data, sigNoUP); err != nil {
t.Errorf("Verify with noTouchRequired failed: %v", err)
}
if err := pk.Verify(h.data, h.sign(t, flagUserPresence|0x04)); err != nil {
t.Errorf("Verify UP|UV with noTouchRequired failed: %v", err)
}
}
func TestSKKeyWithoutUP(t *testing.T) {
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
edPub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
skEC := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
skED := &skEd25519PublicKey{PublicKey: edPub}
certEC := &Certificate{Key: &skECDSAPublicKey{PublicKey: ecKey.PublicKey}}
certED := &Certificate{Key: &skEd25519PublicKey{PublicKey: edPub}}
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
rsaPub, err := NewPublicKey(&rsaKey.PublicKey)
if err != nil {
t.Fatal(err)
}
certRSA := &Certificate{Key: rsaPub}
// SK raw keys: return a clone with the flag set, original untouched.
gotEC := skKeyWithoutUP(skEC)
if gotEC == skEC {
t.Error("skECDSAPublicKey: expected a clone, got the original pointer")
}
if !gotEC.(*skECDSAPublicKey).noTouchRequired {
t.Error("skECDSAPublicKey clone: noTouchRequired not set")
}
if skEC.noTouchRequired {
t.Error("skECDSAPublicKey: original mutated")
}
gotED := skKeyWithoutUP(skED)
if gotED == skED {
t.Error("skEd25519PublicKey: expected a clone, got the original pointer")
}
if !gotED.(*skEd25519PublicKey).noTouchRequired {
t.Error("skEd25519PublicKey clone: noTouchRequired not set")
}
if skED.noTouchRequired {
t.Error("skEd25519PublicKey: original mutated")
}
// Certificate wrapping SK: return a clone of the cert with a cloned
// SK key inside. Neither the original cert nor the original inner
// key must be mutated.
originalInnerEC := certEC.Key
gotCertEC := skKeyWithoutUP(certEC)
if gotCertEC == certEC {
t.Error("*Certificate(SK ecdsa): expected a clone, got the original pointer")
}
if got := gotCertEC.(*Certificate).Key.(*skECDSAPublicKey); !got.noTouchRequired {
t.Error("*Certificate(SK ecdsa): inner clone missing noTouchRequired")
}
if certEC.Key != originalInnerEC {
t.Error("*Certificate(SK ecdsa): original cert's Key pointer mutated")
}
if originalInnerEC.(*skECDSAPublicKey).noTouchRequired {
t.Error("*Certificate(SK ecdsa): original inner key mutated")
}
gotCertED := skKeyWithoutUP(certED)
if gotCertED == certED {
t.Error("*Certificate(SK ed25519): expected a clone, got the original pointer")
}
if got := gotCertED.(*Certificate).Key.(*skEd25519PublicKey); !got.noTouchRequired {
t.Error("*Certificate(SK ed25519): inner clone missing noTouchRequired")
}
// Non-SK key inside a cert: return original unchanged (nothing to clone).
if got := skKeyWithoutUP(certRSA); got != certRSA {
t.Error("*Certificate(RSA): expected the original pointer back")
}
// Plain non-SK key: return original unchanged.
if got := skKeyWithoutUP(rsaPub); got != rsaPub {
t.Error("rsaPublicKey: expected the original pointer back")
}
// Pathological: *Certificate whose Key is itself a *Certificate.
// The SSH cert format forbids this and parseCert rejects it, but
// a Go caller can still construct such a value. skKeyWithoutUP
// must not recurse into it (or panic); it returns the input
// unchanged. This also defends against a hypothetical cycle built
// from hand-constructed Certificate pointers.
nestedCert := &Certificate{Key: &Certificate{Key: skEC}}
if got := skKeyWithoutUP(nestedCert); got != nestedCert {
t.Error("*Certificate wrapping *Certificate: expected the original pointer back")
}
selfCycle := &Certificate{}
selfCycle.Key = selfCycle
if got := skKeyWithoutUP(selfCycle); got != selfCycle {
t.Error("self-referential *Certificate: expected the original pointer back")
}
}
func TestNoTouchAllowed(t *testing.T) {
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
sk := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
certNoExt := &Certificate{Key: sk}
certOptOut := &Certificate{
Key: sk,
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": ""}},
}
// Non-empty value: OpenSSH writes "" but any value must be accepted
// because the check is presence-only.
certOptOutNonEmpty := &Certificate{
Key: sk,
Permissions: Permissions{Extensions: map[string]string{"no-touch-required": "yes"}},
}
// no-touch-required belongs in Extensions, never CriticalOptions.
// Putting it in CriticalOptions must NOT be treated as opt-out.
certCritOnly := &Certificate{
Key: sk,
Permissions: Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}},
}
permsEmpty := &Permissions{}
permsOptOut := &Permissions{Extensions: map[string]string{"no-touch-required": ""}}
permsCritOnly := &Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}}
cases := []struct {
name string
pub PublicKey
perms *Permissions
want bool
}{
{"nil perms, no cert", sk, nil, false},
{"empty perms, no cert", sk, permsEmpty, false},
{"perms opt-out, raw key", sk, permsOptOut, true},
{"nil perms, cert opt-out", certOptOut, nil, true},
{"nil perms, cert opt-out non-empty value", certOptOutNonEmpty, nil, true},
{"nil perms, cert no ext", certNoExt, nil, false},
{"perms opt-out, cert no ext", certNoExt, permsOptOut, true},
{"empty perms, cert opt-out", certOptOut, permsEmpty, true},
// Negative controls: CriticalOptions must not waive UP.
{"critical-options only, raw key", sk, permsCritOnly, false},
{"critical-options only, cert", certCritOnly, nil, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := noTouchAllowed(tc.pub, tc.perms); got != tc.want {
t.Errorf("noTouchAllowed = %v, want %v", got, tc.want)
}
})
}
}
func TestSKUserPresenceEd25519(t *testing.T) {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
h := skTestHarness{
format: "sk-ssh-ed25519@openssh.com",
application: "ssh:",
data: []byte("test data"),
signBlob: func(blob []byte) []byte {
return ed25519.Sign(priv, blob)
},
}
pk := &skEd25519PublicKey{
application: h.application,
PublicKey: pub,
}
if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
t.Errorf("Verify failed with UP=1: %v", err)
}
sigNoUP := h.sign(t, 0)
if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
t.Errorf("expected errSKMissingUserPresence, got: %v", err)
}
pk.noTouchRequired = true
if err := pk.Verify(h.data, sigNoUP); err != nil {
t.Errorf("Verify with noTouchRequired failed: %v", err)
}
}
func TestNewSignerWithAlgos(t *testing.T) {
algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
if !ok {
t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
}
_, err := NewSignerWithAlgorithms(algorithSigner, nil)
if err == nil {
t.Error("signer with algos created with no algorithms")
}
_, err = NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoED25519})
if err == nil {
t.Error("signer with algos created with invalid algorithms")
}
_, err = NewSignerWithAlgorithms(algorithSigner, []string{CertAlgoRSASHA256v01})
if err == nil {
t.Error("signer with algos created with certificate algorithms")
}
mas, err := NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
if err != nil {
t.Errorf("unable to create signer with valid algorithms: %v", err)
}
_, err = NewSignerWithAlgorithms(mas, []string{KeyAlgoRSA})
if err == nil {
t.Error("signer with algos created with restricted algorithms")
}
}
func TestCryptoPublicKey(t *testing.T) {
for _, priv := range testSigners {
p1 := priv.PublicKey()
key, ok := p1.(CryptoPublicKey)
if !ok {
continue
}
p2, err := NewPublicKey(key.CryptoPublicKey())
if err != nil {
t.Fatalf("NewPublicKey(CryptoPublicKey) failed for %s, got: %v", p1.Type(), err)
}
if !reflect.DeepEqual(p1, p2) {
t.Errorf("got %#v in NewPublicKey, want %#v", p2, p1)
}
}
for _, d := range testdata.SKData {
p1, _, _, _, err := ParseAuthorizedKey(d.PubKey)
if err != nil {
t.Fatalf("parseAuthorizedKey returned error: %v", err)
}
k1, ok := p1.(CryptoPublicKey)
if !ok {
t.Fatalf("%T does not implement CryptoPublicKey", p1)
}
var p2 PublicKey
switch pub := k1.CryptoPublicKey().(type) {
case *ecdsa.PublicKey:
p2 = &skECDSAPublicKey{
application: "ssh:",
PublicKey: *pub,
}
case ed25519.PublicKey:
p2 = &skEd25519PublicKey{
application: "ssh:",
PublicKey: pub,
}
default:
t.Fatalf("unexpected type %T from CryptoPublicKey()", pub)
}
if !reflect.DeepEqual(p1, p2) {
t.Errorf("got %#v, want %#v", p2, p1)
}
}
}
func TestParseCertWithCertSignatureKey(t *testing.T) {
certBytes := []byte(`-----BEGIN SSH CERTIFICATE-----
AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPSp27hvNSB0
IotJnVhjC4zxNgNS8BHlUCxD0VJi4D/eAAAAIIJMi1e5qfx+IFuKD/p/Ssqcb3os
CpOw/4wBs1pQ53zwAAAAAAAAAAEAAAACAAAAAAAAABMAAAAPZm9vLmV4YW1wbGUu
Y29tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0AAAAgc3NoLWVkMjU1
MTktY2VydC12MDFAb3BlbnNzaC5jb20AAAAg+sNYhCO35mQT1UBMpmMk8ey+culd
IU8vBlPEl4B07swAAAAggiv+RLnboS4znGCVl/n1jDg2uD0h15tW4s/04eS2mLQA
AAAAAAAAAQAAAAIAAAAAAAAAEwAAAA9mb28uZXhhbXBsZS5jb20AAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCV2wETgLKL
Kt0bRl3YUnd/ZYSlq0xJMbn4Jj3cdPWykQAAAFMAAAALc3NoLWVkMjU1MTkAAABA
WOdbRGEzyRAhiIK227CLUQD5caXYMV8FvSIB7toEE2M/8HnWdG9H3Rsg/v3unruQ
JrQldnuPJNe7KOP2+zvUDgAAAFMAAAALc3NoLWVkMjU1MTkAAABAm3bIPp85ZpIe
D+izJcUqlcAOri7HO8bULFNHT6LVegvB06xQ5TLwMlrxWUF4cafl1tSe8JQck4a6
cLYUOHfQDw==
-----END SSH CERTIFICATE-----
`)
block, _ := pem.Decode(certBytes)
if block == nil {
t.Fatal("invalid test certificate")
}
if _, err := ParsePublicKey(block.Bytes); err == nil {
t.Fatal("parsing an SSH certificate using another certificate as signature key succeeded; expected failure")
}
}