Skip to content

Commit 7cfb916

Browse files
samiponkanengopherbot
authored andcommitted
ssh: return unexpected msg error when server fails keyboard-interactive auth early
Seems the OpenSSH server running on windows fails keyboard-interactive auth this way without sending any prompt to client. In such case the golang ssh client should not retry keyboard-interactive auth when the auth method is wrapped in a RetryableAuthMethod(). Rather the auth method should be immediately marked as tried&failed and the client auth process should move on to next available and acceptable auth method. Fixes golang/go#67855 Change-Id: I6c64ae58ff8325774e37af716601b112f8833d8f GitHub-Last-Rev: 7fafc4d GitHub-Pull-Request: #297 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/590956 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Nicola Murino <nicola.murino@gmail.com> Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
1 parent b61b08d commit 7cfb916

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

ssh/client_auth.go

+5
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
555555
}
556556

557557
gotMsgExtInfo := false
558+
gotUserAuthInfoRequest := false
558559
for {
559560
packet, err := c.readPacket()
560561
if err != nil {
@@ -585,6 +586,9 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
585586
if msg.PartialSuccess {
586587
return authPartialSuccess, msg.Methods, nil
587588
}
589+
if !gotUserAuthInfoRequest {
590+
return authFailure, msg.Methods, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
591+
}
588592
return authFailure, msg.Methods, nil
589593
case msgUserAuthSuccess:
590594
return authSuccess, nil, nil
@@ -596,6 +600,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
596600
if err := Unmarshal(packet, &msg); err != nil {
597601
return authFailure, nil, err
598602
}
603+
gotUserAuthInfoRequest = true
599604

600605
// Manually unpack the prompt/echo pairs.
601606
rest := msg.Prompts

ssh/client_auth_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -1293,3 +1293,92 @@ func TestCertAuthOpenSSHCompat(t *testing.T) {
12931293
t.Fatalf("unable to dial remote side: %s", err)
12941294
}
12951295
}
1296+
1297+
func TestKeyboardInteractiveAuthEarlyFail(t *testing.T) {
1298+
const maxAuthTries = 2
1299+
1300+
c1, c2, err := netPipe()
1301+
if err != nil {
1302+
t.Fatalf("netPipe: %v", err)
1303+
}
1304+
defer c1.Close()
1305+
defer c2.Close()
1306+
1307+
// Start testserver
1308+
serverConfig := &ServerConfig{
1309+
MaxAuthTries: maxAuthTries,
1310+
KeyboardInteractiveCallback: func(c ConnMetadata,
1311+
client KeyboardInteractiveChallenge) (*Permissions, error) {
1312+
// Fail keyboard-interactive authentication early before
1313+
// any prompt is sent to client.
1314+
return nil, errors.New("keyboard-interactive auth failed")
1315+
},
1316+
PasswordCallback: func(c ConnMetadata,
1317+
pass []byte) (*Permissions, error) {
1318+
if string(pass) == clientPassword {
1319+
return nil, nil
1320+
}
1321+
return nil, errors.New("password auth failed")
1322+
},
1323+
}
1324+
serverConfig.AddHostKey(testSigners["rsa"])
1325+
1326+
serverDone := make(chan struct{})
1327+
go func() {
1328+
defer func() { serverDone <- struct{}{} }()
1329+
conn, chans, reqs, err := NewServerConn(c2, serverConfig)
1330+
if err != nil {
1331+
return
1332+
}
1333+
_ = conn.Close()
1334+
1335+
discarderDone := make(chan struct{})
1336+
go func() {
1337+
defer func() { discarderDone <- struct{}{} }()
1338+
DiscardRequests(reqs)
1339+
}()
1340+
for newChannel := range chans {
1341+
newChannel.Reject(Prohibited,
1342+
"testserver not accepting requests")
1343+
}
1344+
1345+
<-discarderDone
1346+
}()
1347+
1348+
// Connect to testserver, expect KeyboardInteractive() to be not called,
1349+
// PasswordCallback() to be called and connection to succeed.
1350+
passwordCallbackCalled := false
1351+
clientConfig := &ClientConfig{
1352+
User: "testuser",
1353+
Auth: []AuthMethod{
1354+
RetryableAuthMethod(KeyboardInteractive(func(name,
1355+
instruction string, questions []string,
1356+
echos []bool) ([]string, error) {
1357+
t.Errorf("unexpected call to KeyboardInteractive()")
1358+
return []string{clientPassword}, nil
1359+
}), maxAuthTries),
1360+
RetryableAuthMethod(PasswordCallback(func() (secret string,
1361+
err error) {
1362+
t.Logf("PasswordCallback()")
1363+
passwordCallbackCalled = true
1364+
return clientPassword, nil
1365+
}), maxAuthTries),
1366+
},
1367+
HostKeyCallback: InsecureIgnoreHostKey(),
1368+
}
1369+
1370+
conn, _, _, err := NewClientConn(c1, "", clientConfig)
1371+
if err != nil {
1372+
t.Errorf("unexpected NewClientConn() error: %v", err)
1373+
}
1374+
if conn != nil {
1375+
conn.Close()
1376+
}
1377+
1378+
// Wait for server to finish.
1379+
<-serverDone
1380+
1381+
if !passwordCallbackCalled {
1382+
t.Errorf("expected PasswordCallback() to be called")
1383+
}
1384+
}

0 commit comments

Comments
 (0)