@@ -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