@@ -15,6 +15,7 @@ import (
1515 "os"
1616 "runtime"
1717 "strings"
18+ "sync"
1819 "testing"
1920)
2021
@@ -1282,3 +1283,147 @@ 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+ defer listener .Close ()
1365+ serverAddr = listener .Addr ().String ()
1366+ wg .Done ()
1367+
1368+ nConn , err := listener .Accept ()
1369+ if err != nil {
1370+ return
1371+ }
1372+
1373+ conn , chans , reqs , err := NewServerConn (nConn , config )
1374+ if err != nil {
1375+ return
1376+ }
1377+ _ = conn .Close ()
1378+
1379+ var connWg sync.WaitGroup
1380+ connWg .Add (1 )
1381+ go func () {
1382+ defer connWg .Done ()
1383+ DiscardRequests (reqs )
1384+ }()
1385+ for newChannel := range chans {
1386+ newChannel .Reject (Prohibited ,
1387+ "testserver not accepting requests" )
1388+ }
1389+ connWg .Wait ()
1390+ }()
1391+
1392+ wg .Wait ()
1393+ return serverAddr , serverErr
1394+ }()
1395+ if err != nil {
1396+ t .Fatalf ("failed to start testserver: %v" , err )
1397+ }
1398+
1399+ // Connect to testserver expect KeyboardInteractive() to be not called and
1400+ // PasswordCallback() to be called.
1401+ passwordCallbackCalled := false
1402+ cfg := & ClientConfig {
1403+ User : "testuser" ,
1404+ Auth : []AuthMethod {
1405+ RetryableAuthMethod (KeyboardInteractive (func (name ,
1406+ instruction string , questions []string ,
1407+ echos []bool ) ([]string , error ) {
1408+ t .Errorf ("unexpected call to KeyboardInteractive()" )
1409+ return []string {clientPassword }, nil
1410+ }), maxAuthTries ),
1411+ RetryableAuthMethod (PasswordCallback (func () (secret string ,
1412+ err error ) {
1413+ t .Logf ("PasswordCallback()" )
1414+ passwordCallbackCalled = true
1415+ return clientPassword , nil
1416+ }), maxAuthTries ),
1417+ },
1418+ HostKeyCallback : InsecureIgnoreHostKey (),
1419+ }
1420+
1421+ conn , _ := Dial ("tcp" , dst , cfg )
1422+ if conn != nil {
1423+ conn .Close ()
1424+ }
1425+
1426+ if ! passwordCallbackCalled {
1427+ t .Errorf ("expected PasswordCallback() to be called" )
1428+ }
1429+ }
0 commit comments