Skip to content

Commit 732afad

Browse files
committed
crypto/ssh: return unexpected msg error when server fails keyboard-interactive auth early
1 parent d4e7c9c commit 732afad

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-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

+144
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"runtime"
1717
"strings"
18+
"sync"
1819
"testing"
1920
)
2021

@@ -1282,3 +1283,146 @@ func TestCertAuthOpenSSHCompat(t *testing.T) {
12821283
t.Fatalf("unable to dial remote side: %s", err)
12831284
}
12841285
}
1286+
1287+
func TestKeyboardInteractiveAuthEarlyFail(t *testing.T) {
1288+
const maxAuthTries = 2
1289+
1290+
// Start testserver
1291+
dst, err := func() (string, error) {
1292+
var serverAddr string
1293+
var serverErr error
1294+
var wg sync.WaitGroup
1295+
wg.Add(1)
1296+
1297+
go func() {
1298+
config := &ServerConfig{
1299+
MaxAuthTries: maxAuthTries,
1300+
KeyboardInteractiveCallback: func(c ConnMetadata,
1301+
client KeyboardInteractiveChallenge) (*Permissions, error) {
1302+
// Fail keyboard-interactive authentication early before
1303+
// any prompt is sent to client.
1304+
return nil, errors.New("keyboard-interactive auth failed")
1305+
},
1306+
PasswordCallback: func(c ConnMetadata,
1307+
pass []byte) (*Permissions, error) {
1308+
if string(pass) == clientPassword {
1309+
return nil, nil
1310+
}
1311+
return nil, errors.New("password auth failed")
1312+
},
1313+
}
1314+
1315+
// Use a static hostkey.
1316+
// This key has been generated with following ssh-keygen command
1317+
// and used exclusively in this unit test:
1318+
// $ ssh-keygen -t RSA -b 2048 -f /tmp/static_hostkey \
1319+
// -C "Static RSA hostkey for golang.org/x/crypto/ssh unit test"
1320+
1321+
const privKeyData = `-----BEGIN OPENSSH PRIVATE KEY-----
1322+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
1323+
NhAAAAAwEAAQAAAQEAsg9ZsQ3vbWppRLe2NzzUIV5NcPbpO5EBvLyzfItURKmYHmwa6aoy
1324+
P34fmEG3BVVx5f1pgw54Rdaic4hG2p2nvGIijTktDxFz+tREwwMfywpwrlJbGslUTi0TKO
1325+
jTWkyDACjMwo65yXbsSZLq+8rGD3uinf3Vq1bVlaEckmClhWMLTsynr/YpdF2I/+InPCep
1326+
1AuaWj1dHFNL8fbWXd8xNONumkMS1i6xtP3PnzdUqN+DuoGy26x5ic3qxWVrUp69/J2J42
1327+
/B0WEYbATAfCQiL8iGeeM7Ll45GASI4r93uDnXropnHQy+RThG5BFBRiAqmzN6kncri/k5
1328+
65p63Jb33QAAA/AX6WXzF+ll8wAAAAdzc2gtcnNhAAABAQCyD1mxDe9tamlEt7Y3PNQhXk
1329+
1w9uk7kQG8vLN8i1REqZgebBrpqjI/fh+YQbcFVXHl/WmDDnhF1qJziEbanae8YiKNOS0P
1330+
EXP61ETDAx/LCnCuUlsayVROLRMo6NNaTIMAKMzCjrnJduxJkur7ysYPe6Kd/dWrVtWVoR
1331+
ySYKWFYwtOzKev9il0XYj/4ic8J6nUC5paPV0cU0vx9tZd3zE0426aQxLWLrG0/c+fN1So
1332+
34O6gbLbrHmJzerFZWtSnr38nYnjb8HRYRhsBMB8JCIvyIZ54zsuXjkYBIjiv3e4Odeuim
1333+
cdDL5FOEbkEUFGICqbM3qSdyuL+TnrmnrclvfdAAAAAwEAAQAAAQAJjuVjqbnWh8XK2InB
1334+
gVRpziQeEkMG3YvYU9DWuKv3W5s81tTDAk3cNqr/g1eNw75veCD31gkCxrjFtuUGyzu70x
1335+
DDv/P5QRiWuFpQlZRZU+Akm2skjvYllCnZIlZmHIFTutzy/LJgbC/W6zoN9h6Xqi1aicu0
1336+
fN7OP23HNcTs2gNAhzidpDMGOAxdzpcnXeQ3JCFOcv4LSi7TgmJHvLv1AgXQggSHeB8Nf1
1337+
DvS+E86O9Wm6Xj+OKRiEgrRlngNQQ0om9yhLmUMat7Nw3hn2ZSb2+ByaaYuDfQ6FAG9nno
1338+
HjxaqSHF83/8fKXJW/wku6ee3hvjTBNuuvUCLkLF48DBAAAAgQDUV28GyoWTqIR+uOa/4t
1339+
OyFjfTtTdQ6fnLaqxbiwOaPz6SXCiwE6qIEZF5Ll5QK+7tMPDKWcOak2uGaSUHcjaEXVh0
1340+
kaKwFqiFIBY3IGwCuyixjJITl+g+48SrFgLWNrNpwVrw1NOv+iBz+z6SGqcsi5f030qyzv
1341+
O2P2wkSZkQqgAAAIEA5UB0F2+Lh63JHvdoUDZutBvTgrsjpwiReIuy+7WgxeGHe54DaXTY
1342+
HMOORZM6unDRvi6uBBul7ON9Cs20UGeER+ZMA2SKXTicb0UwJuCYKKO8AIlMP0ykyfaF1p
1343+
ZJw9DciKEu92jx+e2MdhEOnIIt1jQ9e5UIMLsI/SicnseTDLUAAACBAMbV19NIEhjLczBp
1344+
MEYSBGDnyN6HWyrHCuCFLSpnHWePd6/apExGE049YRmAgLYaycmnjX9VJRKoumk9zYv1zu
1345+
W9WTuewZVuLjLpOq/4mO5/jOQortL1dUigiiA7ZTGazTFMHwG+fZdfVqgSxbMfEU2rYhND
1346+
S0UghNmRaqbzNl+JAAAAOFN0YXRpYyBSU0EgaG9zdGtleSBmb3IgZ29sYW5nLm9yZy94L2
1347+
NyeXB0by9zc2ggdW5pdCB0ZXN0AQI=
1348+
-----END OPENSSH PRIVATE KEY-----`
1349+
1350+
private, err := ParsePrivateKey([]byte(privKeyData))
1351+
if err != nil {
1352+
serverErr = err
1353+
wg.Done()
1354+
return
1355+
}
1356+
config.AddHostKey(private)
1357+
1358+
listener, err := net.Listen("tcp", "127.0.0.1:")
1359+
if err != nil {
1360+
serverErr = err
1361+
wg.Done()
1362+
return
1363+
}
1364+
serverAddr = listener.Addr().String()
1365+
wg.Done()
1366+
1367+
nConn, err := listener.Accept()
1368+
if err != nil {
1369+
return
1370+
}
1371+
1372+
conn, chans, reqs, err := NewServerConn(nConn, config)
1373+
if err != nil {
1374+
return
1375+
}
1376+
_ = conn.Close()
1377+
1378+
var connWg sync.WaitGroup
1379+
connWg.Add(1)
1380+
go func() {
1381+
defer connWg.Done()
1382+
DiscardRequests(reqs)
1383+
}()
1384+
for newChannel := range chans {
1385+
newChannel.Reject(Prohibited,
1386+
"testserver not accepting requests")
1387+
}
1388+
connWg.Wait()
1389+
}()
1390+
1391+
wg.Wait()
1392+
return serverAddr, serverErr
1393+
}()
1394+
if err != nil {
1395+
t.Fatalf("failed to start testserver: %v", err)
1396+
}
1397+
1398+
// Connect to testserver expect KeyboardInteractive() to be not called and
1399+
// PasswordCallback() to be called.
1400+
passwordCallbackCalled := false
1401+
cfg := &ClientConfig{
1402+
User: "testuser",
1403+
Auth: []AuthMethod{
1404+
RetryableAuthMethod(KeyboardInteractive(func(name,
1405+
instruction string, questions []string,
1406+
echos []bool) ([]string, error) {
1407+
t.Errorf("unexpected call to KeyboardInteractive()")
1408+
return []string{clientPassword}, nil
1409+
}), maxAuthTries),
1410+
RetryableAuthMethod(PasswordCallback(func() (secret string,
1411+
err error) {
1412+
t.Logf("PasswordCallback()")
1413+
passwordCallbackCalled = true
1414+
return clientPassword, nil
1415+
}), maxAuthTries),
1416+
},
1417+
HostKeyCallback: InsecureIgnoreHostKey(),
1418+
}
1419+
1420+
conn, _ := Dial("tcp", dst, cfg)
1421+
if conn != nil {
1422+
conn.Close()
1423+
}
1424+
1425+
if !passwordCallbackCalled {
1426+
t.Errorf("expected PasswordCallback() to be called")
1427+
}
1428+
}

0 commit comments

Comments
 (0)