ssh: respect MaxAuthTries also for "none" auth attempts
Only the first "none" auth attempt is allowed without penality Change-Id: Ibe776e968ba406445eeb94e8f1959383b88c98f7 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/575995 Reviewed-by: Filippo Valsorda <filippo@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Auto-Submit: Nicola Murino <nicola.murino@gmail.com> Commit-Queue: Nicola Murino <nicola.murino@gmail.com> Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
committed by
Gopher Robot
parent
6f79b5a20c
commit
b92bf9480d
+4
-2
@@ -468,6 +468,7 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
|
||||
var perms *Permissions
|
||||
|
||||
authFailures := 0
|
||||
noneAuthCount := 0
|
||||
var authErrs []error
|
||||
var displayedBanner bool
|
||||
partialSuccessReturned := false
|
||||
@@ -534,6 +535,7 @@ userAuthLoop:
|
||||
|
||||
switch userAuthReq.Method {
|
||||
case "none":
|
||||
noneAuthCount++
|
||||
// We don't allow none authentication after a partial success
|
||||
// response.
|
||||
if config.NoClientAuth && !partialSuccessReturned {
|
||||
@@ -755,7 +757,7 @@ userAuthLoop:
|
||||
failureMsg.PartialSuccess = true
|
||||
} else {
|
||||
// Allow initial attempt of 'none' without penalty.
|
||||
if authFailures > 0 || userAuthReq.Method != "none" {
|
||||
if authFailures > 0 || userAuthReq.Method != "none" || noneAuthCount != 1 {
|
||||
authFailures++
|
||||
}
|
||||
if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries {
|
||||
@@ -777,7 +779,7 @@ userAuthLoop:
|
||||
// disconnect, should we only send that message.)
|
||||
//
|
||||
// Either way, OpenSSH disconnects immediately after the last
|
||||
// failed authnetication attempt, and given they are typically
|
||||
// failed authentication attempt, and given they are typically
|
||||
// considered the golden implementation it seems reasonable
|
||||
// to match that behavior.
|
||||
continue
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -62,6 +64,133 @@ func TestClientAuthRestrictedPublicKeyAlgos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxAuthTriesNoneMethod(t *testing.T) {
|
||||
username := "testuser"
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 2,
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == username && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
var serverAuthErrors []error
|
||||
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
|
||||
serverAuthErrors = append(serverAuthErrors, err)
|
||||
}
|
||||
go newServer(c1, serverConfig)
|
||||
|
||||
clientConfig := ClientConfig{
|
||||
User: username,
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
clientConfig.SetDefaults()
|
||||
// Our client will send 'none' auth only once, so we need to send the
|
||||
// requests manually.
|
||||
c := &connection{
|
||||
sshConn: sshConn{
|
||||
conn: c2,
|
||||
user: username,
|
||||
clientVersion: []byte(packageVersion),
|
||||
},
|
||||
}
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to exchange version: %v", err)
|
||||
}
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, clientConfig.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, &clientConfig, "", c.sshConn.RemoteAddr())
|
||||
if err := c.transport.waitSession(); err != nil {
|
||||
t.Fatalf("unable to wait session: %v", err)
|
||||
}
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||
t.Fatalf("unable to send ssh-userauth message: %v", err)
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(packet) > 0 && packet[0] == msgExtInfo {
|
||||
packet, err = c.transport.readPacket()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
var serviceAccept serviceAcceptMsg
|
||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i <= serverConfig.MaxAuthTries; i++ {
|
||||
auth := new(noneAuth)
|
||||
_, _, err := auth.auth(c.sessionID, clientConfig.User, c.transport, clientConfig.Rand, nil)
|
||||
if i < serverConfig.MaxAuthTries {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("client: got no error")
|
||||
} else if !strings.Contains(err.Error(), "too many authentication failures") {
|
||||
t.Fatalf("client: got unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
if len(serverAuthErrors) != 3 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
for _, err := range serverAuthErrors {
|
||||
if !errors.Is(err, ErrNoAuth) {
|
||||
t.Errorf("go error: %v; want: %v", err, ErrNoAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxAuthTriesFirstNoneAuthErrorIgnored(t *testing.T) {
|
||||
username := "testuser"
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 1,
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if conn.User() == username && string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
clientConfig := &ClientConfig{
|
||||
User: username,
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
if len(serverAuthErrors) != 2 {
|
||||
t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
|
||||
}
|
||||
if !errors.Is(serverAuthErrors[0], ErrNoAuth) {
|
||||
t.Errorf("go error: %v; want: %v", serverAuthErrors[0], ErrNoAuth)
|
||||
}
|
||||
if serverAuthErrors[1] != nil {
|
||||
t.Errorf("unexpected error: %v", serverAuthErrors[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerConnValidationErrors(t *testing.T) {
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
|
||||
|
||||
Reference in New Issue
Block a user