ssh: don't close the connection on an unparseable public key In the server's publickey authentication path, a public key blob that ParsePublicKey could not decode caused serverAuthenticate to return an error, tearing down the whole connection. A key we cannot parse is part of an individual authentication attempt, not a transport-level framing error, so it must fail only that attempt. This lets the client fall back to other keys or authentication methods. Updates golang/go#80075 Change-Id: I8d882d08bb50eb33e0054d9d9c2bd9e0b3039443 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795420 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> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Filippo Valsorda <filippo@golang.org> Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go index 2e48342..6568722 100644 --- a/ssh/client_auth_test.go +++ b/ssh/client_auth_test.go
@@ -7,10 +7,12 @@ import ( "bytes" "crypto/rand" + "crypto/rsa" "errors" "fmt" "io" "log" + "math/big" "net" "os" "runtime" @@ -169,6 +171,40 @@ } } +// invalidRSASigner offers an RSA public key the server cannot parse: its public +// exponent is even, which parseRSA rejects regardless of any key size limit. +// The key is intentionally invalid for a structural reason rather than for its +// size, so the test stays meaningful even if the accepted modulus size changes. +// Its Sign method is never reached, because the key is rejected during the +// initial public key query. +type invalidRSASigner struct{} + +func (invalidRSASigner) PublicKey() PublicKey { + n := new(big.Int).Lsh(big.NewInt(1), 2048) + pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 2}) // incorrect exponent. + if err != nil { + panic(err) + } + return pub +} + +func (invalidRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return nil, errors.New("ssh: invalid test key must not be used to sign") +} + +func TestClientAuthInvalidPublicKey(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(invalidRSASigner{}, testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("client auth failed but should have fallen back to a valid key: %s", err) + } +} + // partialSuccessPublicKeyAndKbdInteractiveServer returns a server config // where the rsa key gets partial success and the server offers both publickey // and keyboard-interactive as next methods. The VerifiedPublicKeyCallback
diff --git a/ssh/server.go b/ssh/server.go index d1a798a..18992a5 100644 --- a/ssh/server.go +++ b/ssh/server.go
@@ -796,7 +796,8 @@ pubKey, err := ParsePublicKey(pubKeyData) if err != nil { - return nil, err + authErr = err + break } candidate, ok := cache.get(s.user, pubKeyData)