diff --git a/binaries/puttycac-0.77u2-installer.msi b/binaries/puttycac-0.77u2-installer.msi deleted file mode 100644 index 9f187f0c..00000000 Binary files a/binaries/puttycac-0.77u2-installer.msi and /dev/null differ diff --git a/binaries/puttycac-0.77u2.zip b/binaries/puttycac-0.77u2.zip deleted file mode 100644 index 7ad797ee..00000000 Binary files a/binaries/puttycac-0.77u2.zip and /dev/null differ diff --git a/binaries/puttycac-0.78-installer.msi b/binaries/puttycac-0.78-installer.msi new file mode 100644 index 00000000..df1c33b4 Binary files /dev/null and b/binaries/puttycac-0.78-installer.msi differ diff --git a/binaries/puttycac-0.78.zip b/binaries/puttycac-0.78.zip new file mode 100644 index 00000000..602a735d Binary files /dev/null and b/binaries/puttycac-0.78.zip differ diff --git a/binaries/puttycac-64bit-0.77u2-installer.msi b/binaries/puttycac-64bit-0.77u2-installer.msi deleted file mode 100644 index cd403e65..00000000 Binary files a/binaries/puttycac-64bit-0.77u2-installer.msi and /dev/null differ diff --git a/binaries/puttycac-64bit-0.77u2.zip b/binaries/puttycac-64bit-0.77u2.zip deleted file mode 100644 index a9264e19..00000000 Binary files a/binaries/puttycac-64bit-0.77u2.zip and /dev/null differ diff --git a/binaries/puttycac-64bit-0.78-installer.msi b/binaries/puttycac-64bit-0.78-installer.msi new file mode 100644 index 00000000..219146ce Binary files /dev/null and b/binaries/puttycac-64bit-0.78-installer.msi differ diff --git a/binaries/puttycac-64bit-0.78.zip b/binaries/puttycac-64bit-0.78.zip new file mode 100644 index 00000000..bed21268 Binary files /dev/null and b/binaries/puttycac-64bit-0.78.zip differ diff --git a/binaries/puttycac-hash.txt b/binaries/puttycac-hash.txt index d662b232..48c7f174 100644 --- a/binaries/puttycac-hash.txt +++ b/binaries/puttycac-hash.txt @@ -1,81 +1,81 @@ Algorithm Hash Path --------- ---- ---- -SHA256 59E9281BFAEA97B70340C7CEBD9819DC3A936365D5C5A3A3035A67EFBB07F279 x64\pageant.exe -SHA256 3D6EDED0E445143585961C2F41764561C5AFE7EAAFD2B906E73954BE82F8DC45 x64\plink.exe -SHA256 EA66470D23043B36A50F2715ECCE8ADFFE0CAE90271FF4E2153EDB38FC1949DA x64\pscp.exe -SHA256 E196A6631518F78E6E104D4879C7879A62CAB2EFCA93C114AFD9CF66C0C5A765 x64\psftp.exe -SHA256 CD2A4E6EC2DEFBA414803D10695B31DE90071ACF1093B401694A88D8A1BF20A4 x64\pterm.exe -SHA256 0DFC2EE46A66B6C999E24B4565A049FEFD63DD41671BFCD53A23563230F4B5EB x64\putty.exe -SHA256 47E82AE6876A986030F24569FEA6E26DA58B6838EE48E0EDBF5837B5AA2A2D05 x64\puttygen.exe -SHA256 2F2B4A8860FE7F1C14A03909C5A6D6784A37D858EA6B9765061F4A3F99FEE526 x64\puttyimp.exe -SHA256 5E409A2E68AE8D6AAE31E270E020260F0F73C7FF0792494E239BA05B51866288 x64\puttytel.exe -SHA256 694E194A039CD2C8CBBB53078B004F58633F7CC142E3FD92AA70D49FAF004B42 x86\pageant.exe -SHA256 1EFDEE12335551C6683D6C9B657018C8B0422DFC2BB7498C0218F5F42A52603B x86\plink.exe -SHA256 C2BFC26AC770AA981B8D82AD1F3C7D5B39FE6F59B71778DEB83416401FA71D40 x86\pscp.exe -SHA256 A27215260EEC195BB52B33161AB4B08F6CA814D9D032CB5F8BD0ADE28E92FCD1 x86\psftp.exe -SHA256 639C56980DFDC5BFD98872FEA3C41133BA02F641C289D7BD76C2CE7FD5E0422F x86\pterm.exe -SHA256 5DCD23CB3A7AC62BF9A9372F8136367EEDF230177C389C728E2A78AB9EA68FE8 x86\putty.exe -SHA256 FB808167E2C183173A26D6647CA81D2902D41AAFEB8D8B1A9A09BBB3D3DDA6ED x86\puttygen.exe -SHA256 AAC441836AF3E8D1A213C8C79D00A653E702BBDD1D9B7708B0E8FDE503F315E3 x86\puttyimp.exe -SHA256 110071004EEC95FDF2B6F7CB34BA5EA9A984E6B65D7B0CBD21CFAF22A4A37892 x86\puttytel.exe -SHA256 5590A2B63F5AC87FFEF6BAFBF45AD4B97EE32411B3CE2FCBFBB257E7D34C6175 puttycac-0.77u2-installer.msi -SHA256 59FDF55B3498D549A4F793CF3005456DB8EDD2914A22F54760477E38AC0E9B07 puttycac-0.77u2.zip -SHA256 E17EEBC4943F90AC4A48DD99A9A60000FD3908F92F562979F4189D2E8767B9D5 puttycac-64bit-0.77u2-installer.msi -SHA256 BF451F06FD5BF6208FBD5DECB7F9D89C2E7A806F3501634CE6AD6B0983BC1065 puttycac-64bit-0.77u2.zip +SHA256 67076D98F34C5F64CB7E37C0589D009201AD2E57E5B499ABE219BF29E2A303A7 x64\pageant.exe +SHA256 9252A3FB6CAF3DE80F32BE6C5B361A9F66490F6955C58093698A8DAC06A0566D x64\plink.exe +SHA256 65BCC24DFE73645E75DCC4FB1E95195F59B2023550CEA5DF5F4FD8E28F6FA543 x64\pscp.exe +SHA256 74D3FCE74B9BC41FC6D3B121746E4B4BEBFD90F4A8A764B0270B77B2AC4253BE x64\psftp.exe +SHA256 6BE32633D563414B5F4008AA6A42814AAF187F69739D3FD8A172A46E6A80CD63 x64\pterm.exe +SHA256 F679749D9BEE5882B26AF904665445DDEFA463575BB07E68DCDBCC300D623BAE x64\putty.exe +SHA256 F0BBF17DF4FF8D4AA44E0C510379CABE5300351ABF5AEB14A386812CDABECF3E x64\puttygen.exe +SHA256 17BF5D98FCF579CF6FA373FC5441B11FADBF1AE11E628319C683C465691FB874 x64\puttyimp.exe +SHA256 952D63E8C55C5040538FA145180509C08D2818FE6BD8E3EA6800EA8B724561ED x64\puttytel.exe +SHA256 7A98D56BD528F82903682CA38C8A614C930621CF3F9DB3F5DE421FB217A3DD91 x86\pageant.exe +SHA256 532F50D838A1F82EA552210B9C554F5BD1DF3C7F8FA60D2066385F05E82FC4D2 x86\plink.exe +SHA256 E35E7008FD094EA1AC16D7B3B428784292F7234C5D1AC6693E7C9C707C84C63F x86\pscp.exe +SHA256 7248A491FD9DC6691A0FE4E685E8E400074C255FFDB49B43605EBED11BFCAAF2 x86\psftp.exe +SHA256 3543038AA2AE857D84F9EE503AC3049DD80BC60FC57CBB6062C93717F849F3ED x86\pterm.exe +SHA256 89AA92DC8EFC02456029C2B51126E41C9D1422F5B187482DE573B80584940845 x86\putty.exe +SHA256 5BA5D91AD44B9CE0E60614295A9A735B625FD336EF4EA349E6815D372EC946F7 x86\puttygen.exe +SHA256 5AA475B7AB0C9354739355FCB058E96E352BE865F3EE24D123158E86B49D4D3A x86\puttyimp.exe +SHA256 6B5A233926C3D3C1597168AC60E170460CA6F5B72092A424736AD7DAAC5920A7 x86\puttytel.exe +SHA256 5B788BBDEE9D1926F39576EB786E0CDB207B599788B70634AF87203C0F9149F4 puttycac-0.78-installer.msi +SHA256 09752907E3A923DDB0378509DC5F6532AA48DC2790BA076C9F8CC884975F52ED puttycac-0.78.zip +SHA256 E43DCD6D976CC2132F849DDB5F20EBF82BD2FDB6B509B90D70B14FF113FE8866 puttycac-64bit-0.78-installer.msi +SHA256 DBBE9325C1987CA1DC3F8CE1BEBE9BF816D78B7C59281160FC11DB0BE0E64704 puttycac-64bit-0.78.zip Algorithm Hash Path --------- ---- ---- -SHA1 3811B46C568475687CD60374F620651B4C44087F x64\pageant.exe -SHA1 6B9AE2A4B8634FC6096E595CEFD9D73FA785D945 x64\plink.exe -SHA1 241306ED9BCCFFC4E51A940ABC65317AD3AE5D54 x64\pscp.exe -SHA1 1D9E22360DF010CE9D9BF6648BA31C12F705546B x64\psftp.exe -SHA1 FB3FB36DABA51D19632A8682D8997B73F606A187 x64\pterm.exe -SHA1 F8841AC03C4B1BB9AD3C3F15B757EC917184C467 x64\putty.exe -SHA1 6451423CBA4E8FB02B1D1876442EA88192A1559D x64\puttygen.exe -SHA1 578358F1684FFB7F3C60BF2FFA17ABB6470F8E48 x64\puttyimp.exe -SHA1 6C8D4B0D66EB1A0500AA3384358DF335D8E7A46A x64\puttytel.exe -SHA1 C7FFDB97FD8A7DC4B974F60E8EC9E3C6CA47269E x86\pageant.exe -SHA1 9226C5DA104577D6A6A867C30C0C5518645D23A7 x86\plink.exe -SHA1 CD2913E9F6E91C01744301FA1A4E43E03D931BF8 x86\pscp.exe -SHA1 958020F5D5A73FD6BBBCD01926320843A2918281 x86\psftp.exe -SHA1 F91989C58403008B67B1C06E2207AB8E40F29A35 x86\pterm.exe -SHA1 8A775FCF68362F689B3D959CD7468D415C9D1502 x86\putty.exe -SHA1 3C64600756E4588BC30214F8ADA2CF2F29C47CA8 x86\puttygen.exe -SHA1 A98F7088D04BD3631BCF0C48AA8B38898AF96933 x86\puttyimp.exe -SHA1 58682032E4FD09F3E6F5455B8206EEAE781C9024 x86\puttytel.exe -SHA1 B6CAE6DAECEA6F74768EA3E88232762E4D42B324 puttycac-0.77u2-installer.msi -SHA1 653F7D3C41A543C3AACEBD30038033E65BE3A15D puttycac-0.77u2.zip -SHA1 FABD910F2547D7B7D44A02FA23E19862B225F695 puttycac-64bit-0.77u2-installer.msi -SHA1 6E60A664C064497E37FF83D5B6F54FB33C2B7855 puttycac-64bit-0.77u2.zip +SHA1 380E0F1FB711FEE33809038EEAB7E4F8389C97F0 x64\pageant.exe +SHA1 C825440447D5186731A6E60506A8C38015BC0206 x64\plink.exe +SHA1 15CFD87043E4050BF119045B45447CA8C6038DE6 x64\pscp.exe +SHA1 68AACBBB4AE41F96DF34EEBC6BD6B9268C2A51C4 x64\psftp.exe +SHA1 19074586EFA9164DC63B4A242E91BB80B448B7C8 x64\pterm.exe +SHA1 03C6AA8BDD603402BBBC7EB43FAB664EC46F990B x64\putty.exe +SHA1 76FF6C3B0055D4B2BA62E38DF90688B8D5E7BDA0 x64\puttygen.exe +SHA1 BC77E83A89E38C74CA24C1700C69C7943570E721 x64\puttyimp.exe +SHA1 7DE1037B656D515DEE75DE51022708EAF3676B2A x64\puttytel.exe +SHA1 15B051365ED6D17A34C6E5B63271A925889A4B79 x86\pageant.exe +SHA1 E0580CEE1108DE613CBE6F272967015F6C05F4A6 x86\plink.exe +SHA1 B2D4072850C7F74BFE9FB659D66148B22E862B06 x86\pscp.exe +SHA1 3843AB09935C79589EB5378A81E2B0C226353CD3 x86\psftp.exe +SHA1 FC10B09FF9D2E04640EC7D4BDE38A82D8717C373 x86\pterm.exe +SHA1 F105C1C3F790E32C384EE68D71C9147C340D97C8 x86\putty.exe +SHA1 F0439E022D3E85AA18640D2AAF1568493834557A x86\puttygen.exe +SHA1 1397300FD9D49DB7B4169DA9DF57D237D6540D51 x86\puttyimp.exe +SHA1 2357309D00144086F0049F269884DB061B8B5AB3 x86\puttytel.exe +SHA1 77FE38CDA6A216D1F439525A70BA8AE583DD1A46 puttycac-0.78-installer.msi +SHA1 9495E5BF65D61D141CED904F49687D89D8F28ECE puttycac-0.78.zip +SHA1 04FB55D8A5F20A800EA686C17BD71B1584F783B8 puttycac-64bit-0.78-installer.msi +SHA1 C109B23C2FA0771472590DFCA5F92BF12AA8C853 puttycac-64bit-0.78.zip Algorithm Hash Path --------- ---- ---- -MD5 76184817D3652C4345A913E83E5DD7D0 x64\pageant.exe -MD5 0BE0E2297CB7B49D85F366B5FCCAEA69 x64\plink.exe -MD5 6B5DB12850A4A3DDDFEF7619390823ED x64\pscp.exe -MD5 4ED5D6BA0EAF332C08788166A3822095 x64\psftp.exe -MD5 8D9F9E40B43D195E6472691727E40C45 x64\pterm.exe -MD5 42B9CEE50487E31C01EA329A9BD5B0FD x64\putty.exe -MD5 C72093FE27D1271471367A3EA1410D9C x64\puttygen.exe -MD5 82055A0E77645946BDABA9CAF3536C46 x64\puttyimp.exe -MD5 30E2267637BB3D8A11878A7EC87FD193 x64\puttytel.exe -MD5 B5EFCC811D5A6AF06F6DF826688F704D x86\pageant.exe -MD5 A325198C53EE540533723E9388F7D719 x86\plink.exe -MD5 F40AC38439655C9289F4BA7790560143 x86\pscp.exe -MD5 6E02371BDD6F8DA8B01E9BC826B4F773 x86\psftp.exe -MD5 8DEEE02FC9C6E3AB0BA1C5FFBF57FC7C x86\pterm.exe -MD5 86E904C3A442BCCF8340A042813A276C x86\putty.exe -MD5 1C88025D3C945C38C4EDD2D232E476B0 x86\puttygen.exe -MD5 5561332D84C89FCAE03D8F3C7D41D009 x86\puttyimp.exe -MD5 4B89B4A9C795683C83CE341A576E2B95 x86\puttytel.exe -MD5 EBE8492A5346D4A3835565630D2129BC puttycac-0.77u2-installer.msi -MD5 EE9BF35E14B6E75847B01B9D4EAECBB2 puttycac-0.77u2.zip -MD5 F0F535813886593531A4C40C5800787C puttycac-64bit-0.77u2-installer.msi -MD5 6B364146C7E362C4862D8D99C1B97374 puttycac-64bit-0.77u2.zip +MD5 F82B6FCAA1A5E2F313E0FD3DA15A9508 x64\pageant.exe +MD5 5444B7A3EE84A110D766676D4BC29DAF x64\plink.exe +MD5 287BDF73560425E25E89A88A9BC76E80 x64\pscp.exe +MD5 C1A656D8E313FAD22757FB0AC08A63F2 x64\psftp.exe +MD5 01CCEA300E749B184124F348582898CB x64\pterm.exe +MD5 C35CCF7AC5B170B8D9824982D5EB369F x64\putty.exe +MD5 BAD369A488CB27841A9970D0AB4FDCFC x64\puttygen.exe +MD5 F38675971C9B24F67809DDA5A7F2A2FD x64\puttyimp.exe +MD5 CD28574EEC5372DAA855A05E3816CC51 x64\puttytel.exe +MD5 493BE54C132AC5EEF4B489A415FEF3D2 x86\pageant.exe +MD5 65439F77E8076FA656E79A0638067C2E x86\plink.exe +MD5 D5AC78B5B8F80EE9A20A16ED54E4FE15 x86\pscp.exe +MD5 F7EBC4185D9F7A924C8C663C1B7DC0C9 x86\psftp.exe +MD5 0ABD4E4D15E5CA86F6EE511BFE12BC78 x86\pterm.exe +MD5 71C87BC6F593FB560ADE6A1D5A34DCB6 x86\putty.exe +MD5 F6CCC4B6065B3249C3780D808F61FD1D x86\puttygen.exe +MD5 0B8BF860B850AC0A898282AF9443586F x86\puttyimp.exe +MD5 E33D15309F6C0387179BAE7A7F1EC02B x86\puttytel.exe +MD5 B91F5D35233D1C70355C6D28F48819A7 puttycac-0.78-installer.msi +MD5 E3003C343F41BE91408B90FCF348F094 puttycac-0.78.zip +MD5 F23D034C833CAFB5457CC4AE9E3F5874 puttycac-64bit-0.78-installer.msi +MD5 7358F10257677ADEDD8E9099D83C4D1C puttycac-64bit-0.78.zip diff --git a/binaries/x64/pageant.exe b/binaries/x64/pageant.exe index 9e5ff0db..decef9e4 100644 Binary files a/binaries/x64/pageant.exe and b/binaries/x64/pageant.exe differ diff --git a/binaries/x64/plink.exe b/binaries/x64/plink.exe index 90ffe51a..5c4b0717 100644 Binary files a/binaries/x64/plink.exe and b/binaries/x64/plink.exe differ diff --git a/binaries/x64/pscp.exe b/binaries/x64/pscp.exe index 7c0ecbf5..9bc6d0c4 100644 Binary files a/binaries/x64/pscp.exe and b/binaries/x64/pscp.exe differ diff --git a/binaries/x64/psftp.exe b/binaries/x64/psftp.exe index 38d92be7..00cf8540 100644 Binary files a/binaries/x64/psftp.exe and b/binaries/x64/psftp.exe differ diff --git a/binaries/x64/pterm.exe b/binaries/x64/pterm.exe index aea0a60a..1a83db54 100644 Binary files a/binaries/x64/pterm.exe and b/binaries/x64/pterm.exe differ diff --git a/binaries/x64/putty.exe b/binaries/x64/putty.exe index 92b0ebee..dfd41f6d 100644 Binary files a/binaries/x64/putty.exe and b/binaries/x64/putty.exe differ diff --git a/binaries/x64/puttygen.exe b/binaries/x64/puttygen.exe index d624224b..41cc0464 100644 Binary files a/binaries/x64/puttygen.exe and b/binaries/x64/puttygen.exe differ diff --git a/binaries/x64/puttyimp.exe b/binaries/x64/puttyimp.exe index b63fc140..484a1259 100644 Binary files a/binaries/x64/puttyimp.exe and b/binaries/x64/puttyimp.exe differ diff --git a/binaries/x64/puttytel.exe b/binaries/x64/puttytel.exe index 3fa0549d..67287fc8 100644 Binary files a/binaries/x64/puttytel.exe and b/binaries/x64/puttytel.exe differ diff --git a/binaries/x86/pageant.exe b/binaries/x86/pageant.exe index 4ea6dcd7..b9767a7b 100644 Binary files a/binaries/x86/pageant.exe and b/binaries/x86/pageant.exe differ diff --git a/binaries/x86/plink.exe b/binaries/x86/plink.exe index 732fbac5..aae9a596 100644 Binary files a/binaries/x86/plink.exe and b/binaries/x86/plink.exe differ diff --git a/binaries/x86/pscp.exe b/binaries/x86/pscp.exe index 903f4664..3d2d9839 100644 Binary files a/binaries/x86/pscp.exe and b/binaries/x86/pscp.exe differ diff --git a/binaries/x86/psftp.exe b/binaries/x86/psftp.exe index 04d71fb5..787d5533 100644 Binary files a/binaries/x86/psftp.exe and b/binaries/x86/psftp.exe differ diff --git a/binaries/x86/pterm.exe b/binaries/x86/pterm.exe index 407e637b..63965606 100644 Binary files a/binaries/x86/pterm.exe and b/binaries/x86/pterm.exe differ diff --git a/binaries/x86/putty.exe b/binaries/x86/putty.exe index f4fdbf56..35c69686 100644 Binary files a/binaries/x86/putty.exe and b/binaries/x86/putty.exe differ diff --git a/binaries/x86/puttygen.exe b/binaries/x86/puttygen.exe index 667e667c..b3da8f8e 100644 Binary files a/binaries/x86/puttygen.exe and b/binaries/x86/puttygen.exe differ diff --git a/binaries/x86/puttyimp.exe b/binaries/x86/puttyimp.exe index fadaae0b..35f32bb0 100644 Binary files a/binaries/x86/puttyimp.exe and b/binaries/x86/puttyimp.exe differ diff --git a/binaries/x86/puttytel.exe b/binaries/x86/puttytel.exe index 580cbd0b..1827ca46 100644 Binary files a/binaries/x86/puttytel.exe and b/binaries/x86/puttytel.exe differ diff --git a/code/.gitignore b/code/.gitignore index 02f43020..26985d7d 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -12,6 +12,7 @@ Win32/ cmake_install.cmake /shipped.txt *.dir/ +*.lib .dirstamp .deps .DS_Store @@ -81,6 +82,7 @@ Makefile /doc/*.hlp /doc/*.gid /doc/*.GID +/doc/*.chm /doc/*.log /doc/*.1 /doc/*.info @@ -138,3 +140,6 @@ Makefile /windows/*.msi /windows/*.wixobj /windows/*.wixpdb +!CMakeLists.txt +!/doc/putty.chm +!/contrib/**/*.lib \ No newline at end of file diff --git a/code/Buildscr b/code/Buildscr index bd3aad20..02de6432 100644 --- a/code/Buildscr +++ b/code/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 18136 # update this at every release +set Epoch 18293 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days @@ -55,7 +55,7 @@ ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcs ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid) -in putty/doc do echo "\\versionid $(Puttytextver)" > version.but +in putty/doc do echo "\\\\versionid $(Puttytextver)" > version.but # Set up the version string for use in the SSH connection greeting. # @@ -150,7 +150,7 @@ delegate - # library list), and no other Windows toolchain we build with is that # picky. So this ensures the Windows library structure continues to # work in the most difficult circumstance we expect it to encounter. -in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake +in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake -DSTRICT=ON in putty do make -j$(nproc) VERBOSE=1 enddelegate @@ -172,15 +172,25 @@ mkdir putty/windows/abuild64 # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DPUTTY_SUBSYSTEM_VERSION=5.01 -DSTRICT=ON in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +# The cmake mechanism used to set the subsystem version is a bit of a +# bodge (it depends on knowing how cmake set up all its build command +# variables), so just in case it breaks in future, double-check we +# really did get the subsystem version we wanted. +in putty/windows/build32 do objdump -x putty.exe > exe-headers.txt +in putty/windows/build32 do grep -Ex 'MajorOSystemVersion[[:space:]]+5' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MinorOSystemVersion[[:space:]]+1' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MajorSubsystemVersion[[:space:]]+5' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MinorSubsystemVersion[[:space:]]+1' exe-headers.txt + # Build experimental Arm Windows binaries. -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Make a list of the Windows binaries we're going to ship, so that we @@ -245,7 +255,7 @@ in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # # There's no installer to go with these, so they must also embed the # help file. -in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DSTRICT=ON in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Regenerate to-sign.txt with the 'old' binaries included. diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index f5234a9b..57f68aac 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -5,6 +5,7 @@ enable_language(CXX) endif() include(cmake/setup.cmake) + # Scan the docs directory first, so that when we start calling # installed_program(), we'll know if we have man pages available add_subdirectory(doc) @@ -17,6 +18,7 @@ add_library(utils STATIC ${GENERATED_COMMIT_C}) add_dependencies(utils cmake_commit_c) add_subdirectory(utils) +add_subdirectory(stubs) add_library(logging OBJECT logging.c) @@ -35,7 +37,7 @@ add_library(crypto STATIC add_subdirectory(crypto) add_library(network STATIC - stubs/nullplug.c errsock.c logging.c x11disp.c + errsock.c logging.c x11disp.c proxy/proxy.c proxy/http.c proxy/socks4.c @@ -57,7 +59,7 @@ add_library(guiterminal STATIC $) add_library(noterminal STATIC - stubs/noterm.c ldisc.c) + stubs/no-term.c ldisc.c) add_library(all-backends OBJECT pinger.c) @@ -96,6 +98,11 @@ add_executable(test_wildcard target_compile_definitions(test_wildcard PRIVATE TEST) target_link_libraries(test_wildcard utils ${platform_libraries}) +add_executable(test_cert_expr + utils/cert-expr.c) +target_compile_definitions(test_cert_expr PRIVATE TEST) +target_link_libraries(test_cert_expr utils ${platform_libraries}) + add_executable(bidi_gettype terminal/bidi_gettype.c) target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries}) @@ -137,7 +144,7 @@ installed_program(psftp) add_executable(psocks ${platform}/psocks.c psocks.c - stubs/norand.c + stubs/no-rand.c proxy/nocproxy.c proxy/nosshproxy.c ssh/portfwd.c) diff --git a/code/LICENCE b/code/LICENCE index ca9c6700..e90600eb 100644 --- a/code/LICENCE +++ b/code/LICENCE @@ -1,4 +1,4 @@ -PuTTY is copyright 1997-2022 Simon Tatham & Bryan Berns. +PuTTY CAC is copyright 1997-2022 Simon Tatham & Bryan Berns. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, diff --git a/code/README b/code/README index c996c3a8..979c02ba 100644 --- a/code/README +++ b/code/README @@ -24,9 +24,9 @@ pterm will still work, but not update the user login databases. Documentation (in various formats including Windows Help and Unix `man' pages) is built from the Halibut (`.but') files in the `doc' -subdirectory using `doc/Makefile'. If you aren't using one of our -source snapshots, you'll need to do this yourself. Halibut can be -found at . +subdirectory. If you aren't using one of our source snapshots, +you'll need to do this yourself. Halibut can be found at +. The PuTTY home web site is diff --git a/code/charset/localenc.c b/code/charset/localenc.c index 03340d6d..49719fbe 100644 --- a/code/charset/localenc.c +++ b/code/charset/localenc.c @@ -103,7 +103,7 @@ int charset_from_localenc(const char *name) p = name; q = localencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/code/charset/mimeenc.c b/code/charset/mimeenc.c index 2fec6d26..8a0203b3 100644 --- a/code/charset/mimeenc.c +++ b/code/charset/mimeenc.c @@ -207,7 +207,7 @@ int charset_from_mimeenc(const char *name) p = name; q = mimeencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/code/charset/sbcsgen.pl b/code/charset/sbcsgen.pl index bedf6b36..6012c933 100644 --- a/code/charset/sbcsgen.pl +++ b/code/charset/sbcsgen.pl @@ -38,7 +38,7 @@ my @sortpriority = (); while () { - chomp; + chomp; y/\r//d; if (/^charset (.*)$/) { $charsetname = $1; @vals = (); diff --git a/code/charset/xenc.c b/code/charset/xenc.c index 964ca6ff..24592ad1 100644 --- a/code/charset/xenc.c +++ b/code/charset/xenc.c @@ -82,7 +82,7 @@ int charset_from_xenc(const char *name) p = name; q = xencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/code/cmake/cmake.h.in b/code/cmake/cmake.h.in index 2041f096..5ad32515 100644 --- a/code/cmake/cmake.h.in +++ b/code/cmake/cmake.h.in @@ -8,6 +8,7 @@ #cmakedefine01 HAVE_WINRES_H #cmakedefine01 HAVE_WIN_H #cmakedefine01 HAVE_NO_STDINT_H +#cmakedefine01 HAVE_AFUNIX_H #cmakedefine01 HAVE_GCP_RESULTSW #cmakedefine01 HAVE_ADDDLLDIRECTORY #cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID @@ -42,13 +43,18 @@ #cmakedefine01 HAVE_CLOCK_MONOTONIC #cmakedefine01 HAVE_CLOCK_GETTIME #cmakedefine01 HAVE_SO_PEERCRED +#cmakedefine01 HAVE_NULLARY_SETPGRP +#cmakedefine01 HAVE_BINARY_SETPGRP #cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE #cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES #cmakedefine01 HAVE_AES_NI #cmakedefine01 HAVE_SHA_NI #cmakedefine01 HAVE_SHAINTRIN_H +#cmakedefine01 HAVE_CLMUL #cmakedefine01 HAVE_NEON_CRYPTO +#cmakedefine01 HAVE_NEON_PMULL +#cmakedefine01 HAVE_NEON_VADDQ_P128 #cmakedefine01 HAVE_NEON_SHA512 #cmakedefine01 HAVE_NEON_SHA512_INTRINSICS #cmakedefine01 USE_ARM64_NEON_H diff --git a/code/cmake/gitcommit.cmake b/code/cmake/gitcommit.cmake index 4326c01d..4d516f2f 100644 --- a/code/cmake/gitcommit.cmake +++ b/code/cmake/gitcommit.cmake @@ -45,6 +45,7 @@ if(OUTPUT_TYPE STREQUAL header) * Generated by cmake/gitcommit.cmake. */ +#include \"putty.h\" const char commitid[] = \"${commit}\"; ") elseif(OUTPUT_TYPE STREQUAL halibut) diff --git a/code/cmake/gtk.cmake b/code/cmake/gtk.cmake index 85ecb7de..13ff7705 100644 --- a/code/cmake/gtk.cmake +++ b/code/cmake/gtk.cmake @@ -3,7 +3,7 @@ set(PUTTY_GTK_VERSION "ANY" CACHE STRING "Which major version of GTK to build with") set_property(CACHE PUTTY_GTK_VERSION - PROPERTY STRINGS ANY 3 2 1) + PROPERTY STRINGS ANY 3 2 1 NONE) set(GTK_FOUND FALSE) @@ -74,6 +74,7 @@ if(GTK_FOUND) # Check for some particular Pango functions. function(pango_check_subscope) set(CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LINK_OPTIONS ${GTK_LDFLAGS}) set(CMAKE_REQUIRED_LIBRARIES ${GTK_LIBRARIES}) check_symbol_exists(pango_font_family_is_monospace "pango/pango.h" HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE) diff --git a/code/cmake/platforms/unix.cmake b/code/cmake/platforms/unix.cmake index 6a788cb4..4d056d0a 100644 --- a/code/cmake/platforms/unix.cmake +++ b/code/cmake/platforms/unix.cmake @@ -51,6 +51,21 @@ int main(int argc, char **argv) { cr.pid + cr.uid + cr.gid; }" HAVE_SO_PEERCRED) +check_c_source_compiles(" +#include +#include + +int main(int argc, char **argv) { + setpgrp(); +}" HAVE_NULLARY_SETPGRP) +check_c_source_compiles(" +#include +#include + +int main(int argc, char **argv) { + setpgrp(0, 0); +}" HAVE_BINARY_SETPGRP) + if(HAVE_GETADDRINFO AND PUTTY_IPV6) set(NO_IPV6 OFF) else() @@ -65,21 +80,27 @@ endif() include(cmake/gtk.cmake) -# See if we have X11 available. This requires libX11 itself, and also -# the GDK integration to X11. -find_package(X11) - -function(check_x11) - list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) - check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) - - if(X11_FOUND AND HAVE_GDK_GDKX_H) - set(NOT_X_WINDOWS OFF PARENT_SCOPE) - else() - set(NOT_X_WINDOWS ON PARENT_SCOPE) - endif() -endfunction() -check_x11() +if(GTK_FOUND) + # See if we have X11 available. This requires libX11 itself, and also + # the GDK integration to X11. + find_package(X11) + + function(check_x11) + list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) + + if(X11_FOUND AND HAVE_GDK_GDKX_H) + set(NOT_X_WINDOWS OFF PARENT_SCOPE) + else() + set(NOT_X_WINDOWS ON PARENT_SCOPE) + endif() + endfunction() + check_x11() +else() + # If we didn't even have GTK, behave as if X11 is not available. + # (There's nothing useful we could do with it even if there was.) + set(NOT_X_WINDOWS ON) +endif() include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) link_directories(${GTK_LIBRARY_DIRS}) @@ -108,21 +129,86 @@ if(PUTTY_GSSAPI STREQUAL DYNAMIC) endif() if(PUTTY_GSSAPI STREQUAL STATIC) + set(KRB5_CFLAGS) + set(KRB5_LDFLAGS) + + # First try using pkg-config find_package(PkgConfig) pkg_check_modules(KRB5 krb5-gssapi) + + # Failing that, try the dedicated krb5-config + if(NOT KRB5_FOUND) + find_program(KRB5_CONFIG krb5-config) + if(KRB5_CONFIG) + execute_process(COMMAND ${KRB5_CONFIG} --cflags gssapi + OUTPUT_VARIABLE krb5_config_cflags + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE krb5_config_cflags_result) + execute_process(COMMAND ${KRB5_CONFIG} --libs gssapi + OUTPUT_VARIABLE krb5_config_libs + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE krb5_config_libs_result) + + if(krb5_config_cflags_result EQUAL 0 AND krb5_config_libs_result EQUAL 0) + set(KRB5_INCLUDE_DIRS) + set(KRB5_LIBRARY_DIRS) + set(KRB5_LIBRARIES) + + # We can safely put krb5-config's cflags directly into cmake's + # cflags, without bothering to extract the include directories. + set(KRB5_CFLAGS ${krb5_config_cflags}) + + # But krb5-config --libs isn't so simple. It will actually + # deliver a mix of libraries and other linker options. We have + # to separate them for cmake purposes, because if we pass the + # whole lot to add_link_options then they'll appear too early + # in the command line (so that by the time our own code refers + # to GSSAPI functions it'll be too late to search these + # libraries for them), and if we pass the whole lot to + # link_libraries then it'll get confused about options that + # aren't libraries. + separate_arguments(krb5_config_libs NATIVE_COMMAND + ${krb5_config_libs}) + foreach(opt ${krb5_config_libs}) + string(REGEX MATCH "^-l" ok ${opt}) + if(ok) + list(APPEND KRB5_LIBRARIES ${opt}) + continue() + endif() + string(REGEX MATCH "^-L" ok ${opt}) + if(ok) + string(REGEX REPLACE "^-L" "" optval ${opt}) + list(APPEND KRB5_LIBRARY_DIRS ${optval}) + continue() + endif() + list(APPEND KRB5_LDFLAGS ${opt}) + endforeach() + + message(STATUS "Found Kerberos via krb5-config") + set(KRB5_FOUND YES) + endif() + endif() + endif() + if(KRB5_FOUND) include_directories(${KRB5_INCLUDE_DIRS}) link_directories(${KRB5_LIBRARY_DIRS}) link_libraries(${KRB5_LIBRARIES}) + add_compile_options(${KRB5_CFLAGS}) + add_link_options(${KRB5_LDFLAGS}) set(STATIC_GSSAPI ON) else() message(WARNING - "Could not find krb5 via pkg-config -- \ + "Could not find krb5 via pkg-config or krb5-config -- \ cannot provide static GSSAPI support") set(NO_GSSAPI ON) endif() endif() +if(PUTTY_GSSAPI STREQUAL OFF) + set(NO_GSSAPI ON) +endif() + if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") diff --git a/code/cmake/platforms/windows.cmake b/code/cmake/platforms/windows.cmake index 162f1c34..beb76c23 100644 --- a/code/cmake/platforms/windows.cmake +++ b/code/cmake/platforms/windows.cmake @@ -7,6 +7,15 @@ set(PUTTY_LINK_MAPS OFF set(PUTTY_EMBEDDED_CHM_FILE "" CACHE FILEPATH "Path to a .chm help file to embed in the binaries") +if(PUTTY_SUBSYSTEM_VERSION) + string(REPLACE + "subsystem:windows" "subsystem:windows,${PUTTY_SUBSYSTEM_VERSION}" + CMAKE_C_CREATE_WIN32_EXE ${CMAKE_C_CREATE_WIN32_EXE}) + string(REPLACE + "subsystem:console" "subsystem:console,${PUTTY_SUBSYSTEM_VERSION}" + CMAKE_C_CREATE_CONSOLE_EXE ${CMAKE_C_CREATE_CONSOLE_EXE}) +endif() + function(define_negation newvar oldvar) if(${oldvar}) set(${newvar} OFF PARENT_SCOPE) @@ -41,6 +50,8 @@ define_negation(NO_MULTIMON HAVE_MULTIMON_H) check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H) define_negation(NO_HTMLHELP HAVE_HTMLHELP_H) +check_include_files("winsock2.h;afunix.h" HAVE_AFUNIX_H) + check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX) check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) check_symbol_exists(SetDefaultDllDirectories "windows.h" @@ -100,9 +111,22 @@ else() set(LFLAG_MANIFEST_NO "") endif() -if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR - CMAKE_C_COMPILER_ID MATCHES "Clang")) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") +if(STRICT) + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") + elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") + endif() +endif() + +if(CMAKE_C_COMPILER_ID MATCHES "Clang") + # Switch back from MSVC-style error message format + # "file.c(line,col)" to clang's native style "file.c:line:col:". I + # find the latter more convenient because it matches other Unixy + # tools like grep, and I have tooling to parse that format and jump + # to the sites of error messages. + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -Xclang -fdiagnostics-format -Xclang clang") endif() if(CMAKE_C_COMPILER_ID MATCHES "MSVC") diff --git a/code/cmake/toolchain-mingw.cmake b/code/cmake/toolchain-mingw.cmake index 013dbeb5..2e0bc669 100644 --- a/code/cmake/toolchain-mingw.cmake +++ b/code/cmake/toolchain-mingw.cmake @@ -8,3 +8,5 @@ set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) set(CMAKE_AR x86_64-w64-mingw32-ar) set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib) + +add_compile_definitions(__USE_MINGW_ANSI_STDIO) diff --git a/code/cmdgen.c b/code/cmdgen.c index e0006efc..b12758a1 100644 --- a/code/cmdgen.c +++ b/code/cmdgen.c @@ -130,13 +130,19 @@ void help(void) " public RFC 4716 / ssh.com public key\n" " public-openssh OpenSSH public key\n" " fingerprint output the key fingerprint\n" + " cert-info print certificate information\n" " text output the key components as " "'name=0x####'\n" " -o specify output file\n" " -l equivalent to `-O fingerprint'\n" " -L equivalent to `-O public-openssh'\n" " -p equivalent to `-O public'\n" + " --cert-info equivalent to `-O cert-info'\n" " --dump equivalent to `-O text'\n" + " -E fptype specify fingerprint output type:\n" + " sha256, md5, sha256-cert, md5-cert\n" + " --certificate file incorporate a certificate into the key\n" + " --remove-certificate remove any certificate from the key\n" " --reencrypt load a key and save it with fresh " "encryption\n" " --old-passphrase file\n" @@ -235,7 +241,7 @@ int main(int argc, char **argv) enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, - OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE; + OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE; int bits = -1; const char *comment = NULL; char *origcomment = NULL; @@ -250,6 +256,8 @@ int main(int argc, char **argv) char *old_passphrase = NULL, *new_passphrase = NULL; bool load_encrypted; const char *random_device = NULL; + char *certfile = NULL; + bool remove_cert = false; int exit_status = 0; const PrimeGenerationPolicy *primegen = &primegen_probabilistic; bool strong_rsa = false; @@ -295,75 +303,79 @@ int main(int argc, char **argv) while (*p && *p != '=') p++; /* find end of option */ if (*p == '=') { - *p++ = '\0'; - val = p; + *p++ = '\0'; + val = p; } else val = NULL; if (!strcmp(opt, "-help")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - help(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + help(); + nogo = true; + } } else if (!strcmp(opt, "-version")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - showversion(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + showversion(); + nogo = true; + } } else if (!strcmp(opt, "-pgpfp")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - /* support --pgpfp for consistency */ - pgp_fingerprints(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + /* support --pgpfp for consistency */ + pgp_fingerprints(); + nogo = true; + } } else if (!strcmp(opt, "-old-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - old_passphrase = readpassphrase(val); - if (!old_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + old_passphrase = readpassphrase(val); + if (!old_passphrase) + errs = true; + } } else if (!strcmp(opt, "-new-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - new_passphrase = readpassphrase(val); - if (!new_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + new_passphrase = readpassphrase(val); + if (!new_passphrase) + errs = true; + } } else if (!strcmp(opt, "-random-device")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - random_device = val; - } + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + random_device = val; + } } else if (!strcmp(opt, "-dump")) { outtype = TEXT; + } else if (!strcmp(opt, "-cert-info") || + !strcmp(opt, "-certinfo") || + !strcmp(opt, "-cert_info")) { + outtype = CERTINFO; } else if (!strcmp(opt, "-primes")) { if (!val && argc > 1) --argc, val = *++argv; @@ -392,6 +404,18 @@ int main(int argc, char **argv) } } else if (!strcmp(opt, "-strong-rsa")) { strong_rsa = true; + } else if (!strcmp(opt, "-certificate")) { + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + certfile = val; + } + } else if (!strcmp(opt, "-remove-certificate")) { + remove_cert = true; } else if (!strcmp(opt, "-reencrypt")) { reencrypt = true; } else if (!strcmp(opt, "-ppk-param") || @@ -470,9 +494,9 @@ int main(int argc, char **argv) } } } else { - errs = true; - fprintf(stderr, - "puttygen: no such option `-%s'\n", opt); + errs = true; + fprintf(stderr, + "puttygen: no such option `-%s'\n", opt); } p = NULL; break; @@ -578,6 +602,8 @@ int main(int argc, char **argv) outtype = SSHCOM, sshver = 2; else if (!strcmp(p, "text")) outtype = TEXT; + else if (!strcmp(p, "cert-info")) + outtype = CERTINFO; else { fprintf(stderr, "puttygen: unknown output type `%s'\n", p); @@ -592,6 +618,10 @@ int main(int argc, char **argv) fptype = SSH_FPTYPE_MD5; else if (!strcmp(p, "sha256")) fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(p, "md5-cert")) + fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(p, "sha256-cert")) + fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "puttygen: unknown fingerprint " "type `%s'\n", p); @@ -799,7 +829,8 @@ int main(int argc, char **argv) outfiletmp = dupcat(outfile, ".tmp"); } - if (!change_passphrase && !comment && !reencrypt) { + if (!change_passphrase && !comment && !reencrypt && !certfile && + !remove_cert) { fprintf(stderr, "puttygen: this command would perform no useful" " action\n"); RETURN(1); @@ -849,6 +880,26 @@ int main(int argc, char **argv) RETURN(1); } + /* + * Check consistency properties relating to certificates. + */ + if (certfile && !(sshver == 2 && intype_has_private && + outtype_has_private && infile)) { + fprintf(stderr, "puttygen: certificates can only be added to " + "existing SSH-2 private key files\n"); + RETURN(1); + } + if (remove_cert && !(sshver == 2 && infile)) { + fprintf(stderr, "puttygen: certificates can only be removed from " + "existing SSH-2 key files\n"); + RETURN(1); + } + if (certfile && remove_cert) { + fprintf(stderr, "puttygen: cannot both add and remove a " + "certificate\n"); + RETURN(1); + } + /* ------------------------------------------------------------------ * Now we're ready to actually do some stuff. */ @@ -1081,6 +1132,124 @@ int main(int argc, char **argv) } } + /* + * Swap out the public key for a different one, if asked to. + */ + if (certfile) { + Filename *certfilename = filename_from_str(certfile); + LoadedFile *certfile_lf; + const char *error = NULL; + + if (!strcmp(certfile, "-")) + certfile_lf = lf_load_keyfile_fp(stdin, &error); + else + certfile_lf = lf_load_keyfile(certfilename, &error); + + filename_free(certfilename); + + if (!certfile_lf) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + RETURN(1); + } + + char *algname = NULL; + char *comment = NULL; + strbuf *pub = strbuf_new(); + if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname, + BinarySink_UPCAST(pub), &comment, &error)) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + strbuf_free(pub); + sfree(algname); + sfree(comment); + lf_free(certfile_lf); + RETURN(1); + } + + lf_free(certfile_lf); + sfree(comment); + + const ssh_keyalg *alg = find_pubkey_alg(algname); + if (!alg) { + fprintf(stderr, "puttygen: certificate file `%s' has unsupported " + "algorithm name `%s'\n", certfile, algname); + strbuf_free(pub); + sfree(algname); + RETURN(1); + } + + sfree(algname); + + /* Check the two public keys match apart from certificates */ + strbuf *old_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(ssh2key->key), + BinarySink_UPCAST(old_basepub)); + + ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); + strbuf *new_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(new_pubkey), + BinarySink_UPCAST(new_basepub)); + ssh_key_free(new_pubkey); + + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), + ptrlen_from_strbuf(new_basepub)); + strbuf_free(old_basepub); + strbuf_free(new_basepub); + + if (!match) { + fprintf(stderr, "puttygen: certificate in `%s' does not match " + "public key in `%s'\n", certfile, infile); + strbuf_free(pub); + RETURN(1); + } + + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv)); + ssh_key *newkey = ssh_key_new_priv( + alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); + strbuf_free(pub); + strbuf_free(priv); + + if (!newkey) { + fprintf(stderr, "puttygen: unable to combine certificate in `%s' " + "with private key\n", certfile); + RETURN(1); + } + + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (remove_cert) { + /* + * Removing a certificate can be meaningfully done to a pure + * public key blob, as well as a full key pair. + */ + if (ssh2key) { + ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key)); + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (ssh2blob) { + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + + if (!alg) { + fprintf(stderr, "puttygen: input file `%s' has unsupported " + "algorithm name `%.*s'\n", infile, + PTRLEN_PRINTF(algname)); + RETURN(1); + } + + ssh_key *tmpkey = ssh_key_new_pub( + alg, ptrlen_from_strbuf(ssh2blob)); + strbuf_clear(ssh2blob); + ssh_key_public_blob(ssh_key_base_key(tmpkey), + BinarySink_UPCAST(ssh2blob)); + ssh_key_free(tmpkey); + } + } + /* * Unless we're changing the passphrase, the old one (if any) is a * reasonable default. @@ -1173,29 +1342,29 @@ int main(int argc, char **argv) FILE *fp; if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } if (sshver == 1) { - ssh1_write_pubkey(fp, ssh1key); + ssh1_write_pubkey(fp, ssh1key); } else { - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = strbuf_new(); - ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); - } - - ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, - ssh2blob->s, ssh2blob->len, - (outtype == PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = strbuf_new(); + ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); + } + + ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, + ssh2blob->s, ssh2blob->len, + (outtype == PUBLIC ? + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : + SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); } if (outfile) @@ -1209,26 +1378,26 @@ int main(int argc, char **argv) char *fingerprint; if (sshver == 1) { - assert(ssh1key); - fingerprint = rsa_ssh1_fingerprint(ssh1key); + assert(ssh1key); + fingerprint = rsa_ssh1_fingerprint(ssh1key); } else { - if (ssh2key) { - fingerprint = ssh2_fingerprint(ssh2key->key, fptype); - } else { - assert(ssh2blob); - fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(ssh2blob), fptype); - } + if (ssh2key) { + fingerprint = ssh2_fingerprint(ssh2key->key, fptype); + } else { + assert(ssh2blob); + fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(ssh2blob), fptype); + } } if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } fprintf(fp, "%s\n", fingerprint); if (outfile) @@ -1280,9 +1449,8 @@ int main(int argc, char **argv) } else { assert(ssh2blob); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob)); - ptrlen algname = get_string(src); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); const ssh_keyalg *alg = find_pubkey_alg_len(algname); if (!alg) { fprintf(stderr, "puttygen: cannot extract key components " @@ -1313,16 +1481,55 @@ int main(int argc, char **argv) } for (size_t i = 0; i < kc->ncomponents; i++) { - if (kc->components[i].is_mp_int) { - char *hex = mp_get_hex(kc->components[i].mp); - fprintf(fp, "%s=0x%s\n", kc->components[i].name, hex); + key_component *comp = &kc->components[i]; + fprintf(fp, "%s=", comp->name); + switch (comp->type) { + case KCT_MPINT: { + char *hex = mp_get_hex(comp->mp); + fprintf(fp, "0x%s\n", hex); smemclr(hex, strlen(hex)); sfree(hex); - } else { - fprintf(fp, "%s=\"", kc->components[i].name); - write_c_string_literal(fp, ptrlen_from_asciz( - kc->components[i].text)); + break; + } + case KCT_TEXT: + fputs("\"", fp); + write_c_string_literal(fp, ptrlen_from_strbuf(comp->str)); fputs("\"\n", fp); + break; + case KCT_BINARY: { + /* + * Display format for binary key components is to show + * them as base64, with a wrapper so that the actual + * printed string is along the lines of + * 'b64("aGVsbG8sIHdvcmxkCg==")'. + * + * That's a compromise between not being too verbose + * for a human reader, and still being reasonably + * friendly to people pasting the output of this + * 'puttygen --dump' option into Python code (which + * the format is designed to permit in general). + * + * Python users pasting a dump containing one of these + * will have to define a function 'b64' in advance + * which takes a string, which you can do most easily + * using this import statement, as seen in + * cryptsuite.py: + * + * from base64 import b64decode as b64 + */ + fputs("b64(\"", fp); + char b64[4]; + for (size_t j = 0; j < comp->str->len; j += 3) { + size_t len = comp->str->len - j; + if (len > 3) len = 3; + base64_encode_atom(comp->str->u + j, len, b64); + fwrite(b64, 1, 4, fp); + } + fputs("\")\n", fp); + break; + } + default: + unreachable("bad key component type"); } } @@ -1331,6 +1538,83 @@ int main(int argc, char **argv) key_components_free(kc); break; } + + case CERTINFO: { + if (sshver == 1) { + fprintf(stderr, "puttygen: SSH-1 keys cannot contain " + "certificates\n"); + RETURN(1); + } + + const ssh_keyalg *alg; + ssh_key *sk; + bool sk_allocated = false; + + if (ssh2key) { + sk = ssh2key->key; + alg = ssh_key_alg(sk); + } else { + assert(ssh2blob); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + alg = find_pubkey_alg_len(algname); + if (!alg) { + fprintf(stderr, "puttygen: cannot extract certificate info " + "from public key of unknown type '%.*s'\n", + PTRLEN_PRINTF(algname)); + RETURN(1); + } + sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob)); + if (!sk) { + fprintf(stderr, "puttygen: unable to decode public key\n"); + RETURN(1); + } + sk_allocated = true; + } + + if (!alg->is_certificate) { + fprintf(stderr, "puttygen: key is not a certificate\n"); + } else { + SeatDialogText *text = ssh_key_cert_info(sk); + + FILE *fp; + if (outfile) { + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } + } else { + fp = stdout; + } + + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(fp, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(fp, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(fp, ":\n%s\n", item->text); + break; + default: + break; + } + } + + if (outfile) + fclose(fp); + + seat_dialog_text_free(text); + } + + if (sk_allocated) + ssh_key_free(sk); + break; + } } out: diff --git a/code/cmdline.c b/code/cmdline.c index f162ef66..14d30bc7 100644 --- a/code/cmdline.c +++ b/code/cmdline.c @@ -82,8 +82,8 @@ void cmdline_cleanup(void) /* * Similar interface to seat_get_userpass_input(), except that here a - * -1 return means that we aren't capable of processing the prompt and - * someone else should do it. + * SPR(K)_INCOMPLETE return means that we aren't capable of processing + * the prompt and someone else should do it. */ SeatPromptResult cmdline_get_passwd_input( prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) @@ -91,9 +91,13 @@ SeatPromptResult cmdline_get_passwd_input( /* * We only handle prompts which don't echo (which we assume to be * passwords), and (currently) we only cope with a password prompt - * that comes in a prompt-set on its own. + * that comes in a prompt-set on its own. Also, we don't use a + * command-line password for any kind of prompt which is destined + * for local use rather than to be sent to the server: the idea is + * to pre-fill _passwords_, not private-key passphrases (for which + * there are better alternatives available). */ - if (p->n_prompts != 1 || p->prompts[0]->echo) { + if (p->n_prompts != 1 || p->prompts[0]->echo || !p->to_server) { return SPR_INCOMPLETE; } @@ -751,6 +755,16 @@ int cmdline_process_param(const char *p, char *value, filename_free(fn); } + if (!strcmp(p, "-cert")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_detached_cert, fn); + filename_free(fn); + } + if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); diff --git a/code/config.c b/code/config.c index 7153ab61..da5c80b7 100644 --- a/code/config.c +++ b/code/config.c @@ -9,7 +9,7 @@ #include "putty.h" #include "dialog.h" #include "storage.h" - +#include "tree234.h" #ifdef PUTTY_CAC #include "cert_common.h" #endif // PUTTY_CAC @@ -19,7 +19,7 @@ #define HOST_BOX_TITLE "Host Name (or IP address)" #define PORT_BOX_TITLE "Port" -void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -32,7 +32,7 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, * is the one selected. */ if (event == EVENT_REFRESH) { - int val = conf_get_int(conf, ctrl->radio.context.i); + int val = conf_get_int(conf, ctrl->context.i); for (button = 0; button < ctrl->radio.nbuttons; button++) if (val == ctrl->radio.buttondata[button].i) break; @@ -42,12 +42,12 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - conf_set_int(conf, ctrl->radio.context.i, + conf_set_int(conf, ctrl->context.i, ctrl->radio.buttondata[button].i); } } -void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -59,7 +59,7 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, * config option. */ if (event == EVENT_REFRESH) { - int val = conf_get_bool(conf, ctrl->radio.context.i); + int val = conf_get_bool(conf, ctrl->context.i); for (button = 0; button < ctrl->radio.nbuttons; button++) if (val == ctrl->radio.buttondata[button].i) break; @@ -69,13 +69,13 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - conf_set_bool(conf, ctrl->radio.context.i, + conf_set_bool(conf, ctrl->context.i, ctrl->radio.buttondata[button].i); } } #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, +void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int key; @@ -86,7 +86,7 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, * For a standard checkbox, the context parameter gives the * primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT. */ - key = ctrl->checkbox.context.i; + key = ctrl->context.i; if (key & CHECKBOX_INVERT) { key &= ~CHECKBOX_INVERT; invert = true; @@ -107,27 +107,23 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, } } -void conf_editbox_handler(union control *ctrl, dlgparam *dlg, +const struct conf_editbox_handler_type conf_editbox_str = {.type = EDIT_STR}; +const struct conf_editbox_handler_type conf_editbox_int = {.type = EDIT_INT}; + +void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { /* - * The standard edit-box handler expects the main `context' - * field to contain the primary key. The secondary `context2' - * field indicates the type of this field: - * - * - if context2 > 0, the field is a string. - * - if context2 == -1, the field is an int and the edit box - * is numeric. - * - if context2 < -1, the field is an int and the edit box is - * _floating_, and (-context2) gives the scale. (E.g. if - * context2 == -1000, then typing 1.2 into the box will set - * the field to 1200.) + * The standard edit-box handler expects the main `context' field + * to contain the primary key. The secondary `context2' field is a + * pointer to the struct conf_editbox_handler_type defined in + * putty.h. */ - int key = ctrl->editbox.context.i; - int length = ctrl->editbox.context2.i; + int key = ctrl->context.i; + const struct conf_editbox_handler_type *type = ctrl->context2.cp; Conf *conf = (Conf *)data; - if (length > 0) { + if (type->type == EDIT_STR) { if (event == EVENT_REFRESH) { char *field = conf_get_str(conf, key); dlg_editbox_set(ctrl, dlg, field); @@ -136,30 +132,30 @@ void conf_editbox_handler(union control *ctrl, dlgparam *dlg, conf_set_str(conf, key, field); sfree(field); } - } else if (length < 0) { + } else { if (event == EVENT_REFRESH) { char str[80]; int value = conf_get_int(conf, key); - if (length == -1) + if (type->type == EDIT_INT) sprintf(str, "%d", value); else - sprintf(str, "%g", (double)value / (double)(-length)); + sprintf(str, "%g", (double)value / type->denominator); dlg_editbox_set(ctrl, dlg, str); } else if (event == EVENT_VALCHANGE) { char *str = dlg_editbox_get(ctrl, dlg); - if (length == -1) + if (type->type == EDIT_INT) conf_set_int(conf, key, atoi(str)); else - conf_set_int(conf, key, (int)((-length) * atof(str))); + conf_set_int(conf, key, (int)(type->denominator * atof(str))); sfree(str); } } } -void conf_filesel_handler(union control *ctrl, dlgparam *dlg, +void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - int key = ctrl->fileselect.context.i; + int key = ctrl->context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -172,10 +168,10 @@ void conf_filesel_handler(union control *ctrl, dlgparam *dlg, } } -void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, +void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - int key = ctrl->fontselect.context.i; + int key = ctrl->context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -188,7 +184,7 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, } } -static void config_host_handler(union control *ctrl, dlgparam *dlg, +static void config_host_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -220,7 +216,7 @@ static void config_host_handler(union control *ctrl, dlgparam *dlg, } } -static void config_port_handler(union control *ctrl, dlgparam *dlg, +static void config_port_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -261,7 +257,7 @@ static void config_port_handler(union control *ctrl, dlgparam *dlg, } struct hostport { - union control *host, *port, *protradio, *protlist; + dlgcontrol *host, *port, *protradio, *protlist; bool mid_refresh; }; @@ -272,12 +268,12 @@ struct hostport { * and refreshes both host and port boxes when switching to/from the * serial backend. */ -static void config_protocols_handler(union control *ctrl, dlgparam *dlg, +static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; int curproto = conf_get_int(conf, CONF_protocol); - struct hostport *hp = (struct hostport *)ctrl->generic.context.p; + struct hostport *hp = (struct hostport *)ctrl->context.p; if (event == EVENT_REFRESH) { /* @@ -425,7 +421,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg, } } -static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg, +static void loggingbuttons_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -454,7 +450,7 @@ static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg, } } -static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg, +static void numeric_keypad_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -485,7 +481,7 @@ static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg, } } -static void cipherlist_handler(union control *ctrl, dlgparam *dlg, +static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -494,6 +490,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg, static const struct { const char *s; int c; } ciphers[] = { { "ChaCha20 (SSH-2 only)", CIPHER_CHACHA20 }, + { "AES-GCM (SSH-2 only)", CIPHER_AESGCM }, { "3DES", CIPHER_3DES }, { "Blowfish", CIPHER_BLOWFISH }, { "DES", CIPHER_DES }, @@ -531,7 +528,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg, } #ifndef NO_GSSAPI -static void gsslist_handler(union control *ctrl, dlgparam *dlg, +static void gsslist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -558,7 +555,7 @@ static void gsslist_handler(union control *ctrl, dlgparam *dlg, } #endif -static void kexlist_handler(union control *ctrl, dlgparam *dlg, +static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -566,12 +563,17 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg, int i; static const struct { const char *s; int k; } kexes[] = { - { "Diffie-Hellman group 1", KEX_DHGROUP1 }, - { "Diffie-Hellman group 14", KEX_DHGROUP14 }, - { "Diffie-Hellman group exchange", KEX_DHGEX }, - { "RSA-based key exchange", KEX_RSA }, - { "ECDH key exchange", KEX_ECDH }, - { "-- warn below here --", KEX_WARN } + { "Diffie-Hellman group 1 (1024-bit)", KEX_DHGROUP1 }, + { "Diffie-Hellman group 14 (2048-bit)", KEX_DHGROUP14 }, + { "Diffie-Hellman group 15 (3072-bit)", KEX_DHGROUP15 }, + { "Diffie-Hellman group 16 (4096-bit)", KEX_DHGROUP16 }, + { "Diffie-Hellman group 17 (6144-bit)", KEX_DHGROUP17 }, + { "Diffie-Hellman group 18 (8192-bit)", KEX_DHGROUP18 }, + { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, + { "ECDH key exchange", KEX_ECDH }, + { "NTRU Prime / Curve25519 hybrid kex", KEX_NTRU_HYBRID }, + { "-- warn below here --", KEX_WARN } }; /* Set up the "kex preference" box. */ @@ -602,8 +604,8 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg, } } -static void hklist_handler(union control *ctrl, dlgparam *dlg, - void *data, int event) +static void hklist_handler(dlgcontrol *ctrl, dlgparam *dlg, + void *data, int event) { Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -646,7 +648,7 @@ static void hklist_handler(union control *ctrl, dlgparam *dlg, } } -static void printerbox_handler(union control *ctrl, dlgparam *dlg, +static void printerbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -682,7 +684,7 @@ static void printerbox_handler(union control *ctrl, dlgparam *dlg, } } -static void codepage_handler(union control *ctrl, dlgparam *dlg, +static void codepage_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -706,7 +708,7 @@ static void codepage_handler(union control *ctrl, dlgparam *dlg, } } -static void sshbug_handler(union control *ctrl, dlgparam *dlg, +static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -717,7 +719,7 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg, * spurious SELCHANGE we trigger in the process will overwrite * the value we wanted to keep. */ - int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + int oldconf = conf_get_int(conf, ctrl->context.i); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); @@ -735,11 +737,11 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg, i = AUTO; else i = dlg_listbox_getid(ctrl, dlg, i); - conf_set_int(conf, ctrl->listbox.context.i, i); + conf_set_int(conf, ctrl->context.i, i); } } -static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, +static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { /* @@ -750,7 +752,7 @@ static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, */ Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + int oldconf = conf_get_int(conf, ctrl->context.i); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); @@ -766,13 +768,13 @@ static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, i = FORCE_OFF; else i = dlg_listbox_getid(ctrl, dlg, i); - conf_set_int(conf, ctrl->listbox.context.i, i); + conf_set_int(conf, ctrl->context.i, i); } } struct sessionsaver_data { - union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; - union control *okbutton, *cancelbutton; + dlgcontrol *editbox, *listbox, *loadbutton, *savebutton, *delbutton; + dlgcontrol *okbutton, *cancelbutton; struct sesslist sesslist; bool midsession; char *savedsession; /* the current contents of ssd->editbox */ @@ -780,14 +782,14 @@ struct sessionsaver_data { #ifdef PUTTY_CAC struct cert_data { - union control* cert_set_pkcs_button, * cert_set_fido_button, * cert_set_capi_button, * cert_clear_button, * cert_view_button, + dlgcontrol* cert_set_pkcs_button, * cert_set_fido_button, * cert_set_capi_button, * cert_clear_button, * cert_view_button, * cert_thumbprint_text, * cert_copy_clipboard_button, * cert_enable_auth, * cert_auth_checkbox; }; -void cert_event_handler(union control* ctrl, dlgparam* dlg, void* data, int event) +void cert_event_handler(dlgcontrol* ctrl, dlgparam* dlg, void* data, int event) { Conf* conf = (Conf*)data; - struct cert_data* certd = (struct cert_data*)ctrl->generic.context.p; + struct cert_data* certd = (struct cert_data*)ctrl->context.p; // use the clear button initialization to prepopulate the thumbprint field if (ctrl == certd->cert_clear_button && event == EVENT_REFRESH) @@ -805,7 +807,7 @@ void cert_event_handler(union control* ctrl, dlgparam* dlg, void* data, int even char* szCert = conf_get_str(conf, CONF_cert_fingerprint); char* szKeyString = cert_key_string(szCert); if (szKeyString == NULL) return; - write_aclip(CLIP_SYSTEM, szKeyString, strlen(szKeyString), 0); + write_aclip(CLIP_SYSTEM, szKeyString, strlen(szKeyString), true); sfree(szKeyString); } @@ -864,14 +866,14 @@ void cert_event_handler(union control* ctrl, dlgparam* dlg, void* data, int even } struct fido_data { - union control* fido_create_key_button, * fido_delete_key_button, * fido_import_key_button, * fido_import_ssh_button, + dlgcontrol* fido_create_key_button, * fido_delete_key_button, * fido_import_key_button, * fido_import_ssh_button, * fido_clear_key_button, * fido_algo_combobox, * fido_app_text, * fido_verification_radio, * fido_resident_radio; }; -void fido_event_handler(union control* ctrl, dlgparam* dlg, void* data, int event) +void fido_event_handler(dlgcontrol* ctrl, dlgparam* dlg, void* data, int event) { Conf* conf = (Conf*)data; - struct fido_data* fidod = (struct fido_data*)ctrl->generic.context.p; + struct fido_data* fidod = (struct fido_data*)ctrl->context.p; static const char* szAlgTable[] = { "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", @@ -1016,15 +1018,16 @@ void fido_event_handler(union control* ctrl, dlgparam* dlg, void* data, int even } struct capi_data { - union control* capi_create_key_button, * capi_delete_key_button, * capi_provider_radio, * capi_algo_combobox, + dlgcontrol* capi_create_key_button, * capi_delete_key_button, * capi_provider_radio, * capi_algo_combobox, * capi_name_text, * capi_no_expired_checkbox, * capi_smartcard_only_checkbox, * capi_trusted_certs_checkbox, * cert_store_button; }; -void capi_event_handler(union control* ctrl, dlgparam* dlg, void* data, int event) +void capi_event_handler(dlgcontrol* ctrl, dlgparam* dlg, void* data, int event) { Conf* conf = (Conf*)data; - struct capi_data* capid = (struct capi_data*)ctrl->generic.context.p; + + struct capi_data* capid = (struct capi_data*)ctrl->context.p; static const char* szAlgTable[] = { "rsa-1024", "rsa-2048", @@ -1173,12 +1176,12 @@ static bool load_selected_session( return true; } -static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, +static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct sessionsaver_data *ssd = - (struct sessionsaver_data *)ctrl->generic.context.p; + (struct sessionsaver_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ssd->editbox) { @@ -1311,15 +1314,15 @@ static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, } struct charclass_data { - union control *listbox, *editbox, *button; + dlgcontrol *listbox, *editbox, *button; }; -static void charclass_handler(union control *ctrl, dlgparam *dlg, +static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct charclass_data *ccd = - (struct charclass_data *)ctrl->generic.context.p; + (struct charclass_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ccd->listbox) { @@ -1352,7 +1355,7 @@ static void charclass_handler(union control *ctrl, dlgparam *dlg, } struct colour_data { - union control *listbox, *redit, *gedit, *bedit, *button; + dlgcontrol *listbox, *redit, *gedit, *bedit, *button; }; /* Array of the user-visible colour names defined in the list macro in @@ -1363,12 +1366,12 @@ static const char *const colours[] = { #undef CONF_COLOUR_NAME_DECL }; -static void colour_handler(union control *ctrl, dlgparam *dlg, +static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct colour_data *cd = - (struct colour_data *)ctrl->generic.context.p; + (struct colour_data *)ctrl->context.p; bool update = false, clear = false; int r, g, b; @@ -1469,15 +1472,15 @@ static void colour_handler(union control *ctrl, dlgparam *dlg, } struct ttymodes_data { - union control *valradio, *valbox, *setbutton, *listbox; + dlgcontrol *valradio, *valbox, *setbutton, *listbox; }; -static void ttymodes_handler(union control *ctrl, dlgparam *dlg, +static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct ttymodes_data *td = - (struct ttymodes_data *)ctrl->generic.context.p; + (struct ttymodes_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == td->listbox) { @@ -1554,15 +1557,15 @@ static void ttymodes_handler(union control *ctrl, dlgparam *dlg, } struct environ_data { - union control *varbox, *valbox, *addbutton, *rembutton, *listbox; + dlgcontrol *varbox, *valbox, *addbutton, *rembutton, *listbox; }; -static void environ_handler(union control *ctrl, dlgparam *dlg, +static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct environ_data *ed = - (struct environ_data *)ctrl->generic.context.p; + (struct environ_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ed->listbox) { @@ -1626,19 +1629,19 @@ static void environ_handler(union control *ctrl, dlgparam *dlg, } struct portfwd_data { - union control *addbutton, *rembutton, *listbox; - union control *sourcebox, *destbox, *direction; + dlgcontrol *addbutton, *rembutton, *listbox; + dlgcontrol *sourcebox, *destbox, *direction; #ifndef NO_IPV6 - union control *addressfamily; + dlgcontrol *addressfamily; #endif }; -static void portfwd_handler(union control *ctrl, dlgparam *dlg, +static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct portfwd_data *pfd = - (struct portfwd_data *)ctrl->generic.context.p; + (struct portfwd_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == pfd->listbox) { @@ -1794,15 +1797,15 @@ static void portfwd_handler(union control *ctrl, dlgparam *dlg, } struct manual_hostkey_data { - union control *addbutton, *rembutton, *listbox, *keybox; + dlgcontrol *addbutton, *rembutton, *listbox, *keybox; }; -static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, +static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct manual_hostkey_data *mh = - (struct manual_hostkey_data *)ctrl->generic.context.p; + (struct manual_hostkey_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == mh->listbox) { @@ -1860,13 +1863,13 @@ static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, } } -static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, +static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; - int setting = ctrl->generic.context.i; + int setting = ctrl->context.i; #ifdef NAMED_CLIPBOARDS - int strsetting = ctrl->editbox.context2.i; + int strsetting = ctrl->context2.i; #endif static const struct { @@ -1948,7 +1951,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, } static void clipboard_control(struct controlset *s, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, int setting, int strsetting) { #ifdef NAMED_CLIPBOARDS @@ -1961,7 +1964,7 @@ static void clipboard_control(struct controlset *s, const char *label, #endif } -static void serial_parity_handler(union control *ctrl, dlgparam *dlg, +static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -1974,7 +1977,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg, {"Mark", SER_PAR_MARK}, {"Space", SER_PAR_SPACE}, }; - int mask = ctrl->listbox.context.i; + int mask = ctrl->context.i; int i, j; Conf *conf = (Conf *)data; @@ -2016,7 +2019,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg, } } -static void serial_flow_handler(union control *ctrl, dlgparam *dlg, +static void serial_flow_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -2028,7 +2031,7 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg, {"RTS/CTS", SER_FLOW_RTSCTS}, {"DSR/DTR", SER_FLOW_DSRDTR}, }; - int mask = ctrl->listbox.context.i; + int mask = ctrl->context.i; int i, j; Conf *conf = (Conf *)data; @@ -2069,6 +2072,67 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg, } } +void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + /* + * We must fetch the previously configured value from the Conf + * before we start modifying the drop-down list, otherwise the + * spurious SELCHANGE we trigger in the process will overwrite + * the value we wanted to keep. + */ + int proxy_type = conf_get_int(conf, CONF_proxy_type); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + + int index_to_select = 0, current_index = 0; + +#define ADD(id, title) do { \ + dlg_listbox_addwithid(ctrl, dlg, title, id); \ + if (id == proxy_type) \ + index_to_select = current_index; \ + current_index++; \ + } while (0) + + ADD(PROXY_NONE, "None"); + ADD(PROXY_SOCKS5, "SOCKS 5"); + ADD(PROXY_SOCKS4, "SOCKS 4"); + ADD(PROXY_HTTP, "HTTP CONNECT"); + if (ssh_proxy_supported) { + ADD(PROXY_SSH_TCPIP, "SSH to proxy and use port forwarding"); + ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command"); + ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem"); + } + if (ctrl->context.i & PROXY_UI_FLAG_LOCAL) { + ADD(PROXY_CMD, "Local (run a subprogram to connect)"); + } + ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)"); + +#undef ADD + + dlg_listbox_select(ctrl, dlg, index_to_select); + + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = AUTO; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, CONF_proxy_type, i); + } +} + +static void host_ca_button_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + show_ca_config_box(dp); +} + void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo) { @@ -2081,7 +2145,7 @@ void setup_config_box(struct controlbox *b, bool midsession, struct environ_data *ed; struct portfwd_data *pfd; struct manual_hostkey_data *mh; - union control *c; + dlgcontrol *c; bool resize_forbidden = false; char *str; @@ -2104,11 +2168,11 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(no_help), sessionsaver_handler, P(ssd)); ssd->okbutton->button.isdefault = true; - ssd->okbutton->generic.column = 3; + ssd->okbutton->column = 3; ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), sessionsaver_handler, P(ssd)); ssd->cancelbutton->button.iscancel = true; - ssd->cancelbutton->generic.column = 4; + ssd->cancelbutton->column = 4; /* We carefully don't close the 5-column part, so that platform- * specific add-ons can put extra buttons alongside Open and Cancel. */ @@ -2130,12 +2194,12 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100, HELPCTX(session_hostname), config_host_handler, I(0), I(0)); - c->generic.column = 0; + c->column = 0; hp->host = c; c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100, HELPCTX(session_hostname), config_port_handler, I(0), I(0)); - c->generic.column = 1; + c->column = 1; hp->port = c; ctrl_columns(s, 1, 100); @@ -2143,8 +2207,8 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 62, 38); c = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, HELPCTX(session_hostname), - config_protocols_handler, P(hp), NULL); - c->generic.column = 0; + config_protocols_handler, P(hp)); + c->column = 0; hp->protradio = c; c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *); c->radio.shortcuts = sresize(c->radio.shortcuts, PROTOCOL_LIMIT, char); @@ -2179,10 +2243,10 @@ void setup_config_box(struct controlbox *b, bool midsession, config_protocols_handler, P(hp)); hp->protlist = c; /* droplist is populated in config_protocols_handler */ - c->generic.column = 1; + c->column = 1; /* Vertically centre the two protocol controls w.r.t. each other */ - hp->protlist->generic.align_next_to = hp->protradio; + hp->protlist->align_next_to = hp->protradio; ctrl_columns(s, 1, 100); } @@ -2198,7 +2262,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, HELPCTX(session_saved), sessionsaver_handler, P(ssd), P(NULL)); - ssd->editbox->generic.column = 0; + ssd->editbox->column = 0; /* Reset columns so that the buttons are alongside the list, rather * than alongside that edit box. */ ctrl_columns(s, 1, 100); @@ -2206,13 +2270,13 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->listbox->generic.column = 0; + ssd->listbox->column = 0; ssd->listbox->listbox.height = 7; if (!midsession) { ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->loadbutton->generic.column = 1; + ssd->loadbutton->column = 1; } else { /* We can't offer the Load button mid-session, as it would allow the * user to load and subsequently save settings they can't see. (And @@ -2224,12 +2288,12 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->savebutton->generic.column = 1; + ssd->savebutton->column = 1; if (!midsession) { ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->delbutton->generic.column = 1; + ssd->delbutton->column = 1; } else { /* Disable the Delete button mid-session too, for UI consistency. */ ssd->delbutton = NULL; @@ -2243,7 +2307,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_close_on_exit), "Always", I(FORCE_ON), "Never", I(FORCE_OFF), - "Only on clean exit", I(AUTO), NULL); + "Only on clean exit", I(AUTO)); /* * The Session/Logging panel. @@ -2273,8 +2337,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Printable output", 'p', I(LGTYP_ASCII), "All session output", 'l', I(LGTYP_DEBUG), sshlogname, 's', I(LGTYP_PACKETS), - sshrawlogname, 'r', I(LGTYP_SSHRAW), - NULL); + sshrawlogname, 'r', I(LGTYP_SSHRAW)); } ctrl_filesel(s, "Log file name:", 'f', NULL, true, "Select session log file name", @@ -2288,13 +2351,13 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_logxfovr), "Always overwrite it", I(LGXF_OVR), "Always append to the end of it", I(LGXF_APN), - "Ask the user every time", I(LGXF_ASK), NULL); + "Ask the user every time", I(LGXF_ASK)); ctrl_checkbox(s, "Flush log file frequently", 'u', - HELPCTX(logging_flush), - conf_checkbox_handler, I(CONF_logflush)); + HELPCTX(logging_flush), + conf_checkbox_handler, I(CONF_logflush)); ctrl_checkbox(s, "Include header", 'i', - HELPCTX(logging_header), - conf_checkbox_handler, I(CONF_logheader)); + HELPCTX(logging_header), + conf_checkbox_handler, I(CONF_logheader)); if ((midsession && protocol == PROT_SSH) || (!midsession && backend_vt_from_proto(PROT_SSH))) { @@ -2334,7 +2397,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_blinktext)); ctrl_editbox(s, "Answerback to ^E:", 's', 100, HELPCTX(terminal_answerback), - conf_editbox_handler, I(CONF_answerback), I(1)); + conf_editbox_handler, I(CONF_answerback), ED_STR); s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); ctrl_radiobuttons(s, "Local echo:", 'l', 3, @@ -2342,13 +2405,13 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler,I(CONF_localecho), "Auto", I(AUTO), "Force on", I(FORCE_ON), - "Force off", I(FORCE_OFF), NULL); + "Force off", I(FORCE_OFF)); ctrl_radiobuttons(s, "Local line editing:", 't', 3, HELPCTX(terminal_localedit), conf_radiobutton_handler,I(CONF_localedit), "Auto", I(AUTO), "Force on", I(FORCE_ON), - "Force off", I(FORCE_OFF), NULL); + "Force off", I(FORCE_OFF)); s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100, @@ -2367,12 +2430,12 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(keyboard_backspace), conf_radiobutton_bool_handler, I(CONF_bksp_is_delete), - "Control-H", I(0), "Control-? (127)", I(1), NULL); + "Control-H", I(0), "Control-? (127)", I(1)); ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, HELPCTX(keyboard_homeend), conf_radiobutton_bool_handler, I(CONF_rxvt_homeend), - "Standard", I(false), "rxvt", I(true), NULL); + "Standard", I(false), "rxvt", I(true)); ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4, HELPCTX(keyboard_funkeys), conf_radiobutton_handler, @@ -2383,14 +2446,13 @@ void setup_config_box(struct controlbox *b, bool midsession, "VT400", I(FUNKY_VT400), "VT100+", I(FUNKY_VT100P), "SCO", I(FUNKY_SCO), - "Xterm 216+", I(FUNKY_XTERM_216), - NULL); + "Xterm 216+", I(FUNKY_XTERM_216)); ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2, HELPCTX(keyboard_sharrow), conf_radiobutton_handler, I(CONF_sharrow_type), "Ctrl toggles app mode", I(SHARROW_APPLICATION), - "xterm-style bitmap", I(SHARROW_BITMAP), NULL); + "xterm-style bitmap", I(SHARROW_BITMAP)); s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", "Application keypad settings:"); @@ -2398,12 +2460,11 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(keyboard_appcursor), conf_radiobutton_bool_handler, I(CONF_app_cursor), - "Normal", I(0), "Application", I(1), NULL); + "Normal", I(0), "Application", I(1)); ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, HELPCTX(keyboard_appkeypad), numeric_keypad_handler, P(NULL), - "Normal", I(0), "Application", I(1), "NetHack", I(2), - NULL); + "Normal", I(0), "Application", I(1), "NetHack", I(2)); /* * The Terminal/Bell panel. @@ -2417,7 +2478,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_beep), "None (bell disabled)", I(BELL_DISABLED), "Make default system alert sound", I(BELL_DEFAULT), - "Visual bell (flash window)", I(BELL_VISUAL), NULL); + "Visual bell (flash window)", I(BELL_VISUAL)); s = ctrl_getset(b, "Terminal/Bell", "overload", "Control the bell overload behaviour"); @@ -2426,17 +2487,21 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_bellovl)); ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, HELPCTX(bell_overload), - conf_editbox_handler, I(CONF_bellovl_n), I(-1)); + conf_editbox_handler, I(CONF_bellovl_n), ED_INT); + + static const struct conf_editbox_handler_type conf_editbox_tickspersec = { + .type = EDIT_FIXEDPOINT, .denominator = TICKSPERSEC}; + ctrl_editbox(s, "... in this many seconds", 't', 20, HELPCTX(bell_overload), conf_editbox_handler, I(CONF_bellovl_t), - I(-TICKSPERSEC)); + CP(&conf_editbox_tickspersec)); ctrl_text(s, "The bell is re-enabled after a few seconds of silence.", HELPCTX(bell_overload)); ctrl_editbox(s, "Seconds of silence required", 's', 20, HELPCTX(bell_overload), conf_editbox_handler, I(CONF_bellovl_s), - I(-TICKSPERSEC)); + CP(&conf_editbox_tickspersec)); /* * The Terminal/Features panel. @@ -2471,7 +2536,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_remote_qtitle_action), "None", I(TITLE_NONE), "Empty string", I(TITLE_EMPTY), - "Window title", I(TITLE_REAL), NULL); + "Window title", I(TITLE_REAL)); ctrl_checkbox(s, "Disable remote-controlled clearing of scrollback", 'e', HELPCTX(features_clearscroll), conf_checkbox_handler, @@ -2505,12 +2570,12 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 50, 50); c = ctrl_editbox(s, "Columns", 'm', 100, HELPCTX(window_size), - conf_editbox_handler, I(CONF_width), I(-1)); - c->generic.column = 0; + conf_editbox_handler, I(CONF_width), ED_INT); + c->column = 0; c = ctrl_editbox(s, "Rows", 'r', 100, HELPCTX(window_size), - conf_editbox_handler, I(CONF_height),I(-1)); - c->generic.column = 1; + conf_editbox_handler, I(CONF_height),ED_INT); + c->column = 1; ctrl_columns(s, 1, 100); } @@ -2518,7 +2583,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Control the scrollback in the window"); ctrl_editbox(s, "Lines of scrollback", 's', 50, HELPCTX(window_scrollback), - conf_editbox_handler, I(CONF_savelines), I(-1)); + conf_editbox_handler, I(CONF_savelines), ED_INT); ctrl_checkbox(s, "Display scrollbar", 'd', HELPCTX(window_scrollback), conf_checkbox_handler, I(CONF_scrollbar)); @@ -2548,7 +2613,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_cursor_type), "Block", 'l', I(0), "Underline", 'u', I(1), - "Vertical line", 'v', I(2), NULL); + "Vertical line", 'v', I(2)); ctrl_checkbox(s, "Cursor blinks", 'b', HELPCTX(appearance_cursor), conf_checkbox_handler, I(CONF_blink_cur)); @@ -2570,7 +2635,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, HELPCTX(appearance_border), conf_editbox_handler, - I(CONF_window_border), I(-1)); + I(CONF_window_border), ED_INT); /* * The Window/Behaviour panel. @@ -2583,7 +2648,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Adjust the behaviour of the window title"); ctrl_editbox(s, "Window title:", 't', 100, HELPCTX(appearance_title), - conf_editbox_handler, I(CONF_wintitle), I(1)); + conf_editbox_handler, I(CONF_wintitle), ED_STR); ctrl_checkbox(s, "Separate window and icon titles", 'i', HELPCTX(appearance_title), conf_checkbox_handler, @@ -2614,13 +2679,12 @@ void setup_config_box(struct controlbox *b, bool midsession, str = dupprintf("Adjust how %s handles line drawing characters", appname); s = ctrl_getset(b, "Window/Translation", "linedraw", str); sfree(str); - ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1, - HELPCTX(translation_linedraw), - conf_radiobutton_handler, - I(CONF_vtmode), - "Use Unicode line drawing code points",'u',I(VT_UNICODE), - "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), - NULL); + ctrl_radiobuttons( + s, "Handling of line drawing characters:", NO_SHORTCUT,1, + HELPCTX(translation_linedraw), + conf_radiobutton_handler, I(CONF_vtmode), + "Use Unicode line drawing code points",'u',I(VT_UNICODE), + "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN)); ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', HELPCTX(selection_linedraw), conf_checkbox_handler, I(CONF_rawcnp)); @@ -2645,7 +2709,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_rect_select), "Normal", 'n', I(false), - "Rectangular block", 'r', I(true), NULL); + "Rectangular block", 'r', I(true)); s = ctrl_getset(b, "Window/Selection", "clipboards", "Assign copy/paste actions to clipboards"); @@ -2693,11 +2757,11 @@ void setup_config_box(struct controlbox *b, bool midsession, ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50, HELPCTX(copy_charclasses), charclass_handler, P(ccd), P(NULL)); - ccd->editbox->generic.column = 0; + ccd->editbox->column = 0; ccd->button = ctrl_pushbutton(s, "Set", 's', HELPCTX(copy_charclasses), charclass_handler, P(ccd)); - ccd->button->generic.column = 1; + ccd->button->column = 1; ctrl_columns(s, 1, 100); /* @@ -2721,8 +2785,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_bold_style), "The font", I(1), "The colour", I(2), - "Both", I(3), - NULL); + "Both", I(3)); str = dupprintf("Adjust the precise colours %s displays", appname); s = ctrl_getset(b, "Window/Colours", "adjust", str); @@ -2734,22 +2797,22 @@ void setup_config_box(struct controlbox *b, bool midsession, cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data)); cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u', HELPCTX(colours_config), colour_handler, P(cd)); - cd->listbox->generic.column = 0; + cd->listbox->column = 0; cd->listbox->listbox.height = 7; c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); - c->generic.column = 1; + c->column = 1; cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config), colour_handler, P(cd), P(NULL)); - cd->redit->generic.column = 1; + cd->redit->column = 1; cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config), colour_handler, P(cd), P(NULL)); - cd->gedit->generic.column = 1; + cd->gedit->column = 1; cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config), colour_handler, P(cd), P(NULL)); - cd->bedit->generic.column = 1; + cd->bedit->column = 1; cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), colour_handler, P(cd)); - cd->button->generic.column = 1; + cd->button->column = 1; ctrl_columns(s, 1, 100); /* @@ -2764,8 +2827,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Sending of null packets to keep session active"); ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, HELPCTX(connection_keepalive), - conf_editbox_handler, I(CONF_ping_interval), - I(-1)); + conf_editbox_handler, I(CONF_ping_interval), ED_INT); if (!midsession) { s = ctrl_getset(b, "Connection", "tcp", @@ -2780,15 +2842,14 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_tcp_keepalives)); #ifndef NO_IPV6 s = ctrl_getset(b, "Connection", "ipversion", - "Internet protocol version"); + "Internet protocol version"); ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, - HELPCTX(connection_ipversion), - conf_radiobutton_handler, - I(CONF_addressfamily), - "Auto", 'u', I(ADDRTYPE_UNSPEC), - "IPv4", '4', I(ADDRTYPE_IPV4), - "IPv6", '6', I(ADDRTYPE_IPV6), - NULL); + HELPCTX(connection_ipversion), + conf_radiobutton_handler, + I(CONF_addressfamily), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6)); #endif { @@ -2799,7 +2860,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Logical name of remote host"); ctrl_editbox(s, label, 'm', 100, HELPCTX(connection_loghost), - conf_editbox_handler, I(CONF_loghost), I(1)); + conf_editbox_handler, I(CONF_loghost), ED_STR); } } @@ -2814,7 +2875,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Login details"); ctrl_editbox(s, "Auto-login username", 'u', 50, HELPCTX(connection_username), - conf_editbox_handler, I(CONF_username), I(1)); + conf_editbox_handler, I(CONF_username), ED_STR); { /* We assume the local username is sufficiently stable * to include on the dialog box. */ @@ -2827,8 +2888,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_username_from_env), "Prompt", I(false), - userlabel, I(true), - NULL); + userlabel, I(true)); sfree(userlabel); } @@ -2836,10 +2896,10 @@ void setup_config_box(struct controlbox *b, bool midsession, "Terminal details"); ctrl_editbox(s, "Terminal-type string", 't', 50, HELPCTX(connection_termtype), - conf_editbox_handler, I(CONF_termtype), I(1)); + conf_editbox_handler, I(CONF_termtype), ED_STR); ctrl_editbox(s, "Terminal speeds", 's', 50, HELPCTX(connection_termspeed), - conf_editbox_handler, I(CONF_termspeed), I(1)); + conf_editbox_handler, I(CONF_termspeed), ED_STR); s = ctrl_getset(b, "Connection/Data", "env", "Environment variables"); @@ -2849,19 +2909,19 @@ void setup_config_box(struct controlbox *b, bool midsession, ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, HELPCTX(telnet_environ), environ_handler, P(ed), P(NULL)); - ed->varbox->generic.column = 0; + ed->varbox->column = 0; ed->valbox = ctrl_editbox(s, "Value", 'l', 60, HELPCTX(telnet_environ), environ_handler, P(ed), P(NULL)); - ed->valbox->generic.column = 0; + ed->valbox->column = 0; ed->addbutton = ctrl_pushbutton(s, "Add", 'd', HELPCTX(telnet_environ), environ_handler, P(ed)); - ed->addbutton->generic.column = 1; + ed->addbutton->column = 1; ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', HELPCTX(telnet_environ), environ_handler, P(ed)); - ed->rembutton->generic.column = 1; + ed->rembutton->column = 1; ctrl_columns(s, 1, 100); ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(telnet_environ), @@ -2883,43 +2943,25 @@ void setup_config_box(struct controlbox *b, bool midsession, "Options controlling proxy usage"); s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); - c = ctrl_radiobuttons(s, "Proxy type:", 't', 3, - HELPCTX(proxy_type), - conf_radiobutton_handler, - I(CONF_proxy_type), - "None", I(PROXY_NONE), - "SOCKS 4", I(PROXY_SOCKS4), - "SOCKS 5", I(PROXY_SOCKS5), - "HTTP", I(PROXY_HTTP), - "Telnet", I(PROXY_TELNET), - NULL); - if (ssh_proxy_supported) { - /* Add an extra radio button to the above list. */ - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = dupstr("SSH"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_SSH); - } + c = ctrl_droplist(s, "Proxy type:", 't', 70, + HELPCTX(proxy_type), proxy_type_handler, I(0)); ctrl_columns(s, 2, 80, 20); c = ctrl_editbox(s, "Proxy hostname", 'y', 100, HELPCTX(proxy_main), conf_editbox_handler, - I(CONF_proxy_host), I(1)); - c->generic.column = 0; + I(CONF_proxy_host), ED_STR); + c->column = 0; c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(proxy_main), conf_editbox_handler, I(CONF_proxy_port), - I(-1)); - c->generic.column = 1; + ED_INT); + c->column = 1; ctrl_columns(s, 1, 100); ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, HELPCTX(proxy_exclude), conf_editbox_handler, - I(CONF_proxy_exclude_list), I(1)); + I(CONF_proxy_exclude_list), ED_STR); ctrl_checkbox(s, "Consider proxying local host connections", 'x', HELPCTX(proxy_exclude), conf_checkbox_handler, @@ -2930,20 +2972,20 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_proxy_dns), "No", I(FORCE_OFF), "Auto", I(AUTO), - "Yes", I(FORCE_ON), NULL); + "Yes", I(FORCE_ON)); ctrl_editbox(s, "Username", 'u', 60, HELPCTX(proxy_auth), conf_editbox_handler, - I(CONF_proxy_username), I(1)); + I(CONF_proxy_username), ED_STR); c = ctrl_editbox(s, "Password", 'w', 60, HELPCTX(proxy_auth), conf_editbox_handler, - I(CONF_proxy_password), I(1)); + I(CONF_proxy_password), ED_STR); c->editbox.password = true; - ctrl_editbox(s, "Telnet command", 'm', 100, + ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100, HELPCTX(proxy_command), conf_editbox_handler, - I(CONF_proxy_telnet_command), I(1)); + I(CONF_proxy_telnet_command), ED_STR); ctrl_radiobuttons(s, "Print proxy diagnostics " "in the terminal window", 'r', 5, @@ -2952,7 +2994,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_proxy_log_to_term), "No", I(FORCE_OFF), "Yes", I(FORCE_ON), - "Only until session starts", I(AUTO), NULL); + "Only until session starts", I(AUTO)); } /* @@ -2993,7 +3035,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Data to send to the server"); ctrl_editbox(s, "Remote command:", 'r', 100, HELPCTX(ssh_command), - conf_editbox_handler, I(CONF_remote_cmd), I(1)); + conf_editbox_handler, I(CONF_remote_cmd), ED_STR); s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); ctrl_checkbox(s, "Don't start a shell or command at all", 'n', @@ -3039,7 +3081,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_sshprot), "2", '2', I(3), - "1 (INSECURE)", '1', I(0), NULL); + "1 (INSECURE)", '1', I(0)); } /* @@ -3072,19 +3114,19 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_ssh_rekey_time), - I(-1)); + ED_INT); #ifndef NO_GSSAPI ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_gssapirekey), - I(-1)); + ED_INT); #endif ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_ssh_rekey_data), - I(16)); + ED_STR); ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", HELPCTX(ssh_kex_repeat)); } @@ -3120,7 +3162,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 75, 25); c = ctrl_text(s, "Host keys or fingerprints to accept:", HELPCTX(ssh_kex_manual_hostkeys)); - c->generic.column = 0; + c->column = 0; /* You want to select from the list, _then_ hit Remove. So * tab order should be that way round. */ mh = (struct manual_hostkey_data *) @@ -3128,8 +3170,8 @@ void setup_config_box(struct controlbox *b, bool midsession, mh->rembutton = ctrl_pushbutton(s, "Remove", 'r', HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); - mh->rembutton->generic.column = 1; - mh->rembutton->generic.tabdelay = true; + mh->rembutton->column = 1; + mh->rembutton->delay_taborder = true; mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); @@ -3143,14 +3185,25 @@ void setup_config_box(struct controlbox *b, bool midsession, mh->keybox = ctrl_editbox(s, "Key", 'k', 80, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh), P(NULL)); - mh->keybox->generic.column = 0; + mh->keybox->column = 0; mh->addbutton = ctrl_pushbutton(s, "Add key", 'y', HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); - mh->addbutton->generic.column = 1; + mh->addbutton->column = 1; ctrl_columns(s, 1, 100); } + /* + * But there's no reason not to forbid access to the host CA + * configuration box, which is common across sessions in any + * case. + */ + s = ctrl_getset(b, "Connection/SSH/Host keys", "ca", + "Configure trusted certification authorities"); + c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT, + HELPCTX(ssh_kex_cert), + host_ca_button_handler, I(0)); + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { /* * The Connection/SSH/Cipher panel. @@ -3197,19 +3250,19 @@ void setup_config_box(struct controlbox *b, bool midsession, // buttons to support setting and remove of certificate certd->cert_set_capi_button = ctrl_pushbutton(s, "Set CAPI Cert...", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_set_capi_button->generic.column = 0; + certd->cert_set_capi_button->column = 0; certd->cert_set_pkcs_button = ctrl_pushbutton(s, "Set PKCS Cert...", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_set_pkcs_button->generic.column = 0; + certd->cert_set_pkcs_button->column = 0; certd->cert_set_fido_button = ctrl_pushbutton(s, "Set FIDO Key...", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_set_fido_button->generic.column = 0; + certd->cert_set_fido_button->column = 0; certd->cert_clear_button = ctrl_pushbutton(s, "Clear Selected", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_clear_button->generic.column = 2; + certd->cert_clear_button->column = 2; certd->cert_view_button = ctrl_pushbutton(s, "View Selected", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_view_button->generic.column = 2; + certd->cert_view_button->column = 2; // textbox for thumbprint ctrl_text(s, " ", HELPCTX(no_help)); @@ -3221,7 +3274,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_text(s, "Authorized Keys file value:", HELPCTX(no_help)); certd->cert_copy_clipboard_button = ctrl_pushbutton(s, "Copy To Clipboard", NO_SHORTCUT, HELPCTX(no_help), cert_event_handler, P(certd)); - certd->cert_copy_clipboard_button->generic.column = 0; + certd->cert_copy_clipboard_button->column = 0; /* * The Connection/SSH/FIDO Tools panel. @@ -3247,16 +3300,16 @@ void setup_config_box(struct controlbox *b, bool midsession, "Key type:", 'r', 1, HELPCTX(no_help), fido_event_handler, P(fidod), "Resident Key", I(FORCE_OFF), "Non-Resident Key", I(FORCE_OFF), NULL); - fidod->fido_resident_radio->generic.column = 0; + fidod->fido_resident_radio->column = 0; fidod->fido_verification_radio = ctrl_radiobuttons( s, "User Verification:", 'u', 1, HELPCTX(no_help), fido_event_handler, P(fidod), "Key touch", I(false), "Key touch & PIN", I(true), NULL); - fidod->fido_verification_radio->generic.column = 2; + fidod->fido_verification_radio->column = 2; fidod->fido_create_key_button = ctrl_pushbutton(s, "Create Key...", NO_SHORTCUT, HELPCTX(no_help), fido_event_handler, P(fidod)); - fidod->fido_create_key_button->generic.column = 2; + fidod->fido_create_key_button->column = 2; // section for fido imports s = ctrl_getset(b, "Connection/SSH/Certificate/FIDO Tools", "import_params", "Key management"); @@ -3268,19 +3321,19 @@ void setup_config_box(struct controlbox *b, bool midsession, fidod->fido_clear_key_button = ctrl_pushbutton(s, "Clear Key Cache", NO_SHORTCUT, HELPCTX(no_help), fido_event_handler, P(fidod)); - fidod->fido_clear_key_button->generic.column = 0; + fidod->fido_clear_key_button->column = 0; fidod->fido_import_key_button = ctrl_pushbutton(s, "Import Keys...", NO_SHORTCUT, HELPCTX(no_help), fido_event_handler, P(fidod)); - fidod->fido_import_key_button->generic.column = 2; + fidod->fido_import_key_button->column = 2; fidod->fido_import_ssh_button = ctrl_pushbutton(s, "Import Key File...", NO_SHORTCUT, HELPCTX(no_help), fido_event_handler, P(fidod)); - fidod->fido_import_ssh_button->generic.column = 2; + fidod->fido_import_ssh_button->column = 2; fidod->fido_delete_key_button = ctrl_pushbutton(s, "Delete Key...", NO_SHORTCUT, HELPCTX(no_help), fido_event_handler, P(fidod)); - fidod->fido_delete_key_button->generic.column = 0; + fidod->fido_delete_key_button->column = 0; /* * The Connection/SSH/CAPI Tools panel. @@ -3307,12 +3360,12 @@ void setup_config_box(struct controlbox *b, bool midsession, "Provider type:", 'r', 1, HELPCTX(no_help), capi_event_handler, P(capid), "Smart Card / Token", I(FORCE_OFF), "Software", I(FORCE_OFF), NULL); - capid->capi_provider_radio->generic.column = 0; + capid->capi_provider_radio->column = 0; - ctrl_text(s, " ", HELPCTX(no_help))->generic.column = 2; + ctrl_text(s, " ", HELPCTX(no_help))->column = 2; capid->capi_create_key_button = ctrl_pushbutton(s, "Create Key...", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->capi_create_key_button->generic.column = 2; + capid->capi_create_key_button->column = 2; // selection for capi filter s = ctrl_getset(b, "Connection/SSH/Certificate/CAPI Tools", "filter_params", "Certificate selection filters"); @@ -3323,15 +3376,15 @@ void setup_config_box(struct controlbox *b, bool midsession, capid->capi_trusted_certs_checkbox = ctrl_checkbox(s, "Only Trusted", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->capi_trusted_certs_checkbox->generic.column = 0; + capid->capi_trusted_certs_checkbox->column = 0; capid->capi_smartcard_only_checkbox = ctrl_checkbox(s, "Only Smart Card", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->capi_smartcard_only_checkbox->generic.column = 0; + capid->capi_smartcard_only_checkbox->column = 0; capid->capi_no_expired_checkbox = ctrl_checkbox(s, "Not Expired", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->capi_no_expired_checkbox->generic.column = 2; + capid->capi_no_expired_checkbox->column = 2; // selection for other options filter s = ctrl_getset(b, "Connection/SSH/Certificate/CAPI Tools", "other_params", "Other options"); @@ -3339,11 +3392,11 @@ void setup_config_box(struct controlbox *b, bool midsession, capid->cert_store_button = ctrl_pushbutton(s, "Open Store...", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->cert_store_button->generic.column = 0; + capid->cert_store_button->column = 0; capid->capi_delete_key_button = ctrl_pushbutton(s, "Delete Key...", NO_SHORTCUT, HELPCTX(no_help), capi_event_handler, P(capid)); - capid->capi_delete_key_button->generic.column = 2; + capid->capi_delete_key_button->column = 2; #endif // PUTTY_CAC /* * The Connection/SSH/Auth panel. @@ -3380,8 +3433,8 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_try_ki_auth)); - s = ctrl_getset(b, "Connection/SSH/Auth", "params", - "Authentication parameters"); + s = ctrl_getset(b, "Connection/SSH/Auth", "aux", + "Other authentication-related options"); ctrl_checkbox(s, "Allow agent forwarding", 'f', HELPCTX(ssh_auth_agentfwd), conf_checkbox_handler, I(CONF_agentfwd)); @@ -3389,11 +3442,26 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_changeuser), conf_checkbox_handler, I(CONF_change_username)); + + ctrl_settitle(b, "Connection/SSH/Auth/Credentials", + "Credentials to authenticate with"); + + s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "publickey", + "Public-key authentication"); ctrl_filesel(s, "Private key file for authentication:", 'k', FILTER_KEY_FILES, false, "Select private key file", HELPCTX(ssh_auth_privkey), conf_filesel_handler, I(CONF_keyfile)); - + ctrl_filesel(s, "Certificate to use with the private key:", 'e', + NULL, false, "Select certificate file", + HELPCTX(ssh_auth_cert), + conf_filesel_handler, I(CONF_detached_cert)); + + s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "plugin", + "Plugin to provide authentication responses"); + ctrl_editbox(s, "Plugin command to run", NO_SHORTCUT, 100, + HELPCTX(ssh_auth_plugin), + conf_editbox_handler, I(CONF_auth_plugin), ED_STR); #ifndef NO_GSSAPI /* * Connection/SSH/Auth/GSSAPI, which sadly won't fit on @@ -3482,12 +3550,12 @@ void setup_config_box(struct controlbox *b, bool midsession, td->listbox->listbox.percentages[1] = 60; ctrl_columns(s, 2, 75, 25); c = ctrl_text(s, "For selected mode, send:", HELPCTX(ssh_ttymodes)); - c->generic.column = 0; + c->column = 0; td->setbutton = ctrl_pushbutton(s, "Set", 's', HELPCTX(ssh_ttymodes), ttymodes_handler, P(td)); - td->setbutton->generic.column = 1; - td->setbutton->generic.tabdelay = true; + td->setbutton->column = 1; + td->setbutton->delay_taborder = true; ctrl_columns(s, 1, 100); /* column break */ /* Bit of a hack to get the value radio buttons and * edit-box on the same row. */ @@ -3497,14 +3565,13 @@ void setup_config_box(struct controlbox *b, bool midsession, ttymodes_handler, P(td), "Auto", NO_SHORTCUT, P(NULL), "Nothing", NO_SHORTCUT, P(NULL), - "This:", NO_SHORTCUT, P(NULL), - NULL); - td->valradio->generic.column = 0; + "This:", NO_SHORTCUT, P(NULL)); + td->valradio->column = 0; td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, HELPCTX(ssh_ttymodes), ttymodes_handler, P(td), P(NULL)); - td->valbox->generic.column = 1; - td->valbox->generic.align_next_to = td->valradio; + td->valbox->column = 1; + td->valbox->align_next_to = td->valradio; ctrl_tabdelay(s, td->setbutton); } @@ -3521,13 +3588,13 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler,I(CONF_x11_forward)); ctrl_editbox(s, "X display location", 'x', 50, HELPCTX(ssh_tunnels_x11), - conf_editbox_handler, I(CONF_x11_display), I(1)); + conf_editbox_handler, I(CONF_x11_display), ED_STR); ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, HELPCTX(ssh_tunnels_x11auth), conf_radiobutton_handler, I(CONF_x11_auth), "MIT-Magic-Cookie-1", I(X11_MIT), - "XDM-Authorization-1", I(X11_XDM), NULL); + "XDM-Authorization-1", I(X11_XDM)); } /* @@ -3549,15 +3616,15 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 3, 55, 20, 25); c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); - c->generic.column = COLUMN_FIELD(0,2); + c->column = COLUMN_FIELD(0,2); /* You want to select from the list, _then_ hit Remove. So tab order * should be that way round. */ pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data)); pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r', HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); - pfd->rembutton->generic.column = 2; - pfd->rembutton->generic.tabdelay = true; + pfd->rembutton->column = 2; + pfd->rembutton->delay_taborder = true; pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); @@ -3573,12 +3640,12 @@ void setup_config_box(struct controlbox *b, bool midsession, pfd->addbutton = ctrl_pushbutton(s, "Add", 'd', HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); - pfd->addbutton->generic.column = 2; - pfd->addbutton->generic.tabdelay = true; + pfd->addbutton->column = 2; + pfd->addbutton->delay_taborder = true; pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd), P(NULL)); - pfd->sourcebox->generic.column = 0; + pfd->sourcebox->column = 0; pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd), P(NULL)); @@ -3587,8 +3654,7 @@ void setup_config_box(struct controlbox *b, bool midsession, portfwd_handler, P(pfd), "Local", 'l', P(NULL), "Remote", 'm', P(NULL), - "Dynamic", 'y', P(NULL), - NULL); + "Dynamic", 'y', P(NULL)); #ifndef NO_IPV6 pfd->addressfamily = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, @@ -3596,8 +3662,7 @@ void setup_config_box(struct controlbox *b, bool midsession, portfwd_handler, P(pfd), "Auto", 'u', I(ADDRTYPE_UNSPEC), "IPv4", '4', I(ADDRTYPE_IPV4), - "IPv6", '6', I(ADDRTYPE_IPV6), - NULL); + "IPv6", '6', I(ADDRTYPE_IPV6)); #endif ctrl_tabdelay(s, pfd->addbutton); ctrl_columns(s, 1, 100); @@ -3633,6 +3698,10 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_bugs_dropstart), sshbug_handler_manual_only, I(CONF_sshbug_dropstart)); + ctrl_droplist(s, "Chokes on PuTTY's full KEXINIT", 'p', 20, + HELPCTX(ssh_bugs_filter_kexinit), + sshbug_handler_manual_only, + I(CONF_sshbug_filter_kexinit)); ctrl_settitle(b, "Connection/SSH/More bugs", "Further workarounds for SSH server bugs"); @@ -3685,22 +3754,26 @@ void setup_config_box(struct controlbox *b, bool midsession, "Select a serial line"); ctrl_editbox(s, "Serial line to connect to", 'l', 40, HELPCTX(serial_line), - conf_editbox_handler, I(CONF_serline), I(1)); + conf_editbox_handler, I(CONF_serline), ED_STR); } s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); ctrl_editbox(s, "Speed (baud)", 's', 40, HELPCTX(serial_speed), - conf_editbox_handler, I(CONF_serspeed), I(-1)); + conf_editbox_handler, I(CONF_serspeed), ED_INT); ctrl_editbox(s, "Data bits", 'b', 40, HELPCTX(serial_databits), - conf_editbox_handler, I(CONF_serdatabits), I(-1)); + conf_editbox_handler, I(CONF_serdatabits), ED_INT); /* * Stop bits come in units of one half. */ + static const struct conf_editbox_handler_type conf_editbox_stopbits = { + .type = EDIT_FIXEDPOINT, .denominator = 2}; + ctrl_editbox(s, "Stop bits", 't', 40, HELPCTX(serial_stopbits), - conf_editbox_handler, I(CONF_serstopbits), I(-2)); + conf_editbox_handler, I(CONF_serstopbits), + CP(&conf_editbox_stopbits)); ctrl_droplist(s, "Parity", 'p', 40, HELPCTX(serial_parity), serial_parity_handler, I(ser_vt->serial_parity_mask)); @@ -3726,12 +3799,12 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_rfc_environ), "BSD (commonplace)", 'b', I(false), - "RFC 1408 (unusual)", 'f', I(true), NULL); + "RFC 1408 (unusual)", 'f', I(true)); ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, HELPCTX(telnet_passive), conf_radiobutton_bool_handler, I(CONF_passive_telnet), - "Passive", I(true), "Active", I(false), NULL); + "Passive", I(true), "Active", I(false)); } ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', HELPCTX(telnet_specialkeys), @@ -3754,7 +3827,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Data to send to the server"); ctrl_editbox(s, "Local username:", 'l', 50, HELPCTX(rlogin_localuser), - conf_editbox_handler, I(CONF_localusername), I(1)); + conf_editbox_handler, I(CONF_localusername), ED_STR); } @@ -3770,7 +3843,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_editbox(s, "Location string", 'l', 70, HELPCTX(supdup_location), conf_editbox_handler, I(CONF_supdup_location), - I(1)); + ED_STR); ctrl_radiobuttons(s, "Extended ASCII Character set:", 'e', 4, HELPCTX(supdup_ascii), @@ -3778,7 +3851,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_supdup_ascii_set), "None", I(SUPDUP_CHARSET_ASCII), "ITS", I(SUPDUP_CHARSET_ITS), - "WAITS", I(SUPDUP_CHARSET_WAITS), NULL); + "WAITS", I(SUPDUP_CHARSET_WAITS)); ctrl_checkbox(s, "**MORE** processing", 'm', HELPCTX(supdup_more), diff --git a/code/console.c b/code/console.c index 465fdfa7..62c64aff 100644 --- a/code/console.c +++ b/code/console.c @@ -9,56 +9,6 @@ #include "misc.h" #include "console.h" -char *hk_absentmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "The host key is not cached for this server:\n" - " %s (port %d)\n" - "You have no guarantee that the server is the computer\n" - "you think it is.\n" - "The server's %s key fingerprint is:\n" - " %s\n", host, port, keytype, fingerprint); -} - -const char hk_absentmsg_interactive_intro[] = - "If you trust this host, enter \"y\" to add the key to\n" - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without\n" - "adding the key to the cache, enter \"n\".\n" - "If you do not trust this host, press Return to abandon the\n" - "connection.\n"; -const char hk_absentmsg_interactive_prompt[] = - "Store key in cache? (y/n, Return cancels connection, " - "i for more info) "; - -char *hk_wrongmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The host key does not match the one PuTTY has cached\n" - "for this server:\n" - " %s (port %d)\n" - "This means that either the server administrator has\n" - "changed the host key, or you have actually connected\n" - "to another computer pretending to be the server.\n" - "The new %s key fingerprint is:\n" - " %s\n", host, port, keytype, fingerprint); -} - -const char hk_wrongmsg_interactive_intro[] = - "If you were expecting this change and trust the new key,\n" - "enter \"y\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating\n" - "the cache, enter \"n\".\n" - "If you want to abandon the connection completely, press\n" - "Return to cancel. Pressing Return is the ONLY guaranteed\n" - "safe choice.\n"; -const char hk_wrongmsg_interactive_prompt[] = - "Update cached key? (y/n, Return cancels connection, " - "i for more info) "; - const char weakcrypto_msg_common_fmt[] = "The first %s supported by the server is\n" "%s, which is below the configured warning threshold.\n"; @@ -73,6 +23,17 @@ const char weakhk_msg_common_fmt[] = const char console_continue_prompt[] = "Continue with connection? (y/n) "; const char console_abandoned_msg[] = "Connection abandoned.\n"; +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "enter \"y\"", + .hk_connect_once_action = "enter \"n\"", + .hk_cancel_action = "press Return", + .hk_cancel_action_Participle = "Pressing Return", + }; + return &descs; +} + bool console_batch_mode = false; /* diff --git a/code/contrib/authplugin-example.py b/code/contrib/authplugin-example.py new file mode 100644 index 00000000..395bd2c8 --- /dev/null +++ b/code/contrib/authplugin-example.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 + +# This is a demonstration example of how to write a +# keyboard-interactive authentication helper plugin using PuTTY's +# protocol for involving it in SSH connection setup. + +import io +import os +import struct +import sys + +# Exception class we'll use to get a clean exit on EOF. +class PluginEOF(Exception): pass + +# ---------------------------------------------------------------------- +# +# Marshalling and unmarshalling routines to write and read the +# necessary SSH data types to/from a binary file handle (which can +# include an io.BytesIO if you need to encode/decode in-process). +# +# Error handling is a totally ad-hoc mixture of 'assert' and just +# assuming things will have the right type, or be the right length of +# tuple, or be valid UTF-8. So it should be _robust_, in the sense +# that you'll get a Python exception if anything fails. But no +# sensible error reporting or recovery is implemented. +# +# That should be good enough, because PuTTY will log the plugin's +# standard error in its Event Log, so if the plugin crashes, you'll be +# able to retrieve the traceback. + +def wr_byte(fh, b): + assert 0 <= b < 0x100 + fh.write(bytes([b])) + +def wr_boolean(fh, b): + wr_byte(fh, 1 if b else 0) + +def wr_uint32(fh, u): + assert 0 <= u < 0x100000000 + fh.write(struct.pack(">I", u)) + +def wr_string(fh, s): + wr_uint32(fh, len(s)) + fh.write(s) + +def wr_string_utf8(fh, s): + wr_string(fh, s.encode("UTF-8")) + +def rd_n(fh, n): + data = fh.read(n) + if len(data) < n: + raise PluginEOF() + return data + +def rd_byte(fh): + return rd_n(fh, 1)[0] + +def rd_boolean(fh): + return rd_byte(fh) != 0 + +def rd_uint32(fh): + return struct.unpack(">I", rd_n(fh, 4))[0] + +def rd_string(fh): + length = rd_uint32(fh) + return rd_n(fh, length) + +def rd_string_utf8(fh): + return rd_string(fh).decode("UTF-8") + +# ---------------------------------------------------------------------- +# +# Protocol definitions. + +our_max_version = 2 + +PLUGIN_INIT = 1 +PLUGIN_INIT_RESPONSE = 2 +PLUGIN_PROTOCOL = 3 +PLUGIN_PROTOCOL_ACCEPT = 4 +PLUGIN_PROTOCOL_REJECT = 5 +PLUGIN_AUTH_SUCCESS = 6 +PLUGIN_AUTH_FAILURE = 7 +PLUGIN_INIT_FAILURE = 8 +PLUGIN_KI_SERVER_REQUEST = 20 +PLUGIN_KI_SERVER_RESPONSE = 21 +PLUGIN_KI_USER_REQUEST = 22 +PLUGIN_KI_USER_RESPONSE = 23 + +# ---------------------------------------------------------------------- +# +# Classes to make it easy to construct and receive messages. +# +# OutMessage is constructed with the message type; then you use the +# wr_foo() routines to add fields to it, and finally call its send() +# method. +# +# InMessage is constructed via the expect() class method, to which you +# give a list of message types you expect to see one of at this stage. +# Once you've got one, you can rd_foo() fields from it. + +class OutMessage: + def __init__(self, msgtype): + self.buf = io.BytesIO() + wr_byte(self.buf, msgtype) + self.write = self.buf.write + + def send(self, fh=sys.stdout.buffer): + wr_string(fh, self.buf.getvalue()) + fh.flush() + +class InMessage: + @classmethod + def expect(cls, expected_types, fh=sys.stdin.buffer): + self = cls() + self.buf = io.BytesIO(rd_string(fh)) + self.msgtype = rd_byte(self.buf) + self.read = self.buf.read + + if self.msgtype not in expected_types: + raise ValueError("received packet type {:d}, expected {}".format( + self.msgtype, ",".join(map("{:d}".format, + sorted(expected_types))))) + return self + +# ---------------------------------------------------------------------- +# +# The main implementation of the protocol. + +def protocol(): + # Start by expecting PLUGIN_INIT. + msg = InMessage.expect({PLUGIN_INIT}) + their_version = rd_uint32(msg) + hostname = rd_string_utf8(msg) + port = rd_uint32(msg) + username = rd_string_utf8(msg) + print(f"Got hostname {hostname!r}, port {port!r}", file=sys.stderr) + + # Decide which protocol version we're speaking. + version = min(their_version, our_max_version) + assert version != 0, "Protocol version 0 does not exist" + + if "TESTPLUGIN_INIT_FAIL" in os.environ: + # Test the plugin failing at startup time. + msg = OutMessage(PLUGIN_INIT_FAILURE) + wr_string_utf8(msg, os.environ["TESTPLUGIN_INIT_FAIL"]) + msg.send() + return + + # Send INIT_RESPONSE, with our protocol version and an overridden + # username. + # + # By default this test plugin doesn't override the username, but + # you can make it do so by setting TESTPLUGIN_USERNAME in the + # environment. + msg = OutMessage(PLUGIN_INIT_RESPONSE) + wr_uint32(msg, version) + wr_string_utf8(msg, os.environ.get("TESTPLUGIN_USERNAME", "")) + msg.send() + + # Outer loop run once per authentication protocol. + while True: + # Expect a message telling us what the protocol is. + msg = InMessage.expect({PLUGIN_PROTOCOL}) + method = rd_string(msg) + + if "TESTPLUGIN_PROTO_REJECT" in os.environ: + # Test the plugin failing at PLUGIN_PROTOCOL time. + msg = OutMessage(PLUGIN_PROTOCOL_REJECT) + wr_string_utf8(msg, os.environ["TESTPLUGIN_PROTO_REJECT"]) + msg.send() + continue + + # We only support keyboard-interactive. If we supported other + # auth methods, this would be the place to add further clauses + # to this if statement for them. + if method == b"keyboard-interactive": + msg = OutMessage(PLUGIN_PROTOCOL_ACCEPT) + msg.send() + + # Inner loop run once per keyboard-interactive exchange + # with the SSH server. + while True: + # Expect a set of prompts from the server, or + # terminate the loop on SUCCESS or FAILURE. + # + # (We could also respond to SUCCESS or FAILURE by + # updating caches of our own, if we had any that were + # useful.) + msg = InMessage.expect({PLUGIN_KI_SERVER_REQUEST, + PLUGIN_AUTH_SUCCESS, + PLUGIN_AUTH_FAILURE}) + if (msg.msgtype == PLUGIN_AUTH_SUCCESS or + msg.msgtype == PLUGIN_AUTH_FAILURE): + break + + # If we didn't just break, we're sitting on a + # PLUGIN_KI_SERVER_REQUEST message. Get all its bits + # and pieces out. + name = rd_string_utf8(msg) + instructions = rd_string_utf8(msg) + language = rd_string(msg) + nprompts = rd_uint32(msg) + prompts = [] + for i in range(nprompts): + prompt = rd_string_utf8(msg) + echo = rd_boolean(msg) + prompts.append((prompt, echo)) + + # Actually make up some answers for the prompts. This + # is the part that a non-example implementation would + # do very differently, of course! + # + # Here, we answer "foo" to every prompt, except that + # if there are exactly two prompts in the packet then + # we answer "stoat" to the first and "weasel" to the + # second. + # + # (These answers are consistent with the ones required + # by PuTTY's test SSH server Uppity in its own + # keyboard-interactive test implementation: that + # presents a two-prompt packet and expects + # "stoat","weasel" as the answers, and then presents a + # zero-prompt packet. So this test plugin will get you + # through Uppity's k-i in a one-touch manner. The + # "foo" in this code isn't used by Uppity at all; I + # just include it because I had to have _some_ + # handling for the else clause.) + # + # If TESTPLUGIN_PROMPTS is set in the environment, we + # ask the user questions of our own by sending them + # back to PuTTY as USER_REQUEST messages. + if nprompts == 2: + if "TESTPLUGIN_PROMPTS" in os.environ: + for i in range(2): + # Make up some questions to ask. + msg = OutMessage(PLUGIN_KI_USER_REQUEST) + wr_string_utf8( + msg, "Plugin request #{:d} (name)".format(i)) + wr_string_utf8( + msg, "Plugin request #{:d} (instructions)" + .format(i)) + wr_string(msg, b"") + wr_uint32(msg, 2) + wr_string_utf8(msg, "Prompt 1 of 2 (echo): ") + wr_boolean(msg, True) + wr_string_utf8(msg, "Prompt 2 of 2 (no echo): ") + wr_boolean(msg, False) + msg.send() + + # Expect the answers. + msg = InMessage.expect({PLUGIN_KI_USER_RESPONSE}) + user_nprompts = rd_uint32(msg) + assert user_nprompts == 2, ( + "Should match what we just sent") + for i in range(nprompts): + user_response = rd_string_utf8(msg) + # We don't actually check these + # responses for anything. + + answers = ["stoat", "weasel"] + + else: + answers = ["foo"] * nprompts + + # Send the answers to the SSH server's questions. + msg = OutMessage(PLUGIN_KI_SERVER_RESPONSE) + wr_uint32(msg, len(answers)) + for answer in answers: + wr_string_utf8(msg, answer) + msg.send() + + else: + # Default handler if we don't speak the offered protocol + # at all. + msg = OutMessage(PLUGIN_PROTOCOL_REJECT) + wr_string_utf8(msg, "") + msg.send() + +# Demonstration write to stderr, to prove that it shows up in PuTTY's +# Event Log. +print("Hello from test plugin's stderr", file=sys.stderr) + +try: + protocol() +except PluginEOF: + pass diff --git a/code/contrib/gdb.py b/code/contrib/gdb.py index 4c41edcc..fb7413ec 100644 --- a/code/contrib/gdb.py +++ b/code/contrib/gdb.py @@ -30,12 +30,29 @@ def to_string(self): return "mp_int(NULL)".format(address) return "mp_int(invalid @ {:#x})".format(address) +class PuTTYPtrlenPrettyPrinter(gdb.printing.PrettyPrinter): + "Pretty-print strings in PuTTY's ptrlen type." + name = "ptrlen" + + def __init__(self, val): + super(PuTTYPtrlenPrettyPrinter, self).__init__(self.name) + self.val = val + + def to_string(self): + length = int(self.val["len"]) + char_array_ptr_type = gdb.lookup_type( + "char").const().array(length).pointer() + line = self.val["ptr"].cast(char_array_ptr_type).dereference() + return repr(bytes(int(line[i]) for i in range(length))).lstrip('b') + class PuTTYPrinterSelector(gdb.printing.PrettyPrinter): def __init__(self): super(PuTTYPrinterSelector, self).__init__("PuTTY") def __call__(self, val): if str(val.type) == "mp_int *": return PuTTYMpintPrettyPrinter(val) + if str(val.type) == "ptrlen": + return PuTTYPtrlenPrettyPrinter(val) return None gdb.printing.register_pretty_printer(None, PuTTYPrinterSelector()) diff --git a/code/contrib/libfido2/include/fido.h b/code/contrib/libfido2/include/fido.h index 63d6de87..ce7da160 100644 --- a/code/contrib/libfido2/include/fido.h +++ b/code/contrib/libfido2/include/fido.h @@ -1,7 +1,29 @@ /* - * Copyright (c) 2018 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_H @@ -66,6 +88,7 @@ const unsigned char *fido_assert_sig_ptr(const fido_assert_t *, size_t); const unsigned char *fido_assert_user_id_ptr(const fido_assert_t *, size_t); const unsigned char *fido_assert_blob_ptr(const fido_assert_t *, size_t); +char **fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *); char **fido_cbor_info_extensions_ptr(const fido_cbor_info_t *); char **fido_cbor_info_options_name_ptr(const fido_cbor_info_t *); char **fido_cbor_info_transports_ptr(const fido_cbor_info_t *); @@ -86,6 +109,7 @@ const char *fido_dev_info_path(const fido_dev_info_t *); const char *fido_dev_info_product_string(const fido_dev_info_t *); const fido_dev_info_t *fido_dev_info_ptr(const fido_dev_info_t *, size_t); const uint8_t *fido_cbor_info_protocols_ptr(const fido_cbor_info_t *); +const uint64_t *fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *); const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *); const unsigned char *fido_cred_aaguid_ptr(const fido_cred_t *); const unsigned char *fido_cred_attstmt_ptr(const fido_cred_t *); @@ -178,6 +202,7 @@ size_t fido_assert_user_id_len(const fido_assert_t *, size_t); size_t fido_assert_blob_len(const fido_assert_t *, size_t); size_t fido_cbor_info_aaguid_len(const fido_cbor_info_t *); size_t fido_cbor_info_algorithm_count(const fido_cbor_info_t *); +size_t fido_cbor_info_certs_len(const fido_cbor_info_t *); size_t fido_cbor_info_extensions_len(const fido_cbor_info_t *); size_t fido_cbor_info_options_len(const fido_cbor_info_t *); size_t fido_cbor_info_protocols_len(const fido_cbor_info_t *); @@ -207,12 +232,17 @@ uint8_t fido_dev_build(const fido_dev_t *); uint8_t fido_dev_flags(const fido_dev_t *); int16_t fido_dev_info_vendor(const fido_dev_info_t *); int16_t fido_dev_info_product(const fido_dev_info_t *); +uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *); uint64_t fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *); uint64_t fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *); uint64_t fido_cbor_info_maxcredidlen(const fido_cbor_info_t *); uint64_t fido_cbor_info_maxlargeblob(const fido_cbor_info_t *); uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *); -uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_attempts(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_modality(const fido_cbor_info_t *); +int64_t fido_cbor_info_rk_remaining(const fido_cbor_info_t *); bool fido_dev_has_pin(const fido_dev_t *); bool fido_dev_has_uv(const fido_dev_t *); @@ -223,6 +253,7 @@ bool fido_dev_supports_cred_prot(const fido_dev_t *); bool fido_dev_supports_permissions(const fido_dev_t *); bool fido_dev_supports_pin(const fido_dev_t *); bool fido_dev_supports_uv(const fido_dev_t *); +bool fido_cbor_info_new_pin_required(const fido_cbor_info_t *); int fido_dev_largeblob_get(fido_dev_t *, const unsigned char *, size_t, unsigned char **, size_t *); diff --git a/code/contrib/libfido2/include/fido/bio.h b/code/contrib/libfido2/include/fido/bio.h index afe9ca47..f5039e03 100644 --- a/code/contrib/libfido2/include/fido/bio.h +++ b/code/contrib/libfido2/include/fido/bio.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2019 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_BIO_H diff --git a/code/contrib/libfido2/include/fido/config.h b/code/contrib/libfido2/include/fido/config.h index d8134a3c..cba286f0 100644 --- a/code/contrib/libfido2/include/fido/config.h +++ b/code/contrib/libfido2/include/fido/config.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2020 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_CONFIG_H diff --git a/code/contrib/libfido2/include/fido/credman.h b/code/contrib/libfido2/include/fido/credman.h index 66a96697..9f9dff1d 100644 --- a/code/contrib/libfido2/include/fido/credman.h +++ b/code/contrib/libfido2/include/fido/credman.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2019-2021 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_CREDMAN_H diff --git a/code/contrib/libfido2/include/fido/eddsa.h b/code/contrib/libfido2/include/fido/eddsa.h index 083721cc..7981a6f8 100644 --- a/code/contrib/libfido2/include/fido/eddsa.h +++ b/code/contrib/libfido2/include/fido/eddsa.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2019 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_EDDSA_H diff --git a/code/contrib/libfido2/include/fido/err.h b/code/contrib/libfido2/include/fido/err.h index 74fdf9d2..7db25f26 100644 --- a/code/contrib/libfido2/include/fido/err.h +++ b/code/contrib/libfido2/include/fido/err.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2018 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_ERR_H diff --git a/code/contrib/libfido2/include/fido/es256.h b/code/contrib/libfido2/include/fido/es256.h index 683494da..0450de29 100644 --- a/code/contrib/libfido2/include/fido/es256.h +++ b/code/contrib/libfido2/include/fido/es256.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2018-2021 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_ES256_H diff --git a/code/contrib/libfido2/include/fido/es384.h b/code/contrib/libfido2/include/fido/es384.h new file mode 100644 index 00000000..b4b4ca71 --- /dev/null +++ b/code/contrib/libfido2/include/fido/es384.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ES384_H +#define _FIDO_ES384_H + +#include + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +es384_pk_t *es384_pk_new(void); +void es384_pk_free(es384_pk_t **); +EVP_PKEY *es384_pk_to_EVP_PKEY(const es384_pk_t *); + +int es384_pk_from_EC_KEY(es384_pk_t *, const EC_KEY *); +int es384_pk_from_EVP_PKEY(es384_pk_t *, const EVP_PKEY *); +int es384_pk_from_ptr(es384_pk_t *, const void *, size_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_ES384_H */ diff --git a/code/contrib/libfido2/include/fido/param.h b/code/contrib/libfido2/include/fido/param.h index 7c6db98c..511370bc 100644 --- a/code/contrib/libfido2/include/fido/param.h +++ b/code/contrib/libfido2/include/fido/param.h @@ -1,7 +1,29 @@ /* - * Copyright (c) 2018-2021 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_PARAM_H @@ -82,12 +104,13 @@ #define FIDO_CAP_NMSG 0x08 /* if set, device doesn't support CTAP_CMD_MSG */ /* Supported COSE algorithms. */ -#define COSE_UNSPEC 0 -#define COSE_ES256 -7 -#define COSE_EDDSA -8 -#define COSE_ECDH_ES256 -25 -#define COSE_RS256 -257 -#define COSE_RS1 -65535 +#define COSE_UNSPEC 0 +#define COSE_ES256 -7 +#define COSE_EDDSA -8 +#define COSE_ECDH_ES256 -25 +#define COSE_ES384 -35 +#define COSE_RS256 -257 +#define COSE_RS1 -65535 /* Supported COSE types. */ #define COSE_KTY_OKP 1 @@ -96,6 +119,7 @@ /* Supported curves. */ #define COSE_P256 1 +#define COSE_P384 2 #define COSE_ED25519 6 /* Supported extensions. */ @@ -118,4 +142,19 @@ FIDO_EXT_MINPINLEN) #endif /* _FIDO_INTERNAL */ +/* Recognised UV modes. */ +#define FIDO_UV_MODE_TUP 0x0001 /* internal test of user presence */ +#define FIDO_UV_MODE_FP 0x0002 /* internal fingerprint check */ +#define FIDO_UV_MODE_PIN 0x0004 /* internal pin check */ +#define FIDO_UV_MODE_VOICE 0x0008 /* internal voice recognition */ +#define FIDO_UV_MODE_FACE 0x0010 /* internal face recognition */ +#define FIDO_UV_MODE_LOCATION 0x0020 /* internal location check */ +#define FIDO_UV_MODE_EYE 0x0040 /* internal eyeprint check */ +#define FIDO_UV_MODE_DRAWN 0x0080 /* internal drawn pattern check */ +#define FIDO_UV_MODE_HAND 0x0100 /* internal handprint verification */ +#define FIDO_UV_MODE_NONE 0x0200 /* TUP/UV not required */ +#define FIDO_UV_MODE_ALL 0x0400 /* all supported UV modes required */ +#define FIDO_UV_MODE_EXT_PIN 0x0800 /* external pin verification */ +#define FIDO_UV_MODE_EXT_DRAWN 0x1000 /* external drawn pattern check */ + #endif /* !_FIDO_PARAM_H */ diff --git a/code/contrib/libfido2/include/fido/rs256.h b/code/contrib/libfido2/include/fido/rs256.h index 03981619..6f8c7819 100644 --- a/code/contrib/libfido2/include/fido/rs256.h +++ b/code/contrib/libfido2/include/fido/rs256.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2018-2021 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_RS256_H diff --git a/code/contrib/libfido2/include/fido/types.h b/code/contrib/libfido2/include/fido/types.h index 593a6a6b..cfb4c7a7 100644 --- a/code/contrib/libfido2/include/fido/types.h +++ b/code/contrib/libfido2/include/fido/types.h @@ -1,7 +1,29 @@ /* * Copyright (c) 2018-2022 Yubico AB. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _FIDO_TYPES_H @@ -73,6 +95,12 @@ typedef struct es256_sk { unsigned char d[32]; } es256_sk_t; +/* COSE ES384 (ECDSA over P-384 with SHA-384) public key */ +typedef struct es384_pk { + unsigned char x[48]; + unsigned char y[48]; +} es384_pk_t; + /* COSE RS256 (2048-bit RSA with PKCS1 padding and SHA-256) public key */ typedef struct rs256_pk { unsigned char n[256]; @@ -105,6 +133,7 @@ typedef struct fido_attcred { int type; /* credential's cose algorithm */ union { /* credential's public key */ es256_pk_t es256; + es384_pk_t es384; rs256_pk_t rs256; eddsa_pk_t eddsa; } pubkey; @@ -219,6 +248,12 @@ typedef struct fido_algo_array { size_t len; } fido_algo_array_t; +typedef struct fido_cert_array { + char **name; + uint64_t *value; + size_t len; +} fido_cert_array_t; + typedef struct fido_cbor_info { fido_str_array_t versions; /* supported versions: fido2|u2f */ fido_str_array_t extensions; /* list of supported extensions */ @@ -233,6 +268,13 @@ typedef struct fido_cbor_info { uint64_t fwversion; /* firmware version */ uint64_t maxcredbloblen; /* max credBlob length */ uint64_t maxlargeblob; /* max largeBlob array length */ + uint64_t maxrpid_minlen; /* max rpid in set_pin_minlen_rpid */ + uint64_t minpinlen; /* min pin len enforced */ + uint64_t uv_attempts; /* platform uv attempts */ + uint64_t uv_modality; /* bitmask of supported uv types */ + int64_t rk_remaining; /* remaining resident credentials */ + bool new_pin_reqd; /* new pin required */ + fido_cert_array_t certs; /* associated certifications */ } fido_cbor_info_t; typedef struct fido_dev_info { @@ -281,6 +323,7 @@ typedef struct fido_dev fido_dev_t; typedef struct fido_dev_info fido_dev_info_t; typedef struct es256_pk es256_pk_t; typedef struct es256_sk es256_sk_t; +typedef struct es384_pk es384_pk_t; typedef struct rs256_pk rs256_pk_t; typedef struct eddsa_pk eddsa_pk_t; #endif /* _FIDO_INTERNAL */ diff --git a/code/contrib/libfido2/include/openssl/opensslv.h b/code/contrib/libfido2/include/openssl/opensslv.h index 1cafae6b..f24afb9c 100644 --- a/code/contrib/libfido2/include/openssl/opensslv.h +++ b/code/contrib/libfido2/include/openssl/opensslv.h @@ -3,9 +3,9 @@ #define HEADER_OPENSSLV_H /* These will change with each release of LibreSSL-portable */ -#define LIBRESSL_VERSION_NUMBER 0x3050200fL +#define LIBRESSL_VERSION_NUMBER 0x3050300fL /* ^ Patch starts here */ -#define LIBRESSL_VERSION_TEXT "LibreSSL 3.5.2" +#define LIBRESSL_VERSION_TEXT "LibreSSL 3.5.3" /* These will never change */ #define OPENSSL_VERSION_NUMBER 0x20000000L diff --git a/code/contrib/libfido2/lib/x64/cbor.lib b/code/contrib/libfido2/lib/x64/cbor.lib index f3924b30..039c145f 100644 Binary files a/code/contrib/libfido2/lib/x64/cbor.lib and b/code/contrib/libfido2/lib/x64/cbor.lib differ diff --git a/code/contrib/libfido2/lib/x64/crypto-49.lib b/code/contrib/libfido2/lib/x64/crypto-49.lib index 65aaecd3..a04e3e00 100644 Binary files a/code/contrib/libfido2/lib/x64/crypto-49.lib and b/code/contrib/libfido2/lib/x64/crypto-49.lib differ diff --git a/code/contrib/libfido2/lib/x64/fido2.lib b/code/contrib/libfido2/lib/x64/fido2.lib index 32efa270..f5f38e35 100644 Binary files a/code/contrib/libfido2/lib/x64/fido2.lib and b/code/contrib/libfido2/lib/x64/fido2.lib differ diff --git a/code/contrib/libfido2/lib/x64/zlib1.lib b/code/contrib/libfido2/lib/x64/zlib1.lib index c8136d5d..07e4b106 100644 Binary files a/code/contrib/libfido2/lib/x64/zlib1.lib and b/code/contrib/libfido2/lib/x64/zlib1.lib differ diff --git a/code/crypto/CMakeLists.txt b/code/crypto/CMakeLists.txt index c6420361..4b0aa907 100644 --- a/code/crypto/CMakeLists.txt +++ b/code/crypto/CMakeLists.txt @@ -2,6 +2,10 @@ add_sources_from_current_dir(crypto aes-common.c aes-select.c aes-sw.c + aesgcm-common.c + aesgcm-select.c + aesgcm-sw.c + aesgcm-ref-poly.c arcfour.c argon2.c bcrypt.c @@ -20,6 +24,8 @@ add_sources_from_current_dir(crypto mac_simple.c md5.c mpint.c + ntru.c + openssh-certs.c prng.c pubkey-pem.c pubkey-ppk.c @@ -121,6 +127,16 @@ if(HAVE_WMMINTRIN_H) volatile __m128i r, a, b, c; int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); }" ADD_SOURCES_IF_SUCCESSFUL sha256-ni.c sha1-ni.c) + + test_compile_with_flags(HAVE_CLMUL + GNU_FLAGS -msse4.1 -mpclmul + TEST_SOURCE " + #include + #include + volatile __m128i r, a, b; + int main(void) { r = _mm_clmulepi64_si128(a, b, 5); + r = _mm_shuffle_epi8(r, a); }" + ADD_SOURCES_IF_SUCCESSFUL aesgcm-clmul.c) endif() # ---------------------------------------------------------------------- @@ -168,6 +184,25 @@ if(neon) int main(void) { r = vaeseq_u8(a, b); s = vsha256hq_u32(x, y, z); }" ADD_SOURCES_IF_SUCCESSFUL aes-neon.c sha256-neon.c sha1-neon.c) + test_compile_with_flags(HAVE_NEON_PMULL + GNU_FLAGS -march=armv8-a+crypto + MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS + TEST_SOURCE " + #include <${neon_header}> + volatile poly128_t r; + volatile poly64_t a, b; + volatile poly64x2_t u, v; + int main(void) { r = vmull_p64(a, b); r = vmull_high_p64(u, v); }" + ADD_SOURCES_IF_SUCCESSFUL aesgcm-neon.c) + + test_compile_with_flags(HAVE_NEON_VADDQ_P128 + GNU_FLAGS -march=armv8-a+crypto + MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS + TEST_SOURCE " + #include <${neon_header}> + volatile poly128_t r; + int main(void) { r = vaddq_p128(r, r); }") + # The 'sha3' architecture extension, despite the name, includes # support for SHA-512 (from the SHA-2 standard) as well as SHA-3 # proper. diff --git a/code/crypto/aes-common.c b/code/crypto/aes-common.c index e1c41ddf..3bed2af1 100644 --- a/code/crypto/aes-common.c +++ b/code/crypto/aes-common.c @@ -12,3 +12,9 @@ const uint8_t aes_key_setup_round_constants[10] = { * regardless of the key. */ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, }; + +void aesgcm_cipher_crypt_length( + ssh_cipher *cipher, void *blk, int len, unsigned long seq) +{ + /* Do nothing: lengths are sent in clear for this cipher. */ +} diff --git a/code/crypto/aes-neon.c b/code/crypto/aes-neon.c index d47d2fd3..5cd9f2d1 100644 --- a/code/crypto/aes-neon.c +++ b/code/crypto/aes-neon.c @@ -176,6 +176,18 @@ static inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); } +/* + * Much simpler auxiliary routine to increment the counter for GCM + * mode. This only has to increment the low word. + */ +static inline uint8x16_t aes_neon_gcm_increment(uint8x16_t in) +{ + uint32x4_t inw = vreinterpretq_u32_u8(in); + uint32x4_t ONE = vcombine_u32(vcreate_u32(0), vcreate_u32(1)); + inw = vaddq_u32(inw, ONE); + return vreinterpretq_u8_u32(inw); +} + /* * The SSH interface and the cipher modes. */ @@ -211,7 +223,7 @@ static void aes_neon_setkey(ssh_cipher *ciph, const void *vkey) const unsigned char *key = (const unsigned char *)vkey; aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); + ctx->keysched_e, ctx->keysched_d); } static void aes_neon_setiv_cbc(ssh_cipher *ciph, const void *iv) @@ -227,6 +239,28 @@ static void aes_neon_setiv_sdctr(ssh_cipher *ciph, const void *iv) ctx->iv = aes_neon_sdctr_reverse(counter); } +static void aes_neon_setiv_gcm(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t counter = vld1q_u8(iv); + ctx->iv = aes_neon_sdctr_reverse(counter); + ctx->iv = vreinterpretq_u8_u32(vsetq_lane_u32( + 1, vreinterpretq_u32_u8(ctx->iv), 2)); +} + +static void aes_neon_next_message_gcm(ssh_cipher *ciph) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint32x4_t iv = vreinterpretq_u32_u8(ctx->iv); + uint64_t msg_counter = vgetq_lane_u32(iv, 0); + msg_counter = (msg_counter << 32) | vgetq_lane_u32(iv, 3); + msg_counter++; + iv = vsetq_lane_u32(msg_counter >> 32, iv, 0); + iv = vsetq_lane_u32(msg_counter, iv, 3); + iv = vsetq_lane_u32(1, iv, 2); + ctx->iv = vreinterpretq_u8_u32(iv); +} + typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); static inline void aes_cbc_neon_encrypt( @@ -275,6 +309,31 @@ static inline void aes_sdctr_neon( } } +static inline void aes_encrypt_ecb_block_neon( + ssh_cipher *ciph, void *blk, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t plaintext = vld1q_u8(blk); + uint8x16_t ciphertext = encrypt(plaintext, ctx->keysched_e); + vst1q_u8(blk, ciphertext); +} + +static inline void aes_gcm_neon( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); + uint8x16_t keystream = encrypt(counter, ctx->keysched_e); + uint8x16_t input = vld1q_u8(blk); + uint8x16_t output = veorq_u8(input, keystream); + vst1q_u8(blk, output); + ctx->iv = aes_neon_gcm_increment(ctx->iv); + } +} + #define NEON_ENC_DEC(len) \ static void aes##len##_neon_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -285,6 +344,12 @@ static inline void aes_sdctr_neon( static void aes##len##_neon_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static void aes##len##_neon_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static void aes##len##_neon_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_neon(ciph, vblk, aes_neon_##len##_e); } NEON_ENC_DEC(128) NEON_ENC_DEC(192) diff --git a/code/crypto/aes-ni.c b/code/crypto/aes-ni.c index 22348de4..67d82b86 100644 --- a/code/crypto/aes-ni.c +++ b/code/crypto/aes-ni.c @@ -137,6 +137,16 @@ static inline __m128i aes_ni_sdctr_increment(__m128i v) return v; } +/* + * Much simpler auxiliary routine to increment the counter for GCM + * mode. This only has to increment the low word. + */ +static inline __m128i aes_ni_gcm_increment(__m128i v) +{ + const __m128i ONE = _mm_setr_epi32(1,0,0,0); + return _mm_add_epi32(v, ONE); +} + /* * Auxiliary routine to reverse the byte order of a vector, so that * the SDCTR IV can be made big-endian for feeding to the cipher. @@ -214,6 +224,25 @@ static void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv) ctx->iv = aes_ni_sdctr_reverse(counter); } +static void aes_ni_setiv_gcm(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i counter = _mm_loadu_si128(iv); + ctx->iv = aes_ni_sdctr_reverse(counter); + ctx->iv = _mm_insert_epi32(ctx->iv, 1, 0); +} + +static void aes_ni_next_message_gcm(ssh_cipher *ciph) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + uint32_t fixed = _mm_extract_epi32(ctx->iv, 3); + uint64_t msg_counter = _mm_extract_epi32(ctx->iv, 2); + msg_counter <<= 32; + msg_counter |= (uint32_t)_mm_extract_epi32(ctx->iv, 1); + msg_counter++; + ctx->iv = _mm_set_epi32(fixed, msg_counter >> 32, msg_counter, 1); +} + typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); static inline void aes_cbc_ni_encrypt( @@ -262,6 +291,31 @@ static inline void aes_sdctr_ni( } } +static inline void aes_encrypt_ecb_block_ni( + ssh_cipher *ciph, void *blk, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i plaintext = _mm_loadu_si128(blk); + __m128i ciphertext = encrypt(plaintext, ctx->keysched_e); + _mm_storeu_si128(blk, ciphertext); +} + +static inline void aes_gcm_ni( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i counter = aes_ni_sdctr_reverse(ctx->iv); + __m128i keystream = encrypt(counter, ctx->keysched_e); + __m128i input = _mm_loadu_si128((const __m128i *)blk); + __m128i output = _mm_xor_si128(input, keystream); + _mm_storeu_si128((__m128i *)blk, output); + ctx->iv = aes_ni_gcm_increment(ctx->iv); + } +} + #define NI_ENC_DEC(len) \ static void aes##len##_ni_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -272,6 +326,12 @@ static inline void aes_sdctr_ni( static void aes##len##_ni_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static void aes##len##_ni_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static void aes##len##_ni_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_ni(ciph, vblk, aes_ni_##len##_e); } NI_ENC_DEC(128) NI_ENC_DEC(192) diff --git a/code/crypto/aes-select.c b/code/crypto/aes-select.c index f0c5031f..62b4ab01 100644 --- a/code/crypto/aes-select.c +++ b/code/crypto/aes-select.c @@ -39,7 +39,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) #define IF_NEON(...) #endif -#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits) \ +#define AES_SELECTOR_VTABLE(mode_c, id, mode_display, bits, ...) \ static const ssh_cipheralg * \ ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ @@ -49,21 +49,33 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) }; \ const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \ .new = aes_select, \ - .ssh2_id = "aes" #bits "-" mode_protocol, \ + .ssh2_id = id, \ .blksize = 16, \ .real_keybits = bits, \ .padded_keybytes = bits/8, \ .text_name = "AES-" #bits " " mode_display \ " (dummy selector vtable)", \ .extra = ssh_aes ## bits ## _ ## mode_c ## _impls, \ + __VA_ARGS__ \ } -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128); -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 192); -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256); +AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, ); +AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, ); +AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, ); +AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, ); +AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, ); +AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, ); +AES_SELECTOR_VTABLE(gcm, "aes128-gcm@openssh.com", "GCM", 128, + .required_mac = &ssh2_aesgcm_mac); +AES_SELECTOR_VTABLE(gcm, "aes256-gcm@openssh.com", "GCM", 256, + .required_mac = &ssh2_aesgcm_mac); + +/* 192-bit AES-GCM is included only so that testcrypt can run standard + * test vectors against it. OpenSSH doesn't define a protocol id for + * it. Hence setting its ssh2_id to NULL here, and more importantly, + * leaving it out of aesgcm_list[] below. */ +AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192, + .required_mac = &ssh2_aesgcm_mac); static const ssh_cipheralg ssh_rijndael_lysator = { /* Same as aes256_cbc, but with a different protocol ID */ @@ -87,3 +99,12 @@ static const ssh_cipheralg *const aes_list[] = { }; const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; + +static const ssh_cipheralg *const aesgcm_list[] = { + /* OpenSSH only defines protocol ids for 128- and 256-bit AES-GCM, + * not 192-bit. */ + &ssh_aes128_gcm, + &ssh_aes256_gcm, +}; + +const ssh2_ciphers ssh2_aesgcm = { lenof(aesgcm_list), aesgcm_list }; diff --git a/code/crypto/aes-sw.c b/code/crypto/aes-sw.c index f8512388..aaa3c475 100644 --- a/code/crypto/aes-sw.c +++ b/code/crypto/aes-sw.c @@ -827,6 +827,18 @@ struct aes_sw_context { uint8_t keystream[SLICE_PARALLELISM * 16]; uint8_t *keystream_pos; } sdctr; + struct { + /* In GCM mode, the cipher preimage consists of three + * sections: one fixed, one that increments per message + * sent and MACed, and one that increments per cipher + * block. */ + uint64_t msg_counter; + uint32_t fixed_iv, block_counter; + /* But we keep the precomputed keystream chunks just like + * SDCTR mode. */ + uint8_t keystream[SLICE_PARALLELISM * 16]; + uint8_t *keystream_pos; + } gcm; } iv; ssh_cipher ciph; }; @@ -874,6 +886,31 @@ static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); } +static void aes_sw_setiv_gcm(ssh_cipher *ciph, const void *viv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + const uint8_t *iv = (const uint8_t *)viv; + + ctx->iv.gcm.fixed_iv = GET_32BIT_MSB_FIRST(iv); + ctx->iv.gcm.msg_counter = GET_64BIT_MSB_FIRST(iv + 4); + ctx->iv.gcm.block_counter = 1; + + /* Set keystream_pos to indicate that the keystream cache is + * currently empty */ + ctx->iv.gcm.keystream_pos = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); +} + +static void aes_sw_next_message_gcm(ssh_cipher *ciph) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + ctx->iv.gcm.msg_counter++; + ctx->iv.gcm.block_counter = 1; + ctx->iv.gcm.keystream_pos = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); +} + typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) @@ -1021,6 +1058,56 @@ static inline void aes_sdctr_sw( } } +static inline void aes_encrypt_ecb_block_sw(ssh_cipher *ciph, void *blk) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + aes_sliced_e_serial(blk, blk, &ctx->sk); +} + +static inline void aes_gcm_sw( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * GCM encrypt/decrypt looks just like SDCTR, except that the + * method of generating more keystream varies slightly. + */ + + uint8_t *keystream_end = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + + if (ctx->iv.gcm.keystream_pos == keystream_end) { + /* + * Generate some keystream. + */ + for (uint8_t *block = ctx->iv.gcm.keystream; + block < keystream_end; block += 16) { + /* Format the counter value into the buffer. */ + PUT_32BIT_MSB_FIRST(block, ctx->iv.gcm.fixed_iv); + PUT_64BIT_MSB_FIRST(block + 4, ctx->iv.gcm.msg_counter); + PUT_32BIT_MSB_FIRST(block + 12, ctx->iv.gcm.block_counter); + + /* Increment the counter. */ + ctx->iv.gcm.block_counter++; + } + + /* Encrypt all those counter blocks. */ + aes_sliced_e_parallel(ctx->iv.gcm.keystream, + ctx->iv.gcm.keystream, &ctx->sk); + + /* Reset keystream_pos to the start of the buffer. */ + ctx->iv.gcm.keystream_pos = ctx->iv.gcm.keystream; + } + + memxor16(blk, blk, ctx->iv.gcm.keystream_pos); + ctx->iv.gcm.keystream_pos += 16; + } +} + #define SW_ENC_DEC(len) \ static void aes##len##_sw_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -1030,7 +1117,13 @@ static inline void aes_sdctr_sw( { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ static void aes##len##_sw_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_sw(ciph, vblk, blklen); } + { aes_sdctr_sw(ciph, vblk, blklen); } \ + static void aes##len##_sw_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_sw(ciph, vblk, blklen); } \ + static void aes##len##_sw_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_sw(ciph, vblk); } SW_ENC_DEC(128) SW_ENC_DEC(192) diff --git a/code/crypto/aes.h b/code/crypto/aes.h index 1960713a..cab5b989 100644 --- a/code/crypto/aes.h +++ b/code/crypto/aes.h @@ -15,6 +15,11 @@ struct aes_extra { /* Point to a writable substructure. */ struct aes_extra_mutable *mut; + + /* Extra API function specific to AES, to encrypt a single block + * in ECB mode without touching the IV. Used by AES-GCM MAC + * setup. */ + void (*encrypt_ecb_block)(ssh_cipher *, void *); }; struct aes_extra_mutable { bool checked_availability; @@ -30,6 +35,17 @@ static inline bool check_availability(const struct aes_extra *extra) return extra->mut->is_available; } +/* Shared stub function for all the AES-GCM vtables. */ +void aesgcm_cipher_crypt_length( + ssh_cipher *cipher, void *blk, int len, unsigned long seq); + +/* External entry point for the encrypt_ecb_block function. */ +static inline void aes_encrypt_ecb_block(ssh_cipher *ciph, void *blk) +{ + const struct aes_extra *extra = ciph->vt->extra; + extra->encrypt_ecb_block(ciph, blk); +} + /* * Macros to define vtables for AES variants. There are a lot of * these, because of the cross product between cipher modes, key @@ -37,13 +53,19 @@ static inline bool check_availability(const struct aes_extra *extra) * some effort here to reduce the boilerplate in the sub-files. */ -#define AES_EXTRA(impl_c) \ +#define AES_EXTRA_BITS(impl_c, bits) \ static struct aes_extra_mutable aes ## impl_c ## _extra_mut; \ - static const struct aes_extra aes ## impl_c ## _extra = { \ + static const struct aes_extra aes ## bits ## impl_c ## _extra = { \ .check_available = aes ## impl_c ## _available, \ .mut = &aes ## impl_c ## _extra_mut, \ + .encrypt_ecb_block = &aes ## bits ## impl_c ## _encrypt_ecb_block, \ } +#define AES_EXTRA(impl_c) \ + AES_EXTRA_BITS(impl_c, 128); \ + AES_EXTRA_BITS(impl_c, 192); \ + AES_EXTRA_BITS(impl_c, 256) + #define AES_CBC_VTABLE(impl_c, impl_display, bits) \ const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = { \ .new = aes ## impl_c ## _new, \ @@ -52,13 +74,14 @@ static inline bool check_availability(const struct aes_extra *extra) .setkey = aes ## impl_c ## _setkey, \ .encrypt = aes ## bits ## impl_c ## _cbc_encrypt, \ .decrypt = aes ## bits ## impl_c ## _cbc_decrypt, \ + .next_message = nullcipher_next_message, \ .ssh2_id = "aes" #bits "-cbc", \ .blksize = 16, \ .real_keybits = bits, \ .padded_keybytes = bits/8, \ .flags = SSH_CIPHER_IS_CBC, \ .text_name = "AES-" #bits " CBC (" impl_display ")", \ - .extra = &aes ## impl_c ## _extra, \ + .extra = &aes ## bits ## impl_c ## _extra, \ } #define AES_SDCTR_VTABLE(impl_c, impl_display, bits) \ @@ -69,13 +92,38 @@ static inline bool check_availability(const struct aes_extra *extra) .setkey = aes ## impl_c ## _setkey, \ .encrypt = aes ## bits ## impl_c ## _sdctr, \ .decrypt = aes ## bits ## impl_c ## _sdctr, \ + .next_message = nullcipher_next_message, \ .ssh2_id = "aes" #bits "-ctr", \ .blksize = 16, \ .real_keybits = bits, \ .padded_keybytes = bits/8, \ .flags = 0, \ .text_name = "AES-" #bits " SDCTR (" impl_display ")", \ - .extra = &aes ## impl_c ## _extra, \ + .extra = &aes ## bits ## impl_c ## _extra, \ + } + +#define AES_GCM_VTABLE(impl_c, impl_display, bits) \ + const ssh_cipheralg ssh_aes ## bits ## _gcm ## impl_c = { \ + .new = aes ## impl_c ## _new, \ + .free = aes ## impl_c ## _free, \ + .setiv = aes ## impl_c ## _setiv_gcm, \ + .setkey = aes ## impl_c ## _setkey, \ + .encrypt = aes ## bits ## impl_c ## _gcm, \ + .decrypt = aes ## bits ## impl_c ## _gcm, \ + .encrypt_length = aesgcm_cipher_crypt_length, \ + .decrypt_length = aesgcm_cipher_crypt_length, \ + .next_message = aes ## impl_c ## _next_message_gcm, \ + /* 192-bit AES-GCM is included only so that testcrypt can run \ + * standard test vectors against it. OpenSSH doesn't define a \ + * protocol id for it. So we set its ssh2_id to NULL. */ \ + .ssh2_id = bits==192 ? NULL : "aes" #bits "-gcm@openssh.com", \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = SSH_CIPHER_SEPARATE_LENGTH, \ + .text_name = "AES-" #bits " GCM (" impl_display ")", \ + .required_mac = &ssh2_aesgcm_mac, \ + .extra = &aes ## bits ## impl_c ## _extra, \ } #define AES_ALL_VTABLES(impl_c, impl_display) \ @@ -84,7 +132,10 @@ static inline bool check_availability(const struct aes_extra *extra) AES_CBC_VTABLE(impl_c, impl_display, 256); \ AES_SDCTR_VTABLE(impl_c, impl_display, 128); \ AES_SDCTR_VTABLE(impl_c, impl_display, 192); \ - AES_SDCTR_VTABLE(impl_c, impl_display, 256) + AES_SDCTR_VTABLE(impl_c, impl_display, 256); \ + AES_GCM_VTABLE(impl_c, impl_display, 128); \ + AES_GCM_VTABLE(impl_c, impl_display, 192); \ + AES_GCM_VTABLE(impl_c, impl_display, 256) /* * Macros to repeat a piece of code particular numbers of times that diff --git a/code/crypto/aesgcm-clmul.c b/code/crypto/aesgcm-clmul.c new file mode 100644 index 00000000..cfb72e26 --- /dev/null +++ b/code/crypto/aesgcm-clmul.c @@ -0,0 +1,180 @@ +/* + * Implementation of the GCM polynomial hash using the x86 CLMUL + * extension, which provides 64x64->128 polynomial multiplication (or + * 'carry-less', which is what the CL stands for). + * + * Follows the reference implementation in aesgcm-ref-poly.c; see + * there for comments on the underlying technique. Here the comments + * just discuss the x86-specific details. + */ + +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID(out) __cpuid(out, 1) +#endif + +#include "ssh.h" +#include "aesgcm.h" + +typedef struct aesgcm_clmul { + AESGCM_COMMON_FIELDS; + __m128i var, acc, mask; + void *ptr_to_free; +} aesgcm_clmul; + +static bool aesgcm_clmul_available(void) +{ + /* + * Determine if CLMUL is available on this CPU. + */ + unsigned int CPUInfo[4]; + GET_CPU_ID(CPUInfo); + return (CPUInfo[2] & (1 << 1)); +} + +/* + * __m128i has to be aligned to 16 bytes, and x86 mallocs may not + * guarantee that, so we must over-allocate to make sure a large + * enough 16-byte region can be found, and ensure the aesgcm_clmul + * struct pointer is at least that well aligned. + */ +#define SPECIAL_ALLOC +static aesgcm_clmul *aesgcm_clmul_alloc(void) +{ + char *p = smalloc(sizeof(aesgcm_clmul) + 15); + uintptr_t ip = (uintptr_t)p; + ip = (ip + 15) & ~15; + aesgcm_clmul *ctx = (aesgcm_clmul *)ip; + memset(ctx, 0, sizeof(aesgcm_clmul)); + ctx->ptr_to_free = p; + return ctx; +} + +#define SPECIAL_FREE +static void aesgcm_clmul_free(aesgcm_clmul *ctx) +{ + void *ptf = ctx->ptr_to_free; + smemclr(ctx, sizeof(*ctx)); + sfree(ptf); +} + +/* Helper function to reverse the 16 bytes in a 128-bit vector */ +static inline __m128i mm_byteswap(__m128i vec) +{ + const __m128i reverse = _mm_set_epi64x( + 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); + return _mm_shuffle_epi8(vec, reverse); +} + +/* Helper function to swap the two 64-bit words in a 128-bit vector */ +static inline __m128i mm_wordswap(__m128i vec) +{ + return _mm_shuffle_epi32(vec, 0x4E); +} + +/* Load and store a 128-bit vector in big-endian fashion */ +static inline __m128i mm_load_be(const void *p) +{ + return mm_byteswap(_mm_loadu_si128(p)); +} +static inline void mm_store_be(void *p, __m128i vec) +{ + _mm_storeu_si128(p, mm_byteswap(vec)); +} + +/* + * Key setup is just like in aesgcm-ref-poly.c. There's no point using + * vector registers to accelerate this, because it happens rarely. + */ +static void aesgcm_clmul_setkey_impl(aesgcm_clmul *ctx, + const unsigned char *var) +{ + uint64_t hi = GET_64BIT_MSB_FIRST(var); + uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (hi >> 63); + hi = (hi << 1) ^ (lo >> 63); + lo = (lo << 1) ^ bit; + hi ^= 0xC200000000000000 & -bit; + + ctx->var = _mm_set_epi64x(hi, lo); +} + +static inline void aesgcm_clmul_setup(aesgcm_clmul *ctx, + const unsigned char *mask) +{ + ctx->mask = mm_load_be(mask); + ctx->acc = _mm_set_epi64x(0, 0); +} + +/* + * Folding a coefficient into the accumulator is done by essentially + * the algorithm in aesgcm-ref-poly.c. I don't speak these intrinsics + * all that well, so in the parts where I needed to XOR half of one + * vector into half of another, I did a lot of faffing about with + * masks like 0xFFFFFFFFFFFFFFFF0000000000000000. Very likely this can + * be streamlined by a better x86-speaker than me. Patches welcome. + */ +static inline void aesgcm_clmul_coeff(aesgcm_clmul *ctx, + const unsigned char *coeff) +{ + ctx->acc = _mm_xor_si128(ctx->acc, mm_load_be(coeff)); + + /* Compute ah^al and bh^bl by word-swapping each of a and b and + * XORing with the original. That does more work than necessary - + * you end up with each of the desired values repeated twice - + * but I don't know of a neater way. */ + __m128i aswap = mm_wordswap(ctx->acc); + __m128i vswap = mm_wordswap(ctx->var); + aswap = _mm_xor_si128(ctx->acc, aswap); + vswap = _mm_xor_si128(ctx->var, vswap); + + /* Do the three multiplications required by Karatsuba */ + __m128i md = _mm_clmulepi64_si128(aswap, vswap, 0x00); + __m128i lo = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x00); + __m128i hi = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x11); + /* Combine lo and hi into md */ + md = _mm_xor_si128(md, lo); + md = _mm_xor_si128(md, hi); + + /* Now we must XOR the high half of md into the low half of hi, + * and the low half of md into the high half of hi. Simplest thing + * is to swap the words of md (so that each one lines up with the + * register it's going to end up in), and then mask one off in + * each case. */ + md = mm_wordswap(md); + lo = _mm_xor_si128(lo, _mm_and_si128(md, _mm_set_epi64x(~0ULL, 0ULL))); + hi = _mm_xor_si128(hi, _mm_and_si128(md, _mm_set_epi64x(0ULL, ~0ULL))); + + /* The reduction stage is transformed similarly from the version + * in aesgcm-ref-poly.c. */ + __m128i r1 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), + lo, 0x00); + r1 = mm_wordswap(r1); + r1 = _mm_xor_si128(r1, lo); + hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(~0ULL, 0ULL))); + + __m128i r2 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), + r1, 0x10); + hi = _mm_xor_si128(hi, r2); + hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(0ULL, ~0ULL))); + + ctx->acc = hi; +} + +static inline void aesgcm_clmul_output(aesgcm_clmul *ctx, + unsigned char *output) +{ + mm_store_be(output, _mm_xor_si128(ctx->acc, ctx->mask)); + smemclr(&ctx->acc, 16); + smemclr(&ctx->mask, 16); +} + +#define AESGCM_FLAVOUR clmul +#define AESGCM_NAME "CLMUL accelerated" +#include "aesgcm-footer.h" diff --git a/code/crypto/aesgcm-common.c b/code/crypto/aesgcm-common.c new file mode 100644 index 00000000..1e20c87b --- /dev/null +++ b/code/crypto/aesgcm-common.c @@ -0,0 +1,8 @@ +#include "ssh.h" +#include "aesgcm.h" + +void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad) +{ + const struct aesgcm_extra *extra = mac->vt->extra; + extra->set_prefix_lengths(mac, skip, aad); +} diff --git a/code/crypto/aesgcm-footer.h b/code/crypto/aesgcm-footer.h new file mode 100644 index 00000000..981905da --- /dev/null +++ b/code/crypto/aesgcm-footer.h @@ -0,0 +1,368 @@ +/* + * Common footer included by every implementation of the AES-GCM MAC. + * + * The difficult part of AES-GCM, which is done differently depending + * on what hardware acceleration is available, is the actual + * evaluation of a polynomial over GF(2^128) whose coefficients are + * 128-bit chunks of data. But preparing those chunks in the first + * place (out of the ciphertext, associated data, and an + * administrative block containing the lengths of both) is done in the + * same way no matter what technique is used for the evaluation, so + * that's centralised into this file, along with as much of the other + * functionality as posible. + * + * This footer file is #included by each implementation, but each one + * will define its own struct type for the state, so that each alloc + * function will test sizeof() a different structure, and similarly + * for free when it zeroes out the state on cleanup. + * + * The functions in the source file may be defined as 'inline' so that + * the functions in here can inline them. The 'coeff' function in + * particular probably should be, because that's called once per + * 16-byte block, so eliminating function call overheads is especially + * useful there. + * + * This footer has the following expectations from the source file + * that #includes it: + * + * - define AESGCM_FLAVOUR to be a fragment of a C identifier that + * will be included in all the function names (both the ones + * defined in the implementation source file and those in here). + * For example purposes below I'll suppose that this is 'foo'. + * + * - define AESGCM_NAME to be a string literal that will be included + * in the display name of the implementation. + * + * - define a typedef 'aesgcm_foo' to be the state structure for the + * implementation, and inside that structure, expand the macro + * AESGCM_COMMON_FIELDS defined in aesgcm.h + * + * - define the following functions: + * + * // Determine whether this implementation is available at run time + * static bool aesgcm_foo_available(void); + * + * // Set up the 'key' of the polynomial part of the MAC, that is, + * // the value at which the polynomial will be evaluated. 'var' is + * // a 16-byte data block in the byte order it comes out of AES. + * static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx, + * const unsigned char *var); + * + * // Set up at the start of evaluating an individual polynomial. + * // 'mask' is the 16-byte data block that will be XORed into the + * // output value of the polynomial, also in AES byte order. This + * // function should store 'mask' in whatever form is most + * // convenient, and initialise an accumulator to zero. + * static void aesgcm_foo_setup(aesgcm_foo *ctx, + * const unsigned char *mask); + * + * // Fold in a coefficient of the polynomial, by means of XORing + * // it into the accumulator and then multiplying the accumulator + * // by the variable passed to setkey_impl() above. + * // + * // 'coeff' points to the 16-byte block of data that the + * // polynomial coefficient will be made out of. + * // + * // You probably want to mark this function 'inline'. + * static void aesgcm_foo_coeff(aesgcm_foo *ctx, + * const unsigned char *coeff); + * + * // Generate the output MAC, by XORing the accumulator's final + * // value with the mask passed to setup() above. + * // + * // 'output' points to a 16-byte region of memory to write the + * // result to. + * static void aesgcm_foo_output(aesgcm_foo *ctx, + * unsigned char *output); + * + * - if allocation of the state structure must be done in a + * non-standard way (e.g. x86 needs this to force greater alignment + * than standard malloc provides), then #define SPECIAL_ALLOC and + * define this additional function: + * + * // Allocate a state structure, zero out its contents, and return it. + * static aesgcm_foo *aesgcm_foo_alloc(void); + * + * - if freeing must also be done in an unusual way, #define + * SPECIAL_FREE and define this function: + * + * // Zero out the state structure to avoid information leaks if the + * // memory is reused, and then free it. + * static void aesgcm_foo_free(aesgcm_foo *ctx); + */ + +#ifndef AESGCM_FLAVOUR +#error AESGCM_FLAVOUR must be defined by any module including this footer +#endif +#ifndef AESGCM_NAME +#error AESGCM_NAME must be defined by any module including this footer +#endif + +#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR) +#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name)) + +#include "aes.h" // for aes_encrypt_ecb_block + +static const char *PREFIX(mac_text_name)(ssh2_mac *mac) +{ + return "AES-GCM (" AESGCM_NAME ")"; +} + +static void PREFIX(mac_next_message)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the mask value for a single MAC instance, by encrypting + * the all-zeroes word using the associated AES instance in its + * ordinary GCM fashion. This consumes the first block of + * keystream (with per-block counter equal to 1), leaving the + * second block of keystream ready to be used on the first block + * of plaintext. + */ + unsigned char buf[16]; + memset(buf, 0, 16); + ssh_cipher_encrypt(ctx->cipher, buf, 16); + PREFIX(setup)(ctx, buf); /* give it to the implementation to store */ + smemclr(buf, sizeof(buf)); +} + +static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the value of the polynomial variable, by encrypting the + * all-zeroes word using the associated AES instance in the + * special ECB mode. This is done via the special AES-specific API + * function encrypt_ecb_block, which doesn't touch the counter + * state at all. + */ + unsigned char var[16]; + memset(var, 0, 16); + aes_encrypt_ecb_block(ctx->cipher, var); + PREFIX(setkey_impl)(ctx, var); + smemclr(var, sizeof(var)); + + PREFIX(mac_next_message)(mac); /* set up mask */ +} + +static void PREFIX(mac_start)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0; +} + +/* + * Handle receiving data via the BinarySink API and turning it into a + * collection of 16-byte blocks to use as polynomial coefficients. + * + * This code is written in a fully general way, which is able to + * handle an arbitrary number of bytes at the start of the data to + * ignore completely (necessary for PuTTY integration), and an + * arbitrary number to treat as associated data, and the rest will be + * regarded as ciphertext. The stream can be interrupted at any byte + * position and resumed later; a partial block will be stored as + * necessary. + * + * At the time of writing this comment, in live use most of that + * generality isn't required: the full data is passed to this function + * in just one call. But there's no guarantee of that staying true in + * future, so we do the full deal here just in case, and the test + * vectors in cryptsuite.py will test it. (And they'll use + * set_prefix_lengths to set up different configurations from the SSH + * usage.) + */ +static void PREFIX(mac_BinarySink_write)( + BinarySink *bs, const void *blkv, size_t len) +{ + CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT); + const unsigned char *blk = (const unsigned char *)blkv; + + /* + * Skip the prefix sequence number used as implicit extra data in + * SSH MACs. This is not included in the associated data field for + * GCM, because the IV incrementation policy provides its own + * sequence numbering. + */ + if (ctx->skipgot < ctx->skiplen) { + size_t n = ctx->skiplen - ctx->skipgot; + if (n > len) + n = len; + blk += n; + len -= n; + ctx->skipgot += n; + + if (len == 0) + return; + } + + /* + * Read additional authenticated data and fold it in to the MAC. + */ + while (ctx->aadgot < ctx->aadlen) { + size_t n = ctx->aadlen - ctx->aadgot; + if (n > len) + n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of AAD. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->aadgot += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (ctx->aadgot == ctx->aadlen && ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (len == 0) + return; + } + + /* + * Read the main ciphertext and fold it in to the MAC. + */ + while (len > 0) { + size_t n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of ciphertext. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->ciphertextlen += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + } +} + +static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Consume any partial block of ciphertext remaining. + */ + if (ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + } + + /* + * Consume the final block giving the lengths of the AAD and ciphertext. + */ + unsigned char blk[16]; + memset(blk, 0, 16); + PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8); + PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8); + PREFIX(coeff)(ctx, blk); + + /* + * And call the implementation's output function. + */ + PREFIX(output)(ctx, output); + + smemclr(blk, sizeof(blk)); + smemclr(ctx->partblk, 16); +} + +static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher) +{ + const struct aesgcm_extra *extra = alg->extra; + if (!check_aesgcm_availability(extra)) + return NULL; + +#ifdef SPECIAL_ALLOC + CONTEXT *ctx = PREFIX(alloc)(); +#else + CONTEXT *ctx = snew(CONTEXT); + memset(ctx, 0, sizeof(CONTEXT)); +#endif + + ctx->mac.vt = alg; + ctx->cipher = cipher; + /* Default values for SSH-2, overridable by set_prefix_lengths for + * testcrypt purposes */ + ctx->skiplen = 4; + ctx->aadlen = 4; + BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write)); + BinarySink_DELEGATE_INIT(&ctx->mac, ctx); + return &ctx->mac; +} + +static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + ctx->skiplen = skip; + ctx->aadlen = aad; +} + +static void PREFIX(mac_free)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); +#ifdef SPECIAL_FREE + PREFIX(free)(ctx); +#else + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +#endif +} + +static struct aesgcm_extra_mutable PREFIX(extra_mut); + +static const struct aesgcm_extra PREFIX(extra) = { + .check_available = PREFIX(available), + .mut = &PREFIX(extra_mut), + .set_prefix_lengths = PREFIX(set_prefix_lengths), +}; + +const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = { + .new = PREFIX(mac_new), + .free = PREFIX(mac_free), + .setkey = PREFIX(mac_setkey), + .start = PREFIX(mac_start), + .genresult = PREFIX(mac_genresult), + .next_message = PREFIX(mac_next_message), + .text_name = PREFIX(mac_text_name), + .name = "", + .etm_name = "", /* Not selectable independently */ + .len = 16, + .keylen = 0, + .extra = &PREFIX(extra), +}; diff --git a/code/crypto/aesgcm-neon.c b/code/crypto/aesgcm-neon.c new file mode 100644 index 00000000..64bc8349 --- /dev/null +++ b/code/crypto/aesgcm-neon.c @@ -0,0 +1,164 @@ +/* + * Implementation of the GCM polynomial hash using Arm NEON vector + * intrinsics, in particular the multiplication operation for + * polynomials over GF(2). + * + * Follows the reference implementation in aesgcm-ref-poly.c; see + * there for comments on the underlying technique. Here the comments + * just discuss the NEON-specific details. + */ + +#include "ssh.h" +#include "aesgcm.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +typedef struct aesgcm_neon { + AESGCM_COMMON_FIELDS; + poly128_t var, acc, mask; +} aesgcm_neon; + +static bool aesgcm_neon_available(void) +{ + return platform_pmull_neon_available(); +} + +/* + * The NEON types involved are: + * + * 'poly128_t' is a type that lives in a 128-bit vector register and + * represents a 128-bit polynomial over GF(2) + * + * 'poly64x2_t' is a type that lives in a 128-bit vector register and + * represents a vector of two 64-bit polynomials. These appear as + * intermediate results in some of the helper functions below, but we + * never need to actually have a variable of that type. + * + * 'poly64x1_t' is a type that lives in a 128-bit vector register and + * represents a vector of one 64-bit polynomial. + * + * That is distinct from 'poly64_t', which is a type that lives in + * ordinary scalar registers and is a typedef for an integer type. + * + * Generally here we try to work in terms of poly128_t and 64-bit + * integer types, and let everything else be handled as internal + * details of these helper functions. + */ + +/* Make a poly128_t from two halves */ +static inline poly128_t create_p128(poly64_t hi, poly64_t lo) +{ + return vreinterpretq_p128_p64( + vcombine_p64(vcreate_p64(lo), vcreate_p64(hi))); +} + +/* Retrieve the high and low halves of a poly128_t */ +static inline poly64_t hi_half(poly128_t v) +{ + return vgetq_lane_p64(vreinterpretq_p64_p128(v), 1); +} +static inline poly64_t lo_half(poly128_t v) +{ + return vgetq_lane_p64(vreinterpretq_p64_p128(v), 0); +} + +/* 64x64 -> 128 bit polynomial multiplication, the largest we can do + * in one CPU operation */ +static inline poly128_t pmul(poly64_t v, poly64_t w) +{ + return vmull_p64(v, w); +} + +/* Load and store a poly128_t in the form of big-endian bytes. This + * involves separately swapping the halves of the register and + * reversing the bytes within each half. */ +static inline poly128_t load_p128_be(const void *p) +{ + poly128_t swapped = vreinterpretq_p128_u8(vrev64q_u8(vld1q_u8(p))); + return create_p128(lo_half(swapped), hi_half(swapped)); +} +static inline void store_p128_be(void *p, poly128_t v) +{ + poly128_t swapped = create_p128(lo_half(v), hi_half(v)); + vst1q_u8(p, vrev64q_u8(vreinterpretq_u8_p128(swapped))); +} + +#if !HAVE_NEON_VADDQ_P128 +static inline poly128_t vaddq_p128(poly128_t a, poly128_t b) +{ + return vreinterpretq_p128_u32(veorq_u32( + vreinterpretq_u32_p128(a), vreinterpretq_u32_p128(b))); +} +#endif + +/* + * Key setup is just like in aesgcm-ref-poly.c. There's no point using + * vector registers to accelerate this, because it happens rarely. + */ +static void aesgcm_neon_setkey_impl(aesgcm_neon *ctx, const unsigned char *var) +{ + uint64_t hi = GET_64BIT_MSB_FIRST(var); + uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (hi >> 63); + hi = (hi << 1) ^ (lo >> 63); + lo = (lo << 1) ^ bit; + hi ^= 0xC200000000000000 & -bit; + + ctx->var = create_p128(hi, lo); +} + +static inline void aesgcm_neon_setup(aesgcm_neon *ctx, + const unsigned char *mask) +{ + ctx->mask = load_p128_be(mask); + ctx->acc = create_p128(0, 0); +} + +/* + * Folding a coefficient into the accumulator is done by exactly the + * algorithm in aesgcm-ref-poly.c, translated line by line. + * + * It's possible that this could be improved by some clever manoeuvres + * that avoid having to break vectors in half and put them together + * again. Patches welcome if anyone has better ideas. + */ +static inline void aesgcm_neon_coeff(aesgcm_neon *ctx, + const unsigned char *coeff) +{ + ctx->acc = vaddq_p128(ctx->acc, load_p128_be(coeff)); + + poly64_t ah = hi_half(ctx->acc), al = lo_half(ctx->acc); + poly64_t bh = hi_half(ctx->var), bl = lo_half(ctx->var); + poly128_t md = pmul(ah ^ al, bh ^ bl); + poly128_t lo = pmul(al, bl); + poly128_t hi = pmul(ah, bh); + md = vaddq_p128(md, vaddq_p128(hi, lo)); + hi = create_p128(hi_half(hi), lo_half(hi) ^ hi_half(md)); + lo = create_p128(hi_half(lo) ^ lo_half(md), lo_half(lo)); + + poly128_t r1 = pmul((poly64_t)0xC200000000000000, lo_half(lo)); + hi = create_p128(hi_half(hi), lo_half(hi) ^ lo_half(lo) ^ hi_half(r1)); + lo = create_p128(hi_half(lo) ^ lo_half(r1), lo_half(lo)); + + poly128_t r2 = pmul((poly64_t)0xC200000000000000, hi_half(lo)); + hi = vaddq_p128(hi, r2); + hi = create_p128(hi_half(hi) ^ hi_half(lo), lo_half(hi)); + + ctx->acc = hi; +} + +static inline void aesgcm_neon_output(aesgcm_neon *ctx, unsigned char *output) +{ + store_p128_be(output, vaddq_p128(ctx->acc, ctx->mask)); + ctx->acc = create_p128(0, 0); + ctx->mask = create_p128(0, 0); +} + +#define AESGCM_FLAVOUR neon +#define AESGCM_NAME "NEON accelerated" +#include "aesgcm-footer.h" diff --git a/code/crypto/aesgcm-ref-poly.c b/code/crypto/aesgcm-ref-poly.c new file mode 100644 index 00000000..f6ca0fa5 --- /dev/null +++ b/code/crypto/aesgcm-ref-poly.c @@ -0,0 +1,364 @@ +/* + * Implementation of the GCM polynomial hash in pure software, but + * based on a primitive that performs 64x64->128 bit polynomial + * multiplication over GF(2). + * + * This implementation is technically correct (should even be + * side-channel safe as far as I can see), but it's hopelessly slow, + * so no live SSH connection should ever use it. Therefore, it's + * deliberately not included in the lists in aesgcm-select.c. For pure + * software GCM in live use, you want aesgcm-sw.c, and that's what the + * selection system will choose. + * + * However, this implementation _is_ made available to testcrypt, so + * all the GCM tests in cryptsuite.py are run over this as well as the + * other implementations. + * + * The reason why this code exists at all is to act as a reference for + * GCM implementations that use a CPU-specific polynomial multiply + * intrinsic or asm statement. This version will run on whatever + * platform you're trying to port to, and will generate all the same + * intermediate results you expect the CPU-specific one to go through. + * So you can insert parallel diagnostics in this version and in your + * new version, to see where the two diverge. + * + * Also, this version is a good place to put long comments explaining + * the entire strategy of this implementation and its rationale. That + * avoids those comments having to be duplicated in the multiple + * platform-specific implementations, which can focus on commenting + * the way the local platform's idioms match up to this version, and + * refer to this file for the explanation of the underlying technique. + */ + +#include "ssh.h" +#include "aesgcm.h" + +/* + * Store a 128-bit value in the most convenient form standard C will + * let us, namely two uint64_t giving its most and least significant + * halves. + */ +typedef struct { + uint64_t hi, lo; +} value128_t; + +typedef struct aesgcm_ref_poly { + AESGCM_COMMON_FIELDS; + + /* + * The state of our GCM implementation is represented entirely by + * three 128-bit values: + */ + + /* + * The value at which we're evaluating the polynomial. The GCM + * spec calls this 'H'. It's defined once at GCM key setup time, + * by encrypting the all-zeroes value with our block cipher. + */ + value128_t var; + + /* + * Accumulator containing the result of evaluating the polynomial + * so far. + */ + value128_t acc; + + /* + * The mask value that is XORed into the final value of 'acc' to + * produce the output MAC. This is different for every MAC + * generated, because its purpose is to ensure that no information + * gathered from a legal MAC can be used to help the forgery of + * another one, and that comparing two legal MACs gives you no + * useful information about the text they cover, because in each + * case, the masks are different and pseudorandom. + */ + value128_t mask; +} aesgcm_ref_poly; + +static bool aesgcm_ref_poly_available(void) +{ + return true; /* pure software implementation, always available */ +} + +/* + * Primitive function that takes two uint64_t values representing + * polynomials, multiplies them, and returns a value128_t struct + * containing the full product. + * + * Because the input polynomials have maximum degree 63, the output + * has max degree 63+63 = 127, not 128. As a result, the topmost bit + * of the output is always zero. + * + * The inside of this function is implemented in the simplest way, + * with no attention paid to performance. The important feature of + * this implementation is not what's _inside_ this function, but + * what's _outside_ it: aesgcm_ref_poly_coeff() tries to minimise the + * number of these operations. + */ +static value128_t pmul(uint64_t x, uint64_t y) +{ + value128_t r; + r.hi = r.lo = 0; + + uint64_t bit = 1 & y; + r.lo ^= x & -bit; + + for (unsigned i = 1; i < 64; i++) { + bit = 1 & (y >> i); + uint64_t z = x & -bit; + r.lo ^= z << i; + r.hi ^= z >> (64-i); + } + + return r; +} + +/* + * OK, I promised a long comment explaining what's going on in this + * implementation, and now it's time. + * + * The way AES-GCM _itself_ is defined by its own spec, its finite + * field consists of polynomials over GF(2), constrained to be 128 + * bits long by reducing them modulo P = x^128 + x^7 + x^2 + x + 1. + * Using the usual binary representation in which bit i is the + * coefficient of x^i, that's 0x100000000000000000000000000000087. + * + * That is, whenever you multiply two polynomials and find a term + * x^128, you can replace it with x^7+x^2+x+1. More generally, + * x^(128+n) can be replaced with x^(7+n)+x^(2+n)+x^(1+n)+x^n. In + * binary terms, a 1 bit at the 128th position or above is replaced by + * 0x87 exactly 128 bits further down. + * + * So you'd think that multiplying two 128-bit polynomials mod P would + * be a matter of generating their full 256-bit product in the form of + * four words HI:HU:LU:LO, and then reducing it mod P by a two-stage + * process of computing HI * 0x87 and XORing it into HU:LU, then + * computing HU * 0x87 and XORing it into LU:LO. + * + * But it's not! + * + * The reason why not is because when AES-GCM is applied to SSH, + * somehow the _bit_ order got reversed. A 16-byte block of data in + * memory is converted into a polynomial by regarding bit 7 of the + * first byte as the constant term, bit 0 of the first byte as the x^7 + * coefficient, ..., bit 0 of the last byte as the x^127 coefficient. + * So if we load that 16-byte block as a big-endian 128-bit integer, + * we end up with it representing a polynomial back to front, with the + * constant term at the top and the x^127 bit at the bottom. + * + * Well, that _shouldn't_ be a problem, right? The nice thing about + * polynomial multiplication is that it's essentially reversible. If + * you reverse the order of the coefficients of two polynomials, then + * the product of the reversed polys is exactly the reversal of the + * product of the original ones. So we bit-reverse our modulo + * polynomial to get 0x1c2000000000000000000000000000001, and we just + * pretend we're working mod that instead. + * + * And that is basically what we're about to do. But there's one + * complication, that arises from the semantics of the polynomial + * multiplication function we're using as our primitive operation. + * + * That function multiplies two polynomials of degree at most 63, to + * give one with degree at most 127. So it returns a 128-bit integer + * whose low bit is the constant term, and its very highest bit is 0, + * and its _next_ highest bit is the product of the high bits of the + * two inputs. + * + * That operation is _not_ symmetric in bit-reversal. If you give it + * the 64-bit-wise reversals of two polynomials P,Q, then its output + * is not the 128-bit-wise reversal of their product PQ, because that + * would require the constant term of PQ to appear in bit 127 of the + * output, and in fact it appears in bit 126. So in fact, what we get + * is offset by one bit from where we'd like it: it's the bit-reversal + * of PQx, not of PQ. + * + * There's more than one way we could fix this. One approach would be + * to work around this off-by-one error by taking the 128-bit output + * of pmul() and shifting it left by a bit. Then it _is_ the bitwise + * reversal of the 128-bit value we'd have wanted to get, and we could + * implement the exact algorithm described above, in fully + * bit-reversed form. + * + * But a 128-bit left shift is not a trivial operation in the vector + * architectures that this code is acting as a reference for. So we'd + * prefer to find a fix that doesn't need compensation during the + * actual per-block multiplication step. + * + * If we did the obvious thing anyway - compute the unshifted 128-bit + * product representing the bit-reversal of PQx, and reduce it mod + * 0x1c2000000000000000000000000000001 - then we'd get a result which + * is exactly what we want, except that it's got a factor of x in it + * that we need to get rid of. The obvious answer is to divide by x + * (which is legal and safe, since mod P, x is invertible). + * + * Dividing a 128-bit polynomial by x is easy in principle. Shift left + * (because we're still bit-reversed), and if that shifted a 1 bit off + * the top, XOR 0xc2000000000000000000000000000001 into the remaining + * 128 bits. + * + * But we're back to having that expensive left shift. What can we do + * about that? + * + * Happily, one of the two input values to our per-block multiply + * operation is fixed! It's given to us at key setup stage, and never + * changed until the next rekey. So if at key setup time we do this + * left shift business _once_, replacing the logical value Q with Q/x, + * then that exactly cancels out the unwanted factor of x that shows + * up in our multiply operation. And now it doesn't matter that it's + * expensive (in the sense of 'a few more instructions than you'd + * like'), because it only happens once per SSH key exchange, not once + * per 16 bytes of data transferred. + */ + +static void aesgcm_ref_poly_setkey_impl(aesgcm_ref_poly *ctx, + const unsigned char *var) +{ + /* + * Key setup function. We copy the provided 16-byte 'var' + * value into our polynomial. But, as discussed above, we also + * need to divide it by x. + */ + + ctx->var.hi = GET_64BIT_MSB_FIRST(var); + ctx->var.lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (ctx->var.hi >> 63); + ctx->var.hi = (ctx->var.hi << 1) ^ (ctx->var.lo >> 63); + ctx->var.lo = (ctx->var.lo << 1) ^ bit; + ctx->var.hi ^= 0xC200000000000000 & -bit; +} + +static inline void aesgcm_ref_poly_setup(aesgcm_ref_poly *ctx, + const unsigned char *mask) +{ + /* + * Set up to start evaluating a particular MAC. Copy in the mask + * value for this packet, and initialise acc to zero. + */ + + ctx->mask.hi = GET_64BIT_MSB_FIRST(mask); + ctx->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); + ctx->acc.hi = ctx->acc.lo = 0; +} + +static inline void aesgcm_ref_poly_coeff(aesgcm_ref_poly *ctx, + const unsigned char *coeff) +{ + /* + * One step of Horner's-rule polynomial evaluation (with each + * coefficient of the polynomial being an element of GF(2^128), + * itself composed of polynomials over GF(2) mod P). + * + * We take our accumulator value, add the incoming coefficient + * (which means XOR, by GF(2) rules), and multiply by x (that is, + * 'var'). + */ + + /* + * The addition first, which is easy. + */ + ctx->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); + ctx->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); + + /* + * First, create the 256-bit product of the two 128-bit + * polynomials over GF(2) stored in ctx->acc and ctx->var. + * + * The obvious way to do this is by four smaller multiplications + * of 64x64 -> 128 bits. But we can do better using a single + * iteration of the Karatsuba technique, which is actually more + * convenient in polynomials over GF(2) than it is in integers, + * because there aren't all those awkward carries complicating + * things. + * + * Letting B denote x^64, and imagining our two inputs are split + * up into 64-bit chunks ah,al,bh,bl, the product we want is + * + * (ah B + al) (bh B + bl) + * = (ah bh) B^2 + (al bh + ah bl) B + (al bl) + * + * which looks like four smaller multiplications of each of ah,al + * with each of bh,bl. But Karatsuba's trick is to first compute + * + * (ah + al) (bh + bl) + * = ah bh + al bh + ah bl + al bl + * + * and then subtract the terms (ah bh) and (al bl), which we had + * to compute anyway, to get the middle two terms (al bh + ah bl) + * which are our coefficient of B. + * + * This involves more bookkeeping instructions like XORs, but with + * any luck those are faster than the main multiplication. + */ + uint64_t ah = ctx->acc.hi, al = ctx->acc.lo; + uint64_t bh = ctx->var.hi, bl = ctx->var.lo; + /* Compute the outer two terms */ + value128_t lo = pmul(al, bl); + value128_t hi = pmul(ah, bh); + /* Compute the trick product (ah+al)(bh+bl) */ + value128_t md = pmul(ah ^ al, bh ^ bl); + /* Subtract off the outer two terms to get md = al bh + ah bl */ + md.hi ^= lo.hi ^ hi.hi; + md.lo ^= lo.lo ^ hi.lo; + /* And add that into the 256-bit value given by hi * x^128 + lo */ + lo.hi ^= md.lo; + hi.lo ^= md.hi; + + /* + * OK. Now hi and lo together make up the 256-bit full product. + * Now reduce it mod the reversal of the GCM modulus polynomial. + * As discussed above, that's 0x1c2000000000000000000000000000001. + * + * We want the _topmost_ 128 bits of this, because we're working + * in a bit-reversed world. So what we fundamentally want to do is + * to take our 256-bit product, and add to it the product of its + * low 128 bits with 0x1c2000000000000000000000000000001. Then the + * top 128 bits will be the output we want. + * + * Since there's no carrying in this arithmetic, it's enough to + * discard the 1 bit at the bottom of that, because it won't + * affect anything in the half we're keeping. So it's enough to + * add 0x1c2000000000000000000000000000000 * lo to (hi:lo). + * + * We can only work with 64 bits at a time, so the first thing we + * do is to break that up: + * + * - add 0x1c200000000000000 * lo.lo to (hi.lo : lo.hi) + * - add 0x1c200000000000000 * lo.hi to (hi.hi : hi.lo) + * + * But there's still a problem: 0x1c200000000000000 is just too + * _big_ to fit in 64 bits. So we have to break it up into the low + * 64 bits 0xc200000000000000, and its leading 1. So each of those + * steps of the form 'add 0x1c200000000000000 * x to y:z' becomes + * 'add 0xc200000000000000 * x to y:z' followed by 'add x to y', + * the latter step dealing with the leading 1. + */ + + /* First step, adding to the middle two words of our number. After + * this the lowest word (in lo.lo) is ignored. */ + value128_t r1 = pmul(0xC200000000000000, lo.lo); + hi.lo ^= r1.hi ^ lo.lo; + lo.hi ^= r1.lo; + + /* Second of those steps, adding to the top two words, and + * discarding lo.hi. */ + value128_t r2 = pmul(0xC200000000000000, lo.hi); + hi.hi ^= r2.hi ^ lo.hi; + hi.lo ^= r2.lo; + + /* Now 'hi' is precisely what we have left. */ + ctx->acc = hi; +} + +static inline void aesgcm_ref_poly_output(aesgcm_ref_poly *ctx, + unsigned char *output) +{ + PUT_64BIT_MSB_FIRST(output, ctx->acc.hi ^ ctx->mask.hi); + PUT_64BIT_MSB_FIRST(output + 8, ctx->acc.lo ^ ctx->mask.lo); + smemclr(&ctx->acc, 16); + smemclr(&ctx->mask, 16); +} + +#define AESGCM_FLAVOUR ref_poly +#define AESGCM_NAME "reference polynomial-based implementation" +#include "aesgcm-footer.h" diff --git a/code/crypto/aesgcm-select.c b/code/crypto/aesgcm-select.c new file mode 100644 index 00000000..eefe7148 --- /dev/null +++ b/code/crypto/aesgcm-select.c @@ -0,0 +1,38 @@ +#include "ssh.h" +#include "aesgcm.h" + +static ssh2_mac *aesgcm_mac_selector_new(const ssh2_macalg *alg, + ssh_cipher *cipher) +{ + static const ssh2_macalg *const real_algs[] = { +#if HAVE_CLMUL + &ssh2_aesgcm_mac_clmul, +#endif +#if HAVE_NEON_PMULL + &ssh2_aesgcm_mac_neon, +#endif + &ssh2_aesgcm_mac_sw, + NULL, + }; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh2_macalg *alg = real_algs[i]; + const struct aesgcm_extra *alg_extra = + (const struct aesgcm_extra *)alg->extra; + if (check_aesgcm_availability(alg_extra)) + return ssh2_mac_new(alg, cipher); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only GCM, which is + * always available. */ + unreachable("aesgcm_select ran off the end of its list"); +} + +const ssh2_macalg ssh2_aesgcm_mac = { + .new = aesgcm_mac_selector_new, + .name = "", + .etm_name = "", /* Not selectable independently */ + .len = 16, + .keylen = 0, +}; diff --git a/code/crypto/aesgcm-sw.c b/code/crypto/aesgcm-sw.c new file mode 100644 index 00000000..f322ae30 --- /dev/null +++ b/code/crypto/aesgcm-sw.c @@ -0,0 +1,145 @@ +/* + * Implementation of the GCM polynomial hash in pure software. + * + * I don't know of a faster way to do this in a side-channel safe + * manner than by precomputing a giant table and iterating over the + * whole thing. + * + * The original GCM reference suggests that you precompute the effects + * of multiplying a 128-bit value by the fixed key, in the form of a + * table indexed by some number of bits of the input value, so that + * you end up computing something of the form + * + * table1[x & 0xFF] ^ table2[(x>>8) & 0xFF] ^ ... ^ table15[(x>>120) & 0xFF] + * + * But that was obviously written before cache and timing leaks were + * known about. What's a time-safe approach? + * + * Well, the above technique isn't fixed to 8 bits of input per table. + * You could trade off the number of tables against the size of each + * table. At one extreme of this tradeoff, you have 128 tables each + * indexed by a single input bit - which is to say, you have 128 + * values, each 128 bits wide, and you XOR together the subset of + * those values corresponding to the input bits, which you can do by + * making a bitmask out of each input bit using standard constant- + * time-coding bit twiddling techniques. + * + * That's pretty unpleasant when GCM is supposed to be a fast + * algorithm, but I don't know of a better approach that meets current + * security standards! Suggestions welcome, if they can get through + * testsc. + */ + +#include "ssh.h" +#include "aesgcm.h" + +/* + * Store a 128-bit value in the most convenient form standard C will + * let us, namely two uint64_t giving its most and least significant + * halves. + */ +typedef struct { + uint64_t hi, lo; +} value128_t; + +typedef struct aesgcm_sw { + AESGCM_COMMON_FIELDS; + + /* Accumulator for the current evaluation, and mask that will be + * XORed in at the end. High */ + value128_t acc, mask; + + /* + * Table of values to XOR in for each bit, representing the effect + * of multiplying by the fixed key. The key itself doesn't need to + * be stored separately, because it's never used. (However, it is + * also the first entry in the table, so if you _do_ need it, + * there it is.) + * + * Table is indexed from the low bit of the input upwards. + */ + value128_t table[128]; +} aesgcm_sw; + +static bool aesgcm_sw_available(void) +{ + return true; /* pure software implementation, always available */ +} + +static void aesgcm_sw_setkey_impl(aesgcm_sw *gcm, const unsigned char *var) +{ + value128_t v; + v.hi = GET_64BIT_MSB_FIRST(var); + v.lo = GET_64BIT_MSB_FIRST(var + 8); + + /* + * Prepare the table. This has to be done in reverse order, so + * that the original value of the variable corresponds to + * table[127], because AES-GCM works in the bit-reversal of its + * logical specification so that's where the logical constant term + * lives. (See more detailed comment in aesgcm-ref-poly.c.) + */ + for (size_t i = 0; i < 128; i++) { + gcm->table[127 - i] = v; + + /* Multiply v by x, which means shifting right (bit reversal + * again) and then adding 0xE1 at the top if we shifted a 1 out. */ + uint64_t lobit = v.lo & 1; + v.lo = (v.lo >> 1) ^ (v.hi << 63); + v.hi = (v.hi >> 1) ^ (0xE100000000000000ULL & -lobit); + } +} + +static inline void aesgcm_sw_setup(aesgcm_sw *gcm, const unsigned char *mask) +{ + gcm->mask.hi = GET_64BIT_MSB_FIRST(mask); + gcm->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); + gcm->acc.hi = gcm->acc.lo = 0; +} + +static inline void aesgcm_sw_coeff(aesgcm_sw *gcm, const unsigned char *coeff) +{ + /* XOR in the new coefficient */ + gcm->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); + gcm->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); + + /* And now just loop over the bits of acc, making up a new value + * by XORing together the entries of 'table' corresponding to set + * bits. */ + + value128_t out; + out.lo = out.hi = 0; + + const value128_t *tableptr = gcm->table; + + for (size_t i = 0; i < 64; i++) { + uint64_t bit = 1 & gcm->acc.lo; + gcm->acc.lo >>= 1; + uint64_t mask = -bit; + out.hi ^= mask & tableptr->hi; + out.lo ^= mask & tableptr->lo; + tableptr++; + } + for (size_t i = 0; i < 64; i++) { + uint64_t bit = 1 & gcm->acc.hi; + gcm->acc.hi >>= 1; + uint64_t mask = -bit; + out.hi ^= mask & tableptr->hi; + out.lo ^= mask & tableptr->lo; + tableptr++; + } + + gcm->acc = out; +} + +static inline void aesgcm_sw_output(aesgcm_sw *gcm, unsigned char *output) +{ + PUT_64BIT_MSB_FIRST(output, gcm->acc.hi ^ gcm->mask.hi); + PUT_64BIT_MSB_FIRST(output + 8, gcm->acc.lo ^ gcm->mask.lo); + smemclr(&gcm->acc, 16); + smemclr(&gcm->mask, 16); +} + +#define AESGCM_FLAVOUR sw +#define AESGCM_NAME "unaccelerated" +#include "aesgcm-footer.h" diff --git a/code/crypto/aesgcm.h b/code/crypto/aesgcm.h new file mode 100644 index 00000000..48077004 --- /dev/null +++ b/code/crypto/aesgcm.h @@ -0,0 +1,44 @@ +/* + * Common parts of the state structure for AESGCM MAC implementations. + */ +#define AESGCM_COMMON_FIELDS \ + ssh_cipher *cipher; \ + unsigned char partblk[16]; \ + size_t skiplen, aadlen, ciphertextlen; \ + size_t skipgot, aadgot, partlen; \ + BinarySink_IMPLEMENTATION; \ + ssh2_mac mac + +/* + * The 'extra' structure is used to include information about how to + * check if a given implementation is available at run time, and + * whether we've already checked. + */ +struct aesgcm_extra_mutable; +struct aesgcm_extra { + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct aesgcm_extra_mutable *mut; + + /* + * Extra API function specific to this MAC type that allows + * testcrypt to set more general lengths for skiplen and aadlen. + */ + void (*set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad); +}; +struct aesgcm_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_aesgcm_availability(const struct aesgcm_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} diff --git a/code/crypto/arcfour.c b/code/crypto/arcfour.c index 53821473..87d59022 100644 --- a/code/crypto/arcfour.c +++ b/code/crypto/arcfour.c @@ -110,6 +110,7 @@ const ssh_cipheralg ssh_arcfour128_ssh2 = { .setkey = arcfour_ssh2_setkey, .encrypt = arcfour_ssh2_block, .decrypt = arcfour_ssh2_block, + .next_message = nullcipher_next_message, .ssh2_id = "arcfour128", .blksize = 1, .real_keybits = 128, @@ -125,6 +126,7 @@ const ssh_cipheralg ssh_arcfour256_ssh2 = { .setkey = arcfour_ssh2_setkey, .encrypt = arcfour_ssh2_block, .decrypt = arcfour_ssh2_block, + .next_message = nullcipher_next_message, .ssh2_id = "arcfour256", .blksize = 1, .real_keybits = 256, diff --git a/code/crypto/bcrypt.c b/code/crypto/bcrypt.c index a4eb384a..1ccd4756 100644 --- a/code/crypto/bcrypt.c +++ b/code/crypto/bcrypt.c @@ -11,8 +11,8 @@ #include "ssh.h" #include "blowfish.h" -BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes) +static BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes) { int i; BlowfishContext *ctx; @@ -32,9 +32,9 @@ BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, return ctx; } -void bcrypt_hash(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes, - unsigned char output[32]) +static void bcrypt_hash(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes, + unsigned char output[32]) { BlowfishContext *ctx; int i; @@ -49,10 +49,10 @@ void bcrypt_hash(const unsigned char *key, int keybytes, blowfish_free_context(ctx); } -void bcrypt_genblock(int counter, - const unsigned char hashed_passphrase[64], - const unsigned char *salt, int saltbytes, - unsigned char output[32]) +static void bcrypt_genblock(int counter, + const unsigned char hashed_passphrase[64], + const unsigned char *salt, int saltbytes, + unsigned char output[32]) { unsigned char hashed_salt[64]; diff --git a/code/crypto/blowfish.c b/code/crypto/blowfish.c index e8886898..a4e44652 100644 --- a/code/crypto/blowfish.c +++ b/code/crypto/blowfish.c @@ -234,7 +234,7 @@ static const uint32_t sbox3[] = { #define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t ) static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t *S0 = ctx->S0; uint32_t *S1 = ctx->S1; @@ -267,7 +267,7 @@ static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output, } static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t *S0 = ctx->S0; uint32_t *S1 = ctx->S1; @@ -300,7 +300,7 @@ static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output, } static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -327,7 +327,7 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, ctx->iv1 = iv1; } -void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) +void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext *ctx) { unsigned char *blk = (unsigned char *)vblk; uint32_t xL, xR, out[2]; @@ -346,7 +346,7 @@ void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) } static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -374,7 +374,7 @@ static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -402,7 +402,7 @@ static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -430,7 +430,7 @@ static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_sdctr(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t b[2], iv0, iv1, tmp; @@ -471,7 +471,7 @@ void blowfish_initkey(BlowfishContext *ctx) } } -void blowfish_expandkey(BlowfishContext * ctx, +void blowfish_expandkey(BlowfishContext *ctx, const void *vkey, short keybytes, const void *vsalt, short saltbytes) { @@ -654,6 +654,7 @@ const ssh_cipheralg ssh_blowfish_ssh1 = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh1_encrypt_blk, .decrypt = blowfish_ssh1_decrypt_blk, + .next_message = nullcipher_next_message, .blksize = 8, .real_keybits = 128, .padded_keybytes = SSH1_SESSION_KEY_LENGTH, @@ -668,6 +669,7 @@ const ssh_cipheralg ssh_blowfish_ssh2 = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh2_encrypt_blk, .decrypt = blowfish_ssh2_decrypt_blk, + .next_message = nullcipher_next_message, .ssh2_id = "blowfish-cbc", .blksize = 8, .real_keybits = 128, @@ -683,6 +685,7 @@ const ssh_cipheralg ssh_blowfish_ssh2_ctr = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh2_sdctr, .decrypt = blowfish_ssh2_sdctr, + .next_message = nullcipher_next_message, .ssh2_id = "blowfish-ctr", .blksize = 8, .real_keybits = 256, diff --git a/code/crypto/chacha20-poly1305.c b/code/crypto/chacha20-poly1305.c index dd25b997..4216a64d 100644 --- a/code/crypto/chacha20-poly1305.c +++ b/code/crypto/chacha20-poly1305.c @@ -869,6 +869,7 @@ struct ccp_context { BinarySink_IMPLEMENTATION; ssh_cipher ciph; ssh2_mac mac_if; + bool ciph_allocated, mac_allocated; }; static ssh2_mac *poly_ssh2_new( @@ -876,13 +877,27 @@ static ssh2_mac *poly_ssh2_new( { struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); ctx->mac_if.vt = alg; + ctx->mac_allocated = true; BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); return &ctx->mac_if; } +static void ccp_common_free(struct ccp_context *ctx) +{ + if (ctx->ciph_allocated || ctx->mac_allocated) + return; + + smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); + smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); + smemclr(&ctx->mac, sizeof(ctx->mac)); + sfree(ctx); +} + static void poly_ssh2_free(ssh2_mac *mac) { - /* Not allocated, just forwarded, no need to free */ + struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); + ctx->mac_allocated = false; + ccp_common_free(ctx); } static void poly_setkey(ssh2_mac *mac, ptrlen key) @@ -949,6 +964,7 @@ const ssh2_macalg ssh2_poly1305 = { .setkey = poly_setkey, .start = poly_start, .genresult = poly_genresult, + .next_message = nullmac_next_message, .text_name = poly_text_name, .name = "", .etm_name = "", /* Not selectable individually, just part of @@ -963,16 +979,16 @@ static ssh_cipher *ccp_new(const ssh_cipheralg *alg) BinarySink_INIT(ctx, poly_BinarySink_write); poly1305_init(&ctx->mac); ctx->ciph.vt = alg; + ctx->ciph_allocated = true; + ctx->mac_allocated = false; return &ctx->ciph; } static void ccp_free(ssh_cipher *cipher) { struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); - smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); - smemclr(&ctx->mac, sizeof(ctx->mac)); - sfree(ctx); + ctx->ciph_allocated = false; + ccp_common_free(ctx); } static void ccp_iv(ssh_cipher *cipher, const void *iv) @@ -1046,6 +1062,7 @@ const ssh_cipheralg ssh2_chacha20_poly1305 = { .decrypt = ccp_decrypt, .encrypt_length = ccp_encrypt_length, .decrypt_length = ccp_decrypt_length, + .next_message = nullcipher_next_message, .ssh2_id = "chacha20-poly1305@openssh.com", .blksize = 1, .real_keybits = 512, diff --git a/code/crypto/des.c b/code/crypto/des.c index e8a26f0a..1cbec8fa 100644 --- a/code/crypto/des.c +++ b/code/crypto/des.c @@ -296,7 +296,7 @@ static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) s73 ^= c73 & t->t73; c73 += 0x00080008; } debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 - " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); + " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); /* Final selection within each pair */ s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); @@ -689,6 +689,7 @@ const ssh_cipheralg ssh_des = { .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "des-cbc", .blksize = 8, .real_keybits = 56, @@ -705,6 +706,7 @@ const ssh_cipheralg ssh_des_sshcom_ssh2 = { .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "des-cbc@ssh.com", .blksize = 8, .real_keybits = 56, @@ -808,6 +810,7 @@ const ssh_cipheralg ssh_3des_ssh2 = { .setkey = des3_cbc1_setkey, .encrypt = des3_cbc1_cbc_encrypt, .decrypt = des3_cbc1_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "3des-cbc", .blksize = 8, .real_keybits = 168, @@ -905,6 +908,7 @@ const ssh_cipheralg ssh_3des_ssh2_ctr = { .setkey = des3_sdctr_setkey, .encrypt = des3_sdctr_encrypt_decrypt, .decrypt = des3_sdctr_encrypt_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "3des-ctr", .blksize = 8, .real_keybits = 168, @@ -1040,6 +1044,7 @@ const ssh_cipheralg ssh_3des_ssh1 = { .setkey = des3_cbc3_setkey, .encrypt = des3_cbc3_cbc_encrypt, .decrypt = des3_cbc3_cbc_decrypt, + .next_message = nullcipher_next_message, .blksize = 8, .real_keybits = 168, .padded_keybytes = 24, diff --git a/code/crypto/diffie-hellman.c b/code/crypto/diffie-hellman.c index 914167bb..4da2d471 100644 --- a/code/crypto/diffie-hellman.c +++ b/code/crypto/diffie-hellman.c @@ -35,13 +35,52 @@ spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )' ctx->g = mp_from_integer(2); } +static void dh_group15_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 4: +spigot -B16 '2^3072 - 2^3008 - 1 + 2^64 * ( floor(2^2942 pi) + 1690314 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group16_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 5: +spigot -B16 '2^4096 - 2^4032 - 1 + 2^64 * ( floor(2^3966 pi) + 240904 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group17_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 6: +spigot -B16 '2^6144 - 2^6080 - 1 + 2^64 * ( floor(2^6014 pi) + 929484 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group18_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 7: +spigot -B16 '2^8192 - 2^8128 - 1 + 2^64 * ( floor(2^8062 pi) + 4743158 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + static const struct dh_extra extra_group1 = { false, dh_group1_construct, }; const ssh_kex ssh_diffiehellman_group1_sha1 = { - "diffie-hellman-group1-sha1", "group1", - KEXTYPE_DH, &ssh_sha1, &extra_group1, + .name = "diffie-hellman-group1-sha1", + .groupname = "group1", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_group1, }; static const ssh_kex *const group1_list[] = { @@ -50,18 +89,104 @@ static const ssh_kex *const group1_list[] = { const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list }; +static const struct dh_extra extra_group18 = { + false, dh_group18_construct, +}; + +const ssh_kex ssh_diffiehellman_group18_sha512 = { + .name = "diffie-hellman-group18-sha512", + .groupname = "group18", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group18, +}; + +static const ssh_kex *const group18_list[] = { + &ssh_diffiehellman_group18_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group18 = { + lenof(group18_list), group18_list +}; + +static const struct dh_extra extra_group17 = { + false, dh_group17_construct, +}; + +const ssh_kex ssh_diffiehellman_group17_sha512 = { + .name = "diffie-hellman-group17-sha512", + .groupname = "group17", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group17, +}; + +static const ssh_kex *const group17_list[] = { + &ssh_diffiehellman_group17_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group17 = { + lenof(group17_list), group17_list +}; + +static const struct dh_extra extra_group16 = { + false, dh_group16_construct, +}; + +const ssh_kex ssh_diffiehellman_group16_sha512 = { + .name = "diffie-hellman-group16-sha512", + .groupname = "group16", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group16, +}; + +static const ssh_kex *const group16_list[] = { + &ssh_diffiehellman_group16_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group16 = { + lenof(group16_list), group16_list +}; + +static const struct dh_extra extra_group15 = { + false, dh_group15_construct, +}; + +const ssh_kex ssh_diffiehellman_group15_sha512 = { + .name = "diffie-hellman-group15-sha512", + .groupname = "group15", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group15, +}; + +static const ssh_kex *const group15_list[] = { + &ssh_diffiehellman_group15_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group15 = { + lenof(group15_list), group15_list +}; + static const struct dh_extra extra_group14 = { false, dh_group14_construct, }; const ssh_kex ssh_diffiehellman_group14_sha256 = { - "diffie-hellman-group14-sha256", "group14", - KEXTYPE_DH, &ssh_sha256, &extra_group14, + .name = "diffie-hellman-group14-sha256", + .groupname = "group14", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha256, + .extra = &extra_group14, }; const ssh_kex ssh_diffiehellman_group14_sha1 = { - "diffie-hellman-group14-sha1", "group14", - KEXTYPE_DH, &ssh_sha1, &extra_group14, + .name = "diffie-hellman-group14-sha1", + .groupname = "group14", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_group14, }; static const ssh_kex *const group14_list[] = { @@ -76,13 +201,19 @@ const ssh_kexes ssh_diffiehellman_group14 = { static const struct dh_extra extra_gex = { true }; static const ssh_kex ssh_diffiehellman_gex_sha256 = { - "diffie-hellman-group-exchange-sha256", NULL, - KEXTYPE_DH, &ssh_sha256, &extra_gex, + .name = "diffie-hellman-group-exchange-sha256", + .groupname = NULL, + .main_type = KEXTYPE_DH, + .hash = &ssh_sha256, + .extra = &extra_gex, }; static const ssh_kex ssh_diffiehellman_gex_sha1 = { - "diffie-hellman-group-exchange-sha1", NULL, - KEXTYPE_DH, &ssh_sha1, &extra_gex, + .name = "diffie-hellman-group-exchange-sha1", + .groupname = NULL, + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_gex, }; static const ssh_kex *const gex_list[] = { @@ -92,33 +223,80 @@ static const ssh_kex *const gex_list[] = { const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; -/* - * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 - * as the mechanism. - * - * This suffix is the base64-encoded MD5 hash of the byte sequence - * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER - * encoding of the object ID 1.2.840.113554.1.2.2 which designates - * Kerberos v5. - * - * (The same encoded OID, minus the two-byte DER header, is defined in - * ssh/pgssapi.c as GSS_MECH_KRB5.) - */ -#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" - static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { - "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, - KEXTYPE_GSS, &ssh_sha1, &extra_gex, + .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH, + .groupname = NULL, + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_gex, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group18_sha512 = { + .name = "gss-group18-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group18", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group18, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group17_sha512 = { + .name = "gss-group17-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group17", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group17, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group16_sha512 = { + .name = "gss-group16-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group16", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group16, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group15_sha512 = { + .name = "gss-group15-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group15", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group15, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group14_sha256 = { + .name = "gss-group14-sha256-" GSS_KRB5_OID_HASH, + .groupname = "group14", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha256, + .extra = &extra_group14, +}; + +static const ssh_kex *const gssk5_sha2_kex_list[] = { + &ssh_gssk5_diffiehellman_group16_sha512, + &ssh_gssk5_diffiehellman_group17_sha512, + &ssh_gssk5_diffiehellman_group18_sha512, + &ssh_gssk5_diffiehellman_group15_sha512, + &ssh_gssk5_diffiehellman_group14_sha256, +}; + +const ssh_kexes ssh_gssk5_sha2_kex = { + lenof(gssk5_sha2_kex_list), gssk5_sha2_kex_list }; static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { - "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", - KEXTYPE_GSS, &ssh_sha1, &extra_group14, + .name = "gss-group14-sha1-" GSS_KRB5_OID_HASH, + .groupname = "group14", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_group14, }; static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { - "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", - KEXTYPE_GSS, &ssh_sha1, &extra_group1, + .name = "gss-group1-sha1-" GSS_KRB5_OID_HASH, + .groupname = "group1", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_group1, }; static const ssh_kex *const gssk5_sha1_kex_list[] = { diff --git a/code/crypto/dsa.c b/code/crypto/dsa.c index 43b51c8c..71fcd94a 100644 --- a/code/crypto/dsa.c +++ b/code/crypto/dsa.c @@ -317,6 +317,12 @@ static void dsa_openssh_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, dsa->x); } +static bool dsa_has_private(ssh_key *key) +{ + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); + return dsa->x != NULL; +} + static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { ssh_key *sshk; @@ -335,8 +341,8 @@ static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) } mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, - mp_int *private_key, - unsigned char *digest, int digest_len) + mp_int *private_key, + unsigned char *digest, int digest_len) { /* * The basic DSA signing algorithm is: @@ -484,6 +490,8 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) mp_free(s); } +static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); } + const ssh_keyalg ssh_dsa = { .new_pub = dsa_new_pub, .new_priv = dsa_new_priv, @@ -495,9 +503,15 @@ const ssh_keyalg ssh_dsa = { .public_blob = dsa_public_blob, .private_blob = dsa_private_blob, .openssh_blob = dsa_openssh_blob, + .has_private = dsa_has_private, .cache_str = dsa_cache_str, .components = dsa_components, + .base_key = nullkey_base_key, .pubkey_bits = dsa_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = dsa_alg_desc, + .variable_size = nullkey_variable_size_yes, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/code/crypto/ecc-ssh.c b/code/crypto/ecc-ssh.c index 5063a745..35b96302 100644 --- a/code/crypto/ecc-ssh.c +++ b/code/crypto/ecc-ssh.c @@ -6,7 +6,7 @@ * References: * * Elliptic curves in SSH are specified in RFC 5656: - * http://tools.ietf.org/html/rfc5656 + * https://www.rfc-editor.org/rfc/rfc5656 * * That specification delegates details of public key formatting and a * lot of underlying mechanism to SEC 1: @@ -325,6 +325,9 @@ struct ecsign_extra { const unsigned char *oid; int oidlen; + /* Human-readable algorithm description */ + const char *alg_desc; + /* Some EdDSA instances prefix a string to all hash preimages, to * disambiguate which signature variant they're being used with */ ptrlen hash_prefix; @@ -787,6 +790,12 @@ static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, ek->privateKey); } +static bool ecdsa_has_private(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + return ek->privateKey != NULL; +} + static void eddsa_private_blob(ssh_key *key, BinarySink *bs) { struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); @@ -796,6 +805,12 @@ static void eddsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); } +static bool eddsa_has_private(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + return ek->privateKey != NULL; +} + static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) { ssh_key *sshk = ecdsa_new_pub(alg, pub); @@ -1239,9 +1254,16 @@ static void eddsa_sign(ssh_key *key, ptrlen data, mp_free(s); } +static char *ec_alg_desc(const ssh_keyalg *self) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)self->extra; + return dupstr(extra->alg_desc); +} + static const struct ecsign_extra sign_extra_ed25519 = { ec_ed25519, &ssh_sha512, - NULL, 0, PTRLEN_DECL_LITERAL(""), + NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""), }; const ssh_keyalg ssh_ecdsa_ed25519 = { .new_pub = eddsa_new_pub, @@ -1254,9 +1276,15 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ssh-ed25519", .cache_id = "ssh-ed25519", .extra = &sign_extra_ed25519, @@ -1264,7 +1292,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { static const struct ecsign_extra sign_extra_ed448 = { ec_ed448, &ssh_shake256_114bytes, - NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"), + NULL, 0, "Ed448", PTRLEN_DECL_LITERAL("SigEd448\0\0"), }; const ssh_keyalg ssh_ecdsa_ed448 = { .new_pub = eddsa_new_pub, @@ -1277,9 +1305,15 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ssh-ed448", .cache_id = "ssh-ed448", .extra = &sign_extra_ed448, @@ -1291,7 +1325,7 @@ static const unsigned char nistp256_oid[] = { }; static const struct ecsign_extra sign_extra_nistp256 = { ec_p256, &ssh_sha256, - nistp256_oid, lenof(nistp256_oid), + nistp256_oid, lenof(nistp256_oid), "NIST p256", }; const ssh_keyalg ssh_ecdsa_nistp256 = { .new_pub = ecdsa_new_pub, @@ -1304,9 +1338,15 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp256", .cache_id = "ecdsa-sha2-nistp256", .extra = &sign_extra_nistp256, @@ -1318,7 +1358,7 @@ static const unsigned char nistp384_oid[] = { }; static const struct ecsign_extra sign_extra_nistp384 = { ec_p384, &ssh_sha384, - nistp384_oid, lenof(nistp384_oid), + nistp384_oid, lenof(nistp384_oid), "NIST p384", }; const ssh_keyalg ssh_ecdsa_nistp384 = { .new_pub = ecdsa_new_pub, @@ -1331,9 +1371,15 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp384", .cache_id = "ecdsa-sha2-nistp384", .extra = &sign_extra_nistp384, @@ -1345,7 +1391,7 @@ static const unsigned char nistp521_oid[] = { }; static const struct ecsign_extra sign_extra_nistp521 = { ec_p521, &ssh_sha512, - nistp521_oid, lenof(nistp521_oid), + nistp521_oid, lenof(nistp521_oid), "NIST p521", }; const ssh_keyalg ssh_ecdsa_nistp521 = { .new_pub = ecdsa_new_pub, @@ -1358,9 +1404,15 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp521", .cache_id = "ecdsa-sha2-nistp521", .extra = &sign_extra_nistp521, @@ -1436,9 +1488,15 @@ const ssh_keyalg ssh_ecdsa_nistp256_sk = { .public_blob = ecdsa_public_blob_sk, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "sk-ecdsa-sha2-nistp256@openssh.com", .cache_id = "sk-ecdsa-sha2-nistp256@openssh.com", .extra = &sign_extra_nistp256, @@ -1455,9 +1513,15 @@ const ssh_keyalg ssh_ecdsa_nistp384_sk = { .public_blob = ecdsa_public_blob_sk, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "sk-ecdsa-sha2-nistp384@openssh.com", .cache_id = "sk-ecdsa-sha2-nistp384@openssh.com", .extra = &sign_extra_nistp384, @@ -1474,9 +1538,15 @@ const ssh_keyalg ssh_ecdsa_nistp521_sk = { .public_blob = ecdsa_public_blob_sk, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "sk-ecdsa-sha2-nistp521@openssh.com", .cache_id = "sk-ecdsa-sha2-nistp521@openssh.com", .extra = &sign_extra_nistp521, @@ -1543,9 +1613,15 @@ const ssh_keyalg ssh_ecdsa_ed25519_sk = { .public_blob = eddsa_public_blob_sk, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "sk-ssh-ed25519@openssh.com", .cache_id = "sk-ssh-ed25519@openssh.com", .extra = &sign_extra_ed25519, @@ -1553,121 +1629,131 @@ const ssh_keyalg ssh_ecdsa_ed25519_sk = { #endif // PUTTY_CAC /* ---------------------------------------------------------------------- - * Exposed ECDH interface + * Exposed ECDH interfaces */ struct eckex_extra { struct ec_curve *(*curve)(void); - void (*setup)(ecdh_key *dh); - void (*cleanup)(ecdh_key *dh); - void (*getpublic)(ecdh_key *dh, BinarySink *bs); - mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey); }; -struct ecdh_key { +typedef struct ecdh_key_w { const struct eckex_extra *extra; const struct ec_curve *curve; mp_int *private; - union { - WeierstrassPoint *w_public; - MontgomeryPoint *m_public; - }; -}; + WeierstrassPoint *w_public; + + ecdh_key ek; +} ecdh_key_w; -const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex) +typedef struct ecdh_key_m { + const struct eckex_extra *extra; + const struct ec_curve *curve; + mp_int *private; + MontgomeryPoint *m_public; + + ecdh_key ek; +} ecdh_key_m; + +static ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) { const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - struct ec_curve *curve = extra->curve(); - return curve->textname; -} + const struct ec_curve *curve = extra->curve(); + + ecdh_key_w *dhw = snew(ecdh_key_w); + dhw->ek.vt = kex->ecdh_vt; + dhw->extra = extra; + dhw->curve = curve; -static void ssh_ecdhkex_w_setup(ecdh_key *dh) -{ mp_int *one = mp_from_integer(1); - dh->private = mp_random_in_range(one, dh->curve->w.G_order); + dhw->private = mp_random_in_range(one, dhw->curve->w.G_order); mp_free(one); - dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private); + dhw->w_public = ecc_weierstrass_multiply(dhw->curve->w.G, dhw->private); + + return &dhw->ek; } -static void ssh_ecdhkex_m_setup(ecdh_key *dh) +static ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server) { + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + + ecdh_key_m *dhm = snew(ecdh_key_m); + dhm->ek.vt = kex->ecdh_vt; + dhm->extra = extra; + dhm->curve = curve; + strbuf *bytes = strbuf_new_nm(); - random_read(strbuf_append(bytes, dh->curve->fieldBytes), - dh->curve->fieldBytes); + random_read(strbuf_append(bytes, dhm->curve->fieldBytes), + dhm->curve->fieldBytes); - dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); + dhm->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); /* Ensure the private key has the highest valid bit set, and no * bits _above_ the highest valid one */ - mp_reduce_mod_2to(dh->private, dh->curve->fieldBits); - mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1); + mp_reduce_mod_2to(dhm->private, dhm->curve->fieldBits); + mp_set_bit(dhm->private, dhm->curve->fieldBits - 1, 1); /* Clear a curve-specific number of low bits */ - for (unsigned bit = 0; bit < dh->curve->m.log2_cofactor; bit++) - mp_set_bit(dh->private, bit, 0); + for (unsigned bit = 0; bit < dhm->curve->m.log2_cofactor; bit++) + mp_set_bit(dhm->private, bit, 0); strbuf_free(bytes); - dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private); -} - -ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); + dhm->m_public = ecc_montgomery_multiply(dhm->curve->m.G, dhm->private); - ecdh_key *dh = snew(ecdh_key); - dh->extra = extra; - dh->curve = curve; - dh->extra->setup(dh); - return dh; + return &dhm->ek; } static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) { - put_wpoint(bs, dh->w_public, dh->curve, true); + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); + put_wpoint(bs, dhw->w_public, dhw->curve, true); } static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) { + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); mp_int *x; - ecc_montgomery_get_affine(dh->m_public, &x); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + ecc_montgomery_get_affine(dhm->m_public, &x); + for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) put_byte(bs, mp_get_byte(x, i)); mp_free(x); } -void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs) +static bool ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) { - dh->extra->getpublic(dh, bs); -} + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); -static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey) -{ - WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve); + WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dhw->curve); if (!remote_p) - return NULL; + return false; if (ecc_weierstrass_is_identity(remote_p)) { /* Not a sensible Diffie-Hellman input value */ ecc_weierstrass_point_free(remote_p); - return NULL; + return false; } - WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private); + WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private); mp_int *x; ecc_weierstrass_get_affine(p, &x, NULL); + put_mp_ssh2(bs, x); + mp_free(x); ecc_weierstrass_point_free(remote_p); ecc_weierstrass_point_free(p); - return x; + return true; } -static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) +static bool ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) { + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); + mp_int *remote_x = mp_from_bytes_le(remoteKey); /* Per RFC 7748 section 5, discard any set bits of the other @@ -1675,18 +1761,18 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) * to represent all valid values. However, an overlarge value that * still fits into the remaining number of bits is accepted, and * will be reduced mod p. */ - mp_reduce_mod_2to(remote_x, dh->curve->fieldBits); + mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits); MontgomeryPoint *remote_p = ecc_montgomery_point_new( - dh->curve->m.mc, remote_x); + dhm->curve->m.mc, remote_x); mp_free(remote_x); - MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private); + MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dhm->private); if (ecc_montgomery_is_identity(p)) { ecc_montgomery_point_free(remote_p); ecc_montgomery_point_free(p); - return NULL; + return false; } mp_int *x; @@ -1710,100 +1796,138 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) * with the _low_ byte zero, i.e. a multiple of 256. */ strbuf *sb = strbuf_new(); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) put_byte(sb, mp_get_byte(x, i)); mp_free(x); x = mp_from_bytes_be(ptrlen_from_strbuf(sb)); strbuf_free(sb); + put_mp_ssh2(bs, x); + mp_free(x); - return x; + return true; } -mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey) +static void ssh_ecdhkex_w_free(ecdh_key *dh) { - return dh->extra->getkey(dh, remoteKey); + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); + mp_free(dhw->private); + ecc_weierstrass_point_free(dhw->w_public); + sfree(dhw); } -static void ssh_ecdhkex_w_cleanup(ecdh_key *dh) +static void ssh_ecdhkex_m_free(ecdh_key *dh) { - ecc_weierstrass_point_free(dh->w_public); + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); + mp_free(dhm->private); + ecc_montgomery_point_free(dhm->m_public); + sfree(dhm); } -static void ssh_ecdhkex_m_cleanup(ecdh_key *dh) +static char *ssh_ecdhkex_description(const ssh_kex *kex) { - ecc_montgomery_point_free(dh->m_public); + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + return dupprintf("ECDH key exchange with curve %s", curve->textname); } -void ssh_ecdhkex_freekey(ecdh_key *dh) -{ - mp_free(dh->private); - dh->extra->cleanup(dh); - sfree(dh); -} +static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 }; -static const struct eckex_extra kex_extra_curve25519 = { - ec_curve25519, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, +static const ecdh_keyalg ssh_ecdhkex_m_alg = { + .new = ssh_ecdhkex_m_new, + .free = ssh_ecdhkex_m_free, + .getpublic = ssh_ecdhkex_m_getpublic, + .getkey = ssh_ecdhkex_m_getkey, + .description = ssh_ecdhkex_description, }; const ssh_kex ssh_ec_kex_curve25519 = { - "curve25519-sha256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, + .name = "curve25519-sha256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve25519, }; /* Pre-RFC alias */ -const ssh_kex ssh_ec_kex_curve25519_libssh = { - "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, +static const ssh_kex ssh_ec_kex_curve25519_libssh = { + .name = "curve25519-sha256@libssh.org", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve25519, }; - -static const struct eckex_extra kex_extra_curve448 = { - ec_curve448, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_curve25519_gss = { + .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve25519, }; + +static const struct eckex_extra kex_extra_curve448 = { ec_curve448 }; const ssh_kex ssh_ec_kex_curve448 = { - "curve448-sha512", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_curve448, + .name = "curve448-sha512", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve448, }; -static const struct eckex_extra kex_extra_nistp256 = { - ec_p256, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, +static const ecdh_keyalg ssh_ecdhkex_w_alg = { + .new = ssh_ecdhkex_w_new, + .free = ssh_ecdhkex_w_free, + .getpublic = ssh_ecdhkex_w_getpublic, + .getkey = ssh_ecdhkex_w_getkey, + .description = ssh_ecdhkex_description, }; +static const struct eckex_extra kex_extra_nistp256 = { ec_p256 }; const ssh_kex ssh_ec_kex_nistp256 = { - "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_nistp256, + .name = "ecdh-sha2-nistp256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp256, }; - -static const struct eckex_extra kex_extra_nistp384 = { - ec_p384, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp256_gss = { + .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp256, }; + +static const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; const ssh_kex ssh_ec_kex_nistp384 = { - "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, - &ssh_sha384, &kex_extra_nistp384, + .name = "ecdh-sha2-nistp384", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha384, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp384, }; - -static const struct eckex_extra kex_extra_nistp521 = { - ec_p521, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp384_gss = { + .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha384, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp384, }; + +static const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; const ssh_kex ssh_ec_kex_nistp521 = { - "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_nistp521, + .name = "ecdh-sha2-nistp521", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp521, +}; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp521_gss = { + .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp521, }; static const ssh_kex *const ec_kex_list[] = { @@ -1817,13 +1941,24 @@ static const ssh_kex *const ec_kex_list[] = { const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; +static const ssh_kex *const ec_gss_kex_list[] = { + &ssh_ec_kex_curve25519_gss, + &ssh_ec_kex_nistp521_gss, + &ssh_ec_kex_nistp384_gss, + &ssh_ec_kex_nistp256_gss, +}; + +const ssh_kexes ssh_gssk5_ecdh_kex = { + lenof(ec_gss_kex_list), ec_gss_kex_list +}; + /* ---------------------------------------------------------------------- * Helper functions for finding key algorithms and returning auxiliary * data. */ const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve) + const struct ec_curve **curve) { static const ssh_keyalg *algs_with_oid[] = { &ssh_ecdsa_nistp256, diff --git a/code/crypto/hmac.c b/code/crypto/hmac.c index f04d74b5..adeccd29 100644 --- a/code/crypto/hmac.c +++ b/code/crypto/hmac.c @@ -167,6 +167,7 @@ const ssh2_macalg ssh_hmac_sha256 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha2-256", .etm_name = "hmac-sha2-256-etm@openssh.com", @@ -182,6 +183,7 @@ const ssh2_macalg ssh_hmac_md5 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-md5", .etm_name = "hmac-md5-etm@openssh.com", @@ -198,6 +200,7 @@ const ssh2_macalg ssh_hmac_sha1 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1", .etm_name = "hmac-sha1-etm@openssh.com", @@ -214,6 +217,7 @@ const ssh2_macalg ssh_hmac_sha1_96 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1-96", .etm_name = "hmac-sha1-96-etm@openssh.com", @@ -232,6 +236,7 @@ const ssh2_macalg ssh_hmac_sha1_buggy = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1", .len = 20, @@ -249,6 +254,7 @@ const ssh2_macalg ssh_hmac_sha1_96_buggy = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1-96", .len = 12, diff --git a/code/crypto/mpint.c b/code/crypto/mpint.c index 544a18b5..437c7e8c 100644 --- a/code/crypto/mpint.c +++ b/code/crypto/mpint.c @@ -82,6 +82,14 @@ mp_int *mp_new(size_t maxbits) return mp_make_sized(words); } +mp_int *mp_resize(mp_int *mp, size_t newmaxbits) +{ + mp_int *copy = mp_new(newmaxbits); + mp_copy_into(copy, mp); + mp_free(mp); + return copy; +} + mp_int *mp_from_integer(uintmax_t n) { mp_int *x = mp_make_sized( diff --git a/code/crypto/ntru.c b/code/crypto/ntru.c new file mode 100644 index 00000000..edc57a91 --- /dev/null +++ b/code/crypto/ntru.c @@ -0,0 +1,1889 @@ +/* + * Implementation of OpenSSH 9.x's hybrid key exchange protocol + * sntrup761x25519-sha512@openssh.com . + * + * This consists of the 'Streamlined NTRU Prime' quantum-resistant + * cryptosystem, run in parallel with ordinary Curve25519 to generate + * a shared secret combining the output of both systems. + * + * (Hence, even if you don't trust this newfangled NTRU Prime thing at + * all, it's at least no _less_ secure than the kex you were using + * already.) + * + * References for the NTRU Prime cryptosystem, up to and including + * binary encodings of public and private keys and the exact preimages + * of the hashes used in key exchange: + * + * https://ntruprime.cr.yp.to/ + * https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf + * + * The SSH protocol layer is not documented anywhere I could find (as + * of 2022-04-15, not even in OpenSSH's PROTOCOL.* files). I had to + * read OpenSSH's source code to find out how it worked, and the + * answer is as follows: + * + * This hybrid kex method is treated for SSH purposes as a form of + * elliptic-curve Diffie-Hellman, and shares the same SSH message + * sequence: client sends SSH2_MSG_KEX_ECDH_INIT containing its public + * half, server responds with SSH2_MSG_KEX_ECDH_REPLY containing _its_ + * public half plus the host key and signature on the shared secret. + * + * (This is a bit of a fudge, because unlike actual ECDH, this kex + * method is asymmetric: one side sends a public key, and the other + * side encrypts something with it and sends the ciphertext back. So + * while the normal ECDH implementations can compute the two sides + * independently in parallel, this system reusing the same messages + * has to be serial. But the order of the messages _is_ firmly + * specified in SSH ECDH, so it works anyway.) + * + * For this kex method, SSH2_MSG_KEX_ECDH_INIT still contains a single + * SSH 'string', which consists of the concatenation of a Streamlined + * NTRU Prime public key with the Curve25519 public value. (Both of + * these have fixed length in bytes, so there's no ambiguity in the + * concatenation.) + * + * SSH2_MSG_KEX_ECDH_REPLY is mostly the same as usual. The only + * string in the packet that varies is the second one, which would + * normally contain the server's public elliptic curve point. Instead, + * it now contains the concatenation of + * + * - a Streamlined NTRU Prime ciphertext + * - the 'confirmation hash' specified in ntruprime-20201007.pdf, + * hashing the plaintext of that ciphertext together with the + * public key + * - the Curve25519 public point as usual. + * + * Again, all three of those elements have fixed lengths. + * + * The client decrypts the ciphertext, checks the confirmation hash, + * and if successful, generates the 'session hash' specified in + * ntruprime-20201007.pdf, which is 32 bytes long and is the ultimate + * output of the Streamlined NTRU Prime key exchange. + * + * The output of the hybrid kex method as a whole is an SSH 'string' + * of length 64 containing the SHA-512 hash of the concatenatio of + * + * - the Streamlined NTRU Prime session hash (32 bytes) + * - the Curve25519 shared secret (32 bytes). + * + * That string is included directly into the SSH exchange hash and key + * derivation hashes, in place of the mpint that comes out of most + * other kex methods. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" +#include "ntru.h" + +/* ---------------------------------------------------------------------- + * Preliminaries: we're going to need to do modular arithmetic on + * small values (considerably smaller than 2^16), and we need to do it + * without using integer division which might not be time-safe. + * + * The strategy for this is the same as I used in + * mp_mod_known_integer: see there for the proofs. The basic idea is + * that we precompute the reciprocal of our modulus as a fixed-point + * number, and use that to get an approximate quotient which we + * subtract off. For these integer sizes, precomputing a fixed-point + * reciprocal of the form (2^48 / modulus) leaves us at most off by 1 + * in the quotient, so there's a single (time-safe) trial subtraction + * at the end. + * + * (It's possible that some speed could be gained by not reducing + * fully at every step. But then you'd have to carefully identify all + * the places in the algorithm where things are compared to zero. This + * was the easiest way to get it all working in the first place.) + */ + +/* Precompute the reciprocal */ +static uint64_t reciprocal_for_reduction(uint16_t q) +{ + return ((uint64_t)1 << 48) / q; +} + +/* Reduce x mod q, assuming qrecip == reciprocal_for_reduction(q) */ +static uint16_t reduce(uint32_t x, uint16_t q, uint64_t qrecip) +{ + uint64_t unshifted_quot = x * qrecip; + uint64_t quot = unshifted_quot >> 48; + uint16_t reduced = x - quot * q; + reduced -= q * (1 & ((q-1 - reduced) >> 15)); + return reduced; +} + +/* Reduce x mod q as above, but also return the quotient */ +static uint16_t reduce_with_quot(uint32_t x, uint32_t *quot_out, + uint16_t q, uint64_t qrecip) +{ + uint64_t unshifted_quot = x * qrecip; + uint64_t quot = unshifted_quot >> 48; + uint16_t reduced = x - quot * q; + uint64_t extraquot = (1 & ((q-1 - reduced) >> 15)); + reduced -= extraquot * q; + *quot_out = quot + extraquot; + return reduced; +} + +/* Invert x mod q, assuming it's nonzero. (For time-safety, no check + * is made for zero; it just returns 0.) */ +static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip) +{ + /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */ + uint32_t sq = x, bit = 1, acc = 1, exp = q-2; + while (1) { + if (exp & bit) { + acc = reduce(acc * sq, q, qrecip); + exp &= ~bit; + if (!exp) + return acc; + } + sq = reduce(sq * sq, q, qrecip); + bit <<= 1; + } +} + +/* Check whether x == 0, time-safely, and return 1 if it is or 0 otherwise. */ +static unsigned iszero(uint16_t x) +{ + return 1 & ~((x + 0xFFFF) >> 16); +} + +/* + * Handy macros to cut down on all those extra function parameters. In + * the common case where a function is working mod the same modulus + * throughout (and has called it q), you can just write 'SETUP;' at + * the top and then call REDUCE(...) and INVERT(...) without having to + * write out q and qrecip every time. + */ +#define SETUP uint64_t qrecip = reciprocal_for_reduction(q) +#define REDUCE(x) reduce(x, q, qrecip) +#define INVERT(x) invert(x, q, qrecip) + +/* ---------------------------------------------------------------------- + * Quotient-ring functions. + * + * NTRU Prime works with two similar but different quotient rings: + * + * Z_q[x] / where p,q are the prime parameters of the system + * Z_3[x] / with the same p, but coefficients mod 3. + * + * The former is a field (every nonzero element is invertible), + * because the system parameters are chosen such that x^p-x-1 is + * invertible over Z_q. The latter is not a field (or not necessarily, + * and in particular, not for the value of p we use here). + * + * In these core functions, you pass in the modulus you want as the + * parameter q, which is either the 'real' q specified in the system + * parameters, or 3 if you're doing one of the mod-3 parts of the + * algorithm. + */ + +/* + * Multiply two elements of a quotient ring. + * + * 'a' and 'b' are arrays of exactly p coefficients, with constant + * term first. 'out' is an array the same size to write the inverse + * into. + */ +void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, + unsigned p, unsigned q) +{ + SETUP; + + /* + * Strategy: just compute the full product with 2p coefficients, + * and then reduce it mod x^p-x-1 by working downwards from the + * top coefficient replacing x^{p+k} with (x+1)x^k for k = ...,1,0. + * + * Possibly some speed could be gained here by doing the recursive + * Karatsuba optimisation for the initial multiplication? But I + * haven't tried it. + */ + uint32_t *unreduced = snewn(2*p, uint32_t); + for (unsigned i = 0; i < 2*p; i++) + unreduced[i] = 0; + for (unsigned i = 0; i < p; i++) + for (unsigned j = 0; j < p; j++) + unreduced[i+j] = REDUCE(unreduced[i+j] + a[i] * b[j]); + + for (unsigned i = 2*p - 1; i >= p; i--) { + unreduced[i-p] += unreduced[i]; + unreduced[i-p+1] += unreduced[i]; + unreduced[i] = 0; + } + + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(unreduced[i]); + + smemclr(unreduced, 2*p * sizeof(*unreduced)); + sfree(unreduced); +} + +/* + * Invert an element of the quotient ring. + * + * 'in' is an array of exactly p coefficients, with constant term + * first. 'out' is an array the same size to write the inverse into. + * + * Method: essentially Stein's gcd algorithm, taking the gcd of the + * input (regarded as an element of Z_q[x] proper) and x^p-x-1. Given + * two polynomials over a field which are not both divisible by x, you + * can find their gcd by iterating the following procedure: + * + * - if one is divisible by x, divide off x + * - otherwise, subtract from the higher-degree one whatever scalar + * multiple of the lower-degree one will make it divisible by x, + * and _then_ divide off x + * + * Neither of these types of step changes the gcd of the two + * polynomials. + * + * Each step reduces the sum of the two polynomials' degree by at + * least one, as long as at least one of the degrees is positive. + * (Maybe more than one if all the stars align in the second case, if + * the subtraction cancels the leading term as well as the constant + * term.) So in at most deg A + deg B steps, we must have reached the + * situation where both polys are constants; in one more step after + * that, one of them will be zero; and in one step after _that_, the + * zero one will reliably be the one we're dividing by x. Or rather, + * that's what happens in the case where A,B are coprime; if not, then + * one hits zero while the other is still nonzero. + * + * In a normal gcd algorithm, you'd track a linear combination of the + * two original polynomials that yields each working value, and end up + * with a linear combination of the inputs that yields the gcd. In + * this algorithm, the 'divide off x' step makes that awkward - but we + * can solve that by instead multiplying by the inverse of x in the + * ring that we want our answer to be valid in! And since the modulus + * polynomial of the ring is x^p-x-1, the inverse of x is easy to + * calculate, because it's always just x^{p-1} - 1, which is also very + * easy to multiply by. + */ +unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q) +{ + SETUP; + + /* Size of the polynomial arrays we'll work with */ + const size_t SIZE = p+1; + + /* Number of steps of the algorithm is the max possible value of + * deg A + deg B + 2, where deg A <= p-1 and deg B = p */ + const size_t STEPS = 2*p + 1; + + /* Our two working polynomials */ + uint16_t *A = snewn(SIZE, uint16_t); + uint16_t *B = snewn(SIZE, uint16_t); + + /* Coefficient of the input value in each one */ + uint16_t *Ac = snewn(SIZE, uint16_t); + uint16_t *Bc = snewn(SIZE, uint16_t); + + /* Initialise A to the input, and Ac correspondingly to 1 */ + memcpy(A, in, p*sizeof(uint16_t)); + A[p] = 0; + Ac[0] = 1; + for (size_t i = 1; i < SIZE; i++) + Ac[i] = 0; + + /* Initialise B to the quotient polynomial of the ring, x^p-x-1 + * And Bc = 0 */ + B[0] = B[1] = q-1; + for (size_t i = 2; i < p; i++) + B[i] = 0; + B[p] = 1; + for (size_t i = 0; i < SIZE; i++) + Bc[i] = 0; + + /* Run the gcd-finding algorithm. */ + for (size_t i = 0; i < STEPS; i++) { + /* + * First swap round so that A is the one we'll be dividing by x. + * + * In the case where one of the two polys has a zero constant + * term, it's that one. In the other case, it's the one of + * smaller degree. We must compute both, and choose between + * them in a side-channel-safe way. + */ + unsigned x_divides_A = iszero(A[0]); + unsigned x_divides_B = iszero(B[0]); + unsigned B_is_bigger = 0; + { + unsigned not_seen_top_term_of_A = 1, not_seen_top_term_of_B = 1; + for (size_t j = SIZE; j-- > 0 ;) { + not_seen_top_term_of_A &= iszero(A[j]); + not_seen_top_term_of_B &= iszero(B[j]); + B_is_bigger |= (~not_seen_top_term_of_B & + not_seen_top_term_of_A); + } + } + unsigned need_swap = x_divides_B | (~x_divides_A & B_is_bigger); + uint16_t swap_mask = -need_swap; + for (size_t j = 0; j < SIZE; j++) { + uint16_t diff = (A[j] ^ B[j]) & swap_mask; + A[j] ^= diff; + B[j] ^= diff; + } + for (size_t j = 0; j < SIZE; j++) { + uint16_t diff = (Ac[j] ^ Bc[j]) & swap_mask; + Ac[j] ^= diff; + Bc[j] ^= diff; + } + + /* + * Replace A with a linear combination of both A and B that + * has constant term zero, which we do by calculating + * + * (constant term of B) * A - (constant term of A) * B + * + * In one of the two cases, A's constant term is already zero, + * so the coefficient of B will be zero too; hence, this will + * do nothing useful (it will merely scale A by some scalar + * value), but it will take the same length of time as doing + * something, which is just what we want. + */ + uint16_t Amult = B[0], Bmult = q - A[0]; + for (size_t j = 0; j < SIZE; j++) + A[j] = REDUCE(Amult * A[j] + Bmult * B[j]); + /* And do the same transformation to Ac */ + for (size_t j = 0; j < SIZE; j++) + Ac[j] = REDUCE(Amult * Ac[j] + Bmult * Bc[j]); + + /* + * Now divide A by x, and compensate by multiplying Ac by + * x^{p-1}-1 mod x^p-x-1. + * + * That multiplication is particularly easy, precisely because + * x^{p-1}-1 is the multiplicative inverse of x! Each x^n term + * for n>0 just moves down to the x^{n-1} term, and only the + * constant term has to be dealt with in an interesting way. + */ + for (size_t j = 1; j < SIZE; j++) + A[j-1] = A[j]; + A[SIZE-1] = 0; + uint16_t Ac0 = Ac[0]; + for (size_t j = 1; j < p; j++) + Ac[j-1] = Ac[j]; + Ac[p-1] = Ac0; + Ac[0] = REDUCE(Ac[0] + q - Ac0); + } + + /* + * Now we expect that A is 0, and B is a constant. If so, then + * they are coprime, and we're going to return success. If not, + * they have a common factor. + */ + unsigned success = iszero(A[0]) & (1 ^ iszero(B[0])); + for (size_t j = 1; j < SIZE; j++) + success &= iszero(A[j]) & iszero(B[j]); + + /* + * So we're going to return Bc, but first, scale it by the + * multiplicative inverse of the constant we ended up with in + * B[0]. + */ + uint16_t scale = INVERT(B[0]); + for (size_t i = 0; i < p; i++) + out[i] = REDUCE(scale * Bc[i]); + + smemclr(A, SIZE * sizeof(*A)); + sfree(A); + smemclr(B, SIZE * sizeof(*B)); + sfree(B); + smemclr(Ac, SIZE * sizeof(*Ac)); + sfree(Ac); + smemclr(Bc, SIZE * sizeof(*Bc)); + sfree(Bc); + + return success; +} + +/* + * Given an array of values mod q, convert each one to its + * minimum-absolute-value representative, and then reduce mod 3. + * + * Output values are 0, 1 and 0xFFFF, representing -1. + * + * (Normally our arrays of uint16_t are in 'minimal non-negative + * residue' form, so the output of this function is unusual. But it's + * useful to have it in this form so that it can be reused by + * ntru_round3. You can put it back to the usual representation using + * ntru_normalise, below.) + */ +void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + uint64_t qrecip = reciprocal_for_reduction(q); + uint64_t recip3 = reciprocal_for_reduction(3); + + unsigned bias = q/2; + uint16_t adjust = 3 - reduce(bias-1, 3, recip3); + + for (unsigned i = 0; i < p; i++) { + uint16_t val = reduce(in[i] + bias, q, qrecip); + uint16_t residue = reduce(val + adjust, 3, recip3); + out[i] = residue - 1; + } +} + +/* + * Given an array of values mod q, round each one to the nearest + * multiple of 3 to its minimum-absolute-value representative. + * + * Output values are signed integers coerced to uint16_t, so again, + * use ntru_normalise afterwards to put them back to normal. + */ +void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + SETUP; + unsigned bias = q/2; + ntru_mod3(out, in, p, q); + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] + bias) - bias - out[i]; +} + +/* + * Given an array of signed integers coerced to uint16_t in the range + * [-q/2,+q/2], normalise them back to mod q values. + */ +static void ntru_normalise(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q) +{ + for (unsigned i = 0; i < p; i++) + out[i] = in[i] + q * (in[i] >> 15); +} + +/* + * Given an array of values mod q, add a constant to each one. + */ +void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, + unsigned p, unsigned q) +{ + SETUP; + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] + bias); +} + +/* + * Given an array of values mod q, multiply each one by a constant. + */ +void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, + unsigned p, unsigned q) +{ + SETUP; + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] * scale); +} + +/* + * Given an array of values mod 3, convert them to values mod q in a + * way that maps -1,0,+1 to -1,0,+1. + */ +static void ntru_expand( + uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + for (size_t i = 0; i < p; i++) { + uint16_t v = in[i]; + /* Map 2 to q-1, and leave 0 and 1 unchanged */ + v += (v >> 1) * (q-3); + out[i] = v; + } +} + +/* ---------------------------------------------------------------------- + * Implement the binary encoding from ntruprime-20201007.pdf, which is + * used to encode public keys and ciphertexts (though not plaintexts, + * which are done in a much simpler way). + * + * The general idea is that your encoder takes as input a list of + * small non-negative integers (r_i), and a sequence of limits (m_i) + * such that 0 <= r_i < m_i, and emits a sequence of bytes that encode + * all of these as tightly as reasonably possible. + * + * That's more general than is really needed, because in both the + * actual uses of this encoding, the input m_i are all the same! But + * the array of (r_i,m_i) pairs evolves during encoding, so they don't + * _stay_ all the same, so you still have to have all the generality. + * + * The encoding process makes a number of passes along the list of + * inputs. In each step, pairs of adjacent numbers are combined into + * one larger one by turning (r_i,m_i) and (r_{i+1},m_{i+1}) into the + * pair (r_i + m_i r_{i+1}, m_i m_{i+1}), i.e. so that the original + * numbers could be recovered by taking the quotient and remaiinder of + * the new r value by m_i. Then, if the new m_i is at least 2^14, we + * emit the low 8 bits of r_i to the output stream and reduce r_i and + * its limit correspondingly. So at the end of the pass, we've got + * half as many numbers still to encode, they're all still not too + * big, and we've emitted some amount of data into the output. Then do + * another pass, keep going until there's only one number left, and + * emit it little-endian. + * + * That's all very well, but how do you decode it again? DJB exhibits + * a pair of recursive functions that are supposed to be mutually + * inverse, but I didn't have any confidence that I'd be able to debug + * them sensibly if they turned out not to be (or rather, if I + * implemented one of them wrong). So I came up with my own strategy + * instead. + * + * In my strategy, we start by processing just the (m_i) into an + * 'encoding schedule' consisting of a sequence of simple + * instructions. The instructions operate on a FIFO queue of numbers, + * initialised to the original (r_i). The three instruction types are: + * + * - 'COMBINE': consume two numbers a,b from the head of the queue, + * combine them by calculating a + m*b for some specified m, and + * push the result on the tail of the queue. + * + * - 'BYTE': divide the tail element of the queue by 2^8 and emit the + * low bits into the output stream. + * + * - 'COPY': pop a number from the head of the queue and push it + * straight back on the tail. (Used for handling the leftover + * element at the end of a pass if the input to the pass was a list + * of odd length.) + * + * So we effectively implement DJB's encoding process in simulation, + * and instead of actually processing a set of (r_i), we 'compile' the + * process into a sequence of instructions that can be handed just the + * (r_i) later and encode them in the right way. At the end of the + * instructions, the queue is expected to have been reduced to length + * 1 and contain the single integer 0. + * + * The nice thing about this system is that each of those three + * instructions is easy to reverse. So you can also use the same + * instructions for decoding: start with a queue containing 0, and + * process the instructions in reverse order and reverse sense. So + * BYTE means to _consume_ a byte from the encoded data (starting from + * the rightmost end) and use it to make a queue element bigger; and + * COMBINE run in reverse pops a single element from one end of the + * queue, divides it by m, and pushes the quotient and remainder on + * the other end. + * + * (So it's easy to debug, because the queue passes through the exact + * same sequence of states during decoding that it did during + * encoding, just in reverse order.) + * + * Also, the encoding schedule comes with information about the + * expected size of the encoded data, because you can find that out + * easily by just counting the BYTE commands. + */ + +enum { + /* + * Command values appearing in the 'ops' array. ENC_COPY and + * ENC_BYTE are single values; values of the form + * (ENC_COMBINE_BASE + m) represent a COMBINE command with + * parameter m. + */ + ENC_COPY, ENC_BYTE, ENC_COMBINE_BASE +}; +struct NTRUEncodeSchedule { + /* + * Object representing a compiled set of encoding instructions. + * + * 'nvals' is the number of r_i we expect to encode. 'nops' is the + * number of encoding commands in the 'ops' list; 'opsize' is the + * physical size of the array, used during construction. + * + * 'endpos' is used to avoid a last-minute faff during decoding. + * We implement our FIFO of integers as a ring buffer of size + * 'nvals'. Encoding cycles round it some number of times, and the + * final 0 element ends up at some random location in the array. + * If we know _where_ the 0 ends up during encoding, we can put + * the initial 0 there at the start of decoding, and then when we + * finish reversing all the instructions, we'll end up with the + * output numbers already arranged at their correct positions, so + * that there's no need to rotate the array at the last minute. + */ + size_t nvals, endpos, nops, opsize; + uint32_t *ops; +}; +static inline void sched_append(NTRUEncodeSchedule *sched, uint16_t op) +{ + /* Helper function to append an operation to the schedule, and + * update endpos. */ + sgrowarray(sched->ops, sched->opsize, sched->nops); + sched->ops[sched->nops++] = op; + if (op != ENC_BYTE) + sched->endpos = (sched->endpos + 1) % sched->nvals; +} + +/* + * Take in the list of limit values (m_i) and compute the encoding + * schedule. + */ +NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n) +{ + NTRUEncodeSchedule *sched = snew(NTRUEncodeSchedule); + sched->nvals = n; + sched->endpos = n-1; + sched->nops = sched->opsize = 0; + sched->ops = NULL; + + assert(n != 0); + + /* + * 'ms' is the list of (m_i) on input to the current pass. + * 'ms_new' is the list output from the current pass. After each + * pass we swap the arrays round. + */ + uint32_t *ms = snewn(n, uint32_t); + uint32_t *msnew = snewn(n, uint32_t); + for (size_t i = 0; i < n; i++) + ms[i] = ms_in[i]; + + while (n > 1) { + size_t nnew = 0; + for (size_t i = 0; i < n; i += 2) { + if (i+1 == n) { + /* + * Odd element at the end of the input list: just copy + * it unchanged to the output. + */ + sched_append(sched, ENC_COPY); + msnew[nnew++] = ms[i]; + break; + } + + /* + * Normal case: consume two elements from the input list + * and combine them. + */ + uint32_t m1 = ms[i], m2 = ms[i+1], m = m1*m2; + sched_append(sched, ENC_COMBINE_BASE + m1); + + /* + * And then, as long as the combined limit is big enough, + * emit an output byte from the bottom of it. + */ + while (m >= (1<<14)) { + sched_append(sched, ENC_BYTE); + m = (m + 0xFF) >> 8; + } + + /* + * Whatever is left after that, we emit into the output + * list and append to the fifo. + */ + msnew[nnew++] = m; + } + + /* + * End of pass. The output list of (m_i) now becomes the input + * list. + */ + uint32_t *tmp = ms; + ms = msnew; + n = nnew; + msnew = tmp; + } + + /* + * When that loop terminates, it's because there's exactly one + * number left to encode. (Or, technically, _at most_ one - but we + * don't support encoding a completely empty list in this + * implementation, because what would be the point?) That number + * is just emitted little-endian until its limit is 1 (meaning its + * only possible actual value is 0). + */ + assert(n == 1); + uint32_t m = ms[0]; + while (m > 1) { + sched_append(sched, ENC_BYTE); + m = (m + 0xFF) >> 8; + } + + sfree(ms); + sfree(msnew); + + return sched; +} + +void ntru_encode_schedule_free(NTRUEncodeSchedule *sched) +{ + sfree(sched->ops); + sfree(sched); +} + +/* + * Calculate the output length of the encoded data in bytes. + */ +size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched) +{ + size_t len = 0; + for (size_t i = 0; i < sched->nops; i++) + if (sched->ops[i] == ENC_BYTE) + len++; + return len; +} + +/* + * Retrieve the number of items encoded. (Used by testcrypt.) + */ +size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched) +{ + return sched->nvals; +} + +/* + * Actually encode a sequence of (r_i), emitting the output bytes to + * an arbitrary BinarySink. + */ +void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, + BinarySink *bs) +{ + size_t n = sched->nvals; + uint32_t *rs = snewn(n, uint32_t); + for (size_t i = 0; i < n; i++) + rs[i] = rs_in[i]; + + /* + * The head and tail pointers of the queue are both 'full'. That + * is, rs[head] is the first element actually in the queue, and + * rs[tail] is the last element. + * + * So you append to the queue by first advancing 'tail' and then + * writing to rs[tail], whereas you consume from the queue by + * first reading rs[head] and _then_ advancing 'head'. + * + * The more normal thing would be to make 'tail' point to the + * first empty slot instead of the last full one. But then you'd + * have to faff about with modular arithmetic to find the last + * full slot for the BYTE command, so in this case, it's easier to + * do it the less usual way. + */ + size_t head = 0, tail = n-1; + + for (size_t i = 0; i < sched->nops; i++) { + uint16_t op = sched->ops[i]; + switch (op) { + case ENC_BYTE: + put_byte(bs, rs[tail] & 0xFF); + rs[tail] >>= 8; + break; + case ENC_COPY: { + uint32_t r = rs[head]; + head = (head + 1) % n; + tail = (tail + 1) % n; + rs[tail] = r; + break; + } + default: { + uint32_t r1 = rs[head]; + head = (head + 1) % n; + uint32_t r2 = rs[head]; + head = (head + 1) % n; + tail = (tail + 1) % n; + rs[tail] = r1 + (op - ENC_COMBINE_BASE) * r2; + break; + } + } + } + + /* + * Expect that we've ended up with a single zero in the queue, at + * exactly the position that the setup-time analysis predicted it. + */ + assert(head == sched->endpos); + assert(tail == sched->endpos); + assert(rs[head] == 0); + + smemclr(rs, n * sizeof(*rs)); + sfree(rs); +} + +/* + * Decode a ptrlen of binary data into a sequence of (r_i). The data + * is expected to be of exactly the right length (on pain of assertion + * failure). + */ +void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data) +{ + size_t n = sched->nvals; + const uint8_t *base = (const uint8_t *)data.ptr; + const uint8_t *pos = base + data.len; + + /* + * Initialise the queue to a single zero, at the 'endpos' position + * that will mean the final output is correctly aligned. + * + * 'head' and 'tail' have the same meanings as in encoding. So + * 'tail' is the location that BYTE modifies and COPY and COMBINE + * consume from, and 'head' is the location that COPY and COMBINE + * push on to. As in encoding, they both point at the extremal + * full slots in the array. + */ + uint32_t *rs = snewn(n, uint32_t); + size_t head = sched->endpos, tail = head; + rs[tail] = 0; + + for (size_t i = sched->nops; i-- > 0 ;) { + uint16_t op = sched->ops[i]; + switch (op) { + case ENC_BYTE: { + assert(pos > base); + uint8_t byte = *--pos; + rs[tail] = (rs[tail] << 8) | byte; + break; + } + case ENC_COPY: { + uint32_t r = rs[tail]; + tail = (tail + n - 1) % n; + head = (head + n - 1) % n; + rs[head] = r; + break; + } + default: { + uint32_t r = rs[tail]; + tail = (tail + n - 1) % n; + + uint32_t m = op - ENC_COMBINE_BASE; + uint64_t mrecip = reciprocal_for_reduction(m); + + uint32_t r1, r2; + r1 = reduce_with_quot(r, &r2, m, mrecip); + + head = (head + n - 1) % n; + rs[head] = r2; + head = (head + n - 1) % n; + rs[head] = r1; + break; + } + } + } + + assert(pos == base); + assert(head == 0); + assert(tail == n-1); + + for (size_t i = 0; i < n; i++) + rs_out[i] = rs[i]; + smemclr(rs, n * sizeof(*rs)); + sfree(rs); +} + +/* ---------------------------------------------------------------------- + * The actual public-key cryptosystem. + */ + +struct NTRUKeyPair { + unsigned p, q, w; + uint16_t *h; /* public key */ + uint16_t *f3, *ginv; /* private key */ + uint16_t *rho; /* for implicit rejection */ +}; + +/* Helper function to free an array of uint16_t containing a ring + * element, clearing it on the way since some of them are sensitive. */ +static void ring_free(uint16_t *val, unsigned p) +{ + smemclr(val, p*sizeof(*val)); + sfree(val); +} + +void ntru_keypair_free(NTRUKeyPair *keypair) +{ + ring_free(keypair->h, keypair->p); + ring_free(keypair->f3, keypair->p); + ring_free(keypair->ginv, keypair->p); + ring_free(keypair->rho, keypair->p); + sfree(keypair); +} + +/* Trivial accessors used by test programs. */ +unsigned ntru_keypair_p(NTRUKeyPair *keypair) { return keypair->p; } +const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; } + +/* + * Generate a value of the class DJB describes as 'Short': it consists + * of p terms that are all either 0 or +1 or -1, and exactly w of them + * are not zero. + * + * Values of this kind are used for several purposes: part of the + * private key, a plaintext, and the 'rho' fake-plaintext value used + * for deliberately returning a duff but non-revealing session hash if + * things go wrong. + * + * -1 is represented as 2 in the output array. So if you want these + * numbers mod 3, then they come out already in the right form. + * Otherwise, use ntru_expand. + */ +void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) +{ + /* + * Get enough random data to generate a polynomial all of whose p + * terms are in {0,+1,-1}, and exactly w of them are nonzero. + * We'll do this by making up a completely random sequence of + * {+1,-1} and then setting a random subset of them to 0. + * + * So we'll need p random bits to choose the nonzero values, and + * then (doing it the simplest way) log2(p!) bits to shuffle them, + * plus say 128 bits to ensure any fluctuations in uniformity are + * negligible. + * + * log2(p!) is a pain to calculate, so we'll bound it above by + * p*log2(p), which we bound in turn by p*16. + */ + size_t randbitpos = 17 * p + 128; + mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); + + /* + * Initial value before zeroing out some terms: p randomly chosen + * values in {1,2}. + */ + for (size_t i = 0; i < p; i++) + v[i] = 1 + mp_get_bit(randdata, --randbitpos); + + /* + * Hereafter we're going to extract random bits by multiplication, + * treating randdata as a large fixed-point number. + */ + mp_reduce_mod_2to(randdata, randbitpos); + + /* + * Zero out some terms, leaving a randomly selected w of them + * nonzero. + */ + uint32_t nonzeros_left = w; + mp_int *x = mp_new(64); + for (size_t i = p; i-- > 0 ;) { + /* + * Pick a random number out of the number of terms remaning. + */ + mp_mul_integer_into(randdata, randdata, i+1); + mp_rshift_fixed_into(x, randdata, randbitpos); + mp_reduce_mod_2to(randdata, randbitpos); + size_t j = mp_get_integer(x); + + /* + * If that's less than nonzeros_left, then we're leaving this + * number nonzero. Otherwise we're zeroing it out. + */ + uint32_t keep = (uint32_t)(j - nonzeros_left) >> 31; + v[i] &= -keep; /* clear this field if keep == 0 */ + nonzeros_left -= keep; /* decrement counter if keep == 1 */ + } + + mp_free(x); + mp_free(randdata); +} + +/* + * Make a single attempt at generating a key pair. This involves + * inventing random elements of both our quotient rings and hoping + * they're both invertible. + * + * They may not be, if you're unlucky. The element of Z_q/ + * will _almost_ certainly be invertible, because that is a field, so + * invertibility can only fail if you were so unlucky as to choose the + * all-0s element. But the element of Z_3/ may fail to be + * invertible because it has a common factor with x^p-x-1 (which, over + * Z_3, is not irreducible). + * + * So we can't guarantee to generate a key pair in constant time, + * because there's no predicting how many retries we'll need. However, + * this isn't a failure of side-channel safety, because we completely + * discard all the random numbers and state from each failed attempt. + * So if there were a side-channel leakage from a failure, the only + * thing it would give away would be a bunch of random numbers that + * turned out not to be used anyway. + * + * But a _successful_ call to this function should execute in a + * secret-independent manner, and this 'make a single attempt' + * function is exposed in the API so that 'testsc' can check that. + */ +NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w) +{ + /* + * First invent g, which is the one more likely to fail to invert. + * This is simply a uniformly random polynomial with p terms over + * Z_3. So we need p*log2(3) random bits for it, plus 128 for + * uniformity. It's easiest to bound log2(3) above by 2. + */ + size_t randbitpos = 2 * p + 128; + mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); + + /* + * Select p random values from {0,1,2}. + */ + uint16_t *g = snewn(p, uint16_t); + mp_int *x = mp_new(64); + for (size_t i = 0; i < p; i++) { + mp_mul_integer_into(randdata, randdata, 3); + mp_rshift_fixed_into(x, randdata, randbitpos); + mp_reduce_mod_2to(randdata, randbitpos); + g[i] = mp_get_integer(x); + } + mp_free(x); + mp_free(randdata); + + /* + * Try to invert g over Z_3, and fail if it isn't invertible. + */ + uint16_t *ginv = snewn(p, uint16_t); + if (!ntru_ring_invert(ginv, g, p, 3)) { + ring_free(g, p); + ring_free(ginv, p); + return NULL; + } + + /* + * Fine; we have g. Now make up an f, and convert it to a + * polynomial over q. + */ + uint16_t *f = snewn(p, uint16_t); + ntru_gen_short(f, p, w); + ntru_expand(f, f, p, q); + + /* + * Multiply f by 3. + */ + uint16_t *f3 = snewn(p, uint16_t); + ntru_scale(f3, f, 3, p, q); + + /* + * Try to invert 3*f over Z_q. This should be _almost_ guaranteed + * to succeed, since Z_q/ is a field, so the only + * non-invertible value is 0. Even so, there _is_ one, so check + * the return value! + */ + uint16_t *f3inv = snewn(p, uint16_t); + if (!ntru_ring_invert(f3inv, f3, p, q)) { + ring_free(f, p); + ring_free(f3, p); + ring_free(f3inv, p); + ring_free(g, p); + ring_free(ginv, p); + return NULL; + } + + /* + * Make the public key, by converting g to a polynomial over q and + * then multiplying by f3inv. + */ + uint16_t *g_q = snewn(p, uint16_t); + ntru_expand(g_q, g, p, q); + uint16_t *h = snewn(p, uint16_t); + ntru_ring_multiply(h, g_q, f3inv, p, q); + + /* + * Make up rho, used to substitute for the plaintext in the + * session hash in case of confirmation failure. + */ + uint16_t *rho = snewn(p, uint16_t); + ntru_gen_short(rho, p, w); + + /* + * And we're done! Free everything except the pieces we're + * returning. + */ + NTRUKeyPair *keypair = snew(NTRUKeyPair); + keypair->p = p; + keypair->q = q; + keypair->w = w; + keypair->h = h; + keypair->f3 = f3; + keypair->ginv = ginv; + keypair->rho = rho; + ring_free(f, p); + ring_free(f3inv, p); + ring_free(g, p); + ring_free(g_q, p); + return keypair; +} + +/* + * The top-level key generation function for real use (as opposed to + * testsc): keep trying to make a key until you succeed. + */ +NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w) +{ + while (1) { + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + if (keypair) + return keypair; + } +} + +/* + * Public-key encryption. + */ +void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, + uint16_t *pubkey, unsigned p, unsigned q) +{ + uint16_t *r_q = snewn(p, uint16_t); + ntru_expand(r_q, plaintext, p, q); + + uint16_t *unrounded = snewn(p, uint16_t); + ntru_ring_multiply(unrounded, r_q, pubkey, p, q); + + ntru_round3(ciphertext, unrounded, p, q); + ntru_normalise(ciphertext, ciphertext, p, q); + + ring_free(r_q, p); + ring_free(unrounded, p); +} + +/* + * Public-key decryption. + */ +void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, + NTRUKeyPair *keypair) +{ + unsigned p = keypair->p, q = keypair->q, w = keypair->w; + uint16_t *tmp = snewn(p, uint16_t); + + ntru_ring_multiply(tmp, ciphertext, keypair->f3, p, q); + + ntru_mod3(tmp, tmp, p, q); + ntru_normalise(tmp, tmp, p, 3); + + ntru_ring_multiply(plaintext, tmp, keypair->ginv, p, 3); + ring_free(tmp, p); + + /* + * With luck, this should have recovered exactly the original + * plaintext. But, as per the spec, we check whether it has + * exactly w nonzero coefficients, and if not, then something has + * gone wrong - and in that situation we time-safely substitute a + * different output. + * + * (I don't know exactly why we do this, but I assume it's because + * otherwise the mis-decoded output could be made to disgorge a + * secret about the private key in some way.) + */ + + unsigned weight = p; + for (size_t i = 0; i < p; i++) + weight -= iszero(plaintext[i]); + unsigned ok = iszero(weight ^ w); + + /* + * The default failure return value consists of w 1s followed by + * 0s. + */ + unsigned mask = ok - 1; + for (size_t i = 0; i < w; i++) { + uint16_t diff = (1 ^ plaintext[i]) & mask; + plaintext[i] ^= diff; + } + for (size_t i = w; i < p; i++) { + uint16_t diff = (0 ^ plaintext[i]) & mask; + plaintext[i] ^= diff; + } +} + +/* ---------------------------------------------------------------------- + * Encode and decode public keys, ciphertexts and plaintexts. + * + * Public keys and ciphertexts use the complicated binary encoding + * system implemented above. In both cases, the inputs are regarded as + * symmetric about zero, and are first biased to map their most + * negative permitted value to 0, so that they become non-negative and + * hence suitable as inputs to the encoding system. In the case of a + * ciphertext, where the input coefficients have also been coerced to + * be multiples of 3, we divide by 3 as well, saving space by reducing + * the upper bounds (m_i) on all the encoded numbers. + */ + +/* + * Compute the encoding schedule for a public key. + */ +static NTRUEncodeSchedule *ntru_encode_pubkey_schedule(unsigned p, unsigned q) +{ + uint16_t *ms = snewn(p, uint16_t); + for (size_t i = 0; i < p; i++) + ms[i] = q; + NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); + sfree(ms); + return sched; +} + +/* + * Encode a public key. + */ +void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, + BinarySink *bs) +{ + /* Compute the biased version for encoding */ + uint16_t *biased_pubkey = snewn(p, uint16_t); + ntru_bias(biased_pubkey, pubkey, q / 2, p, q); + + /* Encode it */ + NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); + ntru_encode(sched, biased_pubkey, bs); + ntru_encode_schedule_free(sched); + + ring_free(biased_pubkey, p); +} + +/* + * Decode a public key and write it into 'pubkey'. We also return a + * ptrlen pointing at the chunk of data we removed from the + * BinarySource. + */ +ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, + BinarySource *src) +{ + NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); + + /* Retrieve the right number of bytes from the source */ + size_t len = ntru_encode_schedule_length(sched); + ptrlen encoded = get_data(src, len); + if (get_err(src)) { + /* If there wasn't enough data, give up and return all-zeroes + * purely for determinism. But that value should never be + * used, because the caller will also check get_err(src). */ + memset(pubkey, 0, p*sizeof(*pubkey)); + } else { + /* Do the decoding */ + ntru_decode(sched, pubkey, encoded); + + /* Unbias the coefficients */ + ntru_bias(pubkey, pubkey, q-q/2, p, q); + } + + ntru_encode_schedule_free(sched); + return encoded; +} + +/* + * For ciphertext biasing: work out the largest absolute value a + * ciphertext element can take, which is given by taking q/2 and + * rounding it to the nearest multiple of 3. + */ +static inline unsigned ciphertext_bias(unsigned q) +{ + return (q/2+1) / 3; +} + +/* + * The number of possible values of a ciphertext coefficient (for use + * as the m_i in encoding) ranges from +ciphertext_bias(q) to + * -ciphertext_bias(q) inclusive. + */ +static inline unsigned ciphertext_m(unsigned q) +{ + return 1 + 2 * ciphertext_bias(q); +} + +/* + * Compute the encoding schedule for a ciphertext. + */ +static NTRUEncodeSchedule *ntru_encode_ciphertext_schedule( + unsigned p, unsigned q) +{ + unsigned m = ciphertext_m(q); + uint16_t *ms = snewn(p, uint16_t); + for (size_t i = 0; i < p; i++) + ms[i] = m; + NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); + sfree(ms); + return sched; +} + +/* + * Encode a ciphertext. + */ +void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, + BinarySink *bs) +{ + SETUP; + + /* + * Bias the ciphertext, and scale down by 1/3, which we do by + * modular multiplication by the inverse of 3 mod q. (That only + * works if we know the inputs are all _exact_ multiples of 3 + * - but we do!) + */ + uint16_t *biased_ciphertext = snewn(p, uint16_t); + ntru_bias(biased_ciphertext, ciphertext, 3 * ciphertext_bias(q), p, q); + ntru_scale(biased_ciphertext, biased_ciphertext, INVERT(3), p, q); + + /* Encode. */ + NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); + ntru_encode(sched, biased_ciphertext, bs); + ntru_encode_schedule_free(sched); + + ring_free(biased_ciphertext, p); +} + +ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, + BinarySource *src) +{ + unsigned p = keypair->p, q = keypair->q; + + NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); + + /* Retrieve the right number of bytes from the source */ + size_t len = ntru_encode_schedule_length(sched); + ptrlen encoded = get_data(src, len); + if (get_err(src)) { + /* As above, return deterministic nonsense on failure */ + memset(ct, 0, p*sizeof(*ct)); + } else { + /* Do the decoding */ + ntru_decode(sched, ct, encoded); + + /* Undo the scaling and bias */ + ntru_scale(ct, ct, 3, p, q); + ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q); + } + + ntru_encode_schedule_free(sched); + return encoded; /* also useful to the caller, optionally */ +} + +/* + * Encode a plaintext. + * + * This is a much simpler encoding than the NTRUEncodeSchedule system: + * since elements of a plaintext are mod 3, we just encode each one in + * 2 bits, applying the usual bias so that {-1,0,+1} map to {0,1,2} + * respectively. + * + * There's no corresponding decode function, because plaintexts are + * never transmitted on the wire (the whole point is that they're too + * secret!). Plaintexts are only encoded in order to put them into + * hash preimages. + */ +void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, + BinarySink *bs) +{ + unsigned byte = 0, bitpos = 0; + for (size_t i = 0; i < p; i++) { + unsigned encoding = (plaintext[i] + 1) * iszero(plaintext[i] >> 1); + byte |= encoding << bitpos; + bitpos += 2; + if (bitpos == 8 || i+1 == p) { + put_byte(bs, byte); + byte = 0; + bitpos = 0; + } + } +} + +/* ---------------------------------------------------------------------- + * Compute the hashes required by the key exchange layer of NTRU Prime. + * + * There are two of these. The 'confirmation hash' is sent by the + * server along with the ciphertext, and the client can recalculate it + * to check whether the ciphertext was decrypted correctly. Then, the + * 'session hash' is the actual output of key exchange, and if the + * confirmation hash doesn't match, it gets deliberately corrupted. + */ + +/* + * Make the confirmation hash, whose inputs are the plaintext and the + * public key. + * + * This is defined as H(2 || H(3 || r) || H(4 || K)), where r is the + * plaintext and K is the public key (as encoded by the above + * functions), and the constants 2,3,4 are single bytes. The choice of + * hash function (H itself) is SHA-512 truncated to 256 bits. + * + * (To be clear: that is _not_ the thing that FIPS 180-4 6.7 defines + * as "SHA-512/256", which varies the initialisation vector of the + * SHA-512 algorithm as well as truncating the output. _This_ + * algorithm uses the standard SHA-512 IV, and _just_ truncates the + * output, in the manner suggested by FIPS 180-4 section 7.) + * + * 'out' should therefore expect to receive 32 bytes of data. + */ +static void ntru_confirmation_hash( + uint8_t *out, const uint16_t *plaintext, + const uint16_t *pubkey, unsigned p, unsigned q) +{ + /* The outer hash object */ + ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512); + put_byte(hconfirm, 2); /* initial byte 2 */ + + uint8_t hashdata[64]; + + /* Compute H(3 || r) and add it to the main hash */ + ssh_hash *h3r = ssh_hash_new(&ssh_sha512); + put_byte(h3r, 3); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); + ssh_hash_final(h3r, hashdata); + put_data(hconfirm, hashdata, 32); + + /* Compute H(4 || K) and add it to the main hash */ + ssh_hash *h4K = ssh_hash_new(&ssh_sha512); + put_byte(h4K, 4); + ntru_encode_pubkey(pubkey, p, q, BinarySink_UPCAST(h4K)); + ssh_hash_final(h4K, hashdata); + put_data(hconfirm, hashdata, 32); + + /* Compute the full output of the main SHA-512 hash */ + ssh_hash_final(hconfirm, hashdata); + + /* And copy the first 32 bytes into the caller's output array */ + memcpy(out, hashdata, 32); + smemclr(hashdata, sizeof(hashdata)); +} + +/* + * Make the session hash, whose inputs are the plaintext, the + * ciphertext, and the confirmation hash (hence, transitively, a + * dependence on the public key as well). + * + * As computed by the server, and by the client if the confirmation + * hash matched, this is defined as + * + * H(1 || H(3 || r) || ciphertext || confirmation hash) + * + * but if the confirmation hash _didn't_ match, then the plaintext r + * is replaced with the dummy plaintext-shaped value 'rho' we invented + * during key generation (presumably to avoid leaking any information + * about our secrets), and the initial byte 1 is replaced with 0 (to + * ensure that the resulting hash preimage can't match any legitimate + * preimage). So in that case, you instead get + * + * H(0 || H(3 || rho) || ciphertext || confirmation hash) + * + * The inputs to this function include 'ok', which is the value to use + * as the initial byte (1 on success, 0 on failure), and 'plaintext' + * which should already have been substituted with rho in case of + * failure. + * + * The ciphertext is provided in already-encoded form. + */ +static void ntru_session_hash( + uint8_t *out, unsigned ok, const uint16_t *plaintext, + unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) +{ + /* The outer hash object */ + ssh_hash *hsession = ssh_hash_new(&ssh_sha512); + put_byte(hsession, ok); /* initial byte 1 or 0 */ + + uint8_t hashdata[64]; + + /* Compute H(3 || r), or maybe H(3 || rho), and add it to the main hash */ + ssh_hash *h3r = ssh_hash_new(&ssh_sha512); + put_byte(h3r, 3); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); + ssh_hash_final(h3r, hashdata); + put_data(hsession, hashdata, 32); + + /* Put the ciphertext and confirmation hash in */ + put_datapl(hsession, ciphertext); + put_datapl(hsession, confirmation_hash); + + /* Compute the full output of the main SHA-512 hash */ + ssh_hash_final(hsession, hashdata); + + /* And copy the first 32 bytes into the caller's output array */ + memcpy(out, hashdata, 32); + smemclr(hashdata, sizeof(hashdata)); +} + +/* ---------------------------------------------------------------------- + * Top-level key exchange and SSH integration. + * + * Although this system borrows the ECDH packet structure, it's unlike + * true ECDH in that it is completely asymmetric between client and + * server. So we have two separate vtables of methods for the two + * sides of the system, and a third vtable containing only the class + * methods, in particular a constructor which chooses which one to + * instantiate. + */ + +/* + * The parameters p,q,w for the system. There are other choices of + * these, but OpenSSH only specifies this set. (If that ever changes, + * we'll need to turn these into elements of the state structures.) + */ +#define p_LIVE 761 +#define q_LIVE 4591 +#define w_LIVE 286 + +static char *ssh_ntru_description(const ssh_kex *kex) +{ + return dupprintf("NTRU Prime / Curve25519 hybrid key exchange"); +} + +/* + * State structure for the client, which takes the role of inventing a + * key pair and decrypting a secret plaintext sent to it by the server. + */ +typedef struct ntru_client_key { + NTRUKeyPair *keypair; + ecdh_key *curve25519; + + ecdh_key ek; +} ntru_client_key; + +static void ssh_ntru_client_free(ecdh_key *dh); +static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs); +static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs); + +static const ecdh_keyalg ssh_ntru_client_vt = { + /* This vtable has no 'new' method, because it's constructed via + * the selector vt below */ + .free = ssh_ntru_client_free, + .getpublic = ssh_ntru_client_getpublic, + .getkey = ssh_ntru_client_getkey, + .description = ssh_ntru_description, +}; + +static ecdh_key *ssh_ntru_client_new(void) +{ + ntru_client_key *nk = snew(ntru_client_key); + nk->ek.vt = &ssh_ntru_client_vt; + + nk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE); + nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); + + return &nk->ek; +} + +static void ssh_ntru_client_free(ecdh_key *dh) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + ntru_keypair_free(nk->keypair); + ecdh_key_free(nk->curve25519); + sfree(nk); +} + +static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + + /* + * The client's public information is a single SSH string + * containing the NTRU public key and the Curve25519 public point + * concatenated. So write both of those into the output + * BinarySink. + */ + ntru_encode_pubkey(nk->keypair->h, p_LIVE, q_LIVE, bs); + ecdh_key_getpublic(nk->curve25519, bs); +} + +static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + + /* + * We expect the server to have sent us a string containing a + * ciphertext, a confirmation hash, and a Curve25519 public point. + * Extract all three. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + uint16_t *ciphertext = snewn(p_LIVE, uint16_t); + ptrlen ciphertext_encoded = ntru_decode_ciphertext( + ciphertext, nk->keypair, src); + ptrlen confirmation_hash = get_data(src, 32); + ptrlen curve25519_remoteKey = get_data(src, 32); + + if (get_err(src) || get_avail(src)) { + /* Hard-fail if the input wasn't exactly the right length */ + ring_free(ciphertext, p_LIVE); + return false; + } + + /* + * Main hash object which will combine the NTRU and Curve25519 + * outputs. + */ + ssh_hash *h = ssh_hash_new(&ssh_sha512); + + /* Reusable buffer for storing various hash outputs. */ + uint8_t hashdata[64]; + + /* + * NTRU side. + */ + { + /* Decrypt the ciphertext to recover the server's plaintext */ + uint16_t *plaintext = snewn(p_LIVE, uint16_t); + ntru_decrypt(plaintext, ciphertext, nk->keypair); + + /* Make the confirmation hash */ + ntru_confirmation_hash(hashdata, plaintext, nk->keypair->h, + p_LIVE, q_LIVE); + + /* Check it matches the one the server sent */ + unsigned ok = smemeq(hashdata, confirmation_hash.ptr, 32); + + /* If not, substitute in rho for the plaintext in the session hash */ + unsigned mask = ok-1; + for (size_t i = 0; i < p_LIVE; i++) + plaintext[i] ^= mask & (plaintext[i] ^ nk->keypair->rho[i]); + + /* Compute the session hash, whether or not we did that */ + ntru_session_hash(hashdata, ok, plaintext, p_LIVE, ciphertext_encoded, + confirmation_hash); + + /* Free temporary values */ + ring_free(plaintext, p_LIVE); + ring_free(ciphertext, p_LIVE); + + /* And put the NTRU session hash into the main hash object. */ + put_data(h, hashdata, 32); + } + + /* + * Curve25519 side. + */ + { + strbuf *otherkey = strbuf_new_nm(); + + /* Call out to Curve25519 to compute the shared secret from that + * kex method */ + bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, + BinarySink_UPCAST(otherkey)); + + /* If that failed (which only happens if the other end does + * something wrong, like sending a low-order curve point + * outside the subgroup it's supposed to), we might as well + * just abort and return failure. That's what we'd have done + * in standalone Curve25519. */ + if (!ok) { + ssh_hash_free(h); + smemclr(hashdata, sizeof(hashdata)); + strbuf_free(otherkey); + return false; + } + + /* + * ecdh_key_getkey will have returned us a chunk of data + * containing an encoded mpint, which is how the Curve25519 + * output normally goes into the exchange hash. But in this + * context we want to treat it as a fixed big-endian 32 bytes, + * so extract it from its encoding and put it into the main + * hash object in the new format. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); + mp_int *curvekey = get_mp_ssh2(src); + + for (unsigned i = 32; i-- > 0 ;) + put_byte(h, mp_get_byte(curvekey, i)); + + mp_free(curvekey); + strbuf_free(otherkey); + } + + /* + * Finish up: compute the final output hash (full 64 bytes of + * SHA-512 this time), and return it encoded as a string. + */ + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +/* + * State structure for the server, which takes the role of inventing a + * secret plaintext and sending it to the client encrypted with the + * public key the client sent. + */ +typedef struct ntru_server_key { + uint16_t *plaintext; + strbuf *ciphertext_encoded, *confirmation_hash; + ecdh_key *curve25519; + + ecdh_key ek; +} ntru_server_key; + +static void ssh_ntru_server_free(ecdh_key *dh); +static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs); +static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs); + +static const ecdh_keyalg ssh_ntru_server_vt = { + /* This vtable has no 'new' method, because it's constructed via + * the selector vt below */ + .free = ssh_ntru_server_free, + .getpublic = ssh_ntru_server_getpublic, + .getkey = ssh_ntru_server_getkey, + .description = ssh_ntru_description, +}; + +static ecdh_key *ssh_ntru_server_new(void) +{ + ntru_server_key *nk = snew(ntru_server_key); + nk->ek.vt = &ssh_ntru_server_vt; + + nk->plaintext = snewn(p_LIVE, uint16_t); + nk->ciphertext_encoded = strbuf_new_nm(); + nk->confirmation_hash = strbuf_new_nm(); + ntru_gen_short(nk->plaintext, p_LIVE, w_LIVE); + + nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); + + return &nk->ek; +} + +static void ssh_ntru_server_free(ecdh_key *dh) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + ring_free(nk->plaintext, p_LIVE); + strbuf_free(nk->ciphertext_encoded); + strbuf_free(nk->confirmation_hash); + ecdh_key_free(nk->curve25519); + sfree(nk); +} + +static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + + /* + * In the server, getkey is called first, with the public + * information received from the client. We expect the client to + * have sent us a string containing a public key and a Curve25519 + * public point. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + uint16_t *pubkey = snewn(p_LIVE, uint16_t); + ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); + ptrlen curve25519_remoteKey = get_data(src, 32); + + if (get_err(src) || get_avail(src)) { + /* Hard-fail if the input wasn't exactly the right length */ + ring_free(pubkey, p_LIVE); + return false; + } + + /* + * Main hash object which will combine the NTRU and Curve25519 + * outputs. + */ + ssh_hash *h = ssh_hash_new(&ssh_sha512); + + /* Reusable buffer for storing various hash outputs. */ + uint8_t hashdata[64]; + + /* + * NTRU side. + */ + { + /* Encrypt the plaintext we generated at construction time, + * and encode the ciphertext into a strbuf so we can reuse it + * for both the session hash and sending to the client. */ + uint16_t *ciphertext = snewn(p_LIVE, uint16_t); + ntru_encrypt(ciphertext, nk->plaintext, pubkey, p_LIVE, q_LIVE); + ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, + BinarySink_UPCAST(nk->ciphertext_encoded)); + ring_free(ciphertext, p_LIVE); + + /* Compute the confirmation hash, and write it into another + * strbuf. */ + ntru_confirmation_hash(hashdata, nk->plaintext, pubkey, + p_LIVE, q_LIVE); + put_data(nk->confirmation_hash, hashdata, 32); + + /* Compute the session hash (which is easy on the server side, + * requiring no conditional substitution). */ + ntru_session_hash(hashdata, 1, nk->plaintext, p_LIVE, + ptrlen_from_strbuf(nk->ciphertext_encoded), + ptrlen_from_strbuf(nk->confirmation_hash)); + + /* And put the NTRU session hash into the main hash object. */ + put_data(h, hashdata, 32); + + /* Now we can free the public key */ + ring_free(pubkey, p_LIVE); + } + + /* + * Curve25519 side. + */ + { + strbuf *otherkey = strbuf_new_nm(); + + /* Call out to Curve25519 to compute the shared secret from that + * kex method */ + bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, + BinarySink_UPCAST(otherkey)); + /* As on the client side, abort if Curve25519 reported failure */ + if (!ok) { + ssh_hash_free(h); + smemclr(hashdata, sizeof(hashdata)); + strbuf_free(otherkey); + return false; + } + + /* As on the client side, decode Curve25519's mpint so we can + * re-encode it appropriately for our hash preimage */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); + mp_int *curvekey = get_mp_ssh2(src); + + for (unsigned i = 32; i-- > 0 ;) + put_byte(h, mp_get_byte(curvekey, i)); + + mp_free(curvekey); + strbuf_free(otherkey); + } + + /* + * Finish up: compute the final output hash (full 64 bytes of + * SHA-512 this time), and return it encoded as a string. + */ + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + + /* + * In the server, this function is called after getkey, so we + * already have all our pieces prepared. Just concatenate them all + * into the 'server's public data' string to go in ECDH_REPLY. + */ + put_datapl(bs, ptrlen_from_strbuf(nk->ciphertext_encoded)); + put_datapl(bs, ptrlen_from_strbuf(nk->confirmation_hash)); + ecdh_key_getpublic(nk->curve25519, bs); +} + +/* ---------------------------------------------------------------------- + * Selector vtable that instantiates the appropriate one of the above, + * depending on is_server. + */ +static ecdh_key *ssh_ntru_new(const ssh_kex *kex, bool is_server) +{ + if (is_server) + return ssh_ntru_server_new(); + else + return ssh_ntru_client_new(); +} + +static const ecdh_keyalg ssh_ntru_selector_vt = { + /* This is a never-instantiated vtable which only implements the + * functions that don't require an instance. */ + .new = ssh_ntru_new, + .description = ssh_ntru_description, +}; + +static const ssh_kex ssh_ntru_curve25519 = { + .name = "sntrup761x25519-sha512@openssh.com", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ntru_selector_vt, +}; + +static const ssh_kex *const hybrid_list[] = { + &ssh_ntru_curve25519, +}; + +const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list }; diff --git a/code/crypto/ntru.h b/code/crypto/ntru.h new file mode 100644 index 00000000..4789491b --- /dev/null +++ b/code/crypto/ntru.h @@ -0,0 +1,53 @@ +/* + * Internal functions for the NTRU cryptosystem, exposed in a header + * that is expected to be included only by ntru.c and test programs. + */ + +#ifndef PUTTY_CRYPTO_NTRU_H +#define PUTTY_CRYPTO_NTRU_H + +unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q); +void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, + unsigned p, unsigned q); +void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); +void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); +void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, + unsigned p, unsigned q); +void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, + unsigned p, unsigned q); + +NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n); +void ntru_encode_schedule_free(NTRUEncodeSchedule *sched); +size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched); +size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched); +void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, + BinarySink *bs); +void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data); + +void ntru_gen_short(uint16_t *v, unsigned p, unsigned w); + +NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w); +NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w); +void ntru_keypair_free(NTRUKeyPair *keypair); + +void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, + uint16_t *pubkey, unsigned p, unsigned q); +void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, + NTRUKeyPair *keypair); + +void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, + BinarySink *bs); +ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, + BinarySource *src); +void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, + BinarySink *bs); +ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, + BinarySource *src); +void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, + BinarySink *bs); + +unsigned ntru_keypair_p(NTRUKeyPair *keypair); +const uint16_t *ntru_pubkey(NTRUKeyPair *keypair); + +#endif /* PUTTY_CRYPTO_NTRU_H */ diff --git a/code/crypto/openssh-certs.c b/code/crypto/openssh-certs.c new file mode 100644 index 00000000..cf0c2af3 --- /dev/null +++ b/code/crypto/openssh-certs.c @@ -0,0 +1,1160 @@ +/* + * Public key type for OpenSSH certificates. + */ + +#include "ssh.h" +#include "putty.h" + +enum { + SSH_CERT_TYPE_USER = 1, + SSH_CERT_TYPE_HOST = 2, +}; + +typedef struct opensshcert_key { + strbuf *nonce; + uint64_t serial; + uint32_t type; + strbuf *key_id; + strbuf *valid_principals; + uint64_t valid_after, valid_before; + strbuf *critical_options; + strbuf *extensions; + strbuf *reserved; + strbuf *signature_key; + strbuf *signature; + + ssh_key *basekey; + + ssh_key sshk; +} opensshcert_key; + +typedef struct blob_fmt { + const unsigned *fmt; + size_t len; +} blob_fmt; + +typedef struct opensshcert_extra { + /* + * OpenSSH certificate formats aren't completely consistent about + * the relationship between the public+private blob uploaded to + * the agent for the certified key type, and the one for the base + * key type. Here we specify the mapping. + * + * Each of these foo_fmt strings indicates the layout of a + * particular version of the key, in the form of an array of + * integers together with a length, with each integer describing + * one of the components of the key. The integers are defined by + * enums, so that they're tightly packed; the general idea is that + * if you're converting from one form to another, then you use the + * format list for the source format to read out a succession of + * SSH strings from the source data and put them in an array + * indexed by the integer ids, and then use the list for the + * destination format to write the strings out to the destination + * in the right (maybe different) order. + * + * pub_fmt describes the format of the public-key blob for the + * base key type, not counting the initial string giving the key + * type identifier itself. As far as I know, this always matches + * the format of the public-key data appearing in the middle of + * the certificate. + * + * base_ossh_fmt describes the format of the full OpenSSH blob + * appearing in the ssh-agent protocol for the base key, + * containing the public and private key data. + * + * cert_ossh_fmt describes the format of the OpenSSH blob for the + * certificate key format, beginning just after the certificate + * string itself. + */ + blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt; + + /* + * The RSA-SHA2 algorithm names have their SSH id set to names + * like "rsa-sha2-512-cert-...", which is what will be received in + * the KEXINIT algorithm list if a host key in one of those + * algorithms is presented. But the _key_ type id that will appear + * in the public key blob is "ssh-rsa-cert-...". So we need a + * separate field to indicate the key type id we expect to see in + * certified public keys, and also the one we want to put back + * into the artificial public blob we make to pass to the + * constructor for the underlying key. + * + * (In rsa.c this is managed much more simply, because everything + * sharing the same vtable wants the same key type id.) + */ + const char *cert_key_ssh_id, *base_key_ssh_id; +} opensshcert_extra; + +/* + * The actual integer arrays defining the per-key blob formats. + */ + +/* DSA is the most orthodox: only the obviously necessary public key + * info appears at all, it's in the same order everywhere, and none of + * it is repeated unnecessarily */ +enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; +static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y }; +static const unsigned dsa_base_ossh_fmt[] = { + DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; +static const unsigned dsa_cert_ossh_fmt[] = { DSA_x }; + +/* ECDSA is almost as nice, except that it pointlessly mentions the + * curve name in the public data, which shouldn't be necessary given + * that the SSH key id has already implied it. But at least that's + * consistent everywhere. */ +enum { ECDSA_curve, ECDSA_point, ECDSA_exp }; +static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point }; +static const unsigned ecdsa_base_ossh_fmt[] = { + ECDSA_curve, ECDSA_point, ECDSA_exp }; +static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp }; + +/* Ed25519 has the oddity that the private data following the + * certificate in the OpenSSH blob is preceded by an extra copy of the + * public data, for no obviously necessary reason since that doesn't + * happen in any of the rest of these formats */ +enum { EDDSA_point, EDDSA_exp }; +static const unsigned eddsa_pub_fmt[] = { EDDSA_point }; +static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; +static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; + +/* And RSA has the quirk that the modulus and exponent are reversed in + * the base key type's OpenSSH blob! */ +enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp }; +static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n }; +static const unsigned rsa_base_ossh_fmt[] = { + RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp }; +static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp }; + +/* + * Routines to transform one kind of blob into another based on those + * foo_fmt integer arrays. + */ +typedef struct BlobTransformer { + ptrlen *parts; + size_t nparts; +} BlobTransformer; + +#define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } } + +static inline void blobtrans_clear(BlobTransformer *bt) +{ + sfree(bt->parts); + bt->parts = NULL; + bt->nparts = 0; +} + +static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src, + blob_fmt blob) +{ + size_t nparts = bt->nparts; + for (size_t i = 0; i < blob.len; i++) + if (nparts < blob.fmt[i]+1) + nparts = blob.fmt[i]+1; + + if (nparts > bt->nparts) { + bt->parts = sresize(bt->parts, nparts, ptrlen); + while (bt->nparts < nparts) + bt->parts[bt->nparts++] = make_ptrlen(NULL, 0); + } + + for (size_t i = 0; i < blob.len; i++) { + size_t j = blob.fmt[i]; + ptrlen part = get_string(src); + if (bt->parts[j].ptr) { + /* + * If the same string appears in both the public blob and + * the private data, check they match. (This happens in + * Ed25519: an extra copy of the public point string + * appears in the certified OpenSSH data after the + * certificate and before the private key.) + */ + if (!ptrlen_eq_ptrlen(bt->parts[j], part)) + return false; + } + bt->parts[j] = part; + } + + return true; +} + +static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs, + blob_fmt blob) +{ + for (size_t i = 0; i < blob.len; i++) { + assert(i < bt->nparts); + ptrlen part = bt->parts[blob.fmt[i]]; + assert(part.ptr); + put_stringpl(bs, part); + } +} + +/* + * Forward declarations. + */ +static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub); +static ssh_key *opensshcert_new_priv( + const ssh_keyalg *self, ptrlen pub, ptrlen priv); +static ssh_key *opensshcert_new_priv_openssh( + const ssh_keyalg *self, BinarySource *src); +static void opensshcert_freekey(ssh_key *key); +static char *opensshcert_invalid(ssh_key *key, unsigned flags); +static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, + BinarySink *bs); +static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data); +static void opensshcert_public_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_private_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs); +static SeatDialogText *opensshcert_cert_info(ssh_key *key); +static bool opensshcert_has_private(ssh_key *key); +static char *opensshcert_cache_str(ssh_key *key); +static key_components *opensshcert_components(ssh_key *key); +static ssh_key *opensshcert_base_key(ssh_key *key); +static bool opensshcert_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + const ca_options *opts, BinarySink *error); +static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); +static unsigned opensshcert_supported_flags(const ssh_keyalg *self); +static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, + unsigned flags); +static char *opensshcert_alg_desc(const ssh_keyalg *self); +static bool opensshcert_variable_size(const ssh_keyalg *self); +static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, + const ssh_keyalg *base); + +/* + * Top-level vtables for the certified key formats, defined via a list + * macro so I can also make an array of them all. + */ + +#define KEYALG_LIST(X) \ + X(ssh_dsa, "ssh-dss", "ssh-dss", dsa) \ + X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa) \ + X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa) \ + X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa) \ + X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa) \ + X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \ + X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \ + X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \ + /* end of list */ + +#define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \ + static const struct opensshcert_extra opensshcert_##name##_extra = { \ + .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \ + .len = lenof(fmt_prefix ## _pub_fmt) }, \ + .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \ + .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \ + .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \ + .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \ + .cert_key_ssh_id = ssh_key_id_prefix "-cert-v01@openssh.com", \ + .base_key_ssh_id = ssh_key_id_prefix, \ + }; \ + \ + const ssh_keyalg opensshcert_##name = { \ + .new_pub = opensshcert_new_pub, \ + .new_priv = opensshcert_new_priv, \ + .new_priv_openssh = opensshcert_new_priv_openssh, \ + .freekey = opensshcert_freekey, \ + .invalid = opensshcert_invalid, \ + .sign = opensshcert_sign, \ + .verify = opensshcert_verify, \ + .public_blob = opensshcert_public_blob, \ + .private_blob = opensshcert_private_blob, \ + .openssh_blob = opensshcert_openssh_blob, \ + .has_private = opensshcert_has_private, \ + .cache_str = opensshcert_cache_str, \ + .components = opensshcert_components, \ + .base_key = opensshcert_base_key, \ + .ca_public_blob = opensshcert_ca_public_blob, \ + .check_cert = opensshcert_check_cert, \ + .cert_id_string = opensshcert_cert_id_string, \ + .cert_info = opensshcert_cert_info, \ + .pubkey_bits = opensshcert_pubkey_bits, \ + .supported_flags = opensshcert_supported_flags, \ + .alternate_ssh_id = opensshcert_alternate_ssh_id, \ + .alg_desc = opensshcert_alg_desc, \ + .variable_size = opensshcert_variable_size, \ + .related_alg = opensshcert_related_alg, \ + .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ + .cache_id = "opensshcert-" ssh_key_id_prefix, \ + .extra = &opensshcert_##name##_extra, \ + .is_certificate = true, \ + .base_alg = &name, \ + }; +KEYALG_LIST(KEYALG_DEF) +#undef KEYALG_DEF + +#define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name, +static const ssh_keyalg *const opensshcert_all_keyalgs[] = { + KEYALG_LIST(KEYALG_LIST_ENTRY) +}; +#undef KEYALG_LIST_ENTRY + +static strbuf *get_base_public_blob(BinarySource *src, + const opensshcert_extra *extra) +{ + strbuf *basepub = strbuf_new(); + put_stringz(basepub, extra->base_key_ssh_id); + + /* Make the base public key blob out of the public key + * material in the certificate. This invocation of the + * blobtrans system doesn't do any format translation, but it + * does ensure that the right amount of data is copied so that + * src ends up in the right position to read the remaining + * certificate fields. */ + BLOBTRANS_DECLARE(bt); + blobtrans_read(bt, src, extra->pub_fmt); + blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt); + blobtrans_clear(bt); + + return basepub; +} + +static opensshcert_key *opensshcert_new_shared( + const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out) +{ + const opensshcert_extra *extra = self->extra; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + + /* Check the initial key-type string */ + if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id)) + return NULL; + + opensshcert_key *ck = snew(opensshcert_key); + memset(ck, 0, sizeof(*ck)); + ck->sshk.vt = self; + + ck->nonce = strbuf_dup(get_string(src)); + strbuf *basepub = get_base_public_blob(src, extra); + ck->serial = get_uint64(src); + ck->type = get_uint32(src); + ck->key_id = strbuf_dup(get_string(src)); + ck->valid_principals = strbuf_dup(get_string(src)); + ck->valid_after = get_uint64(src); + ck->valid_before = get_uint64(src); + ck->critical_options = strbuf_dup(get_string(src)); + ck->extensions = strbuf_dup(get_string(src)); + ck->reserved = strbuf_dup(get_string(src)); + ck->signature_key = strbuf_dup(get_string(src)); + ck->signature = strbuf_dup(get_string(src)); + + + if (get_err(src)) { + ssh_key_free(&ck->sshk); + strbuf_free(basepub); + return NULL; + } + + *basepub_out = basepub; + return ck; +} + +static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub) +{ + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); + if (!ck) + return NULL; + + ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub)); + strbuf_free(basepub); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static ssh_key *opensshcert_new_priv( + const ssh_keyalg *self, ptrlen pub, ptrlen priv) +{ + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); + if (!ck) + return NULL; + + ck->basekey = ssh_key_new_priv(self->base_alg, + ptrlen_from_strbuf(basepub), priv); + strbuf_free(basepub); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static ssh_key *opensshcert_new_priv_openssh( + const ssh_keyalg *self, BinarySource *src) +{ + const opensshcert_extra *extra = self->extra; + + ptrlen cert = get_string(src); + + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub); + if (!ck) + return NULL; + + strbuf *baseossh = strbuf_new(); + + /* Make the base OpenSSH key blob out of the public key blob + * returned from opensshcert_new_shared, and the trailing + * private data following the certificate */ + BLOBTRANS_DECLARE(bt); + + BinarySource pubsrc[1]; + BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub)); + get_string(pubsrc); /* skip key type id */ + + /* blobtrans_read might fail in this case, because we're reading + * from two sources and they might fail to match */ + bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) && + blobtrans_read(bt, src, extra->cert_ossh_fmt); + + blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt); + blobtrans_clear(bt); + + if (!success) { + ssh_key_free(&ck->sshk); + strbuf_free(basepub); + strbuf_free(baseossh); + return NULL; + } + + strbuf_free(basepub); + + BinarySource osshsrc[1]; + BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh)); + ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc); + strbuf_free(baseossh); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static void opensshcert_freekey(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + + /* If this function is called from one of the above constructors + * because it failed part way through, we might not have managed + * to construct ck->basekey, so it might be NULL. */ + if (ck->basekey) + ssh_key_free(ck->basekey); + + strbuf_free(ck->nonce); + strbuf_free(ck->key_id); + strbuf_free(ck->valid_principals); + strbuf_free(ck->critical_options); + strbuf_free(ck->extensions); + strbuf_free(ck->reserved); + strbuf_free(ck->signature_key); + strbuf_free(ck->signature); + + sfree(ck); +} + +static ssh_key *opensshcert_base_key(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ck->basekey; +} + +/* + * Make a public key object from the CA public blob, potentially + * taking into account that the signature might override the algorithm + * name + */ +static ssh_key *opensshcert_ca_pub_key( + opensshcert_key *ck, ptrlen sig, ptrlen *algname) +{ + ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key); + + ptrlen alg_source = sig.ptr ? sig : ca_keyblob; + if (algname) + *algname = pubkey_blob_to_alg_name(alg_source); + + const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source); + if (!ca_alg) + return NULL; /* don't even recognise the certifying key type */ + + return ssh_key_new_pub(ca_alg, ca_keyblob); +} + +static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs) +{ + const opensshcert_extra *extra = ck->sshk.vt->extra; + put_stringz(bs, extra->cert_key_ssh_id); + put_stringpl(bs, ptrlen_from_strbuf(ck->nonce)); + + strbuf *basepub = strbuf_new(); + ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub)); + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub)); + get_string(src); /* skip initial key type string */ + put_data(bs, get_ptr(src), get_avail(src)); + strbuf_free(basepub); + + put_uint64(bs, ck->serial); + put_uint32(bs, ck->type); + put_stringpl(bs, ptrlen_from_strbuf(ck->key_id)); + put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals)); + put_uint64(bs, ck->valid_after); + put_uint64(bs, ck->valid_before); + put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options)); + put_stringpl(bs, ptrlen_from_strbuf(ck->extensions)); + put_stringpl(bs, ptrlen_from_strbuf(ck->reserved)); + put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key)); +} + +static void opensshcert_public_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + + opensshcert_signature_preimage(ck, bs); + put_stringpl(bs, ptrlen_from_strbuf(ck->signature)); +} + +static void opensshcert_private_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + ssh_key_private_blob(ck->basekey, bs); +} + +static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + const opensshcert_extra *extra = key->vt->extra; + + strbuf *cert = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(cert)); + put_stringsb(bs, cert); + + strbuf *baseossh = strbuf_new_nm(); + ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh)); + BinarySource basesrc[1]; + BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh)); + + BLOBTRANS_DECLARE(bt); + blobtrans_read(bt, basesrc, extra->base_ossh_fmt); + blobtrans_write(bt, bs, extra->cert_ossh_fmt); + blobtrans_clear(bt); + + strbuf_free(baseossh); +} + +static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + put_datapl(bs, ptrlen_from_strbuf(ck->signature_key)); +} + +static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + put_datapl(bs, ptrlen_from_strbuf(ck->key_id)); +} + +static bool opensshcert_has_private(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_has_private(ck->basekey); +} + +static char *opensshcert_cache_str(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_cache_str(ck->basekey); +} + +static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) +{ + time_t t = time; + char buf[256]; + put_data(bs, buf, strftime(buf, sizeof(buf), + "%Y-%m-%d %H:%M:%S UTC", gmtime(&t))); +} + +static void opensshcert_string_list_key_components( + key_components *kc, strbuf *input, const char *title, const char *title2) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input)); + + const char *titles[2] = { title, title2 }; + size_t ntitles = (title2 ? 2 : 1); + + unsigned index = 0; + while (get_avail(src)) { + for (size_t ti = 0; ti < ntitles; ti++) { + ptrlen value = get_string(src); + if (get_err(src)) + break; + char *name = dupprintf("%s_%u", titles[ti], index); + key_components_add_text_pl(kc, name, value); + sfree(name); + } + index++; + } +} + +static key_components *opensshcert_components(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + key_components *kc = ssh_key_components(ck->basekey); + key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce)); + key_components_add_uint(kc, "cert_serial", ck->serial); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + key_components_add_text(kc, "cert_type", "host"); + break; + case SSH_CERT_TYPE_USER: + key_components_add_text(kc, "cert_type", "user"); + break; + default: + key_components_add_uint(kc, "cert_type", ck->type); + break; + } + key_components_add_text(kc, "cert_key_id", ck->key_id->s); + opensshcert_string_list_key_components(kc, ck->valid_principals, + "cert_valid_principal", NULL); + key_components_add_uint(kc, "cert_valid_after", ck->valid_after); + key_components_add_uint(kc, "cert_valid_before", ck->valid_before); + /* Translate the validity period into human-legible dates, but + * only if they're not the min/max integer. Rationale: if you see + * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be + * as likely to think it's a weird buffer overflow as half a + * trillion years in the future! */ + if (ck->valid_after != 0) { + strbuf *date = strbuf_new(); + opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after); + key_components_add_text_pl(kc, "cert_valid_after_date", + ptrlen_from_strbuf(date)); + strbuf_free(date); + } + if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) { + strbuf *date = strbuf_new(); + opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before); + key_components_add_text_pl(kc, "cert_valid_before_date", + ptrlen_from_strbuf(date)); + strbuf_free(date); + } + opensshcert_string_list_key_components(kc, ck->critical_options, + "cert_critical_option", + "cert_critical_option_data"); + opensshcert_string_list_key_components(kc, ck->extensions, + "cert_extension", + "cert_extension_data"); + key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf( + ck->signature_key)); + + ptrlen ca_algname; + ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), + &ca_algname); + key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname); + + if (ca_key) { + key_components *kc_ca_key = ssh_key_components(ca_key); + for (size_t i = 0; i < kc_ca_key->ncomponents; i++) { + key_component *comp = &kc_ca_key->components[i]; + char *subname = dupcat("cert_ca_key_", comp->name); + key_components_add_copy(kc, subname, comp); + sfree(subname); + } + key_components_free(kc_ca_key); + ssh_key_free(ca_key); + } + + key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf( + ck->signature)); + return kc; +} + +static SeatDialogText *opensshcert_cert_info(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + SeatDialogText *text = seat_dialog_text_new(); + strbuf *tmp = strbuf_new(); + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate type"); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "host key"); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid host names"); + break; + case SSH_CERT_TYPE_USER: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "user authentication key"); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid user names"); + break; + default: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "unknown type %" PRIu32, ck->type); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid principals"); + break; + } + + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->valid_principals)); + const char *sep = ""; + strbuf_clear(tmp); + while (get_avail(src)) { + ptrlen principal = get_string(src); + if (get_err(src)) + break; + put_dataz(tmp, sep); + sep = ","; + put_datapl(tmp, principal); + } + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%s", tmp->s); + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Validity period"); + strbuf_clear(tmp); + if (ck->valid_after == 0) { + if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { + put_dataz(tmp, "forever"); + } else { + put_dataz(tmp, "until "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_before); + } + } else { + if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { + put_dataz(tmp, "after "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_after); + } else { + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_after); + put_dataz(tmp, " - "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_before); + } + } + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s); + + /* + * List critical options we know about. (This is everything listed + * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types + * that PuTTY doesn't currently support.) + */ + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->critical_options)); + strbuf_clear(tmp); + while (get_avail(src)) { + ptrlen key = get_string(src); + ptrlen value = get_string(src); + if (get_err(src)) + break; + if (ck->type == SSH_CERT_TYPE_USER && + ptrlen_eq_string(key, "source-address")) { + BinarySource src2[1]; + BinarySource_BARE_INIT_PL(src2, value); + ptrlen addresslist = get_string(src2); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Permitted client IP addresses"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%.*s", PTRLEN_PRINTF(addresslist)); + } else if (ck->type == SSH_CERT_TYPE_USER && + ptrlen_eq_string(key, "force-command")) { + BinarySource src2[1]; + BinarySource_BARE_INIT_PL(src2, value); + ptrlen command = get_string(src2); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Forced remote command"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%.*s", PTRLEN_PRINTF(command)); + } + } + } + + /* + * List certificate extensions. Again, we go through everything in + * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types. + * But we also flip the sense round for user-readability: I think + * it's more likely that the typical key will permit all these + * things, so we emit no output in that case, and only mention the + * things that _aren't_ enabled. + */ + + bool x11_ok = false, agent_ok = false, portfwd_ok = false; + bool pty_ok = false, user_rc_ok = false; + + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->extensions)); + while (get_avail(src)) { + ptrlen key = get_string(src); + /* ptrlen value = */ get_string(src); // nothing needs this yet + if (get_err(src)) + break; + if (ptrlen_eq_string(key, "permit-X11-forwarding")) { + x11_ok = true; + } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) { + agent_ok = true; + } else if (ptrlen_eq_string(key, "permit-port-forwarding")) { + portfwd_ok = true; + } else if (ptrlen_eq_string(key, "permit-pty")) { + pty_ok = true; + } else if (ptrlen_eq_string(key, "permit-user-rc")) { + user_rc_ok = true; + } + } + } + + if (ck->type == SSH_CERT_TYPE_USER) { + if (!x11_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "X11 forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!agent_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Agent forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!portfwd_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Port forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!pty_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "PTY allocation permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!user_rc_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Running user ~/.ssh.rc permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate ID string"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%s", ck->key_id->s); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate serial number"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%" PRIu64, ck->serial); + + char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key), + SSH_FPTYPE_DEFAULT); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Fingerprint of signing CA key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); + sfree(fp); + + fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT)); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Fingerprint including certificate"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); + sfree(fp); + + strbuf_free(tmp); + return text; +} + +static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + + get_string(src); /* key type */ + get_string(src); /* nonce */ + strbuf *basepub = get_base_public_blob(src, self->extra); + int bits = ssh_key_public_bits( + self->base_alg, ptrlen_from_strbuf(basepub)); + strbuf_free(basepub); + return bits; +} + +static unsigned opensshcert_supported_flags(const ssh_keyalg *self) +{ + return ssh_keyalg_supported_flags(self->base_alg); +} + +static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, + unsigned flags) +{ + const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags); + + for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { + const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; + if (!strcmp(base_id, alg_i->base_alg->ssh_id)) + return alg_i->ssh_id; + } + + return self->ssh_id; +} + +static char *opensshcert_alg_desc(const ssh_keyalg *self) +{ + char *base_desc = ssh_keyalg_desc(self->base_alg); + char *our_desc = dupcat(base_desc, " cert"); + sfree(base_desc); + return our_desc; +} + +static bool opensshcert_variable_size(const ssh_keyalg *self) +{ + return ssh_keyalg_variable_size(self->base_alg); +} + +static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, + const ssh_keyalg *base) +{ + for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { + const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; + if (base == alg_i->base_alg) + return alg_i; + } + + return self; +} + +static char *opensshcert_invalid(ssh_key *key, unsigned flags) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_invalid(ck->basekey, flags); +} + +static bool opensshcert_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + const ca_options *opts, BinarySink *error) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + bool result = false; + ssh_key *ca_key = NULL; + strbuf *preimage = strbuf_new(); + BinarySource src[1]; + + ptrlen signature = ptrlen_from_strbuf(ck->signature); + + /* + * The OpenSSH certificate spec is one-layer only: it explicitly + * forbids using a certified key in turn as the CA. + * + * If it did not, then we'd also have to recursively verify + * everything up the CA chain until we reached the ultimate root, + * and then make sure _that_ was something we trusted. (Not to + * mention that there'd probably be an additional SSH_CERT_TYPE_CA + * or some such, and certificate options saying what kinds of + * certificate a CA was trusted to sign for, and ...) + */ + ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL); + if (!ca_key) { + put_fmt(error, "Certificate's signing key is invalid"); + goto out; + } + if (ssh_key_alg(ca_key)->is_certificate) { + put_fmt(error, "Certificate is signed with a certified key " + "(forbidden by OpenSSH certificate specification)"); + goto out; + } + + /* + * Now re-instantiate the key in a way that matches the signature + * (i.e. so that if the key is an RSA one we get the right subtype + * of RSA). + */ + ssh_key_free(ca_key); + ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + if (!ca_key) { + put_fmt(error, "Certificate's signing key does not match " + "signature type"); + goto out; + } + + /* Check which signature algorithm is actually in use, because + * that might be a reason to reject the certificate (e.g. ssh-rsa + * when we wanted rsa-sha2-*). */ + const ssh_keyalg *sig_alg = ssh_key_alg(ca_key); + if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) || + (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) || + (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) { + put_fmt(error, "Certificate signature uses '%s' signature type " + "(forbidden by user configuration)", sig_alg->ssh_id); + goto out; + } + + opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage)); + + if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) { + put_fmt(error, "Certificate's signature is invalid"); + goto out; + } + + uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER; + if (ck->type != expected_type) { + put_fmt(error, "Certificate type is "); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + put_fmt(error, "host"); + break; + case SSH_CERT_TYPE_USER: + put_fmt(error, "user"); + break; + default: + put_fmt(error, "unknown value %" PRIu32, ck->type); + break; + } + put_fmt(error, "; expected %s", host ? "host" : "user"); + goto out; + } + + /* + * Check the time bounds on the certificate. + */ + if (time < ck->valid_after) { + put_fmt(error, "Certificate is not valid until "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time); + goto out; + } + if (time >= ck->valid_before) { + put_fmt(error, "Certificate expired at "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time); + goto out; + } + + /* + * Check that this certificate is for the right thing. + * + * If valid_principals is a zero-length string then this is + * specified to be a carte-blanche certificate valid for any + * principal (at least, provided you trust the CA that issued it). + */ + if (ck->valid_principals->len != 0) { + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->valid_principals)); + + while (get_avail(src)) { + ptrlen valid_principal = get_string(src); + if (get_err(src)) { + put_fmt(error, "Certificate's valid principals list is " + "incorrectly formatted"); + goto out; + } + if (ptrlen_eq_ptrlen(valid_principal, principal)) + goto principal_ok; + } + + /* + * No valid principal matched. Now go through the list a + * second time writing the cert contents into the error + * message, so that the user can see at a glance what went + * wrong. + * + * (If you've typed the wrong spelling of the host name, you + * really need to see "This cert is for 'foo.example.com' and + * I was trying to match it against 'foo'", rather than just + * "Computer says no".) + */ + put_fmt(error, "Certificate's %s list [", + host ? "hostname" : "username"); + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->valid_principals)); + const char *sep = ""; + while (get_avail(src)) { + ptrlen valid_principal = get_string(src); + put_fmt(error, "%s\"", sep); + put_c_string_literal(error, valid_principal); + put_fmt(error, "\""); + sep = ", "; + } + put_fmt(error, "] does not contain expected %s \"", + host ? "hostname" : "username"); + put_c_string_literal(error, principal); + put_fmt(error, "\""); + goto out; + principal_ok:; + } + + /* + * Check for critical options. + */ + { + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->critical_options)); + + while (get_avail(src)) { + ptrlen option = get_string(src); + ptrlen data = get_string(src); + if (get_err(src)) { + put_fmt(error, "Certificate's critical options list is " + "incorrectly formatted"); + goto out; + } + + /* + * If we ever do support any options, this will be where + * we insert code to recognise and validate them. + * + * At present, we implement no critical options at all. + * (For host certs, as of 2022-04-20, OpenSSH hasn't + * defined any. For user certs, the only SSH server using + * this is Uppity, which doesn't support key restrictions + * in general.) + */ + (void)data; /* no options supported => no use made of the data */ + + /* + * Report an unrecognised literal. + */ + put_fmt(error, "Certificate specifies an unsupported critical " + "option \""); + put_c_string_literal(error, option); + put_fmt(error, "\""); + goto out; + } + } + + /* If we get here without failing any check, accept the certificate! */ + result = true; + + out: + if (ca_key) + ssh_key_free(ca_key); + strbuf_free(preimage); + return result; +} + +static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + /* This method is pure *signature* verification; checking the + * certificate is done elsewhere. */ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_verify(ck->basekey, sig, data); +} + +static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, + BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + ssh_key_sign(ck->basekey, data, flags, bs); +} diff --git a/code/crypto/rsa.c b/code/crypto/rsa.c index ef832868..aa0e08a6 100644 --- a/code/crypto/rsa.c +++ b/code/crypto/rsa.c @@ -76,6 +76,21 @@ RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) return rsa; } +void duprsakey(RSAKey *dst, const RSAKey *src) +{ + dst->bits = src->bits; + dst->bytes = src->bytes; + dst->modulus = mp_copy(src->modulus); + dst->exponent = mp_copy(src->exponent); + dst->private_exponent = src->private_exponent ? + mp_copy(src->private_exponent) : NULL; + dst->p = mp_copy(src->p); + dst->q = mp_copy(src->q); + dst->iqmp = mp_copy(src->iqmp); + dst->comment = src->comment ? dupstr(src->comment) : NULL; + dst->sshk.vt = src->sshk.vt; +} + bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) { mp_int *b1, *b2; @@ -522,6 +537,12 @@ static key_components *rsa2_components(ssh_key *key) return rsa_components(rsa); } +static bool rsa2_has_private(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsa->private_exponent != NULL; +} + static void rsa2_public_blob(ssh_key *key, BinarySink *bs) { RSAKey *rsa = container_of(key, RSAKey, sshk); @@ -542,7 +563,7 @@ static void rsa2_private_blob(ssh_key *key, BinarySink *bs) } static ssh_key *rsa2_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) + ptrlen pub, ptrlen priv) { BinarySource src[1]; ssh_key *sshk; @@ -839,6 +860,23 @@ static char *rsa2_invalid(ssh_key *key, unsigned flags) return NULL; } +static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self) +{ + return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512; +} + +static const char *ssh_rsa_alternate_ssh_id( + const ssh_keyalg *self, unsigned flags) +{ + if (flags & SSH_AGENT_RSA_SHA2_512) + return ssh_rsa_sha512.ssh_id; + if (flags & SSH_AGENT_RSA_SHA2_256) + return ssh_rsa_sha256.ssh_id; + return self->ssh_id; +} + +static char *rsa2_alg_desc(const ssh_keyalg *self) { return dupstr("RSA"); } + static const struct ssh2_rsa_extra rsa_extra = { 0 }, rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, @@ -855,29 +893,36 @@ static const struct ssh2_rsa_extra .public_blob = rsa2_public_blob, \ .private_blob = rsa2_private_blob, \ .openssh_blob = rsa2_openssh_blob, \ + .has_private = rsa2_has_private, \ .cache_str = rsa2_cache_str, \ .components = rsa2_components, \ + .base_key = nullkey_base_key, \ .pubkey_bits = rsa2_pubkey_bits, \ + .alg_desc = rsa2_alg_desc, \ + .variable_size = nullkey_variable_size_yes, \ .cache_id = "rsa2" const ssh_keyalg ssh_rsa = { COMMON_KEYALG_FIELDS, .ssh_id = "ssh-rsa", - .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, + .supported_flags = ssh_rsa_supported_flags, + .alternate_ssh_id = ssh_rsa_alternate_ssh_id, .extra = &rsa_extra, }; const ssh_keyalg ssh_rsa_sha256 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-256", - .supported_flags = 0, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .extra = &rsa_sha256_extra, }; const ssh_keyalg ssh_rsa_sha512 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-512", - .supported_flags = 0, + .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .extra = &rsa_sha512_extra, }; @@ -1092,13 +1137,17 @@ static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; static const ssh_kex ssh_rsa_kex_sha1 = { - "rsa1024-sha1", NULL, KEXTYPE_RSA, - &ssh_sha1, &ssh_rsa_kex_extra_sha1, + .name = "rsa1024-sha1", + .main_type = KEXTYPE_RSA, + .hash = &ssh_sha1, + .extra = &ssh_rsa_kex_extra_sha1, }; static const ssh_kex ssh_rsa_kex_sha256 = { - "rsa2048-sha256", NULL, KEXTYPE_RSA, - &ssh_sha256, &ssh_rsa_kex_extra_sha256, + .name = "rsa2048-sha256", + .main_type = KEXTYPE_RSA, + .hash = &ssh_sha256, + .extra = &ssh_rsa_kex_extra_sha256, }; static const ssh_kex *const rsa_kex_list[] = { diff --git a/code/defs.h b/code/defs.h index f1bbf51a..286e0c96 100644 --- a/code/defs.h +++ b/code/defs.h @@ -116,6 +116,9 @@ typedef struct LogPolicyVtable LogPolicyVtable; typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; +typedef struct SeatDialogText SeatDialogText; +typedef struct SeatDialogTextItem SeatDialogTextItem; +typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions; typedef struct SeatPromptResult SeatPromptResult; typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state; @@ -143,6 +146,8 @@ typedef struct Channel Channel; typedef struct SshChannel SshChannel; typedef struct mainchan mainchan; +typedef struct CertExprBuilder CertExprBuilder; + typedef struct ssh_sharing_state ssh_sharing_state; typedef struct ssh_sharing_connstate ssh_sharing_connstate; typedef struct share_channel share_channel; @@ -169,12 +174,19 @@ typedef struct ssh_cipher ssh_cipher; typedef struct ssh2_ciphers ssh2_ciphers; typedef struct dh_ctx dh_ctx; typedef struct ecdh_key ecdh_key; +typedef struct ecdh_keyalg ecdh_keyalg; +typedef struct NTRUKeyPair NTRUKeyPair; +typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; typedef struct dlgparam dlgparam; +typedef struct dlgcontrol dlgcontrol; typedef struct settings_w settings_w; typedef struct settings_r settings_r; typedef struct settings_e settings_e; +typedef struct ca_options ca_options; +typedef struct host_ca host_ca; +typedef struct host_ca_enum host_ca_enum; typedef struct SessionSpecial SessionSpecial; @@ -243,4 +255,14 @@ struct unicode_data; #define CAT_INNER(x,y) x ## y #define CAT(x,y) CAT_INNER(x,y) +/* + * Structure shared between ssh.h and storage.h, giving strictness + * options relating to checking of an OpenSSH certificate. It's a bit + * cheaty to put something so specific in here, but more painful to + * put it in putty.h. + */ +struct ca_options { + bool permit_rsa_sha1, permit_rsa_sha256, permit_rsa_sha512; +}; + #endif /* PUTTY_DEFS_H */ diff --git a/code/dialog.c b/code/dialog.c index 7409daaa..b9306982 100644 --- a/code/dialog.c +++ b/code/dialog.c @@ -204,31 +204,31 @@ void *ctrl_alloc(struct controlbox *b, size_t size) return ctrl_alloc_with_free(b, size, ctrl_default_free); } -static union control *ctrl_new(struct controlset *s, int type, - intorptr helpctx, handler_fn handler, - intorptr context) +static dlgcontrol *ctrl_new(struct controlset *s, int type, + HelpCtx helpctx, handler_fn handler, + intorptr context) { - union control *c = snew(union control); + dlgcontrol *c = snew(dlgcontrol); sgrowarray(s->ctrls, s->ctrlsize, s->ncontrols); s->ctrls[s->ncontrols++] = c; /* * Fill in the standard fields. */ - c->generic.type = type; - c->generic.tabdelay = false; - c->generic.column = COLUMN_FIELD(0, s->ncolumns); - c->generic.helpctx = helpctx; - c->generic.handler = handler; - c->generic.context = context; - c->generic.label = NULL; - c->generic.align_next_to = NULL; + c->type = type; + c->delay_taborder = false; + c->column = COLUMN_FIELD(0, s->ncolumns); + c->helpctx = helpctx; + c->handler = handler; + c->context = context; + c->label = NULL; + c->align_next_to = NULL; return c; } /* `ncolumns' is followed by that many percentages, as integers. */ -union control *ctrl_columns(struct controlset *s, int ncolumns, ...) +dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...) { - union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, NULL_HELPCTX, NULL, P(NULL)); assert(s->ncolumns == 1 || ncolumns == 1); c->columns.ncols = ncolumns; s->ncolumns = ncolumns; @@ -246,33 +246,33 @@ union control *ctrl_columns(struct controlset *s, int ncolumns, ...) return c; } -union control *ctrl_editbox(struct controlset *s, const char *label, - char shortcut, int percentage, - intorptr helpctx, handler_fn handler, - intorptr context, intorptr context2) +dlgcontrol *ctrl_editbox(struct controlset *s, const char *label, + char shortcut, int percentage, + HelpCtx helpctx, handler_fn handler, + intorptr context, intorptr context2) { - union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->editbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; c->editbox.password = false; c->editbox.has_list = false; - c->editbox.context2 = context2; + c->context2 = context2; return c; } -union control *ctrl_combobox(struct controlset *s, const char *label, - char shortcut, int percentage, - intorptr helpctx, handler_fn handler, - intorptr context, intorptr context2) +dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, + char shortcut, int percentage, + HelpCtx helpctx, handler_fn handler, + intorptr context, intorptr context2) { - union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->editbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; c->editbox.password = false; c->editbox.has_list = true; - c->editbox.context2 = context2; + c->context2 = context2; return c; } @@ -282,14 +282,14 @@ union control *ctrl_combobox(struct controlset *s, const char *label, * title is expected to be followed by a shortcut _iff_ `shortcut' * is NO_SHORTCUT. */ -union control *ctrl_radiobuttons(struct controlset *s, const char *label, - char shortcut, int ncolumns, intorptr helpctx, +dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label, + char shortcut, int ncolumns, HelpCtx helpctx, handler_fn handler, intorptr context, ...) { va_list ap; int i; - union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); - c->radio.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->radio.shortcut = shortcut; c->radio.ncolumns = ncolumns; /* @@ -328,24 +328,24 @@ union control *ctrl_radiobuttons(struct controlset *s, const char *label, return c; } -union control *ctrl_pushbutton(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); - c->button.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->button.shortcut = shortcut; c->button.isdefault = false; c->button.iscancel = false; return c; } -union control *ctrl_listbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ c->listbox.draglist = false; @@ -357,12 +357,12 @@ union control *ctrl_listbox(struct controlset *s, const char *label, return c; } -union control *ctrl_droplist(struct controlset *s, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, + char shortcut, int percentage, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 0; /* means it's a drop-down list */ c->listbox.draglist = false; @@ -374,12 +374,12 @@ union control *ctrl_droplist(struct controlset *s, const char *label, return c; } -union control *ctrl_draglist(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ c->listbox.draglist = true; @@ -391,61 +391,63 @@ union control *ctrl_draglist(struct controlset *s, const char *label, return c; } -union control *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, + char shortcut, const char *filter, bool write, + const char *title, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); - c->fileselect.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->fileselect.shortcut = shortcut; c->fileselect.filter = filter; c->fileselect.for_writing = write; c->fileselect.title = dupstr(title); + c->fileselect.just_button = false; return c; } -union control *ctrl_fontsel(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); - c->fontselect.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->fontselect.shortcut = shortcut; return c; } -union control *ctrl_tabdelay(struct controlset *s, union control *ctrl) +dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl) { - union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, NULL_HELPCTX, NULL, P(NULL)); c->tabdelay.ctrl = ctrl; return c; } -union control *ctrl_text(struct controlset *s, const char *text, - intorptr helpctx) +dlgcontrol *ctrl_text(struct controlset *s, const char *text, + HelpCtx helpctx) { - union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); - c->text.label = dupstr(text); + dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); + c->label = dupstr(text); + c->text.wrap = true; return c; } -union control *ctrl_checkbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); - c->checkbox.label = label ? dupstr(label) : NULL; + dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); + c->label = label ? dupstr(label) : NULL; c->checkbox.shortcut = shortcut; return c; } -void ctrl_free(union control *ctrl) +void ctrl_free(dlgcontrol *ctrl) { int i; - sfree(ctrl->generic.label); - switch (ctrl->generic.type) { + sfree(ctrl->label); + switch (ctrl->type) { case CTRL_RADIO: for (i = 0; i < ctrl->radio.nbuttons; i++) sfree(ctrl->radio.buttons[i]); diff --git a/code/dialog.h b/code/dialog.h index 86ebfc20..ef9a9dfe 100644 --- a/code/dialog.h +++ b/code/dialog.h @@ -43,11 +43,12 @@ enum { * included with DEFINE_INTORPTR_FNS defined. This is a total pain, * but such is life. */ -typedef union { void *p; int i; } intorptr; +typedef union { void *p; const void *cp; int i; } intorptr; #ifndef INLINE intorptr I(int i); intorptr P(void *p); +intorptr CP(const void *p); #endif #if defined DEFINE_INTORPTR_FNS || defined INLINE @@ -58,6 +59,7 @@ intorptr P(void *p); #endif PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; } PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } +PREFIX intorptr CP(const void *p) { intorptr ret; ret.cp = p; return ret; } #undef PREFIX #endif @@ -73,8 +75,6 @@ PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } #define COLUMN_START(field) ( (field) & 0xFFFF ) #define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 ) -union control; - /* * The number of event types is being deliberately kept small, on * the grounds that not all platforms might be able to report a @@ -103,326 +103,346 @@ enum { EVENT_SELCHANGE, EVENT_CALLBACK }; -typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, +typedef void (*handler_fn)(dlgcontrol *ctrl, dlgparam *dp, void *data, int event); -#define STANDARD_PREFIX \ - int type; \ - char *label; \ - bool tabdelay; \ - int column; \ - handler_fn handler; \ - intorptr context; \ - intorptr helpctx; \ - union control *align_next_to +struct dlgcontrol { + /* + * Generic fields shared by all the control types. + */ + int type; + /* + * Every control except CTRL_COLUMNS has _some_ sort of label. By + * putting it in the `generic' union as well as everywhere else, + * we avoid having to have an irritating switch statement when we + * go through and deallocate all the memory in a config-box + * structure. + * + * Yes, this does mean that any non-NULL value in this field is + * expected to be dynamically allocated and freeable. + * + * For CTRL_COLUMNS, this field MUST be NULL. + */ + char *label; + /* + * If `delay_taborder' is true, it indicates that this particular + * control should not yet appear in the tab order. A subsequent + * CTRL_TABDELAY entry will place it. + */ + bool delay_taborder; + /* + * Indicate which column(s) this control occupies. This can be + * unpacked into starting column and column span by the COLUMN + * macros above. + */ + int column; + /* + * Most controls need to provide a function which gets called when + * that control's setting is changed, or when the control's + * setting needs initialising. + * + * The `data' parameter points to the writable data being modified + * as a result of the configuration activity; for example, the + * PuTTY `Conf' structure, although not necessarily. + * + * The `dlg' parameter is passed back to the platform- specific + * routines to read and write the actual control state. + */ + handler_fn handler; + /* + * Almost all of the above functions will find it useful to be + * able to store one or two pieces of `void *' or `int' data. + */ + intorptr context, context2; + /* + * For any control, we also allow the storage of a piece of data + * for use by context-sensitive help. For example, on Windows you + * can click the magic question mark and then click a control, and + * help for that control should spring up. Hence, here is a slot + * in which to store per-control data that a particular + * platform-specific driver can use to ensure it brings up the + * right piece of help text. + */ + HelpCtx helpctx; + /* + * Setting this to non-NULL coerces two or more controls to have + * their y-coordinates adjusted so that they can sit alongside + * each other and look nicely aligned, even if they're different + * heights. + * + * Set this field on later controls (in terms of order in the data + * structure), pointing back to earlier ones, so that when each + * control is instantiated, the referred-to one is already there + * to be referred to. + * + * Don't expect this to change the position of the _first_ + * control. Currently, the layout is done one control at a time, + * so that once the first control has been placed, the second one + * can't cause the first one to be retrospectively moved. + */ + dlgcontrol *align_next_to; -union control { /* - * The first possibility in this union is the generic header - * shared by all the structures, which we are therefore allowed - * to access through any one of them. + * Union of further fields specific to each control type. */ - struct { - int type; - /* - * Every control except CTRL_COLUMNS has _some_ sort of - * label. By putting it in the `generic' union as well as - * everywhere else, we avoid having to have an irritating - * switch statement when we go through and deallocate all - * the memory in a config-box structure. - * - * Yes, this does mean that any non-NULL value in this - * field is expected to be dynamically allocated and - * freeable. - * - * For CTRL_COLUMNS, this field MUST be NULL. - */ - char *label; - /* - * If `tabdelay' is non-zero, it indicates that this - * particular control should not yet appear in the tab - * order. A subsequent CTRL_TABDELAY entry will place it. - */ - bool tabdelay; - /* - * Indicate which column(s) this control occupies. This can - * be unpacked into starting column and column span by the - * COLUMN macros above. - */ - int column; - /* - * Most controls need to provide a function which gets - * called when that control's setting is changed, or when - * the control's setting needs initialising. - * - * The `data' parameter points to the writable data being - * modified as a result of the configuration activity; for - * example, the PuTTY `Conf' structure, although not - * necessarily. - * - * The `dlg' parameter is passed back to the platform- - * specific routines to read and write the actual control - * state. - */ - handler_fn handler; - /* - * Almost all of the above functions will find it useful to - * be able to store a piece of `void *' or `int' data. - */ - intorptr context; - /* - * For any control, we also allow the storage of a piece of - * data for use by context-sensitive help. For example, on - * Windows you can click the magic question mark and then - * click a control, and help for that control should spring - * up. Hence, here is a slot in which to store per-control - * data that a particular platform-specific driver can use - * to ensure it brings up the right piece of help text. - */ - intorptr helpctx; - /* - * Setting this to non-NULL coerces two controls to have their - * y-coordinates adjusted so that they can sit alongside each - * other and look nicely aligned, even if they're different - * heights. - * - * Set this field on the _second_ control of the pair (in - * terms of order in the data structure), so that when it's - * instantiated, the first one is already there to be referred - * to. - */ - union control *align_next_to; - } generic; - struct { - STANDARD_PREFIX; - union control *ctrl; - } tabdelay; - struct { - STANDARD_PREFIX; - } text; - struct { - STANDARD_PREFIX; - char shortcut; /* keyboard shortcut */ - /* - * Percentage of the dialog-box width used by the edit box. - * If this is set to 100, the label is on its own line; - * otherwise the label is on the same line as the box - * itself. - */ - int percentwidth; - bool password; /* details of input are hidden */ - /* - * A special case of the edit box is the combo box, which - * has a drop-down list built in. (Note that a _non_- - * editable drop-down list is done as a special case of a - * list box.) - * - * Don't try setting has_list and password on the same - * control; front ends are not required to support that - * combination. - */ - bool has_list; - /* - * Edit boxes tend to need two items of context, so here's - * a spare. - */ - intorptr context2; - } editbox; - struct { - STANDARD_PREFIX; - /* - * `shortcut' here is a single keyboard shortcut which is - * expected to select the whole group of radio buttons. It - * can be NO_SHORTCUT if required, and there is also a way - * to place individual shortcuts on each button; see below. - */ - char shortcut; - /* - * There are separate fields for `ncolumns' and `nbuttons' - * for several reasons. - * - * Firstly, we sometimes want the last of a set of buttons - * to have a longer label than the rest; we achieve this by - * setting `ncolumns' higher than `nbuttons', and the - * layout code is expected to understand that the final - * button should be given all the remaining space on the - * line. This sounds like a ludicrously specific special - * case (if we're doing this sort of thing, why not have - * the general ability to have a particular button span - * more than one column whether it's the last one or not?) - * but actually it's reasonably common for the sort of - * three-way control you get a lot of in PuTTY: `yes' - * versus `no' versus `some more complex way to decide'. - * - * Secondly, setting `nbuttons' higher than `ncolumns' lets - * us have more than one line of radio buttons for a single - * setting. A very important special case of this is - * setting `ncolumns' to 1, so that each button is on its - * own line. - */ - int ncolumns; - int nbuttons; - /* - * This points to a dynamically allocated array of `char *' - * pointers, each of which points to a dynamically - * allocated string. - */ - char **buttons; /* `nbuttons' button labels */ - /* - * This points to a dynamically allocated array of `char' - * giving the individual keyboard shortcuts for each radio - * button. The array may be NULL if none are required. - */ - char *shortcuts; /* `nbuttons' shortcuts; may be NULL */ - /* - * This points to a dynamically allocated array of - * intorptr, giving helpful data for each button. - */ - intorptr *buttondata; /* `nbuttons' entries; may be NULL */ - } radio; - struct { - STANDARD_PREFIX; - char shortcut; - } checkbox; - struct { - STANDARD_PREFIX; - char shortcut; - /* - * At least Windows has the concept of a `default push - * button', which gets implicitly pressed when you hit - * Return even if it doesn't have the input focus. - */ - bool isdefault; - /* - * Also, the reverse of this: a default cancel-type button, - * which is implicitly pressed when you hit Escape. - */ - bool iscancel; - } button; - struct { - STANDARD_PREFIX; - char shortcut; /* keyboard shortcut */ - /* - * Height of the list box, in approximate number of lines. - * If this is zero, the list is a drop-down list. - */ - int height; /* height in lines */ - /* - * If this is set, the list elements can be reordered by - * the user (by drag-and-drop or by Up and Down buttons, - * whatever the per-platform implementation feels - * comfortable with). This is not guaranteed to work on a - * drop-down list, so don't try it! - */ - bool draglist; - /* - * If this is non-zero, the list can have more than one - * element selected at a time. This is not guaranteed to - * work on a drop-down list, so don't try it! - * - * Different non-zero values request slightly different - * types of multi-selection (this may well be meaningful - * only in GTK, so everyone else can ignore it if they - * want). 1 means the list box expects to have individual - * items selected, whereas 2 means it expects the user to - * want to select a large contiguous range at a time. - */ - int multisel; - /* - * Percentage of the dialog-box width used by the list box. - * If this is set to 100, the label is on its own line; - * otherwise the label is on the same line as the box - * itself. Setting this to anything other than 100 is not - * guaranteed to work on a _non_-drop-down list, so don't - * try it! - */ - int percentwidth; - /* - * Some list boxes contain strings that contain tab - * characters. If `ncols' is greater than 0, then - * `percentages' is expected to be non-zero and to contain - * the respective widths of `ncols' columns, which together - * will exactly fit the width of the list box. Otherwise - * `percentages' must be NULL. - * - * There should never be more than one column in a - * drop-down list (one with height==0), because front ends - * may have to implement it as a special case of an - * editable combo box. - */ - int ncols; /* number of columns */ - int *percentages; /* % width of each column */ - /* - * Flag which can be set to false to suppress the horizontal - * scroll bar if a list box entry goes off the right-hand - * side. - */ - bool hscroll; - } listbox; - struct { - STANDARD_PREFIX; - char shortcut; - /* - * `filter' dictates what type of files will be selected by - * default; for example, when selecting private key files - * the file selector would do well to only show .PPK files - * (on those systems where this is the chosen extension). - * - * The precise contents of `filter' are platform-defined, - * unfortunately. The special value NULL means `all files' - * and is always a valid fallback. - * - * Unlike almost all strings in this structure, this value - * is NOT expected to require freeing (although of course - * you can always use ctrl_alloc if you do need to create - * one on the fly). This is because the likely mode of use - * is to define string constants in a platform-specific - * header file, and directly reference those. Or worse, a - * particular platform might choose to cast integers into - * this pointer type... - */ - char const *filter; - /* - * Some systems like to know whether a file selector is - * choosing a file to read or one to write (and possibly - * create). - */ - bool for_writing; - /* - * On at least some platforms, the file selector is a - * separate dialog box, and contains a user-settable title. - * - * This value _is_ expected to require freeing. - */ - char *title; - } fileselect; - struct { - /* In this variant, `label' MUST be NULL. */ - STANDARD_PREFIX; - int ncols; /* number of columns */ - int *percentages; /* % width of each column */ - /* - * Every time this control type appears, exactly one of - * `ncols' and the previous number of columns MUST be one. - * Attempting to allow a seamless transition from a four- - * to a five-column layout, for example, would be way more - * trouble than it was worth. If you must lay things out - * like that, define eight unevenly sized columns and use - * column-spanning a lot. But better still, just don't. - * - * `percentages' may be NULL if ncols==1, to save space. - */ - } columns; - struct { - STANDARD_PREFIX; - char shortcut; - } fontselect; + union { + struct { /* for CTRL_TABDELAY */ + dlgcontrol *ctrl; + } tabdelay; + struct { /* for CTRL_EDITBOX */ + char shortcut; /* keyboard shortcut */ + /* + * Percentage of the dialog-box width used by the edit + * box. If this is set to 100, the label is on its own + * line; otherwise the label is on the same line as the + * box itself. + */ + int percentwidth; + bool password; /* details of input are hidden */ + /* + * A special case of the edit box is the combo box, which + * has a drop-down list built in. (Note that a _non_- + * editable drop-down list is done as a special case of a + * list box.) + * + * Don't try setting has_list and password on the same + * control; front ends are not required to support that + * combination. + */ + bool has_list; + } editbox; + struct { /* for CTRL_RADIO */ + /* + * `shortcut' here is a single keyboard shortcut which is + * expected to select the whole group of radio buttons. It + * can be NO_SHORTCUT if required, and there is also a way + * to place individual shortcuts on each button; see + * below. + */ + char shortcut; + /* + * There are separate fields for `ncolumns' and `nbuttons' + * for several reasons. + * + * Firstly, we sometimes want the last of a set of buttons + * to have a longer label than the rest; we achieve this + * by setting `ncolumns' higher than `nbuttons', and the + * layout code is expected to understand that the final + * button should be given all the remaining space on the + * line. This sounds like a ludicrously specific special + * case (if we're doing this sort of thing, why not have + * the general ability to have a particular button span + * more than one column whether it's the last one or not?) + * but actually it's reasonably common for the sort of + * three-way control you get a lot of in PuTTY: `yes' + * versus `no' versus `some more complex way to decide'. + * + * Secondly, setting `nbuttons' higher than `ncolumns' + * lets us have more than one line of radio buttons for a + * single setting. A very important special case of this + * is setting `ncolumns' to 1, so that each button is on + * its own line. + */ + int ncolumns; + int nbuttons; + /* + * This points to a dynamically allocated array of `char *' + * pointers, each of which points to a dynamically + * allocated string. + */ + char **buttons; /* `nbuttons' button labels */ + /* + * This points to a dynamically allocated array of `char' + * giving the individual keyboard shortcuts for each radio + * button. The array may be NULL if none are required. + */ + char *shortcuts; /* `nbuttons' shortcuts; may be NULL */ + /* + * This points to a dynamically allocated array of + * intorptr, giving helpful data for each button. + */ + intorptr *buttondata; /* `nbuttons' entries; may be NULL */ + } radio; + struct { /* for CTRL_CHECKBOX */ + char shortcut; + } checkbox; + struct { /* for CTRL_BUTTON */ + char shortcut; + /* + * At least Windows has the concept of a `default push + * button', which gets implicitly pressed when you hit + * Return even if it doesn't have the input focus. + */ + bool isdefault; + /* + * Also, the reverse of this: a default cancel-type + * button, which is implicitly pressed when you hit + * Escape. + */ + bool iscancel; + } button; + struct { /* for CTRL_LISTBOX */ + char shortcut; /* keyboard shortcut */ + /* + * Height of the list box, in approximate number of lines. + * If this is zero, the list is a drop-down list. + */ + int height; /* height in lines */ + /* + * If this is set, the list elements can be reordered by + * the user (by drag-and-drop or by Up and Down buttons, + * whatever the per-platform implementation feels + * comfortable with). This is not guaranteed to work on a + * drop-down list, so don't try it! + */ + bool draglist; + /* + * If this is non-zero, the list can have more than one + * element selected at a time. This is not guaranteed to + * work on a drop-down list, so don't try it! + * + * Different non-zero values request slightly different + * types of multi-selection (this may well be meaningful + * only in GTK, so everyone else can ignore it if they + * want). 1 means the list box expects to have individual + * items selected, whereas 2 means it expects the user to + * want to select a large contiguous range at a time. + */ + int multisel; + /* + * Percentage of the dialog-box width used by the list + * box. If this is set to 100, the label is on its own + * line; otherwise the label is on the same line as the + * box itself. Setting this to anything other than 100 is + * not guaranteed to work on a _non_-drop-down list, so + * don't try it! + */ + int percentwidth; + /* + * Some list boxes contain strings that contain tab + * characters. If `ncols' is greater than 0, then + * `percentages' is expected to be non-zero and to contain + * the respective widths of `ncols' columns, which + * together will exactly fit the width of the list box. + * Otherwise `percentages' must be NULL. + * + * There should never be more than one column in a + * drop-down list (one with height==0), because front ends + * may have to implement it as a special case of an + * editable combo box. + */ + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Flag which can be set to false to suppress the + * horizontal scroll bar if a list box entry goes off the + * right-hand side. + */ + bool hscroll; + } listbox; + struct { /* for CTRL_FILESELECT */ + char shortcut; + /* + * `filter' dictates what type of files will be selected + * by default; for example, when selecting private key + * files the file selector would do well to only show .PPK + * files (on those systems where this is the chosen + * extension). + * + * The precise contents of `filter' are platform-defined, + * unfortunately. The special value NULL means `all files' + * and is always a valid fallback. + * + * Unlike almost all strings in this structure, this value + * is NOT expected to require freeing (although of course + * you can always use ctrl_alloc if you do need to create + * one on the fly). This is because the likely mode of use + * is to define string constants in a platform-specific + * header file, and directly reference those. Or worse, a + * particular platform might choose to cast integers into + * this pointer type... + */ + char const *filter; + /* + * Some systems like to know whether a file selector is + * choosing a file to read or one to write (and possibly + * create). + */ + bool for_writing; + /* + * On at least some platforms, the file selector is a + * separate dialog box, and contains a user-settable + * title. + * + * This value _is_ expected to require freeing. + */ + char *title; + /* + * Reduce the file selector to just a single browse + * button. + * + * Normally, a file selector is used to set a config + * option that consists of a file name, so that that file + * will be read or written at run time. In that situation, + * it makes sense to have an edit box showing the + * currently selected file name, and a button to change it + * interactively. + * + * But occasionally a file selector is used to load a file + * _during_ configuration. For example, host CA public + * keys are entered directly into the configuration as + * strings, not stored by reference to a filename; but if + * you have one in a file, you want to be able to load it + * during the lifetime of the CA config box rather than + * awkwardly copy-pasting it. So in that case you just + * want a 'pop up a file chooser' button, and when that + * delivers a file name, you'll deal with it there and + * then and write some other thing (like the file's + * contents) into a nearby edit box. + * + * If you set this flag, then you may not call + * dlg_filesel_set on the file selector at all, because it + * doesn't store a filename. And you can only call + * dlg_filesel_get on it in the handler for EVENT_ACTION, + * which is what will be sent to you when the user has + * used it to choose a filename. + */ + bool just_button; + } fileselect; + struct { /* for CTRL_COLUMNS */ + /* In this variant, `label' MUST be NULL. */ + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Every time this control type appears, exactly one of + * `ncols' and the previous number of columns MUST be one. + * Attempting to allow a seamless transition from a four- + * to a five-column layout, for example, would be way more + * trouble than it was worth. If you must lay things out + * like that, define eight unevenly sized columns and use + * column-spanning a lot. But better still, just don't. + * + * `percentages' may be NULL if ncols==1, to save space. + */ + } columns; + struct { /* for CTRL_FONTSELECT */ + char shortcut; + } fontselect; + struct { /* for CTRL_TEXT */ + /* + * If this is true (the default), the text will wrap on to + * multiple lines. If false, it will stay on the same + * line, with a horizontal scrollbar if necessary. + */ + bool wrap; + } text; + }; }; #undef STANDARD_PREFIX /* - * `controlset' is a container holding an array of `union control' + * `controlset' is a container holding an array of `dlgcontrol' * structures, together with a panel name and a title for the whole * set. In Windows and any similar-looking GUI, each `controlset' * in the config will be a container box within a panel. @@ -435,9 +455,9 @@ struct controlset { char *boxname; /* internal short name of controlset */ char *boxtitle; /* title of container box */ int ncolumns; /* current no. of columns at bottom */ - size_t ncontrols; /* number of `union control' in array */ + size_t ncontrols; /* number of `dlgcontrol' in array */ size_t ctrlsize; /* allocated size of array */ - union control **ctrls; /* actual array */ + dlgcontrol **ctrls; /* actual array */ }; typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */ @@ -471,7 +491,7 @@ struct controlset *ctrl_getset(struct controlbox *, const char *path, const char *name, const char *boxtitle); void ctrl_free_set(struct controlset *); -void ctrl_free(union control *); +void ctrl_free(dlgcontrol *); /* * This function works like `malloc', but the memory it returns @@ -490,73 +510,77 @@ void *ctrl_alloc_with_free(struct controlbox *b, size_t size, ctrl_freefn_t freefunc); /* - * Individual routines to create `union control' structures in a controlset. + * Individual routines to create `dlgcontrol' structures in a controlset. * * Most of these routines allow the most common fields to be set * directly, and put default values in the rest. Each one returns a - * pointer to the `union control' it created, so that final tweaks + * pointer to the `dlgcontrol' it created, so that final tweaks * can be made. */ /* `ncolumns' is followed by that many percentages, as integers. */ -union control *ctrl_columns(struct controlset *, int ncolumns, ...); -union control *ctrl_editbox(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, - intorptr context, intorptr context2); -union control *ctrl_combobox(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, - intorptr context, intorptr context2); +dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...); +dlgcontrol *ctrl_editbox(struct controlset *, const char *label, + char shortcut, int percentage, HelpCtx helpctx, + handler_fn handler, + intorptr context, intorptr context2); +dlgcontrol *ctrl_combobox(struct controlset *, const char *label, + char shortcut, int percentage, HelpCtx helpctx, + handler_fn handler, + intorptr context, intorptr context2); /* * `ncolumns' is followed by (alternately) radio button titles and * intorptrs, until a NULL in place of a title string is seen. Each * title is expected to be followed by a shortcut _iff_ `shortcut' * is NO_SHORTCUT. */ -union control *ctrl_radiobuttons(struct controlset *, const char *label, - char shortcut, int ncolumns, intorptr helpctx, +dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label, + char shortcut, int ncolumns, HelpCtx helpctx, handler_fn handler, intorptr context, ...); -union control *ctrl_pushbutton(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_listbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, +#define ctrl_radiobuttons(...) \ + ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL) +dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); -union control *ctrl_droplist(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_draglist(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_filesel(struct controlset *, const char *label, - char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_fontsel(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_text(struct controlset *, const char *text, - intorptr helpctx); -union control *ctrl_checkbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_tabdelay(struct controlset *, union control *); +dlgcontrol *ctrl_listbox(struct controlset *, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_droplist(struct controlset *, const char *label, + char shortcut, int percentage, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_draglist(struct controlset *, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_filesel(struct controlset *, const char *label, + char shortcut, const char *filter, bool write, + const char *title, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_text(struct controlset *, const char *text, + HelpCtx helpctx); +dlgcontrol *ctrl_checkbox(struct controlset *, const char *label, + char shortcut, HelpCtx helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *); /* * Routines the platform-independent dialog code can call to read * and write the values of controls. */ -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton); -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp); -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked); -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp); -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text); -char *dlg_editbox_get(union control *ctrl, dlgparam *dp); /* result must be freed by caller */ +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton); +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked); +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len); /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp); -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index); -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text); +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp); +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text); /* * Each listbox entry may have a numeric id associated with it. * Note that some front ends only permit a string to be stored at @@ -564,44 +588,44 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text); * strings in any listbox then you MUST not assign them different * IDs and expect to get meaningful results back. */ -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id); -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index); +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index); /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp); -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index); -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index); -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text); -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn); -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp); -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn); -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp); +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp); +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn); +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn); +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp); /* * Bracketing a large set of updates in these two functions will * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, dlgparam *dp); -void dlg_update_done(union control *ctrl, dlgparam *dp); +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp); +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp); /* * Set input focus into a particular control. */ -void dlg_set_focus(union control *ctrl, dlgparam *dp); +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp); /* * Change the label text on a control. */ -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text); +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text); /* * Return the `ctrl' structure for the most recent control that had * the input focus apart from the one mentioned. This is NOT * GUARANTEED to work on all platforms, so don't base any critical * functionality on it! */ -union control *dlg_last_focused(union control *ctrl, dlgparam *dp); +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp); /* * Find out whether a particular control is currently visible. */ -bool dlg_is_visible(union control *ctrl, dlgparam *dp); +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp); /* * During event processing, you might well want to give an error * indication to the user. dlg_beep() is a quick and easy generic @@ -629,9 +653,9 @@ void dlg_end(dlgparam *dp, int value); * dlg_coloursel_start() accepts an RGB triple which is used to * initialise the colour selector to its starting value. */ -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b); -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b); /* @@ -643,7 +667,7 @@ bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, * If `ctrl' is NULL, _all_ controls in the dialog get refreshed * (for loading or saving entire sets of settings). */ -void dlg_refresh(union control *ctrl, dlgparam *dp); +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp); /* * Standard helper functions for reading a controlbox structure. @@ -663,3 +687,9 @@ int ctrl_path_elements(const char *path); /* Return the number of matching path elements at the starts of p1 and p2, * or INT_MAX if the paths are identical. */ int ctrl_path_compare(const char *p1, const char *p2); + +/* + * Normalise the align_next_to fields in a controlset so that they + * form a backwards linked list. + */ +void ctrlset_normalise_aligns(struct controlset *s); diff --git a/code/doc/CMakeLists.txt b/code/doc/CMakeLists.txt new file mode 100644 index 00000000..79b1ba1f --- /dev/null +++ b/code/doc/CMakeLists.txt @@ -0,0 +1,182 @@ +cmake_minimum_required(VERSION 3.7) +project(putty-documentation LANGUAGES) + +# This build script can be run standalone, or included as a +# subdirectory of the main PuTTY cmake build system. If the latter, a +# couple of things change: it has to set variables telling the rest of +# the build system what manpages are available to be installed, and it +# will change whether the 'make doc' target is included in 'make all'. + +include(FindGit) +include(FindPerl) +find_program(HALIBUT halibut) + +set(doc_outputs) +set(manpage_outputs) + +if(HALIBUT AND PERL_EXECUTABLE) + # Build the main manual, which requires not only Halibut, but also + # Perl to run licence.pl to generate the copyright and licence + # sections from the master data outside this directory. + + # If this is a source archive in which a fixed version.but was + # provided, use that. Otherwise, infer one from the git checkout (if + # possible). + + set(manual_dependencies) # extra target names to depend on + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/version.but) + set(VERSION_BUT ${CMAKE_CURRENT_SOURCE_DIR}/version.but) + else() + set(VERSION_BUT ${CMAKE_CURRENT_BINARY_DIR}/cmake_version.but) + set(INTERMEDIATE_VERSION_BUT ${VERSION_BUT}.tmp) + add_custom_target(check_git_commit_for_doc + BYPRODUCTS ${INTERMEDIATE_VERSION_BUT} + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DOUTPUT_FILE=${INTERMEDIATE_VERSION_BUT} + -DOUTPUT_TYPE=halibut + -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. + COMMENT "Checking current git commit") + add_custom_target(cmake_version_but + BYPRODUCTS ${VERSION_BUT} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${INTERMEDIATE_VERSION_BUT} ${VERSION_BUT} + DEPENDS check_git_commit_for_doc ${INTERMEDIATE_VERSION_BUT} + COMMENT "Updating cmake_version.but") + set(manual_dependencies ${manual_dependencies} cmake_version_but) + endif() + + add_custom_target(copy_but + BYPRODUCTS copy.but + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl + --copyrightdoc -o copy.but + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) + add_custom_target(licence_but + BYPRODUCTS licence.but + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl + --licencedoc -o licence.but + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) + set(manual_dependencies ${manual_dependencies} copy_but licence_but) + + set(manual_sources + ${CMAKE_CURRENT_BINARY_DIR}/copy.but + ${CMAKE_CURRENT_SOURCE_DIR}/blurb.but + ${CMAKE_CURRENT_SOURCE_DIR}/intro.but + ${CMAKE_CURRENT_SOURCE_DIR}/gs.but + ${CMAKE_CURRENT_SOURCE_DIR}/using.but + ${CMAKE_CURRENT_SOURCE_DIR}/config.but + ${CMAKE_CURRENT_SOURCE_DIR}/pscp.but + ${CMAKE_CURRENT_SOURCE_DIR}/psftp.but + ${CMAKE_CURRENT_SOURCE_DIR}/plink.but + ${CMAKE_CURRENT_SOURCE_DIR}/pubkey.but + ${CMAKE_CURRENT_SOURCE_DIR}/pageant.but + ${CMAKE_CURRENT_SOURCE_DIR}/errors.but + ${CMAKE_CURRENT_SOURCE_DIR}/faq.but + ${CMAKE_CURRENT_SOURCE_DIR}/feedback.but + ${CMAKE_CURRENT_SOURCE_DIR}/pubkeyfmt.but + ${CMAKE_CURRENT_BINARY_DIR}/licence.but + ${CMAKE_CURRENT_SOURCE_DIR}/udp.but + ${CMAKE_CURRENT_SOURCE_DIR}/pgpkeys.but + ${CMAKE_CURRENT_SOURCE_DIR}/sshnames.but + ${CMAKE_CURRENT_SOURCE_DIR}/authplugin.but + ${CMAKE_CURRENT_SOURCE_DIR}/index.but + ${VERSION_BUT}) + + # The HTML manual goes in a subdirectory, for convenience. + set(html_dir ${CMAKE_CURRENT_BINARY_DIR}/html) + file(MAKE_DIRECTORY ${html_dir}) + add_custom_command(OUTPUT ${html_dir}/index.html + COMMAND ${HALIBUT} --html ${manual_sources} + WORKING_DIRECTORY ${html_dir} + DEPENDS ${manual_sources} ${manual_dependencies}) + list(APPEND doc_outputs ${html_dir}/index.html) + + # Windows help. + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/chmextra.but + "\\cfg{chm-extra-file}{${CMAKE_CURRENT_SOURCE_DIR}/chm.css}{chm.css}\n") + add_custom_command(OUTPUT putty.chm + COMMAND ${HALIBUT} --chm chmextra.but ${manual_sources} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${manual_sources} ${manual_dependencies}) + list(APPEND doc_outputs putty.chm) + + # Plain text. + add_custom_command(OUTPUT puttydoc.txt + COMMAND ${HALIBUT} --text ${manual_sources} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${manual_sources} ${manual_dependencies}) + list(APPEND doc_outputs puttydoc.txt) +endif() + +macro(register_manpage title section) + list(APPEND manpage_outputs ${title}.${section}) + if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # Only set this variable if there _is_ a parent scope. + set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) + endif() +endmacro() + +if(NOT HALIBUT) + # If we don't have Halibut available to rebuild the man pages from + # source, we must check whether the build and source directories + # correspond, so as to suppress the build rules that copy them from + # the source dir to the build dir. (Otherwise, someone unpacking + # putty-src.zip and building on a system without Halibut will find + # that there's a circular dependency in the makefile, which at least + # Ninja complains about.) + get_filename_component(DOCBUILDDIR ${CMAKE_CURRENT_BINARY_DIR} REALPATH) + get_filename_component(DOCSRCDIR ${CMAKE_CURRENT_SOURCE_DIR} REALPATH) +endif() + +macro(manpage title section) + if(HALIBUT) + add_custom_command(OUTPUT ${title}.${section} + COMMAND ${HALIBUT} --man=${title}.${section} + ${CMAKE_CURRENT_SOURCE_DIR}/mancfg.but + ${CMAKE_CURRENT_SOURCE_DIR}/man-${title}.but + DEPENDS + mancfg.but man-${title}.but) + register_manpage(${title} ${section}) + elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) + # Our tarballs include prebuilt man pages in the source tree, so + # they can be installed from there even if Halibut isn't available. + if(NOT (DOCBUILDDIR STREQUAL DOCSRCDIR)) + # Iff the build tree isn't the source tree, they'll need copying + # to the build tree first. + add_custom_command(OUTPUT ${title}.${section} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) + endif() + register_manpage(${title} ${section}) + endif() +endmacro() + +manpage(putty 1) +manpage(puttygen 1) +manpage(plink 1) +manpage(pscp 1) +manpage(psftp 1) +manpage(puttytel 1) +manpage(pterm 1) +manpage(pageant 1) +manpage(psocks 1) +manpage(psusan 1) + +add_custom_target(manpages ALL DEPENDS ${manpage_outputs}) +add_custom_target(doc DEPENDS ${doc_outputs} manpages) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # If we're doing a cmake from just the doc subdir, we expect the + # user to want to make all the documentation, including HTML and so + # forth. (What else would be the point?) + # + # But if we're included from the main makefile, then by default we + # only make the man pages (which are necessary for 'make install'), + # and we leave everything else to a separate 'make doc' target which + # the user can invoke if they need to. + add_custom_target(doc-default ALL DEPENDS doc) +endif() diff --git a/code/doc/authplugin.but b/code/doc/authplugin.but new file mode 100644 index 00000000..41bc0daa --- /dev/null +++ b/code/doc/authplugin.but @@ -0,0 +1,519 @@ +\A{authplugin} PuTTY authentication plugin protocol + +This appendix contains the specification for the protocol spoken over +local IPC between PuTTY and an authentication helper plugin. + +If you already have an authentication plugin and want to configure +PuTTY to use it, see \k{config-ssh-authplugin} for how to do that. +This appendix is for people writing new authentication plugins. + +\H{authplugin-req} Requirements + +The following requirements informed the specification of this protocol. + +\s{Automate keyboard-interactive authentication.} We're motivated in +the first place by the observation that the general SSH userauth +method \cq{keyboard-interactive} (defined in \k{authplugin-ref-ki}) +can be used for many kinds of challenge/response or one-time-password +styles of authentication, and in more than one of those, the necessary +responses might be obtained from an auxiliary network connection, such +as an HTTPS transaction. So it's useful if a user doesn't have to +manually copy-type or copy-paste from their web browser into their SSH +client, but instead, the process can be automated. + +\s{Be able to pass prompts on to the user.} On the other hand, some +userauth methods can be only \e{partially} automated; some of the +server's prompts might still require human input. Also, the plugin +automating the authentication might need to ask its own questions that +are not provided by the SSH server. (For example, \q{please enter the +master key that the real response will be generated by hashing}.) So +after the plugin intercepts the server's questions, it needs to be +able to ask its own questions of the user, which may or may not be the +same questions sent by the server. + +\s{Allow automatic generation of the username.} Sometimes, the +authentication method comes with a mechanism for discovering the +username to be used in the SSH login. So the plugin has to start up +early enough that the client hasn't committed to a username yet. + +\s{Future expansion route to other SSH userauth flavours.} The initial +motivation for this protocol is specific to keyboard-interactive. But +other SSH authentication methods exist, and they may also benefit from +automation in future. We're making no attempt here to predict what +those methods might be or how they might be automated, but we do need +to leave a space where they can be slotted in later if necessary. + +\s{Minimal information loss.} Keyboard-interactive prompts and replies +should be passed to and from the plugin in a form as close as possible +to the way they look on the wire in SSH itself. Therefore, the +protocol resembles SSH in its data formats and marshalling (instead +of, for example, translating from SSH binary packet style to another +well-known format such as JSON, which would introduce edge cases in +character encoding). + +\s{Half-duplex.} Simultaneously trying to read one I/O stream and +write another adds a lot of complexity to software. It becomes +necessary to have an organised event loop containing \cw{select} or +\cw{WaitForMultipleObjects} or similar, which can invoke the handler +for whichever event happens soonest. There's no need to add that +complexity in an application like this, which isn't transferring large +amounts of bulk data or multiplexing unrelated activities. So, to keep +life simple for plugin authors, we set the ground rule that it must +always be 100% clear which side is supposed to be sending a message +next. That way, the plugin can be written as sequential code +progressing through the protocol, making simple read and write calls +to receive or send each message. + +\s{Communicate success/failure, to facilitate caching in the plugin.} +A plugin might want to cache recently used data for next time, but +only in the case where authentication using that data was actually +successful. So the client has to tell the plugin what the outcome was, +if it's known. (But this is best-effort only. Obviously the plugin +cannot \e{depend} on hearing the answer, because any IPC protocol at +all carries the risk that the other end might crash or be killed by +things outside its control.) + +\H{authplugin-transport} Transport and configuration + +Plugins are executable programs on the client platform. + +The SSH client must be manually configured to use a plugin for a +particular connection. The configuration takes the form of a command +line, including the location of the plugin executable, and optionally +command-line arguments that are meaningful to the particular plugin. + +The client invokes the plugin as a subprocess, passing it a pair of +8-bit-clean pipes as its standard input and output. On those pipes, +the client and plugin will communicate via the protocol specified +below. + +\H{authplugin-formats} Data formats and marshalling + +This protocol borrows the low-level data formatting from SSH itself, +in particular the following wire encodings from +\k{authplugin-ref-arch} section 5: + +\dt \s{byte} + +\dd An integer between 0 and 0xFF inclusive, transmitted as a single +byte of binary data. + +\dt \s{boolean} + +\dd The values \q{true} or \q{false}, transmitted as the bytes 1 and 0 +respectively. + +\dt \s{uint32} + +\dd An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4 +bytes of binary data, in big-endian (\q{network}) byte order. + +\dt \s{string} + +\dd A sequence of bytes, preceded by a \s{uint32} giving the number of +bytes in the sequence. The length field does not include itself. For +example, the empty string is represented by four zero bytes (the +\s{uint32} encoding of 0); the string "AB" is represented by the six +bytes 0,0,0,2,'A','B'. + +Unlike SSH itself, the protocol spoken between the client and the +plugin is unencrypted, because local inter-process pipes are assumed +to be secured by the OS kernel. So the binary packet protocol is much +simpler than SSH proper, and is similar to SFTP and the OpenSSH agent +protocol. + +The data sent in each direction of the conversation consists of a +sequence of \s{messages} exchanged between the SSH client and the +plugin. Each message is encoded as a \s{string}. The contents of the +string begin with a \s{byte} giving the message type, which determines +the format of the rest of the message. + +\H{authplugin-version} Protocol versioning + +This protocol itself is versioned. At connection setup, the client +states the highest version number it knows how to speak, and then the +plugin responds by choosing the version number that will actually be +spoken (which may not be higher than the client's value). + +Including a version number makes it possible to make breaking changes +to the protocol later. + +Even version numbers represent released versions of this spec. Odd +numbers represent drafts or development versions in between releases. +A client and plugin negotiating an odd version number are not +guaranteed to interoperate; the developer testing the combination is +responsible for ensuring the two are compatible. + +This document describes version 2 of the protocol, the first released +version. (The initial drafts had version 1.) + +\H{authplugin-overview} Overview and sequence of events + +At the very beginning of the user authentication phase of SSH, the +client launches the plugin subprocess, if one is configured. It +immediately sends the \cw{PLUGIN_INIT} message, telling the plugin +some initial information about where the SSH connection is to. + +The plugin responds with \cw{PLUGIN_INIT_RESPONSE}, which may +optionally tell the SSH client what username to use. + +The client begins trying to authenticate with the SSH server in the +usual way, using the username provided by the plugin (if any) or +alternatively one obtained via its normal (non-plugin) policy. + +The client follows its normal policy for selecting authentication +methods to attempt. If it chooses a method that this protocol does not +cover, then the client will perform that method in its own way without +consulting the plugin. + +However, if the client and server decide to attempt a method that this +protocol \e{does} cover, then the client sends \cw{PLUGIN_PROTOCOL} +specifying the SSH protocol id for the authentication method being +used. The plugin responds with \cw{PLUGIN_PROTOCOL_ACCEPT} if it's +willing to assist with this auth method, or +\cw{PLUGIN_PROTOCOL_REJECT} if it isn't. + +If the plugin sends \cw{PLUGIN_PROTOCOL_REJECT}, then the client will +proceed as if the plugin were not present. Later, if another auth +method is negotiated (either because this one failed, or because it +succeeded but the server wants multiple auth methods), the client may +send a further \cw{PLUGIN_PROTOCOL} and try again. + +If the plugin sends \cw{PLUGIN_PROTOCOL_ACCEPT}, then a protocol +segment begins that is specific to that auth method, terminating in +either \cw{PLUGIN_AUTH_SUCCESS} or \cw{PLUGIN_AUTH_FAILURE}. After +that, again, the client may send a further \cw{PLUGIN_PROTOCOL}. + +Currently the only supported method is \cq{keyboard-interactive}, +defined in \k{authplugin-ref-ki}. Once the client has announced this +to the server, the followup protocol is as follows: + +Each time the server sends an \cw{SSH_MSG_USERAUTH_INFO_REQUEST} +message requesting authentication responses from the user, the SSH +client translates the message into \cw{PLUGIN_KI_SERVER_REQUEST} and +passes it on to the plugin. + +At this point, the plugin may optionally send back +\cw{PLUGIN_KI_USER_REQUEST} containing prompts to be presented to the +actual user. The client will reply with a matching +\cw{PLUGIN_KI_USER_RESPONSE} after asking the user to reply to the +question(s) in the request message. The plugin can repeat this cycle +multiple times. + +Once the plugin has all the information it needs to respond to the +server's authentication prompts, it sends \cw{PLUGIN_KI_SERVER_RESPONSE} +back to the client, which translates it into +\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} to send on to the server. + +After that, as described in \k{authplugin-ref-ki}, the server is free +to accept authentication, reject it, or send another +\cw{SSH_MSG_USERAUTH_INFO_REQUEST}. Each +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} is dealt with in the same way as +above. + +If the server terminates keyboard-interactive authentication with +\cw{SSH_MSG_USERAUTH_SUCCESS} or \cw{SSH_MSG_USERAUTH_FAILURE}, the +client informs the plugin by sending either \cw{PLUGIN_AUTH_SUCCESS} +or \cw{PLUGIN_AUTH_FAILURE}. \cw{PLUGIN_AUTH_SUCCESS} is sent when +\e{that particular authentication method} was successful, regardless +of whether the SSH server chooses to request further authentication +afterwards: in particular, \cw{SSH_MSG_USERAUTH_FAILURE} with the +\q{partial success} flag (see \k{authplugin-ref-userauth} section 5.1) translates +into \cw{PLUGIN_AUTH_SUCCESS}. + +The plugin's standard input will close when the client no longer +requires the plugin's services, for any reason. This could be because +authentication is complete (with overall success or overall failure), +or because the user has manually aborted the session in +mid-authentication, or because the client crashed. + +\H{authplugin-messages} Message formats + +This section describes the format of every message in the protocol. + +As described in \k{authplugin-formats}, every message starts with the same two +fields: + +\b \s{uint32}: overall length of the message + +\b \s{byte}: message type. + +The length field does not include itself, but does include the type +code. + +The following subsections each give the format of the remainder of the +message, after the type code. + +The type codes themselves are defined here: + +\c #define PLUGIN_INIT 1 +\c #define PLUGIN_INIT_RESPONSE 2 +\c #define PLUGIN_PROTOCOL 3 +\c #define PLUGIN_PROTOCOL_ACCEPT 4 +\c #define PLUGIN_PROTOCOL_REJECT 5 +\c #define PLUGIN_AUTH_SUCCESS 6 +\c #define PLUGIN_AUTH_FAILURE 7 +\c #define PLUGIN_INIT_FAILURE 8 +\c +\c #define PLUGIN_KI_SERVER_REQUEST 20 +\c #define PLUGIN_KI_SERVER_RESPONSE 21 +\c #define PLUGIN_KI_USER_REQUEST 22 +\c #define PLUGIN_KI_USER_RESPONSE 23 + +If this protocol is extended to be able to assist with further auth +methods, their message type codes will also begin from 20, overlapping +the codes for keyboard-interactive. + +\S{PLUGIN_INIT} \cw{PLUGIN_INIT} + +\s{Direction}: client to plugin + +\s{When}: the first message sent at connection startup + +\s{What happens next}: the plugin will send \cw{PLUGIN_INIT_RESPONSE} +or \cw{PLUGIN_INIT_FAILURE} + +\s{Message contents after the type code}: + +\b \s{uint32}: the highest version number of this protocol that the +client knows how to speak. + +\b \s{string}: the hostname of the server. This will be the \e{logical} +hostname, in cases where it differs from the physical destination of +the network connection. Whatever name would be used by the SSH client +to cache the server's host key, that's the same name passed in this +message. + +\b \s{uint32}: the port number on the server. (Together with the host +name, this forms a primary key identifying a particular server. Port +numbers may be vital because a single host can run two unrelated SSH +servers with completely different authentication requirements, e.g. +system sshd on port 22 and Gerrit on port 29418.) + +\b \s{string}: the username that the client will use to log in, if the +plugin chooses not to override it. An empty string means that the +client has no opinion about this (and might, for example, prompt the +user). + +\S{PLUGIN_INIT_RESPONSE} \cw{PLUGIN_INIT_RESPONSE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_INIT} + +\s{What happens next}: the client will send \cw{PLUGIN_PROTOCOL}, or +perhaps terminate the session (if no auth method is ever negotiated +that the plugin can help with) + +\s{Message contents after the type code}: + +\b \s{uint32}: the version number of this protocol that the connection +will use. Must be no greater than the max version number sent by the +client in \cw{PLUGIN_INIT}. + +\b \s{string}: the username that the plugin suggests the client use. An +empty string means that the plugin has no opinion and the client +should stick with the username it already had (or prompt the user, if +it had none). + +\S{PLUGIN_INIT_FAILURE} \cw{PLUGIN_INIT_FAILURE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_INIT} + +\s{What happens next}: the session is over + +\s{Message contents after the type code}: + +\b \s{string}: an error message to present to the user indicating why +the plugin was unable to start up. + +\S{PLUGIN_PROTOCOL} \cw{PLUGIN_PROTOCOL} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_INIT_RESPONSE}, or after a previous +auth phase terminates with \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE} + +\s{What happens next}: the plugin will send +\cw{PLUGIN_PROTOCOL_ACCEPT} or \cw{PLUGIN_PROTOCOL_REJECT} + +\s{Message contents after the type code}: + +\b \s{string}: the SSH protocol id of the auth method the client +intends to attempt. Currently the only method specified for use in +this protocol is \cq{keyboard-interactive}. + +\S{PLUGIN_PROTOCOL_REJECT} \cw{PLUGIN_PROTOCOL_REJECT} + +\s{Direction}: plugin to client + +\s{When}: sent after \cw{PLUGIN_PROTOCOL} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: + +\b \s{string}: an error message to present to the user, explaining why +the plugin cannot help with this authentication protocol. + +\lcont{ + +An example might be \q{unable to open : }, if the plugin depends on some configuration that the user +has not set up. + +If the plugin does not support this this particular authentication +protocol at all, this string should be left blank, so that no message +will be presented to the user at all. + +} + +\S{PLUGIN_PROTOCOL_ACCEPT} \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{Direction}: plugin to client + +\s{When}: sent after \cw{PLUGIN_PROTOCOL} + +\s{What happens next}: depends on the auth protocol agreed on. For +keyboard-interactive, the client will send +\cw{PLUGIN_KI_SERVER_REQUEST} or \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE}. No other method is specified. + +\s{Message contents after the type code}: none. + +\S{PLUGIN_KI_SERVER_REQUEST} \cw{PLUGIN_KI_SERVER_REQUEST} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_PROTOCOL}, or after a previous +\cw{PLUGIN_KI_SERVER_RESPONSE}, when the SSH server has sent +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} + +\s{What happens next}: the plugin will send either +\cw{PLUGIN_KI_USER_REQUEST} or \cw{PLUGIN_KI_SERVER_RESPONSE} + +\s{Message contents after the type code}: the exact contents of the +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} just sent by the server. See +\k{authplugin-ref-ki} section 3.2 for details. The summary: + +\b \s{string}: name of this prompt collection (e.g. to use as a +dialog-box title) + +\b \s{string}: instructions to be displayed before this prompt +collection + +\b \s{string}: language tag (deprecated) + +\b \s{uint32}: number of prompts in this collection + +\b That many copies of: + +\lcont{ + +\b \s{string}: prompt (in UTF-8) + +\b \s{boolean}: whether the response to this prompt is safe to echo to +the screen + +} + +\S{PLUGIN_KI_SERVER_RESPONSE} \cw{PLUGIN_KI_SERVER_RESPONSE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, perhaps after one +or more intervening pairs of \cw{PLUGIN_KI_USER_REQUEST} and +\cw{PLUGIN_KI_USER_RESPONSE} + +\s{What happens next}: the client will send a further +\cw{PLUGIN_KI_SERVER_REQUEST}, or \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE} + +\s{Message contents after the type code}: the exact contents of the +\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} that the client should send back +to the server. See \k{authplugin-ref-ki} section 3.4 for details. The +summary: + +\b \s{uint32}: number of responses (must match the \q{number of +prompts} field from the corresponding server request) + +\b That many copies of: + +\lcont{ + +\b \s{string}: response to the \e{n}th prompt (in UTF-8) + +} + +\S{PLUGIN_KI_USER_REQUEST} \cw{PLUGIN_KI_USER_REQUEST} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, if the plugin +cannot answer the server's auth prompts without presenting prompts of +its own to the user + +\s{What happens next}: the client will send \cw{PLUGIN_KI_USER_RESPONSE} + +\s{Message contents after the type code}: exactly the same as in +\cw{PLUGIN_KI_SERVER_REQUEST} (see \k{PLUGIN_KI_SERVER_REQUEST}). + +\S{PLUGIN_KI_USER_RESPONSE} \cw{PLUGIN_KI_USER_RESPONSE} + +\s{Direction}: client to plugin + +\s{When}: response to \cw{PLUGIN_KI_USER_REQUEST} + +\s{What happens next}: the plugin will send +\cw{PLUGIN_KI_SERVER_RESPONSE}, or another \cw{PLUGIN_KI_USER_REQUEST} + +\s{Message contents after the type code}: exactly the same as in +\cw{PLUGIN_KI_SERVER_RESPONSE} (see \k{PLUGIN_KI_SERVER_RESPONSE}). + +\S{PLUGIN_AUTH_SUCCESS} \cw{PLUGIN_AUTH_SUCCESS} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual +cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: none + +\S{PLUGIN_AUTH_FAILURE} \cw{PLUGIN_AUTH_FAILURE} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual +cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: none + +\H{authplugin-refs} References + +\B{authplugin-ref-arch} \W{https://www.rfc-editor.org/rfc/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol +Architecture}. + +\B{authplugin-ref-userauth} \W{https://www.rfc-editor.org/rfc/rfc4252}{RFC +4252}, \q{The Secure Shell (SSH) Authentication Protocol}. + +\B{authplugin-ref-ki} +\W{https://www.rfc-editor.org/rfc/rfc4256}{RFC 4256}, +\q{Generic Message Exchange Authentication for the Secure Shell +Protocol (SSH)} (better known by its wire id +\q{keyboard-interactive}). + +\BR{authplugin-ref-arch} [RFC4251] + +\BR{authplugin-ref-userauth} [RFC4252] + +\BR{authplugin-ref-ki} [RFC4256] diff --git a/code/doc/config.but b/code/doc/config.but index ed3839de..f258a356 100644 --- a/code/doc/config.but +++ b/code/doc/config.but @@ -1943,14 +1943,14 @@ it must always be explicitly configured. \S{config-proxy-type} Setting the proxy type -The \q{Proxy type} radio buttons allow you to configure what type of +The \q{Proxy type} drop-down allows you to configure what type of proxy you want PuTTY to use for its network connections. The default setting is \q{None}; in this mode no proxy is used for any connection. -\b Selecting \I{HTTP proxy}\q{HTTP} allows you to proxy your connections -through a web server supporting the HTTP \cw{CONNECT} command, as documented -in \W{http://www.ietf.org/rfc/rfc2817.txt}{RFC 2817}. +\b Selecting \I{HTTP proxy}\q{HTTP CONNECT} allows you to proxy your +connections through a web server supporting the HTTP \cw{CONNECT} command, +as documented in \W{https://www.rfc-editor.org/rfc/rfc2817}{RFC 2817}. \b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your connections through a \i{SOCKS server}. @@ -1962,10 +1962,9 @@ through to an external host. Selecting \I{Telnet proxy}\q{Telnet} allows you to tell PuTTY to use this type of proxy, with the precise command specified as described in \k{config-proxy-command}. -\b Selecting \q{SSH} causes PuTTY to make a secondary SSH connection -to the proxy host (sometimes called a \q{\i{jump host}} in this -context), and then open a port-forwarding channel to the -final destination host. +\b There are several ways to use a SSH server as a proxy. All of +these cause PuTTY to make a secondary SSH connection to the proxy host +(sometimes called a \q{\i{jump host}} in this context). \lcont{ The \q{Proxy hostname} field will be interpreted as the name of a @@ -1973,6 +1972,20 @@ PuTTY saved session if one exists, or a hostname if not. This allows multi-hop jump paths, if the referenced saved session is itself configured to use an SSH proxy; and it allows combining SSH and non-SSH proxying. + +\b \q{SSH to proxy and use port forwarding} causes PuTTY to use the +secondary SSH connection to open a port-forwarding channel to the +final destination host (similar to OpenSSH's \cw{-J} option). + +\b \q{SSH to proxy and execute a command} causes PuTTY to run an +arbitrary remote command on the proxy SSH server and use that +command's standard input and output streams to run the primary +connection over. The remote command line is specified as described in +\k{config-proxy-command}. + +\b \q{SSH to proxy and invoke a subsystem} is similar but causes PuTTY +to start an SSH \q{\i{subsystem}} rather than an ordinary command line. +This might be useful with a specially set up SSH proxy server. } \b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary @@ -2116,16 +2129,21 @@ Telnet/Local proxy command (see \k{config-proxy-command}). If you do so, and don't also specify the actual username and/or password in the configuration, PuTTY will interactively prompt for them. -\S{config-proxy-command} Specifying the Telnet or Local proxy command +\S{config-proxy-command} Specifying the Telnet, SSH, or Local proxy command If you are using the \i{Telnet proxy} type, the usual command required by the firewall's Telnet server is \c{connect}, followed by a host name and a port number. If your proxy needs a different command, -you can enter an alternative here. +you can enter an alternative in the \q{Command to send to proxy} box. If you are using the \i{Local proxy} type, the local command to run is specified here. +If you are using the \q{SSH to proxy and execute a command} type, the +command to run on the SSH proxy server is specified here. Similarly, if +you are using \q{SSH to proxy and invoke a subsystem}, the subsystem +name is constructed as specified here. + In this string, you can use \c{\\n} to represent a new-line, \c{\\r} to represent a carriage return, \c{\\t} to represent a tab character, and \c{\\x} followed by two hex digits to represent any @@ -2133,13 +2151,15 @@ other character. \c{\\\\} is used to encode the \c{\\} character itself. Also, the special strings \c{%host} and \c{%port} will be replaced -by the host name and port number you want to connect to. The strings -\c{%user} and \c{%pass} will be replaced by the proxy username and -password (which, if not specified in the configuration, will be -prompted for). The strings \c{%proxyhost} and \c{%proxyport} +by the host name and port number you want to connect to. For Telnet +and Local proxy types, the strings \c{%user} and \c{%pass} will be +replaced by the proxy username and password (which, if not specified +in the configuration, will be prompted for) \dash this does not happen +with SSH proxy types (because the proxy username/password are used +for SSH authentication). The strings \c{%proxyhost} and \c{%proxyport} will be replaced by the host details specified on the \e{Proxy} panel, -if any (this is most likely to be useful for the Local proxy type). -To get a literal \c{%} sign, enter \c{%%}. +if any (this is most likely to be useful for proxy types using a +local or remote command). To get a literal \c{%} sign, enter \c{%%}. If a Telnet proxy server prompts for a username and password before commands can be sent, you can use a command such as: @@ -2327,24 +2347,51 @@ cipher selection (see \k{config-ssh-encryption}). PuTTY currently supports the following key exchange methods: -\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}. +\b \q{NTRU Prime / Curve25519 hybrid}: \q{\i{Streamlined NTRU Prime}} +is a lattice-based algorithm intended to resist \i{quantum attacks}. +In this key exchange method, it is run in parallel with a conventional +Curve25519-based method (one of those included in \q{ECDH}), in such +a way that it should be no \e{less} secure than that commonly-used +method, and hopefully also resistant to a new class of attacks. + +\b \q{\i{ECDH}}: elliptic curve Diffie-Hellman key exchange, +with a variety of standard curves and hash algorithms. + +\b The original form of \i{Diffie-Hellman key exchange}, with a +variety of well-known groups and hashes: + +\lcont{ +\b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512 +hash function. + +\b \q{Group 17}, a well-known 6144-bit group, used with the SHA-512 +hash function. -\b \q{Group 14}: Diffie-Hellman key exchange with a well-known -2048-bit group. +\b \q{Group 16}, a well-known 4096-bit group, used with the SHA-512 +hash function. -\b \q{Group 1}: Diffie-Hellman key exchange with a well-known -1024-bit group. We no longer recommend using this method, and it's -not used by default in new installations; however, it may be the -only method supported by very old server software. +\b \q{Group 15}, a well-known 3072-bit group, used with the SHA-512 +hash function. -\b \q{\ii{Group exchange}}: with this method, instead of using a fixed -group, PuTTY requests that the server suggest a group to use for key -exchange; the server can avoid groups known to be weak, and possibly -invent new ones over time, without any changes required to PuTTY's -configuration. We recommend use of this method instead of the -well-known groups, if possible. +\b \q{Group 14}: a well-known 2048-bit group, used with the SHA-256 +hash function or, if the server doesn't support that, SHA-1. + +\b \q{Group 1}: a well-known 1024-bit group, used with the SHA-1 +hash function. Neither we nor current SSH standards recommend using +this method any longer, and it's not used by default in new +installations; however, it may be the only method supported by very +old server software. +} -\b \q{\i{RSA key exchange}}: this requires much less computational +\b \q{Diffie-Hellman \i{group exchange}}: with this method, instead +of using a fixed group, PuTTY requests that the server suggest a group +to use for a subsequent Diffie-Hellman key exchange; the server can +avoid groups known to be weak, and possibly invent new ones over time, +without any changes required to PuTTY's configuration. This key +exchange method uses the SHA-256 hash or, if the server doesn't +support that, SHA-1. + +\b \q{\i{RSA-based key exchange}}: this requires much less computational effort on the part of the client, and somewhat less on the part of the server, than Diffie-Hellman key exchange. @@ -2366,6 +2413,10 @@ when using Kerberos V5, and not other GSSAPI mechanisms. If the user running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. +There's a GSSAPI-based equivalent to most of the ordinary methods +listed in \k{config-ssh-kex-order}; server support determines which +one will be used. (PuTTY's preference order for GSSAPI-authenticated +key exchange methods is fixed, not controlled by the preference list.) The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation (see @@ -2380,7 +2431,8 @@ support GSSAPI in the SSH user authentication phase. This will still let you log in using your Kerberos credentials, but will only allow you to delegate the credentials that are active at the beginning of the session; they can't be refreshed automatically later, in a -long-running session. +long-running session. See \k{config-ssh-auth-gssapi} for how to +control GSSAPI user authentication in PuTTY. Another effect of GSSAPI key exchange is that it replaces the usual SSH mechanism of permanent host keys described in \k{gs-hostkey}. @@ -2494,7 +2546,7 @@ larger elliptic curve with a 448-bit instead of 255-bit modulus (so it has a higher security level than Ed25519). \b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the -NIST-standardised elliptic curves. +\i{NIST}-standardised elliptic curves. \b \q{DSA}: straightforward \i{DSA} using modular exponentiation. @@ -2590,6 +2642,146 @@ neither read \e{nor written}, unless you explicitly do so. If the box is empty (as it usually is), then PuTTY's automated host key management will work as normal. +\S{config-ssh-kex-cert} Configuring PuTTY to accept host \i{certificates} + +In some environments, the SSH host keys for a lot of servers will all +be signed in turn by a central \q{certification authority} (\q{CA} for +short). This simplifies host key configuration for users, because if +they configure their SSH client to accept host keys certified by that +CA, then they don't need to individually confirm each host key the +first time they connect to that server. + +In order to do this, press the \q{Configure host CAs} button in the +\q{Host keys} configuration panel. This will launch a secondary +configuration dialog box where you can configure what CAs PuTTY will +accept signatures from. + +\s{Note that this configuration is common to all saved sessions}. +Everything in the main PuTTY configuration is specific to one saved +session, and you can prepare a separate session with all the +configuration different. But there's only one copy of the host CA +configuration, and it applies to all sessions PuTTY runs, whether +saved or not. + +(Otherwise, it would be useless \dash configuring a CA by hand for +each new host wouldn't be any more convenient than pressing the +\q{confirm} button for each new host's host key.) + +To set up a new CA using this config box: + +First, load the CA's public key from a file, or paste it directly into +the \q{Public key of certification authority} edit box. If your +organisation signs its host keys in this way, they will publish the +public key of their CA so that SSH users can include it in their +configuration. + +Next, in the \q{Valid hosts this key is trusted to certify} box, +configure at least one hostname wildcard to say what servers PuTTY +should trust this CA to speak for. For example, suppose you work for +Example Corporation (\cw{example.com}), and the Example Corporation IT +department has advertised a CA that signs all the Example internal +machines' host keys. Then probably you want to trust that CA to sign +host keys for machines in the domain \cw{example.com}, but not for +anything else. So you might enter \cq{*.example.com} into the \q{Valid +hosts} box. + +\s{It's important to limit what the CA key is allowed to sign}. Don't +just enter \cq{*} in that box! If you do that, you're saying that +Example Corporation IT department is authorised to sign a host key for +\e{anything at all} you might decide to connect to \dash even if +you're connecting out of the company network to a machine somewhere +else, such as your own personal server. So that configuration would +enable the Example IT department to act as a \q{man-in-the-middle} +between your PuTTY process and your server, and listen in to your +communications \dash exactly the thing SSH is supposed to avoid. + +So, if the CA was provided to you by the sysadmins responsible for +\cw{example.com} (or whatever), make sure PuTTY will \e{only} trust it +for machines in the \cw{example.com} domain. + +For the full syntax of the \q{Valid hosts} expression, see +\k{config-ssh-cert-valid-expr}. + +Finally, choose an identifying name for this CA; enter that name in +the \q{Name for this CA} edit box at the top of the window, and press +\q{Save} to record the CA in your configuration. The name you chose +will appear in the list of saved CAs to the left of the \q{Save} +button. + +The identifying name can be anything you like. It's there so that if +you store multiple certificates you can tell which is which later when +you want to edit or delete them. It also appears in the PuTTY Event +Log when a server presents a certificate signed by that CA. + +To reload an existing CA configuration, select it in the list box and +press \q{Load}. Then you can make changes, and save it again. + +To remove a CA from your configuration completely, select it in the +list and press \q{Delete}. + +\S2{config-ssh-cert-valid-expr} Expressions you can enter in \q{Valid +hosts} + +The simplest thing you can enter in the \q{Valid hosts this key is +trusted to certify} edit box is just a hostname wildcard such as +\cq{*.example.com}. This matches any host in any subdomain, so +both \cq{ssh.example.com} and \cq{login.dept.example.com} would +match, but \cq{prod.example.net} would not. + +But you can also enter multiple host name wildcards, and port number +ranges, and make complicated Boolean expressions out of them using the +operators \cq{&&} for \q{and}, \cq{||} for \q{or}, \cq{!} for \q{not}, +and parentheses. + +For example, here are some other things you could enter. + +\b \cq{*.foo.example.com || *.bar.example.com}. This means the CA is +trusted to sign the host key for a connection if the host name matches +\q{*.foo.example.com} \e{or} it matches \q{*.bar.example.com}. In +other words, the CA has authority over those two particular subdomains +of \cw{example.com}, but not for anything else, like +\cw{www.example.com}. + +\b \cq{*.example.com && ! *.extrasecure.example.com}. This means the +CA is trusted to sign the host key for a connection if the host name +matches \q{*.example.com} \e{but does not} match +\q{*.extrasecure.example.com}. (Imagine if there was one top-secret +set of servers in your company that the main IT department didn't have +security clearance to administer.) + +\b \cq{*.example.com && port:22}. This means the CA is trusted to sign +the host key for a connection if the host name matches +\q{*.example.com} \e{and} the port number is 22. SSH servers running +on other ports would not be covered. + +\b \cq{(*.foo.example.com || *.bar.example.com) && port:0-1023}. This +matches two subdomains of \cw{example.com}, as before, but \e{also} +restricts the port number to the range 0-1023. + +A certificate configuration expression consists of one or more +individual requirements which can each be a hostname wildcard, a +single port number, or a port number range, combined together with +these Boolean operators. + +Unlike other languages such as C, there is no implied priority between +\cq{&&} and \cq{||}. If you write \cq{A && B || C} (where \cw{A}, +\cw{B} and \cw{C} are some particular requirements), then PuTTY will +report a syntax error, because you haven't said which of the \cq{&&} +and \cq{||} takes priority tightly. You will have to write either +\cq{(A && B) || C}, meaning \q{both of \cw{A} and \cw{B}, or +alternatively just \cw{C}}, or \cq{A && (B || C)} (\q{\cw{A}, and also +at least one of \cw{B} and \cw{C}}), to make it clear. + +\S2{config-ssh-cert-rsa-hash} RSA signature types in certificates + +RSA keys can be used to generate signatures with a choice of secure +hash function. Typically, any version of OpenSSH new enough to support +certificates at all will also be new enough to avoid using SHA-1, so +the default settings of accepting the more modern SHA-256 and SHA-512 +should be suitable for nearly all cases. For completeness, however, +you can configure which types of RSA signature PuTTY will accept in a +certificate from a CA using an RSA key. + \H{config-ssh-encryption} The Cipher panel PuTTY supports a variety of different \i{encryption algorithm}s, and @@ -2604,7 +2796,8 @@ PuTTY currently supports the following algorithms: \b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only) -\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) +\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or +256 or 128-bit GCM (SSH-2 only) \b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) @@ -2808,6 +3001,12 @@ username more than once, in case the server complains. If you know your server can cope with it, you can enable the \q{Allow attempted changes of username} option to modify PuTTY's behaviour. +\H{config-ssh-auth-creds} The Credentials panel + +This subpane of the Auth panel contains configuration options that +specify actual \e{credentials} to present to the server: key files and +certificates. + \S{config-ssh-privkey} \q{\ii{Private key} file for authentication} This box is where you enter the name of your private key file if you @@ -2829,6 +3028,54 @@ in this case (in RFC 4716 or OpenSSH format), as that's sufficient to identify the key to Pageant, but of course if Pageant isn't present PuTTY can't fall back to using this file itself. +\S{config-ssh-cert} \q{\ii{Certificate} to use with the private key} + +In some environments, user authentication keys can be signed in turn +by a \q{certifying authority} (\q{CA} for short), and user accounts on +an SSH server can be configured to automatically trust any key that's +certified by the right signature. + +This can be a convenient setup if you have a very large number of +servers. When you change your key pair, you might otherwise have to +edit the \cw{authorized_keys} file on every server individually, to +make them all accept the new key. But if instead you configure all +those servers \e{once} to accept keys signed as yours by a CA, then +when you change your public key, all you have to do is to get the new +key certified by the same CA as before, and then all your servers will +automatically accept it without needing individual reconfiguration. + +One way to use a certificate is to incorporate it into your private +key file. \K{puttygen-cert} explains how to do that using PuTTYgen. +But another approach is to tell PuTTY itself where to find the public +certificate file, and then it will automatically present that +certificate when authenticating with the corresponding private key. + +To do this, enter the pathname of the certificate file into the +\q{Certificate to use with the private key} file selector. + +When this setting is configured, PuTTY will honour it no matter +whether the private key is found in a file, or loaded into Pageant. + +\S{config-ssh-authplugin} \q{\ii{Plugin} to provide authentication responses} + +An SSH server can use the \q{keyboard-interactive} protocol to present +a series of arbitrary questions and answers. Sometimes this is used +for ordinary passwords, but sometimes the server will use the same +mechanism for something more complicated, such as a one-time password +system. + +Some of these systems can be automated. For this purpose, PuTTY allows +you to provide a separate program to act as a \q{plugin} which will +take over the authentication and send answers to the questions on your +behalf. + +If you have been provided with a plugin of this type, you can +configure it here, by entering a full command line in the \q{Plugin +command to run} box. + +(If you want to \e{write} a plugin of this type, see \k{authplugin} +for the full specification of how the plugin is expected to behave.) + \H{config-ssh-auth-gssapi} The \i{GSSAPI} panel The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of @@ -3388,6 +3635,32 @@ auto-detection relies on the version string in the server's greeting, and PuTTY has to decide whether to expect this bug \e{before} it sees the server's greeting. So this is a manual workaround only. +\S{config-ssh-bug-filter-kexinit} \q{Chokes on PuTTY's full \cw{KEXINIT}} + +At the start of an SSH connection, the client and server exchange long +messages of type \cw{SSH_MSG_KEXINIT}, containing lists of all the +cryptographic algorithms they're prepared to use. This is used to +negotiate a set of algorithms that both ends can speak. + +Occasionally, a badly written server might have a length limit on the +list it's prepared to receive, and refuse to make a connection simply +because PuTTY is giving it too many choices. + +A workaround is to enable this flag, which will make PuTTY wait to +send \cw{KEXINIT} until after it receives the one from the server, and +then filter its own \cw{KEXINIT} to leave out any algorithm the server +doesn't also announce support for. This will generally make PuTTY's +\cw{KEXINIT} at most the size of the server's, and will otherwise make +no difference to the algorithm negotiation. + +This flag is a minor violation of the SSH protocol, because both sides +are supposed to send \cw{KEXINIT} proactively. It still works provided +\e{one} side sends its \cw{KEXINIT} without waiting, but if both +client and server waited for the other one to speak first, the +connection would deadlock. We don't know of any servers that do this, +but if there is one, then this flag will make PuTTY unable to speak to +them at all. + \S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be diff --git a/code/doc/errors.but b/code/doc/errors.but index 27e2ae32..a35a7256 100644 --- a/code/doc/errors.but +++ b/code/doc/errors.but @@ -39,6 +39,9 @@ the one PuTTY has cached for this server}, means that PuTTY has connected to the SSH server before, knows what its host key \e{should} be, but has found a different one. +(If the message instead talks about a \q{certified host key}, see +instead \k{errors-cert-mismatch}.) + This may mean that a malicious attacker has replaced your server with a different one, or has redirected your network connection to their own machine. On the other hand, it may simply mean that the @@ -52,6 +55,41 @@ in the same way as you would if it was new. See \k{gs-hostkey} for more information on host keys. +\H{errors-cert-mismatch} \q{This server presented a certified host key +which was signed by a different certification authority ...} + +If you've configured PuTTY to trust at least one +\I{certificate}certification authority for signing host keys (see +\k{config-ssh-kex-cert}), then it will ask the SSH server to send it +any available certified host keys. If the server sends back a +certified key signed by a \e{different} certification authority, PuTTY +will present this variant of the host key prompt, preceded by +\q{WARNING - POTENTIAL SECURITY BREACH!} + +One reason why this can happen is a deliberate attack. Just like an +ordinary man-in-the-middle attack which substitutes a wrong host key, +a particularly ambitious attacker might substitute an entire wrong +certification authority, and hope that you connect anyway. + +But it's also possible in some situations that this error might arise +legitimately. For example, if your organisation's IT department has +just rolled out a new CA key which you haven't yet entered in PuTTY's +configuration, or if your CA configuration involves two overlapping +domains, or something similar. + +So, unfortunately, you'll have to work out what to do about it +yourself: make an exception for this specific case, or abandon this +connection and install a new CA key before trying again (if you're +really sure you trust the CA), or edit your configuration in some +other way, or just stop trying to use this server. + +If you're convinced that this particular server is legitimate even +though the CA is not one you trust, PuTTY will let you cache the +certified host key, treating it in the same way as an uncertified one. +Then that particular certificate will be accepted for future +connections to this specific server, even though other certificates +signed by the same CA will still be rejected. + \H{errors-ssh-protocol} \q{SSH protocol version 2 required by our configuration but remote only provides (old, insecure) SSH-1} diff --git a/code/doc/faq.but b/code/doc/faq.but index e9dddb66..9a8deea2 100644 --- a/code/doc/faq.but +++ b/code/doc/faq.but @@ -150,7 +150,7 @@ data at the server end; it's your guarantee that it hasn't been removed and replaced somewhere on the way. Host key checking makes the attacker's job \e{astronomically} hard, compared to packet sniffing, and even compared to subverting a router. Instead of -applying a little intelligence and keeping an eye on Bugtraq, the +applying a little intelligence and keeping an eye on oss-security, the attacker must now perform a brute-force attack against at least one military-strength cipher. That insignificant host key prompt really does make \e{that} much difference. @@ -220,7 +220,7 @@ Currently, release versions of PuTTY tools only run on Windows systems and Unix. As of 0.68, the supplied PuTTY executables run on versions of Windows -from XP onwards, up to and including Windows 10; and we know of no +from XP onwards, up to and including Windows 11; and we know of no reason why PuTTY should not continue to work on future versions of Windows. We provide 32-bit and 64-bit Windows executables for the common x86 processor family; see \k{faq-32bit-64bit} for discussion @@ -250,8 +250,7 @@ There are Unix ports of most of the traditional PuTTY tools, and also one entirely new application. If you look at the source release, you should find a \c{unix} -subdirectory. There are a couple of ways of building it, -including the usual \c{configure}/\c{make}; see the file \c{README} +subdirectory. You need \c{cmake} to build it; see the file \c{README} in the source distribution. This should build you: \b Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much @@ -585,7 +584,7 @@ You can also paste by pressing Shift-Ins. keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink? Most major features (e.g., public keys, port forwarding) are available -through command line options. See the documentation. +through command line options. See \k{using-general-opts}. Not all features are accessible from the command line yet, although we'd like to fix this. In the meantime, you can use most of @@ -607,9 +606,16 @@ To use PSCP properly, run it from a Command Prompt window. See \S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use PSCP to copy a file whose name has spaces in? -If PSCP is using the traditional SCP protocol, this is confusing. If -you're specifying a file at the local end, you just use one set of -quotes as you would normally do: +If PSCP is using the newer SFTP protocol (which is usual with most +modern servers), this is straightforward; all filenames with spaces +in are specified using a single pair of quotes in the obvious way: + +\c pscp "local file" user@host: +\c pscp user@host:"remote file" . + +However, if PSCP is using the older SCP protocol for some reason, +things are more confusing. If you're specifying a file at the local +end, you just use one set of quotes as you would normally do: \c pscp "local filename with spaces" user@host: \c pscp user@host:myfile "local filename with spaces" @@ -633,13 +639,6 @@ Instead, you need to specify the local file name in full: \c c:\>pscp user@host:"\"oo er\"" "oo er" -If PSCP is using the newer SFTP protocol, none of this is a problem, -and all filenames with spaces in are specified using a single pair -of quotes in the obvious way: - -\c pscp "local file" user@host: -\c pscp user@host:"remote file" . - \S{faq-32bit-64bit}{Question} Should I run the 32-bit or the 64-bit version? @@ -1153,6 +1152,22 @@ running, but it doesn't stop the process's memory as a whole from being swapped completely out to disk when the process is long-term inactive. And Pageant spends most of its time inactive. +\S{faq-windowsstore}{Question} Is the version of PuTTY in the +\i{Microsoft Store} legit? + +The free-of-charge \q{PuTTY} application at +\W{https://apps.microsoft.com/store/detail/putty/XPFNZKSKLBP7RJ}{this link} +is published and maintained by us. The copy there is the latest +release, usually updated within a few days of us publishing it on our +own website. + +There have been other copies of PuTTY on the store, some looking quite +similar, and some charging money. Those were uploaded by other people, +and we can't guarantee anything about them. + +The first version we published to the Microsoft Store was 0.76 (some +time after its initial release on our website). + \H{faq-admin} Administrative questions \S{faq-putty-org}{Question} Is \cw{putty.org} your website? @@ -1287,7 +1302,7 @@ Small donations (tens of dollars or tens of euros) will probably be spent on beer or curry, which helps motivate our volunteer team to continue doing this for the world. Larger donations will be spent on something that actually helps development, if we can find anything -(perhaps new hardware, or a copy of Windows XP), but if we can't +(perhaps new hardware, or a new version of Windows), but if we can't find anything then we'll just distribute the money among the developers. If you want to be sure your donation is going towards something worthwhile, ask us first. If you don't like these terms, diff --git a/code/doc/gs.but b/code/doc/gs.but index 4eff8967..8b915dbf 100644 --- a/code/doc/gs.but +++ b/code/doc/gs.but @@ -80,10 +80,10 @@ PuTTY \I{host key cache}records the host key for each server you connect to, in the Windows \i{Registry}. Every time you connect to a server, it checks that the host key presented by the server is the same host key as it was the last time you connected. If it is not, -you will see a warning, and you will have the chance to abandon your -connection before you type any private information (such as a -password) into it. (See \k{errors-hostkey-wrong} for what that looks -like.) +you will see a stronger warning, and you will have the chance to +abandon your connection before you type any private information (such +as a password) into it. (See \k{errors-hostkey-wrong} for what that +looks like.) However, when you connect to a server you have not connected to before, PuTTY has no way of telling whether the host key is the diff --git a/code/doc/html/AppendixA.html b/code/doc/html/AppendixA.html index 5df1bd2f..0ab11040 100644 --- a/code/doc/html/AppendixA.html +++ b/code/doc/html/AppendixA.html @@ -103,6 +103,7 @@
  • A.8.2 What does PuTTY leave on a system? How can I clean up after it?
  • A.8.3 How come PuTTY now supports DSA, when the website used to say how insecure it was?
  • A.8.4 Couldn't Pageant use VirtualLock() to stop private keys being written to disk?
  • +
  • A.8.5 Is the version of PuTTY in the Microsoft Store legit?
  • A.9 Administrative questions
      @@ -193,7 +194,7 @@

      A.2.5 Does PuTTY suppor

      A.2.6 Does PuTTY support storing its settings in a disk file?

      -Not at present, although section 4.32 in the documentation gives a method of achieving the same effect. +Not at present, although section 4.33 in the documentation gives a method of achieving the same effect.

      A.2.7 Does PuTTY support full-screen mode, like a DOS box?

      @@ -220,7 +221,7 @@

      A.2.9 Is there an option to Those annoying host key prompts are the whole point of SSH. Without them, all the cryptographic technology SSH uses to secure your session is doing nothing more than making an attacker's job slightly harder; instead of sitting between you and the server with a packet sniffer, the attacker must actually subvert a router and start modifying the packets going back and forth. But that's not all that much harder than just sniffing; and without host key checking, it will go completely undetected by client or server.

      -Host key checking is your guarantee that the encryption you put on your data at the client end is the same encryption taken off the data at the server end; it's your guarantee that it hasn't been removed and replaced somewhere on the way. Host key checking makes the attacker's job astronomically hard, compared to packet sniffing, and even compared to subverting a router. Instead of applying a little intelligence and keeping an eye on Bugtraq, the attacker must now perform a brute-force attack against at least one military-strength cipher. That insignificant host key prompt really does make that much difference. +Host key checking is your guarantee that the encryption you put on your data at the client end is the same encryption taken off the data at the server end; it's your guarantee that it hasn't been removed and replaced somewhere on the way. Host key checking makes the attacker's job astronomically hard, compared to packet sniffing, and even compared to subverting a router. Instead of applying a little intelligence and keeping an eye on oss-security, the attacker must now perform a brute-force attack against at least one military-strength cipher. That insignificant host key prompt really does make that much difference.

      If you're having a specific problem with host key checking - perhaps you want an automated batch job to make use of PSCP or Plink, and the interactive host key prompt is hanging the batch process - then the right way to fix it is to add the correct host key to the Registry in advance, or if the Registry is not available, to use the -hostkey command-line option. That way, you retain the important feature of host key checking: the right key will be accepted and the wrong ones will not. Adding an option to turn host key checking off completely is the wrong solution and we will not do it. @@ -263,7 +264,7 @@

      A.3.1 What ports of PuT Currently, release versions of PuTTY tools only run on Windows systems and Unix.

      -As of 0.68, the supplied PuTTY executables run on versions of Windows from XP onwards, up to and including Windows 10; and we know of no reason why PuTTY should not continue to work on future versions of Windows. We provide 32-bit and 64-bit Windows executables for the common x86 processor family; see question A.6.10 for discussion of the compatibility issues around that. The 32-bit executables require a Pentium 4 or newer processor. We also provide executables for Windows on Arm processors. +As of 0.68, the supplied PuTTY executables run on versions of Windows from XP onwards, up to and including Windows 11; and we know of no reason why PuTTY should not continue to work on future versions of Windows. We provide 32-bit and 64-bit Windows executables for the common x86 processor family; see question A.6.10 for discussion of the compatibility issues around that. The 32-bit executables require a Pentium 4 or newer processor. We also provide executables for Windows on Arm processors.

      (We used to also provide executables for Windows for the Alpha processor, but stopped after 0.58 due to lack of interest.) @@ -285,7 +286,7 @@

      A.3.2 Is there There are Unix ports of most of the traditional PuTTY tools, and also one entirely new application.

      -If you look at the source release, you should find a unix subdirectory. There are a couple of ways of building it, including the usual configure/make; see the file README in the source distribution. This should build you: +If you look at the source release, you should find a unix subdirectory. You need cmake to build it; see the file README in the source distribution. This should build you:

      • Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much the same as their Windows counterparts; @@ -483,7 +484,7 @@

        A.6.6 How do I

        A.6.7 How do I use all PuTTY's features (public keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink?

        -Most major features (e.g., public keys, port forwarding) are available through command line options. See the documentation. +Most major features (e.g., public keys, port forwarding) are available through command line options. See section 3.11.3.

        Not all features are accessible from the command line yet, although we'd like to fix this. In the meantime, you can use most of PuTTY's features if you create a PuTTY saved session, and then use the name of the saved session on the command line in place of a hostname. This works for PSCP, PSFTP and Plink (but don't expect port forwarding in the file transfer applications!). @@ -497,7 +498,13 @@

        A.6.8 How do I use PSCP.EXE? Whe

        A.6.9 How do I use PSCP to copy a file whose name has spaces in?

        -If PSCP is using the traditional SCP protocol, this is confusing. If you're specifying a file at the local end, you just use one set of quotes as you would normally do: +If PSCP is using the newer SFTP protocol (which is usual with most modern servers), this is straightforward; all filenames with spaces in are specified using a single pair of quotes in the obvious way: +

        +
        pscp "local file" user@host:
        +pscp user@host:"remote file" .
        +
        +

        +However, if PSCP is using the older SCP protocol for some reason, things are more confusing. If you're specifying a file at the local end, you just use one set of quotes as you would normally do:

        pscp "local filename with spaces" user@host:
         pscp user@host:myfile "local filename with spaces"
        @@ -520,12 +527,6 @@ 

        A.6.9 H

        c:\>pscp user@host:"\"oo er\"" "oo er"
         
        -

        -If PSCP is using the newer SFTP protocol, none of this is a problem, and all filenames with spaces in are specified using a single pair of quotes in the obvious way: -

        -
        pscp "local file" user@host:
        -pscp user@host:"remote file" .
        -

        A.6.10 Should I run the 32-bit or the 64-bit version?

        If you're not sure, the 32-bit version is generally the safe option. It will run perfectly well on all processors and on all versions of Windows that PuTTY supports. PuTTY doesn't require to run as a 64-bit application to work well, and having a 32-bit PuTTY on a 64-bit system isn't likely to cause you any trouble. @@ -534,7 +535,7 @@

        A.6.10 Should I run the The 64-bit version (first released in 0.68) will only run if you have a 64-bit processor and a 64-bit edition of Windows (both of these things are likely to be true of any recent Windows PC). It will run somewhat faster (in particular, the cryptography will be faster, especially during link setup), but it will consume slightly more memory.

        -If you need to use an external DLL for GSSAPI authentication, that DLL may only be available in a 32-bit or 64-bit form, and that will dictate the version of PuTTY you need to use. (You will probably know if you're doing this; see section 4.22.2 in the documentation.) +If you need to use an external DLL for GSSAPI authentication, that DLL may only be available in a 32-bit or 64-bit form, and that will dictate the version of PuTTY you need to use. (You will probably know if you're doing this; see section 4.23.2 in the documentation.)

        A.7 Troubleshooting

        A.7.1 Why do I see ‘Fatal: Protocol error: Expected control record’ in PSCP?

        @@ -727,7 +728,7 @@

        A.7.18 PSFTP commands

        A.7.19 Do you want to hear about ‘Software caused connection abort’?

        -In the documentation for PuTTY 0.53 and 0.53b, we mentioned that we'd like to hear about any occurrences of this error. Since the release of PuTTY 0.54, however, we've been convinced that this error doesn't indicate that PuTTY's doing anything wrong, and we don't need to hear about further occurrences. See section 10.15 for our current documentation of this error. +In the documentation for PuTTY 0.53 and 0.53b, we mentioned that we'd like to hear about any occurrences of this error. Since the release of PuTTY 0.54, however, we've been convinced that this error doesn't indicate that PuTTY's doing anything wrong, and we don't need to hear about further occurrences. See section 10.16 for our current documentation of this error.

        A.7.20 My SSH-2 session locks up for a few seconds every so often.

        @@ -752,7 +753,7 @@

        A.7.23 After I upgraded PuTTY If your SSH server has started unexpectedly closing SSH connections after you enter your password, and it worked before 0.68, you may have a buggy server that objects to certain SSH protocol extensions.

        -The SSH protocol recently gained a new ‘terminal mode’, IUTF8, which PuTTY sends by default; see section 4.23.2. This is the first new terminal mode since the SSH-2 protocol was defined. While servers are supposed to ignore modes they don't know about, some buggy servers will unceremoniously close the connection if they see anything they don't recognise. SSH servers in embedded devices, network appliances, and the like seem to disproportionately have this bug. +The SSH protocol recently gained a new ‘terminal mode’, IUTF8, which PuTTY sends by default; see section 4.24.2. This is the first new terminal mode since the SSH-2 protocol was defined. While servers are supposed to ignore modes they don't know about, some buggy servers will unceremoniously close the connection if they see anything they don't recognise. SSH servers in embedded devices, network appliances, and the like seem to disproportionately have this bug.

        If you think you have such a server, from 0.69 onwards you can disable sending of the IUTF8 mode: on the SSH / TTY panel, select IUTF8 on the list, select ‘Nothing’, and press ‘Set’. (It's not possible to disable sending this mode in 0.68.) @@ -789,6 +790,16 @@

        A.8.4 Couldn't Pageant us

        Unfortunately not. The VirtualLock() function in the Windows API doesn't do a proper job: it may prevent small pieces of a process's memory from being paged to disk while the process is running, but it doesn't stop the process's memory as a whole from being swapped completely out to disk when the process is long-term inactive. And Pageant spends most of its time inactive.

        +

        A.8.5 Is the version of PuTTY in the Microsoft Store legit?

        +

        +The free-of-charge ‘PuTTY’ application at this link is published and maintained by us. The copy there is the latest release, usually updated within a few days of us publishing it on our own website. +

        +

        +There have been other copies of PuTTY on the store, some looking quite similar, and some charging money. Those were uploaded by other people, and we can't guarantee anything about them. +

        +

        +The first version we published to the Microsoft Store was 0.76 (some time after its initial release on our website). +

        A.9 Administrative questions

        A.9.1 Is putty.org your website?

        @@ -853,7 +864,7 @@

        A.9.8 How can I donate to P Having said all that, if you still really want to give us money, we won't argue :-) The easiest way for us to accept donations is if you send money to <anakin@pobox.com> using PayPal (www.paypal.com). If you don't like PayPal, talk to us; we can probably arrange some alternative means.

        -Small donations (tens of dollars or tens of euros) will probably be spent on beer or curry, which helps motivate our volunteer team to continue doing this for the world. Larger donations will be spent on something that actually helps development, if we can find anything (perhaps new hardware, or a copy of Windows XP), but if we can't find anything then we'll just distribute the money among the developers. If you want to be sure your donation is going towards something worthwhile, ask us first. If you don't like these terms, feel perfectly free not to donate. We don't mind. +Small donations (tens of dollars or tens of euros) will probably be spent on beer or curry, which helps motivate our volunteer team to continue doing this for the world. Larger donations will be spent on something that actually helps development, if we can find anything (perhaps new hardware, or a new version of Windows), but if we can't find anything then we'll just distribute the money among the developers. If you want to be sure your donation is going towards something worthwhile, ask us first. If you don't like these terms, feel perfectly free not to donate. We don't mind.

        A.9.9 Can I have permission to put PuTTY on a cover disk / distribute it with other software / etc?

        @@ -983,7 +994,7 @@

        A.9.17 The sha1sums<

      A.10 Miscellaneous questions

      -

      A.10.1 Is PuTTY a port of OpenSSH, or based on OpenSSH or OpenSSL?

      +

      A.10.1 Is PuTTY a port of OpenSSH, or based on OpenSSH or OpenSSL?

      No, it isn't. PuTTY is almost completely composed of code written from scratch for PuTTY. The only code we share with OpenSSH is the detector for SSH-1 CRC compensation attacks, written by CORE SDI S.A; we share no code at all with OpenSSL.

      @@ -1003,5 +1014,6 @@

      A.10.4 How do I pronounce Exactly like the English word ‘putty’, which we pronounce /ˈpʌti/.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixB.html b/code/doc/html/AppendixB.html index d2d7fcbf..6cb4a329 100644 --- a/code/doc/html/AppendixB.html +++ b/code/doc/html/AppendixB.html @@ -268,5 +268,6 @@

      B.11 E-mail address

      The actual address to mail is <putty@projects.tartarus.org>.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixC.html b/code/doc/html/AppendixC.html index 4c2eb2e1..a68be37b 100644 --- a/code/doc/html/AppendixC.html +++ b/code/doc/html/AppendixC.html @@ -38,7 +38,7 @@

      Appendix C: PPK file format

      This appendix documents the file format used by PuTTY to store private keys.

      -In this appendix, binary data structures are described using data type representations such as ‘uint32’, ‘string’ and ‘mpint’ as used in the SSH protocol standards themselves. These are defined authoritatively by RFC 4251 section 5, ‘Data Type Representations Used in the SSH Protocols’. +In this appendix, binary data structures are described using data type representations such as ‘uint32’, ‘string’ and ‘mpint’ as used in the SSH protocol standards themselves. These are defined authoritatively by RFC 4251 section 5, ‘Data Type Representations Used in the SSH Protocols’.

      C.1 Overview

      @@ -86,7 +86,7 @@

      C.2 Outer layer

      key-comment-string is a free text field giving the comment. This can contain any byte values other than 13 and 10 (CR and LF).

      -The next part of the file gives the public key. This is stored unencrypted but base64-encoded (RFC 4648), and is preceded by a header line saying how many lines of base64 data are shown, looking like this: +The next part of the file gives the public key. This is stored unencrypted but base64-encoded (RFC 4648), and is preceded by a header line saying how many lines of base64 data are shown, looking like this:

      Public-Lines: number-of-lines
       that many lines of base64 data
      @@ -202,7 +202,7 @@ 

      C.3.2 DSA

    C.3.3 NIST elliptic-curve keys

    -NIST elliptic-curve keys are stored using one of the following algorithm-name values, each corresponding to a different elliptic curve and key size: +NIST elliptic-curve keys are stored using one of the following algorithm-name values, each corresponding to a different elliptic curve and key size:

    • ecdsa-sha2-nistp256’ @@ -349,5 +349,6 @@

      C.5.2 Version 1

      In an unencrypted version 1 key file, the MAC is replaced by a plain SHA-1 hash of the private key data. This is indicated by the ‘Private-MAC:’ header being replaced with ‘Private-Hash:’ instead.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixD.html b/code/doc/html/AppendixD.html index 531f10f5..4ca34731 100644 --- a/code/doc/html/AppendixD.html +++ b/code/doc/html/AppendixD.html @@ -29,5 +29,6 @@

      Appendix D: PuTTY Li THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixE.html b/code/doc/html/AppendixE.html index fadbe29f..8dc00f56 100644 --- a/code/doc/html/AppendixE.html +++ b/code/doc/html/AppendixE.html @@ -416,5 +416,6 @@

      E.13 Do as we say, not as w This should not be taken as a licence to go ahead and violate the rules. Where we violate them ourselves, we're not happy about it, and we would welcome patches that fix any existing problems. Please try to help us make our code better, not worse!

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixF.html b/code/doc/html/AppendixF.html index ee7f93fb..ed5d1c28 100644 --- a/code/doc/html/AppendixF.html +++ b/code/doc/html/AppendixF.html @@ -278,5 +278,6 @@

      F.3 Key rollover

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/AppendixG.html b/code/doc/html/AppendixG.html index 35eb79de..9d482390 100644 --- a/code/doc/html/AppendixG.html +++ b/code/doc/html/AppendixG.html @@ -8,10 +8,10 @@ - + -

      Previous | Contents | Index | Next

      +

      Previous | Contents | Index | Next

      • Appendix G: SSH-2 names specified for PuTTY @@ -42,7 +42,7 @@

        G.1 Connection protocol ch
        PuTTY sends this request along with some SSH_MSG_CHANNEL_WINDOW_ADJUST messages as part of its window-size tuning. It can be sent on any type of channel. There is no message-specific data. Servers MUST treat it as an unrecognised request and respond with SSH_MSG_CHANNEL_FAILURE.

        -(Some SSH servers get confused by this message, so there is a bug-compatibility mode for disabling it. See section 4.26.3.) +(Some SSH servers get confused by this message, so there is a bug-compatibility mode for disabling it. See section 4.27.3.)

        @@ -98,7 +98,7 @@

        G.3 Encryption algorithm n

        G.4 Agent extension request names

        -The SSH agent protocol, which is only specified in an Internet-Draft at the time of writing (draft-miller-ssh-agent), defines an extension mechanism. These names can be sent in an SSH_AGENTC_EXTENSION message. +The SSH agent protocol, which is only specified in an Internet-Draft at the time of writing (draft-miller-ssh-agent), defines an extension mechanism. These names can be sent in an SSH_AGENTC_EXTENSION message.

        add-ppk@putty.projects.tartarus.org @@ -147,9 +147,7 @@

        G.4 Agent extension request

        -

        - ersionid PuTTY release 0.77 -

        -

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +[PuTTY release 0.78]
        diff --git a/code/doc/html/AppendixH.html b/code/doc/html/AppendixH.html new file mode 100644 index 00000000..6b47b670 --- /dev/null +++ b/code/doc/html/AppendixH.html @@ -0,0 +1,459 @@ + + + + +PuTTY authentication plugin protocol + + + + + + + +

        Previous | Contents | Index | Next

        + + +

        Appendix H: PuTTY authentication plugin protocol

        +

        +This appendix contains the specification for the protocol spoken over local IPC between PuTTY and an authentication helper plugin. +

        +

        +If you already have an authentication plugin and want to configure PuTTY to use it, see section 4.22.3 for how to do that. This appendix is for people writing new authentication plugins. +

        +

        H.1 Requirements

        +

        +The following requirements informed the specification of this protocol. +

        +

        +Automate keyboard-interactive authentication. We're motivated in the first place by the observation that the general SSH userauth method ‘keyboard-interactive’ (defined in [RFC4256]) can be used for many kinds of challenge/response or one-time-password styles of authentication, and in more than one of those, the necessary responses might be obtained from an auxiliary network connection, such as an HTTPS transaction. So it's useful if a user doesn't have to manually copy-type or copy-paste from their web browser into their SSH client, but instead, the process can be automated. +

        +

        +Be able to pass prompts on to the user. On the other hand, some userauth methods can be only partially automated; some of the server's prompts might still require human input. Also, the plugin automating the authentication might need to ask its own questions that are not provided by the SSH server. (For example, ‘please enter the master key that the real response will be generated by hashing’.) So after the plugin intercepts the server's questions, it needs to be able to ask its own questions of the user, which may or may not be the same questions sent by the server. +

        +

        +Allow automatic generation of the username. Sometimes, the authentication method comes with a mechanism for discovering the username to be used in the SSH login. So the plugin has to start up early enough that the client hasn't committed to a username yet. +

        +

        +Future expansion route to other SSH userauth flavours. The initial motivation for this protocol is specific to keyboard-interactive. But other SSH authentication methods exist, and they may also benefit from automation in future. We're making no attempt here to predict what those methods might be or how they might be automated, but we do need to leave a space where they can be slotted in later if necessary. +

        +

        +Minimal information loss. Keyboard-interactive prompts and replies should be passed to and from the plugin in a form as close as possible to the way they look on the wire in SSH itself. Therefore, the protocol resembles SSH in its data formats and marshalling (instead of, for example, translating from SSH binary packet style to another well-known format such as JSON, which would introduce edge cases in character encoding). +

        +

        +Half-duplex. Simultaneously trying to read one I/O stream and write another adds a lot of complexity to software. It becomes necessary to have an organised event loop containing select or WaitForMultipleObjects or similar, which can invoke the handler for whichever event happens soonest. There's no need to add that complexity in an application like this, which isn't transferring large amounts of bulk data or multiplexing unrelated activities. So, to keep life simple for plugin authors, we set the ground rule that it must always be 100% clear which side is supposed to be sending a message next. That way, the plugin can be written as sequential code progressing through the protocol, making simple read and write calls to receive or send each message. +

        +

        +Communicate success/failure, to facilitate caching in the plugin. A plugin might want to cache recently used data for next time, but only in the case where authentication using that data was actually successful. So the client has to tell the plugin what the outcome was, if it's known. (But this is best-effort only. Obviously the plugin cannot depend on hearing the answer, because any IPC protocol at all carries the risk that the other end might crash or be killed by things outside its control.) +

        +

        H.2 Transport and configuration

        +

        +Plugins are executable programs on the client platform. +

        +

        +The SSH client must be manually configured to use a plugin for a particular connection. The configuration takes the form of a command line, including the location of the plugin executable, and optionally command-line arguments that are meaningful to the particular plugin. +

        +

        +The client invokes the plugin as a subprocess, passing it a pair of 8-bit-clean pipes as its standard input and output. On those pipes, the client and plugin will communicate via the protocol specified below. +

        +

        H.3 Data formats and marshalling

        +

        +This protocol borrows the low-level data formatting from SSH itself, in particular the following wire encodings from [RFC4251] section 5: +

        +
        +byte +
        +
        +An integer between 0 and 0xFF inclusive, transmitted as a single byte of binary data. +
        +
        +boolean +
        +
        +The values ‘true’ or ‘false’, transmitted as the bytes 1 and 0 respectively. +
        +
        +uint32 +
        +
        +An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4 bytes of binary data, in big-endian (‘network’) byte order. +
        +
        +string +
        +
        +A sequence of bytes, preceded by a uint32 giving the number of bytes in the sequence. The length field does not include itself. For example, the empty string is represented by four zero bytes (the uint32 encoding of 0); the string "AB" is represented by the six bytes 0,0,0,2,'A','B'. +
        +
        +

        +Unlike SSH itself, the protocol spoken between the client and the plugin is unencrypted, because local inter-process pipes are assumed to be secured by the OS kernel. So the binary packet protocol is much simpler than SSH proper, and is similar to SFTP and the OpenSSH agent protocol. +

        +

        +The data sent in each direction of the conversation consists of a sequence of messages exchanged between the SSH client and the plugin. Each message is encoded as a string. The contents of the string begin with a byte giving the message type, which determines the format of the rest of the message. +

        +

        H.4 Protocol versioning

        +

        +This protocol itself is versioned. At connection setup, the client states the highest version number it knows how to speak, and then the plugin responds by choosing the version number that will actually be spoken (which may not be higher than the client's value). +

        +

        +Including a version number makes it possible to make breaking changes to the protocol later. +

        +

        +Even version numbers represent released versions of this spec. Odd numbers represent drafts or development versions in between releases. A client and plugin negotiating an odd version number are not guaranteed to interoperate; the developer testing the combination is responsible for ensuring the two are compatible. +

        +

        +This document describes version 2 of the protocol, the first released version. (The initial drafts had version 1.) +

        +

        H.5 Overview and sequence of events

        +

        +At the very beginning of the user authentication phase of SSH, the client launches the plugin subprocess, if one is configured. It immediately sends the PLUGIN_INIT message, telling the plugin some initial information about where the SSH connection is to. +

        +

        +The plugin responds with PLUGIN_INIT_RESPONSE, which may optionally tell the SSH client what username to use. +

        +

        +The client begins trying to authenticate with the SSH server in the usual way, using the username provided by the plugin (if any) or alternatively one obtained via its normal (non-plugin) policy. +

        +

        +The client follows its normal policy for selecting authentication methods to attempt. If it chooses a method that this protocol does not cover, then the client will perform that method in its own way without consulting the plugin. +

        +

        +However, if the client and server decide to attempt a method that this protocol does cover, then the client sends PLUGIN_PROTOCOL specifying the SSH protocol id for the authentication method being used. The plugin responds with PLUGIN_PROTOCOL_ACCEPT if it's willing to assist with this auth method, or PLUGIN_PROTOCOL_REJECT if it isn't. +

        +

        +If the plugin sends PLUGIN_PROTOCOL_REJECT, then the client will proceed as if the plugin were not present. Later, if another auth method is negotiated (either because this one failed, or because it succeeded but the server wants multiple auth methods), the client may send a further PLUGIN_PROTOCOL and try again. +

        +

        +If the plugin sends PLUGIN_PROTOCOL_ACCEPT, then a protocol segment begins that is specific to that auth method, terminating in either PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE. After that, again, the client may send a further PLUGIN_PROTOCOL. +

        +

        +Currently the only supported method is ‘keyboard-interactive’, defined in [RFC4256]. Once the client has announced this to the server, the followup protocol is as follows: +

        +

        +Each time the server sends an SSH_MSG_USERAUTH_INFO_REQUEST message requesting authentication responses from the user, the SSH client translates the message into PLUGIN_KI_SERVER_REQUEST and passes it on to the plugin. +

        +

        +At this point, the plugin may optionally send back PLUGIN_KI_USER_REQUEST containing prompts to be presented to the actual user. The client will reply with a matching PLUGIN_KI_USER_RESPONSE after asking the user to reply to the question(s) in the request message. The plugin can repeat this cycle multiple times. +

        +

        +Once the plugin has all the information it needs to respond to the server's authentication prompts, it sends PLUGIN_KI_SERVER_RESPONSE back to the client, which translates it into SSH_MSG_USERAUTH_INFO_RESPONSE to send on to the server. +

        +

        +After that, as described in [RFC4256], the server is free to accept authentication, reject it, or send another SSH_MSG_USERAUTH_INFO_REQUEST. Each SSH_MSG_USERAUTH_INFO_REQUEST is dealt with in the same way as above. +

        +

        +If the server terminates keyboard-interactive authentication with SSH_MSG_USERAUTH_SUCCESS or SSH_MSG_USERAUTH_FAILURE, the client informs the plugin by sending either PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE. PLUGIN_AUTH_SUCCESS is sent when that particular authentication method was successful, regardless of whether the SSH server chooses to request further authentication afterwards: in particular, SSH_MSG_USERAUTH_FAILURE with the ‘partial success’ flag (see [RFC4252] section 5.1) translates into PLUGIN_AUTH_SUCCESS. +

        +

        +The plugin's standard input will close when the client no longer requires the plugin's services, for any reason. This could be because authentication is complete (with overall success or overall failure), or because the user has manually aborted the session in mid-authentication, or because the client crashed. +

        +

        H.6 Message formats

        +

        +This section describes the format of every message in the protocol. +

        +

        +As described in section H.3, every message starts with the same two fields: +

        +
        • +uint32: overall length of the message +
        • +
        • +byte: message type. +
        • +
        +

        +The length field does not include itself, but does include the type code. +

        +

        +The following subsections each give the format of the remainder of the message, after the type code. +

        +

        +The type codes themselves are defined here: +

        +
        #define PLUGIN_INIT                   1
        +#define PLUGIN_INIT_RESPONSE          2
        +#define PLUGIN_PROTOCOL               3
        +#define PLUGIN_PROTOCOL_ACCEPT        4
        +#define PLUGIN_PROTOCOL_REJECT        5
        +#define PLUGIN_AUTH_SUCCESS           6
        +#define PLUGIN_AUTH_FAILURE           7
        +#define PLUGIN_INIT_FAILURE           8
        +
        +#define PLUGIN_KI_SERVER_REQUEST     20
        +#define PLUGIN_KI_SERVER_RESPONSE    21
        +#define PLUGIN_KI_USER_REQUEST       22
        +#define PLUGIN_KI_USER_RESPONSE      23
        +
        +

        +If this protocol is extended to be able to assist with further auth methods, their message type codes will also begin from 20, overlapping the codes for keyboard-interactive. +

        +

        H.6.1 PLUGIN_INIT

        +

        +Direction: client to plugin +

        +

        +When: the first message sent at connection startup +

        +

        +What happens next: the plugin will send PLUGIN_INIT_RESPONSE or PLUGIN_INIT_FAILURE +

        +

        +Message contents after the type code: +

        +
        • +uint32: the highest version number of this protocol that the client knows how to speak. +
        • +
        • +string: the hostname of the server. This will be the logical hostname, in cases where it differs from the physical destination of the network connection. Whatever name would be used by the SSH client to cache the server's host key, that's the same name passed in this message. +
        • +
        • +uint32: the port number on the server. (Together with the host name, this forms a primary key identifying a particular server. Port numbers may be vital because a single host can run two unrelated SSH servers with completely different authentication requirements, e.g. system sshd on port 22 and Gerrit on port 29418.) +
        • +
        • +string: the username that the client will use to log in, if the plugin chooses not to override it. An empty string means that the client has no opinion about this (and might, for example, prompt the user). +
        • +
        +

        H.6.2 PLUGIN_INIT_RESPONSE

        +

        +Direction: plugin to client +

        +

        +When: response to PLUGIN_INIT +

        +

        +What happens next: the client will send PLUGIN_PROTOCOL, or perhaps terminate the session (if no auth method is ever negotiated that the plugin can help with) +

        +

        +Message contents after the type code: +

        +
        • +uint32: the version number of this protocol that the connection will use. Must be no greater than the max version number sent by the client in PLUGIN_INIT. +
        • +
        • +string: the username that the plugin suggests the client use. An empty string means that the plugin has no opinion and the client should stick with the username it already had (or prompt the user, if it had none). +
        • +
        +

        H.6.3 PLUGIN_INIT_FAILURE

        +

        +Direction: plugin to client +

        +

        +When: response to PLUGIN_INIT +

        +

        +What happens next: the session is over +

        +

        +Message contents after the type code: +

        +
        • +string: an error message to present to the user indicating why the plugin was unable to start up. +
        • +
        +

        H.6.4 PLUGIN_PROTOCOL

        +

        +Direction: client to plugin +

        +

        +When: sent after PLUGIN_INIT_RESPONSE, or after a previous auth phase terminates with PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE +

        +

        +What happens next: the plugin will send PLUGIN_PROTOCOL_ACCEPT or PLUGIN_PROTOCOL_REJECT +

        +

        +Message contents after the type code: +

        +
        • +string: the SSH protocol id of the auth method the client intends to attempt. Currently the only method specified for use in this protocol is ‘keyboard-interactive’. +
        • +
        +

        H.6.5 PLUGIN_PROTOCOL_REJECT

        +

        +Direction: plugin to client +

        +

        +When: sent after PLUGIN_PROTOCOL +

        +

        +What happens next: the client will either send another PLUGIN_PROTOCOL or terminate the session +

        +

        +Message contents after the type code: +

        +
        • +string: an error message to present to the user, explaining why the plugin cannot help with this authentication protocol. +

          +An example might be ‘unable to open <config file>: <OS error message>’, if the plugin depends on some configuration that the user has not set up. +

          +

          +If the plugin does not support this this particular authentication protocol at all, this string should be left blank, so that no message will be presented to the user at all. +

          + +
        • +
        +

        H.6.6 PLUGIN_PROTOCOL_ACCEPT

        +

        +Direction: plugin to client +

        +

        +When: sent after PLUGIN_PROTOCOL +

        +

        +What happens next: depends on the auth protocol agreed on. For keyboard-interactive, the client will send PLUGIN_KI_SERVER_REQUEST or PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE. No other method is specified. +

        +

        +Message contents after the type code: none. +

        +

        H.6.7 PLUGIN_KI_SERVER_REQUEST

        +

        +Direction: client to plugin +

        +

        +When: sent after PLUGIN_PROTOCOL, or after a previous PLUGIN_KI_SERVER_RESPONSE, when the SSH server has sent SSH_MSG_USERAUTH_INFO_REQUEST +

        +

        +What happens next: the plugin will send either PLUGIN_KI_USER_REQUEST or PLUGIN_KI_SERVER_RESPONSE +

        +

        +Message contents after the type code: the exact contents of the SSH_MSG_USERAUTH_INFO_REQUEST just sent by the server. See [RFC4256] section 3.2 for details. The summary: +

        +
        • +string: name of this prompt collection (e.g. to use as a dialog-box title) +
        • +
        • +string: instructions to be displayed before this prompt collection +
        • +
        • +string: language tag (deprecated) +
        • +
        • +uint32: number of prompts in this collection +
        • +
        • +That many copies of: +
          • +string: prompt (in UTF-8) +
          • +
          • +boolean: whether the response to this prompt is safe to echo to the screen +
          • +
          + +
        • +
        +

        H.6.8 PLUGIN_KI_SERVER_RESPONSE

        +

        +Direction: plugin to client +

        +

        +When: response to PLUGIN_KI_SERVER_REQUEST, perhaps after one or more intervening pairs of PLUGIN_KI_USER_REQUEST and PLUGIN_KI_USER_RESPONSE +

        +

        +What happens next: the client will send a further PLUGIN_KI_SERVER_REQUEST, or PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE +

        +

        +Message contents after the type code: the exact contents of the SSH_MSG_USERAUTH_INFO_RESPONSE that the client should send back to the server. See [RFC4256] section 3.4 for details. The summary: +

        +
        • +uint32: number of responses (must match the ‘number of prompts’ field from the corresponding server request) +
        • +
        • +That many copies of: +
          • +string: response to the nth prompt (in UTF-8) +
          • +
          + +
        • +
        +

        H.6.9 PLUGIN_KI_USER_REQUEST

        +

        +Direction: plugin to client +

        +

        +When: response to PLUGIN_KI_SERVER_REQUEST, if the plugin cannot answer the server's auth prompts without presenting prompts of its own to the user +

        +

        +What happens next: the client will send PLUGIN_KI_USER_RESPONSE +

        +

        +Message contents after the type code: exactly the same as in PLUGIN_KI_SERVER_REQUEST (see section H.6.7). +

        +

        H.6.10 PLUGIN_KI_USER_RESPONSE

        +

        +Direction: client to plugin +

        +

        +When: response to PLUGIN_KI_USER_REQUEST +

        +

        +What happens next: the plugin will send PLUGIN_KI_SERVER_RESPONSE, or another PLUGIN_KI_USER_REQUEST +

        +

        +Message contents after the type code: exactly the same as in PLUGIN_KI_SERVER_RESPONSE (see section H.6.8). +

        +

        H.6.11 PLUGIN_AUTH_SUCCESS

        +

        +Direction: client to plugin +

        +

        +When: sent after PLUGIN_KI_SERVER_RESPONSE, or (in unusual cases) after PLUGIN_PROTOCOL_ACCEPT +

        +

        +What happens next: the client will either send another PLUGIN_PROTOCOL or terminate the session +

        +

        +Message contents after the type code: none +

        +

        H.6.12 PLUGIN_AUTH_FAILURE

        +

        +Direction: client to plugin +

        +

        +When: sent after PLUGIN_KI_SERVER_RESPONSE, or (in unusual cases) after PLUGIN_PROTOCOL_ACCEPT +

        +

        +What happens next: the client will either send another PLUGIN_PROTOCOL or terminate the session +

        +

        +Message contents after the type code: none +

        +

        H.7 References

        +

        +[RFC4251] RFC 4251, ‘The Secure Shell (SSH) Protocol Architecture’. +

        +

        +[RFC4252] RFC 4252, ‘The Secure Shell (SSH) Authentication Protocol’. +

        +

        +[RFC4256] RFC 4256, ‘Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)’ (better known by its wire id ‘keyboard-interactive’). +

        + +

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +[PuTTY release 0.78]
        + diff --git a/code/doc/html/Chapter1.html b/code/doc/html/Chapter1.html index 9c1e3e2d..b2ae008b 100644 --- a/code/doc/html/Chapter1.html +++ b/code/doc/html/Chapter1.html @@ -84,5 +84,6 @@

        1.2 How do SSH, Telnet, Rlogin, a If your client and server are both behind the same (good) firewall, it is more likely to be safe to use Telnet, Rlogin, or SUPDUP, but we still recommend you use SSH.

        -

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +[PuTTY release 0.78]
        diff --git a/code/doc/html/Chapter10.html b/code/doc/html/Chapter10.html index 10e57dea..427df03f 100644 --- a/code/doc/html/Chapter10.html +++ b/code/doc/html/Chapter10.html @@ -18,23 +18,24 @@

      Chapter 10: Common error messages

      @@ -62,6 +63,9 @@

      10.2 ‘WARNING - This message, followed by ‘The server's host key does not match the one PuTTY has cached for this server’, means that PuTTY has connected to the SSH server before, knows what its host key should be, but has found a different one.

      +(If the message instead talks about a ‘certified host key’, see instead section 10.3.) +

      +

      This may mean that a malicious attacker has replaced your server with a different one, or has redirected your network connection to their own machine. On the other hand, it may simply mean that the administrator of your server has accidentally changed the key while upgrading the SSH software; this shouldn't happen but it is unfortunately possible.

      @@ -70,9 +74,25 @@

      10.2 ‘WARNING -

      See section 2.2 for more information on host keys.

      -

      10.3 ‘SSH protocol version 2 required by our configuration but remote only provides (old, insecure) SSH-1’

      +

      10.3 ‘This server presented a certified host key which was signed by a different certification authority ...’

      +

      +If you've configured PuTTY to trust at least one certification authority for signing host keys (see section 4.19.4), then it will ask the SSH server to send it any available certified host keys. If the server sends back a certified key signed by a different certification authority, PuTTY will present this variant of the host key prompt, preceded by ‘WARNING - POTENTIAL SECURITY BREACH!’ +

      +

      +One reason why this can happen is a deliberate attack. Just like an ordinary man-in-the-middle attack which substitutes a wrong host key, a particularly ambitious attacker might substitute an entire wrong certification authority, and hope that you connect anyway. +

      +

      +But it's also possible in some situations that this error might arise legitimately. For example, if your organisation's IT department has just rolled out a new CA key which you haven't yet entered in PuTTY's configuration, or if your CA configuration involves two overlapping domains, or something similar. +

      +

      +So, unfortunately, you'll have to work out what to do about it yourself: make an exception for this specific case, or abandon this connection and install a new CA key before trying again (if you're really sure you trust the CA), or edit your configuration in some other way, or just stop trying to use this server. +

      +

      +If you're convinced that this particular server is legitimate even though the CA is not one you trust, PuTTY will let you cache the certified host key, treating it in the same way as an uncertified one. Then that particular certificate will be accepted for future connections to this specific server, even though other certificates signed by the same CA will still be rejected. +

      +

      10.4 ‘SSH protocol version 2 required by our configuration but remote only provides (old, insecure) SSH-1’

      -By default, PuTTY only supports connecting to SSH servers that implement SSH protocol version 2. If you see this message, the server you're trying to connect to only supports the older SSH-1 protocol. +By default, PuTTY only supports connecting to SSH servers that implement SSH protocol version 2. If you see this message, the server you're trying to connect to only supports the older SSH-1 protocol.

      If the server genuinely only supports SSH-1, then you need to either change the ‘SSH protocol version’ setting (see section 4.17.4), or use the -1 command-line option; in any case, you should not treat the resulting connection as secure. @@ -80,9 +100,9 @@

      10.3 ‘SSH protoc

      You might start seeing this message with new versions of PuTTY (from 0.68 onwards) where you didn't before, because it used to be possible to configure PuTTY to automatically fall back from SSH-2 to SSH-1. This is no longer supported, to prevent the possibility of a downgrade attack.

      -

      10.4 ‘The first cipher supported by the server is ... below the configured warning threshold’

      +

      10.5 ‘The first cipher supported by the server is ... below the configured warning threshold’

      -This occurs when the SSH server does not offer any ciphers which you have configured PuTTY to consider strong enough. By default, PuTTY puts up this warning only for Blowfish, single-DES, and Arcfour encryption. +This occurs when the SSH server does not offer any ciphers which you have configured PuTTY to consider strong enough. By default, PuTTY puts up this warning only for Blowfish, single-DES, and Arcfour encryption.

      See section 4.20 for more information on this message. @@ -90,17 +110,17 @@

      10.4 ‘The firs

      (There are similar messages for other cryptographic primitives, such as host key algorithms.)

      -

      10.5 ‘Remote side sent disconnect message type 2 (protocol error): "Too many authentication failures for root"’

      +

      10.6 ‘Remote side sent disconnect message type 2 (protocol error): "Too many authentication failures for root"’

      -This message is produced by an OpenSSH (or Sun SSH) server if it receives more failed authentication attempts than it is willing to tolerate. +This message is produced by an OpenSSH (or Sun SSH) server if it receives more failed authentication attempts than it is willing to tolerate.

      -This can easily happen if you are using Pageant and have a large number of keys loaded into it, since these servers count each offer of a public key as an authentication attempt. This can be worked around by specifying the key that's required for the authentication in the PuTTY configuration (see section 4.21.9); PuTTY will ignore any other keys Pageant may have, but will ask Pageant to do the authentication, so that you don't have to type your passphrase. +This can easily happen if you are using Pageant and have a large number of keys loaded into it, since these servers count each offer of a public key as an authentication attempt. This can be worked around by specifying the key that's required for the authentication in the PuTTY configuration (see section 4.22.1); PuTTY will ignore any other keys Pageant may have, but will ask Pageant to do the authentication, so that you don't have to type your passphrase.

      On the server, this can be worked around by disabling public-key authentication or (for Sun SSH only) by increasing MaxAuthTries in sshd_config.

      -

      10.6 ‘Out of memory’

      +

      10.7 ‘Out of memory’

      This occurs when PuTTY tries to allocate more memory than the system can give it. This may happen for genuine reasons: if the computer really has run out of memory, or if you have configured an extremely large number of lines of scrollback in your terminal. PuTTY is not able to recover from running out of memory; it will terminate immediately after giving this error.

      @@ -111,16 +131,16 @@

      10.6 ‘question A.7.3 in the FAQ).

      -This can also happen in PSCP or PSFTP, if your login scripts on the server generate output: the client program will be expecting an SFTP message starting with a length, and if it receives some text from your login scripts instead it will try to interpret them as a message length. See question A.7.4 for details of this. +This can also happen in PSCP or PSFTP, if your login scripts on the server generate output: the client program will be expecting an SFTP message starting with a length, and if it receives some text from your login scripts instead it will try to interpret them as a message length. See question A.7.4 for details of this.

      -

      10.7 ‘Internal error’, ‘Internal fault’, ‘Assertion failed’

      +

      10.8 ‘Internal error’, ‘Internal fault’, ‘Assertion failed’

      Any error beginning with the word ‘Internal’ should never occur. If it does, there is a bug in PuTTY by definition; please see appendix B and report it to us.

      Similarly, any error message starting with ‘Assertion failed’ is a bug in PuTTY. Please report it to us, and include the exact text from the error message box.

      -

      10.8 ‘Unable to use key file’, ‘Couldn't load private key’, ‘Couldn't load this key’

      +

      10.9 ‘Unable to use key file’, ‘Couldn't load private key’, ‘Couldn't load this key’

      Various forms of this error are printed in the PuTTY window, or written to the PuTTY Event Log (see section 3.1.3.1) when trying public-key authentication, or given by Pageant when trying to load a private key.

      @@ -128,12 +148,12 @@

      10.8 ‘Unable to If you see one of these messages, it often indicates that you've tried to load a key of an inappropriate type into PuTTY, Plink, PSCP, PSFTP, or Pageant.

      -You may have tried to load an SSH-2 key in a ‘foreign’ format (OpenSSH or ssh.com) directly into one of the PuTTY tools, in which case you need to import it into PuTTY's native format (*.PPK) using PuTTYgen – see section 8.2.14. +You may have tried to load an SSH-2 key in a ‘foreign’ format (OpenSSH or ssh.com) directly into one of the PuTTY tools, in which case you need to import it into PuTTY's native format (*.PPK) using PuTTYgen – see section 8.2.15.

      Alternatively, you may have specified a key that's inappropriate for the connection you're making. The SSH-2 and the old SSH-1 protocols require different private key formats, and a SSH-1 key can't be used for a SSH-2 connection (or vice versa).

      -

      10.9 ‘Server refused our key’, ‘Server refused our public key’, ‘Key refused’

      +

      10.10 ‘Server refused our key’, ‘Server refused our public key’, ‘Key refused’

      Various forms of this error are printed in the PuTTY window, or written to the PuTTY Event Log (see section 3.1.3.1) when trying public-key authentication.

      @@ -146,7 +166,7 @@

      10.9 ‘Server refused

      Section 8.3 has some hints on server-side public key setup.

      -

      10.10 ‘Access denied’, ‘Authentication refused’

      +

      10.11 ‘Access denied’, ‘Authentication refused’

      Various forms of this error are printed in the PuTTY window, or written to the PuTTY Event Log (see section 3.1.3.1) during authentication.

      @@ -157,13 +177,13 @@

      10.10 ‘Access It may be worth checking the Event Log for diagnostic messages from the server giving more detail.

      -This error can be caused by buggy SSH-1 servers that fail to cope with the various strategies we use for camouflaging passwords in transit. Upgrade your server, or use the workarounds described in section 4.26.12 and possibly section 4.26.13. +This error can be caused by buggy SSH-1 servers that fail to cope with the various strategies we use for camouflaging passwords in transit. Upgrade your server, or use the workarounds described in section 4.27.13 and possibly section 4.27.14.

      -

      10.11 ‘No supported authentication methods available’

      +

      10.12 ‘No supported authentication methods available’

      This error indicates that PuTTY has run out of ways to authenticate you to an SSH server. This may be because PuTTY has TIS or keyboard-interactive authentication disabled, in which case see section 4.21.5 and section 4.21.6.

      -

      10.12 ‘Incorrect MAC received on packet’ or ‘Incorrect CRC received on packet’

      +

      10.13 ‘Incorrect MAC received on packet’ or ‘Incorrect CRC received on packet’

      This error occurs when PuTTY decrypts an SSH packet and its checksum is not correct. This probably means something has gone wrong in the encryption or decryption process. It's difficult to tell from this error message whether the problem is in the client, in the server, or in between.

      @@ -171,19 +191,19 @@

      10.12 ‘Incorrect

      -Occasionally this has been caused by server bugs. An example is the bug described at section 4.26.9, although you're very unlikely to encounter that one these days. +Occasionally this has been caused by server bugs. An example is the bug described at section 4.27.10, although you're very unlikely to encounter that one these days.

      -In this context MAC stands for Message Authentication Code. It's a cryptographic term, and it has nothing at all to do with Ethernet MAC (Media Access Control) addresses, or with the Apple computer. +In this context MAC stands for Message Authentication Code. It's a cryptographic term, and it has nothing at all to do with Ethernet MAC (Media Access Control) addresses, or with the Apple computer.

      -

      10.13 ‘Incoming packet was garbled on decryption’

      +

      10.14 ‘Incoming packet was garbled on decryption’

      This error occurs when PuTTY decrypts an SSH packet and the decrypted data makes no sense. This probably means something has gone wrong in the encryption or decryption process. It's difficult to tell from this error message whether the problem is in the client, in the server, or in between.

      -If you get this error, one thing you could try would be to fiddle with the setting of ‘Miscomputes SSH-2 encryption keys’ (see section 4.26.11) or ‘Ignores SSH-2 maximum packet size’ (see section 4.26.5) on the Bugs panel. +If you get this error, one thing you could try would be to fiddle with the setting of ‘Miscomputes SSH-2 encryption keys’ (see section 4.27.12) or ‘Ignores SSH-2 maximum packet size’ (see section 4.27.5) on the Bugs panel.

      -

      10.14 ‘PuTTY X11 proxy: various errors

      +

      10.15 ‘PuTTY X11 proxy: various errors

      This family of errors are reported when PuTTY is doing X forwarding. They are sent back to the X application running on the SSH server, which will usually report the error to the user.

      @@ -196,7 +216,7 @@

      10.14 ‘PuTTY X11 p

      If this happens, it is not a problem with PuTTY. You need to arrange for your X authentication data to be passed from the user you logged in as to the user you used su to become. How you do this depends on your particular system; in fact many modern versions of su do it automatically.

      -

      10.15 ‘Network error: Software caused connection abort’

      +

      10.16 ‘Network error: Software caused connection abort’

      This is a generic error produced by the Windows network code when it kills an established connection for some reason. For example, it might happen if you pull the network cable out of the back of an Ethernet-connected computer, or if Windows has any other similar reason to believe the entire network has become unreachable.

      @@ -209,24 +229,24 @@

      10.15 ‘Network e

      We are not aware of any reason why this error might occur that would represent a bug in PuTTY. The problem is between you, your Windows system, your network and the remote system.

      -

      10.16 ‘Network error: Connection reset by peer’

      +

      10.17 ‘Network error: Connection reset by peer’

      This error occurs when the machines at each end of a network connection lose track of the state of the connection between them. For example, you might see it if your SSH server crashes, and manages to reboot fully before you next attempt to send data to it.

      -However, the most common reason to see this message is if you are connecting through a firewall or a NAT router which has timed the connection out. See question A.7.8 in the FAQ for more details. You may be able to improve the situation by using keepalives; see section 4.14.1 for details on this. +However, the most common reason to see this message is if you are connecting through a firewall or a NAT router which has timed the connection out. See question A.7.8 in the FAQ for more details. You may be able to improve the situation by using keepalives; see section 4.14.1 for details on this.

      Note that Windows can produce this error in some circumstances without seeing a connection reset from the server, for instance if the connection to the network is lost.

      -

      10.17 ‘Network error: Connection refused’

      +

      10.18 ‘Network error: Connection refused’

      This error means that the network connection PuTTY tried to make to your server was rejected by the server. Usually this happens because the server does not provide the service which PuTTY is trying to access.

      Check that you are connecting with the correct protocol (SSH, Telnet, etc), and check that the port number is correct. If that fails, consult the administrator of your server.

      -

      10.18 ‘Network error: Connection timed out’

      +

      10.19 ‘Network error: Connection timed out’

      This error means that the network connection PuTTY tried to make to your server received no response at all from the server. Usually this happens because the server machine is completely isolated from the network, or because it is turned off.

      @@ -234,9 +254,9 @@

      10.18 ‘Network Check that you have correctly entered the host name or IP address of your server machine. If that fails, consult the administrator of your server.

      -Unix also generates this error when it tries to send data down a connection and contact with the server has been completely lost during a connection. (There is a delay of minutes before Unix gives up on receiving a reply from the server.) This can occur if you type things into PuTTY while the network is down, but it can also occur if PuTTY decides of its own accord to send data: due to a repeat key exchange in SSH-2 (see section 4.18.2) or due to keepalives (section 4.14.1). +Unix also generates this error when it tries to send data down a connection and contact with the server has been completely lost during a connection. (There is a delay of minutes before Unix gives up on receiving a reply from the server.) This can occur if you type things into PuTTY while the network is down, but it can also occur if PuTTY decides of its own accord to send data: due to a repeat key exchange in SSH-2 (see section 4.18.2) or due to keepalives (section 4.14.1).

      -

      10.19 ‘Network error: Cannot assign requested address’

      +

      10.20 ‘Network error: Cannot assign requested address’

      This means that the operating system rejected the parameters of the network connection PuTTY tried to make, usually without actually trying to connect to anything, because they were simply invalid.

      @@ -244,5 +264,6 @@

      10.19 ‘N A common way to provoke this error is to accidentally try to connect to port 0, which is not a valid port number.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/Chapter2.html b/code/doc/html/Chapter2.html index 7e3d4c83..e2766107 100644 --- a/code/doc/html/Chapter2.html +++ b/code/doc/html/Chapter2.html @@ -71,7 +71,7 @@

      2.2 Verifying To prevent this attack, each server has a unique identifying code, called a host key. These keys are created in a way that prevents one server from forging another server's key. So if you connect to a server and it sends you a different host key from the one you were expecting, PuTTY can warn you that the server may have been switched and that a spoofing attack might be in progress.

      -PuTTY records the host key for each server you connect to, in the Windows Registry. Every time you connect to a server, it checks that the host key presented by the server is the same host key as it was the last time you connected. If it is not, you will see a warning, and you will have the chance to abandon your connection before you type any private information (such as a password) into it. (See section 10.2 for what that looks like.) +PuTTY records the host key for each server you connect to, in the Windows Registry. Every time you connect to a server, it checks that the host key presented by the server is the same host key as it was the last time you connected. If it is not, you will see a stronger warning, and you will have the chance to abandon your connection before you type any private information (such as a password) into it. (See section 10.2 for what that looks like.)

      However, when you connect to a server you have not connected to before, PuTTY has no way of telling whether the host key is the right one or not. So it gives the warning shown above, and asks you whether you want to trust this host key or not. @@ -119,5 +119,6 @@

      2.5 Logging out You can close a PuTTY session using the Close button in the window border, but this might confuse the server - a bit like hanging up a telephone unexpectedly in the middle of a conversation. We recommend you do not do this unless the server has stopped responding to you and you cannot close the window any other way.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/Chapter3.html b/code/doc/html/Chapter3.html index d7811b04..70863cb2 100644 --- a/code/doc/html/Chapter3.html +++ b/code/doc/html/Chapter3.html @@ -172,7 +172,7 @@

      3.1.3.2 Erase Character

      -PuTTY can also be configured to send this when the Backspace key is pressed; see section 4.29.3. +PuTTY can also be configured to send this when the Backspace key is pressed; see section 4.30.3.

    • @@ -198,14 +198,14 @@

      3.1.3.2 Interrupt Process

      -PuTTY can also be configured to send this when Ctrl-C is typed; see section 4.29.3. +PuTTY can also be configured to send this when Ctrl-C is typed; see section 4.30.3.

    • Suspend Process

      -PuTTY can also be configured to send this when Ctrl-Z is typed; see section 4.29.3. +PuTTY can also be configured to send this when Ctrl-Z is typed; see section 4.30.3.

    • @@ -289,7 +289,7 @@

      3.4 Using In order to use this feature, you will need an X display server for your Windows machine, such as Cygwin/X, X-Win32, or Exceed. This will probably install itself as display number 0 on your local machine; if it doesn't, the manual for the X server should tell you what it does do.

      -You should then tick the ‘Enable X11 forwarding’ box in the X11 panel (see section 4.24) before starting your SSH session. The ‘X display location’ box is blank by default, which means that PuTTY will try to use a sensible default such as :0, which is the usual display location where your X server will be installed. If that needs changing, then change it. +You should then tick the ‘Enable X11 forwarding’ box in the X11 panel (see section 4.25) before starting your SSH session. The ‘X display location’ box is blank by default, which means that PuTTY will try to use a sensible default such as :0, which is the usual display location where your X server will be installed. If that needs changing, then change it.

      Now you should be able to log in to the SSH server as normal. To check that X forwarding has been successfully negotiated during connection startup, you can check the PuTTY Event Log (see section 3.1.3.1). It should say something like this: @@ -307,7 +307,7 @@

      3.4 Using If this works, you should then be able to run X applications in the remote session and have them display their windows on your PC.

      -For more options relating to X11 forwarding, see section 4.24. +For more options relating to X11 forwarding, see section 4.25.

      3.5 Using port forwarding in SSH

      @@ -320,7 +320,7 @@

      3.5 Using port number on your local machine where PuTTY should listen for incoming connections. There are likely to be plenty of unused port numbers above 3000. (You can also use a local loopback address here; see below for more details.)
    • -Now, before you start your SSH connection, go to the Tunnels panel (see section 4.25). Make sure the ‘Local’ radio button is set. Enter the local port number into the ‘Source port’ box. Enter the destination host name and port number into the ‘Destination’ box, separated by a colon (for example, popserver.example.com:110 to connect to a POP-3 server). +Now, before you start your SSH connection, go to the Tunnels panel (see section 4.26). Make sure the ‘Local’ radio button is set. Enter the local port number into the ‘Source port’ box. Enter the destination host name and port number into the ‘Destination’ box, separated by a colon (for example, popserver.example.com:110 to connect to a POP-3 server).
    • Now click the ‘Add’ button. The details of your port forwarding should appear in the list box. @@ -358,7 +358,7 @@

      3.5 Using question A.7.17.)

      -For more options relating to port forwarding, see section 4.25. +For more options relating to port forwarding, see section 4.26.

      If the connection you are forwarding over SSH is itself a second SSH connection made by another copy of PuTTY, you might find the ‘logical host name’ configuration option useful to warn PuTTY of which host key it should be expecting. See section 4.14.5 for details of this. @@ -368,7 +368,7 @@

      3.6 Connecting to a local seri PuTTY can connect directly to a local serial line as an alternative to making a network connection. In this mode, text typed into the PuTTY window will be sent straight out of your computer's serial port, and data received through that port will be displayed in the PuTTY window. You might use this mode, for example, if your serial port is connected to another computer which has a serial connection.

      -To make a connection of this type, simply select ‘Serial’ from the ‘Connection type’ radio buttons on the ‘Session’ configuration panel (see section 4.1.1). The ‘Host Name’ and ‘Port’ boxes will transform into ‘Serial line’ and ‘Speed’, allowing you to specify which serial line to use (if your computer has more than one) and what speed (baud rate) to use when transferring data. For further configuration options (data bits, stop bits, parity, flow control), you can use the ‘Serial’ configuration panel (see section 4.28). +To make a connection of this type, simply select ‘Serial’ from the ‘Connection type’ radio buttons on the ‘Session’ configuration panel (see section 4.1.1). The ‘Host Name’ and ‘Port’ boxes will transform into ‘Serial line’ and ‘Speed’, allowing you to specify which serial line to use (if your computer has more than one) and what speed (baud rate) to use when transferring data. For further configuration options (data bits, stop bits, parity, flow control), you can use the ‘Serial’ configuration panel (see section 4.29).

      After you start up PuTTY in serial mode, you might find that you have to make the first move, by sending some data out of the serial line in order to notify the device at the other end that someone is there for it to talk to. This probably depends on the device. If you start up a PuTTY serial session and nothing appears in the window, try pressing Return a few times and see if that helps. @@ -417,7 +417,7 @@

      3.10 Connecting using the

      -To make a connection of this type, select ‘SUPDUP’ from the ‘Connection type’ radio buttons on the ‘Session’ panel (see section 4.1.1). For further configuration options (character set, more processing, scrolling), you can use the ‘SUPDUP’ configuration panel (see section 4.31). +To make a connection of this type, select ‘SUPDUP’ from the ‘Connection type’ radio buttons on the ‘Session’ panel (see section 4.1.1). For further configuration options (character set, more processing, scrolling), you can use the ‘SUPDUP’ configuration panel (see section 4.32).

      In SUPDUP, terminal emulation is more integrated with the network protocol than in other protocols such as SSH. The SUPDUP protocol can thus only be used with PuTTY proper, not with the command-line tool Plink. @@ -493,7 +493,7 @@

      3.11.3.2 Select -ssh selects the SSH protocol.

    • --ssh-connection selects the bare ssh-connection protocol. (This is only useful in specialised circumstances; see section 4.27 for more information.) +-ssh-connection selects the bare ssh-connection protocol. (This is only useful in specialised circumstances; see section 4.28 for more information.)
    • -telnet selects the Telnet protocol. @@ -530,7 +530,7 @@

      3.11.3.4 3.11.3.5 -L, -R and -D: set up port forwardings

      -As well as setting up port forwardings in the PuTTY configuration (see section 4.25), you can also set up forwardings on the command line. The command-line options work just like the ones in Unix ssh programs. +As well as setting up port forwardings in the PuTTY configuration (see section 4.26), you can also set up forwardings on the command line. The command-line options work just like the ones in Unix ssh programs.

      To forward a local port (say 5110) to a remote destination (say popserver.example.com port 110), you can write something like one of these: @@ -627,7 +627,7 @@

      3.11.3.11 section 3.4.

      -These options are equivalent to the X11 forwarding checkbox in the X11 panel of the PuTTY configuration box (see section 4.24). +These options are equivalent to the X11 forwarding checkbox in the X11 panel of the PuTTY configuration box (see section 4.25).

      These options are not available in the file transfer tools PSCP and PSFTP. @@ -637,7 +637,7 @@

      3.11.3.12 section 4.23.1). +These options are equivalent to the ‘Don't allocate a pseudo-terminal’ checkbox in the SSH panel of the PuTTY configuration box (see section 4.24.1).

      These options are not available in the file transfer tools PSCP and PSFTP. @@ -708,31 +708,38 @@

      3.11.3.18 public-key authentication, see chapter 8.

      -This option is equivalent to the ‘Private key file for authentication’ box in the Auth panel of the PuTTY configuration box (see section 4.21.9). +This option is equivalent to the ‘Private key file for authentication’ box in the Auth panel of the PuTTY configuration box (see section 4.22.1).

      -

      3.11.3.19 -no-trivial-auth: disconnect if SSH authentication succeeds trivially

      +

      3.11.3.19 -cert: specify an SSH certificate

      +

      +The -cert option allows you to specify the name of a certificate file containing a signed version of your public key. If you specify this option, PuTTY will present that certificate in place of the plain public key, whenever it tries to authenticate with a key that matches. (This applies whether the key is stored in Pageant or loaded directly from a file by PuTTY.) +

      +

      +This option is equivalent to the ‘Certificate to use with the private key’ box in the Auth panel of the PuTTY configuration box (see section 4.22.2). +

      +

      3.11.3.20 -no-trivial-auth: disconnect if SSH authentication succeeds trivially

      This option causes PuTTY to abandon an SSH session if the server accepts authentication without ever having asked for any kind of password or signature or token.

      See section 4.21.3 for why you might want this.

      -

      3.11.3.20 -loghost: specify a logical host name

      +

      3.11.3.21 -loghost: specify a logical host name

      -This option overrides PuTTY's normal SSH host key caching policy by telling it the name of the host you expect your connection to end up at (in cases where this differs from the location PuTTY thinks it's connecting to). It can be a plain host name, or a host name followed by a colon and a port number. See section 4.14.5 for more detail on this. +This option overrides PuTTY's normal SSH host key caching policy by telling it the name of the host you expect your connection to end up at (in cases where this differs from the location PuTTY thinks it's connecting to). It can be a plain host name, or a host name followed by a colon and a port number. See section 4.14.5 for more detail on this.

      -

      3.11.3.21 -hostkey: manually specify an expected host key

      +

      3.11.3.22 -hostkey: manually specify an expected host key

      -This option overrides PuTTY's normal SSH host key caching policy by telling it exactly what host key to expect, which can be useful if the normal automatic host key store in the Registry is unavailable. The argument to this option should be either a host key fingerprint, or an SSH-2 public key blob. See section 4.19.3 for more information. +This option overrides PuTTY's normal SSH host key caching policy by telling it exactly what host key to expect, which can be useful if the normal automatic host key store in the Registry is unavailable. The argument to this option should be either a host key fingerprint, or an SSH-2 public key blob. See section 4.19.3 for more information.

      You can specify this option more than once if you want to configure more than one key to be accepted.

      -

      3.11.3.22 -pgpfp: display PGP key fingerprints

      +

      3.11.3.23 -pgpfp: display PGP key fingerprints

      -This option causes the PuTTY tools not to run as normal, but instead to display the fingerprints of the PuTTY PGP Master Keys, in order to aid with verifying new versions. See appendix F for more information. +This option causes the PuTTY tools not to run as normal, but instead to display the fingerprints of the PuTTY PGP Master Keys, in order to aid with verifying new versions. See appendix F for more information.

      -

      3.11.3.23 -sercfg: specify serial port configuration

      +

      3.11.3.24 -sercfg: specify serial port configuration

      This option specifies the configuration parameters for the serial port (baud rate, stop bits etc). Its argument is interpreted as a comma-separated list of configuration options, which can be as follows:

      @@ -755,9 +762,9 @@

      3.11.3.23 For example, ‘-sercfg 19200,8,n,1,N’ denotes a baud rate of 19200, 8 data bits, no parity, 1 stop bit and no flow control.

      -

      3.11.3.24 -sessionlog, -sshlog, -sshrawlog: enable session logging

      +

      3.11.3.25 -sessionlog, -sshlog, -sshrawlog: enable session logging

      -These options cause the PuTTY network tools to write out a log file. Each of them expects a file name as an argument, e.g. ‘-sshlog putty.log’ causes an SSH packet log to be written to a file called ‘putty.log’. The three different options select different logging modes, all available from the GUI too: +These options cause the PuTTY network tools to write out a log file. Each of them expects a file name as an argument, e.g. ‘-sshlog putty.log’ causes an SSH packet log to be written to a file called ‘putty.log’. The three different options select different logging modes, all available from the GUI too:

      • -sessionlog selects ‘All session output’ logging mode. @@ -772,18 +779,18 @@

        3.11.3.24 For more information on logging configuration, see section 4.2.

        -

        3.11.3.25 -logoverwrite, -logappend: control behaviour with existing log file

        +

        3.11.3.26 -logoverwrite, -logappend: control behaviour with existing log file

        If logging has been enabled (in the saved configuration, or by another command-line option), and the specified log file already exists, these options tell the PuTTY network tools what to do so that they don't have to ask the user. See section 4.2.2 for details.

        -

        3.11.3.26 -proxycmd: specify a local proxy command

        +

        3.11.3.27 -proxycmd: specify a local proxy command

        -This option enables PuTTY's mode for running a command on the local machine and using it as a proxy for the network connection. It expects a shell command string as an argument. +This option enables PuTTY's mode for running a command on the local machine and using it as a proxy for the network connection. It expects a shell command string as an argument.

        See section 4.16.1 for more information on this, and on other proxy settings. In particular, note that since the special sequences described there are understood in the argument string, literal backslashes must be doubled (if you want \ in your command, you must put \\ on the command line).

        -

        3.11.3.27 -restrict-acl: restrict the Windows process ACL

        +

        3.11.3.28 -restrict-acl: restrict the Windows process ACL

        This option (on Windows only) causes PuTTY (or another PuTTY tool) to try to lock down the operating system's access control on its own process. If this succeeds, it should present an extra obstacle to malware that has managed to run under the same user id as the PuTTY process, by preventing it from attaching to PuTTY using the same interfaces debuggers use and either reading sensitive information out of its memory or hijacking its network session.

        @@ -794,8 +801,13 @@

        3.11.3.27 A PuTTY process started with -restrict-acl will pass that on to any processes started with Duplicate Session, New Session etc. (However, if you're invoking PuTTY tools explicitly, for instance as a proxy command, you'll need to arrange to pass them the -restrict-acl option yourself, if that's what you want.)

        -If Pageant is started with the -restrict-acl option, and you use it to launch a PuTTY session from its System Tray submenu, then Pageant will not default to starting the PuTTY subprocess with a restricted ACL. This is because PuTTY is more likely to suffer reduced functionality as a result of restricted ACLs (e.g. screen reader software will have a greater need to interact with it), whereas Pageant stores the more critical information (hence benefits more from the extra protection), so it's reasonable to want to run Pageant but not PuTTY with the ACL restrictions. You can force Pageant to start subsidiary PuTTY processes with a restricted ACL if you also pass the -restrict-putty-acl option. +If Pageant is started with the -restrict-acl option, and you use it to launch a PuTTY session from its System Tray submenu, then Pageant will not default to starting the PuTTY subprocess with a restricted ACL. This is because PuTTY is more likely to suffer reduced functionality as a result of restricted ACLs (e.g. screen reader software will have a greater need to interact with it), whereas Pageant stores the more critical information (hence benefits more from the extra protection), so it's reasonable to want to run Pageant but not PuTTY with the ACL restrictions. You can force Pageant to start subsidiary PuTTY processes with a restricted ACL if you also pass the -restrict-putty-acl option. +

        +

        3.11.3.29 -host-ca: launch the host CA configuration

        +

        +If you start PuTTY with the -host-ca option, it will not launch a session at all. Instead, it will just display the configuration dialog box for host certification authorities, as described in section 4.19.4. When you dismiss that dialog box, PuTTY will terminate.

        -

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +[PuTTY release 0.78]
        diff --git a/code/doc/html/Chapter4.html b/code/doc/html/Chapter4.html index faaf54be..eee53dcc 100644 --- a/code/doc/html/Chapter4.html +++ b/code/doc/html/Chapter4.html @@ -153,7 +153,7 @@
      • 4.16.2 Excluding parts of the network from proxying
      • 4.16.3 Name resolution when using a proxy
      • 4.16.4 Username and password
      • -
      • 4.16.5 Specifying the Telnet or Local proxy command
      • +
      • 4.16.5 Specifying the Telnet, SSH, or Local proxy command
      • 4.16.6 Controlling proxy logging
    • 4.17 The SSH panel @@ -174,6 +174,7 @@
    • 4.19.1 Host key type selection
    • 4.19.2 Preferring known host keys
    • 4.19.3 Manually configuring host keys
    • +
    • 4.19.4 Configuring PuTTY to accept host certificates
  • 4.20 The Cipher panel
  • 4.21 The Auth panel @@ -186,74 +187,80 @@
  • 4.21.6 ‘Attempt keyboard-interactive authentication’
  • 4.21.7 ‘Allow agent forwarding’
  • 4.21.8 ‘Allow attempted changes of username in SSH-2’
  • -
  • 4.21.9 ‘Private key file for authentication’
  • -
  • 4.22 The GSSAPI panel +
  • 4.22 The Credentials panel
  • -
  • 4.23 The TTY panel +
  • 4.23 The GSSAPI panel
  • -
  • 4.24 The X11 panel +
  • 4.24 The TTY panel
  • -
  • 4.25 The Tunnels panel +
  • 4.25 The X11 panel
  • -
  • 4.26 The Bugs and More Bugs panels +
  • 4.26 The Tunnels panel
  • -
  • 4.27 The ‘Bare ssh-connection’ protocol
  • -
  • 4.28 The Serial panel +
  • 4.27 The Bugs and More Bugs panels
  • -
  • 4.29 The Telnet panel +
  • 4.28 The ‘Bare ssh-connection’ protocol
  • +
  • 4.29 The Serial panel
  • -
  • 4.30 The Rlogin panel +
  • 4.30 The Telnet panel
  • -
  • 4.31 The SUPDUP panel +
  • 4.31 The Rlogin panel
  • -
  • 4.32 Storing configuration in a file
  • +
  • 4.32 The SUPDUP panel +
  • +
  • 4.33 Storing configuration in a file
  • Chapter 4: Configuring PuTTY

    @@ -295,7 +302,7 @@

    4.1.1 The section 3.10 for information about using SUPDUP.
  • -The ‘Bare ssh-connection’ option in the ‘Connection type’ control is intended for specialist uses not involving network connections. See section 4.27 for some information about it. +The ‘Bare ssh-connection’ option in the ‘Connection type’ control is intended for specialist uses not involving network connections. See section 4.28 for some information about it.
  • @@ -305,7 +312,7 @@

    4.1.1 The

    -If you select ‘Serial’ from the ‘Connection type’ radio buttons, the ‘Host Name’ and ‘Port’ boxes are replaced by ‘Serial line’ and ‘Speed’; see section 4.28 for more details of these. +If you select ‘Serial’ from the ‘Connection type’ radio buttons, the ‘Host Name’ and ‘Port’ boxes are replaced by ‘Serial line’ and ‘Speed’; see section 4.29 for more details of these.

    4.1.2 Loading and storing saved sessions

    @@ -351,7 +358,7 @@

    4.1.2 Load
    HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions
     

    -If you need to store them in a file, you could try the method described in section 4.32. +If you need to store them in a file, you could try the method described in section 4.33.

    4.1.3 ‘Close window on exit’

    @@ -596,7 +603,7 @@

    4.4.1 Changing the actio Some terminals believe that the Backspace key should send the same thing to the server as Control-H (ASCII code 8). Other terminals believe that the Backspace key should send ASCII code 127 (usually known as Control-?) so that it can be distinguished from Control-H. This option allows you to choose which code PuTTY generates when you press Backspace.

    -If you are connecting over SSH, PuTTY by default tells the server the value of this option (see section 4.23.2), so you may find that the Backspace key does the right thing either way. Similarly, if you are connecting to a Unix system, you will probably find that the Unix stty command lets you configure which the server expects to see, so again you might not need to change which one PuTTY generates. On other systems, the server's expectation might be fixed and you might have no choice but to configure PuTTY. +If you are connecting over SSH, PuTTY by default tells the server the value of this option (see section 4.24.2), so you may find that the Backspace key does the right thing either way. Similarly, if you are connecting to a Unix system, you will probably find that the Unix stty command lets you configure which the server expects to see, so again you might not need to change which one PuTTY generates. On other systems, the server's expectation might be fixed and you might have no choice but to configure PuTTY.

    If you do have the choice, we recommend configuring PuTTY to generate Control-? and configuring the server to expect it, because that allows applications such as emacs to use Control-H for help. @@ -1307,7 +1314,7 @@

    4.14.1 Using section 4.14.3.)

    -Note that if you are using SSH-1 and the server has a bug that makes it unable to deal with SSH-1 ignore messages (see section 4.26.12), enabling keepalives will have no effect. +Note that if you are using SSH-1 and the server has a bug that makes it unable to deal with SSH-1 ignore messages (see section 4.27.13), enabling keepalives will have no effect.

    4.14.2 ‘Disable Nagle's algorithm’

    @@ -1341,7 +1348,7 @@

    4.14.3 ‘Enab

    4.14.4 ‘Internet protocol version’

    -This option allows the user to select between the old and new Internet protocols and addressing schemes (IPv4 and IPv6). The selected protocol will be used for most outgoing network connections (including connections to proxies); however, tunnels have their own configuration, for which see section 4.25.2. +This option allows the user to select between the old and new Internet protocols and addressing schemes (IPv4 and IPv6). The selected protocol will be used for most outgoing network connections (including connections to proxies); however, tunnels have their own configuration, for which see section 4.26.2.

    The default setting is ‘Auto’, which means PuTTY will do something sensible and try to guess which protocol you wanted. (If you specify a literal Internet address, it will use whichever protocol that address implies. If you provide a hostname, it will see what kinds of address exist for that hostname; it will use IPv6 if there is an IPv6 address available, and fall back to IPv4 if not.) @@ -1387,7 +1394,7 @@

    4.15.2 Use of s When the previous box (section 4.15.1) is left blank, by default, PuTTY will prompt for a username at the time you make a connection.

    -In some environments, such as the networks of large organisations implementing single sign-on, a more sensible default may be to use the name of the user logged in to the local operating system (if any); this is particularly likely to be useful with GSSAPI key exchange and user authentication (see section 4.22 and section 4.18.1.1). This control allows you to change the default behaviour. +In some environments, such as the networks of large organisations implementing single sign-on, a more sensible default may be to use the name of the user logged in to the local operating system (if any); this is particularly likely to be useful with GSSAPI key exchange and user authentication (see section 4.23 and section 4.18.1.1). This control allows you to change the default behaviour.

    The current system username is displayed in the dialog as a convenience. It is not saved in the configuration; if a saved session is later used by a different user, that user's name will be used. @@ -1440,10 +1447,10 @@

    4.16 The Proxy panel

    4.16.1 Setting the proxy type

    -The ‘Proxy type’ radio buttons allow you to configure what type of proxy you want PuTTY to use for its network connections. The default setting is ‘None’; in this mode no proxy is used for any connection. +The ‘Proxy type’ drop-down allows you to configure what type of proxy you want PuTTY to use for its network connections. The default setting is ‘None’; in this mode no proxy is used for any connection.

    • -Selecting ‘HTTP’ allows you to proxy your connections through a web server supporting the HTTP CONNECT command, as documented in RFC 2817. +Selecting ‘HTTP CONNECT’ allows you to proxy your connections through a web server supporting the HTTP CONNECT command, as documented in RFC 2817.
    • Selecting ‘SOCKS 4’ or ‘SOCKS 5’ allows you to proxy your connections through a SOCKS server. @@ -1452,19 +1459,29 @@

      4.16.1 Setting the pro Many firewalls implement a less formal type of proxy in which a user can make a Telnet or TCP connection directly to the firewall machine and enter a command such as connect myhost.com 22 to connect through to an external host. Selecting ‘Telnet’ allows you to tell PuTTY to use this type of proxy, with the precise command specified as described in section 4.16.5.

    • -Selecting ‘SSH’ causes PuTTY to make a secondary SSH connection to the proxy host (sometimes called a ‘jump host’ in this context), and then open a port-forwarding channel to the final destination host. +There are several ways to use a SSH server as a proxy. All of these cause PuTTY to make a secondary SSH connection to the proxy host (sometimes called a ‘jump host’ in this context).

      -The ‘Proxy hostname’ field will be interpreted as the name of a PuTTY saved session if one exists, or a hostname if not. This allows multi-hop jump paths, if the referenced saved session is itself configured to use an SSH proxy; and it allows combining SSH and non-SSH proxying. +The ‘Proxy hostname’ field will be interpreted as the name of a PuTTY saved session if one exists, or a hostname if not. This allows multi-hop jump paths, if the referenced saved session is itself configured to use an SSH proxy; and it allows combining SSH and non-SSH proxying.

      +
      • +‘SSH to proxy and use port forwarding’ causes PuTTY to use the secondary SSH connection to open a port-forwarding channel to the final destination host (similar to OpenSSH's -J option). +
      • +
      • +‘SSH to proxy and execute a command’ causes PuTTY to run an arbitrary remote command on the proxy SSH server and use that command's standard input and output streams to run the primary connection over. The remote command line is specified as described in section 4.16.5. +
      • +
      • +‘SSH to proxy and invoke a subsystem’ is similar but causes PuTTY to start an SSH ‘subsystem’ rather than an ordinary command line. This might be useful with a specially set up SSH proxy server. +
      • +
    • -Selecting ‘Local’ allows you to specify an arbitrary command on the local machine to act as a proxy. When the session is started, instead of creating a TCP connection, PuTTY runs the command (specified in section 4.16.5), and uses its standard input and output streams. +Selecting ‘Local’ allows you to specify an arbitrary command on the local machine to act as a proxy. When the session is started, instead of creating a TCP connection, PuTTY runs the command (specified in section 4.16.5), and uses its standard input and output streams.

      This could be used, for instance, to talk to some kind of network proxy that PuTTY does not natively support; or you could tunnel a connection over something other than TCP/IP entirely.

      -You can also enable this mode on the command line; see section 3.11.3.26. +You can also enable this mode on the command line; see section 3.11.3.27.

    • @@ -1492,14 +1509,14 @@

      4.16.2 Excluding pa This excludes both of the above ranges at once.

      -Connections to the local host (the host name localhost, and any loopback IP address) are never proxied, even if the proxy exclude list does not explicitly contain them. It is very unlikely that this behaviour would ever cause problems, but if it does you can change it by enabling ‘Consider proxying local host connections’. +Connections to the local host (the host name localhost, and any loopback IP address) are never proxied, even if the proxy exclude list does not explicitly contain them. It is very unlikely that this behaviour would ever cause problems, but if it does you can change it by enabling ‘Consider proxying local host connections’.

      -Note that if you are doing DNS at the proxy (see section 4.16.3), you should make sure that your proxy exclusion settings do not depend on knowing the IP address of a host. If the name is passed on to the proxy without PuTTY looking it up, it will never know the IP address and cannot check it against your list. +Note that if you are doing DNS at the proxy (see section 4.16.3), you should make sure that your proxy exclusion settings do not depend on knowing the IP address of a host. If the name is passed on to the proxy without PuTTY looking it up, it will never know the IP address and cannot check it against your list.

      -

      4.16.3 Name resolution when using a proxy

      +

      4.16.3 Name resolution when using a proxy

      -If you are using a proxy to access a private network, it can make a difference whether DNS name resolution is performed by PuTTY itself (on the client machine) or performed by the proxy. +If you are using a proxy to access a private network, it can make a difference whether DNS name resolution is performed by PuTTY itself (on the client machine) or performed by the proxy.

      The ‘Do DNS name lookup at proxy end’ configuration option allows you to control this. If you set it to ‘No’, PuTTY will always do its own DNS, and will always pass an IP address to the proxy. If you set it to ‘Yes’, PuTTY will always pass host names straight to the proxy without trying to look them up first. @@ -1514,14 +1531,14 @@

      4.16.3 < The original SOCKS 4 protocol does not support proxy-side DNS. There is a protocol extension (SOCKS 4A) which does support it, but not all SOCKS 4 servers provide this extension. If you enable proxy DNS and your SOCKS 4 server cannot deal with it, this might be why.

      -If you want to avoid PuTTY making any DNS query related to your destination host name (for example, because your local DNS resolver is very slow to return a negative response in that situation), then as well as setting this control to ‘Yes’, you may also need to turn off GSSAPI authentication and GSSAPI key exchange in SSH (see section 4.22 and section 4.18.1.1 respectively). This is because GSSAPI setup also involves a DNS query for the destination host name, and that query is performed by the separate GSSAPI library, so PuTTY can't override or reconfigure it. +If you want to avoid PuTTY making any DNS query related to your destination host name (for example, because your local DNS resolver is very slow to return a negative response in that situation), then as well as setting this control to ‘Yes’, you may also need to turn off GSSAPI authentication and GSSAPI key exchange in SSH (see section 4.23 and section 4.18.1.1 respectively). This is because GSSAPI setup also involves a DNS query for the destination host name, and that query is performed by the separate GSSAPI library, so PuTTY can't override or reconfigure it.

      -

      4.16.4 Username and password

      +

      4.16.4 Username and password

      -You can enter a username and a password in the ‘Username’ and ‘Password’ boxes, which will be used if your proxy requires authentication. +You can enter a username and a password in the ‘Username’ and ‘Password’ boxes, which will be used if your proxy requires authentication.

      -Note that if you save your session, the proxy password will be saved in plain text, so anyone who can access your PuTTY configuration data will be able to discover it. +Note that if you save your session, the proxy password will be saved in plain text, so anyone who can access your PuTTY configuration data will be able to discover it.

      If PuTTY discovers that it needs a proxy username or password and you have not specified one here, PuTTY will prompt for it interactively in the terminal window. @@ -1532,10 +1549,10 @@

      4.16.4 -

      4.16.5 Specifying the Telnet or Local proxy command

      +

      4.16.5 Specifying the Telnet, SSH, or Local proxy command

      +

      +If you are using the Telnet proxy type, the usual command required by the firewall's Telnet server is connect, followed by a host name and a port number. If your proxy needs a different command, you can enter an alternative in the ‘Command to send to proxy’ box. +

      -If you are using the Telnet proxy type, the usual command required by the firewall's Telnet server is connect, followed by a host name and a port number. If your proxy needs a different command, you can enter an alternative here. +If you are using the Local proxy type, the local command to run is specified here.

      -If you are using the Local proxy type, the local command to run is specified here. +If you are using the ‘SSH to proxy and execute a command’ type, the command to run on the SSH proxy server is specified here. Similarly, if you are using ‘SSH to proxy and invoke a subsystem’, the subsystem name is constructed as specified here.

      In this string, you can use \n to represent a new-line, \r to represent a carriage return, \t to represent a tab character, and \x followed by two hex digits to represent any other character. \\ is used to encode the \ character itself.

      -Also, the special strings %host and %port will be replaced by the host name and port number you want to connect to. The strings %user and %pass will be replaced by the proxy username and password (which, if not specified in the configuration, will be prompted for). The strings %proxyhost and %proxyport will be replaced by the host details specified on the Proxy panel, if any (this is most likely to be useful for the Local proxy type). To get a literal % sign, enter %%. +Also, the special strings %host and %port will be replaced by the host name and port number you want to connect to. For Telnet and Local proxy types, the strings %user and %pass will be replaced by the proxy username and password (which, if not specified in the configuration, will be prompted for) – this does not happen with SSH proxy types (because the proxy username/password are used for SSH authentication). The strings %proxyhost and %proxyport will be replaced by the host details specified on the Proxy panel, if any (this is most likely to be useful for proxy types using a local or remote command). To get a literal % sign, enter %%.

      If a Telnet proxy server prompts for a username and password before commands can be sent, you can use a command such as: @@ -1571,7 +1591,7 @@

      4.16.5 Specifying t

      This will send your username and password as the first two lines to the proxy, followed by a command to connect to the desired host and port. Note that if you do not include the %user or %pass tokens in the Telnet command, then anything specified in ‘Username’ and ‘Password’ configuration fields will be ignored.

      -

      4.16.6 Controlling proxy logging

      +

      4.16.6 Controlling proxy logging

      Often the proxy interaction has its own diagnostic output; this is particularly the case for local proxy commands.

      @@ -1583,21 +1603,21 @@

      4.16.6 Controlling

      4.17 The SSH panel

      -The SSH panel allows you to configure options that only apply to SSH sessions. +The SSH panel allows you to configure options that only apply to SSH sessions.

      4.17.1 Executing a specific command on the server

      -In SSH, you don't have to run a general shell session on the server. Instead, you can choose to run a single specific command (such as a mail user agent, for example). If you want to do this, enter the command in the ‘Remote command’ box. +In SSH, you don't have to run a general shell session on the server. Instead, you can choose to run a single specific command (such as a mail user agent, for example). If you want to do this, enter the command in the ‘Remote command’ box.

      Note that most servers will close the session after executing the command.

      -

      4.17.2 ‘Don't start a shell or command at all’

      +

      4.17.2 ‘Don't start a shell or command at all’

      -If you tick this box, PuTTY will not attempt to run a shell or command after connecting to the remote server. You might want to use this option if you are only using the SSH connection for port forwarding, and your user account on the server does not have the ability to run a shell. +If you tick this box, PuTTY will not attempt to run a shell or command after connecting to the remote server. You might want to use this option if you are only using the SSH connection for port forwarding, and your user account on the server does not have the ability to run a shell.

      -This feature is only available in SSH protocol version 2 (since the version 1 protocol assumes you will always want to run a shell). +This feature is only available in SSH protocol version 2 (since the version 1 protocol assumes you will always want to run a shell).

      This feature can also be enabled using the -N command-line option; see section 3.11.3.13. @@ -1605,13 +1625,13 @@

      4.17.2 ‘Don't s

      If you use this feature in Plink, you will not be able to terminate the Plink process by any graceful means; the only way to kill it will be by pressing Control-C or sending a kill signal from another program.

      -

      4.17.3 ‘Enable compression’

      +

      4.17.3 ‘Enable compression’

      -This enables data compression in the SSH connection: data sent by the server is compressed before sending, and decompressed at the client end. Likewise, data sent by PuTTY to the server is compressed first and the server decompresses it at the other end. This can help make the most of a low-bandwidth connection. +This enables data compression in the SSH connection: data sent by the server is compressed before sending, and decompressed at the client end. Likewise, data sent by PuTTY to the server is compressed first and the server decompresses it at the other end. This can help make the most of a low-bandwidth connection.

      -

      4.17.4 ‘SSH protocol version’

      +

      4.17.4 ‘SSH protocol version’

      -This allows you to select whether to use SSH protocol version 2 or the older version 1. +This allows you to select whether to use SSH protocol version 2 or the older version 1.

      You should normally leave this at the default of ‘2’. As well as having fewer features, the older SSH-1 protocol is no longer developed, has many known cryptographic weaknesses, and is generally not considered to be secure. PuTTY's protocol 1 implementation is provided mainly for compatibility, and is no longer being enhanced. @@ -1655,10 +1675,10 @@

      4.17.5 Sharing an SSH

      4.18 The Kex panel

      -The Kex panel (short for ‘key exchange’) allows you to configure options related to SSH-2 key exchange. +The Kex panel (short for ‘key exchange’) allows you to configure options related to SSH-2 key exchange.

      -Key exchange occurs at the start of an SSH connection (and occasionally thereafter); it establishes a shared secret that is used as the basis for all of SSH's security features. It is therefore very important for the security of the connection that the key exchange is secure. +Key exchange occurs at the start of an SSH connection (and occasionally thereafter); it establishes a shared secret that is used as the basis for all of SSH's security features. It is therefore very important for the security of the connection that the key exchange is secure.

      Key exchange is a cryptographically intensive process; if either the client or the server is a relatively slow machine, the slower methods may take several tens of seconds to complete. @@ -1672,7 +1692,7 @@

      4.18 The Kex panel

      This entire panel is only relevant to SSH protocol version 2; none of these settings affect SSH-1 at all.

      -

      4.18.1 Key exchange algorithm selection

      +

      4.18.1 Key exchange algorithm selection

      PuTTY supports a variety of SSH-2 key exchange methods, and allows you to choose which one you prefer to use; configuration is similar to cipher selection (see section 4.20).

      @@ -1680,19 +1700,39 @@

      4.18.1 elliptic curve Diffie-Hellman key exchange. +‘NTRU Prime / Curve25519 hybrid’: ‘Streamlined NTRU Prime’ is a lattice-based algorithm intended to resist quantum attacks. In this key exchange method, it is run in parallel with a conventional Curve25519-based method (one of those included in ‘ECDH’), in such a way that it should be no less secure than that commonly-used method, and hopefully also resistant to a new class of attacks. + +
    • +‘ECDH’: elliptic curve Diffie-Hellman key exchange, with a variety of standard curves and hash algorithms. +
    • +
    • +The original form of Diffie-Hellman key exchange, with a variety of well-known groups and hashes: +
      • +‘Group 18’, a well-known 8192-bit group, used with the SHA-512 hash function.
      • -‘Group 14’: Diffie-Hellman key exchange with a well-known 2048-bit group. +‘Group 17’, a well-known 6144-bit group, used with the SHA-512 hash function.
      • -‘Group 1’: Diffie-Hellman key exchange with a well-known 1024-bit group. We no longer recommend using this method, and it's not used by default in new installations; however, it may be the only method supported by very old server software. +‘Group 16’, a well-known 4096-bit group, used with the SHA-512 hash function.
      • -‘Group exchange’: with this method, instead of using a fixed group, PuTTY requests that the server suggest a group to use for key exchange; the server can avoid groups known to be weak, and possibly invent new ones over time, without any changes required to PuTTY's configuration. We recommend use of this method instead of the well-known groups, if possible. +‘Group 15’, a well-known 3072-bit group, used with the SHA-512 hash function.
      • -‘RSA key exchange’: this requires much less computational effort on the part of the client, and somewhat less on the part of the server, than Diffie-Hellman key exchange. +‘Group 14’: a well-known 2048-bit group, used with the SHA-256 hash function or, if the server doesn't support that, SHA-1. +
      • +
      • +‘Group 1’: a well-known 1024-bit group, used with the SHA-1 hash function. Neither we nor current SSH standards recommend using this method any longer, and it's not used by default in new installations; however, it may be the only method supported by very old server software. +
      • +
      + +
    • +
    • +‘Diffie-Hellman group exchange’: with this method, instead of using a fixed group, PuTTY requests that the server suggest a group to use for a subsequent Diffie-Hellman key exchange; the server can avoid groups known to be weak, and possibly invent new ones over time, without any changes required to PuTTY's configuration. This key exchange method uses the SHA-256 hash or, if the server doesn't support that, SHA-1. +
    • +
    • +‘RSA-based key exchange’: this requires much less computational effort on the part of the client, and somewhat less on the part of the server, than Diffie-Hellman key exchange.
    • ‘GSSAPI key exchange’: see section 4.18.1.1. @@ -1706,18 +1746,18 @@

      4.18.1.1 GSSAPI- PuTTY supports a set of key exchange methods that also incorporates GSSAPI-based authentication. They are enabled with the ‘Attempt GSSAPI key exchange’ checkbox (which also appears on the ‘GSSAPI’ panel).

      -PuTTY can only perform the GSSAPI-authenticated key exchange methods when using Kerberos V5, and not other GSSAPI mechanisms. If the user running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. +PuTTY can only perform the GSSAPI-authenticated key exchange methods when using Kerberos V5, and not other GSSAPI mechanisms. If the user running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. There's a GSSAPI-based equivalent to most of the ordinary methods listed in section 4.18.1; server support determines which one will be used. (PuTTY's preference order for GSSAPI-authenticated key exchange methods is fixed, not controlled by the preference list.)

      -The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation (see section 4.22.1). The SSH key exchange can be repeated later in the session, and this allows your Kerberos V5 credentials (which are typically short-lived) to be automatically re-delegated to the server when they are refreshed on the client. (This feature is commonly referred to as ‘cascading credentials’.) +The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation (see section 4.23.1). The SSH key exchange can be repeated later in the session, and this allows your Kerberos V5 credentials (which are typically short-lived) to be automatically re-delegated to the server when they are refreshed on the client. (This feature is commonly referred to as ‘cascading credentials’.)

      -If your server doesn't support GSSAPI key exchange, it may still support GSSAPI in the SSH user authentication phase. This will still let you log in using your Kerberos credentials, but will only allow you to delegate the credentials that are active at the beginning of the session; they can't be refreshed automatically later, in a long-running session. +If your server doesn't support GSSAPI key exchange, it may still support GSSAPI in the SSH user authentication phase. This will still let you log in using your Kerberos credentials, but will only allow you to delegate the credentials that are active at the beginning of the session; they can't be refreshed automatically later, in a long-running session. See section 4.23 for how to control GSSAPI user authentication in PuTTY.

      Another effect of GSSAPI key exchange is that it replaces the usual SSH mechanism of permanent host keys described in section 2.2. So if you use this method, then you won't be asked any interactive questions about whether to accept the server's host key. Instead, the Kerberos exchange will verify the identity of the host you connect to, at the same time as verifying your identity to it.

      -

      4.18.2 Repeat key exchange

      +

      4.18.2 Repeat key exchange

      If the session key negotiated at connection startup is used too much or for too long, it may become feasible to mount attacks against the SSH connection. Therefore, the SSH-2 protocol specifies that a new key exchange should take place every so often; this can be initiated by either the client or the server.

      @@ -1732,7 +1772,7 @@

      4.18.2 keepalives aren't always helpful. If you anticipate suffering a network dropout of several hours in the middle of an SSH connection, but were not actually planning to send data down that connection during those hours, then an attempted rekey in the middle of the dropout will probably cause the connection to be abandoned, whereas if rekeys are disabled then the connection should in principle survive (in the absence of interfering firewalls). See section 4.14.1 for more discussion of these issues; for these purposes, rekeys have much the same properties as keepalives. (Except that rekeys have cryptographic value in themselves, so you should bear that in mind when deciding whether to turn them off.) Note, however, the the SSH server can still initiate rekeys. +You might have a need to disable time-based rekeys completely for the same reasons that keepalives aren't always helpful. If you anticipate suffering a network dropout of several hours in the middle of an SSH connection, but were not actually planning to send data down that connection during those hours, then an attempted rekey in the middle of the dropout will probably cause the connection to be abandoned, whereas if rekeys are disabled then the connection should in principle survive (in the absence of interfering firewalls). See section 4.14.1 for more discussion of these issues; for these purposes, rekeys have much the same properties as keepalives. (Except that rekeys have cryptographic value in themselves, so you should bear that in mind when deciding whether to turn them off.) Note, however, the the SSH server can still initiate rekeys.

      • ‘Minutes between GSSAPI checks’, if you're using GSSAPI key exchange, specifies how often the GSSAPI credential cache is checked to see whether new tickets are available for delegation, or current ones are near expiration. If forwarding of GSSAPI credentials is enabled, PuTTY will try to rekey as necessary to keep the delegated credentials from expiring. Frequent checks are recommended; rekeying only happens when needed. @@ -1756,11 +1796,11 @@

        4.18.2 integrity, and to a lesser extent, confidentiality of the SSH-2 protocol depend in part on rekeys occurring before a 32-bit packet sequence number wraps around. Unlike time-based rekeys, data-based rekeys won't occur when the SSH connection is idle, so they shouldn't cause the same problems. The SSH-1 protocol, incidentally, has even weaker integrity protection than SSH-2 without rekeys. +Disabling data-based rekeys entirely is a bad idea. The integrity, and to a lesser extent, confidentiality of the SSH-2 protocol depend in part on rekeys occurring before a 32-bit packet sequence number wraps around. Unlike time-based rekeys, data-based rekeys won't occur when the SSH connection is idle, so they shouldn't cause the same problems. The SSH-1 protocol, incidentally, has even weaker integrity protection than SSH-2 without rekeys.

        4.19 The Host Keys panel

        -The Host Keys panel allows you to configure options related to host key management. +The Host Keys panel allows you to configure options related to host key management.

        Host keys are used to prove the server's identity, and assure you that the server is not being spoofed (either by a man-in-the-middle attack or by completely replacing it on the network). See section 2.2 for a basic introduction to host keys. @@ -1768,7 +1808,7 @@

        4.19 The Host Keys pane

        Much of this panel is only relevant to SSH protocol version 2; SSH-1 only supports one type of host key.

        -

        4.19.1 Host key type selection

        +

        4.19.1 Host key type selection

        PuTTY supports a variety of SSH-2 host key types, and allows you to choose which one you prefer to use to identify the server. Configuration is similar to cipher selection (see section 4.20).

        @@ -1776,19 +1816,19 @@

        4.19.1
        • -‘Ed25519’: Edwards-curve DSA using a twisted Edwards curve with modulus 2^255-19. +‘Ed25519’: Edwards-curve DSA using a twisted Edwards curve with modulus 2^255-19.
        • -‘Ed448’: another Edwards-curve DSA type, using a larger elliptic curve with a 448-bit instead of 255-bit modulus (so it has a higher security level than Ed25519). +‘Ed448’: another Edwards-curve DSA type, using a larger elliptic curve with a 448-bit instead of 255-bit modulus (so it has a higher security level than Ed25519).
        • -‘ECDSA’: elliptic curve DSA using one of the NIST-standardised elliptic curves. +‘ECDSA’: elliptic curve DSA using one of the NIST-standardised elliptic curves.
        • -‘DSA’: straightforward DSA using modular exponentiation. +‘DSA’: straightforward DSA using modular exponentiation.
        • -‘RSA’: the ordinary RSA algorithm. +‘RSA’: the ordinary RSA algorithm.

        @@ -1813,7 +1853,7 @@

        4.19.2

        For this reason, this policy is configurable. By turning this checkbox off, you can reset PuTTY to always use the exact order of host key algorithms configured in the preference list described in section 4.19.1, so that a listener will find out nothing about what keys you had stored.

        -

        4.19.3 Manually configuring host keys

        +

        4.19.3 Manually configuring host keys

        In some situations, if PuTTY's automated host key management is not doing what you need, you might need to manually configure PuTTY to accept a specific host key, or one of a specific set of host keys.

        @@ -1821,7 +1861,7 @@

        4.19.3

        -Another reason is if PuTTY's automated host key management is completely unavailable, e.g. because PuTTY (or Plink or PSFTP, etc) is running in a Windows environment without access to the Registry. In that situation, you will probably want to use the -hostkey command-line option to configure the expected host key(s); see section 3.11.3.21. +Another reason is if PuTTY's automated host key management is completely unavailable, e.g. because PuTTY (or Plink or PSFTP, etc) is running in a Windows environment without access to the Registry. In that situation, you will probably want to use the -hostkey command-line option to configure the expected host key(s); see section 3.11.3.22.

        For situations where PuTTY's automated host key management simply picks the wrong host name to store a key under, you may want to consider setting a ‘logical host name’ instead; see section 4.14.5. @@ -1833,45 +1873,121 @@

        4.19.3
        • -An SHA-256-based host key fingerprint of the form displayed in PuTTY's Event Log and host key dialog boxes, i.e. ‘SHA256:’ followed by 43 case-sensitive characters. +An SHA-256-based host key fingerprint of the form displayed in PuTTY's Event Log and host key dialog boxes, i.e. ‘SHA256:’ followed by 43 case-sensitive characters.
        • -An MD5-based host key fingerprint, i.e. sixteen 2-digit hex numbers separated by colons, optionally preceded by the prefix ‘MD5:’. (The case of the characters does not matter.) +An MD5-based host key fingerprint, i.e. sixteen 2-digit hex numbers separated by colons, optionally preceded by the prefix ‘MD5:’. (The case of the characters does not matter.)
        • A base64-encoded blob describing an SSH-2 public key in OpenSSH's one-line public key format. How you acquire a public key in this format is server-dependent; on an OpenSSH server it can typically be found in a location like /etc/ssh/ssh_host_rsa_key.pub.

        -If this box contains at least one host key or fingerprint when PuTTY makes an SSH connection, then PuTTY's automated host key management is completely bypassed: the connection will be permitted if and only if the host key presented by the server is one of the keys listed in this box, and the host key store in the Registry will be neither read nor written, unless you explicitly do so. +If this box contains at least one host key or fingerprint when PuTTY makes an SSH connection, then PuTTY's automated host key management is completely bypassed: the connection will be permitted if and only if the host key presented by the server is one of the keys listed in this box, and the host key store in the Registry will be neither read nor written, unless you explicitly do so.

        If the box is empty (as it usually is), then PuTTY's automated host key management will work as normal.

        +

        4.19.4 Configuring PuTTY to accept host certificates

        +

        +In some environments, the SSH host keys for a lot of servers will all be signed in turn by a central ‘certification authority’ (‘CA’ for short). This simplifies host key configuration for users, because if they configure their SSH client to accept host keys certified by that CA, then they don't need to individually confirm each host key the first time they connect to that server. +

        +

        +In order to do this, press the ‘Configure host CAs’ button in the ‘Host keys’ configuration panel. This will launch a secondary configuration dialog box where you can configure what CAs PuTTY will accept signatures from. +

        +

        +Note that this configuration is common to all saved sessions. Everything in the main PuTTY configuration is specific to one saved session, and you can prepare a separate session with all the configuration different. But there's only one copy of the host CA configuration, and it applies to all sessions PuTTY runs, whether saved or not. +

        +

        +(Otherwise, it would be useless – configuring a CA by hand for each new host wouldn't be any more convenient than pressing the ‘confirm’ button for each new host's host key.) +

        +

        +To set up a new CA using this config box: +

        +

        +First, load the CA's public key from a file, or paste it directly into the ‘Public key of certification authority’ edit box. If your organisation signs its host keys in this way, they will publish the public key of their CA so that SSH users can include it in their configuration. +

        +

        +Next, in the ‘Valid hosts this key is trusted to certify’ box, configure at least one hostname wildcard to say what servers PuTTY should trust this CA to speak for. For example, suppose you work for Example Corporation (example.com), and the Example Corporation IT department has advertised a CA that signs all the Example internal machines' host keys. Then probably you want to trust that CA to sign host keys for machines in the domain example.com, but not for anything else. So you might enter ‘*.example.com’ into the ‘Valid hosts’ box. +

        +

        +It's important to limit what the CA key is allowed to sign. Don't just enter ‘*’ in that box! If you do that, you're saying that Example Corporation IT department is authorised to sign a host key for anything at all you might decide to connect to – even if you're connecting out of the company network to a machine somewhere else, such as your own personal server. So that configuration would enable the Example IT department to act as a ‘man-in-the-middle’ between your PuTTY process and your server, and listen in to your communications – exactly the thing SSH is supposed to avoid. +

        +

        +So, if the CA was provided to you by the sysadmins responsible for example.com (or whatever), make sure PuTTY will only trust it for machines in the example.com domain. +

        +

        +For the full syntax of the ‘Valid hosts’ expression, see section 4.19.4.1. +

        +

        +Finally, choose an identifying name for this CA; enter that name in the ‘Name for this CA’ edit box at the top of the window, and press ‘Save’ to record the CA in your configuration. The name you chose will appear in the list of saved CAs to the left of the ‘Save’ button. +

        +

        +The identifying name can be anything you like. It's there so that if you store multiple certificates you can tell which is which later when you want to edit or delete them. It also appears in the PuTTY Event Log when a server presents a certificate signed by that CA. +

        +

        +To reload an existing CA configuration, select it in the list box and press ‘Load’. Then you can make changes, and save it again. +

        +

        +To remove a CA from your configuration completely, select it in the list and press ‘Delete’. +

        +

        4.19.4.1 Expressions you can enter in ‘Valid hosts’

        +

        +The simplest thing you can enter in the ‘Valid hosts this key is trusted to certify’ edit box is just a hostname wildcard such as ‘*.example.com’. This matches any host in any subdomain, so both ‘ssh.example.com’ and ‘login.dept.example.com’ would match, but ‘prod.example.net’ would not. +

        +

        +But you can also enter multiple host name wildcards, and port number ranges, and make complicated Boolean expressions out of them using the operators ‘&&’ for ‘and’, ‘||’ for ‘or’, ‘!’ for ‘not’, and parentheses. +

        +

        +For example, here are some other things you could enter. +

        +
        • +‘*.foo.example.com || *.bar.example.com’. This means the CA is trusted to sign the host key for a connection if the host name matches ‘*.foo.example.com’ or it matches ‘*.bar.example.com’. In other words, the CA has authority over those two particular subdomains of example.com, but not for anything else, like www.example.com. +
        • +
        • +‘*.example.com && ! *.extrasecure.example.com’. This means the CA is trusted to sign the host key for a connection if the host name matches ‘*.example.com’ but does not match ‘*.extrasecure.example.com’. (Imagine if there was one top-secret set of servers in your company that the main IT department didn't have security clearance to administer.) +
        • +
        • +‘*.example.com && port:22’. This means the CA is trusted to sign the host key for a connection if the host name matches ‘*.example.com’ and the port number is 22. SSH servers running on other ports would not be covered. +
        • +
        • +‘(*.foo.example.com || *.bar.example.com) && port:0-1023’. This matches two subdomains of example.com, as before, but also restricts the port number to the range 0-1023. +
        • +
        +

        +A certificate configuration expression consists of one or more individual requirements which can each be a hostname wildcard, a single port number, or a port number range, combined together with these Boolean operators. +

        +

        +Unlike other languages such as C, there is no implied priority between ‘&&’ and ‘||’. If you write ‘A && B || C’ (where A, B and C are some particular requirements), then PuTTY will report a syntax error, because you haven't said which of the ‘&&’ and ‘||’ takes priority tightly. You will have to write either ‘(A && B) || C’, meaning ‘both of A and B, or alternatively just C’, or ‘A && (B || C)’ (‘A, and also at least one of B and C’), to make it clear. +

        +

        4.19.4.2 RSA signature types in certificates

        +

        +RSA keys can be used to generate signatures with a choice of secure hash function. Typically, any version of OpenSSH new enough to support certificates at all will also be new enough to avoid using SHA-1, so the default settings of accepting the more modern SHA-256 and SHA-512 should be suitable for nearly all cases. For completeness, however, you can configure which types of RSA signature PuTTY will accept in a certificate from a CA using an RSA key. +

        4.20 The Cipher panel

        -PuTTY supports a variety of different encryption algorithms, and allows you to choose which one you prefer to use. You can do this by dragging the algorithms up and down in the list box (or moving them using the Up and Down buttons) to specify a preference order. When you make an SSH connection, PuTTY will search down the list from the top until it finds an algorithm supported by the server, and then use that. +PuTTY supports a variety of different encryption algorithms, and allows you to choose which one you prefer to use. You can do this by dragging the algorithms up and down in the list box (or moving them using the Up and Down buttons) to specify a preference order. When you make an SSH connection, PuTTY will search down the list from the top until it finds an algorithm supported by the server, and then use that.

        PuTTY currently supports the following algorithms:

        • -ChaCha20-Poly1305, a combined cipher and MAC (SSH-2 only) +ChaCha20-Poly1305, a combined cipher and MAC (SSH-2 only)
        • -AES (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) +AES (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or 256 or 128-bit GCM (SSH-2 only)
        • -Arcfour (RC4) - 256 or 128-bit stream cipher (SSH-2 only) +Arcfour (RC4) - 256 or 128-bit stream cipher (SSH-2 only)
        • -Blowfish - 256-bit SDCTR (SSH-2 only) or 128-bit CBC +Blowfish - 256-bit SDCTR (SSH-2 only) or 128-bit CBC
        • -Triple-DES - 168-bit SDCTR (SSH-2 only) or CBC +Triple-DES - 168-bit SDCTR (SSH-2 only) or CBC
        • -Single-DES - 56-bit CBC (see below for SSH-2) +Single-DES - 56-bit CBC (see below for SSH-2)

        @@ -1893,11 +2009,11 @@

        4.20 The Cipher pane

        4.21 The Auth panel

        -The Auth panel allows you to configure authentication options for SSH sessions. +The Auth panel allows you to configure authentication options for SSH sessions.

        4.21.1 ‘Display pre-authentication banner’

        -SSH-2 servers can provide a message for clients to display to the prospective user before the user logs in; this is sometimes known as a pre-authentication ‘banner’. Typically this is used to provide information about the server and legal notices. +SSH-2 servers can provide a message for clients to display to the prospective user before the user logs in; this is sometimes known as a pre-authentication ‘banner’. Typically this is used to provide information about the server and legal notices.

        By default, PuTTY displays this message before prompting for a password or similar credentials (although, unfortunately, not before prompting for a login name, due to the nature of the protocol design). By unchecking this option, display of the banner can be suppressed entirely. @@ -1910,7 +2026,7 @@

        4.21.2 ‘Bypass a By default, PuTTY assumes the server requires authentication (we've never heard of one that doesn't), and thus must start this process with a username. If you find you are getting username prompts that you cannot answer, you could try enabling this option. However, most SSH servers will reject this.

        -This is not the option you want if you have a username and just want PuTTY to remember it; for that see section 4.15.1. It's also probably not what if you're trying to set up passwordless login to a mainstream SSH server; depending on the server, you probably wanted public-key authentication (chapter 8) or perhaps GSSAPI authentication (section 4.22). (These are still forms of authentication, even if you don't have to interact with them.) +This is not the option you want if you have a username and just want PuTTY to remember it; for that see section 4.15.1. It's also probably not what if you're trying to set up passwordless login to a mainstream SSH server; depending on the server, you probably wanted public-key authentication (chapter 8) or perhaps GSSAPI authentication (section 4.23). (These are still forms of authentication, even if you don't have to interact with them.)

        This option only affects SSH-2 connections. SSH-1 connections always require an authentication step. @@ -1947,53 +2063,86 @@

        4.21.4 ‘Attemp

        See chapter 9 for more information about Pageant in general.

        -

        4.21.5 ‘Attempt TIS or CryptoCard authentication’

        +

        4.21.5 ‘Attempt TIS or CryptoCard authentication’

        -TIS and CryptoCard authentication are (despite their names) generic forms of simple challenge/response authentication available in SSH protocol version 1 only. You might use them if you were using S/Key one-time passwords, for example, or if you had a physical security token that generated responses to authentication challenges. They can even be used to prompt for simple passwords. +TIS and CryptoCard authentication are (despite their names) generic forms of simple challenge/response authentication available in SSH protocol version 1 only. You might use them if you were using S/Key one-time passwords, for example, or if you had a physical security token that generated responses to authentication challenges. They can even be used to prompt for simple passwords.

        With this switch enabled, PuTTY will attempt these forms of authentication if the server is willing to try them. You will be presented with a challenge string (which may be different every time) and must supply the correct response in order to log in. If your server supports this, you should talk to your system administrator about precisely what form these challenges and responses take.

        -

        4.21.6 ‘Attempt keyboard-interactive authentication’

        +

        4.21.6 ‘Attempt keyboard-interactive authentication’

        -The SSH-2 equivalent of TIS authentication is called ‘keyboard-interactive’. It is a flexible authentication method using an arbitrary sequence of requests and responses; so it is not only useful for challenge/response mechanisms such as S/Key, but it can also be used for (for example) asking the user for a new password when the old one has expired. +The SSH-2 equivalent of TIS authentication is called ‘keyboard-interactive’. It is a flexible authentication method using an arbitrary sequence of requests and responses; so it is not only useful for challenge/response mechanisms such as S/Key, but it can also be used for (for example) asking the user for a new password when the old one has expired.

        PuTTY leaves this option enabled by default, but supplies a switch to turn it off in case you should have trouble with it.

        -

        4.21.7 ‘Allow agent forwarding’

        +

        4.21.7 ‘Allow agent forwarding’

        -This option allows the SSH server to open forwarded connections back to your local copy of Pageant. If you are not running Pageant, this option will do nothing. +This option allows the SSH server to open forwarded connections back to your local copy of Pageant. If you are not running Pageant, this option will do nothing.

        See chapter 9 for general information on Pageant, and section 9.4 for information on agent forwarding. Note that there is a security risk involved with enabling this option; see section 9.6 for details.

        -

        4.21.8 ‘Allow attempted changes of username in SSH-2’

        +

        4.21.8 ‘Allow attempted changes of username in SSH-2’

        In the SSH-1 protocol, it is impossible to change username after failing to authenticate. So if you mis-type your username at the PuTTY ‘login as:’ prompt, you will not be able to change it except by restarting PuTTY.

        -The SSH-2 protocol does allow changes of username, in principle, but does not make it mandatory for SSH-2 servers to accept them. In particular, OpenSSH does not accept a change of username; once you have sent one username, it will reject attempts to try to authenticate as another user. (Depending on the version of OpenSSH, it may quietly return failure for all login attempts, or it may send an error message.) +The SSH-2 protocol does allow changes of username, in principle, but does not make it mandatory for SSH-2 servers to accept them. In particular, OpenSSH does not accept a change of username; once you have sent one username, it will reject attempts to try to authenticate as another user. (Depending on the version of OpenSSH, it may quietly return failure for all login attempts, or it may send an error message.)

        For this reason, PuTTY will by default not prompt you for your username more than once, in case the server complains. If you know your server can cope with it, you can enable the ‘Allow attempted changes of username’ option to modify PuTTY's behaviour.

        -

        4.21.9 ‘Private key file for authentication’

        +

        4.22 The Credentials panel

        -This box is where you enter the name of your private key file if you are using public key authentication. See chapter 8 for information about public key authentication in SSH. +This subpane of the Auth panel contains configuration options that specify actual credentials to present to the server: key files and certificates.

        +

        4.22.1 ‘Private key file for authentication’

        -This key must be in PuTTY's native format (*.PPK). If you have a private key in another format that you want to use with PuTTY, see section 8.2.14. +This box is where you enter the name of your private key file if you are using public key authentication. See chapter 8 for information about public key authentication in SSH.

        -You can use the authentication agent Pageant so that you do not need to explicitly configure a key here; see chapter 9. +This key must be in PuTTY's native format (*.PPK). If you have a private key in another format that you want to use with PuTTY, see section 8.2.15. +

        +

        +You can use the authentication agent Pageant so that you do not need to explicitly configure a key here; see chapter 9.

        If a private key file is specified here with Pageant running, PuTTY will first try asking Pageant to authenticate with that key, and ignore any other keys Pageant may have. If that fails, PuTTY will ask for a passphrase as normal. You can also specify a public key file in this case (in RFC 4716 or OpenSSH format), as that's sufficient to identify the key to Pageant, but of course if Pageant isn't present PuTTY can't fall back to using this file itself.

        -

        4.22 The GSSAPI panel

        +

        4.22.2 ‘Certificate to use with the private key’

        -The ‘GSSAPI’ subpanel of the ‘Auth’ panel controls the use of GSSAPI authentication. This is a mechanism which delegates the authentication exchange to a library elsewhere on the client machine, which in principle can authenticate in many different ways but in practice is usually used with the Kerberos single sign-on protocol to implement passwordless login. +In some environments, user authentication keys can be signed in turn by a ‘certifying authority’ (‘CA’ for short), and user accounts on an SSH server can be configured to automatically trust any key that's certified by the right signature. +

        +

        +This can be a convenient setup if you have a very large number of servers. When you change your key pair, you might otherwise have to edit the authorized_keys file on every server individually, to make them all accept the new key. But if instead you configure all those servers once to accept keys signed as yours by a CA, then when you change your public key, all you have to do is to get the new key certified by the same CA as before, and then all your servers will automatically accept it without needing individual reconfiguration. +

        +

        +One way to use a certificate is to incorporate it into your private key file. Section 8.2.9 explains how to do that using PuTTYgen. But another approach is to tell PuTTY itself where to find the public certificate file, and then it will automatically present that certificate when authenticating with the corresponding private key. +

        +

        +To do this, enter the pathname of the certificate file into the ‘Certificate to use with the private key’ file selector. +

        +

        +When this setting is configured, PuTTY will honour it no matter whether the private key is found in a file, or loaded into Pageant. +

        +

        4.22.3 ‘Plugin to provide authentication responses’

        +

        +An SSH server can use the ‘keyboard-interactive’ protocol to present a series of arbitrary questions and answers. Sometimes this is used for ordinary passwords, but sometimes the server will use the same mechanism for something more complicated, such as a one-time password system. +

        +

        +Some of these systems can be automated. For this purpose, PuTTY allows you to provide a separate program to act as a ‘plugin’ which will take over the authentication and send answers to the questions on your behalf. +

        +

        +If you have been provided with a plugin of this type, you can configure it here, by entering a full command line in the ‘Plugin command to run’ box. +

        +

        +(If you want to write a plugin of this type, see appendix H for the full specification of how the plugin is expected to behave.) +

        +

        4.23 The GSSAPI panel

        +

        +The ‘GSSAPI’ subpanel of the ‘Auth’ panel controls the use of GSSAPI authentication. This is a mechanism which delegates the authentication exchange to a library elsewhere on the client machine, which in principle can authenticate in many different ways but in practice is usually used with the Kerberos single sign-on protocol to implement passwordless login.

        GSSAPI authentication is only available in the SSH-2 protocol. @@ -2010,9 +2159,9 @@

        4.22 The 4.22.1 ‘Allow GSSAPI credential delegation’

        +

        4.23.1 ‘Allow GSSAPI credential delegation’

        -GSSAPI credential delegation is a mechanism for passing on your Kerberos (or other) identity to the session on the SSH server. If you enable this option, then not only will PuTTY be able to log in automatically to a server that accepts your Kerberos credentials, but also you will be able to connect out from that server to other Kerberos-supporting services and use the same credentials just as automatically. +GSSAPI credential delegation is a mechanism for passing on your Kerberos (or other) identity to the session on the SSH server. If you enable this option, then not only will PuTTY be able to log in automatically to a server that accepts your Kerberos credentials, but also you will be able to connect out from that server to other Kerberos-supporting services and use the same credentials just as automatically.

        (This option is the Kerberos analogue of SSH agent forwarding; see section 9.4 for some information on that.) @@ -2023,31 +2172,31 @@

        4.22.1

        If your connection is not using GSSAPI key exchange, it is possible for the delegation to expire during your session. See section 4.18.1.1 for more information.

        -

        4.22.2 Preference order for GSSAPI libraries

        +

        4.23.2 Preference order for GSSAPI libraries

        GSSAPI is a mechanism which allows more than one authentication method to be accessed through the same interface. Therefore, more than one authentication library may exist on your system which can be accessed using GSSAPI.

        -PuTTY contains native support for a few well-known such libraries (including Windows' SSPI), and will look for all of them on your system and use whichever it finds. If more than one exists on your system and you need to use a specific one, you can adjust the order in which it will search using this preference list control. +PuTTY contains native support for a few well-known such libraries (including Windows' SSPI), and will look for all of them on your system and use whichever it finds. If more than one exists on your system and you need to use a specific one, you can adjust the order in which it will search using this preference list control.

        One of the options in the preference list is to use a user-specified GSSAPI library. If the library you want to use is not mentioned by name in PuTTY's list of options, you can enter its full pathname in the ‘User-supplied GSSAPI library path’ field, and move the ‘User-supplied GSSAPI library’ option in the preference list to make sure it is selected before anything else.

        -On Windows, such libraries are files with a .dll extension, and must have been built in the same way as the PuTTY executable you're running; if you have a 32-bit DLL, you must run a 32-bit version of PuTTY, and the same with 64-bit (see question A.6.10). On Unix, shared libraries generally have a .so extension. +On Windows, such libraries are files with a .dll extension, and must have been built in the same way as the PuTTY executable you're running; if you have a 32-bit DLL, you must run a 32-bit version of PuTTY, and the same with 64-bit (see question A.6.10). On Unix, shared libraries generally have a .so extension.

        -

        4.23 The TTY panel

        +

        4.24 The TTY panel

        The TTY panel lets you configure the remote pseudo-terminal.

        -

        4.23.1 ‘Don't allocate a pseudo-terminal’

        +

        4.24.1 ‘Don't allocate a pseudo-terminal’

        -When connecting to a Unix system, most interactive shell sessions are run in a pseudo-terminal, which allows the Unix system to pretend it's talking to a real physical terminal device but allows the SSH server to catch all the data coming from that fake device and send it back to the client. +When connecting to a Unix system, most interactive shell sessions are run in a pseudo-terminal, which allows the Unix system to pretend it's talking to a real physical terminal device but allows the SSH server to catch all the data coming from that fake device and send it back to the client.

        Occasionally you might find you have a need to run a session not in a pseudo-terminal. In PuTTY, this is generally only useful for very specialist purposes; although in Plink (see chapter 7) it is the usual way of working.

        -

        4.23.2 Sending terminal modes

        +

        4.24.2 Sending terminal modes

        The SSH protocol allows the client to send ‘terminal modes’ for the remote pseudo-terminal. These usually control the server's expectation of the local terminal's behaviour.

        @@ -2078,37 +2227,37 @@

        4.23.2 Sending POSIX and other Unix systems, and they are most likely to have a useful effect on such systems. (These are the same settings that can usually be changed using the stty command once logged in to such servers.) +The precise effect of each setting, if any, is up to the server. Their names come from POSIX and other Unix systems, and they are most likely to have a useful effect on such systems. (These are the same settings that can usually be changed using the stty command once logged in to such servers.)

        Some notable modes are described below; for fuller explanations, see your server documentation.

        • -ERASE is the character that when typed by the user will delete one space to the left. When set to ‘Auto’ (the default setting), this follows the setting of the local Backspace key in PuTTY (see section 4.4.1). +ERASE is the character that when typed by the user will delete one space to the left. When set to ‘Auto’ (the default setting), this follows the setting of the local Backspace key in PuTTY (see section 4.4.1).

          -This and other special characters are specified using ^C notation for Ctrl-C, and so on. Use ^<27> or ^<0x1B> to specify a character numerically, and ^~ to get a literal ^. Other non-control characters are denoted by themselves. Leaving the box entirely blank indicates that no character should be assigned to the specified function, although this may not be supported by all servers. +This and other special characters are specified using ^C notation for Ctrl-C, and so on. Use ^<27> or ^<0x1B> to specify a character numerically, and ^~ to get a literal ^. Other non-control characters are denoted by themselves. Leaving the box entirely blank indicates that no character should be assigned to the specified function, although this may not be supported by all servers.

        • -QUIT is a special character that usually forcefully ends the current process on the server (SIGQUIT). On many servers its default setting is Ctrl-backslash (^\), which is easy to accidentally invoke on many keyboards. If this is getting in your way, you may want to change it to another character or turn it off entirely. +QUIT is a special character that usually forcefully ends the current process on the server (SIGQUIT). On many servers its default setting is Ctrl-backslash (^\), which is easy to accidentally invoke on many keyboards. If this is getting in your way, you may want to change it to another character or turn it off entirely.
        • Boolean modes such as ECHO and ICANON can be specified in PuTTY in a variety of ways, such as true/false, yes/no, and 0/1. (Explicitly specifying a value of no is different from not sending the mode at all.)
        • -The boolean mode IUTF8 signals to the server whether the terminal character set is UTF-8 or not, for purposes such as basic line editing; if this is set incorrectly, the backspace key may erase the wrong amount of text, for instance. However, simply setting this is not usually sufficient for the server to use UTF-8; POSIX servers will generally also require the locale to be set (by some server-dependent means), although many newer installations default to UTF-8. Also, since this mode was added to the SSH protocol much later than the others, many servers (particularly older servers) do not honour this mode sent over SSH; indeed, a few poorly-written servers object to its mere presence, so you may find you need to set it to not be sent at all. When set to ‘Auto’, this follows the local configured character set (see section 4.10.1). +The boolean mode IUTF8 signals to the server whether the terminal character set is UTF-8 or not, for purposes such as basic line editing; if this is set incorrectly, the backspace key may erase the wrong amount of text, for instance. However, simply setting this is not usually sufficient for the server to use UTF-8; POSIX servers will generally also require the locale to be set (by some server-dependent means), although many newer installations default to UTF-8. Also, since this mode was added to the SSH protocol much later than the others, many servers (particularly older servers) do not honour this mode sent over SSH; indeed, a few poorly-written servers object to its mere presence, so you may find you need to set it to not be sent at all. When set to ‘Auto’, this follows the local configured character set (see section 4.10.1).
        • Terminal speeds are configured elsewhere; see section 4.15.4.
        -

        4.24 The X11 panel

        +

        4.25 The X11 panel

        -The X11 panel allows you to configure forwarding of X11 over an SSH connection. +The X11 panel allows you to configure forwarding of X11 over an SSH connection.

        -If your server lets you run X Window System graphical applications, X11 forwarding allows you to securely give those applications access to a local X display on your PC. +If your server lets you run X Window System graphical applications, X11 forwarding allows you to securely give those applications access to a local X display on your PC.

        To enable X11 forwarding, check the ‘Enable X11 forwarding’ box. If your X display is somewhere unusual, you will need to enter its location in the ‘X display location’ box; if this is left blank, PuTTY will try to find a sensible default in the environment, or use the primary local display (:0) if that fails. @@ -2116,15 +2265,15 @@

        4.24 The X11 panel

        See section 3.4 for more information about X11 forwarding.

        -

        4.24.1 Remote X11 authentication

        +

        4.25.1 Remote X11 authentication

        If you are using X11 forwarding, the virtual X server created on the SSH server machine will be protected by authorisation data. This data is invented, and checked, by PuTTY.

        -The usual authorisation method used for this is called MIT-MAGIC-COOKIE-1. This is a simple password-style protocol: the X client sends some cookie data to the server, and the server checks that it matches the real cookie. The cookie data is sent over an unencrypted X11 connection; so if you allow a client on a third machine to access the virtual X server, then the cookie will be sent in the clear. +The usual authorisation method used for this is called MIT-MAGIC-COOKIE-1. This is a simple password-style protocol: the X client sends some cookie data to the server, and the server checks that it matches the real cookie. The cookie data is sent over an unencrypted X11 connection; so if you allow a client on a third machine to access the virtual X server, then the cookie will be sent in the clear.

        -PuTTY offers the alternative protocol XDM-AUTHORIZATION-1. This is a cryptographically authenticated protocol: the data sent by the X client is different every time, and it depends on the IP address and port of the client's end of the connection and is also stamped with the current time. So an eavesdropper who captures an XDM-AUTHORIZATION-1 string cannot immediately re-use it for their own X connection. +PuTTY offers the alternative protocol XDM-AUTHORIZATION-1. This is a cryptographically authenticated protocol: the data sent by the X client is different every time, and it depends on the IP address and port of the client's end of the connection and is also stamped with the current time. So an eavesdropper who captures an XDM-AUTHORIZATION-1 string cannot immediately re-use it for their own X connection.

        PuTTY's support for XDM-AUTHORIZATION-1 is a somewhat experimental feature, and may encounter several problems: @@ -2142,7 +2291,7 @@

        4.24.1 Remote PuTTY's default is MIT-MAGIC-COOKIE-1. If you change it, you should be sure you know what you're doing.

        -

        4.24.2 X authority file for local display

        +

        4.25.2 X authority file for local display

        If you are using X11 forwarding, the local X server to which your forwarded connections are eventually directed may itself require authorisation.

        @@ -2152,12 +2301,12 @@

        4.24.2 X authority

        One way in which this data might be made available is for the X server to store it somewhere in a file which has the same format as the Unix .Xauthority file. If this is how your Windows X server works, then you can tell PuTTY where to find this file by configuring this option. By default, PuTTY will not attempt to find any authorisation for your local display.

        -

        4.25 The Tunnels panel

        +

        4.26 The Tunnels panel

        The Tunnels panel allows you to configure tunnelling of arbitrary connection types through an SSH connection.

        -Port forwarding allows you to tunnel other types of network connection down an SSH session. See section 3.5 for a general discussion of port forwarding and how it works. +Port forwarding allows you to tunnel other types of network connection down an SSH session. See section 3.5 for a general discussion of port forwarding and how it works.

        The port forwarding section in the Tunnels panel shows a list of all the port forwardings that PuTTY will try to set up when it connects to the server. By default no port forwardings are set up, so this list is empty. @@ -2166,13 +2315,13 @@

        4.25
        • -Set one of the ‘Local’ or ‘Remote’ radio buttons, depending on whether you want to forward a local port to a remote destination (‘Local’) or forward a remote port to a local destination (‘Remote’). Alternatively, select ‘Dynamic’ if you want PuTTY to provide a local SOCKS 4/4A/5 proxy on a local port (note that this proxy only supports TCP connections; the SSH protocol does not support forwarding UDP). +Set one of the ‘Local’ or ‘Remote’ radio buttons, depending on whether you want to forward a local port to a remote destination (‘Local’) or forward a remote port to a local destination (‘Remote’). Alternatively, select ‘Dynamic’ if you want PuTTY to provide a local SOCKS 4/4A/5 proxy on a local port (note that this proxy only supports TCP connections; the SSH protocol does not support forwarding UDP).
        • -Enter a source port number into the ‘Source port’ box. For local forwardings, PuTTY will listen on this port of your PC. For remote forwardings, your SSH server will listen on this port of the remote machine. Note that most servers will not allow you to listen on port numbers less than 1024. +Enter a source port number into the ‘Source port’ box. For local forwardings, PuTTY will listen on this port of your PC. For remote forwardings, your SSH server will listen on this port of the remote machine. Note that most servers will not allow you to listen on port numbers less than 1024.
        • -If you have selected ‘Local’ or ‘Remote’ (this step is not needed with ‘Dynamic’), enter a hostname and port number separated by a colon, in the ‘Destination’ box. Connections received on the source port will be directed to this destination. For example, to connect to a POP-3 server, you might enter popserver.example.com:110. (If you need to enter a literal IPv6 address, enclose it in square brackets, for instance ‘[::1]:2200’.) +If you have selected ‘Local’ or ‘Remote’ (this step is not needed with ‘Dynamic’), enter a hostname and port number separated by a colon, in the ‘Destination’ box. Connections received on the source port will be directed to this destination. For example, to connect to a POP-3 server, you might enter popserver.example.com:110. (If you need to enter a literal IPv6 address, enclose it in square brackets, for instance ‘[::1]:2200’.)
        • Click the ‘Add’ button. Your forwarding details should appear in the list box. @@ -2182,19 +2331,19 @@

          4.25

          -In the ‘Source port’ box, you can also optionally enter an IP address to listen on, by specifying (for instance) 127.0.0.5:79. See section 3.5 for more information on how this works and its restrictions. +In the ‘Source port’ box, you can also optionally enter an IP address to listen on, by specifying (for instance) 127.0.0.5:79. See section 3.5 for more information on how this works and its restrictions.

          -In place of port numbers, you can enter service names, if they are known to the local system. For instance, in the ‘Destination’ box, you could enter popserver.example.com:pop3. +In place of port numbers, you can enter service names, if they are known to the local system. For instance, in the ‘Destination’ box, you could enter popserver.example.com:pop3.

          -You can modify the currently active set of port forwardings in mid-session using ‘Change Settings’ (see section 3.1.3.4). If you delete a local or dynamic port forwarding in mid-session, PuTTY will stop listening for connections on that port, so it can be re-used by another program. If you delete a remote port forwarding, note that: +You can modify the currently active set of port forwardings in mid-session using ‘Change Settings’ (see section 3.1.3.4). If you delete a local or dynamic port forwarding in mid-session, PuTTY will stop listening for connections on that port, so it can be re-used by another program. If you delete a remote port forwarding, note that:

          • The SSH-1 protocol contains no mechanism for asking the server to stop listening on a remote port.
          • -The SSH-2 protocol does contain such a mechanism, but not all SSH servers support it. (In particular, OpenSSH does not support it in any version earlier than 3.9.) +The SSH-2 protocol does contain such a mechanism, but not all SSH servers support it. (In particular, OpenSSH does not support it in any version earlier than 3.9.)

          @@ -2206,20 +2355,20 @@

          4.25 If the connection you are forwarding over SSH is itself a second SSH connection made by another copy of PuTTY, you might find the ‘logical host name’ configuration option useful to warn PuTTY of which host key it should be expecting. See section 4.14.5 for details of this.

          -

          4.25.1 Controlling the visibility of forwarded ports

          +

          4.26.1 Controlling the visibility of forwarded ports

          -The source port for a forwarded connection usually does not accept connections from any machine except the SSH client or server machine itself (for local and remote forwardings respectively). There are controls in the Tunnels panel to change this: +The source port for a forwarded connection usually does not accept connections from any machine except the SSH client or server machine itself (for local and remote forwardings respectively). There are controls in the Tunnels panel to change this:

          • The ‘Local ports accept connections from other hosts’ option allows you to set up local-to-remote port forwardings in such a way that machines other than your client PC can connect to the forwarded port. (This also applies to dynamic SOCKS forwarding.)
          • -The ‘Remote ports do the same’ option does the same thing for remote-to-local port forwardings (so that machines other than the SSH server machine can connect to the forwarded port.) Note that this feature is only available in the SSH-2 protocol, and not all SSH-2 servers support it (OpenSSH 3.0 does not, for example). +The ‘Remote ports do the same’ option does the same thing for remote-to-local port forwardings (so that machines other than the SSH server machine can connect to the forwarded port.) Note that this feature is only available in the SSH-2 protocol, and not all SSH-2 servers support it (OpenSSH 3.0 does not, for example).
          -

          4.25.2 Selecting Internet protocol version for forwarded ports

          +

          4.26.2 Selecting Internet protocol version for forwarded ports

          -This switch allows you to select a specific Internet protocol (IPv4 or IPv6) for the local end of a forwarded port. By default, it is set on ‘Auto’, which means that: +This switch allows you to select a specific Internet protocol (IPv4 or IPv6) for the local end of a forwarded port. By default, it is set on ‘Auto’, which means that:

          • for a local-to-remote port forwarding, PuTTY will listen for incoming connections in both IPv4 and (if available) IPv6 @@ -2232,9 +2381,9 @@

            4.25.2 This overrides the general Internet protocol version preference on the Connection panel (see section 4.14.4).

            -Note that some operating systems may listen for incoming connections in IPv4 even if you specifically asked for IPv6, because their IPv4 and IPv6 protocol stacks are linked together. Apparently Linux does this, and Windows does not. So if you're running PuTTY on Windows and you tick ‘IPv6’ for a local or dynamic port forwarding, it will only be usable by connecting to it using IPv6; whereas if you do the same on Linux, you can also use it with IPv4. However, ticking ‘Auto’ should always give you a port which you can connect to using either protocol. +Note that some operating systems may listen for incoming connections in IPv4 even if you specifically asked for IPv6, because their IPv4 and IPv6 protocol stacks are linked together. Apparently Linux does this, and Windows does not. So if you're running PuTTY on Windows and you tick ‘IPv6’ for a local or dynamic port forwarding, it will only be usable by connecting to it using IPv6; whereas if you do the same on Linux, you can also use it with IPv4. However, ticking ‘Auto’ should always give you a port which you can connect to using either protocol.

            -

            4.26 The Bugs and More Bugs panels

            +

            4.27 The Bugs and More Bugs panels

            Not all SSH servers work properly. Various existing servers have bugs in them, which can make it impossible for a client to talk to them unless it knows about the bug and works around it.

            @@ -2254,16 +2403,16 @@

            4.26 Th ‘Auto’: PuTTY will use the server's version number announcement to try to guess whether or not the server has the bug. (This option is not available for bugs that cannot be detected from the server version, e.g. because they must be acted on before the server version is known.)

          -

          4.26.1 ‘Chokes on SSH-2 ignore messages’

          +

          4.27.1 ‘Chokes on SSH-2 ignore messages’

          -An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server to the client, at any time. Either side is required to ignore the message whenever it receives it. PuTTY uses ignore messages in SSH-2 to confuse the encrypted data stream and make it harder to cryptanalyse. It also uses ignore messages for connection keepalives (see section 4.14.1). +An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server to the client, at any time. Either side is required to ignore the message whenever it receives it. PuTTY uses ignore messages in SSH-2 to confuse the encrypted data stream and make it harder to cryptanalyse. It also uses ignore messages for connection keepalives (see section 4.14.1).

          If it believes the server to have this bug, PuTTY will stop using ignore messages. If this bug is enabled when talking to a correct server, the session will succeed, but keepalives will not work and the session might be less cryptographically secure than it could be.

          -

          4.26.2 ‘Handles SSH-2 key re-exchange badly’

          +

          4.27.2 ‘Handles SSH-2 key re-exchange badly’

          -Some SSH servers cannot cope with repeat key exchange at all, and will ignore attempts by the client to start one. Since PuTTY pauses the session while performing a repeat key exchange, the effect of this would be to cause the session to hang after an hour (unless you have your rekey timeout set differently; see section 4.18.2 for more about rekeys). Other, very old, SSH servers handle repeat key exchange even more badly, and disconnect upon receiving a repeat key exchange request. +Some SSH servers cannot cope with repeat key exchange at all, and will ignore attempts by the client to start one. Since PuTTY pauses the session while performing a repeat key exchange, the effect of this would be to cause the session to hang after an hour (unless you have your rekey timeout set differently; see section 4.18.2 for more about rekeys). Other, very old, SSH servers handle repeat key exchange even more badly, and disconnect upon receiving a repeat key exchange request.

          If this bug is detected, PuTTY will never initiate a repeat key exchange. If this bug is enabled when talking to a correct server, the session should still function, but may be less secure than you would expect. @@ -2271,14 +2420,14 @@

          4.26.2 ‘Handl

          This is an SSH-2-specific bug.

          -

          4.26.3 ‘Chokes on PuTTY's SSH-2 ‘winadj’ requests’

          +

          4.27.3 ‘Chokes on PuTTY's SSH-2 ‘winadj’ requests’

          PuTTY sometimes sends a special request to SSH servers in the middle of channel data, with the name winadj@putty.projects.tartarus.org (see section G.1). The purpose of this request is to measure the round-trip time to the server, which PuTTY uses to tune its flow control. The server does not actually have to understand the message; it is expected to send back a SSH_MSG_CHANNEL_FAILURE message indicating that it didn't understand it. (All PuTTY needs for its timing calculations is some kind of response.)

          It has been known for some SSH servers to get confused by this message in one way or another – because it has a long name, or because they can't cope with unrecognised request names even to the extent of sending back the correct failure response, or because they handle it sensibly but fill up the server's log file with pointless spam, or whatever. PuTTY therefore supports this bug-compatibility flag: if it believes the server has this bug, it will never send its ‘winadj@putty.projects.tartarus.org’ request, and will make do without its timing data.

          -

          4.26.4 ‘Replies to requests on closed channels’

          +

          4.27.4 ‘Replies to requests on closed channels’

          The SSH protocol as published in RFC 4254 has an ambiguity which arises if one side of a connection tries to close a channel, while the other side simultaneously sends a request within the channel and asks for a reply. RFC 4254 leaves it unclear whether the closing side should reply to the channel request after having announced its intention to close the channel.

          @@ -2286,16 +2435,16 @@

          4.26.4 ‘Rep Discussion on the ietf-ssh mailing list in April 2014 formed a clear consensus that the right answer is no. However, because of the ambiguity in the specification, some SSH servers have implemented the other policy; for example, OpenSSH used to until it was fixed.

          -Because PuTTY sends channel requests with the ‘want reply’ flag throughout channels' lifetime (see section 4.26.3), it's possible that when connecting to such a server it might receive a reply to a request after it thinks the channel has entirely closed, and terminate with an error along the lines of ‘Received SSH2_MSG_CHANNEL_FAILURE for nonexistent channel 256’. +Because PuTTY sends channel requests with the ‘want reply’ flag throughout channels' lifetime (see section 4.27.3), it's possible that when connecting to such a server it might receive a reply to a request after it thinks the channel has entirely closed, and terminate with an error along the lines of ‘Received SSH2_MSG_CHANNEL_FAILURE for nonexistent channel 256’.

          -

          4.26.5 ‘Ignores SSH-2 maximum packet size’

          +

          4.27.5 ‘Ignores SSH-2 maximum packet size’

          When an SSH-2 channel is set up, each end announces the maximum size of data packet that it is willing to receive for that channel. Some servers ignore PuTTY's announcement and send packets larger than PuTTY is willing to accept, causing it to report ‘Incoming packet was garbled on decryption’.

          -If this bug is detected, PuTTY never allows the channel's flow-control window to grow large enough to allow the server to send an over-sized packet. If this bug is enabled when talking to a correct server, the session will work correctly, but download performance will be less than it could be. +If this bug is detected, PuTTY never allows the channel's flow-control window to grow large enough to allow the server to send an over-sized packet. If this bug is enabled when talking to a correct server, the session will work correctly, but download performance will be less than it could be.

          -

          4.26.6 ‘Discards data sent before its greeting’

          +

          4.27.6 ‘Discards data sent before its greeting’

          Just occasionally, an SSH connection can be established over some channel that will accidentally discard outgoing data very early in the connection.

          @@ -2308,9 +2457,22 @@

          4.26.6 ‘D

          Note that this bug flag can never be automatically detected, since auto-detection relies on the version string in the server's greeting, and PuTTY has to decide whether to expect this bug before it sees the server's greeting. So this is a manual workaround only.

          -

          4.26.7 ‘Requires padding on SSH-2 RSA signatures’

          +

          4.27.7 ‘Chokes on PuTTY's full KEXINIT

          +

          +At the start of an SSH connection, the client and server exchange long messages of type SSH_MSG_KEXINIT, containing lists of all the cryptographic algorithms they're prepared to use. This is used to negotiate a set of algorithms that both ends can speak. +

          +

          +Occasionally, a badly written server might have a length limit on the list it's prepared to receive, and refuse to make a connection simply because PuTTY is giving it too many choices. +

          +

          +A workaround is to enable this flag, which will make PuTTY wait to send KEXINIT until after it receives the one from the server, and then filter its own KEXINIT to leave out any algorithm the server doesn't also announce support for. This will generally make PuTTY's KEXINIT at most the size of the server's, and will otherwise make no difference to the algorithm negotiation. +

          +

          +This flag is a minor violation of the SSH protocol, because both sides are supposed to send KEXINIT proactively. It still works provided one side sends its KEXINIT without waiting, but if both client and server waited for the other one to speak first, the connection would deadlock. We don't know of any servers that do this, but if there is one, then this flag will make PuTTY unable to speak to them at all. +

          +

          4.27.8 ‘Requires padding on SSH-2 RSA signatures’

          -Versions below 3.3 of OpenSSH require SSH-2 RSA signatures to be padded with zero bytes to the same length as the RSA key modulus. The SSH-2 specification says that an unpadded signature MUST be accepted, so this is a bug. A typical symptom of this problem is that PuTTY mysteriously fails RSA authentication once in every few hundred attempts, and falls back to passwords. +Versions below 3.3 of OpenSSH require SSH-2 RSA signatures to be padded with zero bytes to the same length as the RSA key modulus. The SSH-2 specification says that an unpadded signature MUST be accepted, so this is a bug. A typical symptom of this problem is that PuTTY mysteriously fails RSA authentication once in every few hundred attempts, and falls back to passwords.

          If this bug is detected, PuTTY will pad its signatures in the way OpenSSH expects. If this bug is enabled when talking to a correct server, it is likely that no damage will be done, since correct servers usually still accept padded signatures because they're used to talking to OpenSSH. @@ -2318,7 +2480,7 @@

          4.26.7 ‘Require

          This is an SSH-2-specific bug.

          -

          4.26.8 ‘Only supports pre-RFC4419 SSH-2 DH GEX’

          +

          4.27.9 ‘Only supports pre-RFC4419 SSH-2 DH GEX’

          The SSH key exchange method that uses Diffie-Hellman group exchange was redesigned after its original release, to use a slightly more sophisticated setup message. Almost all SSH implementations switched over to the new version. (PuTTY was one of the last.) A few old servers still only support the old one.

          @@ -2328,9 +2490,9 @@

          4.26.8 ‘Onl

          This is an SSH-2-specific bug.

          -

          4.26.9 ‘Miscomputes SSH-2 HMAC keys’

          +

          4.27.10 ‘Miscomputes SSH-2 HMAC keys’

          -Versions 2.3.0 and below of the SSH server software from ssh.com compute the keys for their HMAC message authentication codes incorrectly. A typical symptom of this problem is that PuTTY dies unexpectedly at the beginning of the session, saying ‘Incorrect MAC received on packet’. +Versions 2.3.0 and below of the SSH server software from ssh.com compute the keys for their HMAC message authentication codes incorrectly. A typical symptom of this problem is that PuTTY dies unexpectedly at the beginning of the session, saying ‘Incorrect MAC received on packet’.

          If this bug is detected, PuTTY will compute its HMAC keys in the same way as the buggy server, so that communication will still be possible. If this bug is enabled when talking to a correct server, communication will fail. @@ -2338,9 +2500,9 @@

          4.26.9 ‘Misco

          This is an SSH-2-specific bug.

          -

          4.26.10 ‘Misuses the session ID in SSH-2 PK auth’

          +

          4.27.11 ‘Misuses the session ID in SSH-2 PK auth’

          -Versions below 2.3 of OpenSSH require SSH-2 public-key authentication to be done slightly differently: the data to be signed by the client contains the session ID formatted in a different way. If public-key authentication mysteriously does not work but the Event Log (see section 3.1.3.1) thinks it has successfully sent a signature, it might be worth enabling the workaround for this bug to see if it helps. +Versions below 2.3 of OpenSSH require SSH-2 public-key authentication to be done slightly differently: the data to be signed by the client contains the session ID formatted in a different way. If public-key authentication mysteriously does not work but the Event Log (see section 3.1.3.1) thinks it has successfully sent a signature, it might be worth enabling the workaround for this bug to see if it helps.

          If this bug is detected, PuTTY will sign data in the way OpenSSH expects. If this bug is enabled when talking to a correct server, SSH-2 public-key authentication will fail. @@ -2348,9 +2510,9 @@

          4.26.10 ‘

          This is an SSH-2-specific bug.

          -

          4.26.11 ‘Miscomputes SSH-2 encryption keys’

          +

          4.27.12 ‘Miscomputes SSH-2 encryption keys’

          -Versions below 2.0.11 of the SSH server software from ssh.com compute the keys for the session encryption incorrectly. This problem can cause various error messages, such as ‘Incoming packet was garbled on decryption’, or possibly even ‘Out of memory’. +Versions below 2.0.11 of the SSH server software from ssh.com compute the keys for the session encryption incorrectly. This problem can cause various error messages, such as ‘Incoming packet was garbled on decryption’, or possibly even ‘Out of memory’.

          If this bug is detected, PuTTY will compute its encryption keys in the same way as the buggy server, so that communication will still be possible. If this bug is enabled when talking to a correct server, communication will fail. @@ -2358,16 +2520,16 @@

          4.26.11 ̵

          This is an SSH-2-specific bug.

          -

          4.26.12 ‘Chokes on SSH-1 ignore messages’

          +

          4.27.13 ‘Chokes on SSH-1 ignore messages’

          -An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server to the client, at any time. Either side is required to ignore the message whenever it receives it. PuTTY uses ignore messages to hide the password packet in SSH-1, so that a listener cannot tell the length of the user's password; it also uses ignore messages for connection keepalives (see section 4.14.1). +An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server to the client, at any time. Either side is required to ignore the message whenever it receives it. PuTTY uses ignore messages to hide the password packet in SSH-1, so that a listener cannot tell the length of the user's password; it also uses ignore messages for connection keepalives (see section 4.14.1).

          -If this bug is detected, PuTTY will stop using ignore messages. This means that keepalives will stop working, and PuTTY will have to fall back to a secondary defence against SSH-1 password-length eavesdropping. See section 4.26.13. If this bug is enabled when talking to a correct server, the session will succeed, but keepalives will not work and the session might be more vulnerable to eavesdroppers than it could be. +If this bug is detected, PuTTY will stop using ignore messages. This means that keepalives will stop working, and PuTTY will have to fall back to a secondary defence against SSH-1 password-length eavesdropping. See section 4.27.14. If this bug is enabled when talking to a correct server, the session will succeed, but keepalives will not work and the session might be more vulnerable to eavesdroppers than it could be.

          -

          4.26.13 ‘Refuses all SSH-1 password camouflage’

          +

          4.27.14 ‘Refuses all SSH-1 password camouflage’

          -When talking to an SSH-1 server which cannot deal with ignore messages (see section 4.26.12), PuTTY will attempt to disguise the length of the user's password by sending additional padding within the password packet. This is technically a violation of the SSH-1 specification, and so PuTTY will only do it when it cannot use standards-compliant ignore messages as camouflage. In this sense, for a server to refuse to accept a padded password packet is not really a bug, but it does make life inconvenient if the server can also not handle ignore messages. +When talking to an SSH-1 server which cannot deal with ignore messages (see section 4.27.13), PuTTY will attempt to disguise the length of the user's password by sending additional padding within the password packet. This is technically a violation of the SSH-1 specification, and so PuTTY will only do it when it cannot use standards-compliant ignore messages as camouflage. In this sense, for a server to refuse to accept a padded password packet is not really a bug, but it does make life inconvenient if the server can also not handle ignore messages.

          If this ‘bug’ is detected, PuTTY will assume that neither ignore messages nor padding are acceptable, and that it thus has no choice but to send the user's password with no form of camouflage, so that an eavesdropping user will be easily able to find out the exact length of the password. If this bug is enabled when talking to a correct server, the session will succeed, but will be more vulnerable to eavesdroppers than it could be. @@ -2375,9 +2537,9 @@

          4.26.13 ‘

          This is an SSH-1-specific bug. SSH-2 is secure against this type of attack.

          -

          4.26.14 ‘Chokes on SSH-1 RSA authentication’

          +

          4.27.15 ‘Chokes on SSH-1 RSA authentication’

          -Some SSH-1 servers cannot deal with RSA authentication messages at all. If Pageant is running and contains any SSH-1 keys, PuTTY will normally automatically try RSA authentication before falling back to passwords, so these servers will crash when they see the RSA attempt. +Some SSH-1 servers cannot deal with RSA authentication messages at all. If Pageant is running and contains any SSH-1 keys, PuTTY will normally automatically try RSA authentication before falling back to passwords, so these servers will crash when they see the RSA attempt.

          If this bug is detected, PuTTY will go straight to password authentication. If this bug is enabled when talking to a correct server, the session will succeed, but of course RSA authentication will be impossible. @@ -2385,7 +2547,7 @@

          4.26.14 ‘Chok

          This is an SSH-1-specific bug.

          -

          4.27 The ‘Bare ssh-connection’ protocol

          +

          4.28 The ‘Bare ssh-connection’ protocol

          In addition to SSH itself, PuTTY also supports a second protocol that is derived from SSH. It's listed in the PuTTY GUI under the name ‘Bare ssh-connection’.

          @@ -2399,7 +2561,7 @@

          4.27 The ‘Bare same computer. In these contexts, the operating system will already have guaranteed that each of the two communicating processes is owned by the expected user (so that no authentication is necessary), and that the communications channel cannot be tapped by a hostile user on the same machine (so that no cryptography is necessary either). Examples of possible uses involve communicating with a strongly separated context such as the inside of a container, or a VM, or a different network namespace.

          -Explicit support for this protocol is new in PuTTY 0.75. As of 2021-04, the only known server for the bare ssh-connection protocol is the Unix program ‘psusan’ that is also part of the PuTTY tool suite. +Explicit support for this protocol is new in PuTTY 0.75. As of 2021-04, the only known server for the bare ssh-connection protocol is the Unix program ‘psusan’ that is also part of the PuTTY tool suite.

          (However, this protocol is also the same one used between instances of PuTTY to implement connection sharing: see section 4.17.5. In fact, in the Unix version of PuTTY, when a sharing upstream records ‘Sharing this connection at [pathname]’ in the Event Log, it's possible to connect another instance of PuTTY directly to that Unix socket, by entering its pathname in the host name box and selecting ‘Bare ssh-connection’ as the protocol!) @@ -2410,36 +2572,36 @@

          4.27 The ‘Bare I repeat, DON'T TRY TO USE THIS PROTOCOL FOR NETWORK CONNECTIONS! That's not what it's for, and it's not at all safe to do it.

          -

          4.28 The Serial panel

          +

          4.29 The Serial panel

          -The Serial panel allows you to configure options that only apply when PuTTY is connecting to a local serial line. +The Serial panel allows you to configure options that only apply when PuTTY is connecting to a local serial line.

          -

          4.28.1 Selecting a serial line to connect to

          +

          4.29.1 Selecting a serial line to connect to

          The ‘Serial line to connect to’ box allows you to choose which serial line you want PuTTY to talk to, if your computer has more than one serial port.

          -On Windows, the first serial line is called COM1, and if there is a second it is called COM2, and so on. +On Windows, the first serial line is called COM1, and if there is a second it is called COM2, and so on.

          This configuration setting is also visible on the Session panel, where it replaces the ‘Host Name’ box (see section 4.1.1) if the connection type is set to ‘Serial’.

          -

          4.28.2 Selecting the speed of your serial line

          +

          4.29.2 Selecting the speed of your serial line

          The ‘Speed’ box allows you to choose the speed (or ‘baud rate’) at which to talk to the serial line. Typical values might be 9600, 19200, 38400 or 57600. Which one you need will depend on the device at the other end of the serial cable; consult the manual for that device if you are in doubt.

          This configuration setting is also visible on the Session panel, where it replaces the ‘Port’ box (see section 4.1.1) if the connection type is set to ‘Serial’.

          -

          4.28.3 Selecting the number of data bits

          +

          4.29.3 Selecting the number of data bits

          The ‘Data bits’ box allows you to choose how many data bits are transmitted in each byte sent or received through the serial line. Typical values are 7 or 8.

          -

          4.28.4 Selecting the number of stop bits

          +

          4.29.4 Selecting the number of stop bits

          The ‘Stop bits’ box allows you to choose how many stop bits are used in the serial line protocol. Typical values are 1, 1.5 or 2.

          -

          4.28.5 Selecting the serial parity checking scheme

          +

          4.29.5 Selecting the serial parity checking scheme

          The ‘Parity’ box allows you to choose what type of parity checking is used on the serial line. The settings are:

          @@ -2459,7 +2621,7 @@

          4.28.5 Selecting th ‘Space’: an extra parity bit is sent alongside each byte, and always set to 0.

        -

        4.28.6 Selecting the serial flow control scheme

        +

        4.29.6 Selecting the serial flow control scheme

        The ‘Flow control’ box allows you to choose what type of flow control checking is used on the serial line. The settings are:

        @@ -2476,21 +2638,21 @@

        4.28.6 Selecting the ‘DSR/DTR’: flow control is done using the DSR and DTR wires on the serial line.

      -

      4.29 The Telnet panel

      +

      4.30 The Telnet panel

      The Telnet panel allows you to configure options that only apply to Telnet sessions.

      -

      4.29.1 ‘Handling of OLD_ENVIRON ambiguity’

      +

      4.30.1 ‘Handling of OLD_ENVIRON ambiguity’

      -The original Telnet mechanism for passing environment variables was badly specified. At the time the standard (RFC 1408) was written, BSD telnet implementations were already supporting the feature, and the intention of the standard was to describe the behaviour the BSD implementations were already using. +The original Telnet mechanism for passing environment variables was badly specified. At the time the standard (RFC 1408) was written, BSD telnet implementations were already supporting the feature, and the intention of the standard was to describe the behaviour the BSD implementations were already using.

      -Sadly there was a typing error in the standard when it was issued, and two vital function codes were specified the wrong way round. BSD implementations did not change, and the standard was not corrected. Therefore, it's possible you might find either BSD or RFC-compliant implementations out there. This switch allows you to choose which one PuTTY claims to be. +Sadly there was a typing error in the standard when it was issued, and two vital function codes were specified the wrong way round. BSD implementations did not change, and the standard was not corrected. Therefore, it's possible you might find either BSD or RFC-compliant implementations out there. This switch allows you to choose which one PuTTY claims to be.

      -The problem was solved by issuing a second standard, defining a new Telnet mechanism called NEW_ENVIRON, which behaved exactly like the original OLD_ENVIRON but was not encumbered by existing implementations. Most Telnet servers now support this, and it's unambiguous. This feature should only be needed if you have trouble passing environment variables to quite an old server. +The problem was solved by issuing a second standard, defining a new Telnet mechanism called NEW_ENVIRON, which behaved exactly like the original OLD_ENVIRON but was not encumbered by existing implementations. Most Telnet servers now support this, and it's unambiguous. This feature should only be needed if you have trouble passing environment variables to quite an old server.

      -

      4.29.2 Passive and active Telnet negotiation modes

      +

      4.30.2 Passive and active Telnet negotiation modes

      In a Telnet connection, there are two types of data passed between the client and the server: actual text, and negotiations about which Telnet extra features to use.

      @@ -2498,85 +2660,85 @@

      4.29.2 Passive and active PuTTY can use two different strategies for negotiation:

      • -In active mode, PuTTY starts to send negotiations as soon as the connection is opened. +In active mode, PuTTY starts to send negotiations as soon as the connection is opened.
      • -In passive mode, PuTTY will wait to negotiate until it sees a negotiation from the server. +In passive mode, PuTTY will wait to negotiate until it sees a negotiation from the server.

      The obvious disadvantage of passive mode is that if the server is also operating in a passive mode, then negotiation will never begin at all. For this reason PuTTY defaults to active mode.

      -However, sometimes passive mode is required in order to successfully get through certain types of firewall and Telnet proxy server. If you have confusing trouble with a firewall, you could try enabling passive mode to see if it helps. +However, sometimes passive mode is required in order to successfully get through certain types of firewall and Telnet proxy server. If you have confusing trouble with a firewall, you could try enabling passive mode to see if it helps.

      -

      4.29.3 ‘Keyboard sends Telnet special commands’

      +

      4.30.3 ‘Keyboard sends Telnet special commands’

      If this box is checked, several key sequences will have their normal actions modified:

      • -the Backspace key on the keyboard will send the Telnet special backspace code; +the Backspace key on the keyboard will send the Telnet special backspace code;
      • -Control-C will send the Telnet special Interrupt Process code; +Control-C will send the Telnet special Interrupt Process code;
      • -Control-Z will send the Telnet special Suspend Process code. +Control-Z will send the Telnet special Suspend Process code.

      You probably shouldn't enable this unless you know what you're doing.

      -

      4.29.4 ‘Return key sends Telnet New Line instead of ^M’

      +

      4.30.4 ‘Return key sends Telnet New Line instead of ^M’

      -Unlike most other remote login protocols, the Telnet protocol has a special ‘new line’ code that is not the same as the usual line endings of Control-M or Control-J. By default, PuTTY sends the Telnet New Line code when you press Return, instead of sending Control-M as it does in most other protocols. +Unlike most other remote login protocols, the Telnet protocol has a special ‘new line’ code that is not the same as the usual line endings of Control-M or Control-J. By default, PuTTY sends the Telnet New Line code when you press Return, instead of sending Control-M as it does in most other protocols.

      Most Unix-style Telnet servers don't mind whether they receive Telnet New Line or Control-M; some servers do expect New Line, and some servers prefer to see ^M. If you are seeing surprising behaviour when you press Return in a Telnet session, you might try turning this option off to see if it helps.

      -

      4.30 The Rlogin panel

      +

      4.31 The Rlogin panel

      -The Rlogin panel allows you to configure options that only apply to Rlogin sessions. +The Rlogin panel allows you to configure options that only apply to Rlogin sessions.

      -

      4.30.1 ‘Local username’

      +

      4.31.1 ‘Local username’

      -Rlogin allows an automated (password-free) form of login by means of a file called .rhosts on the server. You put a line in your .rhosts file saying something like jbloggs@pc1.example.com, and then when you make an Rlogin connection the client transmits the username of the user running the Rlogin client. The server checks the username and hostname against .rhosts, and if they match it does not ask for a password. +Rlogin allows an automated (password-free) form of login by means of a file called .rhosts on the server. You put a line in your .rhosts file saying something like jbloggs@pc1.example.com, and then when you make an Rlogin connection the client transmits the username of the user running the Rlogin client. The server checks the username and hostname against .rhosts, and if they match it does not ask for a password.

      -This only works because Unix systems contain a safeguard to stop a user from pretending to be another user in an Rlogin connection. Rlogin connections have to come from port numbers below 1024, and Unix systems prohibit this to unprivileged processes; so when the server sees a connection from a low-numbered port, it assumes the client end of the connection is held by a privileged (and therefore trusted) process, so it believes the claim of who the user is. +This only works because Unix systems contain a safeguard to stop a user from pretending to be another user in an Rlogin connection. Rlogin connections have to come from port numbers below 1024, and Unix systems prohibit this to unprivileged processes; so when the server sees a connection from a low-numbered port, it assumes the client end of the connection is held by a privileged (and therefore trusted) process, so it believes the claim of who the user is.

      -Windows does not have this restriction: any user can initiate an outgoing connection from a low-numbered port. Hence, the Rlogin .rhosts mechanism is completely useless for securely distinguishing several different users on a Windows machine. If you have a .rhosts entry pointing at a Windows PC, you should assume that anyone using that PC can spoof your username in an Rlogin connection and access your account on the server. +Windows does not have this restriction: any user can initiate an outgoing connection from a low-numbered port. Hence, the Rlogin .rhosts mechanism is completely useless for securely distinguishing several different users on a Windows machine. If you have a .rhosts entry pointing at a Windows PC, you should assume that anyone using that PC can spoof your username in an Rlogin connection and access your account on the server.

      -The ‘Local username’ control allows you to specify what user name PuTTY should claim you have, in case it doesn't match your Windows user name (or in case you didn't bother to set up a Windows user name). +The ‘Local username’ control allows you to specify what user name PuTTY should claim you have, in case it doesn't match your Windows user name (or in case you didn't bother to set up a Windows user name).

      -

      4.31 The SUPDUP panel

      +

      4.32 The SUPDUP panel

      The SUPDUP panel allows you to configure options that only apply to SUPDUP sessions. See section 3.10 for more about the SUPDUP protocol.

      -

      4.31.1 ‘Location string’

      +

      4.32.1 ‘Location string’

      In SUPDUP, the client sends a piece of text of its choice to the server giving the user's location. This is typically displayed in lists of logged-in users.

      By default, PuTTY just defaults this to "The Internet". If you want your location to show up as something more specific, you can configure it here.

      -

      4.31.2 ‘Extended ASCII Character set’

      +

      4.32.2 ‘Extended ASCII Character set’

      This declares what kind of character set extension your terminal supports. If the server supports it, it will send text using that character set. ‘None’ means the standard 95 printable ASCII characters. ‘ITS’ means ASCII extended with printable characters in the control character range. This character set is documented in the SUPDUP protocol definition. ‘WAITS’ is similar to ‘ITS’ but uses some alternative characters in the extended set: most prominently, it will display arrows instead of ^ and _, and } instead of ~. ‘ITS’ extended ASCII is used by ITS and Lisp machines, whilst ‘WAITS’ is only used by the WAITS operating system from the Stanford AI Laboratory.

      -

      4.31.3 ‘**MORE** processing’

      +

      4.32.3 ‘**MORE** processing’

      When **MORE** processing is enabled, the server causes output to pause at the bottom of the screen, until a space is typed.

      -

      4.31.4 ‘Terminal scrolling’

      +

      4.32.4 ‘Terminal scrolling’

      This controls whether the terminal will perform scrolling then the cursor goes below the last line, or if the cursor will return to the first line.

      -

      4.32 Storing configuration in a file

      +

      4.33 Storing configuration in a file

      -PuTTY does not currently support storing its configuration in a file instead of the Registry. However, you can work around this with a couple of batch files. +PuTTY does not currently support storing its configuration in a file instead of the Registry. However, you can work around this with a couple of batch files.

      You will need a file called (say) PUTTY.BAT which imports the contents of a file into the Registry, then runs PuTTY, exports the contents of the Registry back into the file, and deletes the Registry entries. This can all be done using the Regedit command line options, so it's all automatic. Here is what you need in PUTTY.BAT: @@ -2612,5 +2774,6 @@

      4.32 Storin You should replace a:\putty.rnd with the location where you want to store your random number data. If the aim is to carry around PuTTY and its settings on one USB stick, you probably want to store it on the USB stick.

      -

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +

      If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

      +[PuTTY release 0.78]
      diff --git a/code/doc/html/Chapter5.html b/code/doc/html/Chapter5.html index 60986092..bce297c3 100644 --- a/code/doc/html/Chapter5.html +++ b/code/doc/html/Chapter5.html @@ -51,7 +51,7 @@

      5.2 PSCP Usage

      C:\>pscp
       PuTTY Secure Copy client
      -Release 0.77
      +Release 0.78
       Usage: pscp [options] [user@]host:source target
              pscp [options] source [source...] [user@]host:target
              pscp [options] -ls [user@]host:filespec
      @@ -247,7 +247,7 @@ 

      5.2.4 Using section 5.2.1.2). So you would do this:

      • -Run PuTTY, and create a PuTTY saved session (see section 4.1.2) which specifies your private key file (see section 4.21.9). You will probably also want to specify a username to log in as (see section 4.15.1). +Run PuTTY, and create a PuTTY saved session (see section 4.1.2) which specifies your private key file (see section 4.22.1). You will probably also want to specify a username to log in as (see section 4.15.1).
      • In PSCP, you can now use the name of the session instead of a hostname: type pscp sessionname:file localfile, where sessionname is replaced by the name of your saved session. @@ -270,5 +270,6 @@

        5.2.4 Using chapter 8.

        -

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +

        If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

        +[PuTTY release 0.78]
        diff --git a/code/doc/html/Chapter6.html b/code/doc/html/Chapter6.html index 65988426..f80d5943 100644 --- a/code/doc/html/Chapter6.html +++ b/code/doc/html/Chapter6.html @@ -511,7 +511,7 @@

        6.3 Using pu Firstly, PSFTP can use PuTTY saved sessions in place of hostnames. So you might do this:

      • @@ -63,7 +64,7 @@

        8.1 Public ke

        8.2 Using PuTTYgen, the PuTTY key generator

        -PuTTYgen is a key generator. It generates pairs of public and private keys to be used with PuTTY, PSCP, and Plink, as well as the PuTTY authentication agent, Pageant (see chapter 9). PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys. +PuTTYgen is a key generator. It generates pairs of public and private keys to be used with PuTTY, PSCP, PSFTP, and Plink, as well as the PuTTY authentication agent, Pageant (see chapter 9). PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys.

        When you run PuTTYgen you will see a window where you have two main choices: ‘Generate’, to generate a new public/private key pair, or ‘Load’ to load in an existing private key. @@ -82,11 +83,11 @@

        8.2.1 Generating a ne Once you have generated the key, select a comment field (section 8.2.7) and a passphrase (section 8.2.8).
      • -Now you're ready to save the private key to disk; press the ‘Save private key’ button. (See section 8.2.9). +Now you're ready to save the private key to disk; press the ‘Save private key’ button. (See section 8.2.10).

      -Your key pair is now ready for use. You may also want to copy the public key to your server, either by copying it out of the ‘Public key for pasting into OpenSSH authorized_keys file’ box (see section 8.2.11), or by using the ‘Save public key’ button (section 8.2.10). However, you don't need to do this immediately; if you want, you can load the private key back into PuTTYgen later (see section 8.2.13) and the public key will be available for copying and pasting again. +Your key pair is now ready for use. You may also want to copy the public key to your server, either by copying it out of the ‘Public key for pasting into OpenSSH authorized_keys file’ box (see section 8.2.12), or by using the ‘Save public key’ button (section 8.2.11). However, you don't need to do this immediately; if you want, you can load the private key back into PuTTYgen later (see section 8.2.14) and the public key will be available for copying and pasting again.

      Section 8.3 describes the typical process of configuring PuTTY to attempt public-key authentication, and configuring your SSH server to accept it. @@ -119,33 +120,33 @@

      8.2.3 Selecting the siz The ‘Number of bits’ input box allows you to choose the strength of the key PuTTYgen will generate.

      • -For RSA and DSA, 2048 bits should currently be sufficient for most purposes. +For RSA and DSA, 2048 bits should currently be sufficient for most purposes. (Smaller keys of these types are no longer considered secure, and PuTTYgen will warn if you try to generate them.)
      • -For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers equivalent security to RSA with smaller key sizes.) +For ECDSA, only 256, 384, and 521 bits are supported, corresponding to NIST-standardised elliptic curves. (Elliptic-curve keys do not need as many bits as RSA keys for equivalent security, so these numbers are smaller than the RSA recommendations.)
      • -For EdDSA, the only valid sizes are 255 bits (these keys are also known as ‘Ed25519’ and are commonly used) and 448 bits (‘Ed448’, which is much less common at the time of writing). (256 is also accepted for backward compatibility, but the effect is the same as 255.) +For EdDSA, the only valid sizes are 255 bits (these keys are also known as ‘Ed25519’ and are commonly used) and 448 bits (‘Ed448’, which is much less common at the time of writing). (256 is also accepted for backward compatibility, but the effect is the same as 255.)
      -

      8.2.4 Selecting the prime generation method

      +

      8.2.4 Selecting the prime generation method

      -On the ‘Key’ menu, you can also optionally change the method for generating the prime numbers used in the generated key. This is used for RSA and DSA keys only. (The other key types don't require generating prime numbers at all.) +(This is entirely optional. Unless you know better, it's entirely sensible to skip this and use the default settings.)

      -The prime-generation method does not affect compatibility: a key generated with any of these methods will still work with all the same SSH servers. +On the ‘Key’ menu, you can also optionally change the method for generating the prime numbers used in the generated key. This is used for RSA and DSA keys only. (The other key types don't require generating prime numbers at all.)

      -If you don't care about this, it's entirely sensible to leave it on the default setting. +The prime-generation method does not affect compatibility: a key generated with any of these methods will still work with all the same SSH servers.

      The available methods are:

      • -Use probable primes (fast) +Use probable primes (fast)
      • -Use proven primes (slower) +Use proven primes (slower)
      • Use proven primes with even distribution (slowest) @@ -158,13 +159,13 @@

        8.2.4 Selecting the sure are prime, because it generates the output number together with a proof of its primality. This takes more effort, but it eliminates that theoretical risk in the probabilistic method.

        -There in one way in which PuTTYgen's ‘proven primes’ method is not strictly better than its ‘probable primes’ method. If you use PuTTYgen to generate an RSA key on a computer that is potentially susceptible to timing- or cache-based side-channel attacks, such as a shared computer, the ‘probable primes’ method is designed to resist such attacks, whereas the ‘proven primes’ methods are not. (This is only a concern for RSA keys; for other key types, primes are either not secret or not involved.) +There in one way in which PuTTYgen's ‘proven primes’ method is not strictly better than its ‘probable primes’ method. If you use PuTTYgen to generate an RSA key on a computer that is potentially susceptible to timing- or cache-based side-channel attacks, such as a shared computer, the ‘probable primes’ method is designed to resist such attacks, whereas the ‘proven primes’ methods are not. (This is only a concern for RSA keys; for other key types, primes are either not secret or not involved.)

        You might choose to switch from probable to proven primes if you have a local security standard that demands it, or if you don't trust the probabilistic argument for the safety of the usual method.

        -For RSA keys, there's also an option on the ‘Key’ menu to use ‘strong’ primes as the prime factors of the public key. A ‘strong’ prime is a prime number chosen to have a particular structure that makes certain factoring algorithms more difficult to apply, so some security standards recommend their use. However, the most modern factoring algorithms are unaffected, so this option is probably not worth turning on unless you have a local standard that recommends it. +For RSA keys, there's also an option on the ‘Key’ menu to use ‘strong’ primes as the prime factors of the public key. A ‘strong’ prime is a prime number chosen to have a particular structure that makes certain factoring algorithms more difficult to apply, so some security standards recommend their use. However, the most modern factoring algorithms are unaffected, so this option is probably not worth turning on unless you have a local standard that recommends it.

        8.2.5 The ‘Generate’ button

        @@ -179,7 +180,7 @@

        8.2.5 The ‘Genera

        When the key generation is complete, a new set of controls will appear in the window to indicate this.

        -

        8.2.6 The ‘Key fingerprint’ box

        +

        8.2.6 The ‘Key fingerprint’ box

        The ‘Key fingerprint’ box shows you a fingerprint value for the generated key. This is derived cryptographically from the public key value, so it doesn't need to be kept secret; it is supposed to be more manageable for human beings than the public key itself.

        @@ -187,7 +188,7 @@

        8.2.6 The ‘section 9.2.1) and the Unix ssh-add utility, will list key fingerprints rather than the whole public key.

        -By default, PuTTYgen will display fingerprints in the ‘SHA256’ format. If you need to see the fingerprint in the older ‘MD5’ format (which looks like aa:bb:cc:...), you can choose ‘Show fingerprint as MD5’ from the ‘Key’ menu, but bear in mind that this is less cryptographically secure; it may be feasible for an attacker to create a key with the same fingerprint as yours. +By default, PuTTYgen will display SSH-2 key fingerprints in the ‘SHA256’ format. If you need to see the fingerprint in the older ‘MD5’ format (which looks like aa:bb:cc:...), you can choose ‘Show fingerprint as MD5’ from the ‘Key’ menu, but bear in mind that this is less cryptographically secure; it may be feasible for an attacker to create a key with the same fingerprint as yours.

        8.2.7 Setting a comment for your key

        @@ -199,26 +200,48 @@

        8.2.7 Setting a comment

        To alter the key comment, just type your comment text into the ‘Key comment’ box before saving the private key. If you want to change the comment later, you can load the private key back into PuTTYgen, change the comment, and save it again.

        -

        8.2.8 Setting a passphrase for your key

        +

        8.2.8 Setting a passphrase for your key

        -The ‘Key passphrase’ and ‘Confirm passphrase’ boxes allow you to choose a passphrase for your key. The passphrase will be used to encrypt the key on disk, so you will not be able to use the key without first entering the passphrase. +The ‘Key passphrase’ and ‘Confirm passphrase’ boxes allow you to choose a passphrase for your key. The passphrase will be used to encrypt the key on disk, so you will not be able to use the key without first entering the passphrase.

        When you save the key, PuTTYgen will check that the ‘Key passphrase’ and ‘Confirm passphrase’ boxes both contain exactly the same passphrase, and will refuse to save the key otherwise.

        -If you leave the passphrase fields blank, the key will be saved unencrypted. You should not do this without good reason; if you do, your private key file on disk will be all an attacker needs to gain access to any machine configured to accept that key. If you want to be able to log in without having to type a passphrase every time, you should consider using Pageant (chapter 9) so that your decrypted key is only held in memory rather than on disk. +If you leave the passphrase fields blank, the key will be saved unencrypted. You should not do this without good reason; if you do, your private key file on disk will be all an attacker needs to gain access to any machine configured to accept that key. If you want to be able to log in without having to type a passphrase every time, you should consider using Pageant (chapter 9) so that your decrypted key is only held in memory rather than on disk.

        Under special circumstances you may genuinely need to use a key with no passphrase; for example, if you need to run an automated batch script that needs to make an SSH connection, you can't be there to type the passphrase. In this case we recommend you generate a special key for each specific batch script (or whatever) that needs one, and on the server side you should arrange that each key is restricted so that it can only be used for that specific purpose. The documentation for your SSH server should explain how to do this (it will probably vary between servers).

        -Choosing a good passphrase is difficult. Just as you shouldn't use a dictionary word as a password because it's easy for an attacker to run through a whole dictionary, you should not use a song lyric, quotation or other well-known sentence as a passphrase. DiceWare (www.diceware.com) recommends using at least five words each generated randomly by rolling five dice, which gives over 2^64 possible passphrases and is probably not a bad scheme. If you want your passphrase to make grammatical sense, this cuts down the possibilities a lot and you should use a longer one as a result. +Choosing a good passphrase is difficult. Just as you shouldn't use a dictionary word as a password because it's easy for an attacker to run through a whole dictionary, you should not use a song lyric, quotation or other well-known sentence as a passphrase. DiceWare (www.diceware.com) recommends using at least five words each generated randomly by rolling five dice, which gives over 2^64 possible passphrases and is probably not a bad scheme. If you want your passphrase to make grammatical sense, this cuts down the possibilities a lot and you should use a longer one as a result.

        Do not forget your passphrase. There is no way to recover it.

        -

        8.2.9 Saving your private key to a disk file

        +

        8.2.9 Adding a certificate to your key

        +

        +In some environments, user authentication keys can be signed in turn by a ‘certifying authority’ (‘CA’ for short), and user accounts on an SSH server can be configured to automatically trust any key that's certified by the right signature. +

        +

        +This can be a convenient setup if you have a very large number of servers. When you change your key pair, you might otherwise have to edit the authorized_keys file on every server individually, to make them all accept the new key. But if instead you configure all those servers once to accept keys signed as yours by a CA, then when you change your public key, all you have to do is to get the new key certified by the same CA as before, and then all your servers will automatically accept it without needing individual reconfiguration. +

        +

        +To get your key signed by a CA, you'll probably send the CA the new public key (not the private half), and get back a modified version of the public key with the certificate included. +

        +

        +If you want to incorporate the certificate into your PPK file for convenience, you can use the ‘Add certificate to key’ menu option in PuTTYgen's ‘Key’ menu. This will give you a single file containing your private key and the certificate, which is everything you need to authenticate to a server prepared to accept that certificate. +

        +

        +To remove the certificate again and restore the uncertified PPK file, there's also a ‘Remove certificate from key’ option. +

        +

        +(However, you don't have to incorporate the certificate into your PPK file. You can equally well use it separately, via the ‘Certificate to use with the private key’ option in PuTTY itself. See section 4.22.2. It's up to you which you find more convenient.) +

        +

        +When the currently loaded key in PuTTYgen contains a certificate, the large ‘Public key for pasting’ edit box (see section 8.2.12) is replaced by a button that brings up an information box telling you about the certificate, such as who it certifies your key as belonging to, when it expires (if ever), and the fingerprint of the CA key that signed it in turn. +

        +

        8.2.10 Saving your private key to a disk file

        Once you have generated a key, set a comment field and set a passphrase, you are ready to save your private key to disk.

        @@ -226,14 +249,14 @@

        8.2.9 Saving your priva Press the ‘Save private key’ button. PuTTYgen will put up a dialog box asking you where to save the file. Select a directory, type in a file name, and press ‘Save’.

        -This file is in PuTTY's native format (*.PPK); it is the one you will need to tell PuTTY to use for authentication (see section 4.21.9) or tell Pageant to load (see section 9.2.2). +This file is in PuTTY's native format (*.PPK); it is the one you will need to tell PuTTY to use for authentication (see section 4.22.1) or tell Pageant to load (see section 9.2.2).

        -(You can optionally change some details of the PPK format for your saved key files; see section 8.2.12. But The defaults should be fine for most purposes.) +(You can optionally change some details of the PPK format for your saved key files; see section 8.2.13. But The defaults should be fine for most purposes.)

        -

        8.2.10 Saving your public key to a disk file

        +

        8.2.11 Saving your public key to a disk file

        -RFC 4716 specifies a standard format for storing SSH-2 public keys on disk. Some SSH servers (such as ssh.com's) require a public key in this format in order to accept authentication with the corresponding private key. (Others, such as OpenSSH, use a different format; see section 8.2.11.) +RFC 4716 specifies a standard format for storing SSH-2 public keys on disk. Some SSH servers (such as ssh.com's) require a public key in this format in order to accept authentication with the corresponding private key. (Others, such as OpenSSH, use a different format; see section 8.2.12.)

        To save your public key in the SSH-2 standard format, press the ‘Save public key’ button in PuTTYgen. PuTTYgen will put up a dialog box asking you where to save the file. Select a directory, type in a file name, and press ‘Save’. @@ -244,9 +267,9 @@

        8.2.10 Saving your publ

        If you use this option with an SSH-1 key, the file PuTTYgen saves will contain exactly the same text that appears in the ‘Public key for pasting’ box. This is the only existing standard for SSH-1 public keys.

        -

        8.2.11 ‘Public key for pasting into OpenSSH authorized_keys file’

        +

        8.2.12 ‘Public key for pasting into OpenSSH authorized_keys file’

        -The OpenSSH server, among others, requires your public key to be given to it in a one-line format before it will accept authentication with your private key. (SSH-1 servers also used this method.) +The OpenSSH server, among others, requires your public key to be given to it in a one-line format before it will accept authentication with your private key. (SSH-1 servers also used this method.)

        The ‘Public key for pasting into OpenSSH authorized_keys file’ gives the public-key data in the correct one-line format. Typically you will want to select the entire contents of the box using the mouse, press Ctrl+C to copy it to the clipboard, and then paste the data into a PuTTY session which is already connected to the server. @@ -254,14 +277,14 @@

        8.2.11 ‘Public k

        See section 8.3 for general instructions on configuring public-key authentication once you have generated a key.

        -

        8.2.12 Parameters for saving key files

        +

        8.2.13 Parameters for saving key files

        Selecting ‘Parameters for saving key files...’ from the ‘Key’ menu lets you adjust some aspects of PPK-format private key files stored on disk. None of these options affect compatibility with SSH servers.

        In most cases, it's entirely sensible to leave all of these at their default settings.

        -

        8.2.12.1 PPK file version

        +

        8.2.13.1 PPK file version

        This defaults to version 3, which is fine for most uses.

        @@ -271,7 +294,7 @@

        8.2.12.1 PPK

        The version 2 format is less resistant to brute-force decryption, and doesn't support any of the following options to control that.

        -

        8.2.12.2 Options affecting passphrase hashing

        +

        8.2.13.2 Options affecting passphrase hashing

        All of the following options only affect keys saved with passphrases. They control how much work is required to decrypt the key (which happens every time you type its passphrase). This allows you to trade off the cost of legitimate use of the key against the resistance of the encrypted key to password-guessing attacks.

        @@ -282,7 +305,7 @@

        8.2.1 Key derivation function
        -The variant of the Argon2 key derivation function to use. You might change this if you consider your exposure to side-channel attacks to be different to the norm. +The variant of the Argon2 key derivation function to use. You might change this if you consider your exposure to side-channel attacks to be different to the norm.
        Memory to use for passphrase hash @@ -303,7 +326,7 @@

        8.2.1 Number of parallelisable threads that can be used to decrypt the key. The default, 1, forces the process to run single-threaded, even on machines with multiple cores. -

        8.2.13 Reloading a private key

        +

        8.2.14 Reloading a private key

        PuTTYgen allows you to load an existing private key file into memory. If you do this, you can then change the passphrase and comment before saving it again; you can also make extra copies of the public key.

        @@ -311,17 +334,17 @@

        8.2.13 Reloading a private To load an existing key, press the ‘Load’ button. PuTTYgen will put up a dialog box where you can browse around the file system and find your key file. Once you select the file, PuTTYgen will ask you for a passphrase (if necessary) and will then display the key details in the same way as if it had just generated the key.

        -If you use the Load command to load a foreign key format, it will work, but you will see a message box warning you that the key you have loaded is not a PuTTY native key. See section 8.2.14 for information about importing foreign key formats. +If you use the Load command to load a foreign key format, it will work, but you will see a message box warning you that the key you have loaded is not a PuTTY native key. See section 8.2.15 for information about importing foreign key formats.

        -

        8.2.14 Dealing with private keys in other formats

        +

        8.2.15 Dealing with private keys in other formats

        -SSH-2 private keys have no standard format. OpenSSH and ssh.com have different formats, and PuTTY's is different again. So a key generated with one client cannot immediately be used with another. +SSH-2 private keys have no standard format. OpenSSH and ssh.com have different formats, and PuTTY's is different again. So a key generated with one client cannot immediately be used with another.

        -Using the ‘Import’ command from the ‘Conversions’ menu, PuTTYgen can load SSH-2 private keys in OpenSSH's format and ssh.com's format. Once you have loaded one of these key types, you can then save it back out as a PuTTY-format key (*.PPK) so that you can use it with the PuTTY suite. The passphrase will be unchanged by this process (unless you deliberately change it). You may want to change the key comment before you save the key, since some OpenSSH key formats contained no space for a comment, and ssh.com's default comment format is long and verbose. +Using the ‘Import’ command from the ‘Conversions’ menu, PuTTYgen can load SSH-2 private keys in OpenSSH's format and ssh.com's format. Once you have loaded one of these key types, you can then save it back out as a PuTTY-format key (*.PPK) so that you can use it with the PuTTY suite. The passphrase will be unchanged by this process (unless you deliberately change it). You may want to change the key comment before you save the key, since some OpenSSH key formats contained no space for a comment, and ssh.com's default comment format is long and verbose.

        -PuTTYgen can also export private keys in OpenSSH format and in ssh.com format. To do so, select one of the ‘Export’ options from the ‘Conversions’ menu. Exporting a key works exactly like saving it (see section 8.2.9) - you need to have typed your passphrase in beforehand, and you will be warned if you are about to save a key without a passphrase. +PuTTYgen can also export private keys in OpenSSH format and in ssh.com format. To do so, select one of the ‘Export’ options from the ‘Conversions’ menu. Exporting a key works exactly like saving it (see section 8.2.10) - you need to have typed your passphrase in beforehand, and you will be warned if you are about to save a key without a passphrase.

        For OpenSSH there are two options. Modern OpenSSH actually has two formats it uses for storing private keys. ‘Export OpenSSH key’ will automatically choose the oldest format supported for the key type, for maximum backward compatibility with older versions of OpenSSH; for newer key types like Ed25519, it will use the newer format as that is the only legal option. If you have some specific reason for wanting to use OpenSSH's newer format even for RSA, DSA, or ECDSA keys, you can choose ‘Export OpenSSH key (force new file format)’. @@ -329,7 +352,7 @@

        8.2.14 Dealing with

        Most clients for the older SSH-1 protocol use a standard format for storing private keys on disk. PuTTY uses this format as well; so if you have generated an SSH-1 private key using OpenSSH or ssh.com's client, you can use it with PuTTY, and vice versa. Hence, the export options are not available if you have generated an SSH-1 key.

        -

        8.2.15 PuTTYgen command-line configuration

        +

        8.2.16 PuTTYgen command-line configuration

        PuTTYgen supports a set of command-line options to configure many of the same settings you can select in the GUI. This allows you to start it up with your own preferences ready-selected, which might be useful if you generate a lot of keys. (For example, you could make a Windows shortcut that runs PuTTYgen with some command line options, or a batch file or Powershell script that you could distribute to a whole organisation containing your local standards.)

        @@ -340,7 +363,7 @@

        8.2.15 PuTTYgen command-lin -t keytype

        -Type of key to generate. You can select rsa, dsa, ecdsa, eddsa or rsa1. See section 8.2.2. +Type of key to generate. You can select rsa, dsa, ecdsa, eddsa, ed25519, ed448, or rsa1. See section 8.2.2.
        -b bits @@ -364,7 +387,7 @@

        8.2.15 PuTTYgen command-lin --ppk-param key=value,...

        -Allows setting all the same details of the PPK save file format described in section 8.2.12. +Allows setting all the same details of the PPK save file format described in section 8.2.13.

        Aspects to change are specified as a series of key=value pairs separated by commas. The keys are:

        @@ -419,14 +442,14 @@

        8.3 Getting ready for p Connect to your SSH server using PuTTY with the SSH protocol. When the connection succeeds you will be prompted for your user name and password to login. Once logged in, you must configure the server to accept your public key for authentication:

        • -If your server is OpenSSH, you should change into the .ssh directory under your home directory, and open the file authorized_keys with your favourite editor. (You may have to create this file, if this is the first key you have put in it.) Then switch to the PuTTYgen window, select all of the text in the ‘Public key for pasting into OpenSSH authorized_keys file’ box (see section 8.2.11), and copy it to the clipboard (Ctrl+C). Then, switch back to the PuTTY window and insert the data into the open file, making sure it ends up all on one line. Save the file. +If your server is OpenSSH, you should change into the .ssh directory under your home directory, and open the file authorized_keys with your favourite editor. (You may have to create this file, if this is the first key you have put in it.) Then switch to the PuTTYgen window, select all of the text in the ‘Public key for pasting into OpenSSH authorized_keys file’ box (see section 8.2.12), and copy it to the clipboard (Ctrl+C). Then, switch back to the PuTTY window and insert the data into the open file, making sure it ends up all on one line. Save the file.

          (In very old versions of OpenSSH, SSH-2 keys had to be put in a separate file called authorized_keys2. In all current versions, the same authorized_keys file is used for both SSH-1 and SSH-2 keys.)

        • -If your server is ssh.com's product and is using SSH-2, you need to save a public key file from PuTTYgen (see section 8.2.10), and copy that into the .ssh2 directory on the server. Then you should go into that .ssh2 directory, and edit (or create) a file called authorization. In this file you should put a line like Key mykey.pub, with mykey.pub replaced by the name of your key file. +If your server is ssh.com's product and is using SSH-2, you need to save a public key file from PuTTYgen (see section 8.2.11), and copy that into the .ssh2 directory on the server. Then you should go into that .ssh2 directory, and edit (or create) a file called authorization. In this file you should put a line like Key mykey.pub, with mykey.pub replaced by the name of your key file.
        • For other SSH server software, you should refer to the manual for that server. @@ -441,7 +464,7 @@

          8.3 Getting ready for p Your server should now be configured to accept authentication using your private key. Now you need to configure PuTTY to attempt authentication using your private key. You can do this in any of three ways:

          • -Select the private key in PuTTY's configuration. See section 4.21.9 for details. +Select the private key in PuTTY's configuration. See section 4.22.1 for details.
          • Specify the key file on the command line with the -i option. See section 3.11.3.18 for details. @@ -451,5 +474,6 @@

            8.3 Getting ready for p

          -

          If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

          +

          If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

          +[PuTTY release 0.78]
          diff --git a/code/doc/html/Chapter9.html b/code/doc/html/Chapter9.html index 02c91e61..200be8ad 100644 --- a/code/doc/html/Chapter9.html +++ b/code/doc/html/Chapter9.html @@ -28,8 +28,9 @@
        • 9.3.1 Making Pageant automatically load keys on startup
        • 9.3.2 Making Pageant run another program
        • 9.3.3 Integrating with Windows OpenSSH
        • -
        • 9.3.4 Starting with the key list visible
        • -
        • 9.3.5 Restricting the Windows process ACL
        • +
        • 9.3.4 Unix-domain sockets: integrating with WSL 1
        • +
        • 9.3.5 Starting with the key list visible
        • +
        • 9.3.6 Restricting the Windows process ACL

      • 9.4 Using agent forwarding
      • 9.5 Loading keys without decrypting them
      • @@ -79,22 +80,25 @@

        9.2.1 The key lis

        The large list box in the Pageant main window lists the private keys that are currently loaded into Pageant. The list might look something like this:

        -
        ssh-ed25519  SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
        -ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
        +
        Ed25519    SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
        +RSA  2048  SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
         

        For each key, the list box will tell you:

        • -The type of the key. Currently, this can be ssh-rsa (an RSA key for use with the SSH-2 protocol), ssh-dss (a DSA key for use with the SSH-2 protocol), ecdsa-sha2-* (an ECDSA key for use with the SSH-2 protocol), ssh-ed25519 (an Ed25519 key for use with the SSH-2 protocol), ssh-ed448 (an Ed448 key for use with the SSH-2 protocol), or ssh1 (an RSA key for use with the old SSH-1 protocol). +The type of the key. Currently, this can be ‘RSA’ (an RSA key for use with the SSH-2 protocol), ‘DSA’ (a DSA key for use with the SSH-2 protocol), ‘NIST’ (an ECDSA key for use with the SSH-2 protocol), ‘Ed25519’ (an Ed25519 key for use with the SSH-2 protocol), ‘Ed448’ (an Ed448 key for use with the SSH-2 protocol), or ‘SSH-1’ (an RSA key for use with the old SSH-1 protocol). (If the key has an associated certificate, this is shown here with a ‘cert’ suffix.)
        • -The size (in bits) of the key, for key types that come in different sizes. +The size (in bits) of the key, for key types that come in different sizes. (For ECDSA ‘NIST’ keys, this is indicated as ‘p256’ or ‘p384’ or ‘p521’.)
        • -The fingerprint for the public key. This should be the same fingerprint given by PuTTYgen, and (hopefully) also the same fingerprint shown by remote utilities such as ssh-keygen when applied to your authorized_keys file. +The fingerprint for the public key. This should be the same fingerprint given by PuTTYgen, and (hopefully) also the same fingerprint shown by remote utilities such as ssh-keygen when applied to your authorized_keys file.

          -By default this is shown in the ‘SHA256’ format. You can change to the older ‘MD5’ format (which looks like aa:bb:cc:...) with the ‘Fingerprint type’ drop-down, but bear in mind that this format is less secure and should be avoided for comparison purposes where possible. +For SSH-2 keys, by default this is shown in the ‘SHA256’ format. You can change to the older ‘MD5’ format (which looks like aa:bb:cc:...) with the ‘Fingerprint type’ drop-down, but bear in mind that this format is less secure and should be avoided for comparison purposes where possible. +

          +

          +If some of the keys loaded into Pageant have certificates attached, then Pageant will default to showing the fingerprint of the underlying key. This way, a certified and uncertified version of the same key will have the same fingerprint, so you can see that they match. You can instead use the ‘Fingerprint type’ drop-down to ask for a different fingerprint to be shown for certified keys, which includes the certificate as part of the fingerprinted data. That way you can tell two certificates apart.

        • @@ -127,7 +131,7 @@

          9.2.3 The ‘R

          9.3 The Pageant command line

          -Pageant can be made to do things automatically when it starts up, by specifying instructions on its command line. If you're starting Pageant from the Windows GUI, you can arrange this by editing the properties of the Windows shortcut that it was started from. +Pageant can be made to do things automatically when it starts up, by specifying instructions on its command line. If you're starting Pageant from the Windows GUI, you can arrange this by editing the properties of the Windows shortcut that it was started from.

          If Pageant is already running, invoking it again with the options below causes actions to be performed with the existing instance, not a new one. @@ -152,11 +156,11 @@

          9.3.2 Making Page You can arrange for Pageant to start another program once it has initialised itself and loaded any keys specified on its command line. This program (perhaps a PuTTY, or a WinCVS making use of Plink, or whatever) will then be able to use the keys Pageant has loaded.

          -You do this by specifying the -c option followed by the command, like this: +You do this by specifying the -c option followed by the command, like this:

          C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe
           
          -

          9.3.3 Integrating with Windows OpenSSH

          +

          9.3.3 Integrating with Windows OpenSSH

          Windows's own port of OpenSSH uses the same mechanism as Pageant to talk to its SSH agent (Windows named pipes). This means that Windows OpenSSH can talk directly to Pageant, if it knows where to find Pageant's named pipe.

          @@ -164,7 +168,7 @@

          9.3.3 Integrating When Pageant starts up, it can optionally write out a file containing an OpenSSH configuration directive that tells the Windows ssh.exe where to find Pageant. If you include this file from your Windows SSH configuration, then ssh.exe should automatically use Pageant as its agent, so that you can keep your keys in one place and have both SSH clients able to use them.

          -The option is --openssh-config, and you follow it with a filename. +The option is --openssh-config, and you follow it with a filename.

          To refer to this file from your main OpenSSH configuration, you can use the ‘Include’ directive. For example, you might run Pageant like this (with your own username substituted, of course): @@ -185,23 +189,54 @@

          9.3.3 Integrating

          So, if you want to use Windows git with an SSH key held in Pageant, you'll have to set the environment variable GIT_SSH, to point at a different program. You could point it at c:\Windows\System32\OpenSSH\ssh.exe once you've done this setup – but it's just as easy to point it at Plink!

          -

          9.3.4 Starting with the key list visible

          +

          9.3.4 Unix-domain sockets: integrating with WSL 1

          +

          +Pageant can listen on the WinSock implementation of ‘Unix-domain sockets’. These interoperate with the Unix-domain sockets found in the original Windows Subsystem for Linux (now known as WSL 1). So if you ask Pageant to listen on one of these, then your WSL 1 processes can talk directly to Pageant. +

          +

          +To configure this, run Pageant with the option --unix, followed with a pathname. Then, in WSL 1, set the environment variable SSH_AUTH_SOCK to point at the WSL translation of that pathname. +

          +

          +For example, you might run +

          +
          pageant --unix C:\Users\Simon\.ssh\agent.sock
          +
          +

          +and in WSL 1, set the environment variable +

          +
          SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock
          +
          +

          +Alternatively, you can add a line to your .ssh/config file inside WSL that says +

          +
          IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock
          +
          +

          +although doing it like that may mean that ssh-add commands won't find the agent, even though ssh itself will. +

          +

          +Security note: Unix-domain sockets are protected against access by other users by the file protections on their containing directory. So if your Windows machine is multiuser, make sure you create the socket inside a directory that other users can't access at all. (In fact, that's a good idea on general principles.) +

          +

          +Compatibility note: WSL 2 processes cannot talk to Pageant by this mechanism, because WSL 2's Unix-domain sockets are managed by a separate Linux kernel, and not by the same kernel that WinSock talks to. +

          +

          9.3.5 Starting with the key list visible

          -Start Pageant with the --keylist option to show the main window as soon as it starts up. +Start Pageant with the --keylist option to show the main window as soon as it starts up.

          -

          9.3.5 Restricting the Windows process ACL

          +

          9.3.6 Restricting the Windows process ACL

          -Pageant supports the same -restrict-acl option as the other PuTTY utilities to lock down the Pageant process's access control; see section 3.11.3.27 for why you might want to do this. +Pageant supports the same -restrict-acl option as the other PuTTY utilities to lock down the Pageant process's access control; see section 3.11.3.28 for why you might want to do this.

          -By default, if Pageant is started with -restrict-acl, it won't pass this to any PuTTY sessions started from its System Tray submenu. Use -restrict-putty-acl to change this. (Again, see section 3.11.3.27 for details.) +By default, if Pageant is started with -restrict-acl, it won't pass this to any PuTTY sessions started from its System Tray submenu. Use -restrict-putty-acl to change this. (Again, see section 3.11.3.28 for details.)

          -

          9.4 Using agent forwarding

          +

          9.4 Using agent forwarding

          Agent forwarding is a mechanism that allows applications on your SSH server machine to talk to the agent on your client machine.

          -Note that at present, whether agent forwarding in SSH-2 is available depends on your server. Pageant's protocol is compatible with the OpenSSH server, but the ssh.com server uses a different agent protocol, which PuTTY does not yet support. +Note that at present, whether agent forwarding in SSH-2 is available depends on your server. Pageant's protocol is compatible with the OpenSSH server, but the ssh.com server uses a different agent protocol, which PuTTY does not yet support.

          To enable agent forwarding, first start Pageant. Then set up a PuTTY SSH session in which ‘Allow agent forwarding’ is enabled (see section 4.21.7). Open the session as normal. (Alternatively, you can use the -A command line option; see section 3.11.3.10 for details.) @@ -230,7 +265,7 @@

          9.4 Using that SSH connection as well (see the manual for your server-side SSH client to find out how to do this), your authentication keys will still be available on the next machine you connect to - two SSH connections away from where they're actually stored.

          -In addition, if you have a private key on one of the SSH servers, you can send it all the way back to Pageant using the local ssh-add command: +In addition, if you have a private key on one of the SSH servers, you can send it all the way back to Pageant using the local ssh-add command:

          unixbox:~$ ssh-add ~/.ssh/id_rsa
           Need passphrase for /home/fred/.ssh/id_rsa
          @@ -257,7 +292,7 @@ 

          9.5 Loading key
          C:\PuTTY\pageant.exe --encrypted d:\main.ppk
           

          -After a key has been decrypted for the first use, it remains decrypted, so that it can be used again. The main window will list the key with ‘(re-encryptable)’ after it. You can revert it to the previous state, where a passphrase is required, using the ‘Re-encrypt’ button in the Pageant main window. +After a key has been decrypted for the first use, it remains decrypted, so that it can be used again. The main window will list the key with ‘(re-encryptable)’ after it. You can revert it to the previous state, where a passphrase is required, using the ‘Re-encrypt’ button in the Pageant main window.

          You can also ‘re-encrypt’ all keys that were added encrypted by choosing ‘Re-encrypt All Keys’ from the System tray menu. (Note that this does not discard cleartext keys that were not previously added encrypted!) @@ -267,13 +302,13 @@

          9.5 Loading key

          9.6 Security considerations

          -Using Pageant for public-key authentication gives you the convenience of being able to open multiple SSH sessions without having to type a passphrase every time, but also gives you the security benefit of never storing a decrypted private key on disk. Many people feel this is a good compromise between security and convenience. +Using Pageant for public-key authentication gives you the convenience of being able to open multiple SSH sessions without having to type a passphrase every time, but also gives you the security benefit of never storing a decrypted private key on disk. Many people feel this is a good compromise between security and convenience.

          It is a compromise, however. Holding your decrypted private keys in Pageant is better than storing them in easy-to-find disk files, but still less secure than not storing them anywhere at all. This is for two reasons:

        • Chapter 5: Using PSCP to transfer files securely -

          If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

          +

          If you want to provide feedback on this manual or on the PuTTY tools themselves, see the Feedback page.

          +[PuTTY release 0.78]
          diff --git a/code/doc/index.but b/code/doc/index.but index 4c76bb05..187f5a1e 100644 --- a/code/doc/index.but +++ b/code/doc/index.but @@ -688,6 +688,16 @@ saved sessions from \IM{group exchange} Diffie-Hellman group exchange \IM{group exchange} group exchange, Diffie-Hellman +\IM{ECDH} \q{ECDH} (elliptic-curve Diffie-Hellman) +\IM{ECDH} elliptic-curve Diffie-Hellman key exchange +\IM{ECDH} key exchange, elliptic-curve Diffie-Hellman +\IM{ECDH} Diffie-Hellman key exchange, with elliptic curves + +\IM{Streamlined NTRU Prime} Streamlined NTRU Prime +\IM{Streamlined NTRU Prime} NTRU Prime + +\IM{quantum attacks} quantum attacks, resistance to + \IM{repeat key exchange} repeat key exchange \IM{repeat key exchange} key exchange, repeat @@ -819,6 +829,12 @@ saved sessions from \IM{DSA} DSA \IM{DSA} Digital Signature Standard +\IM{ECDSA} ECDSA +\IM{ECDSA} elliptic-curve DSA + +\IM{NIST} NIST-standardised elliptic curves +\IM{NIST} elliptic curves, NIST-standardised + \IM{EdDSA} EdDSA \IM{EdDSA} Edwards-curve DSA @@ -942,3 +958,11 @@ saved sessions from \IM{shifted arrow keys} arrow keys, shifted \IM{shifted arrow keys} shifted arrow keys + +\IM{certificate}{certificates} certificates, SSH +\IM{certificate}{certificates} SSH certificates +\IM{certificate}{certificates} OpenSSH certificates +\IM{certificate}{certificates} CA (certification authority) + +\IM{Microsoft Store} Microsoft Store +\IM{Microsoft Store} Windows Store diff --git a/code/doc/man-pageant.but b/code/doc/man-pageant.but index 3f407a47..d202f166 100644 --- a/code/doc/man-pageant.but +++ b/code/doc/man-pageant.but @@ -256,6 +256,12 @@ be matched \dd to indicate that it is a fingerprint of a specific format +\dt \cq{sha256-cert:} or \cq{md5-cert:} + +\dd to indicate that it is a fingerprint of a specific format, and +specifically matches the fingerprint of the public key \e{including} a +certificate if any + } \dt \cw{--public-openssh} \e{key-identifiers}, \cw{-L} \e{key-identifiers} diff --git a/code/doc/man-puttygen.but b/code/doc/man-puttygen.but index 021af205..e6a2c990 100644 --- a/code/doc/man-puttygen.but +++ b/code/doc/man-puttygen.but @@ -12,10 +12,12 @@ \e bbbbbbbb iiiiiii bb iiiiiii bb iiii bbbbbbbb iiiiii bb \c [ -C new-comment ] [ -P ] [ --reencrypt ] \e bb iiiiiiiiiii bb bbbbbbbbbbb -\c [ -O output-type | -l | -L | -p | --dump ] [ -E fptype ] -\e bb iiiiiiiiiii bb bb bb bbbbbb bb iiiiii -\c [ --ppk-param key=value,... ] -\e bbbbbbbbbbb iiibiiiiib +\c [ --certificate cert-file | --remove-certificate ] +\e bbbbbbbbbbbbb iiiiiiiii bbbbbbbbbbbbbbbbbbbb +\c [ -O output-type | -l | -L | -p | --dump | --cert-info ] +\e bb iiiiiiiiiii bb bb bb bbbbbb bbbbbbbbbbb +\c [ --ppk-param key=value,... | -E fptype ] +\e bbbbbbbbbbb iiibiiiiib bb iiiiii \c [ -o output-file ] \e bb iiiiiiiiiii @@ -58,8 +60,9 @@ ssh.com's implementation. You can also specify a file containing only a \e{public} key here. The operations you can do are limited to outputting another public -key format or a fingerprint. Public keys can be in RFC 4716 or -OpenSSH format, or the standard SSH-1 format. +key format (possibly removing an attached certificate first), or a +fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or +the standard SSH-1 format. } @@ -143,6 +146,19 @@ to type). automatic when you are generating a new key, but not when you are modifying an existing key. +\dt \cw{\-\-certificate} \e{certificate-file} + +\dd Adds an OpenSSH-style certificate to the public half of the key, +so that the output file contains a certified public key with the same +private key. If the input file already contained a certificate, it +will be replaced with the new one. (Use \cq{-} to read a certificate +from standard input.) + +\dt \cw{\-\-remove\-certificate} + +\dd Removes any certificate that was part of the key, to recover the +uncertified version of the underlying key. + \dt \cw{\-\-reencrypt} \dd For an existing private key saved with a passphrase, refresh the @@ -260,6 +276,13 @@ newer format even for RSA, DSA, and ECDSA keys. \dd Save an SSH-2 private key in ssh.com's format. This option is not permitted for SSH-1 keys. +\dt \cw{cert-info} + +\dd Save a textual dump of information about the certificate on the +key, if any: whether it's a host or a user certificate, what host(s) +or user(s) it's certified to be, its validity period, ID and serial +number, and the fingerprint of the signing CA. + \dt \cw{text} \dd Save a textual dump of the numeric components comprising the key @@ -269,8 +292,9 @@ SSH. \lcont{ The output consists of a series of \cw{name=value} lines, where each -\c{value} is either a C-like string literal in double quotes, or a -hexadecimal number starting with \cw{0x...} +\c{value} is either a C-like string literal in double quotes, a +hexadecimal number starting with \cw{0x...}, or a binary blob +encoded with base64, denoted by \cw{b64("...")}. } If no output type is specified, the default is \c{private}. @@ -283,8 +307,9 @@ If no output type is specified, the default is \c{private}. this option is not specified, \c{puttygen} will assume you want to overwrite the original file if the input and output file types are the same (changing a comment or passphrase), and will assume you -want to output to stdout if you are asking for a public key or -fingerprint. Otherwise, the \c{\-o} option is required. +want to output to stdout if you are asking for a public key, +fingerprint, or one of the textual dump types. Otherwise, the +\c{\-o} option is required. \dt \cw{\-l} @@ -298,6 +323,10 @@ fingerprint. Otherwise, the \c{\-o} option is required. \dd Synonym for \q{\cw{-O public}}. +\dt \cw{\-\-cert\-info} + +\dd Synonym for \q{\cw{-O cert-info}}. + \dt \cw{\-\-dump} \dd Synonym for \q{\cw{-O text}}. @@ -305,7 +334,18 @@ fingerprint. Otherwise, the \c{\-o} option is required. \dt \cw{-E} \e{fptype} \dd Specify the algorithm to use if generating a fingerprint. The -options are \cw{sha256} (the default) and \cw{md5}. +available algorithms are are \cw{sha256} (the default) and \cw{md5}. + +\lcont{ + +By default, when showing the fingerprint of a public key that includes +a certificate, \c{puttygen} will not include the certificate, so that +the fingerprint shown will be the same as the underlying public key. +If you want the fingerprint including the certificate (for example, so +as to tell two certified keys apart), you can specify \cw{sha256-cert} +or \cw{md5-cert} as the fingerprint type. + +} \dt \cw{\-\-new\-passphrase} \e{file} diff --git a/code/doc/pageant.1 b/code/doc/pageant.1 index 13020df5..8531b29c 100644 --- a/code/doc/pageant.1 +++ b/code/doc/pageant.1 @@ -127,6 +127,8 @@ to indicate that it is a comment string to indicate that it is a fingerprint; any fingerprint format will be matched .IP "`\fBsha256:\fP' or `\fBmd5:\fP'" to indicate that it is a fingerprint of a specific format +.IP "`\fBsha256-cert:\fP' or `\fBmd5-cert:\fP'" +to indicate that it is a fingerprint of a specific format, and specifically matches the fingerprint of the public key \fIincluding\fP a certificate if any .RE .IP "\fB--public-openssh\fP \fIkey-identifiers\fP, \fB-L\fP \fIkey-identifiers\fP" Print the public half of each specified key, in the one-line format used by OpenSSH, suitable for putting in \fB.ssh/authorized_keys\fP files. diff --git a/code/doc/pageant.but b/code/doc/pageant.but index 1f6b7c21..de6d4cb8 100644 --- a/code/doc/pageant.but +++ b/code/doc/pageant.but @@ -64,21 +64,24 @@ The large list box in the Pageant main window lists the private keys that are currently loaded into Pageant. The list might look something like this: -\c ssh-ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w -\c ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg +\c Ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w +\c RSA 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg For each key, the list box will tell you: \b The type of the key. Currently, this can be -\c{ssh-rsa} (an RSA key for use with the SSH-2 protocol), -\c{ssh-dss} (a DSA key for use with the SSH-2 protocol), -\c{ecdsa-sha2-*} (an ECDSA key for use with the SSH-2 protocol), -\c{ssh-ed25519} (an Ed25519 key for use with the SSH-2 protocol), -\c{ssh-ed448} (an Ed448 key for use with the SSH-2 protocol), -or \c{ssh1} (an RSA key for use with the old SSH-1 protocol). +\q{RSA} (an RSA key for use with the SSH-2 protocol), +\q{DSA} (a DSA key for use with the SSH-2 protocol), +\q{\i{NIST}} (an ECDSA key for use with the SSH-2 protocol), +\q{Ed25519} (an Ed25519 key for use with the SSH-2 protocol), +\q{Ed448} (an Ed448 key for use with the SSH-2 protocol), +or \q{SSH-1} (an RSA key for use with the old SSH-1 protocol). +(If the key has an associated certificate, this is shown here with a +\q{cert} suffix.) \b The size (in bits) of the key, for key types that come in different -sizes. +sizes. (For ECDSA \q{NIST} keys, this is indicated as \q{p256} or +\q{p384} or \q{p521}.) \b The \I{key fingerprint}fingerprint for the public key. This should be the same fingerprint given by PuTTYgen, and (hopefully) also the same @@ -86,10 +89,20 @@ fingerprint shown by remote utilities such as \i\c{ssh-keygen} when applied to your \c{authorized_keys} file. \lcont{ -By default this is shown in the \q{SHA256} format. You can change to the -older \q{MD5} format (which looks like \c{aa:bb:cc:...}) with the -\q{Fingerprint type} drop-down, but bear in mind that this format is -less secure and should be avoided for comparison purposes where possible. +For SSH-2 keys, by default this is shown in the \q{SHA256} format. You +can change to the older \q{MD5} format (which looks like \c{aa:bb:cc:...}) +with the \q{Fingerprint type} drop-down, but bear in mind that this +format is less secure and should be avoided for comparison purposes +where possible. + +If some of the keys loaded into Pageant have certificates attached, +then Pageant will default to showing the fingerprint of the underlying +key. This way, a certified and uncertified version of the same key +will have the same fingerprint, so you can see that they match. You +can instead use the \q{Fingerprint type} drop-down to ask for a +different fingerprint to be shown for certified keys, which includes +the certificate as part of the fingerprinted data. That way you can +tell two certificates apart. } \b The comment attached to the key. @@ -217,6 +230,45 @@ point at a different program. You could point it at \cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this setup \dash but it's just as easy to point it at Plink! +\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1 + +Pageant can listen on the WinSock implementation of \q{Unix-domain +sockets}. These interoperate with the Unix-domain sockets found in the +original Windows Subsystem for Linux (now known as WSL 1). So if you +ask Pageant to listen on one of these, then your WSL 1 processes can +talk directly to Pageant. + +To configure this, run Pageant with the option \c{--unix}, followed +with a pathname. Then, in WSL 1, set the environment variable +\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname. + +For example, you might run + +\c pageant --unix C:\Users\Simon\.ssh\agent.sock + +and in WSL 1, set the environment variable + +\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock + +Alternatively, you can add a line to your \cw{.ssh/config} file inside +WSL that says + +\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock + +although doing it like that may mean that \cw{ssh-add} commands won't +find the agent, even though \cw{ssh} itself will. + +\s{Security note}: Unix-domain sockets are protected against access by +other users by the file protections on their containing directory. So +if your Windows machine is multiuser, make sure you create the socket +inside a directory that other users can't access at all. (In fact, +that's a good idea on general principles.) + +\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this +mechanism, because WSL 2's Unix-domain sockets are managed by a +separate Linux kernel, and not by the same kernel that WinSock talks +to. + \S{pageant-cmdline-keylist} Starting with the key list visible Start Pageant with the \i\c{--keylist} option to show the main window diff --git a/code/doc/plink.but b/code/doc/plink.but index 44b29c52..39b14f2b 100644 --- a/code/doc/plink.but +++ b/code/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c C:\>plink \c Plink: command-line connection utility -\c Release 0.77 +\c Release 0.78 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: diff --git a/code/doc/pscp.but b/code/doc/pscp.but index 23b17a6a..96a2d4b9 100644 --- a/code/doc/pscp.but +++ b/code/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c C:\>pscp \c PuTTY Secure Copy client -\c Release 0.77 +\c Release 0.78 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/code/doc/pubkey.but b/code/doc/pubkey.but index 80382076..5ac59390 100644 --- a/code/doc/pubkey.but +++ b/code/doc/pubkey.but @@ -62,9 +62,9 @@ The key types supported by PuTTY are described in \k{puttygen-keytype}. \H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator PuTTYgen is a key generator. It \I{generating keys}generates pairs of -public and private keys to be used with PuTTY, PSCP, and Plink, as well -as the PuTTY authentication agent, Pageant (see \k{pageant}). PuTTYgen -generates RSA, DSA, ECDSA, and EdDSA keys. +public and private keys to be used with PuTTY, PSCP, PSFTP, and Plink, +as well as the PuTTY authentication agent, Pageant (see \k{pageant}). +PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys. When you run PuTTYgen you will see a window where you have two main choices: \q{Generate}, to generate a new public/private key pair, or @@ -132,10 +132,13 @@ The \q{Number of bits} input box allows you to choose the strength of the key PuTTYgen will generate. \b For RSA and DSA, 2048 bits should currently be sufficient for most -purposes. +purposes. (Smaller keys of these types are no longer considered +secure, and PuTTYgen will warn if you try to generate them.) -\b For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers -equivalent security to RSA with smaller key sizes.) +\b For ECDSA, only 256, 384, and 521 bits are supported, corresponding +to \i{NIST}-standardised elliptic curves. (Elliptic-curve keys do not +need as many bits as RSA keys for equivalent security, so these numbers +are smaller than the RSA recommendations.) \b For EdDSA, the only valid sizes are 255 bits (these keys are also known as \q{\i{Ed25519}} and are commonly used) and 448 bits @@ -145,6 +148,9 @@ the same as 255.) \S{puttygen-primes} Selecting the \i{prime generation method} +(This is entirely optional. Unless you know better, it's entirely +sensible to skip this and use the default settings.) + On the \q{Key} menu, you can also optionally change the method for generating the prime numbers used in the generated key. This is used for RSA and DSA keys only. (The other key types don't require @@ -154,9 +160,6 @@ The prime-generation method does not affect compatibility: a key generated with any of these methods will still work with all the same SSH servers. -If you don't care about this, it's entirely sensible to leave it on the -default setting. - The available methods are: \b Use \i{probable primes} (fast) @@ -239,9 +242,9 @@ a particular fingerprint. So some utilities, such as the Pageant key list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add} utility, will list key fingerprints rather than the whole public key. -By default, PuTTYgen will display fingerprints in the \q{SHA256} -format. If you need to see the fingerprint in the older \q{MD5} format -(which looks like \c{aa:bb:cc:...}), you can choose +By default, PuTTYgen will display SSH-2 key fingerprints in the +\q{SHA256} format. If you need to see the fingerprint in the older +\q{MD5} format (which looks like \c{aa:bb:cc:...}), you can choose \q{Show fingerprint as MD5} from the \q{Key} menu, but bear in mind that this is less cryptographically secure; it may be feasible for an attacker to create a key with the same fingerprint as yours. @@ -307,6 +310,48 @@ a result. \e{Do not forget your passphrase}. There is no way to recover it. +\S{puttygen-cert} Adding a \i{certificate} to your key + +In some environments, user authentication keys can be signed in turn +by a \q{certifying authority} (\q{CA} for short), and user accounts on +an SSH server can be configured to automatically trust any key that's +certified by the right signature. + +This can be a convenient setup if you have a very large number of +servers. When you change your key pair, you might otherwise have to +edit the \cw{authorized_keys} file on every server individually, to +make them all accept the new key. But if instead you configure all +those servers \e{once} to accept keys signed as yours by a CA, then +when you change your public key, all you have to do is to get the new +key certified by the same CA as before, and then all your servers will +automatically accept it without needing individual reconfiguration. + +To get your key signed by a CA, you'll probably send the CA the new +\e{public} key (not the private half), and get back a modified version +of the public key with the certificate included. + +If you want to incorporate the certificate into your PPK file for +convenience, you can use the \q{Add certificate to key} menu option in +PuTTYgen's \q{Key} menu. This will give you a single file containing +your private key and the certificate, which is everything you need to +authenticate to a server prepared to accept that certificate. + +To remove the certificate again and restore the uncertified PPK file, +there's also a \q{Remove certificate from key} option. + +(However, you don't \e{have} to incorporate the certificate into your +PPK file. You can equally well use it separately, via the +\q{Certificate to use with the private key} option in PuTTY itself. +See \k{config-ssh-cert}. It's up to you which you find more +convenient.) + +When the currently loaded key in PuTTYgen contains a certificate, the +large \q{Public key for pasting} edit box (see \k{puttygen-pastekey}) +is replaced by a button that brings up an information box telling you +about the certificate, such as who it certifies your key as belonging +to, when it expires (if ever), and the fingerprint of the CA key that +signed it in turn. + \S{puttygen-savepriv} Saving your private key to a disk file Once you have generated a key, set a comment field and set a @@ -493,7 +538,8 @@ The options supported on the command line are: \dt \cw{\-t} \e{keytype} \dd Type of key to generate. You can select \c{rsa}, \c{dsa}, -\c{ecdsa}, \c{eddsa} or \c{rsa1}. See \k{puttygen-keytype}. +\c{ecdsa}, \c{eddsa}, \c{ed25519}, \c{ed448}, or \c{rsa1}. +See \k{puttygen-keytype}. \dt \cw{\-b} \e{bits} diff --git a/code/doc/pubkeyfmt.but b/code/doc/pubkeyfmt.but index 78da4885..836ed527 100644 --- a/code/doc/pubkeyfmt.but +++ b/code/doc/pubkeyfmt.but @@ -7,7 +7,7 @@ In this appendix, binary data structures are described using data type representations such as \cq{uint32}, \cq{string} and \cq{mpint} as used in the SSH protocol standards themselves. These are defined authoritatively by -\W{https://tools.ietf.org/html/rfc4251#section-5}{RFC 4251 section 5}, +\W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5}, \q{Data Type Representations Used in the SSH Protocols}. \H{ppk-overview} Overview @@ -86,7 +86,7 @@ can contain any byte values other than 13 and 10 (CR and LF). The next part of the file gives the public key. This is stored unencrypted but base64-encoded -(\W{https://tools.ietf.org/html/rfc4648}{RFC 4648}), and is preceded +(\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded by a header line saying how many lines of base64 data are shown, looking like this: @@ -241,7 +241,7 @@ of \e{y} in the group generated by \e{g} mod \e{p}. \S{ppk-privkey-ecdsa} NIST elliptic-curve keys -NIST elliptic-curve keys are stored using one of the following +\i{NIST} elliptic-curve keys are stored using one of the following \s{algorithm-name} values, each corresponding to a different elliptic curve and key size: diff --git a/code/doc/putty.chm b/code/doc/putty.chm index 43a6e050..a0067ae0 100644 Binary files a/code/doc/putty.chm and b/code/doc/putty.chm differ diff --git a/code/doc/puttydoc.txt b/code/doc/puttydoc.txt index a8d8e9a5..c7e3db6c 100644 --- a/code/doc/puttydoc.txt +++ b/code/doc/puttydoc.txt @@ -187,10 +187,10 @@ Chapter 2: Getting started with PuTTY PuTTY records the host key for each server you connect to, in the Windows Registry. Every time you connect to a server, it checks that the host key presented by the server is the same host key as it was - the last time you connected. If it is not, you will see a warning, - and you will have the chance to abandon your connection before you - type any private information (such as a password) into it. (See - section 10.2 for what that looks like.) + the last time you connected. If it is not, you will see a stronger + warning, and you will have the chance to abandon your connection + before you type any private information (such as a password) into + it. (See section 10.2 for what that looks like.) However, when you connect to a server you have not connected to before, PuTTY has no way of telling whether the host key is the @@ -493,7 +493,7 @@ Chapter 3: Using PuTTY - Erase Character PuTTY can also be configured to send this when the Backspace key - is pressed; see section 4.29.3. + is pressed; see section 4.30.3. - Erase Line @@ -510,12 +510,12 @@ Chapter 3: Using PuTTY - Interrupt Process PuTTY can also be configured to send this when Ctrl-C is typed; - see section 4.29.3. + see section 4.30.3. - Suspend Process PuTTY can also be configured to send this when Ctrl-Z is typed; - see section 4.29.3. + see section 4.30.3. - End Of Record @@ -644,7 +644,7 @@ Chapter 3: Using PuTTY what it does do. You should then tick the `Enable X11 forwarding' box in the X11 - panel (see section 4.24) before starting your SSH session. The `X + panel (see section 4.25) before starting your SSH session. The `X display location' box is blank by default, which means that PuTTY will try to use a sensible default such as `:0', which is the usual display location where your X server will be installed. If that @@ -668,7 +668,7 @@ Chapter 3: Using PuTTY If this works, you should then be able to run X applications in the remote session and have them display their windows on your PC. - For more options relating to X11 forwarding, see section 4.24. + For more options relating to X11 forwarding, see section 4.25. 3.5 Using port forwarding in SSH @@ -688,7 +688,7 @@ Chapter 3: Using PuTTY loopback address here; see below for more details.) - Now, before you start your SSH connection, go to the Tunnels - panel (see section 4.25). Make sure the `Local' radio button + panel (see section 4.26). Make sure the `Local' radio button is set. Enter the local port number into the `Source port' box. Enter the destination host name and port number into the `Destination' box, separated by a colon (for example, @@ -768,7 +768,7 @@ Chapter 3: Using PuTTY to obtain a fix from Microsoft in order to use addresses like 127.0.0.5 - see question A.7.17.) - For more options relating to port forwarding, see section 4.25. + For more options relating to port forwarding, see section 4.26. If the connection you are forwarding over SSH is itself a second SSH connection made by another copy of PuTTY, you might find the @@ -792,7 +792,7 @@ Chapter 3: Using PuTTY line to use (if your computer has more than one) and what speed (baud rate) to use when transferring data. For further configuration options (data bits, stop bits, parity, flow control), you can use - the `Serial' configuration panel (see section 4.28). + the `Serial' configuration panel (see section 4.29). After you start up PuTTY in serial mode, you might find that you have to make the first move, by sending some data out of the serial @@ -877,7 +877,7 @@ Chapter 3: Using PuTTY `Connection type' radio buttons on the `Session' panel (see section 4.1.1). For further configuration options (character set, more processing, scrolling), you can use the `SUPDUP' configuration panel - (see section 4.31). + (see section 4.32). In SUPDUP, terminal emulation is more integrated with the network protocol than in other protocols such as SSH. The SUPDUP protocol @@ -978,7 +978,7 @@ Chapter 3: Using PuTTY - `-ssh-connection' selects the bare ssh-connection protocol. (This is only useful in specialised circumstances; see section - 4.27 for more information.) + 4.28 for more information.) - `-telnet' selects the Telnet protocol. @@ -1018,7 +1018,7 @@ Chapter 3: Using PuTTY 3.11.3.5 `-L', `-R' and `-D': set up port forwardings As well as setting up port forwardings in the PuTTY configuration - (see section 4.25), you can also set up forwardings on the command + (see section 4.26), you can also set up forwardings on the command line. The command-line options work just like the ones in Unix `ssh' programs. @@ -1137,7 +1137,7 @@ Chapter 3: Using PuTTY For information on X11 forwarding, see section 3.4. These options are equivalent to the X11 forwarding checkbox in the - X11 panel of the PuTTY configuration box (see section 4.24). + X11 panel of the PuTTY configuration box (see section 4.25). These options are not available in the file transfer tools PSCP and PSFTP. @@ -1150,7 +1150,7 @@ Chapter 3: Using PuTTY These options are equivalent to the `Don't allocate a pseudo- terminal' checkbox in the SSH panel of the PuTTY configuration box - (see section 4.23.1). + (see section 4.24.1). These options are not available in the file transfer tools PSCP and PSFTP. @@ -1256,9 +1256,22 @@ Chapter 3: Using PuTTY This option is equivalent to the `Private key file for authentication' box in the Auth panel of the PuTTY configuration box - (see section 4.21.9). + (see section 4.22.1). -3.11.3.19 `-no-trivial-auth': disconnect if SSH authentication succeeds +3.11.3.19 `-cert': specify an SSH certificate + + The `-cert' option allows you to specify the name of a certificate + file containing a signed version of your public key. If you specify + this option, PuTTY will present that certificate in place of the + plain public key, whenever it tries to authenticate with a key that + matches. (This applies whether the key is stored in Pageant or + loaded directly from a file by PuTTY.) + + This option is equivalent to the `Certificate to use with the + private key' box in the Auth panel of the PuTTY configuration box + (see section 4.22.2). + +3.11.3.20 `-no-trivial-auth': disconnect if SSH authentication succeeds trivially This option causes PuTTY to abandon an SSH session if the server @@ -1267,7 +1280,7 @@ Chapter 3: Using PuTTY See section 4.21.3 for why you might want this. -3.11.3.20 `-loghost': specify a logical host name +3.11.3.21 `-loghost': specify a logical host name This option overrides PuTTY's normal SSH host key caching policy by telling it the name of the host you expect your connection to end up @@ -1276,7 +1289,7 @@ Chapter 3: Using PuTTY by a colon and a port number. See section 4.14.5 for more detail on this. -3.11.3.21 `-hostkey': manually specify an expected host key +3.11.3.22 `-hostkey': manually specify an expected host key This option overrides PuTTY's normal SSH host key caching policy by telling it exactly what host key to expect, which can be @@ -1288,14 +1301,14 @@ Chapter 3: Using PuTTY You can specify this option more than once if you want to configure more than one key to be accepted. -3.11.3.22 `-pgpfp': display PGP key fingerprints +3.11.3.23 `-pgpfp': display PGP key fingerprints This option causes the PuTTY tools not to run as normal, but instead to display the fingerprints of the PuTTY PGP Master Keys, in order to aid with verifying new versions. See appendix F for more information. -3.11.3.23 `-sercfg': specify serial port configuration +3.11.3.24 `-sercfg': specify serial port configuration This option specifies the configuration parameters for the serial port (baud rate, stop bits etc). Its argument is interpreted as @@ -1317,7 +1330,7 @@ Chapter 3: Using PuTTY For example, `-sercfg 19200,8,n,1,N' denotes a baud rate of 19200, 8 data bits, no parity, 1 stop bit and no flow control. -3.11.3.24 `-sessionlog', `-sshlog', `-sshrawlog': enable session logging +3.11.3.25 `-sessionlog', `-sshlog', `-sshrawlog': enable session logging These options cause the PuTTY network tools to write out a log file. Each of them expects a file name as an argument, e.g. `- @@ -1333,7 +1346,7 @@ Chapter 3: Using PuTTY For more information on logging configuration, see section 4.2. -3.11.3.25 `-logoverwrite', `-logappend': control behaviour with existing +3.11.3.26 `-logoverwrite', `-logappend': control behaviour with existing log file If logging has been enabled (in the saved configuration, or by @@ -1341,7 +1354,7 @@ Chapter 3: Using PuTTY exists, these options tell the PuTTY network tools what to do so that they don't have to ask the user. See section 4.2.2 for details. -3.11.3.26 `-proxycmd': specify a local proxy command +3.11.3.27 `-proxycmd': specify a local proxy command This option enables PuTTY's mode for running a command on the local machine and using it as a proxy for the network connection. It @@ -1353,7 +1366,7 @@ Chapter 3: Using PuTTY backslashes must be doubled (if you want `\' in your command, you must put `\\' on the command line). -3.11.3.27 `-restrict-acl': restrict the Windows process ACL +3.11.3.28 `-restrict-acl': restrict the Windows process ACL This option (on Windows only) causes PuTTY (or another PuTTY tool) to try to lock down the operating system's access control on its own @@ -1392,6 +1405,14 @@ Chapter 3: Using PuTTY Pageant to start subsidiary PuTTY processes with a restricted ACL if you also pass the `-restrict-putty-acl' option. +3.11.3.29 `-host-ca': launch the host CA configuration + + If you start PuTTY with the `-host-ca' option, it will not launch + a session at all. Instead, it will just display the configuration + dialog box for host certification authorities, as described in + section 4.19.4. When you dismiss that dialog box, PuTTY will + terminate. + Chapter 4: Configuring PuTTY ---------------------------- @@ -1437,7 +1458,7 @@ Chapter 4: Configuring PuTTY - The `Bare ssh-connection' option in the `Connection type' control is intended for specialist uses not involving - network connections. See section 4.27 for some information + network connections. See section 4.28 for some information about it. - The `Port' box lets you specify which port number on the server @@ -1449,7 +1470,7 @@ Chapter 4: Configuring PuTTY If you select `Serial' from the `Connection type' radio buttons, the `Host Name' and `Port' boxes are replaced by `Serial line' and - `Speed'; see section 4.28 for more details of these. + `Speed'; see section 4.29 for more details of these. 4.1.2 Loading and storing saved sessions @@ -1512,7 +1533,7 @@ Chapter 4: Configuring PuTTY HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions If you need to store them in a file, you could try the method - described in section 4.32. + described in section 4.33. 4.1.3 `Close window on exit' @@ -1917,7 +1938,7 @@ Chapter 4: Configuring PuTTY press Backspace. If you are connecting over SSH, PuTTY by default tells the server - the value of this option (see section 4.23.2), so you may find that + the value of this option (see section 4.24.2), so you may find that the Backspace key does the right thing either way. Similarly, if you are connecting to a Unix system, you will probably find that the Unix `stty' command lets you configure which the server expects @@ -3085,7 +3106,7 @@ Chapter 4: Configuring PuTTY alternative, see section 4.14.3.) Note that if you are using SSH-1 and the server has a bug that makes - it unable to deal with SSH-1 ignore messages (see section 4.26.12), + it unable to deal with SSH-1 ignore messages (see section 4.27.13), enabling keepalives will have no effect. 4.14.2 `Disable Nagle's algorithm' @@ -3132,7 +3153,7 @@ Chapter 4: Configuring PuTTY Internet protocols and addressing schemes (IPv4 and IPv6). The selected protocol will be used for most outgoing network connections (including connections to proxies); however, tunnels have their own - configuration, for which see section 4.25.2. + configuration, for which see section 4.26.2. The default setting is `Auto', which means PuTTY will do something sensible and try to guess which protocol you wanted. (If you specify @@ -3218,7 +3239,7 @@ Chapter 4: Configuring PuTTY implementing single sign-on, a more sensible default may be to use the name of the user logged in to the local operating system (if any); this is particularly likely to be useful with GSSAPI key - exchange and user authentication (see section 4.22 and section + exchange and user authentication (see section 4.23 and section 4.18.1.1). This control allows you to change the default behaviour. The current system username is displayed in the dialog as a @@ -3306,13 +3327,13 @@ Chapter 4: Configuring PuTTY 4.16.1 Setting the proxy type - The `Proxy type' radio buttons allow you to configure what type of + The `Proxy type' drop-down allows you to configure what type of proxy you want PuTTY to use for its network connections. The default setting is `None'; in this mode no proxy is used for any connection. - - Selecting `HTTP' allows you to proxy your connections through a - web server supporting the HTTP CONNECT command, as documented in - RFC 2817. + - Selecting `HTTP CONNECT' allows you to proxy your connections + through a web server supporting the HTTP CONNECT command, as + documented in RFC 2817. - Selecting `SOCKS 4' or `SOCKS 5' allows you to proxy your connections through a SOCKS server. @@ -3325,10 +3346,9 @@ Chapter 4: Configuring PuTTY of proxy, with the precise command specified as described in section 4.16.5. - - Selecting `SSH' causes PuTTY to make a secondary SSH connection - to the proxy host (sometimes called a `jump host' in this - context), and then open a port-forwarding channel to the final - destination host. + - There are several ways to use a SSH server as a proxy. All of + these cause PuTTY to make a secondary SSH connection to the + proxy host (sometimes called a `jump host' in this context). The `Proxy hostname' field will be interpreted as the name of a PuTTY saved session if one exists, or a hostname if not. This @@ -3336,6 +3356,22 @@ Chapter 4: Configuring PuTTY itself configured to use an SSH proxy; and it allows combining SSH and non-SSH proxying. + - `SSH to proxy and use port forwarding' causes PuTTY to use + the secondary SSH connection to open a port-forwarding + channel to the final destination host (similar to OpenSSH's + -J option). + + - `SSH to proxy and execute a command' causes PuTTY to run an + arbitrary remote command on the proxy SSH server and use + that command's standard input and output streams to run + the primary connection over. The remote command line is + specified as described in section 4.16.5. + + - `SSH to proxy and invoke a subsystem' is similar but causes + PuTTY to start an SSH `subsystem' rather than an ordinary + command line. This might be useful with a specially set up + SSH proxy server. + - Selecting `Local' allows you to specify an arbitrary command on the local machine to act as a proxy. When the session is started, instead of creating a TCP connection, PuTTY runs the @@ -3347,7 +3383,7 @@ Chapter 4: Configuring PuTTY tunnel a connection over something other than TCP/IP entirely. You can also enable this mode on the command line; see section - 3.11.3.26. + 3.11.3.27. 4.16.2 Excluding parts of the network from proxying @@ -3421,7 +3457,7 @@ Chapter 4: Configuring PuTTY is very slow to return a negative response in that situation), then as well as setting this control to `Yes', you may also need to turn off GSSAPI authentication and GSSAPI key exchange in SSH (see - section 4.22 and section 4.18.1.1 respectively). This is because + section 4.23 and section 4.18.1.1 respectively). This is because GSSAPI setup also involves a DNS query for the destination host name, and that query is performed by the separate GSSAPI library, so PuTTY can't override or reconfigure it. @@ -3470,29 +3506,36 @@ Chapter 4: Configuring PuTTY and don't also specify the actual username and/or password in the configuration, PuTTY will interactively prompt for them. -4.16.5 Specifying the Telnet or Local proxy command +4.16.5 Specifying the Telnet, SSH, or Local proxy command If you are using the Telnet proxy type, the usual command required by the firewall's Telnet server is `connect', followed by a host name and a port number. If your proxy needs a different command, you - can enter an alternative here. + can enter an alternative in the `Command to send to proxy' box. If you are using the Local proxy type, the local command to run is specified here. + If you are using the `SSH to proxy and execute a command' type, + the command to run on the SSH proxy server is specified here. + Similarly, if you are using `SSH to proxy and invoke a subsystem', + the subsystem name is constructed as specified here. + In this string, you can use `\n' to represent a new-line, `\r' to represent a carriage return, `\t' to represent a tab character, and `\x' followed by two hex digits to represent any other character. `\\' is used to encode the `\' character itself. Also, the special strings `%host' and `%port' will be replaced by - the host name and port number you want to connect to. The strings - `%user' and `%pass' will be replaced by the proxy username and - password (which, if not specified in the configuration, will be - prompted for). The strings `%proxyhost' and `%proxyport' will be + the host name and port number you want to connect to. For Telnet and + Local proxy types, the strings `%user' and `%pass' will be replaced + by the proxy username and password (which, if not specified in the + configuration, will be prompted for) - this does not happen with SSH + proxy types (because the proxy username/password are used for SSH + authentication). The strings `%proxyhost' and `%proxyport' will be replaced by the host details specified on the _Proxy_ panel, if any - (this is most likely to be useful for the Local proxy type). To get - a literal `%' sign, enter `%%'. + (this is most likely to be useful for proxy types using a local or + remote command). To get a literal `%' sign, enter `%%'. If a Telnet proxy server prompts for a username and password before commands can be sent, you can use a command such as: @@ -3680,26 +3723,53 @@ Chapter 4: Configuring PuTTY PuTTY currently supports the following key exchange methods: - - `ECDH': elliptic curve Diffie-Hellman key exchange. + - `NTRU Prime / Curve25519 hybrid': `Streamlined NTRU Prime' is + a lattice-based algorithm intended to resist quantum attacks. + In this key exchange method, it is run in parallel with a + conventional Curve25519-based method (one of those included in + `ECDH'), in such a way that it should be no _less_ secure than + that commonly-used method, and hopefully also resistant to a new + class of attacks. - - `Group 14': Diffie-Hellman key exchange with a well-known 2048- - bit group. + - `ECDH': elliptic curve Diffie-Hellman key exchange, with a + variety of standard curves and hash algorithms. - - `Group 1': Diffie-Hellman key exchange with a well-known 1024- - bit group. We no longer recommend using this method, and it's - not used by default in new installations; however, it may be the - only method supported by very old server software. + - The original form of Diffie-Hellman key exchange, with a variety + of well-known groups and hashes: - - `Group exchange': with this method, instead of using a fixed - group, PuTTY requests that the server suggest a group to use for - key exchange; the server can avoid groups known to be weak, and - possibly invent new ones over time, without any changes required - to PuTTY's configuration. We recommend use of this method - instead of the well-known groups, if possible. + - `Group 18', a well-known 8192-bit group, used with the SHA- + 512 hash function. - - `RSA key exchange': this requires much less computational effort - on the part of the client, and somewhat less on the part of the - server, than Diffie-Hellman key exchange. + - `Group 17', a well-known 6144-bit group, used with the SHA- + 512 hash function. + + - `Group 16', a well-known 4096-bit group, used with the SHA- + 512 hash function. + + - `Group 15', a well-known 3072-bit group, used with the SHA- + 512 hash function. + + - `Group 14': a well-known 2048-bit group, used with the SHA- + 256 hash function or, if the server doesn't support that, + SHA-1. + + - `Group 1': a well-known 1024-bit group, used with the SHA- + 1 hash function. Neither we nor current SSH standards + recommend using this method any longer, and it's not used by + default in new installations; however, it may be the only + method supported by very old server software. + + - `Diffie-Hellman group exchange': with this method, instead of + using a fixed group, PuTTY requests that the server suggest a + group to use for a subsequent Diffie-Hellman key exchange; the + server can avoid groups known to be weak, and possibly invent + new ones over time, without any changes required to PuTTY's + configuration. This key exchange method uses the SHA-256 hash + or, if the server doesn't support that, SHA-1. + + - `RSA-based key exchange': this requires much less computational + effort on the part of the client, and somewhat less on the part + of the server, than Diffie-Hellman key exchange. - `GSSAPI key exchange': see section 4.18.1.1. @@ -3719,10 +3789,14 @@ Chapter 4: Configuring PuTTY running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. + There's a GSSAPI-based equivalent to most of the ordinary methods + listed in section 4.18.1; server support determines which one will + be used. (PuTTY's preference order for GSSAPI-authenticated key + exchange methods is fixed, not controlled by the preference list.) The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation - (see section 4.22.1). The SSH key exchange can be repeated later in + (see section 4.23.1). The SSH key exchange can be repeated later in the session, and this allows your Kerberos V5 credentials (which are typically short-lived) to be automatically re-delegated to the server when they are refreshed on the client. (This feature is @@ -3733,7 +3807,8 @@ Chapter 4: Configuring PuTTY let you log in using your Kerberos credentials, but will only allow you to delegate the credentials that are active at the beginning of the session; they can't be refreshed automatically later, in a long- - running session. + running session. See section 4.23 for how to control GSSAPI user + authentication in PuTTY. Another effect of GSSAPI key exchange is that it replaces the usual SSH mechanism of permanent host keys described in section 2.2. So @@ -3899,7 +3974,7 @@ Chapter 4: Configuring PuTTY is running in a Windows environment without access to the Registry. In that situation, you will probably want to use the -hostkey command-line option to configure the expected host key(s); see - section 3.11.3.21. + section 3.11.3.22. For situations where PuTTY's automated host key management simply picks the wrong host name to store a key under, you may want to @@ -3939,6 +4014,146 @@ Chapter 4: Configuring PuTTY If the box is empty (as it usually is), then PuTTY's automated host key management will work as normal. +4.19.4 Configuring PuTTY to accept host certificates + + In some environments, the SSH host keys for a lot of servers will + all be signed in turn by a central `certification authority' (`CA' + for short). This simplifies host key configuration for users, + because if they configure their SSH client to accept host keys + certified by that CA, then they don't need to individually confirm + each host key the first time they connect to that server. + + In order to do this, press the `Configure host CAs' button in the + `Host keys' configuration panel. This will launch a secondary + configuration dialog box where you can configure what CAs PuTTY will + accept signatures from. + + *Note that this configuration is common to all saved sessions*. + Everything in the main PuTTY configuration is specific to one + saved session, and you can prepare a separate session with all the + configuration different. But there's only one copy of the host CA + configuration, and it applies to all sessions PuTTY runs, whether + saved or not. + + (Otherwise, it would be useless - configuring a CA by hand for each + new host wouldn't be any more convenient than pressing the `confirm' + button for each new host's host key.) + + To set up a new CA using this config box: + + First, load the CA's public key from a file, or paste it directly + into the `Public key of certification authority' edit box. If your + organisation signs its host keys in this way, they will publish the + public key of their CA so that SSH users can include it in their + configuration. + + Next, in the `Valid hosts this key is trusted to certify' box, + configure at least one hostname wildcard to say what servers PuTTY + should trust this CA to speak for. For example, suppose you work for + Example Corporation (example.com), and the Example Corporation IT + department has advertised a CA that signs all the Example internal + machines' host keys. Then probably you want to trust that CA to + sign host keys for machines in the domain example.com, but not for + anything else. So you might enter `*.example.com' into the `Valid + hosts' box. + + *It's important to limit what the CA key is allowed to sign*. Don't + just enter `*' in that box! If you do that, you're saying that + Example Corporation IT department is authorised to sign a host key + for _anything at all_ you might decide to connect to - even if + you're connecting out of the company network to a machine somewhere + else, such as your own personal server. So that configuration would + enable the Example IT department to act as a `man-in-the-middle' + between your PuTTY process and your server, and listen in to your + communications - exactly the thing SSH is supposed to avoid. + + So, if the CA was provided to you by the sysadmins responsible for + example.com (or whatever), make sure PuTTY will _only_ trust it for + machines in the example.com domain. + + For the full syntax of the `Valid hosts' expression, see section + 4.19.4.1. + + Finally, choose an identifying name for this CA; enter that name + in the `Name for this CA' edit box at the top of the window, and + press `Save' to record the CA in your configuration. The name you + chose will appear in the list of saved CAs to the left of the `Save' + button. + + The identifying name can be anything you like. It's there so that if + you store multiple certificates you can tell which is which later + when you want to edit or delete them. It also appears in the PuTTY + Event Log when a server presents a certificate signed by that CA. + + To reload an existing CA configuration, select it in the list box + and press `Load'. Then you can make changes, and save it again. + + To remove a CA from your configuration completely, select it in the + list and press `Delete'. + +4.19.4.1 Expressions you can enter in `Valid hosts' + + The simplest thing you can enter in the `Valid hosts this key is + trusted to certify' edit box is just a hostname wildcard such as + `*.example.com'. This matches any host in any subdomain, so both + `ssh.example.com' and `login.dept.example.com' would match, but + `prod.example.net' would not. + + But you can also enter multiple host name wildcards, and port number + ranges, and make complicated Boolean expressions out of them using + the operators `&&' for `and', `||' for `or', `!' for `not', and + parentheses. + + For example, here are some other things you could enter. + + - `*.foo.example.com || *.bar.example.com'. This means the CA is + trusted to sign the host key for a connection if the host name + matches `*.foo.example.com' _or_ it matches `*.bar.example.com'. + In other words, the CA has authority over those two particular + subdomains of example.com, but not for anything else, like + www.example.com. + + - `*.example.com && ! *.extrasecure.example.com'. This means + the CA is trusted to sign the host key for a connection if + the host name matches `*.example.com' _but does not_ match + `*.extrasecure.example.com'. (Imagine if there was one top- + secret set of servers in your company that the main IT + department didn't have security clearance to administer.) + + - `*.example.com && port:22'. This means the CA is trusted to + sign the host key for a connection if the host name matches + `*.example.com' _and_ the port number is 22. SSH servers running + on other ports would not be covered. + + - `(*.foo.example.com || *.bar.example.com) && port:0-1023'. This + matches two subdomains of example.com, as before, but _also_ + restricts the port number to the range 0-1023. + + A certificate configuration expression consists of one or more + individual requirements which can each be a hostname wildcard, a + single port number, or a port number range, combined together with + these Boolean operators. + + Unlike other languages such as C, there is no implied priority + between `&&' and `||'. If you write `A && B || C' (where A, B + and C are some particular requirements), then PuTTY will report + a syntax error, because you haven't said which of the `&&' and + `||' takes priority tightly. You will have to write either + `(A && B) || C', meaning `both of A and B, or alternatively just C', + or `A && (B || C)' (`A, and also at least one of B and C'), to make + it clear. + +4.19.4.2 RSA signature types in certificates + + RSA keys can be used to generate signatures with a choice of + secure hash function. Typically, any version of OpenSSH new enough + to support certificates at all will also be new enough to avoid + using SHA-1, so the default settings of accepting the more modern + SHA-256 and SHA-512 should be suitable for nearly all cases. + For completeness, however, you can configure which types of RSA + signature PuTTY will accept in a certificate from a CA using an RSA + key. + 4.20 The Cipher panel PuTTY supports a variety of different encryption algorithms, and @@ -3953,7 +4168,8 @@ Chapter 4: Configuring PuTTY - ChaCha20-Poly1305, a combined cipher and MAC (SSH-2 only) - - AES (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) + - AES (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or 256 or + 128-bit GCM (SSH-2 only) - Arcfour (RC4) - 256 or 128-bit stream cipher (SSH-2 only) @@ -4027,7 +4243,7 @@ Chapter 4: Configuring PuTTY also probably not what if you're trying to set up passwordless login to a mainstream SSH server; depending on the server, you probably wanted public-key authentication (chapter 8) or perhaps - GSSAPI authentication (section 4.22). (These are still forms of + GSSAPI authentication (section 4.23). (These are still forms of authentication, even if you don't have to interact with them.) This option only affects SSH-2 connections. SSH-1 connections always @@ -4151,7 +4367,13 @@ Chapter 4: Configuring PuTTY your server can cope with it, you can enable the `Allow attempted changes of username' option to modify PuTTY's behaviour. -4.21.9 `Private key file for authentication' + 4.22 The Credentials panel + + This subpane of the Auth panel contains configuration options that + specify actual _credentials_ to present to the server: key files and + certificates. + +4.22.1 `Private key file for authentication' This box is where you enter the name of your private key file if you are using public key authentication. See chapter 8 for information @@ -4159,7 +4381,7 @@ Chapter 4: Configuring PuTTY This key must be in PuTTY's native format (`*.PPK'). If you have a private key in another format that you want to use with PuTTY, see - section 8.2.14. + section 8.2.15. You can use the authentication agent Pageant so that you do not need to explicitly configure a key here; see chapter 9. @@ -4172,7 +4394,55 @@ Chapter 4: Configuring PuTTY sufficient to identify the key to Pageant, but of course if Pageant isn't present PuTTY can't fall back to using this file itself. - 4.22 The GSSAPI panel +4.22.2 `Certificate to use with the private key' + + In some environments, user authentication keys can be signed in turn + by a `certifying authority' (`CA' for short), and user accounts + on an SSH server can be configured to automatically trust any key + that's certified by the right signature. + + This can be a convenient setup if you have a very large number of + servers. When you change your key pair, you might otherwise have to + edit the authorized_keys file on every server individually, to make + them all accept the new key. But if instead you configure all those + servers _once_ to accept keys signed as yours by a CA, then when you + change your public key, all you have to do is to get the new key + certified by the same CA as before, and then all your servers will + automatically accept it without needing individual reconfiguration. + + One way to use a certificate is to incorporate it into your private + key file. Section 8.2.9 explains how to do that using PuTTYgen. But + another approach is to tell PuTTY itself where to find the public + certificate file, and then it will automatically present that + certificate when authenticating with the corresponding private key. + + To do this, enter the pathname of the certificate file into the + `Certificate to use with the private key' file selector. + + When this setting is configured, PuTTY will honour it no matter + whether the private key is found in a file, or loaded into Pageant. + +4.22.3 `Plugin to provide authentication responses' + + An SSH server can use the `keyboard-interactive' protocol to present + a series of arbitrary questions and answers. Sometimes this is + used for ordinary passwords, but sometimes the server will use the + same mechanism for something more complicated, such as a one-time + password system. + + Some of these systems can be automated. For this purpose, PuTTY + allows you to provide a separate program to act as a `plugin' which + will take over the authentication and send answers to the questions + on your behalf. + + If you have been provided with a plugin of this type, you can + configure it here, by entering a full command line in the `Plugin + command to run' box. + + (If you want to _write_ a plugin of this type, see appendix H for + the full specification of how the plugin is expected to behave.) + + 4.23 The GSSAPI panel The `GSSAPI' subpanel of the `Auth' panel controls the use of GSSAPI authentication. This is a mechanism which delegates the @@ -4204,7 +4474,7 @@ Chapter 4: Configuring PuTTY If both of those checkboxes are disabled, PuTTY will not try any form of GSSAPI at all, and the rest of this panel will be unused. -4.22.1 `Allow GSSAPI credential delegation' +4.23.1 `Allow GSSAPI credential delegation' GSSAPI credential delegation is a mechanism for passing on your Kerberos (or other) identity to the session on the SSH server. If @@ -4231,7 +4501,7 @@ Chapter 4: Configuring PuTTY for the delegation to expire during your session. See section 4.18.1.1 for more information. -4.22.2 Preference order for GSSAPI libraries +4.23.2 Preference order for GSSAPI libraries GSSAPI is a mechanism which allows more than one authentication method to be accessed through the same interface. Therefore, more @@ -4257,11 +4527,11 @@ Chapter 4: Configuring PuTTY of PuTTY, and the same with 64-bit (see question A.6.10). On Unix, shared libraries generally have a .so extension. - 4.23 The TTY panel + 4.24 The TTY panel The TTY panel lets you configure the remote pseudo-terminal. -4.23.1 `Don't allocate a pseudo-terminal' +4.24.1 `Don't allocate a pseudo-terminal' When connecting to a Unix system, most interactive shell sessions are run in a _pseudo-terminal_, which allows the Unix system to @@ -4274,7 +4544,7 @@ Chapter 4: Configuring PuTTY very specialist purposes; although in Plink (see chapter 7) it is the usual way of working. -4.23.2 Sending terminal modes +4.24.2 Sending terminal modes The SSH protocol allows the client to send `terminal modes' for the remote pseudo-terminal. These usually control the server's @@ -4363,7 +4633,7 @@ Chapter 4: Configuring PuTTY - Terminal speeds are configured elsewhere; see section 4.15.4. - 4.24 The X11 panel + 4.25 The X11 panel The X11 panel allows you to configure forwarding of X11 over an SSH connection. @@ -4380,7 +4650,7 @@ Chapter 4: Configuring PuTTY See section 3.4 for more information about X11 forwarding. -4.24.1 Remote X11 authentication +4.25.1 Remote X11 authentication If you are using X11 forwarding, the virtual X server created on the SSH server machine will be protected by authorisation data. This @@ -4425,7 +4695,7 @@ Chapter 4: Configuring PuTTY PuTTY's default is MIT-MAGIC-COOKIE-1. If you change it, you should be sure you know what you're doing. -4.24.2 X authority file for local display +4.25.2 X authority file for local display If you are using X11 forwarding, the local X server to which your forwarded connections are eventually directed may itself require @@ -4444,7 +4714,7 @@ Chapter 4: Configuring PuTTY configuring this option. By default, PuTTY will not attempt to find any authorisation for your local display. - 4.25 The Tunnels panel + 4.26 The Tunnels panel The Tunnels panel allows you to configure tunnelling of arbitrary connection types through an SSH connection. @@ -4529,7 +4799,7 @@ Chapter 4: Configuring PuTTY which host key it should be expecting. See section 4.14.5 for details of this. -4.25.1 Controlling the visibility of forwarded ports +4.26.1 Controlling the visibility of forwarded ports The source port for a forwarded connection usually does not accept connections from any machine except the SSH client or server machine @@ -4548,7 +4818,7 @@ Chapter 4: Configuring PuTTY not all SSH-2 servers support it (OpenSSH 3.0 does not, for example). -4.25.2 Selecting Internet protocol version for forwarded ports +4.26.2 Selecting Internet protocol version for forwarded ports This switch allows you to select a specific Internet protocol (IPv4 or IPv6) for the local end of a forwarded port. By default, it is @@ -4573,7 +4843,7 @@ Chapter 4: Configuring PuTTY `Auto' should always give you a port which you can connect to using either protocol. - 4.26 The Bugs and More Bugs panels + 4.27 The Bugs and More Bugs panels Not all SSH servers work properly. Various existing servers have bugs in them, which can make it impossible for a client to talk to @@ -4602,7 +4872,7 @@ Chapter 4: Configuring PuTTY the server version, e.g. because they must be acted on before the server version is known.) -4.26.1 `Chokes on SSH-2 ignore messages' +4.27.1 `Chokes on SSH-2 ignore messages' An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server @@ -4617,7 +4887,7 @@ Chapter 4: Configuring PuTTY server, the session will succeed, but keepalives will not work and the session might be less cryptographically secure than it could be. -4.26.2 `Handles SSH-2 key re-exchange badly' +4.27.2 `Handles SSH-2 key re-exchange badly' Some SSH servers cannot cope with repeat key exchange at all, and will ignore attempts by the client to start one. Since PuTTY pauses @@ -4635,7 +4905,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.3 `Chokes on PuTTY's SSH-2 `winadj' requests' +4.27.3 `Chokes on PuTTY's SSH-2 `winadj' requests' PuTTY sometimes sends a special request to SSH servers in the middle of channel data, with the name winadj@putty.projects.tartarus.org @@ -4656,7 +4926,7 @@ Chapter 4: Configuring PuTTY its `winadj@putty.projects.tartarus.org' request, and will make do without its timing data. -4.26.4 `Replies to requests on closed channels' +4.27.4 `Replies to requests on closed channels' The SSH protocol as published in RFC 4254 has an ambiguity which arises if one side of a connection tries to close a channel, while @@ -4671,13 +4941,13 @@ Chapter 4: Configuring PuTTY the other policy; for example, OpenSSH used to until it was fixed. Because PuTTY sends channel requests with the `want reply' - flag throughout channels' lifetime (see section 4.26.3), it's + flag throughout channels' lifetime (see section 4.27.3), it's possible that when connecting to such a server it might receive a reply to a request after it thinks the channel has entirely closed, and terminate with an error along the lines of `Received SSH2_MSG_CHANNEL_FAILURE for nonexistent channel 256'. -4.26.5 `Ignores SSH-2 maximum packet size' +4.27.5 `Ignores SSH-2 maximum packet size' When an SSH-2 channel is set up, each end announces the maximum size of data packet that it is willing to receive for that channel. Some @@ -4691,7 +4961,7 @@ Chapter 4: Configuring PuTTY server, the session will work correctly, but download performance will be less than it could be. -4.26.6 `Discards data sent before its greeting' +4.27.6 `Discards data sent before its greeting' Just occasionally, an SSH connection can be established over some channel that will accidentally discard outgoing data very early in @@ -4713,7 +4983,33 @@ Chapter 4: Configuring PuTTY _before_ it sees the server's greeting. So this is a manual workaround only. -4.26.7 `Requires padding on SSH-2 RSA signatures' +4.27.7 `Chokes on PuTTY's full KEXINIT' + + At the start of an SSH connection, the client and server exchange + long messages of type SSH_MSG_KEXINIT, containing lists of all the + cryptographic algorithms they're prepared to use. This is used to + negotiate a set of algorithms that both ends can speak. + + Occasionally, a badly written server might have a length limit on + the list it's prepared to receive, and refuse to make a connection + simply because PuTTY is giving it too many choices. + + A workaround is to enable this flag, which will make PuTTY wait to + send KEXINIT until after it receives the one from the server, and + then filter its own KEXINIT to leave out any algorithm the server + doesn't also announce support for. This will generally make PuTTY's + KEXINIT at most the size of the server's, and will otherwise make no + difference to the algorithm negotiation. + + This flag is a minor violation of the SSH protocol, because both + sides are supposed to send KEXINIT proactively. It still works + provided _one_ side sends its KEXINIT without waiting, but if both + client and server waited for the other one to speak first, the + connection would deadlock. We don't know of any servers that do + this, but if there is one, then this flag will make PuTTY unable to + speak to them at all. + +4.27.8 `Requires padding on SSH-2 RSA signatures' Versions below 3.3 of OpenSSH require SSH-2 RSA signatures to be padded with zero bytes to the same length as the RSA key modulus. @@ -4730,7 +5026,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.8 `Only supports pre-RFC4419 SSH-2 DH GEX' +4.27.9 `Only supports pre-RFC4419 SSH-2 DH GEX' The SSH key exchange method that uses Diffie-Hellman group exchange was redesigned after its original release, to use a slightly more @@ -4745,7 +5041,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.9 `Miscomputes SSH-2 HMAC keys' +4.27.10 `Miscomputes SSH-2 HMAC keys' Versions 2.3.0 and below of the SSH server software from ssh.com compute the keys for their HMAC message authentication codes @@ -4760,7 +5056,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.10 `Misuses the session ID in SSH-2 PK auth' +4.27.11 `Misuses the session ID in SSH-2 PK auth' Versions below 2.3 of OpenSSH require SSH-2 public-key authentication to be done slightly differently: the data to be @@ -4776,7 +5072,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.11 `Miscomputes SSH-2 encryption keys' +4.27.12 `Miscomputes SSH-2 encryption keys' Versions below 2.0.11 of the SSH server software from ssh.com compute the keys for the session encryption incorrectly. This @@ -4790,7 +5086,7 @@ Chapter 4: Configuring PuTTY This is an SSH-2-specific bug. -4.26.12 `Chokes on SSH-1 ignore messages' +4.27.13 `Chokes on SSH-1 ignore messages' An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol which can be sent from the client to the server, or from the server @@ -4803,15 +5099,15 @@ Chapter 4: Configuring PuTTY If this bug is detected, PuTTY will stop using ignore messages. This means that keepalives will stop working, and PuTTY will have to fall back to a secondary defence against SSH-1 password- - length eavesdropping. See section 4.26.13. If this bug is enabled + length eavesdropping. See section 4.27.14. If this bug is enabled when talking to a correct server, the session will succeed, but keepalives will not work and the session might be more vulnerable to eavesdroppers than it could be. -4.26.13 `Refuses all SSH-1 password camouflage' +4.27.14 `Refuses all SSH-1 password camouflage' When talking to an SSH-1 server which cannot deal with ignore - messages (see section 4.26.12), PuTTY will attempt to disguise the + messages (see section 4.27.13), PuTTY will attempt to disguise the length of the user's password by sending additional padding _within_ the password packet. This is technically a violation of the SSH- 1 specification, and so PuTTY will only do it when it cannot use @@ -4831,7 +5127,7 @@ Chapter 4: Configuring PuTTY This is an SSH-1-specific bug. SSH-2 is secure against this type of attack. -4.26.14 `Chokes on SSH-1 RSA authentication' +4.27.15 `Chokes on SSH-1 RSA authentication' Some SSH-1 servers cannot deal with RSA authentication messages at all. If Pageant is running and contains any SSH-1 keys, PuTTY will @@ -4846,7 +5142,7 @@ Chapter 4: Configuring PuTTY This is an SSH-1-specific bug. - 4.27 The `Bare ssh-connection' protocol + 4.28 The `Bare ssh-connection' protocol In addition to SSH itself, PuTTY also supports a second protocol that is derived from SSH. It's listed in the PuTTY GUI under the @@ -4891,12 +5187,12 @@ Chapter 4: Configuring PuTTY I repeat, *DON'T TRY TO USE THIS PROTOCOL FOR NETWORK CONNECTIONS!* That's not what it's for, and it's not at all safe to do it. - 4.28 The Serial panel + 4.29 The Serial panel The Serial panel allows you to configure options that only apply when PuTTY is connecting to a local serial line. -4.28.1 Selecting a serial line to connect to +4.29.1 Selecting a serial line to connect to The `Serial line to connect to' box allows you to choose which serial line you want PuTTY to talk to, if your computer has more @@ -4909,7 +5205,7 @@ Chapter 4: Configuring PuTTY where it replaces the `Host Name' box (see section 4.1.1) if the connection type is set to `Serial'. -4.28.2 Selecting the speed of your serial line +4.29.2 Selecting the speed of your serial line The `Speed' box allows you to choose the speed (or `baud rate') at which to talk to the serial line. Typical values might be 9600, @@ -4921,18 +5217,18 @@ Chapter 4: Configuring PuTTY where it replaces the `Port' box (see section 4.1.1) if the connection type is set to `Serial'. -4.28.3 Selecting the number of data bits +4.29.3 Selecting the number of data bits The `Data bits' box allows you to choose how many data bits are transmitted in each byte sent or received through the serial line. Typical values are 7 or 8. -4.28.4 Selecting the number of stop bits +4.29.4 Selecting the number of stop bits The `Stop bits' box allows you to choose how many stop bits are used in the serial line protocol. Typical values are 1, 1.5 or 2. -4.28.5 Selecting the serial parity checking scheme +4.29.5 Selecting the serial parity checking scheme The `Parity' box allows you to choose what type of parity checking is used on the serial line. The settings are: @@ -4951,7 +5247,7 @@ Chapter 4: Configuring PuTTY - `Space': an extra parity bit is sent alongside each byte, and always set to 0. -4.28.6 Selecting the serial flow control scheme +4.29.6 Selecting the serial flow control scheme The `Flow control' box allows you to choose what type of flow control checking is used on the serial line. The settings are: @@ -4968,12 +5264,12 @@ Chapter 4: Configuring PuTTY - `DSR/DTR': flow control is done using the DSR and DTR wires on the serial line. - 4.29 The Telnet panel + 4.30 The Telnet panel The Telnet panel allows you to configure options that only apply to Telnet sessions. -4.29.1 `Handling of OLD_ENVIRON ambiguity' +4.30.1 `Handling of OLD_ENVIRON ambiguity' The original Telnet mechanism for passing environment variables was badly specified. At the time the standard (RFC 1408) was written, @@ -4995,7 +5291,7 @@ Chapter 4: Configuring PuTTY unambiguous. This feature should only be needed if you have trouble passing environment variables to quite an old server. -4.29.2 Passive and active Telnet negotiation modes +4.30.2 Passive and active Telnet negotiation modes In a Telnet connection, there are two types of data passed between the client and the server: actual text, and _negotiations_ about @@ -5018,7 +5314,7 @@ Chapter 4: Configuring PuTTY you have confusing trouble with a firewall, you could try enabling passive mode to see if it helps. -4.29.3 `Keyboard sends Telnet special commands' +4.30.3 `Keyboard sends Telnet special commands' If this box is checked, several key sequences will have their normal actions modified: @@ -5033,7 +5329,7 @@ Chapter 4: Configuring PuTTY You probably shouldn't enable this unless you know what you're doing. -4.29.4 `Return key sends Telnet New Line instead of ^M' +4.30.4 `Return key sends Telnet New Line instead of ^M' Unlike most other remote login protocols, the Telnet protocol has a special `new line' code that is not the same as the usual line @@ -5047,12 +5343,12 @@ Chapter 4: Configuring PuTTY behaviour when you press Return in a Telnet session, you might try turning this option off to see if it helps. - 4.30 The Rlogin panel + 4.31 The Rlogin panel The Rlogin panel allows you to configure options that only apply to Rlogin sessions. -4.30.1 `Local username' +4.31.1 `Local username' Rlogin allows an automated (password-free) form of login by means of a file called `.rhosts' on the server. You put a line in your @@ -5083,13 +5379,13 @@ Chapter 4: Configuring PuTTY user name (or in case you didn't bother to set up a Windows user name). - 4.31 The SUPDUP panel + 4.32 The SUPDUP panel The SUPDUP panel allows you to configure options that only apply to SUPDUP sessions. See section 3.10 for more about the SUPDUP protocol. -4.31.1 `Location string' +4.32.1 `Location string' In SUPDUP, the client sends a piece of text of its choice to the server giving the user's location. This is typically displayed in @@ -5099,7 +5395,7 @@ Chapter 4: Configuring PuTTY want your location to show up as something more specific, you can configure it here. -4.31.2 `Extended ASCII Character set' +4.32.2 `Extended ASCII Character set' This declares what kind of character set extension your terminal supports. If the server supports it, it will send text using @@ -5113,18 +5409,18 @@ Chapter 4: Configuring PuTTY `WAITS' is only used by the WAITS operating system from the Stanford AI Laboratory. -4.31.3 `**MORE** processing' +4.32.3 `**MORE** processing' When **MORE** processing is enabled, the server causes output to pause at the bottom of the screen, until a space is typed. -4.31.4 `Terminal scrolling' +4.32.4 `Terminal scrolling' This controls whether the terminal will perform scrolling then the cursor goes below the last line, or if the cursor will return to the first line. - 4.32 Storing configuration in a file + 4.33 Storing configuration in a file PuTTY does not currently support storing its configuration in a file instead of the Registry. However, you can work around this with a @@ -5209,7 +5505,7 @@ Chapter 5: Using PSCP to transfer files securely C:\>pscp PuTTY Secure Copy client - Release 0.77 + Release 0.78 Usage: pscp [options] [user@]host:source target pscp [options] source [source...] [user@]host:target pscp [options] -ls [user@]host:filespec @@ -5481,7 +5777,7 @@ Chapter 5: Using PSCP to transfer files securely (see section 5.2.1.2). So you would do this: - Run PuTTY, and create a PuTTY saved session (see section 4.1.2) - which specifies your private key file (see section 4.21.9). You + which specifies your private key file (see section 4.22.1). You will probably also want to specify a username to log in as (see section 4.15.1). @@ -6063,7 +6359,7 @@ Chapter 6: Using PSFTP to transfer files securely So you might do this: - Run PuTTY, and create a PuTTY saved session (see section 4.1.2) - which specifies your private key file (see section 4.21.9). You + which specifies your private key file (see section 4.22.1). You will probably also want to specify a username to log in as (see section 4.15.1). @@ -6131,7 +6427,7 @@ Chapter 7: Using the command-line connection tool Plink C:\>plink Plink: command-line connection utility - Release 0.77 + Release 0.78 Usage: plink [options] [user@]host [command] ("host" can also be a PuTTY saved session name) Options: @@ -6266,7 +6562,7 @@ Chapter 7: Using the command-line connection tool Plink connecting to that server should not give a host key prompt unless the host key changes. Alternatively, you can specify the appropriate host key(s) on Plink's command line every time you use it; see - section 3.11.3.21. + section 3.11.3.22. To avoid being prompted for a user name, you can: @@ -6284,7 +6580,7 @@ Chapter 7: Using the command-line connection tool Plink - Set up a PuTTY saved session that describes the server you are connecting to, and that also specifies a private key file - (see section 4.21.9). For this to work without prompting, your + (see section 4.22.1). For this to work without prompting, your private key will need to have no passphrase. - Store the private key in Pageant. See chapter 9 for further @@ -6395,7 +6691,7 @@ Chapter 7: Using the command-line connection tool Plink output stream going somewhere else is likely to be needed by an 8- bit protocol and must not be tampered with at all.) It also stops happening if you tell Plink to allocate a remote pseudo-terminal - (see section 3.11.3.12 and section 4.23.1), on the basis that in + (see section 3.11.3.12 and section 4.24.1), on the basis that in that situation you often _want_ escape sequences from the server to go to your terminal. @@ -6590,8 +6886,8 @@ Chapter 8: Using public keys for SSH authentication 8.2 Using PuTTYgen, the PuTTY key generator PuTTYgen is a key generator. It generates pairs of public and - private keys to be used with PuTTY, PSCP, and Plink, as well as - the PuTTY authentication agent, Pageant (see chapter 9). PuTTYgen + private keys to be used with PuTTY, PSCP, PSFTP, and Plink, as well + as the PuTTY authentication agent, Pageant (see chapter 9). PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys. When you run PuTTYgen you will see a window where you have two main @@ -6614,14 +6910,14 @@ Chapter 8: Using public keys for SSH authentication 8.2.7) and a passphrase (section 8.2.8). - Now you're ready to save the private key to disk; press the - `Save private key' button. (See section 8.2.9). + `Save private key' button. (See section 8.2.10). Your key pair is now ready for use. You may also want to copy the public key to your server, either by copying it out of the `Public key for pasting into OpenSSH authorized_keys file' box (see section - 8.2.11), or by using the `Save public key' button (section 8.2.10). + 8.2.12), or by using the `Save public key' button (section 8.2.11). However, you don't need to do this immediately; if you want, you can - load the private key back into PuTTYgen later (see section 8.2.13) + load the private key back into PuTTYgen later (see section 8.2.14) and the public key will be available for copying and pasting again. Section 8.3 describes the typical process of configuring PuTTY to @@ -6658,10 +6954,15 @@ Chapter 8: Using public keys for SSH authentication the key PuTTYgen will generate. - For RSA and DSA, 2048 bits should currently be sufficient for - most purposes. + most purposes. (Smaller keys of these types are no longer + considered secure, and PuTTYgen will warn if you try to generate + them.) - - For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA - offers equivalent security to RSA with smaller key sizes.) + - For ECDSA, only 256, 384, and 521 bits are supported, + corresponding to NIST-standardised elliptic curves. (Elliptic- + curve keys do not need as many bits as RSA keys for equivalent + security, so these numbers are smaller than the RSA + recommendations.) - For EdDSA, the only valid sizes are 255 bits (these keys are also known as `Ed25519' and are commonly used) and 448 bits @@ -6671,6 +6972,9 @@ Chapter 8: Using public keys for SSH authentication 8.2.4 Selecting the prime generation method + (This is entirely optional. Unless you know better, it's entirely + sensible to skip this and use the default settings.) + On the `Key' menu, you can also optionally change the method for generating the prime numbers used in the generated key. This is used for RSA and DSA keys only. (The other key types don't require @@ -6680,9 +6984,6 @@ Chapter 8: Using public keys for SSH authentication generated with any of these methods will still work with all the same SSH servers. - If you don't care about this, it's entirely sensible to leave it on - the default setting. - The available methods are: - Use probable primes (fast) @@ -6765,12 +7066,12 @@ Chapter 8: Using public keys for SSH authentication list box (see section 9.2.1) and the Unix `ssh-add' utility, will list key fingerprints rather than the whole public key. - By default, PuTTYgen will display fingerprints in the `SHA256' - format. If you need to see the fingerprint in the older `MD5' format - (which looks like `aa:bb:cc:...'), you can choose `Show fingerprint - as MD5' from the `Key' menu, but bear in mind that this is less - cryptographically secure; it may be feasible for an attacker to - create a key with the same fingerprint as yours. + By default, PuTTYgen will display SSH-2 key fingerprints in the + `SHA256' format. If you need to see the fingerprint in the older + `MD5' format (which looks like `aa:bb:cc:...'), you can choose `Show + fingerprint as MD5' from the `Key' menu, but bear in mind that this + is less cryptographically secure; it may be feasible for an attacker + to create a key with the same fingerprint as yours. 8.2.7 Setting a comment for your key @@ -6831,7 +7132,48 @@ Chapter 8: Using public keys for SSH authentication _Do not forget your passphrase_. There is no way to recover it. - 8.2.9 Saving your private key to a disk file + 8.2.9 Adding a certificate to your key + + In some environments, user authentication keys can be signed in turn + by a `certifying authority' (`CA' for short), and user accounts + on an SSH server can be configured to automatically trust any key + that's certified by the right signature. + + This can be a convenient setup if you have a very large number of + servers. When you change your key pair, you might otherwise have to + edit the authorized_keys file on every server individually, to make + them all accept the new key. But if instead you configure all those + servers _once_ to accept keys signed as yours by a CA, then when you + change your public key, all you have to do is to get the new key + certified by the same CA as before, and then all your servers will + automatically accept it without needing individual reconfiguration. + + To get your key signed by a CA, you'll probably send the CA the new + _public_ key (not the private half), and get back a modified version + of the public key with the certificate included. + + If you want to incorporate the certificate into your PPK file for + convenience, you can use the `Add certificate to key' menu option in + PuTTYgen's `Key' menu. This will give you a single file containing + your private key and the certificate, which is everything you need + to authenticate to a server prepared to accept that certificate. + + To remove the certificate again and restore the uncertified PPK + file, there's also a `Remove certificate from key' option. + + (However, you don't _have_ to incorporate the certificate into + your PPK file. You can equally well use it separately, via the + `Certificate to use with the private key' option in PuTTY itself. + See section 4.22.2. It's up to you which you find more convenient.) + + When the currently loaded key in PuTTYgen contains a certificate, + the large `Public key for pasting' edit box (see section 8.2.12) + is replaced by a button that brings up an information box telling + you about the certificate, such as who it certifies your key as + belonging to, when it expires (if ever), and the fingerprint of the + CA key that signed it in turn. + +8.2.10 Saving your private key to a disk file Once you have generated a key, set a comment field and set a passphrase, you are ready to save your private key to disk. @@ -6842,19 +7184,19 @@ Chapter 8: Using public keys for SSH authentication This file is in PuTTY's native format (`*.PPK'); it is the one you will need to tell PuTTY to use for authentication (see section - 4.21.9) or tell Pageant to load (see section 9.2.2). + 4.22.1) or tell Pageant to load (see section 9.2.2). (You can optionally change some details of the PPK format for your - saved key files; see section 8.2.12. But The defaults should be fine + saved key files; see section 8.2.13. But The defaults should be fine for most purposes.) -8.2.10 Saving your public key to a disk file +8.2.11 Saving your public key to a disk file RFC 4716 specifies a standard format for storing SSH-2 public keys on disk. Some SSH servers (such as ssh.com's) require a public key in this format in order to accept authentication with the corresponding private key. (Others, such as OpenSSH, use a different - format; see section 8.2.11.) + format; see section 8.2.12.) To save your public key in the SSH-2 standard format, press the `Save public key' button in PuTTYgen. PuTTYgen will put up a dialog @@ -6870,7 +7212,7 @@ Chapter 8: Using public keys for SSH authentication for pasting' box. This is the only existing standard for SSH-1 public keys. -8.2.11 `Public key for pasting into OpenSSH authorized_keys file' +8.2.12 `Public key for pasting into OpenSSH authorized_keys file' The OpenSSH server, among others, requires your public key to be given to it in a one-line format before it will accept @@ -6886,7 +7228,7 @@ Chapter 8: Using public keys for SSH authentication See section 8.3 for general instructions on configuring public-key authentication once you have generated a key. -8.2.12 Parameters for saving key files +8.2.13 Parameters for saving key files Selecting `Parameters for saving key files...' from the `Key' menu lets you adjust some aspects of PPK-format private key files @@ -6896,7 +7238,7 @@ Chapter 8: Using public keys for SSH authentication In most cases, it's entirely sensible to leave all of these at their default settings. -8.2.12.1 PPK file version +8.2.13.1 PPK file version This defaults to version 3, which is fine for most uses. @@ -6908,7 +7250,7 @@ Chapter 8: Using public keys for SSH authentication The version 2 format is less resistant to brute-force decryption, and doesn't support any of the following options to control that. -8.2.12.2 Options affecting passphrase hashing +8.2.13.2 Options affecting passphrase hashing All of the following options only affect keys saved with passphrases. They control how much work is required to decrypt the @@ -6941,7 +7283,7 @@ Chapter 8: Using public keys for SSH authentication key. The default, 1, forces the process to run single-threaded, even on machines with multiple cores. -8.2.13 Reloading a private key +8.2.14 Reloading a private key PuTTYgen allows you to load an existing private key file into memory. If you do this, you can then change the passphrase and @@ -6956,10 +7298,10 @@ Chapter 8: Using public keys for SSH authentication If you use the Load command to load a foreign key format, it will work, but you will see a message box warning you that the key you - have loaded is not a PuTTY native key. See section 8.2.14 for + have loaded is not a PuTTY native key. See section 8.2.15 for information about importing foreign key formats. -8.2.14 Dealing with private keys in other formats +8.2.15 Dealing with private keys in other formats SSH-2 private keys have no standard format. OpenSSH and ssh.com have different formats, and PuTTY's is different again. So a key @@ -6978,7 +7320,7 @@ Chapter 8: Using public keys for SSH authentication PuTTYgen can also export private keys in OpenSSH format and in ssh.com format. To do so, select one of the `Export' options from the `Conversions' menu. Exporting a key works exactly like saving - it (see section 8.2.9) - you need to have typed your passphrase in + it (see section 8.2.10) - you need to have typed your passphrase in beforehand, and you will be warned if you are about to save a key without a passphrase. @@ -6997,7 +7339,7 @@ Chapter 8: Using public keys for SSH authentication client, you can use it with PuTTY, and vice versa. Hence, the export options are not available if you have generated an SSH-1 key. -8.2.15 PuTTYgen command-line configuration +8.2.16 PuTTYgen command-line configuration PuTTYgen supports a set of command-line options to configure many of the same settings you can select in the GUI. This allows you to @@ -7012,7 +7354,7 @@ Chapter 8: Using public keys for SSH authentication -t _keytype_ Type of key to generate. You can select `rsa', `dsa', `ecdsa', - `eddsa' or `rsa1'. See section 8.2.2. + `eddsa', `ed25519', `ed448', or `rsa1'. See section 8.2.2. -b _bits_ @@ -7031,7 +7373,7 @@ Chapter 8: Using public keys for SSH authentication --ppk-param _key_=_value_,... Allows setting all the same details of the PPK save file format - described in section 8.2.12. + described in section 8.2.13. Aspects to change are specified as a series of _key_=_value_ pairs separated by commas. The _key_s are: @@ -7082,7 +7424,7 @@ Chapter 8: Using public keys for SSH authentication to create this file, if this is the first key you have put in it.) Then switch to the PuTTYgen window, select all of the text in the `Public key for pasting into OpenSSH authorized_keys - file' box (see section 8.2.11), and copy it to the clipboard + file' box (see section 8.2.12), and copy it to the clipboard (`Ctrl+C'). Then, switch back to the PuTTY window and insert the data into the open file, making sure it ends up all on one line. Save the file. @@ -7093,7 +7435,7 @@ Chapter 8: Using public keys for SSH authentication and SSH-2 keys.) - If your server is ssh.com's product and is using SSH-2, you need - to save a _public_ key file from PuTTYgen (see section 8.2.10), + to save a _public_ key file from PuTTYgen (see section 8.2.11), and copy that into the `.ssh2' directory on the server. Then you should go into that `.ssh2' directory, and edit (or create) a file called `authorization'. In this file you should put a line @@ -7117,7 +7459,7 @@ Chapter 8: Using public keys for SSH authentication three ways: - Select the private key in PuTTY's configuration. See section - 4.21.9 for details. + 4.22.1 for details. - Specify the key file on the command line with the `-i' option. See section 3.11.3.18 for details. @@ -7193,32 +7535,44 @@ Chapter 9: Using Pageant for authentication keys that are currently loaded into Pageant. The list might look something like this: - ssh-ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w - ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg + Ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w + RSA 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg For each key, the list box will tell you: - - The type of the key. Currently, this can be `ssh-rsa' (an RSA - key for use with the SSH-2 protocol), `ssh-dss' (a DSA key for - use with the SSH-2 protocol), `ecdsa-sha2-*' (an ECDSA key for - use with the SSH-2 protocol), `ssh-ed25519' (an Ed25519 key for - use with the SSH-2 protocol), `ssh-ed448' (an Ed448 key for use - with the SSH-2 protocol), or `ssh1' (an RSA key for use with the - old SSH-1 protocol). + - The type of the key. Currently, this can be `RSA' (an RSA key + for use with the SSH-2 protocol), `DSA' (a DSA key for use with + the SSH-2 protocol), `NIST' (an ECDSA key for use with the + SSH-2 protocol), `Ed25519' (an Ed25519 key for use with the + SSH-2 protocol), `Ed448' (an Ed448 key for use with the SSH- + 2 protocol), or `SSH-1' (an RSA key for use with the old SSH- + 1 protocol). (If the key has an associated certificate, this is + shown here with a `cert' suffix.) - The size (in bits) of the key, for key types that come in - different sizes. + different sizes. (For ECDSA `NIST' keys, this is indicated as + `p256' or `p384' or `p521'.) - The fingerprint for the public key. This should be the same fingerprint given by PuTTYgen, and (hopefully) also the same fingerprint shown by remote utilities such as `ssh-keygen' when applied to your `authorized_keys' file. - By default this is shown in the `SHA256' format. You can change - to the older `MD5' format (which looks like `aa:bb:cc:...') - with the `Fingerprint type' drop-down, but bear in mind that - this format is less secure and should be avoided for comparison - purposes where possible. + For SSH-2 keys, by default this is shown in the `SHA256' format. + You can change to the older `MD5' format (which looks like + `aa:bb:cc:...') with the `Fingerprint type' drop-down, but bear + in mind that this format is less secure and should be avoided + for comparison purposes where possible. + + If some of the keys loaded into Pageant have certificates + attached, then Pageant will default to showing the fingerprint + of the underlying key. This way, a certified and uncertified + version of the same key will have the same fingerprint, so you + can see that they match. You can instead use the `Fingerprint + type' drop-down to ask for a different fingerprint to be shown + for certified keys, which includes the certificate as part of + the fingerprinted data. That way you can tell two certificates + apart. - The comment attached to the key. @@ -7343,21 +7697,60 @@ Chapter 9: Using Pageant for authentication c:\Windows\System32\OpenSSH\ssh.exe once you've done this setup - but it's just as easy to point it at Plink! - 9.3.4 Starting with the key list visible + 9.3.4 Unix-domain sockets: integrating with WSL 1 + + Pageant can listen on the WinSock implementation of `Unix-domain + sockets'. These interoperate with the Unix-domain sockets found in + the original Windows Subsystem for Linux (now known as WSL 1). So if + you ask Pageant to listen on one of these, then your WSL 1 processes + can talk directly to Pageant. + + To configure this, run Pageant with the option `--unix', followed + with a pathname. Then, in WSL 1, set the environment variable + SSH_AUTH_SOCK to point at the WSL translation of that pathname. + + For example, you might run + + pageant --unix C:\Users\Simon\.ssh\agent.sock + + and in WSL 1, set the environment variable + + SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock + + Alternatively, you can add a line to your .ssh/config file inside + WSL that says + + IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock + + although doing it like that may mean that ssh-add commands won't + find the agent, even though ssh itself will. + + *Security note*: Unix-domain sockets are protected against access by + other users by the file protections on their containing directory. + So if your Windows machine is multiuser, make sure you create the + socket inside a directory that other users can't access at all. (In + fact, that's a good idea on general principles.) + + *Compatibility note*: WSL 2 processes cannot talk to Pageant by this + mechanism, because WSL 2's Unix-domain sockets are managed by a + separate Linux kernel, and not by the same kernel that WinSock talks + to. + + 9.3.5 Starting with the key list visible Start Pageant with the `--keylist' option to show the main window as soon as it starts up. - 9.3.5 Restricting the Windows process ACL + 9.3.6 Restricting the Windows process ACL Pageant supports the same `-restrict-acl' option as the other PuTTY utilities to lock down the Pageant process's access control; see - section 3.11.3.27 for why you might want to do this. + section 3.11.3.28 for why you might want to do this. By default, if Pageant is started with `-restrict-acl', it won't pass this to any PuTTY sessions started from its System Tray submenu. Use `-restrict-putty-acl' to change this. (Again, see - section 3.11.3.27 for details.) + section 3.11.3.28 for details.) 9.4 Using agent forwarding @@ -7559,6 +7952,9 @@ Chapter 10: Common error messages connected to the SSH server before, knows what its host key _should_ be, but has found a different one. + (If the message instead talks about a `certified host key', see + instead section 10.3.) + This may mean that a malicious attacker has replaced your server with a different one, or has redirected your network connection to their own machine. On the other hand, it may simply mean that @@ -7572,7 +7968,41 @@ Chapter 10: Common error messages See section 2.2 for more information on host keys. - 10.3 `SSH protocol version 2 required by our configuration but remote + 10.3 `This server presented a certified host key which was signed by a + different certification authority ...' + + If you've configured PuTTY to trust at least one certification + authority for signing host keys (see section 4.19.4), then it will + ask the SSH server to send it any available certified host keys. + If the server sends back a certified key signed by a _different_ + certification authority, PuTTY will present this variant of the host + key prompt, preceded by `WARNING - POTENTIAL SECURITY BREACH!' + + One reason why this can happen is a deliberate attack. Just like an + ordinary man-in-the-middle attack which substitutes a wrong host + key, a particularly ambitious attacker might substitute an entire + wrong certification authority, and hope that you connect anyway. + + But it's also possible in some situations that this error might + arise legitimately. For example, if your organisation's IT + department has just rolled out a new CA key which you haven't yet + entered in PuTTY's configuration, or if your CA configuration + involves two overlapping domains, or something similar. + + So, unfortunately, you'll have to work out what to do about it + yourself: make an exception for this specific case, or abandon this + connection and install a new CA key before trying again (if you're + really sure you trust the CA), or edit your configuration in some + other way, or just stop trying to use this server. + + If you're convinced that this particular server is legitimate even + though the CA is not one you trust, PuTTY will let you cache the + certified host key, treating it in the same way as an uncertified + one. Then that particular certificate will be accepted for future + connections to this specific server, even though other certificates + signed by the same CA will still be rejected. + + 10.4 `SSH protocol version 2 required by our configuration but remote only provides (old, insecure) SSH-1' By default, PuTTY only supports connecting to SSH servers that @@ -7591,7 +8021,7 @@ Chapter 10: Common error messages SSH-1. This is no longer supported, to prevent the possibility of a downgrade attack. - 10.4 `The first cipher supported by the server is ... below the + 10.5 `The first cipher supported by the server is ... below the configured warning threshold' This occurs when the SSH server does not offer any ciphers which you @@ -7604,7 +8034,7 @@ Chapter 10: Common error messages (There are similar messages for other cryptographic primitives, such as host key algorithms.) - 10.5 `Remote side sent disconnect message type 2 (protocol error): "Too + 10.6 `Remote side sent disconnect message type 2 (protocol error): "Too many authentication failures for root"' This message is produced by an OpenSSH (or Sun SSH) server if it @@ -7615,7 +8045,7 @@ Chapter 10: Common error messages number of keys loaded into it, since these servers count each offer of a public key as an authentication attempt. This can be worked around by specifying the key that's required for the authentication - in the PuTTY configuration (see section 4.21.9); PuTTY will ignore + in the PuTTY configuration (see section 4.22.1); PuTTY will ignore any other keys Pageant may have, but will ask Pageant to do the authentication, so that you don't have to type your passphrase. @@ -7623,7 +8053,7 @@ Chapter 10: Common error messages authentication or (for Sun SSH only) by increasing `MaxAuthTries' in `sshd_config'. - 10.6 `Out of memory' + 10.7 `Out of memory' This occurs when PuTTY tries to allocate more memory than the system can give it. This _may_ happen for genuine reasons: if the computer @@ -7650,7 +8080,7 @@ Chapter 10: Common error messages from your login scripts instead it will try to interpret them as a message length. See question A.7.4 for details of this. - 10.7 `Internal error', `Internal fault', `Assertion failed' + 10.8 `Internal error', `Internal fault', `Assertion failed' Any error beginning with the word `Internal' should _never_ occur. If it does, there is a bug in PuTTY by definition; please see @@ -7660,7 +8090,7 @@ Chapter 10: Common error messages bug in PuTTY. Please report it to us, and include the exact text from the error message box. - 10.8 `Unable to use key file', `Couldn't load private key', `Couldn't + 10.9 `Unable to use key file', `Couldn't load private key', `Couldn't load this key' Various forms of this error are printed in the PuTTY window, or @@ -7675,14 +8105,14 @@ Chapter 10: Common error messages You may have tried to load an SSH-2 key in a `foreign' format (OpenSSH or ssh.com) directly into one of the PuTTY tools, in which case you need to import it into PuTTY's native format (`*.PPK') - using PuTTYgen - see section 8.2.14. + using PuTTYgen - see section 8.2.15. Alternatively, you may have specified a key that's inappropriate for the connection you're making. The SSH-2 and the old SSH-1 protocols require different private key formats, and a SSH-1 key can't be used for a SSH-2 connection (or vice versa). - 10.9 `Server refused our key', `Server refused our public key', `Key + 10.10 `Server refused our key', `Server refused our public key', `Key refused' Various forms of this error are printed in the PuTTY window, or @@ -7705,7 +8135,7 @@ Chapter 10: Common error messages Section 8.3 has some hints on server-side public key setup. - 10.10 `Access denied', `Authentication refused' + 10.11 `Access denied', `Authentication refused' Various forms of this error are printed in the PuTTY window, or written to the PuTTY Event Log (see section 3.1.3.1) during @@ -7721,16 +8151,16 @@ Chapter 10: Common error messages This error can be caused by buggy SSH-1 servers that fail to cope with the various strategies we use for camouflaging passwords in transit. Upgrade your server, or use the workarounds described in - section 4.26.12 and possibly section 4.26.13. + section 4.27.13 and possibly section 4.27.14. - 10.11 `No supported authentication methods available' + 10.12 `No supported authentication methods available' This error indicates that PuTTY has run out of ways to authenticate you to an SSH server. This may be because PuTTY has TIS or keyboard- interactive authentication disabled, in which case see section 4.21.5 and section 4.21.6. - 10.12 `Incorrect MAC received on packet' or `Incorrect CRC received on + 10.13 `Incorrect MAC received on packet' or `Incorrect CRC received on packet' This error occurs when PuTTY decrypts an SSH packet and its checksum @@ -7748,14 +8178,14 @@ Chapter 10: Common error messages which may not be noticed. Occasionally this has been caused by server bugs. An example is the - bug described at section 4.26.9, although you're very unlikely to + bug described at section 4.27.10, although you're very unlikely to encounter that one these days. In this context MAC stands for Message Authentication Code. It's a cryptographic term, and it has nothing at all to do with Ethernet MAC (Media Access Control) addresses, or with the Apple computer. - 10.13 `Incoming packet was garbled on decryption' + 10.14 `Incoming packet was garbled on decryption' This error occurs when PuTTY decrypts an SSH packet and the decrypted data makes no sense. This probably means something has @@ -7765,10 +8195,10 @@ Chapter 10: Common error messages If you get this error, one thing you could try would be to fiddle with the setting of `Miscomputes SSH-2 encryption keys' (see section - 4.26.11) or `Ignores SSH-2 maximum packet size' (see section 4.26.5) + 4.27.12) or `Ignores SSH-2 maximum packet size' (see section 4.27.5) on the Bugs panel. - 10.14 `PuTTY X11 proxy: _various errors_' + 10.15 `PuTTY X11 proxy: _various errors_' This family of errors are reported when PuTTY is doing X forwarding. They are sent back to the X application running on the SSH server, @@ -7799,7 +8229,7 @@ Chapter 10: Common error messages this depends on your particular system; in fact many modern versions of `su' do it automatically. - 10.15 `Network error: Software caused connection abort' + 10.16 `Network error: Software caused connection abort' This is a generic error produced by the Windows network code when it kills an established connection for some reason. For example, it @@ -7825,7 +8255,7 @@ Chapter 10: Common error messages represent a bug in PuTTY. The problem is between you, your Windows system, your network and the remote system. - 10.16 `Network error: Connection reset by peer' + 10.17 `Network error: Connection reset by peer' This error occurs when the machines at each end of a network connection lose track of the state of the connection between them. @@ -7842,7 +8272,7 @@ Chapter 10: Common error messages without seeing a connection reset from the server, for instance if the connection to the network is lost. - 10.17 `Network error: Connection refused' + 10.18 `Network error: Connection refused' This error means that the network connection PuTTY tried to make to your server was rejected by the server. Usually this happens because @@ -7853,7 +8283,7 @@ Chapter 10: Common error messages Telnet, etc), and check that the port number is correct. If that fails, consult the administrator of your server. - 10.18 `Network error: Connection timed out' + 10.19 `Network error: Connection timed out' This error means that the network connection PuTTY tried to make to your server received no response at all from the server. Usually @@ -7873,7 +8303,7 @@ Chapter 10: Common error messages exchange in SSH-2 (see section 4.18.2) or due to keepalives (section 4.14.1). - 10.19 `Network error: Cannot assign requested address' + 10.20 `Network error: Cannot assign requested address' This means that the operating system rejected the parameters of the network connection PuTTY tried to make, usually without actually @@ -7974,7 +8404,7 @@ Appendix A: PuTTY FAQ A.2.6 Does PuTTY support storing its settings in a disk file? - Not at present, although section 4.32 in the documentation gives a + Not at present, although section 4.33 in the documentation gives a method of achieving the same effect. A.2.7 Does PuTTY support full-screen mode, like a DOS box? @@ -8025,10 +8455,10 @@ Appendix A: PuTTY FAQ been removed and replaced somewhere on the way. Host key checking makes the attacker's job _astronomically_ hard, compared to packet sniffing, and even compared to subverting a router. Instead of - applying a little intelligence and keeping an eye on Bugtraq, the - attacker must now perform a brute-force attack against at least one - military-strength cipher. That insignificant host key prompt really - does make _that_ much difference. + applying a little intelligence and keeping an eye on oss-security, + the attacker must now perform a brute-force attack against at least + one military-strength cipher. That insignificant host key prompt + really does make _that_ much difference. If you're having a specific problem with host key checking - perhaps you want an automated batch job to make use of PSCP or Plink, and @@ -8094,7 +8524,7 @@ A.2.11 Can PSCP or PSFTP transfer files in ASCII mode? systems and Unix. As of 0.68, the supplied PuTTY executables run on versions of - Windows from XP onwards, up to and including Windows 10; and + Windows from XP onwards, up to and including Windows 11; and we know of no reason why PuTTY should not continue to work on future versions of Windows. We provide 32-bit and 64-bit Windows executables for the common x86 processor family; see question A.6.10 @@ -8123,9 +8553,8 @@ A.2.11 Can PSCP or PSFTP transfer files in ASCII mode? also one entirely new application. If you look at the source release, you should find a `unix' - subdirectory. There are a couple of ways of building it, including - the usual `configure'/`make'; see the file `README' in the source - distribution. This should build you: + subdirectory. You need `cmake' to build it; see the file `README' in + the source distribution. This should build you: - Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much the same as their Windows counterparts; @@ -8443,7 +8872,7 @@ A.2.11 Can PSCP or PSFTP transfer files in ASCII mode? selection, etc.) in PSCP, PSFTP and Plink? Most major features (e.g., public keys, port forwarding) are - available through command line options. See the documentation. + available through command line options. See section 3.11.3. Not all features are accessible from the command line yet, although we'd like to fix this. In the meantime, you can use most of PuTTY's @@ -8464,9 +8893,16 @@ A.2.11 Can PSCP or PSFTP transfer files in ASCII mode? A.6.9 How do I use PSCP to copy a file whose name has spaces in? - If PSCP is using the traditional SCP protocol, this is confusing. If - you're specifying a file at the local end, you just use one set of - quotes as you would normally do: + If PSCP is using the newer SFTP protocol (which is usual with most + modern servers), this is straightforward; all filenames with spaces + in are specified using a single pair of quotes in the obvious way: + + pscp "local file" user@host: + pscp user@host:"remote file" . + + However, if PSCP is using the older SCP protocol for some reason, + things are more confusing. If you're specifying a file at the local + end, you just use one set of quotes as you would normally do: pscp "local filename with spaces" user@host: pscp user@host:myfile "local filename with spaces" @@ -8490,13 +8926,6 @@ A.2.11 Can PSCP or PSFTP transfer files in ASCII mode? c:\>pscp user@host:"\"oo er\"" "oo er" - If PSCP is using the newer SFTP protocol, none of this is a problem, - and all filenames with spaces in are specified using a single pair - of quotes in the obvious way: - - pscp "local file" user@host: - pscp user@host:"remote file" . - A.6.10 Should I run the 32-bit or the 64-bit version? If you're not sure, the 32-bit version is generally the safe option. @@ -8515,7 +8944,7 @@ A.6.10 Should I run the 32-bit or the 64-bit version? If you need to use an external DLL for GSSAPI authentication, that DLL may only be available in a 32-bit or 64-bit form, and that will dictate the version of PuTTY you need to use. (You will probably - know if you're doing this; see section 4.22.2 in the documentation.) + know if you're doing this; see section 4.23.2 in the documentation.) A.7 Troubleshooting @@ -8858,7 +9287,7 @@ A.7.19 Do you want to hear about `Software caused connection abort'? we'd like to hear about any occurrences of this error. Since the release of PuTTY 0.54, however, we've been convinced that this error doesn't indicate that PuTTY's doing anything wrong, and we don't - need to hear about further occurrences. See section 10.15 for our + need to hear about further occurrences. See section 10.16 for our current documentation of this error. A.7.20 My SSH-2 session locks up for a few seconds every so often. @@ -8906,7 +9335,7 @@ A.7.23 After I upgraded PuTTY to 0.68, I can no longer connect to my have a buggy server that objects to certain SSH protocol extensions. The SSH protocol recently gained a new `terminal mode', IUTF8, which - PuTTY sends by default; see section 4.23.2. This is the first new + PuTTY sends by default; see section 4.24.2. This is the first new terminal mode since the SSH-2 protocol was defined. While servers are supposed to ignore modes they don't know about, some buggy servers will unceremoniously close the connection if they see @@ -8985,6 +9414,19 @@ A.7.23 After I upgraded PuTTY to 0.68, I can no longer connect to my completely out to disk when the process is long-term inactive. And Pageant spends most of its time inactive. + A.8.5 Is the version of PuTTY in the Microsoft Store legit? + + The free-of-charge `PuTTY' application at this link is published + and maintained by us. The copy there is the latest release, usually + updated within a few days of us publishing it on our own website. + + There have been other copies of PuTTY on the store, some looking + quite similar, and some charging money. Those were uploaded by other + people, and we can't guarantee anything about them. + + The first version we published to the Microsoft Store was 0.76 (some + time after its initial release on our website). + A.9 Administrative questions A.9.1 Is putty.org your website? @@ -9109,11 +9551,11 @@ A.7.23 After I upgraded PuTTY to 0.68, I can no longer connect to my If you don't like PayPal, talk to us; we can probably arrange some alternative means. - Small donations (tens of dollars or tens of euros) will probably - be spent on beer or curry, which helps motivate our volunteer team - to continue doing this for the world. Larger donations will be - spent on something that actually helps development, if we can find - anything (perhaps new hardware, or a copy of Windows XP), but if we + Small donations (tens of dollars or tens of euros) will probably be + spent on beer or curry, which helps motivate our volunteer team to + continue doing this for the world. Larger donations will be spent on + something that actually helps development, if we can find anything + (perhaps new hardware, or a new version of Windows), but if we can't find anything then we'll just distribute the money among the developers. If you want to be sure your donation is going towards something worthwhile, ask us first. If you don't like these terms, @@ -11318,7 +11760,7 @@ Appendix G: SSH-2 names specified for PuTTY request and respond with SSH_MSG_CHANNEL_FAILURE. (Some SSH servers get confused by this message, so there is a - bug-compatibility mode for disabling it. See section 4.26.3.) + bug-compatibility mode for disabling it. See section 4.27.3.) G.2 Key exchange method names @@ -11415,5 +11857,505 @@ Appendix G: SSH-2 names specified for PuTTY user will have to supply a passphrase before the key can be used). - ersionid PuTTY release 0.77 +Appendix H: PuTTY authentication plugin protocol +------------------------------------------------ + + This appendix contains the specification for the protocol spoken + over local IPC between PuTTY and an authentication helper plugin. + + If you already have an authentication plugin and want to configure + PuTTY to use it, see section 4.22.3 for how to do that. This + appendix is for people writing new authentication plugins. + + H.1 Requirements + + The following requirements informed the specification of this + protocol. + + *Automate keyboard-interactive authentication.* We're motivated in + the first place by the observation that the general SSH userauth + method `keyboard-interactive' (defined in [RFC4256]) can be used + for many kinds of challenge/response or one-time-password styles + of authentication, and in more than one of those, the necessary + responses might be obtained from an auxiliary network connection, + such as an HTTPS transaction. So it's useful if a user doesn't have + to manually copy-type or copy-paste from their web browser into + their SSH client, but instead, the process can be automated. + + *Be able to pass prompts on to the user.* On the other hand, some + userauth methods can be only _partially_ automated; some of the + server's prompts might still require human input. Also, the plugin + automating the authentication might need to ask its own questions + that are not provided by the SSH server. (For example, `please + enter the master key that the real response will be generated by + hashing'.) So after the plugin intercepts the server's questions, it + needs to be able to ask its own questions of the user, which may or + may not be the same questions sent by the server. + + *Allow automatic generation of the username.* Sometimes, the + authentication method comes with a mechanism for discovering the + username to be used in the SSH login. So the plugin has to start up + early enough that the client hasn't committed to a username yet. + + *Future expansion route to other SSH userauth flavours.* The initial + motivation for this protocol is specific to keyboard-interactive. + But other SSH authentication methods exist, and they may also + benefit from automation in future. We're making no attempt here to + predict what those methods might be or how they might be automated, + but we do need to leave a space where they can be slotted in later + if necessary. + + *Minimal information loss.* Keyboard-interactive prompts and replies + should be passed to and from the plugin in a form as close as + possible to the way they look on the wire in SSH itself. Therefore, + the protocol resembles SSH in its data formats and marshalling + (instead of, for example, translating from SSH binary packet style + to another well-known format such as JSON, which would introduce + edge cases in character encoding). + + *Half-duplex.* Simultaneously trying to read one I/O stream and + write another adds a lot of complexity to software. It becomes + necessary to have an organised event loop containing select or + WaitForMultipleObjects or similar, which can invoke the handler + for whichever event happens soonest. There's no need to add that + complexity in an application like this, which isn't transferring + large amounts of bulk data or multiplexing unrelated activities. So, + to keep life simple for plugin authors, we set the ground rule that + it must always be 100% clear which side is supposed to be sending a + message next. That way, the plugin can be written as sequential code + progressing through the protocol, making simple read and write calls + to receive or send each message. + + *Communicate success/failure, to facilitate caching in the plugin.* + A plugin might want to cache recently used data for next time, but + only in the case where authentication using that data was actually + successful. So the client has to tell the plugin what the outcome + was, if it's known. (But this is best-effort only. Obviously the + plugin cannot _depend_ on hearing the answer, because any IPC + protocol at all carries the risk that the other end might crash or + be killed by things outside its control.) + + H.2 Transport and configuration + + Plugins are executable programs on the client platform. + + The SSH client must be manually configured to use a plugin for + a particular connection. The configuration takes the form of a + command line, including the location of the plugin executable, + and optionally command-line arguments that are meaningful to the + particular plugin. + + The client invokes the plugin as a subprocess, passing it a pair of + 8-bit-clean pipes as its standard input and output. On those pipes, + the client and plugin will communicate via the protocol specified + below. + + H.3 Data formats and marshalling + + This protocol borrows the low-level data formatting from SSH itself, + in particular the following wire encodings from [RFC4251] section 5: + + *byte* + + An integer between 0 and 0xFF inclusive, transmitted as a single + byte of binary data. + + *boolean* + + The values `true' or `false', transmitted as the bytes 1 and 0 + respectively. + + *uint32* + + An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4 + bytes of binary data, in big-endian (`network') byte order. + + *string* + + A sequence of bytes, preceded by a *uint32* giving the number of + bytes in the sequence. The length field does not include itself. + For example, the empty string is represented by four zero bytes + (the *uint32* encoding of 0); the string "AB" is represented by + the six bytes 0,0,0,2,'A','B'. + + Unlike SSH itself, the protocol spoken between the client and the + plugin is unencrypted, because local inter-process pipes are assumed + to be secured by the OS kernel. So the binary packet protocol is + much simpler than SSH proper, and is similar to SFTP and the OpenSSH + agent protocol. + + The data sent in each direction of the conversation consists of a + sequence of *messages* exchanged between the SSH client and the + plugin. Each message is encoded as a *string*. The contents of the + string begin with a *byte* giving the message type, which determines + the format of the rest of the message. + + H.4 Protocol versioning + + This protocol itself is versioned. At connection setup, the client + states the highest version number it knows how to speak, and then + the plugin responds by choosing the version number that will + actually be spoken (which may not be higher than the client's + value). + + Including a version number makes it possible to make breaking + changes to the protocol later. + + Even version numbers represent released versions of this spec. + Odd numbers represent drafts or development versions in between + releases. A client and plugin negotiating an odd version number + are not guaranteed to interoperate; the developer testing the + combination is responsible for ensuring the two are compatible. + + This document describes version 2 of the protocol, the first + released version. (The initial drafts had version 1.) + + H.5 Overview and sequence of events + + At the very beginning of the user authentication phase of SSH, the + client launches the plugin subprocess, if one is configured. It + immediately sends the PLUGIN_INIT message, telling the plugin some + initial information about where the SSH connection is to. + + The plugin responds with PLUGIN_INIT_RESPONSE, which may optionally + tell the SSH client what username to use. + + The client begins trying to authenticate with the SSH server in the + usual way, using the username provided by the plugin (if any) or + alternatively one obtained via its normal (non-plugin) policy. + + The client follows its normal policy for selecting authentication + methods to attempt. If it chooses a method that this protocol does + not cover, then the client will perform that method in its own way + without consulting the plugin. + + However, if the client and server decide to attempt a method that + this protocol _does_ cover, then the client sends PLUGIN_PROTOCOL + specifying the SSH protocol id for the authentication method being + used. The plugin responds with PLUGIN_PROTOCOL_ACCEPT if it's + willing to assist with this auth method, or PLUGIN_PROTOCOL_REJECT + if it isn't. + + If the plugin sends PLUGIN_PROTOCOL_REJECT, then the client will + proceed as if the plugin were not present. Later, if another auth + method is negotiated (either because this one failed, or because it + succeeded but the server wants multiple auth methods), the client + may send a further PLUGIN_PROTOCOL and try again. + + If the plugin sends PLUGIN_PROTOCOL_ACCEPT, then a protocol segment + begins that is specific to that auth method, terminating in either + PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE. After that, again, the + client may send a further PLUGIN_PROTOCOL. + + Currently the only supported method is `keyboard-interactive', + defined in [RFC4256]. Once the client has announced this to the + server, the followup protocol is as follows: + + Each time the server sends an SSH_MSG_USERAUTH_INFO_REQUEST message + requesting authentication responses from the user, the SSH client + translates the message into PLUGIN_KI_SERVER_REQUEST and passes it + on to the plugin. + + At this point, the plugin may optionally send back + PLUGIN_KI_USER_REQUEST containing prompts to be presented + to the actual user. The client will reply with a matching + PLUGIN_KI_USER_RESPONSE after asking the user to reply to the + question(s) in the request message. The plugin can repeat this cycle + multiple times. + + Once the plugin has all the information it needs to + respond to the server's authentication prompts, it sends + PLUGIN_KI_SERVER_RESPONSE back to the client, which translates it + into SSH_MSG_USERAUTH_INFO_RESPONSE to send on to the server. + + After that, as described in [RFC4256], the server is free + to accept authentication, reject it, or send another + SSH_MSG_USERAUTH_INFO_REQUEST. Each SSH_MSG_USERAUTH_INFO_REQUEST is + dealt with in the same way as above. + + If the server terminates keyboard-interactive authentication + with SSH_MSG_USERAUTH_SUCCESS or SSH_MSG_USERAUTH_FAILURE, the + client informs the plugin by sending either PLUGIN_AUTH_SUCCESS + or PLUGIN_AUTH_FAILURE. PLUGIN_AUTH_SUCCESS is sent when _that + particular authentication method_ was successful, regardless of + whether the SSH server chooses to request further authentication + afterwards: in particular, SSH_MSG_USERAUTH_FAILURE with the + `partial success' flag (see [RFC4252] section 5.1) translates into + PLUGIN_AUTH_SUCCESS. + + The plugin's standard input will close when the client no longer + requires the plugin's services, for any reason. This could be + because authentication is complete (with overall success or overall + failure), or because the user has manually aborted the session in + mid-authentication, or because the client crashed. + + H.6 Message formats + + This section describes the format of every message in the protocol. + + As described in section H.3, every message starts with the same two + fields: + + - *uint32*: overall length of the message + + - *byte*: message type. + + The length field does not include itself, but does include the type + code. + + The following subsections each give the format of the remainder of + the message, after the type code. + + The type codes themselves are defined here: + + #define PLUGIN_INIT 1 + #define PLUGIN_INIT_RESPONSE 2 + #define PLUGIN_PROTOCOL 3 + #define PLUGIN_PROTOCOL_ACCEPT 4 + #define PLUGIN_PROTOCOL_REJECT 5 + #define PLUGIN_AUTH_SUCCESS 6 + #define PLUGIN_AUTH_FAILURE 7 + #define PLUGIN_INIT_FAILURE 8 + + #define PLUGIN_KI_SERVER_REQUEST 20 + #define PLUGIN_KI_SERVER_RESPONSE 21 + #define PLUGIN_KI_USER_REQUEST 22 + #define PLUGIN_KI_USER_RESPONSE 23 + + If this protocol is extended to be able to assist with further + auth methods, their message type codes will also begin from 20, + overlapping the codes for keyboard-interactive. + + H.6.1 PLUGIN_INIT + + *Direction*: client to plugin + + *When*: the first message sent at connection startup + + *What happens next*: the plugin will send PLUGIN_INIT_RESPONSE or + PLUGIN_INIT_FAILURE + + *Message contents after the type code*: + + - *uint32*: the highest version number of this protocol that the + client knows how to speak. + + - *string*: the hostname of the server. This will be the + _logical_ hostname, in cases where it differs from the physical + destination of the network connection. Whatever name would be + used by the SSH client to cache the server's host key, that's + the same name passed in this message. + + - *uint32*: the port number on the server. (Together with the host + name, this forms a primary key identifying a particular server. + Port numbers may be vital because a single host can run two + unrelated SSH servers with completely different authentication + requirements, e.g. system sshd on port 22 and Gerrit on port + 29418.) + + - *string*: the username that the client will use to log in, if + the plugin chooses not to override it. An empty string means + that the client has no opinion about this (and might, for + example, prompt the user). + + H.6.2 PLUGIN_INIT_RESPONSE + + *Direction*: plugin to client + + *When*: response to PLUGIN_INIT + + *What happens next*: the client will send PLUGIN_PROTOCOL, or + perhaps terminate the session (if no auth method is ever negotiated + that the plugin can help with) + + *Message contents after the type code*: + + - *uint32*: the version number of this protocol that the + connection will use. Must be no greater than the max version + number sent by the client in PLUGIN_INIT. + + - *string*: the username that the plugin suggests the client use. + An empty string means that the plugin has no opinion and the + client should stick with the username it already had (or prompt + the user, if it had none). + + H.6.3 PLUGIN_INIT_FAILURE + + *Direction*: plugin to client + + *When*: response to PLUGIN_INIT + + *What happens next*: the session is over + + *Message contents after the type code*: + + - *string*: an error message to present to the user indicating why + the plugin was unable to start up. + + H.6.4 PLUGIN_PROTOCOL + + *Direction*: client to plugin + + *When*: sent after PLUGIN_INIT_RESPONSE, or after a previous auth + phase terminates with PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE + + *What happens next*: the plugin will send PLUGIN_PROTOCOL_ACCEPT or + PLUGIN_PROTOCOL_REJECT + + *Message contents after the type code*: + + - *string*: the SSH protocol id of the auth method the client + intends to attempt. Currently the only method specified for use + in this protocol is `keyboard-interactive'. + + H.6.5 PLUGIN_PROTOCOL_REJECT + + *Direction*: plugin to client + + *When*: sent after PLUGIN_PROTOCOL + + *What happens next*: the client will either send another + PLUGIN_PROTOCOL or terminate the session + + *Message contents after the type code*: + + - *string*: an error message to present to the user, explaining + why the plugin cannot help with this authentication protocol. + + An example might be `unable to open : ', if the plugin depends on some configuration that the + user has not set up. + + If the plugin does not support this this particular + authentication protocol at all, this string should be left + blank, so that no message will be presented to the user at all. + + H.6.6 PLUGIN_PROTOCOL_ACCEPT + + *Direction*: plugin to client + + *When*: sent after PLUGIN_PROTOCOL + + *What happens next*: depends on the auth protocol agreed on. For + keyboard-interactive, the client will send PLUGIN_KI_SERVER_REQUEST + or PLUGIN_AUTH_SUCCESS or PLUGIN_AUTH_FAILURE. No other method is + specified. + + *Message contents after the type code*: none. + + H.6.7 PLUGIN_KI_SERVER_REQUEST + + *Direction*: client to plugin + + *When*: sent after PLUGIN_PROTOCOL, or after a previous + PLUGIN_KI_SERVER_RESPONSE, when the SSH server has sent + SSH_MSG_USERAUTH_INFO_REQUEST + + *What happens next*: the plugin will send either + PLUGIN_KI_USER_REQUEST or PLUGIN_KI_SERVER_RESPONSE + + *Message contents after the type code*: the exact contents of the + SSH_MSG_USERAUTH_INFO_REQUEST just sent by the server. See [RFC4256] + section 3.2 for details. The summary: + + - *string*: name of this prompt collection (e.g. to use as a + dialog-box title) + + - *string*: instructions to be displayed before this prompt + collection + + - *string*: language tag (deprecated) + + - *uint32*: number of prompts in this collection + + - That many copies of: + + - *string*: prompt (in UTF-8) + + - *boolean*: whether the response to this prompt is safe to + echo to the screen + + H.6.8 PLUGIN_KI_SERVER_RESPONSE + + *Direction*: plugin to client + + *When*: response to PLUGIN_KI_SERVER_REQUEST, perhaps after + one or more intervening pairs of PLUGIN_KI_USER_REQUEST and + PLUGIN_KI_USER_RESPONSE + + *What happens next*: the client will send a further + PLUGIN_KI_SERVER_REQUEST, or PLUGIN_AUTH_SUCCESS or + PLUGIN_AUTH_FAILURE + + *Message contents after the type code*: the exact contents of the + SSH_MSG_USERAUTH_INFO_RESPONSE that the client should send back to + the server. See [RFC4256] section 3.4 for details. The summary: + + - *uint32*: number of responses (must match the `number of + prompts' field from the corresponding server request) + + - That many copies of: + + - *string*: response to the _n_th prompt (in UTF-8) + + H.6.9 PLUGIN_KI_USER_REQUEST + + *Direction*: plugin to client + + *When*: response to PLUGIN_KI_SERVER_REQUEST, if the plugin cannot + answer the server's auth prompts without presenting prompts of its + own to the user + + *What happens next*: the client will send PLUGIN_KI_USER_RESPONSE + + *Message contents after the type code*: exactly the same as in + PLUGIN_KI_SERVER_REQUEST (see section H.6.7). + +H.6.10 PLUGIN_KI_USER_RESPONSE + + *Direction*: client to plugin + + *When*: response to PLUGIN_KI_USER_REQUEST + + *What happens next*: the plugin will send PLUGIN_KI_SERVER_RESPONSE, + or another PLUGIN_KI_USER_REQUEST + + *Message contents after the type code*: exactly the same as in + PLUGIN_KI_SERVER_RESPONSE (see section H.6.8). + +H.6.11 PLUGIN_AUTH_SUCCESS + + *Direction*: client to plugin + + *When*: sent after PLUGIN_KI_SERVER_RESPONSE, or (in unusual cases) + after PLUGIN_PROTOCOL_ACCEPT + + *What happens next*: the client will either send another + PLUGIN_PROTOCOL or terminate the session + + *Message contents after the type code*: none + +H.6.12 PLUGIN_AUTH_FAILURE + + *Direction*: client to plugin + + *When*: sent after PLUGIN_KI_SERVER_RESPONSE, or (in unusual cases) + after PLUGIN_PROTOCOL_ACCEPT + + *What happens next*: the client will either send another + PLUGIN_PROTOCOL or terminate the session + + *Message contents after the type code*: none + + H.7 References + + [RFC4251] RFC 4251, `The Secure Shell (SSH) Protocol Architecture'. + + [RFC4252] RFC 4252, `The Secure Shell (SSH) Authentication + Protocol'. + + [RFC4256] RFC 4256, `Generic Message Exchange Authentication for the + Secure Shell Protocol (SSH)' (better known by its wire id `keyboard- + interactive'). +[PuTTY release 0.78] diff --git a/code/doc/puttygen.1 b/code/doc/puttygen.1 index 70fee3fe..717300e6 100644 --- a/code/doc/puttygen.1 +++ b/code/doc/puttygen.1 @@ -9,8 +9,9 @@ .nf \fBputtygen\fP\ (\ \fIkeyfile\fP\ |\ \fB\-t\fP\ \fIkeytype\fP\ [\ \fB\-b\fP\ \fIbits\fP\ ]\ [\ \fB\-\-primes\fP\ \fImethod\fP\ ]\ [\ \fB\-q\fP\ ]\ ) \ \ \ \ \ \ \ \ \ [\ \fB\-C\fP\ \fInew\-comment\fP\ ]\ [\ \fB\-P\fP\ ]\ [\ \fB\-\-reencrypt\fP\ ] -\ \ \ \ \ \ \ \ \ [\ \fB\-O\fP\ \fIoutput\-type\fP\ |\ \fB\-l\fP\ |\ \fB\-L\fP\ |\ \fB\-p\fP\ |\ \fB\-\-dump\fP\ ]\ [\ \fB\-E\fP\ \fIfptype\fP\ ] -\ \ \ \ \ \ \ \ \ \ \ \ [\ \fB\-\-ppk\-param\fP\ \fIkey\fP\fB=\fP\fIvalue\fP\fB,\fP...\ ] +\ \ \ \ \ \ \ \ \ [\ \fB\-\-certificate\fP\ \fIcert\-file\fP\ |\ \fB\-\-remove\-certificate\fP\ ] +\ \ \ \ \ \ \ \ \ [\ \fB\-O\fP\ \fIoutput\-type\fP\ |\ \fB\-l\fP\ |\ \fB\-L\fP\ |\ \fB\-p\fP\ |\ \fB\-\-dump\fP\ |\ \fB\-\-cert\-info\fP\ ] +\ \ \ \ \ \ \ \ \ \ \ \ [\ \fB\-\-ppk\-param\fP\ \fIkey\fP\fB=\fP\fIvalue\fP\fB,\fP...\ |\ \fB\-E\fP\ \fIfptype\fP\ ] \ \ \ \ \ \ \ \ \ [\ \fB\-o\fP\ \fIoutput\-file\fP\ ] .fi .SH "DESCRIPTION" @@ -31,7 +32,7 @@ Specify a key file to be loaded. (Use `\fB-\fP' to read a key file from standard .PP Usually this will be a private key, which can be in the (de facto standard) SSH-1 key format, or in PuTTY's SSH-2 key format, or in either of the SSH-2 private key formats used by OpenSSH and ssh.com's implementation. .PP -You can also specify a file containing only a \fIpublic\fP key here. The operations you can do are limited to outputting another public key format or a fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or the standard SSH-1 format. +You can also specify a file containing only a \fIpublic\fP key here. The operations you can do are limited to outputting another public key format (possibly removing an attached certificate first), or a fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or the standard SSH-1 format. .RE .IP "\fB\-t\fP \fIkeytype\fP" Specify a type of key to generate. The acceptable values here are \fBrsa\fP, \fBdsa\fP, \fBecdsa\fP, \fBeddsa\fP, \fBed25519\fP, and \fBed448\fP (to generate SSH-2 keys), and \fBrsa1\fP (to generate SSH-1 keys). @@ -61,6 +62,10 @@ In the second phase, \fBputtygen\fP optionally alters properties of the key it h Specify a comment string to describe the key. This comment string will be used by PuTTY to identify the key to you (when asking you to enter the passphrase, for example, so that you know which passphrase to type). .IP "\fB\-P\fP" Indicate that you want to change the key's passphrase. This is automatic when you are generating a new key, but not when you are modifying an existing key. +.IP "\fB\-\-certificate\fP \fIcertificate-file\fP" +Adds an OpenSSH-style certificate to the public half of the key, so that the output file contains a certified public key with the same private key. If the input file already contained a certificate, it will be replaced with the new one. (Use `\fB-\fP' to read a certificate from standard input.) +.IP "\fB\-\-remove\-certificate\fP" +Removes any certificate that was part of the key, to recover the uncertified version of the underlying key. .IP "\fB\-\-reencrypt\fP" For an existing private key saved with a passphrase, refresh the encryption without changing the passphrase. .RS @@ -112,27 +117,35 @@ Save an SSH-2 private key in OpenSSH's format, using the oldest format available As \fBprivate-openssh\fP, except that it forces the use of OpenSSH\*(Aqs newer format even for RSA, DSA, and ECDSA keys. .IP "\fBprivate-sshcom\fP" Save an SSH-2 private key in ssh.com's format. This option is not permitted for SSH-1 keys. +.IP "\fBcert-info\fP" +Save a textual dump of information about the certificate on the key, if any: whether it's a host or a user certificate, what host(s) or user(s) it's certified to be, its validity period, ID and serial number, and the fingerprint of the signing CA. .IP "\fBtext\fP" Save a textual dump of the numeric components comprising the key (both the public and private parts, if present). Useful for debugging, or for using PuTTYgen as a key generator for applications other than SSH. .RS .PP -The output consists of a series of \fBname=value\fP lines, where each \fBvalue\fP is either a C-like string literal in double quotes, or a hexadecimal number starting with \fB0x...\fP +The output consists of a series of \fBname=value\fP lines, where each \fBvalue\fP is either a C-like string literal in double quotes, a hexadecimal number starting with \fB0x...\fP, or a binary blob encoded with base64, denoted by \fBb64("...")\fP. .RE .PP If no output type is specified, the default is \fBprivate\fP. .RE .IP "\fB\-o\fP \fIoutput\-file\fP" -Specify the file where \fBputtygen\fP should write its output. If this option is not specified, \fBputtygen\fP will assume you want to overwrite the original file if the input and output file types are the same (changing a comment or passphrase), and will assume you want to output to stdout if you are asking for a public key or fingerprint. Otherwise, the \fB\-o\fP option is required. +Specify the file where \fBputtygen\fP should write its output. If this option is not specified, \fBputtygen\fP will assume you want to overwrite the original file if the input and output file types are the same (changing a comment or passphrase), and will assume you want to output to stdout if you are asking for a public key, fingerprint, or one of the textual dump types. Otherwise, the \fB\-o\fP option is required. .IP "\fB\-l\fP" Synonym for `\fB-O fingerprint\fP'. .IP "\fB\-L\fP" Synonym for `\fB-O public-openssh\fP'. .IP "\fB\-p\fP" Synonym for `\fB-O public\fP'. +.IP "\fB\-\-cert\-info\fP" +Synonym for `\fB-O cert-info\fP'. .IP "\fB\-\-dump\fP" Synonym for `\fB-O text\fP'. .IP "\fB-E\fP \fIfptype\fP" -Specify the algorithm to use if generating a fingerprint. The options are \fBsha256\fP (the default) and \fBmd5\fP. +Specify the algorithm to use if generating a fingerprint. The available algorithms are are \fBsha256\fP (the default) and \fBmd5\fP. +.RS +.PP +By default, when showing the fingerprint of a public key that includes a certificate, \fBputtygen\fP will not include the certificate, so that the fingerprint shown will be the same as the underlying public key. If you want the fingerprint including the certificate (for example, so as to tell two certified keys apart), you can specify \fBsha256-cert\fP or \fBmd5-cert\fP as the fingerprint type. +.RE .IP "\fB\-\-new\-passphrase\fP \fIfile\fP" Specify a file name; the first line will be read from this file (removing any trailing newline) and used as the new passphrase. If the file is empty then the saved key will be unencrypted. \fBCAUTION:\fP If the passphrase is important, the file should be stored on a temporary filesystem or else securely erased after use. .PP diff --git a/code/doc/sshnames.but b/code/doc/sshnames.but index 6cf82f73..a00ac678 100644 --- a/code/doc/sshnames.but +++ b/code/doc/sshnames.but @@ -70,7 +70,7 @@ They have been superseded by \cw{arcfour128} and \cw{arcfour256}. The SSH agent protocol, which is only specified in an Internet-Draft at the time of writing -(\W{https://tools.ietf.org/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}), +(\W{https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}), defines an extension mechanism. These names can be sent in an \cw{SSH_AGENTC_EXTENSION} message. diff --git a/code/doc/using.but b/code/doc/using.but index d5cf41c4..5865ac95 100644 --- a/code/doc/using.but +++ b/code/doc/using.but @@ -1024,6 +1024,19 @@ This option is equivalent to the \q{Private key file for authentication} box in the Auth panel of the PuTTY configuration box (see \k{config-ssh-privkey}). +\S2{using-cmdline-cert} \i\c{-cert}: specify an SSH \i{certificate} + +The \c{-cert} option allows you to specify the name of a certificate +file containing a signed version of your public key. If you specify +this option, PuTTY will present that certificate in place of the plain +public key, whenever it tries to authenticate with a key that matches. +(This applies whether the key is stored in Pageant or loaded directly +from a file by PuTTY.) + +This option is equivalent to the \q{Certificate to use with the +private key} box in the Auth panel of the PuTTY configuration box (see +\k{config-ssh-cert}). + \S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect if SSH authentication succeeds trivially @@ -1162,3 +1175,12 @@ the extra protection), so it's reasonable to want to run Pageant but not PuTTY with the ACL restrictions. You can force Pageant to start subsidiary PuTTY processes with a restricted ACL if you also pass the \i\c{-restrict-putty-acl} option. + +\S2{using-cmdline-host-ca} \i{\c{-host-ca}}: launch the +\I{certificate}host CA configuration + +If you start PuTTY with the \c{-host-ca} option, it will not launch a +session at all. Instead, it will just display the configuration dialog +box for host certification authorities, as described in +\k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will +terminate. diff --git a/code/doc/version.but b/code/doc/version.but index 3b91f150..229426d5 100644 --- a/code/doc/version.but +++ b/code/doc/version.but @@ -1 +1 @@ - ersionid PuTTY release 0.77 +\versionid PuTTY release 0.78 diff --git a/code/icons/Makefile b/code/icons/Makefile index 71b43874..3e3ea456 100644 --- a/code/icons/Makefile +++ b/code/icons/Makefile @@ -13,6 +13,8 @@ PNGS = $(patsubst %.pam,%.png,$(PAMS)) MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS)) TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS)) +SVGS = $(patsubst %,%.svg,$(ICONS)) + ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \ puttyins.ico pterm.ico ptermcfg.ico ICNS = PuTTY.icns Pterm.icns @@ -20,11 +22,12 @@ CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c base: icos cicons -all: pngs monopngs base icns # truepngs currently disabled by default +all: pngs monopngs base icns svgs # truepngs currently disabled by default pngs: $(PNGS) monopngs: $(MONOPNGS) truepngs: $(TRUEPNGS) +svgs: $(SVGS) icos: $(ICOS) icns: $(ICNS) @@ -46,6 +49,9 @@ $(MONOPAMS): %.pam: mkicon.py $(TRUEPAMS): %.pam: mkicon.py ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@ +$(SVGS): %.svg: mksvg.py + ./mksvg.py $(patsubst %.svg,%_icon,$@) -o $@ + putty.ico: putty-16.png putty-32.png putty-48.png \ putty-16-mono.png putty-32-mono.png putty-48-mono.png ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ diff --git a/code/icons/mksvg.py b/code/icons/mksvg.py new file mode 100644 index 00000000..f29ff25b --- /dev/null +++ b/code/icons/mksvg.py @@ -0,0 +1,938 @@ +#!/usr/bin/env python3 + +import argparse +import itertools +import math +import os +import sys +from fractions import Fraction + +import xml.etree.cElementTree as ET + +# Python code which draws the PuTTY icon components in SVG. + +def makegroup(*objects): + if len(objects) == 1: + return objects[0] + g = ET.Element("g") + for obj in objects: + g.append(obj) + return g + +class Container: + "Empty class for keeping things in." + pass + +class SVGthing(object): + def __init__(self): + self.fillc = "none" + self.strokec = "none" + self.strokewidth = 0 + self.strokebehind = False + self.clipobj = None + self.props = Container() + def fmt_colour(self, rgb): + return "#{0:02x}{1:02x}{2:02x}".format(*rgb) + def fill(self, colour): + self.fillc = self.fmt_colour(colour) + def stroke(self, colour, width=1, behind=False): + self.strokec = self.fmt_colour(colour) + self.strokewidth = width + self.strokebehind = behind + def clip(self, obj): + self.clipobj = obj + def styles(self, elt, styles): + elt.attrib["style"] = ";".join("{}:{}".format(k,v) + for k,v in sorted(styles.items())) + def add_clip_paths(self, container, idents, X, Y): + if self.clipobj: + self.clipobj.identifier = next(idents) + clipelt = self.clipobj.render_thing(X, Y) + clippath = ET.Element("clipPath") + clippath.attrib["id"] = self.clipobj.identifier + clippath.append(clipelt) + container.append(clippath) + return True + return False + def render(self, X, Y, with_styles=True): + elt = self.render_thing(X, Y) + if self.clipobj: + elt.attrib["clip-path"] = "url(#{})".format( + self.clipobj.identifier) + estyles = {"fill": self.fillc} + sstyles = {"stroke": self.strokec} + if self.strokewidth: + sstyles["stroke-width"] = "{:g}".format(self.strokewidth) + sstyles["stroke-linecap"] = "round" + sstyles["stroke-linejoin"] = "round" + if not self.strokebehind: + estyles.update(sstyles) + if with_styles: + self.styles(elt, estyles) + if not self.strokebehind: + return elt + selt = self.render_thing(X, Y) + if with_styles: + self.styles(selt, sstyles) + return makegroup(selt, elt) + def bbox(self): + it = self.bb_iter() + xmin, ymin = xmax, ymax = next(it) + for x, y in it: + xmin = min(x, xmin) + xmax = max(x, xmax) + ymin = min(y, ymin) + ymax = max(y, ymax) + r = self.strokewidth / 2.0 + xmin -= r + ymin -= r + xmax += r + ymax += r + if self.clipobj: + x0, y0, x1, y1 = self.clipobj.bbox() + xmin = max(x0, xmin) + xmax = min(x1, xmax) + ymin = max(y0, ymin) + ymax = min(y1, ymax) + return xmin, ymin, xmax, ymax + +class SVGpath(SVGthing): + def __init__(self, pointlists, closed=True): + super().__init__() + self.pointlists = pointlists + self.closed = closed + def bb_iter(self): + for points in self.pointlists: + for x,y,on in points: + yield x,y + def render_thing(self, X, Y): + pathcmds = [] + + for points in self.pointlists: + while not points[-1][2]: + points = points[1:] + [points[0]] + + piter = iter(points) + + if self.closed: + xp, yp, _ = points[-1] + pathcmds.extend(["M", X+xp, Y-yp]) + else: + xp, yp, on = next(piter) + assert on, "Open paths must start with an on-curve point" + pathcmds.extend(["M", X+xp, Y-yp]) + + for x, y, on in piter: + if isinstance(on, type(())): + assert on[0] == "arc" + _, rx, ry, rotation, large, sweep = on + pathcmds.extend(["a", + rx, ry, rotation, + 1 if large else 0, + 1 if sweep else 0, + x-xp, -(y-yp)]) + elif not on: + x0, y0 = x, y + x1, y1, on = next(piter) + assert not on + x, y, on = next(piter) + assert on + pathcmds.extend(["c", x0-xp, -(y0-yp), + ",", x1-xp, -(y1-yp), + ",", x-xp, -(y-yp)]) + elif x == xp: + pathcmds.extend(["v", -(y-yp)]) + elif x == xp: + pathcmds.extend(["h", x-xp]) + else: + pathcmds.extend(["l", x-xp, -(y-yp)]) + + xp, yp = x, y + + if self.closed: + pathcmds.append("z") + + path = ET.Element("path") + path.attrib["d"] = " ".join(str(cmd) for cmd in pathcmds) + return path + +class SVGrect(SVGthing): + def __init__(self, x0, y0, x1, y1): + super().__init__() + self.points = x0, y0, x1, y1 + def bb_iter(self): + x0, y0, x1, y1 = self.points + return iter([(x0,y0), (x1,y1)]) + def render_thing(self, X, Y): + x0, y0, x1, y1 = self.points + rect = ET.Element("rect") + rect.attrib["x"] = "{:g}".format(min(X+x0,X+x1)) + rect.attrib["y"] = "{:g}".format(min(Y-y0,Y-y1)) + rect.attrib["width"] = "{:g}".format(abs(x0-x1)) + rect.attrib["height"] = "{:g}".format(abs(y0-y1)) + return rect + +class SVGpoly(SVGthing): + def __init__(self, points): + super().__init__() + self.points = points + def bb_iter(self): + return iter(self.points) + def render_thing(self, X, Y): + poly = ET.Element("polygon") + poly.attrib["points"] = " ".join("{:g},{:g}".format(X+x,Y-y) + for x,y in self.points) + return poly + +class SVGgroup(object): + def __init__(self, objects, translations=[]): + translations = translations + ( + [(0,0)] * (len(objects)-len(translations))) + self.contents = list(zip(objects, translations)) + self.props = Container() + def render(self, X, Y): + return makegroup(*[obj.render(X+x, Y-y) + for obj, (x,y) in self.contents]) + def add_clip_paths(self, container, idents, X, Y): + toret = False + for obj, (x,y) in self.contents: + if obj.add_clip_paths(container, idents, X+x, Y-y): + toret = True + return toret + def bbox(self): + it = ((x,y) + obj.bbox() for obj, (x,y) in self.contents) + x, y, xmin, ymin, xmax, ymax = next(it) + xmin = x+xmin + ymin = y+ymin + xmax = x+xmax + ymax = y+ymax + for x, y, x0, y0, x1, y1 in it: + xmin = min(x+x0, xmin) + xmax = max(x+x1, xmax) + ymin = min(y+y0, ymin) + ymax = max(y+y1, ymax) + return (xmin, ymin, xmax, ymax) + +class SVGtranslate(object): + def __init__(self, obj, translation): + self.obj = obj + self.tx, self.ty = translation + def render(self, X, Y): + return self.obj.render(X+self.tx, Y+self.ty) + def add_clip_paths(self, container, idents, X, Y): + return self.obj.add_clip_paths(container, idents, X+self.tx, Y-self.ty) + def bbox(self): + xmin, ymin, xmax, ymax = self.obj.bbox() + return xmin+self.tx, ymin+self.ty, xmax+self.tx, ymax+self.ty + +# Code to actually draw pieces of icon. These don't generally worry +# about positioning within a rectangle; they just draw at a standard +# location, return some useful coordinates, and leave composition +# to other pieces of code. + +def sysbox(size): + # The system box of the computer. + + height = 3.6*size + width = 16.51*size + depth = 2*size + highlight = 1*size + + floppystart = 19*size # measured in half-pixels + floppyend = 29*size # measured in half-pixels + floppybottom = highlight + floppyrheight = 0.7 * size + floppyheight = floppyrheight + if floppyheight < 1: + floppyheight = 1 + floppytop = floppybottom + floppyheight + + background_coords = [ + (0,0), (width,0), (width+depth,depth), + (width+depth,height+depth), (depth,height+depth), (0,height)] + background = SVGpoly(background_coords) + background.fill(greypix(0.75)) + + hl_dark = SVGpoly([ + (highlight,0), (highlight,highlight), (width-highlight,highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth,depth), (width,0)]) + hl_dark.fill(greypix(0.5)) + + hl_light = SVGpoly([ + (0,highlight), (highlight,highlight), (highlight,height-highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth-highlight,height+depth), (width-highlight,height), + (0,height)]) + hl_light.fill(cW) + + floppy = SVGrect(floppystart/2.0, floppybottom, + floppyend/2.0, floppytop) + floppy.fill(cK) + + outline = SVGpoly(background_coords) + outline.stroke(cK, width=0.5) + + toret = SVGgroup([background, hl_dark, hl_light, floppy, outline]) + toret.props.sysboxheight = height + toret.props.borderthickness = 1 # FIXME + return toret + +def monitor(size): + # The computer's monitor. + + height = 9.5*size + width = 11.5*size + surround = 1*size + botsurround = 2*size + sheight = height - surround - botsurround + swidth = width - 2*surround + depth = 2*size + highlight = surround/2 + shadow = 0.5*size + + background_coords = [ + (0,0), (width,0), (width+depth,depth), + (width+depth,height+depth), (depth,height+depth), (0,height)] + background = SVGpoly(background_coords) + background.fill(greypix(0.75)) + + hl0_dark = SVGpoly([ + (0,0), (highlight,highlight), (width-highlight,highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth,depth), (width,0)]) + hl0_dark.fill(greypix(0.5)) + + hl0_light = SVGpoly([ + (0,0), (highlight,highlight), (highlight,height-highlight), + (width-highlight,height-highlight), (width,height), (0,height)]) + hl0_light.fill(greypix(1)) + + hl1_dark = SVGpoly([ + (surround-highlight,botsurround-highlight), (surround,botsurround), + (surround,height-surround), (width-surround,height-surround), + (width-surround+highlight,height-surround+highlight), + (surround-highlight,height-surround+highlight)]) + hl1_dark.fill(greypix(0.5)) + + hl1_light = SVGpoly([ + (surround-highlight,botsurround-highlight), (surround,botsurround), + (width-surround,botsurround), (width-surround,height-surround), + (width-surround+highlight,height-surround+highlight), + (width-surround+highlight,botsurround-highlight)]) + hl1_light.fill(greypix(1)) + + screen = SVGrect(surround, botsurround, width-surround, height-surround) + screen.fill(bluepix(1)) + + screenshadow = SVGpoly([ + (surround,botsurround), (surround+shadow,botsurround), + (surround+shadow,height-surround-shadow), + (width-surround,height-surround-shadow), + (width-surround,height-surround), (surround,height-surround)]) + screenshadow.fill(bluepix(0.5)) + + outline = SVGpoly(background_coords) + outline.stroke(cK, width=0.5) + + toret = SVGgroup([background, hl0_dark, hl0_light, hl1_dark, hl1_light, + screen, screenshadow, outline]) + # Give the centre of the screen (for lightning-bolt positioning purposes) + # as the centre of the _light_ area of the screen, not counting the + # shadow on the top and left. I think that looks very slightly nicer. + sbb = (surround+shadow, botsurround, width-surround, height-surround-shadow) + toret.props.screencentre = ((sbb[0]+sbb[2])/2, (sbb[1]+sbb[3])/2) + return toret + +def computer(size): + # Monitor plus sysbox. + m = monitor(size) + s = sysbox(size) + x = (2+size/(size+1))*size + y = int(s.props.sysboxheight + s.props.borderthickness) + mb = m.bbox() + sb = s.bbox() + xoff = mb[0] - sb[0] + x + yoff = mb[1] - sb[1] + y + toret = SVGgroup([s, m], [(0,0), (xoff,yoff)]) + toret.props.screencentre = (m.props.screencentre[0]+xoff, + m.props.screencentre[1]+yoff) + return toret + +def lightning(size): + # The lightning bolt motif. + + # Compute the right size of a lightning bolt to exactly connect + # the centres of the two screens in the main PuTTY icon. We'll use + # that size of bolt for all the other icons too, for consistency. + iconw = iconh = 32 * size + cbb = computer(size).bbox() + assert cbb[2]-cbb[0] <= iconw and cbb[3]-cbb[1] <= iconh + width, height = iconw-(cbb[2]-cbb[0]), iconh-(cbb[3]-cbb[1]) + + degree = math.pi/180 + + centrethickness = 2*size # top-to-bottom thickness of centre bar + innerangle = 46 * degree # slope of the inner slanting line + outerangle = 39 * degree # slope of the outer one + + innery = (height - centrethickness) / 2 + outery = (height + centrethickness) / 2 + innerx = innery / math.tan(innerangle) + outerx = outery / math.tan(outerangle) + + points = [(innerx, innery), (0,0), (outerx, outery)] + points.extend([(width-x, height-y) for x,y in points]) + + # Fill and stroke the lightning bolt. + # + # Most of the filled-and-stroked objects in these icons are filled + # first, and then stroked with width 0.5, so that the edge of the + # filled area runs down the centre line of the stroke. Put another + # way, half the stroke covers what would have been the filled + # area, and the other half covers the background. This seems like + # the normal way to fill-and-stroke a shape of a given size, and + # SVG makes it easy by allowing us to specify the polygon just + # once with both 'fill' and 'stroke' CSS properties. + # + # But if we did that in this case, then the tips of the lightning + # bolt wouldn't have lightning-colour anywhere near them, because + # the two edges are so close together in angle that the point + # where the strokes would first _not_ overlap would be miles away + # from the logical endpoint. + # + # So, for this one case, we stroke the polygon first at double the + # width, and then fill it on top of that, requiring two copies of + # it in the SVG (though my construction class here hides that + # detail). The effect is that we still get a stroke of visible + # width 0.5, but it's entirely outside the filled area of the + # polygon, so the tips of the yellow interior of the lightning + # bolt are exactly at the logical endpoints. + poly = SVGpoly(points) + poly.fill(cY) + poly.stroke(cK, width=1, behind=True) + poly.props.end1 = (0,0) + poly.props.end2 = (width,height) + return poly + +def document(size): + # The document used in the PSCP/PSFTP icon. + + width = 13*size + height = 16*size + + lineht = 0.875*size + linespc = 1.125*size + nlines = int((height-linespc)/(lineht+linespc)) + height = nlines*(lineht+linespc)+linespc # round this so it fits better + + paper = SVGrect(0, 0, width, height) + paper.fill(cW) + paper.stroke(cK, width=0.5) + + objs = [paper] + + # Now draw lines of text. + for line in range(nlines): + # Decide where this line of text begins. + if line == 0: + start = 4*size + elif line < 5*nlines/7: + start = (line * 4/5) * size + else: + start = 1*size + # Decide where it ends. + endpoints = [10, 8, 11, 6, 5, 7, 5] + ey = line * 6.0 / (nlines-1) + eyf = math.floor(ey) + eyc = math.ceil(ey) + exf = endpoints[int(eyf)] + exc = endpoints[int(eyc)] + if eyf == eyc: + end = exf + else: + end = exf * (eyc-ey) + exc * (ey-eyf) + end = end * size + + liney = (lineht+linespc) * (line+1) + line = SVGrect(start, liney-lineht, end, liney) + line.fill(cK) + objs.append(line) + + return SVGgroup(objs) + +def hat(size): + # The secret-agent hat in the Pageant icon. + + leftend = (0, -6*size) + rightend = (28*size, -12*size) + dx = rightend[0]-leftend[0] + dy = rightend[1]-leftend[1] + tcentre = (leftend[0] + 0.5*dx - 0.3*dy, leftend[1] + 0.5*dy + 0.3*dx) + + hatpoints = [leftend + (True,), + (7.5*size, -6*size, True), + (12*size, 0, True), + (14*size, 3*size, False), + (tcentre[0] - 0.1*dx, tcentre[1] - 0.1*dy, False), + tcentre + (True,)] + for x, y, on in list(reversed(hatpoints))[1:]: + vx, vy = x-tcentre[0], y-tcentre[1] + coeff = float(vx*dx + vy*dy) / float(dx*dx + dy*dy) + rx, ry = x - 2*coeff*dx, y - 2*coeff*dy + hatpoints.append((rx, ry, on)) + + mainhat = SVGpath([hatpoints]) + mainhat.fill(cK) + + band = SVGpoly([ + (leftend[0] - 0.1*dy, leftend[1] + 0.1*dx), + (rightend[0] - 0.1*dy, rightend[1] + 0.1*dx), + (rightend[0] - 0.15*dy, rightend[1] + 0.15*dx), + (leftend[0] - 0.15*dy, leftend[1] + 0.15*dx)]) + band.fill(cW) + band.clip(SVGpath([hatpoints])) + + outline = SVGpath([hatpoints]) + outline.stroke(cK, width=1) + + return SVGgroup([mainhat, band, outline]) + +def key(size): + # The key in the PuTTYgen icon. + + keyheadw = 9.5*size + keyheadh = 12*size + keyholed = 4*size + keyholeoff = 2*size + # Ensure keyheadh and keyshafth have the same parity. + keyshafth = (2*size - (int(keyheadh)&1)) / 2 * 2 + (int(keyheadh)&1) + keyshaftw = 18.5*size + keyheaddetail = [x*size for x in [12,11,8,10,9,8,11,12]] + + squarepix = [] + + keyheadcx = keyshaftw + keyheadw / 2.0 + keyheadcy = keyheadh / 2.0 + keyshafttop = keyheadcy + keyshafth / 2.0 + keyshaftbot = keyheadcy - keyshafth / 2.0 + + keyhead = [(0, keyshafttop, True), (keyshaftw, keyshafttop, True), + (keyshaftw, keyshaftbot, + ("arc", keyheadw/2.0, keyheadh/2.0, 0, True, True)), + (len(keyheaddetail)*size, keyshaftbot, True)] + for i, h in reversed(list(enumerate(keyheaddetail))): + keyhead.append(((i+1)*size, keyheadh-h, True)) + keyhead.append(((i)*size, keyheadh-h, True)) + + keyholecx = keyheadcx + keyholeoff + keyholecy = keyheadcy + keyholer = keyholed / 2.0 + + keyhole = [(keyholecx + keyholer, keyholecy, + ("arc", keyholer, keyholer, 0, False, False)), + (keyholecx - keyholer, keyholecy, + ("arc", keyholer, keyholer, 0, False, False))] + + outline = SVGpath([keyhead, keyhole]) + outline.fill(cy) + outline.stroke(cK, width=0.5) + return outline + +def linedist(x1,y1, x2,y2, x,y): + # Compute the distance from the point x,y to the line segment + # joining x1,y1 to x2,y2. Returns the distance vector, measured + # with x,y at the origin. + + vectors = [] + + # Special case: if x1,y1 and x2,y2 are the same point, we + # don't attempt to extrapolate it into a line at all. + if x1 != x2 or y1 != y2: + # First, find the nearest point to x,y on the infinite + # projection of the line segment. So we construct a vector + # n perpendicular to that segment... + nx = y2-y1 + ny = x1-x2 + # ... compute the dot product of (x1,y1)-(x,y) with that + # vector... + nd = (x1-x)*nx + (y1-y)*ny + # ... multiply by the vector we first thought of... + ndx = nd * nx + ndy = nd * ny + # ... and divide twice by the length of n. + ndx = ndx / (nx*nx+ny*ny) + ndy = ndy / (nx*nx+ny*ny) + # That gives us a displacement vector from x,y to the + # nearest point. See if it's within the range of the line + # segment. + cx = x + ndx + cy = y + ndy + if cx >= min(x1,x2) and cx <= max(x1,x2) and \ + cy >= min(y1,y2) and cy <= max(y1,y2): + vectors.append((ndx,ndy)) + + # Now we have up to three candidate result vectors: (ndx,ndy) + # as computed just above, and the two vectors to the ends of + # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the + # shortest. + vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)] + bestlen, best = None, None + for v in vectors: + vlen = v[0]*v[0]+v[1]*v[1] + if bestlen == None or bestlen > vlen: + bestlen = vlen + best = v + return best + +def spanner(size): + # The spanner in the config box icon. + + # Coordinate definitions. + headcentre = 0.5 + 4*size + headradius = headcentre + 0.1 + headhighlight = 1.5*size + holecentre = 0.5 + 3*size + holeradius = 2*size + holehighlight = 1.5*size + shaftend = 0.5 + 25*size + shaftwidth = 2*size + shafthighlight = 1.5*size + cmax = shaftend + shaftwidth + + # The spanner head is a circle centred at headcentre*(1,1) with + # radius headradius, minus a circle at holecentre*(1,1) with + # radius holeradius, and also minus every translate of that circle + # by a negative real multiple of (1,1). + # + # The spanner handle is a diagonally oriented rectangle, of width + # shaftwidth, with the centre of the far end at shaftend*(1,1), + # and the near end terminating somewhere inside the spanner head + # (doesn't really matter exactly where). + # + # Hence, in SVG we can represent the shape using a path of + # straight lines and circular arcs. But first we need to calculate + # the points where the straight lines meet the spanner head circle. + headpt = lambda a, on=True: (headcentre+headradius*math.cos(a), + -headcentre+headradius*math.sin(a), on) + holept = lambda a, on=True: (holecentre+holeradius*math.cos(a), + -holecentre+holeradius*math.sin(a), on) + + # Now we can specify the path. + spannercoords = [[ + holept(math.pi*5/4), + holept(math.pi*1/4, ("arc", holeradius,holeradius,0, False, False)), + headpt(math.pi*3/4 - math.asin(holeradius/headradius)), + headpt(math.pi*7/4 + math.asin(shaftwidth/headradius), + ("arc", headradius,headradius,0, False, True)), + (shaftend+math.sqrt(0.5)*shaftwidth, + -shaftend+math.sqrt(0.5)*shaftwidth, True), + (shaftend-math.sqrt(0.5)*shaftwidth, + -shaftend-math.sqrt(0.5)*shaftwidth, True), + headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), + headpt(math.pi*3/4 + math.asin(holeradius/headradius), + ("arc", headradius,headradius,0, False, True)), + ]] + + base = SVGpath(spannercoords) + base.fill(cY) + + shadowthickness = 2*size + sx, sy, _ = holept(math.pi*5/4) + sx += math.sqrt(0.5) * shadowthickness/2 + sy += math.sqrt(0.5) * shadowthickness/2 + sr = holeradius - shadowthickness/2 + + shadow = SVGpath([ + [(sx, sy, sr), + holept(math.pi*1/4, ("arc", sr, sr, 0, False, False)), + headpt(math.pi*3/4 - math.asin(holeradius/headradius))], + [(shaftend-math.sqrt(0.5)*shaftwidth, + -shaftend-math.sqrt(0.5)*shaftwidth, True), + headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), + headpt(math.pi*3/4 + math.asin(holeradius/headradius), + ("arc", headradius,headradius,0, False, True))], + ], closed=False) + shadow.clip(SVGpath(spannercoords)) + shadow.stroke(cy, width=shadowthickness) + + outline = SVGpath(spannercoords) + outline.stroke(cK, width=0.5) + + return SVGgroup([base, shadow, outline]) + +def box(size, wantback): + # The back side of the cardboard box in the installer icon. + + boxwidth = 15 * size + boxheight = 12 * size + boxdepth = 4 * size + boxfrontflapheight = 5 * size + boxrightflapheight = 3 * size + + # Three shades of basically acceptable brown, all achieved by + # halftoning between two of the Windows-16 colours. I'm quite + # pleased that was feasible at all! + dark = halftone(cr, cK) + med = halftone(cr, cy) + light = halftone(cr, cY) + # We define our halftoning parity in such a way that the black + # pixels along the RHS of the visible part of the box back + # match up with the one-pixel black outline around the + # right-hand side of the box. In other words, we want the pixel + # at (-1, boxwidth-1) to be black, and hence the one at (0, + # boxwidth) too. + parityadjust = int(boxwidth) % 2 + + # The back of the box. + if wantback: + back = SVGpoly([ + (0,0), (boxwidth,0), (boxwidth+boxdepth,boxdepth), + (boxwidth+boxdepth,boxheight+boxdepth), + (boxdepth,boxheight+boxdepth), (0,boxheight)]) + back.fill(dark) + back.stroke(cK, width=0.5) + return back + + # The front face of the box. + front = SVGrect(0, 0, boxwidth, boxheight) + front.fill(med) + front.stroke(cK, width=0.5) + # The right face of the box. + right = SVGpoly([ + (boxwidth,0), (boxwidth+boxdepth,boxdepth), + (boxwidth+boxdepth,boxheight+boxdepth), (boxwidth,boxheight)]) + right.fill(dark) + right.stroke(cK, width=0.5) + frontflap = SVGpoly([ + (0,boxheight), (boxwidth,boxheight), + (boxwidth-boxfrontflapheight/2, boxheight-boxfrontflapheight), + (-boxfrontflapheight/2, boxheight-boxfrontflapheight)]) + frontflap.stroke(cK, width=0.5) + frontflap.fill(light) + rightflap = SVGpoly([ + (boxwidth,boxheight), (boxwidth+boxdepth,boxheight+boxdepth), + (boxwidth+boxdepth+boxrightflapheight, + boxheight+boxdepth-boxrightflapheight), + (boxwidth+boxrightflapheight,boxheight-boxrightflapheight)]) + rightflap.stroke(cK, width=0.5) + rightflap.fill(med) + + return SVGgroup([front, right, frontflap, rightflap]) + +def boxback(size): + return box(size, 1) +def boxfront(size): + return box(size, 0) + +# Functions to draw entire icons by composing the above components. + +def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, c1bb=None, c2bb=None): + # Two unspecified objects and a lightning bolt. + + w = h = 32 * size + + bolt = lightning(size) + + objs = [c2, c1, bolt] + origins = [None] * 3 + + # Position c2 against the top right of the icon. + bb = c2bb if c2bb is not None else c2.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + origins[0] = w-bb[2], h-bb[3] + # Position c1 against the bottom left of the icon. + bb = c1bb if c1bb is not None else c1.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + origins[1] = 0-bb[0], 0-bb[1] + + # Place the lightning bolt so that it ends precisely at the centre + # of the monitor, in whichever of the two sub-pictures has one. + # (In the case of the PuTTY icon proper, in which _both_ + # sub-pictures are computers, it should line up correctly for both.) + origin1 = origin2 = None + if hasattr(c1.props, "screencentre"): + origin1 = ( + c1.props.screencentre[0] + origins[1][0] - bolt.props.end1[0], + c1.props.screencentre[1] + origins[1][1] - bolt.props.end1[1]) + if hasattr(c2.props, "screencentre"): + origin2 = ( + c2.props.screencentre[0] + origins[0][0] - bolt.props.end2[0], + c2.props.screencentre[1] + origins[0][1] - bolt.props.end2[1]) + if origin1 is not None and origin2 is not None: + assert math.hypot(origin1[0]-origin2[0],origin1[1]-origin2[1]<1e-5), ( + "Lightning bolt didn't line up! Off by {}*size".format( + ((origin1[0]-origin2[0])/size, + (origin1[1]-origin2[1])/size))) + origins[2] = origin1 if origin1 is not None else origin2 + assert origins[2] is not None, "Need at least one computer to line up bolt" + + toret = SVGgroup(objs, origins) + toret.props.c1pos = origins[1] + toret.props.c2pos = origins[0] + return toret + +def putty_icon(size): + return xybolt(computer(size), computer(size), size) + +def puttycfg_icon(size): + w = h = 32 * size + s = spanner(size) + b = putty_icon(size) + bb = s.bbox() + return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def puttygen_icon(size): + k = key(size) + # Manually move the key around, by pretending to xybolt that its + # bounding box is offset from where it really is. + kbb = SVGtranslate(k,(2*size,5*size)).bbox() + return xybolt(computer(size), k, size, boltoffx=2, c2bb=kbb) + +def pscp_icon(size): + return xybolt(document(size), computer(size), size) + +def puttyins_icon(size): + boxfront = box(size, False) + boxback = box(size, True) + # The box back goes behind the lightning bolt. + most = xybolt(boxback, computer(size), size, c1bb=boxfront.bbox(), + boltoffx=-2, boltoffy=+1) + # But the box front goes over the top, so that the lightning + # bolt appears to come _out_ of the box. Here it's useful to + # know the exact coordinates where xybolt placed the box back, + # so we can overlay the box front exactly on top of it. + c1x, c1y = most.props.c1pos + return SVGgroup([most, boxfront], [(0,0), most.props.c1pos]) + +def pterm_icon(size): + # Just a really big computer. + + w = h = 32 * size + + c = computer(size * 1.4) + + # Centre c in the output rectangle. + bb = c.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + + return SVGgroup([c], [((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def ptermcfg_icon(size): + w = h = 32 * size + s = spanner(size) + b = pterm_icon(size) + bb = s.bbox() + return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def pageant_icon(size): + # A biggish computer, in a hat. + + w = h = 32 * size + + c = computer(size * 1.2) + ht = hat(size) + + cbb = c.bbox() + hbb = ht.bbox() + + # Determine the relative coordinates of the computer and hat. We + # do this by first centring one on the other, then adjusting by + # hand. + xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 + 2*size + yrel = (cbb[1]+cbb[3]-hbb[1]-hbb[3])/2 + 12*size + + both = SVGgroup([c, ht], [(0,0), (xrel,yrel)]) + + # Mostly-centre the result in the output rectangle. We want + # everything to fit in frame, but we also want to make it look as + # if the computer is more x-centred than the hat. + + # Coordinates that would centre the whole group. + bb = both.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + grx, gry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 + + # Coords that would centre just the computer. + bb = c.bbox() + crx, cry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 + + # Use gry unchanged, but linear-combine grx with crx. + return SVGgroup([both], [(grx+0.6*(crx-grx), gry)]) + +# Test and output functions. + +cK = (0x00, 0x00, 0x00, 0xFF) +cr = (0x80, 0x00, 0x00, 0xFF) +cg = (0x00, 0x80, 0x00, 0xFF) +cy = (0x80, 0x80, 0x00, 0xFF) +cb = (0x00, 0x00, 0x80, 0xFF) +cm = (0x80, 0x00, 0x80, 0xFF) +cc = (0x00, 0x80, 0x80, 0xFF) +cP = (0xC0, 0xC0, 0xC0, 0xFF) +cw = (0x80, 0x80, 0x80, 0xFF) +cR = (0xFF, 0x00, 0x00, 0xFF) +cG = (0x00, 0xFF, 0x00, 0xFF) +cY = (0xFF, 0xFF, 0x00, 0xFF) +cB = (0x00, 0x00, 0xFF, 0xFF) +cM = (0xFF, 0x00, 0xFF, 0xFF) +cC = (0x00, 0xFF, 0xFF, 0xFF) +cW = (0xFF, 0xFF, 0xFF, 0xFF) +cD = (0x00, 0x00, 0x00, 0x80) +cT = (0x00, 0x00, 0x00, 0x00) +def greypix(value): + value = max(min(value, 1), 0) + return (int(round(0xFF*value)),) * 3 + (0xFF,) +def yellowpix(value): + value = max(min(value, 1), 0) + return (int(round(0xFF*value)),) * 2 + (0, 0xFF) +def bluepix(value): + value = max(min(value, 1), 0) + return (0, 0, int(round(0xFF*value)), 0xFF) +def dark(value): + value = max(min(value, 1), 0) + return (0, 0, 0, int(round(0xFF*value))) +def blend(col1, col2): + r1,g1,b1,a1 = col1 + r2,g2,b2,a2 = col2 + r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0)) + g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0)) + b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0)) + a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0)) + return r, g, b, a +def halftone(col1, col2): + r1,g1,b1,a1 = col1 + r2,g2,b2,a2 = col2 + return ((r1+r2)//2, (g1+g2)//2, (b1+b2)//2, (a1+a2)//2) + +def drawicon(func, width, fname): + icon = func(width / 32.0) + minx, miny, maxx, maxy = icon.bbox() + #assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width + + svgroot = ET.Element("svg") + svgroot.attrib["xmlns"] = "http://www.w3.org/2000/svg" + svgroot.attrib["viewBox"] = "0 0 {w:d} {w:d}".format(w=width) + + defs = ET.Element("defs") + idents = ("iconid{:d}".format(n) for n in itertools.count()) + if icon.add_clip_paths(defs, idents, 0, width): + svgroot.append(defs) + + svgroot.append(icon.render(0,width)) + + ET.ElementTree(svgroot).write(fname) + +def main(): + parser = argparse.ArgumentParser(description='Generate PuTTY SVG icons.') + parser.add_argument("icon", help="Which icon to generate.") + parser.add_argument("-s", "--size", type=int, default=48, + help="Notional pixel size to base the SVG on.") + parser.add_argument("-o", "--output", required=True, + help="Output file name.") + args = parser.parse_args() + + drawicon(eval(args.icon), args.size, args.output) + +if __name__ == '__main__': + main() diff --git a/code/import.c b/code/import.c index 41c06a9a..918de50e 100644 --- a/code/import.c +++ b/code/import.c @@ -174,17 +174,6 @@ bool export_ssh2(const Filename *filename, int type, return false; } -/* - * Strip trailing CRs and LFs at the end of a line of text. - */ -void strip_crlf(char *str) -{ - char *p = str + strlen(str); - - while (p > str && (p[-1] == '\r' || p[-1] == '\n')) - *--p = '\0'; -} - /* ---------------------------------------------------------------------- * Helper routines. (The base64 ones are defined in sshpubk.c.) */ @@ -328,7 +317,7 @@ struct openssh_pem_key { strbuf *keyblob; }; -void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) +static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) { const unsigned char *bytes = (const unsigned char *)str.ptr; size_t nbytes = str.len; @@ -498,7 +487,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -801,7 +790,7 @@ static ssh2_userkey *openssh_pem_read( errmsg = NULL; /* no error */ retval = retkey; - error: + error: strbuf_free(blob); strbuf_free(key->keyblob); smemclr(key, sizeof(*key)); @@ -811,7 +800,7 @@ static ssh2_userkey *openssh_pem_read( } static bool openssh_pem_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) + const Filename *filename, ssh2_userkey *ukey, const char *passphrase) { strbuf *pubblob, *privblob, *outblob; unsigned char *spareblob; @@ -825,13 +814,17 @@ static bool openssh_pem_write( FILE *fp; BinarySource src[1]; + /* OpenSSH's private key files never contain a certificate, so + * revert to the underlying base key if necessary */ + ssh_key *key = ssh_key_base_key(ukey->key); + /* * Fetch the key blobs. */ pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); privblob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); + ssh_key_private_blob(key, BinarySink_UPCAST(privblob)); spareblob = NULL; outblob = strbuf_new_nm(); @@ -840,8 +833,8 @@ static bool openssh_pem_write( * Encode the OpenSSH key blob, and also decide on the header * line. */ - if (ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_dsa) { + if (ssh_key_alg(key) == &ssh_rsa || + ssh_key_alg(key) == &ssh_dsa) { strbuf *seq; /* @@ -851,7 +844,7 @@ static bool openssh_pem_write( * bignums per key type and then construct the actual blob in * common code after that. */ - if (ssh_key_alg(key->key) == &ssh_rsa) { + if (ssh_key_alg(key) == &ssh_rsa) { ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; mp_int *bd, *bp, *bq, *bdmp1, *bdmq1; @@ -947,11 +940,11 @@ static bool openssh_pem_write( put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); put_data(outblob, seq->s, seq->len); strbuf_free(seq); - } else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) { + } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key) == &ssh_ecdsa_nistp521) { const unsigned char *oid; - struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk); + struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk); int oidlen; int pointlen; strbuf *seq, *sub; @@ -966,7 +959,7 @@ static bool openssh_pem_write( * [1] * BIT STRING (0x00 public key point) */ - oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen); + oid = ec_alg_oid(ssh_key_alg(key), &oidlen); pointlen = (ec->curve->fieldBits + 7) / 8 * 2; seq = strbuf_new_nm(); @@ -998,7 +991,7 @@ static bool openssh_pem_write( /* Append the BIT STRING to the sequence */ put_ber_id_len(seq, 1, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); put_data(seq, sub->s, sub->len); strbuf_free(sub); @@ -1075,12 +1068,12 @@ static bool openssh_pem_write( fprintf(fp, "%02X", iv[i]); fprintf(fp, "\n\n"); } - base64_encode(fp, outblob->u, outblob->len, 64); + base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64); fputs(footer, fp); fclose(fp); ret = true; - error: + error: if (outblob) strbuf_free(outblob); if (spareblob) { @@ -1245,8 +1238,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, ret->kdfopts.bcrypt.rounds = get_uint32(opts); if (get_err(opts)) { - errmsg = "failed to parse bcrypt options string"; - goto error; + errmsg = "failed to parse bcrypt options string"; + goto error; } break; } @@ -1294,7 +1287,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -1484,7 +1477,7 @@ static ssh2_userkey *openssh_new_read( retval = retkey; retkey = NULL; /* prevent the free */ - error: + error: if (retkey) { sfree(retkey->comment); if (retkey->key) @@ -1499,7 +1492,7 @@ static ssh2_userkey *openssh_new_read( } static bool openssh_new_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) + const Filename *filename, ssh2_userkey *ukey, const char *passphrase) { strbuf *pubblob, *privblob, *cblob; int padvalue; @@ -1509,13 +1502,17 @@ static bool openssh_new_write( const int bcrypt_rounds = 16; FILE *fp; + /* OpenSSH's private key files never contain a certificate, so + * revert to the underlying base key if necessary */ + ssh_key *key = ssh_key_base_key(ukey->key); + /* * Fetch the key blobs and find out the lengths of things. */ pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); privblob = strbuf_new_nm(); - ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob)); + ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob)); /* * Construct the cleartext version of the blob. @@ -1562,11 +1559,11 @@ static bool openssh_new_write( /* Private key. The main private blob goes inline, with no string * wrapper. */ - put_stringz(cpblob, ssh_key_ssh_id(key->key)); + put_stringz(cpblob, ssh_key_ssh_id(key)); put_data(cpblob, privblob->s, privblob->len); /* Comment. */ - put_stringz(cpblob, key->comment); + put_stringz(cpblob, ukey->comment); /* Pad out the encrypted section. */ padvalue = 1; @@ -1606,12 +1603,12 @@ static bool openssh_new_write( if (!fp) goto error; fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp); - base64_encode(fp, cblob->u, cblob->len, 64); + base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64); fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); fclose(fp); ret = true; - error: + error: if (cblob) strbuf_free(cblob); if (privblob) @@ -1633,11 +1630,12 @@ static bool openssh_auto_write( * assume that anything not in that fixed list is newer, and hence * will use the new format. */ - if (ssh_key_alg(key->key) == &ssh_dsa || - ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) + const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key)); + if (alg == &ssh_dsa || + alg == &ssh_rsa || + alg == &ssh_ecdsa_nistp256 || + alg == &ssh_ecdsa_nistp384 || + alg == &ssh_ecdsa_nistp521) return openssh_pem_write(filename, key, passphrase); else return openssh_new_write(filename, key, passphrase); @@ -1845,7 +1843,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -1883,7 +1881,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment) if (!ptrlen_eq_string(str, "none")) answer = true; - done: + done: if (key) { *comment = dupstr(key->comment); strbuf_free(key->keyblob); @@ -1895,7 +1893,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment) return answer; } -void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) +static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) { const unsigned char *bytes = (const unsigned char *)str.ptr; size_t nbytes = str.len; @@ -1979,7 +1977,7 @@ static ssh2_userkey *sshcom_read( !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { type = RSA; } else if (str.len > sizeof(prefix_dsa) - 1 && - !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { + !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { type = DSA; } else { errmsg = "key is of unknown type"; @@ -2134,7 +2132,7 @@ static ssh2_userkey *sshcom_read( errmsg = NULL; /* no error */ ret = retkey; - error: + error: if (blob) { strbuf_free(blob); } @@ -2308,12 +2306,12 @@ static bool sshcom_write( } fprintf(fp, "%s\"\n", c); } - base64_encode(fp, outblob->u, outblob->len, 70); + base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 70); fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); fclose(fp); ret = true; - error: + error: if (outblob) strbuf_free(outblob); if (privblob) diff --git a/code/keygen/mpunsafe.c b/code/keygen/mpunsafe.c index 6265d40f..2cd7a37a 100644 --- a/code/keygen/mpunsafe.c +++ b/code/keygen/mpunsafe.c @@ -7,6 +7,7 @@ #include "puttymem.h" #include "mpint.h" +#include "mpunsafe.h" #include "crypto/mpint_i.h" /* diff --git a/code/ldisc.c b/code/ldisc.c index f0e59658..caff52d0 100644 --- a/code/ldisc.c +++ b/code/ldisc.c @@ -522,7 +522,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) } /* FALLTHROUGH */ default: /* get to this label from ^V handler */ - default_case: + default_case: sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen); ldisc->buf[ldisc->buflen++] = c; if (ECHOING) diff --git a/code/marshal.h b/code/marshal.h index 4d5b0075..b9136292 100644 --- a/code/marshal.h +++ b/code/marshal.h @@ -316,7 +316,7 @@ static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) #define get_err(src) (BinarySource_UPCAST(src)->err) #define get_avail(src) (BinarySource_UPCAST(src)->len - \ - BinarySource_UPCAST(src)->pos) + BinarySource_UPCAST(src)->pos) #define get_ptr(src) \ ((const void *)( \ (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ diff --git a/code/misc.h b/code/misc.h index dea7190b..1b3d324a 100644 --- a/code/misc.h +++ b/code/misc.h @@ -52,6 +52,10 @@ struct strbuf { strbuf *strbuf_new(void); strbuf *strbuf_new_nm(void); +/* Helpers to allocate a strbuf containing an existing string */ +strbuf *strbuf_dup(ptrlen string); +strbuf *strbuf_dup_nm(ptrlen string); + void strbuf_free(strbuf *buf); void *strbuf_append(strbuf *buf, size_t len); void strbuf_shrink_to(strbuf *buf, size_t new_len); @@ -68,9 +72,9 @@ void strbuf_finalise_agent_query(strbuf *buf); wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr, struct unicode_data *ucsdata); + const char *defchr); char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr, struct unicode_data *ucsdata); + const char *defchr); static inline int toint(unsigned u) { @@ -104,6 +108,20 @@ bool strendswith(const char *s, const char *t); void base64_encode_atom(const unsigned char *data, int n, char *out); int base64_decode_atom(const char *atom, unsigned char *out); +void base64_decode_bs(BinarySink *bs, ptrlen data); +void base64_decode_fp(FILE *fp, ptrlen data); +strbuf *base64_decode_sb(ptrlen data); +void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl); +void base64_encode_fp(FILE *fp, ptrlen data, int cpl); +strbuf *base64_encode_sb(ptrlen data, int cpl); +bool base64_valid(ptrlen data); + +void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars); +void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars); +strbuf *percent_encode_sb(ptrlen data, const char *badchars); +void percent_decode_bs(BinarySink *bs, ptrlen data); +void percent_decode_fp(FILE *fp, ptrlen data); +strbuf *percent_decode_sb(ptrlen data); struct bufchain_granule; struct bufchain_tag { @@ -158,6 +176,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len) return pl; } +static inline const void *ptrlen_end(ptrlen pl) +{ + return (const char *)pl.ptr + pl.len; +} + +static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv) +{ + const char *start = (const char *)startv, *end = (const char *)endv; + assert(end >= start); + ptrlen pl; + pl.ptr = start; + pl.len = end - start; + return pl; +} + static inline ptrlen ptrlen_from_asciz(const char *str) { return make_ptrlen(str, strlen(str)); @@ -179,6 +212,8 @@ int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); ptrlen ptrlen_get_word(ptrlen *input, const char *separators); +bool ptrlen_contains(ptrlen input, const char *characters); +bool ptrlen_contains_only(ptrlen input, const char *characters); char *mkstr(ptrlen pl); int string_length_for_printf(size_t); /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ @@ -197,6 +232,8 @@ int string_length_for_printf(size_t); /* Make a ptrlen out of a constant byte array. */ #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) +void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid); + /* Wipe sensitive data out of memory that's about to be freed. Simpler * than memset because we don't need the fill char parameter; also * attempts (by fiddly use of volatile) to inhibit the compiler from @@ -207,9 +244,9 @@ void smemclr(void *b, size_t len); /* Compare two fixed-length chunks of memory for equality, without * data-dependent control flow (so an attacker with a very accurate * stopwatch can't try to guess where the first mismatching byte was). - * Returns false for mismatch or true for equality (unlike memcmp), - * hinted at by the 'eq' in the name. */ -bool smemeq(const void *av, const void *bv, size_t len); + * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at + * by the 'eq' in the name. */ +unsigned smemeq(const void *av, const void *bv, size_t len); /* Encode a single UTF-8 character. Assumes that illegal characters * (such as things in the surrogate range, or > 0x10FFFF) have already @@ -472,4 +509,18 @@ static inline ptrlen ptrlen_from_lf(LoadedFile *lf) * is made to handle difficult overlap cases. */ void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size); +/* Boolean expressions used in OpenSSH certificate configuration */ +bool cert_expr_valid(const char *expression, + char **error_msg, ptrlen *error_loc); +bool cert_expr_match_str(const char *expression, + const char *hostname, unsigned port); +/* Build a certificate expression out of hostname wildcards. Required + * to handle legacy configuration from early in development, when + * multiple wildcards were stored separately in config, implicitly + * ORed together. */ +CertExprBuilder *cert_expr_builder_new(void); +void cert_expr_builder_free(CertExprBuilder *eb); +void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard); +char *cert_expr_expression(CertExprBuilder *eb); + #endif diff --git a/code/mpint.h b/code/mpint.h index ae09a24f..4ddc0e64 100644 --- a/code/mpint.h +++ b/code/mpint.h @@ -42,6 +42,13 @@ mp_int *mp_new(size_t maxbits); void mp_free(mp_int *); void mp_clear(mp_int *x); +/* + * Resize the physical size of existing mp_int, e.g. so that you have + * room to transform it in place to a larger value. Destroys the old + * mp_int in the process. + */ +mp_int *mp_resize(mp_int *, size_t newmaxbits); + /* * Create mp_ints from various sources: little- and big-endian binary * data, an ordinary C unsigned integer type, a decimal or hex string diff --git a/code/network.h b/code/network.h index 4c0b0332..57e6662d 100644 --- a/code/network.h +++ b/code/network.h @@ -397,8 +397,10 @@ void backend_socket_log(Seat *seat, LogContext *logctx, typedef struct ProxyStderrBuf { char buf[8192]; size_t size; + const char *prefix; /* must be statically allocated */ } ProxyStderrBuf; void psb_init(ProxyStderrBuf *psb); +void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix); void log_proxy_stderr( Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); @@ -429,4 +431,6 @@ struct DeferredSocketOpenerVtable { static inline void deferred_socket_opener_free(DeferredSocketOpener *dso) { dso->vt->free(dso); } +DeferredSocketOpener *null_deferred_socket_opener(void); + #endif diff --git a/code/otherbackends/supdup.c b/code/otherbackends/supdup.c index a272b256..9aaf1d4f 100644 --- a/code/otherbackends/supdup.c +++ b/code/otherbackends/supdup.c @@ -1,6 +1,6 @@ /* -* Supdup backend -*/ + * Supdup backend + */ #include #include @@ -10,7 +10,7 @@ /* * TTYOPT FUNCTION BITS (36-bit bitmasks) -*/ + */ #define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input #define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case #define TOERS 0040000000000LL // Selective erase is supported @@ -202,49 +202,49 @@ static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) supdup->td_argindex = 0; supdup->td_code = c; switch (c) { - case TDMOV: + case TDMOV: // %TD codes using 4 arguments supdup->td_argcount = 4; supdup->tdstate = TD_ARGS; break; - case TDMV0: - case TDMV1: + case TDMV0: + case TDMV1: // %TD codes using 2 arguments supdup->td_argcount = 2; supdup->tdstate = TD_ARGS; break; - case TDQOT: - case TDILP: - case TDDLP: - case TDICP: - case TDDCP: + case TDQOT: + case TDILP: + case TDDLP: + case TDICP: + case TDDCP: // %TD codes using 1 argument supdup->td_argcount = 1; supdup->tdstate = TD_ARGS; break; - case TDEOF: - case TDEOL: - case TDDLF: - case TDCRL: - case TDNOP: - case TDORS: - case TDFS: - case TDCLR: - case TDBEL: - case TDBOW: - case TDRST: - case TDBS: - case TDCR: - case TDLF: + case TDEOF: + case TDEOL: + case TDDLF: + case TDCRL: + case TDNOP: + case TDORS: + case TDFS: + case TDCLR: + case TDBEL: + case TDBOW: + case TDRST: + case TDBS: + case TDCR: + case TDLF: // %TD codes using 0 arguments supdup->td_argcount = 0; supdup->tdstate = TD_ARGSDONE; break; - default: + default: // Unhandled, ignore break; } @@ -279,7 +279,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) // Arguments for %TD code have been collected; dispatch based // on the %TD code we're handling. switch (supdup->td_code) { - case TDMOV: + case TDMOV: /* General cursor position code. Followed by four bytes; the first two are the "old" vertical and horizontal @@ -292,8 +292,8 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); break; - case TDMV0: - case TDMV1: + case TDMV0: + case TDMV1: /* General cursor position code. Followed by two bytes; the new vertical and horizontal positions. @@ -301,7 +301,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); break; - case TDEOF: + case TDEOF: /* Erase to end of screen. This is an optional function since many terminals do not support this. If the @@ -315,7 +315,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[J"); break; - case TDEOL: + case TDEOL: /* Erase to end of line. This erases the character position the cursor is at and all positions to the right @@ -324,7 +324,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[K"); break; - case TDDLF: + case TDDLF: /* Clear the character position the cursor is on. The cursor does not move. @@ -332,7 +332,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[X"); break; - case TDCRL: + case TDCRL: /* If the cursor is not on the bottom line of the screen, move cursor to the beginning of the next line and clear @@ -342,13 +342,13 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\015\012"); break; - case TDNOP: + case TDNOP: /* No-op; should be ignored. */ break; - case TDORS: + case TDORS: /* Output reset. This code serves as a data mark for aborting output much as IAC DM does in the ordinary @@ -364,7 +364,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) sk_write(supdup->s, buf, 4); break; - case TDQOT: + case TDQOT: /* Quotes the following character. This is used when sending 8-bit codes which are not %TD codes, for @@ -376,7 +376,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_byte(outbuf, supdup->td_args[0]); break; - case TDFS: + case TDFS: /* Non-destructive forward space. The cursor moves right one position; this code will not be sent at the end of a @@ -386,7 +386,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[C"); break; - case TDCLR: + case TDCLR: /* Erase the screen. Home the cursor to the top left hand corner of the screen. @@ -394,7 +394,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[2J\033[H"); break; - case TDBEL: + case TDBEL: /* Generate an audio tone, bell, whatever. */ @@ -402,7 +402,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\007"); break; - case TDILP: + case TDILP: /* Insert blank lines at the cursor; followed by a byte containing a count of the number of blank lines to @@ -413,7 +413,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dL", supdup->td_args[0]); break; - case TDDLP: + case TDDLP: /* Delete lines at the cursor; followed by a count. The cursor is unmoved. The first line deleted is the one @@ -424,7 +424,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dM", supdup->td_args[0]); break; - case TDICP: + case TDICP: /* Insert blank character positions at the cursor; followed by a count. The cursor is unmoved. The character the @@ -435,7 +435,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d@", supdup->td_args[0]); break; - case TDDCP: + case TDDCP: /* Delete characters at the cursor; followed by a count. The cursor is unmoved. The first character deleted is @@ -445,8 +445,8 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dP", supdup->td_args[0]); break; - case TDBOW: - case TDRST: + case TDBOW: + case TDRST: /* Display black characters on white screen. HIGHLY OPTIONAL. @@ -463,7 +463,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) * official documentation, behavior is based on UNIX * SUPDUP implementation from MIT. */ - case TDBS: + case TDBS: /* * Backspace -- move cursor back one character (does not * appear to wrap...) @@ -471,14 +471,14 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_byte(outbuf, '\010'); break; - case TDLF: + case TDLF: /* * Linefeed -- move cursor down one line (again, no wrapping) */ put_byte(outbuf, '\012'); break; - case TDCR: + case TDCR: /* * Carriage return -- move cursor to start of current line. */ @@ -512,7 +512,7 @@ static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) while (len--) { int c = (unsigned char)*buf++; switch (supdup->state) { - case CONNECTING: + case CONNECTING: // "Following the transmission of the terminal options by // the user, the server should respond with an ASCII // greeting message, terminated with a %TDNOP code..." @@ -528,7 +528,7 @@ static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) } break; - case CONNECTED: + case CONNECTED: // "All transmissions from the server after the %TDNOP // [see above] are either printing characters or virtual // terminal display codes." Forward these on to the @@ -671,13 +671,13 @@ static const InteractorVtable Supdup_interactorvt = { }; /* -* Called to set up the Supdup connection. -* -* Returns an error message, or NULL on success. -* -* Also places the canonical host name into `realhost'. It must be -* freed by the caller. -*/ + * Called to set up the Supdup connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ static char *supdup_init(const BackendVtable *x, Seat *seat, Backend **backend_handle, LogContext *logctx, Conf *conf, @@ -717,15 +717,15 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, *backend_handle = &supdup->backend; switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { - case SUPDUP_CHARSET_ASCII: - supdup->print = print_ascii; - break; - case SUPDUP_CHARSET_ITS: - supdup->print = print_its; - break; - case SUPDUP_CHARSET_WAITS: - supdup->print = print_waits; - break; + case SUPDUP_CHARSET_ASCII: + supdup->print = print_ascii; + break; + case SUPDUP_CHARSET_ITS: + supdup->print = print_its; + break; + case SUPDUP_CHARSET_WAITS: + supdup->print = print_waits; + break; } /* @@ -837,16 +837,16 @@ static void supdup_free(Backend *be) } /* -* Reconfigure the Supdup backend. -*/ + * Reconfigure the Supdup backend. + */ static void supdup_reconfig(Backend *be, Conf *conf) { /* Nothing to do; SUPDUP cannot be reconfigured while running. */ } /* -* Called to send data down the Supdup connection. -*/ + * Called to send data down the Supdup connection. + */ static void supdup_send(Backend *be, const char *buf, size_t len) { Supdup *supdup = container_of(be, Supdup, backend); @@ -867,8 +867,8 @@ static void supdup_send(Backend *be, const char *buf, size_t len) } /* -* Called to query the current socket sendability status. -*/ + * Called to query the current socket sendability status. + */ static size_t supdup_sendbuffer(Backend *be) { Supdup *supdup = container_of(be, Supdup, backend); @@ -876,8 +876,8 @@ static size_t supdup_sendbuffer(Backend *be) } /* -* Called to set the size of the window from Supdup's POV. -*/ + * Called to set the size of the window from Supdup's POV. + */ static void supdup_size(Backend *be, int width, int height) { Supdup *supdup = container_of(be, Supdup, backend); @@ -892,8 +892,8 @@ static void supdup_size(Backend *be, int width, int height) } /* -* Send Telnet special codes. -*/ + * Send Telnet special codes. + */ static void supdup_special(Backend *be, SessionSpecialCode code, int arg) { } @@ -946,8 +946,8 @@ static int supdup_exitcode(Backend *be) } /* -* cfg_info for Dupdup does nothing at all. -*/ + * cfg_info for Supdup does nothing at all. + */ static int supdup_cfg_info(Backend *be) { return 0; diff --git a/code/otherbackends/telnet.c b/code/otherbackends/telnet.c index 1c0f5d68..23b7fc9e 100644 --- a/code/otherbackends/telnet.c +++ b/code/otherbackends/telnet.c @@ -191,7 +191,7 @@ struct Telnet { enum { TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR } state; Conf *conf; @@ -448,11 +448,11 @@ static void process_subneg(Telnet *telnet) } bsize = 20; for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) - bsize += strlen(ekey) + strlen(eval) + 2; + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; user = get_remote_username(telnet->conf); if (user) bsize += 6 + strlen(user); @@ -464,10 +464,10 @@ static void process_subneg(Telnet *telnet) b[3] = TELQUAL_IS; n = 4; for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { + ekey, &ekey)) { b[n++] = var; for (e = ekey; *e; e++) b[n++] = *e; @@ -496,10 +496,10 @@ static void process_subneg(Telnet *telnet) logeventf(telnet->logctx, "client subnegotiation: SB %s IS:", telopt(telnet->sb_opt)); for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { + ekey, &ekey)) { logeventf(telnet->logctx, " %s=%s", ekey, eval); } if (user) diff --git a/code/pageant.c b/code/pageant.c index c555fba2..4c3018f2 100644 --- a/code/pageant.c +++ b/code/pageant.c @@ -36,8 +36,10 @@ struct PageantClientDialogId { int dummy; }; -typedef struct PageantKeySort PageantKeySort; -typedef struct PageantKey PageantKey; +typedef struct PageantPrivateKeySort PageantPrivateKeySort; +typedef struct PageantPublicKeySort PageantPublicKeySort; +typedef struct PageantPrivateKey PageantPrivateKey; +typedef struct PageantPublicKey PageantPublicKey; typedef struct PageantAsyncOp PageantAsyncOp; typedef struct PageantAsyncOpVtable PageantAsyncOpVtable; typedef struct PageantClientRequestNode PageantClientRequestNode; @@ -88,33 +90,106 @@ static void pageant_async_op_callback(void *vctx) } /* - * Master list of all the keys we have stored, in any form at all. + * Master lists of all the keys we have stored, in any form at all. + * + * We store private and public keys in separate lists, because + * multiple public keys can share the same private key (due to one + * having a certificate and the other not, or having more than one + * different certificate). And when we decrypt or re-encrypt a private + * key, we don't really want to faff about doing it multiple times if + * there's more than one public key it goes with. If someone tries to + * re-encrypt a key to make their machine safer against unattended + * access, then it would be embarrassing to find they'd forgotten to + * re-encrypt the _other_ copy of it; conversely, once you've + * decrypted a key, it's pointless to make someone type yet another + * passphrase. + * + * (Causing multiple keys to become decrypted in one go isn't a + * security hole in its own right, because the signatures generated by + * certified and uncertified keys are identical. So an attacker + * gaining access to an agent containing one encrypted and one + * cleartext key with the same private half would still be *able* to + * generate signatures that went with the encrypted one, even if the + * agent refused to hand them out in response to the most obvious kind + * of request.) */ -static tree234 *keytree; -struct PageantKeySort { - /* Prefix of the main PageantKey structure which contains all the - * data that the sorting order depends on. Also simple enough that - * you can construct one for lookup purposes. */ +struct PageantPrivateKeySort { + /* + * Information used by the sorting criterion for the private key + * tree. + */ int ssh_version; /* 1 or 2; primary sort key */ - ptrlen public_blob; /* secondary sort key */ + ptrlen base_pub; /* secondary sort key; never includes a certificate */ }; -struct PageantKey { - PageantKeySort sort; - strbuf *public_blob; /* the true owner of sort.public_blob */ - char *comment; /* stored separately, whether or not in rkey/skey */ +static int privkey_cmpfn(void *av, void *bv) +{ + PageantPrivateKeySort *a = (PageantPrivateKeySort *)av; + PageantPrivateKeySort *b = (PageantPrivateKeySort *)bv; + + if (a->ssh_version != b->ssh_version) + return a->ssh_version < b->ssh_version ? -1 : +1; + else + return ptrlen_strcmp(a->base_pub, b->base_pub); +} + +struct PageantPublicKeySort { + /* + * Information used by the sorting criterion for the public key + * tree. Begins with the private key sorting criterion, so that + * all the public keys sharing a private key appear adjacent in + * the tree. That's a reasonably sensible order to list them in + * for the user, and more importantly, it makes it easy to + * discover when we're deleting the last public key that goes with + * a particular private one, so as to delete that too. Easier than + * messing about with fragile reference counts. + */ + PageantPrivateKeySort priv; + ptrlen full_pub; /* may match priv.base_pub, or may include a cert */ +}; +static int pubkey_cmpfn(void *av, void *bv) +{ + PageantPublicKeySort *a = (PageantPublicKeySort *)av; + PageantPublicKeySort *b = (PageantPublicKeySort *)bv; + + int c = privkey_cmpfn(&a->priv, &b->priv); + if (c) + return c; + else + return ptrlen_strcmp(a->full_pub, b->full_pub); +} + +struct PageantPrivateKey { + PageantPrivateKeySort sort; + strbuf *base_pub; /* the true owner of sort.base_pub */ union { - RSAKey *rkey; /* if ssh_version == 1 */ - ssh2_userkey *skey; /* if ssh_version == 2 */ + RSAKey *rkey; /* if sort.priv.ssh_version == 1 */ + ssh_key *skey; /* if sort.priv.ssh_version == 2 */ }; strbuf *encrypted_key_file; + /* encrypted_key_comment stores the comment belonging to the + * encrypted key file. This is used when presenting deferred + * decryption prompts, because if the user had encrypted their + * uncert and cert keys with different passphrases, the passphrase + * prompt must reliably signal which file they're supposed to be + * entering the passphrase for. */ + char *encrypted_key_comment; bool decryption_prompt_active; PageantKeyRequestNode blocked_requests; PageantClientDialogId dlgid; }; +static tree234 *privkeytree; + +struct PageantPublicKey { + PageantPublicKeySort sort; + strbuf *base_pub; /* the true owner of sort.priv.base_pub */ + strbuf *full_pub; /* the true owner of sort.full_pub */ + char *comment; +}; +static tree234 *pubkeytree; typedef struct PageantSignOp PageantSignOp; struct PageantSignOp { - PageantKey *pk; + PageantPrivateKey *priv; strbuf *data_to_sign; unsigned flags; int crLine; @@ -127,47 +202,40 @@ struct PageantSignOp { /* Master lock that indicates whether a GUI request is currently in * progress */ static bool gui_request_in_progress = false; +static PageantKeyRequestNode requests_blocked_on_gui = + { &requests_blocked_on_gui, &requests_blocked_on_gui }; static void failure(PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...); -static void fail_requests_for_key(PageantKey *pk, const char *reason); -static PageantKey *pageant_nth_key(int ssh_version, int i); +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason); +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i); -static void pk_free(PageantKey *pk) +static void pk_priv_free(PageantPrivateKey *priv) { - if (pk->public_blob) strbuf_free(pk->public_blob); - sfree(pk->comment); - if (pk->sort.ssh_version == 1 && pk->rkey) { - freersakey(pk->rkey); - sfree(pk->rkey); + if (priv->base_pub) + strbuf_free(priv->base_pub); + if (priv->sort.ssh_version == 1 && priv->rkey) { + freersakey(priv->rkey); + sfree(priv->rkey); } - if (pk->sort.ssh_version == 2 && pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); + if (priv->sort.ssh_version == 2 && priv->skey) { + ssh_key_free(priv->skey); } - if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file); - fail_requests_for_key(pk, "key deleted from Pageant while signing " + if (priv->encrypted_key_file) + strbuf_free(priv->encrypted_key_file); + if (priv->encrypted_key_comment) + sfree(priv->encrypted_key_comment); + fail_requests_for_key(priv, "key deleted from Pageant while signing " "request was pending"); - sfree(pk); -} - -static int cmpkeys(void *av, void *bv) -{ - PageantKeySort *a = (PageantKeySort *)av, *b = (PageantKeySort *)bv; - - if (a->ssh_version != b->ssh_version) - return a->ssh_version < b->ssh_version ? -1 : +1; - else - return ptrlen_strcmp(a->public_blob, b->public_blob); + sfree(priv); } -static inline PageantKeySort keysort(int version, ptrlen blob) +static void pk_pub_free(PageantPublicKey *pub) { - PageantKeySort sort; - sort.ssh_version = version; - sort.public_blob = blob; - return sort; + if (pub->full_pub) + strbuf_free(pub->full_pub); + sfree(pub->comment); + sfree(pub); } static strbuf *makeblob1(RSAKey *rkey) @@ -178,126 +246,317 @@ static strbuf *makeblob1(RSAKey *rkey) return blob; } -static strbuf *makeblob2(ssh2_userkey *skey) +static strbuf *makeblob2full(ssh_key *key) { strbuf *blob = strbuf_new(); - ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob)); + ssh_key_public_blob(key, BinarySink_UPCAST(blob)); return blob; } -static PageantKey *findkey1(RSAKey *reqkey) +static strbuf *makeblob2base(ssh_key *key) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(blob)); + return blob; +} + +static PageantPrivateKey *pub_to_priv(PageantPublicKey *pub) +{ + PageantPrivateKey *priv = find234(privkeytree, &pub->sort.priv, NULL); + assert(priv && "Public and private trees out of sync!"); + return priv; +} + +static PageantPublicKey *findpubkey1(RSAKey *reqkey) { strbuf *blob = makeblob1(reqkey); - PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); - PageantKey *toret = find234(keytree, &sort, NULL); + PageantPublicKeySort sort; + sort.priv.ssh_version = 1; + sort.priv.base_pub = ptrlen_from_strbuf(blob); + sort.full_pub = ptrlen_from_strbuf(blob); + PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); strbuf_free(blob); return toret; } -static PageantKey *findkey2(ptrlen blob) +/* + * Constructs the base_pub element of a PageantPublicKeySort, starting + * from full_pub. This may involve allocating a strbuf to store it in, + * which must survive until after you've finished using the resulting + * PageantPublicKeySort. Hence, the strbuf (if any) is returned from + * this function, and if it's non-NULL then the caller must eventually + * free it. + */ +static strbuf *make_base_pub_2(PageantPublicKeySort *sort) +{ + /* Start with the fallback option of making base_pub equal full_pub */ + sort->priv.base_pub = sort->full_pub; + + /* Now reconstruct a distinct base_pub without a cert, if possible + * and necessary */ + strbuf *base_pub = NULL; + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, sort->full_pub); + ptrlen algname = get_string(src); + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + if (alg && alg->is_certificate) { + ssh_key *key = ssh_key_new_pub(alg, sort->full_pub); + if (key) { + base_pub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), + BinarySink_UPCAST(base_pub)); + sort->priv.base_pub = ptrlen_from_strbuf(base_pub); + ssh_key_free(key); + } + } + + return base_pub; /* caller must free once they're done with sort */ +} + +static PageantPublicKey *findpubkey2(ptrlen full_pub) { - PageantKeySort sort = keysort(2, blob); - return find234(keytree, &sort, NULL); + PageantPublicKeySort sort; + sort.priv.ssh_version = 2; + sort.full_pub = full_pub; + strbuf *base_pub = make_base_pub_2(&sort); + PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); + if (base_pub) + strbuf_free(base_pub); + return toret; } -static int find_first_key_for_version(int ssh_version) +static int find_first_pubkey_for_version(int ssh_version) { - PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL("")); + PageantPublicKeySort sort; + sort.priv.ssh_version = ssh_version; + sort.priv.base_pub = PTRLEN_LITERAL(""); + sort.full_pub = PTRLEN_LITERAL(""); int pos; - if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos)) + if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos)) return pos; - return count234(keytree); + return count234(pubkeytree); } static int count_keys(int ssh_version) { - return (find_first_key_for_version(ssh_version + 1) - - find_first_key_for_version(ssh_version)); + return (find_first_pubkey_for_version(ssh_version + 1) - + find_first_pubkey_for_version(ssh_version)); } int pageant_count_ssh1_keys(void) { return count_keys(1); } int pageant_count_ssh2_keys(void) { return count_keys(2); } -static bool pageant_add_ssh1_key(RSAKey *rkey) +/* + * Common code to add a key to the trees. We fill in as many fields + * here as we can share between SSH versions: the ptrlens in the + * sorting field, the whole of pub->sort.priv, and the linked list of + * blocked requests. + */ +static bool pageant_add_key_common(PageantPublicKey *pub, + PageantPrivateKey *priv) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 1; - pk->public_blob = makeblob1(rkey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - - if (add234(keytree, pk) == pk) { - pk->rkey = rkey; - if (rkey->comment) - pk->comment = dupstr(rkey->comment); + int ssh_version = priv->sort.ssh_version; + + priv->sort.base_pub = ptrlen_from_strbuf(priv->base_pub); + + pub->base_pub = strbuf_dup(priv->sort.base_pub); + pub->sort.priv.ssh_version = priv->sort.ssh_version; + pub->sort.priv.base_pub = ptrlen_from_strbuf(pub->base_pub); + pub->sort.full_pub = ptrlen_from_strbuf(pub->full_pub); + priv->blocked_requests.next = priv->blocked_requests.prev = + &priv->blocked_requests; + + /* + * Try to add the private key to privkeytree, or combine new parts + * of it with what's already there. + */ + PageantPrivateKey *priv_in_tree = add234(privkeytree, priv); + if (priv_in_tree == priv) { + /* The key wasn't in the tree at all, and we've just added it. */ + } else { + /* The key was already in the tree, so we'll be freeing priv. */ + + if (ssh_version == 2 && priv->skey && !priv_in_tree->skey) { + /* The key was only stored encrypted, and now we have an + * unencrypted version to add to the existing record. */ + priv_in_tree->skey = priv->skey; + priv->skey = NULL; /* so pk_priv_free won't free it */ + } + + if (ssh_version == 2 && priv->encrypted_key_file && + !priv_in_tree->encrypted_key_file) { + /* Conversely, the key was only stored in clear, and now + * we have an encrypted version to add to it. */ + priv_in_tree->encrypted_key_file = priv->encrypted_key_file; + priv->encrypted_key_file = NULL; + priv_in_tree->encrypted_key_comment = priv->encrypted_key_comment; + priv->encrypted_key_comment = NULL; + } + + pk_priv_free(priv); + } + + /* + * Try to add the public key. + */ + PageantPublicKey *pub_in_tree = add234(pubkeytree, pub); + if (pub_in_tree == pub) { + /* Successfully added a new key. */ return true; } else { - pk_free(pk); + /* This public key was already there. */ + pk_pub_free(pub); return false; } } +static bool pageant_add_ssh1_key(RSAKey *rkey) +{ + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); + + priv->sort.ssh_version = 1; + priv->base_pub = makeblob1(rkey); + pub->full_pub = makeblob1(rkey); + + if (rkey->comment) + pub->comment = dupstr(rkey->comment); + + priv->rkey = snew(RSAKey); + duprsakey(priv->rkey, rkey); + + return pageant_add_key_common(pub, priv); +} + static bool pageant_add_ssh2_key(ssh2_userkey *skey) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 2; - pk->public_blob = makeblob2(skey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); - PageantKey *pk_in_tree = add234(keytree, pk); - if (pk_in_tree == pk) { - /* The key wasn't in the tree at all, and we've just added it. */ - pk->skey = skey; - if (skey->comment) - pk->comment = dupstr(skey->comment); - return true; - } else if (!pk_in_tree->skey) { - /* The key was only stored encrypted, and now we have an - * unencrypted version to add to the existing record. */ - pk_in_tree->skey = skey; - pk_free(pk); - return true; + priv->sort.ssh_version = 2; + priv->base_pub = makeblob2base(skey->key); + pub->full_pub = makeblob2full(skey->key); + + if (skey->comment) + pub->comment = dupstr(skey->comment); + + /* Duplicate the ssh_key to go in priv */ +#ifdef PUTTY_CAC + if (cert_is_certpath(skey->comment)) + { + ssh2_userkey * userkey = cert_load_key(skey->comment); + priv->skey = userkey->key; + priv->encrypted_key_comment = userkey->comment; + sfree(userkey); + } else +#endif // PUTTY_CAC + { + strbuf *tmp = strbuf_new_nm(); + ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(tmp)); + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(tmp)); + priv->skey = ssh_key_new_priv_openssh(ssh_key_alg(skey->key), src); + strbuf_free(tmp); + } + + return pageant_add_key_common(pub, priv); +} + +static bool pageant_add_ssh2_key_encrypted(PageantPublicKeySort sort, + const char *comment, ptrlen keyfile) +{ + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); + + assert(sort.priv.ssh_version == 2); + priv->sort.ssh_version = sort.priv.ssh_version; + priv->base_pub = strbuf_dup(sort.priv.base_pub); + pub->full_pub = strbuf_dup(sort.full_pub); + + pub->comment = dupstr(comment); + + priv->encrypted_key_file = strbuf_dup_nm(keyfile); + priv->encrypted_key_comment = dupstr(comment); + + return pageant_add_key_common(pub, priv); +} + +static void remove_pubkey_cleanup(PageantPublicKey *pub) +{ + /* Common function called when we've just removed a public key + * from pubkeytree: we must also check whether that was the last + * public key sharing a private half, and if so, remove the + * corresponding private entry too. */ + + PageantPublicKeySort pubsearch; + pubsearch.priv = pub->sort.priv; + pubsearch.full_pub = PTRLEN_LITERAL(""); + PageantPublicKey *pubfound = findrel234( + pubkeytree, &pubsearch, NULL, REL234_GE); + + if (pubfound && !privkey_cmpfn(&pub->sort.priv, &pubfound->sort.priv)) { + /* There's still a public key which has the same sort.priv as + * the one we've just removed. We're good. */ } else { - /* The key was already in the tree in full. */ - pk_free(pk); - return false; + /* We've just removed the last public key of the family, so + * delete the private half as well. */ + PageantPrivateKey *priv = del234(privkeytree, &pub->sort.priv); + assert(priv); + assert(!privkey_cmpfn(&priv->sort, &pub->sort.priv)); + pk_priv_free(priv); } } +static PageantPublicKey *del_pubkey_pos(int pos) +{ + PageantPublicKey *deleted = delpos234(pubkeytree, pos); + remove_pubkey_cleanup(deleted); + return deleted; +} + +static void del_pubkey(PageantPublicKey *to_delete) +{ + PageantPublicKey *deleted = del234(pubkeytree, to_delete); + remove_pubkey_cleanup(deleted); +} + static void remove_all_keys(int ssh_version) { - int start = find_first_key_for_version(ssh_version); - int end = find_first_key_for_version(ssh_version + 1); + int start = find_first_pubkey_for_version(ssh_version); + int end = find_first_pubkey_for_version(ssh_version + 1); while (end > start) { - PageantKey *pk = delpos234(keytree, --end); - assert(pk->sort.ssh_version == ssh_version); - pk_free(pk); + PageantPublicKey *pub = del_pubkey_pos(--end); + assert(pub->sort.priv.ssh_version == ssh_version); + pk_pub_free(pub); } } static void list_keys(BinarySink *bs, int ssh_version, bool extended) { int i; - PageantKey *pk; + PageantPublicKey *pub; put_uint32(bs, count_keys(ssh_version)); - for (i = find_first_key_for_version(ssh_version); - NULL != (pk = index234(keytree, i)); i++) { - if (pk->sort.ssh_version != ssh_version) + for (i = find_first_pubkey_for_version(ssh_version); + NULL != (pub = index234(pubkeytree, i)); i++) { + if (pub->sort.priv.ssh_version != ssh_version) break; if (ssh_version > 1) - put_stringpl(bs, pk->sort.public_blob); + put_stringpl(bs, pub->sort.full_pub); else - put_datapl(bs, pk->sort.public_blob); /* no header */ + put_datapl(bs, pub->sort.full_pub); /* no header */ - put_stringpl(bs, ptrlen_from_asciz(pk->comment)); + put_stringpl(bs, ptrlen_from_asciz(pub->comment)); if (extended) { + assert(ssh_version == 2); /* extended lists not supported in v1 */ + /* * Append to each key entry a string containing extension * data. This string begins with a flags word, and may in @@ -306,12 +565,14 @@ static void list_keys(BinarySink *bs, int ssh_version, bool extended) * string, so that clients that only partially understand * it can still find the parts they do understand. */ + PageantPrivateKey *priv = pub_to_priv(pub); + strbuf *sb = strbuf_new(); uint32_t flags = 0; - if (!pk->skey) + if (!priv->skey) flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; - if (pk->encrypted_key_file) + if (priv->encrypted_key_file) flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; put_uint32(sb, flags); @@ -362,13 +623,24 @@ static PRINTF_LIKE(5, 6) void failure( } } -static void signop_link(PageantSignOp *so) +static void signop_link_to_key(PageantSignOp *so) +{ + assert(!so->pkr.prev); + assert(!so->pkr.next); + + so->pkr.prev = so->priv->blocked_requests.prev; + so->pkr.next = &so->priv->blocked_requests; + so->pkr.prev->next = &so->pkr; + so->pkr.next->prev = &so->pkr; +} + +static void signop_link_to_pending_gui_request(PageantSignOp *so) { assert(!so->pkr.prev); assert(!so->pkr.next); - so->pkr.prev = so->pk->blocked_requests.prev; - so->pkr.next = &so->pk->blocked_requests; + so->pkr.prev = requests_blocked_on_gui.prev; + so->pkr.next = &requests_blocked_on_gui; so->pkr.prev->next = &so->pkr; so->pkr.next->prev = &so->pkr; } @@ -379,6 +651,7 @@ static void signop_unlink(PageantSignOp *so) assert(so->pkr.prev); so->pkr.next->prev = so->pkr.prev; so->pkr.prev->next = so->pkr.next; + so->pkr.prev = so->pkr.next = NULL; } else { assert(!so->pkr.prev); } @@ -391,19 +664,19 @@ static void signop_free(PageantAsyncOp *pao) sfree(so); } -static bool request_passphrase(PageantClient *pc, PageantKey *pk) +static bool request_passphrase(PageantClient *pc, PageantPrivateKey *priv) { - if (!pk->decryption_prompt_active) { + if (!priv->decryption_prompt_active) { assert(!gui_request_in_progress); bool created_dlg = pageant_client_ask_passphrase( - pc, &pk->dlgid, pk->comment); + pc, &priv->dlgid, priv->encrypted_key_comment); if (!created_dlg) return false; gui_request_in_progress = true; - pk->decryption_prompt_active = true; + priv->decryption_prompt_active = true; } return true; @@ -416,13 +689,16 @@ static void signop_coroutine(PageantAsyncOp *pao) crBegin(so->crLine); - while (!so->pk->skey && gui_request_in_progress) + while (!so->priv->skey && gui_request_in_progress) { + signop_link_to_pending_gui_request(so); crReturnV; + signop_unlink(so); + } - if (!so->pk->skey) { - assert(so->pk->encrypted_key_file); + if (!so->priv->skey) { + assert(so->priv->encrypted_key_file); - if (!request_passphrase(so->pao.info->pc, so->pk)) { + if (!request_passphrase(so->pao.info->pc, so->priv)) { response = strbuf_new(); failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, "on-demand decryption could not " @@ -430,12 +706,12 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - signop_link(so); + signop_link_to_key(so); crReturnV; signop_unlink(so); } - uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags; + uint32_t supported_flags = ssh_key_supported_flags(so->priv->skey); if (so->flags & ~supported_flags) { /* * We MUST reject any message containing flags we don't @@ -448,7 +724,7 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags); + char *invalid = ssh_key_invalid(so->priv->skey, so->flags); if (invalid) { response = strbuf_new(); failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, @@ -459,13 +735,17 @@ static void signop_coroutine(PageantAsyncOp *pao) strbuf *signature = strbuf_new(); #ifdef PUTTY_CAC - if (cert_is_certpath(so->pk->comment)) + if (cert_is_certpath(so->priv->encrypted_key_comment)) { - cert_sign(so->pk->skey, (LPCBYTE)so->data_to_sign->u, so->data_to_sign->len, so->flags, signature); + ssh2_userkey* newkey = cert_load_key(so->priv->encrypted_key_comment); + cert_sign(newkey, (LPCBYTE)so->data_to_sign->u, so->data_to_sign->len, so->flags, signature); + newkey->key->vt->freekey(newkey->key); + sfree(newkey->comment); + sfree(newkey); } else #endif // PUTTY_CAC - ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign), + ssh_key_sign(so->priv->skey, ptrlen_from_strbuf(so->data_to_sign), so->flags, BinarySink_UPCAST(signature)); response = strbuf_new(); @@ -486,10 +766,10 @@ static const PageantAsyncOpVtable signop_vtable = { .free = signop_free, }; -static void fail_requests_for_key(PageantKey *pk, const char *reason) +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason) { - while (pk->blocked_requests.next != &pk->blocked_requests) { - PageantSignOp *so = container_of(pk->blocked_requests.next, + while (priv->blocked_requests.next != &priv->blocked_requests) { + PageantSignOp *so = container_of(priv->blocked_requests.next, PageantSignOp, pkr); signop_unlink(so); strbuf *sb = strbuf_new(); @@ -502,12 +782,20 @@ static void fail_requests_for_key(PageantKey *pk, const char *reason) } } -static void unblock_requests_for_key(PageantKey *pk) +static void unblock_requests_for_key(PageantPrivateKey *priv) { - for (PageantKeyRequestNode *pkr = pk->blocked_requests.next; - pkr != &pk->blocked_requests; pkr = pkr->next) { - PageantSignOp *so = container_of(pk->blocked_requests.next, - PageantSignOp, pkr); + for (PageantKeyRequestNode *pkr = priv->blocked_requests.next; + pkr != &priv->blocked_requests; pkr = pkr->next) { + PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); + queue_toplevel_callback(pageant_async_op_callback, &so->pao); + } +} + +static void unblock_pending_gui_requests(void) +{ + for (PageantKeyRequestNode *pkr = requests_blocked_on_gui.next; + pkr != &requests_blocked_on_gui; pkr = pkr->next) { + PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); queue_toplevel_callback(pageant_async_op_callback, &so->pao); } } @@ -515,38 +803,33 @@ static void unblock_requests_for_key(PageantKey *pk) void pageant_passphrase_request_success(PageantClientDialogId *dlgid, ptrlen passphrase) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; - if (!pk->skey) { + if (!priv->skey) { const char *error; BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - pk->encrypted_key_file)); - - strbuf *ppsb = strbuf_new_nm(); - put_datapl(ppsb, passphrase); - - pk->skey = ppk_load_s(src, ppsb->s, &error); + priv->encrypted_key_file)); + strbuf *ppsb = strbuf_dup_nm(passphrase); + ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error); strbuf_free(ppsb); - if (!pk->skey) { - fail_requests_for_key(pk, "unable to decrypt key"); + if (!skey) { + fail_requests_for_key(priv, "unable to decrypt key"); return; - } else if (pk->skey == SSH2_WRONG_PASSPHRASE) { - pk->skey = NULL; - + } else if (skey == SSH2_WRONG_PASSPHRASE) { /* * Find a PageantClient to use for another attempt at * request_passphrase. */ - PageantKeyRequestNode *pkr = pk->blocked_requests.next; - if (pkr == &pk->blocked_requests) { + PageantKeyRequestNode *pkr = priv->blocked_requests.next; + if (pkr == &priv->blocked_requests) { /* * Special case: if all the requests have gone away at * this point, we need not bother putting up a request @@ -555,32 +838,39 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid, return; } - PageantSignOp *so = container_of(pk->blocked_requests.next, + PageantSignOp *so = container_of(priv->blocked_requests.next, PageantSignOp, pkr); - pk->decryption_prompt_active = false; - if (!request_passphrase(so->pao.info->pc, pk)) { - fail_requests_for_key(pk, "unable to continue creating " + priv->decryption_prompt_active = false; + if (!request_passphrase(so->pao.info->pc, so->priv)) { + fail_requests_for_key(priv, "unable to continue creating " "passphrase prompts"); } return; } else { + priv->skey = skey->key; + sfree(skey->comment); + sfree(skey); keylist_update(); } } - unblock_requests_for_key(pk); + unblock_requests_for_key(priv); + + unblock_pending_gui_requests(); } void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; + + fail_requests_for_key(priv, "user refused to supply passphrase"); - fail_requests_for_key(pk, "user refused to supply passphrase"); + unblock_pending_gui_requests(); } typedef struct PageantImmOp PageantImmOp; @@ -618,9 +908,11 @@ static const PageantAsyncOpVtable immop_vtable = { .free = immop_free, }; -static bool reencrypt_key(PageantKey *pk) +static bool reencrypt_key(PageantPublicKey *pub) { - if (pk->sort.ssh_version != 2) { + PageantPrivateKey *priv = pub_to_priv(pub); + + if (priv->sort.ssh_version != 2) { /* * We don't support storing SSH-1 keys in encrypted form at * all. @@ -628,7 +920,7 @@ static bool reencrypt_key(PageantKey *pk) return false; } - if (!pk->encrypted_key_file) { + if (!priv->encrypted_key_file) { /* * We can't re-encrypt a key if it doesn't have an encrypted * form. (We could make one up, of course - but with what @@ -637,14 +929,12 @@ static bool reencrypt_key(PageantKey *pk) return false; } - /* Only actually free pk->skey if it exists. But we return success + /* Only actually free priv->skey if it exists. But we return success * regardless, so that 'please ensure this key isn't stored * decrypted' is idempotent. */ - if (pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); - pk->skey = NULL; + if (priv->skey) { + ssh_key_free(priv->skey); + priv->skey = NULL; } return true; @@ -688,9 +978,10 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(1, i)); i++) { - char *fingerprint = rsa_ssh1_fingerprint(pk->rkey); + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(1, i)); i++) { + PageantPrivateKey *priv = pub_to_priv(pub); + char *fingerprint = rsa_ssh1_fingerprint(priv->rkey); pageant_client_log(pc, reqid, "returned key: %s", fingerprint); sfree(fingerprint); @@ -711,12 +1002,12 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { + char *fingerprint = ssh2_double_fingerprint_blob( + pub->sort.full_pub, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -729,7 +1020,8 @@ static PageantAsyncOp *pageant_make_op( * or not. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; + PageantPrivateKey *priv; mp_int *challenge, *response; ptrlen session_id; unsigned response_type; @@ -763,11 +1055,12 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - if ((pk = findkey1(&reqkey)) == NULL) { + if ((pub = findpubkey1(&reqkey)) == NULL) { fail("key not found"); goto challenge1_cleanup; } - response = rsa_ssh1_decrypt(challenge, pk->rkey); + priv = pub_to_priv(pub); + response = rsa_ssh1_decrypt(challenge, priv->rkey); { ssh_hash *h = ssh_hash_new(&ssh_md5); @@ -782,7 +1075,7 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE"); - challenge1_cleanup: + challenge1_cleanup: if (response) mp_free(response); mp_free(challenge); @@ -795,7 +1088,7 @@ static PageantAsyncOp *pageant_make_op( * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen keyblob, sigdata; uint32_t flags; @@ -823,19 +1116,18 @@ static PageantAsyncOp *pageant_make_op( have_flags = true; if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( keyblob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } - if ((pk = findkey2(keyblob)) == NULL) { + if ((pub = findpubkey2(keyblob)) == NULL) { fail("key not found"); goto responded; } - #ifdef PUTTY_CAC char* fingerprint = ssh2_fingerprint_blob(keyblob, SSH_FPTYPE_DEFAULT); - BOOL bContinue = cert_confirm_signing(fingerprint, pk->comment); + BOOL bContinue = cert_confirm_signing(fingerprint, pub->comment); sfree(fingerprint); if (!bContinue) { @@ -859,10 +1151,9 @@ static PageantAsyncOp *pageant_make_op( so->pao.cr.next = &pc->info->head; so->pao.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr; so->pao.reqid = reqid; - so->pk = pk; + so->priv = pub_to_priv(pub); so->pkr.prev = so->pkr.next = NULL; - so->data_to_sign = strbuf_new(); - put_datapl(so->data_to_sign, sigdata); + so->data_to_sign = strbuf_dup(sigdata); so->flags = flags; so->failure_type = failure_type; so->crLine = 0; @@ -907,7 +1198,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add1_cleanup: + add1_cleanup: if (key) { freersakey(key); sfree(key); @@ -988,7 +1279,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add2_cleanup: + add2_cleanup: if (key) { if (key->key) ssh_key_free(key->key); @@ -1005,7 +1296,7 @@ static PageantAsyncOp *pageant_make_op( * start with. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); @@ -1027,15 +1318,15 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - pk = findkey1(&reqkey); + pub = findpubkey1(&reqkey); freersakey(&reqkey); - if (pk) { + if (pub) { pageant_client_log(pc, reqid, "found with comment: %s", - pk->rkey->comment); + pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1050,7 +1341,7 @@ static PageantAsyncOp *pageant_make_op( * perhaps SSH_AGENT_FAILURE if it wasn't in the list to * start with. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen blob; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY"); @@ -1063,23 +1354,23 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); } - pk = findkey2(blob); - if (!pk) { + pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } - pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); + pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1162,22 +1453,24 @@ static PageantAsyncOp *pageant_make_op( goto responded; } + strbuf *base_pub = NULL; + strbuf *full_pub = NULL; BinarySource src[1]; const char *error; - strbuf *public_blob = strbuf_new(); + full_pub = strbuf_new(); char *comment; BinarySource_BARE_INIT_PL(src, keyfile); - if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(public_blob), + if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(full_pub), &comment, &error)) { fail("failed to extract public key blob: %s", error); goto add_ppk_cleanup; } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint_blob( + ptrlen_from_strbuf(full_pub), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "add-ppk: %s %s", fingerprint, comment); sfree(fingerprint); @@ -1210,57 +1503,21 @@ static PageantAsyncOp *pageant_make_op( goto add_ppk_cleanup; } - PageantKeySort sort = - keysort(2, ptrlen_from_strbuf(public_blob)); + PageantPublicKeySort sort; + sort.priv.ssh_version = 2; + sort.full_pub = ptrlen_from_strbuf(full_pub); + base_pub = make_base_pub_2(&sort); - PageantKey *pk = find234(keytree, &sort, NULL); - if (pk) { - /* - * This public key blob already exists in the - * keytree. Add the encrypted key file to the - * existing record, if it doesn't have one already. - */ - if (!pk->encrypted_key_file) { - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log( - pc, reqid, "reply: SSH_AGENT_SUCCESS (added encrypted" - " PPK to existing key record)"); - } else { - fail("key already present"); - } - } else { - /* - * We're adding a new key record containing only - * an encrypted key file. - */ - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - pk->sort.ssh_version = 2; - pk->public_blob = public_blob; - public_blob = NULL; - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->comment = dupstr(comment); - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); - - PageantKey *added = add234(keytree, pk); - assert(added == pk); (void)added; - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS (made" - " new encrypted-only key record)"); - } + pageant_add_ssh2_key_encrypted(sort, comment, keyfile); + keylist_update(); + put_byte(sb, SSH_AGENT_SUCCESS); + pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - add_ppk_cleanup: - if (public_blob) - strbuf_free(public_blob); + add_ppk_cleanup: + if (full_pub) + strbuf_free(full_pub); + if (base_pub) + strbuf_free(base_pub); sfree(comment); break; } @@ -1281,23 +1538,23 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); sfree(fingerprint); } - PageantKey *pk = findkey2(blob); - if (!pk) { + PageantPublicKey *pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, - "found with comment: %s", pk->comment); + "found with comment: %s", pub->comment); - if (!reencrypt_key(pk)) { + if (!reencrypt_key(pub)) { fail("this key couldn't be re-encrypted"); goto responded; } @@ -1324,10 +1581,10 @@ static PageantAsyncOp *pageant_make_op( * having made a state change.) */ unsigned nfailures = 0, nsuccesses = 0; - PageantKey *pk; + PageantPublicKey *pub; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) { - if (reencrypt_key(pk)) + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) { + if (reencrypt_key(pub)) nsuccesses++; else nfailures++; @@ -1364,13 +1621,13 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH2_AGENT_SUCCESS + key list"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { + char *fingerprint = ssh2_double_fingerprint_blob( + ptrlen_from_strbuf(pub->full_pub), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -1412,43 +1669,47 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, void pageant_init(void) { pageant_local = true; - keytree = newtree234(cmpkeys); + pubkeytree = newtree234(pubkey_cmpfn); + privkeytree = newtree234(privkey_cmpfn); } -static PageantKey *pageant_nth_key(int ssh_version, int i) +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i) { - PageantKey *pk = index234( - keytree, find_first_key_for_version(ssh_version) + i); - if (pk && pk->sort.ssh_version == ssh_version) - return pk; + PageantPublicKey *pub = index234( + pubkeytree, find_first_pubkey_for_version(ssh_version) + i); + if (pub && pub->sort.priv.ssh_version == ssh_version) + return pub; else return NULL; } bool pageant_delete_nth_ssh1_key(int i) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(1) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(1) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_delete_nth_ssh2_key(int i) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(2) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_reencrypt_nth_ssh2_key(int i) { - PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = index234( + pubkeytree, find_first_pubkey_for_version(2) + i); + if (!pub) return false; - return reencrypt_key(pk); + return reencrypt_key(pub); } void pageant_delete_all(void) @@ -1459,9 +1720,9 @@ void pageant_delete_all(void) void pageant_reencrypt_all(void) { - PageantKey *pk; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) - reencrypt_key(pk); + PageantPublicKey *pub; + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) + reencrypt_key(pub); } /* ---------------------------------------------------------------------- @@ -2274,8 +2535,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl1) { for (size_t i = 0; i < kl1->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl1->keys[i].blob); + cbkey.blob = strbuf_dup(kl1->keys[i].blob); cbkey.comment = mkstr(kl1->keys[i].comment); cbkey.ssh_version = 1; @@ -2306,8 +2566,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl2) { for (size_t i = 0; i < kl2->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl2->keys[i].blob); + cbkey.blob = strbuf_dup(kl2->keys[i].blob); cbkey.comment = mkstr(kl2->keys[i].comment); cbkey.ssh_version = 2; @@ -2490,13 +2749,13 @@ char* ssh2_pubkey_openssh_str_direct(const char* comment, const void* v_pub_blob char* pageant_nth_ssh2_string(int i) { - PageantKey* pkey = pageant_nth_key(2, i); - return ssh2_pubkey_openssh_str_direct(pkey->comment, pkey->public_blob->s, pkey->public_blob->len); + PageantPublicKey* pkey = pageant_nth_pubkey(2, i); + return ssh2_pubkey_openssh_str_direct(pkey->comment, pkey->base_pub->s, pkey->base_pub->len); } char* pageant_nth_ssh2_comment(int i) { - PageantKey* pkey = pageant_nth_key(2, i); + PageantPublicKey* pkey = pageant_nth_pubkey(2, i); if (pkey == NULL) return NULL; return pkey->comment; } diff --git a/code/proxy/cproxy.c b/code/proxy/cproxy.c index 38e6eef9..40a2f609 100644 --- a/code/proxy/cproxy.c +++ b/code/proxy/cproxy.c @@ -26,7 +26,8 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) return sb; } -void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len) +static void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, + size_t len) { const unsigned char *p = (const unsigned char *)vptr; const char *hexdigits = "0123456789abcdef"; diff --git a/code/proxy/proxy.c b/code/proxy/proxy.c index 801c28f9..bca60a35 100644 --- a/code/proxy/proxy.c +++ b/code/proxy/proxy.c @@ -35,7 +35,7 @@ static void proxy_negotiator_cleanup(ProxySocket *ps) * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ -void proxy_activate(ProxySocket *ps) +static void proxy_activate(ProxySocket *ps) { size_t output_before, output_after; @@ -178,7 +178,7 @@ static void sk_proxy_set_frozen (Socket *s, bool is_frozen) sk_set_frozen(ps->sub_socket, is_frozen); } -static const char * sk_proxy_socket_error (Socket *s) +static const char *sk_proxy_socket_error (Socket *s) { ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->error != NULL || ps->sub_socket == NULL) { @@ -393,8 +393,8 @@ static char *dns_log_msg(const char *host, int addressfamily, } SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) + Conf *conf, int addressfamily, LogContext *logctx, + const char *reason) { if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && do_proxy_dns(conf) && @@ -506,7 +506,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname, char *proxy_canonical_name; Socket *sret; - if (type == PROXY_SSH && + if ((type == PROXY_SSH_TCPIP || + type == PROXY_SSH_EXEC || + type == PROXY_SSH_SUBSYSTEM) && (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, plug, conf, itr)) != NULL) @@ -579,10 +581,10 @@ Socket *new_connection(SockAddr *addr, const char *hostname, { char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", vt->type, - conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_proxy_port), - hostname, port); + " to %s:%d", vt->type, + conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_proxy_port), + hostname, port); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); } diff --git a/code/proxy/sshproxy.c b/code/proxy/sshproxy.c index a699cd7c..165c6c0a 100644 --- a/code/proxy/sshproxy.c +++ b/code/proxy/sshproxy.c @@ -10,6 +10,7 @@ #include "ssh.h" #include "network.h" #include "storage.h" +#include "proxy.h" const bool ssh_proxy_supported = true; @@ -254,8 +255,15 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); + switch (type) { + case SEAT_OUTPUT_STDOUT: + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + break; + case SEAT_OUTPUT_STDERR: + log_proxy_stderr(sp->plug, &sp->psb, data, len); + break; + } return bufchain_size(&sp->ssh_to_socket); } @@ -399,7 +407,7 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) static SeatPromptResult sshproxy_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -410,8 +418,8 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( * request on to it. */ return seat_confirm_ssh_host_key( - wrap(sp->clientseat), host, port, keytype, keystr, keydisp, - key_fingerprints, mismatch, callback, ctx); + wrap(sp->clientseat), host, port, keytype, keystr, text, + helpctx, callback, ctx); } /* @@ -423,8 +431,8 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( } static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -449,8 +457,8 @@ static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( } static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -474,6 +482,20 @@ static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( "weak cached host key"); } +static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions( + Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* If we have a client seat, return their prompt descriptions, so + * that prompts passed on to them will make sense. */ + if (sp->clientseat) + return seat_prompt_descriptions(sp->clientseat); + + /* Otherwise, it doesn't matter what we return, so do the easiest thing. */ + return nullseat_prompt_descriptions(NULL); +} + static StripCtrlChars *sshproxy_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) { @@ -525,6 +547,7 @@ static const SeatVtable SshProxy_seat_vt = { .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, + .prompt_descriptions = sshproxy_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -636,12 +659,47 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, */ conf_set_bool(sp->conf, CONF_ssh_simple, true); - /* - * Configure the main channel of this SSH session to be a - * direct-tcpip connection to the destination host/port. - */ - conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); - conf_set_int(sp->conf, CONF_ssh_nc_port, port); + int proxy_type = conf_get_int(clientconf, CONF_proxy_type); + switch (proxy_type) { + case PROXY_SSH_TCPIP: + /* + * Configure the main channel of this SSH session to be a + * direct-tcpip connection to the destination host/port. + */ + conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); + conf_set_int(sp->conf, CONF_ssh_nc_port, port); + break; + + case PROXY_SSH_SUBSYSTEM: + case PROXY_SSH_EXEC: { + Conf *cmd_conf = conf_copy(clientconf); + + /* + * Unlike the Telnet and Local proxy types, we don't use the + * proxy username and password fields in the formatted + * command, because if we use them at all, it's for + * authenticating to the proxy SSH server. + */ + conf_set_str(cmd_conf, CONF_proxy_username, ""); + conf_set_str(cmd_conf, CONF_proxy_password, ""); + + char *cmd = format_telnet_command(sp->addr, sp->port, cmd_conf, NULL); + conf_free(cmd_conf); + + conf_set_str(sp->conf, CONF_remote_cmd, cmd); + sfree(cmd); + + conf_set_bool(sp->conf, CONF_nopty, true); + + if (proxy_type == PROXY_SSH_SUBSYSTEM) + conf_set_bool(sp->conf, CONF_ssh_subsys, true); + + break; + } + + default: + unreachable("bad SSH proxy type"); + } /* * Do the usual normalisation of things in the Conf like a "user@" diff --git a/code/proxy/telnet.c b/code/proxy/telnet.c index 242c4025..a2efb7b4 100644 --- a/code/proxy/telnet.c +++ b/code/proxy/telnet.c @@ -85,31 +85,31 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf, int i = 0; for (;;) { - eo++; - if (fmt[eo] >= '0' && fmt[eo] <= '9') - v += fmt[eo] - '0'; - else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') - v += fmt[eo] - 'a' + 10; - else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') - v += fmt[eo] - 'A' + 10; - else { - /* non hex character, so we abort and just - * send the whole thing unescaped (including \x) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); eo++; - break; - } - - i++; - v <<= 4; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; } break; } diff --git a/code/pscp.c b/code/pscp.c index da9e9aaa..77de1cd9 100644 --- a/code/pscp.c +++ b/code/pscp.c @@ -77,6 +77,7 @@ static const SeatVtable pscp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -644,8 +645,8 @@ void scp_sftp_listdir(const char *dirname) dirh = fxp_opendir_recv(pktin, req); if (dirh == NULL) { - tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); - errs++; + tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); + errs++; } else { struct list_directory_from_sftp_ctx *ctx = list_directory_from_sftp_new(); @@ -2188,8 +2189,7 @@ static void usage(void) printf("PuTTY Secure Copy client\n"); printf("%s\n", ver); printf("Usage: pscp [options] [user@]host:source target\n"); - printf - (" pscp [options] source [source...] [user@]host:target\n"); + printf(" pscp [options] source [source...] [user@]host:target\n"); printf(" pscp [options] -ls [user@]host:filespec\n"); printf("Options:\n"); printf(" -V print version information and exit\n"); diff --git a/code/psftp.c b/code/psftp.c index bddc18a2..d8b5c400 100644 --- a/code/psftp.c +++ b/code/psftp.c @@ -58,6 +58,7 @@ static const SeatVtable psftp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -2564,10 +2565,10 @@ static void usage(void) static void version(void) { - char *buildinfo_text = buildinfo("\n"); - printf("psftp: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); + char *buildinfo_text = buildinfo("\n"); + printf("psftp: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); } /* @@ -2789,7 +2790,7 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; */ int psftp_main(int argc, char *argv[]) { - int i, ret; + int i, toret; int portnumber = 0; char *userhost, *user; int mode = 0; @@ -2806,7 +2807,7 @@ int psftp_main(int argc, char *argv[]) do_defaults(NULL, conf); for (i = 1; i < argc; i++) { - int ret; + int retd; if (argv[i][0] != '-') { if (userhost) usage(); @@ -2814,12 +2815,13 @@ int psftp_main(int argc, char *argv[]) userhost = dupstr(argv[i]); continue; } - ret = cmdline_process_param(argv[i], i+1 #include "putty.h" +#include "storage.h" #include "misc.h" #include "ssh.h" #include "ssh/channel.h" diff --git a/code/putty.h b/code/putty.h index 8c3b0d8e..2a785337 100644 --- a/code/putty.h +++ b/code/putty.h @@ -266,7 +266,6 @@ struct sesslist { }; struct unicode_data { - char **uni_tbl; bool dbcs_screenfont; int font_codepage; int line_codepage; @@ -423,9 +422,14 @@ enum { KEX_WARN, KEX_DHGROUP1, KEX_DHGROUP14, + KEX_DHGROUP15, + KEX_DHGROUP16, + KEX_DHGROUP17, + KEX_DHGROUP18, KEX_DHGEX, KEX_RSA, KEX_ECDH, + KEX_NTRU_HYBRID, KEX_MAX }; @@ -453,6 +457,7 @@ enum { CIPHER_DES, CIPHER_ARCFOUR, CIPHER_CHACHA20, + CIPHER_AESGCM, CIPHER_MAX /* no. ciphers (inc warn) */ }; @@ -474,7 +479,8 @@ enum { * Proxy types. */ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, - PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH, + PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH_TCPIP, + PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM, PROXY_FUZZ }; @@ -1084,6 +1090,24 @@ typedef enum SeatOutputType { SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR } SeatOutputType; +typedef enum SeatDialogTextType { + SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING, + SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT, + SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB +} SeatDialogTextType; +struct SeatDialogTextItem { + SeatDialogTextType type; + char *text; +}; +struct SeatDialogText { + size_t nitems, itemsize; + SeatDialogTextItem *items; +}; +SeatDialogText *seat_dialog_text_new(void); +void seat_dialog_text_free(SeatDialogText *sdt); +PRINTF_LIKE(3, 4) void seat_dialog_text_append( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...); + /* * Data type 'Seat', which is an API intended to contain essentially * everything that a back end might need to talk to its client for: @@ -1258,9 +1282,8 @@ struct SeatVtable { */ SeatPromptResult (*confirm_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - bool mismatch, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Check with the seat whether it's OK to use a cryptographic @@ -1287,6 +1310,13 @@ struct SeatVtable { Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); + /* + * Some snippets of text describing the UI actions in host key + * prompts / dialog boxes, to be used in ssh/common.c when it + * assembles the full text of those prompts. + */ + const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat); + /* * Indicates whether the seat is expecting to interact with the * user in the UTF-8 character set. (Affects e.g. visual erase @@ -1408,10 +1438,10 @@ static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } static inline SeatPromptResult seat_confirm_ssh_host_key( InteractionReadySeat iseat, const char *h, int p, const char *ktyp, - char *kstr, const char *kdsp, char **fps, bool mis, + char *kstr, SeatDialogText *text, HelpCtx helpctx, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_ssh_host_key( - iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); } + iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); } static inline SeatPromptResult seat_confirm_weak_crypto_primitive( InteractionReadySeat iseat, const char *atyp, const char *aname, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) @@ -1422,6 +1452,9 @@ static inline SeatPromptResult seat_confirm_weak_cached_hostkey( void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_cached_hostkey( iseat.seat, aname, better, cb, ctx); } +static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions( + Seat *seat) +{ return seat->vt->prompt_descriptions(seat); } static inline bool seat_is_utf8(Seat *seat) { return seat->vt->is_utf8(seat); } static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) @@ -1467,6 +1500,12 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data) { return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); } +struct SeatDialogPromptDescriptions { + const char *hk_accept_action; + const char *hk_connect_once_action; + const char *hk_cancel_action, *hk_cancel_action_Participle; +}; + /* In the utils subdir: print a message to the Seat which can't be * spoofed by server-supplied auth-time output such as SSH banners */ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg); @@ -1494,7 +1533,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1502,6 +1541,7 @@ SeatPromptResult nullseat_confirm_weak_crypto_primitive( SeatPromptResult nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat); bool nullseat_is_never_utf8(Seat *seat); bool nullseat_is_always_utf8(Seat *seat); void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); @@ -1509,7 +1549,7 @@ const char *nullseat_get_x_display(Seat *seat); bool nullseat_get_windowid(Seat *seat, long *id_out); bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void nullseat_set_trust_status(Seat *seat, bool trusted); bool nullseat_can_set_trust_status_yes(Seat *seat); bool nullseat_can_set_trust_status_no(Seat *seat); @@ -1529,7 +1569,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); void console_connection_fatal(Seat *seat, const char *message); SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1538,10 +1578,11 @@ SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void console_set_trust_status(Seat *seat, bool trusted); bool console_can_set_trust_status(Seat *seat); bool console_has_mixed_input_stream(Seat *seat); +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat); /* * Other centralised seat functions. @@ -1634,6 +1675,17 @@ struct TermWinVtable { void (*refresh)(TermWin *); + /* request_resize asks the front end if the terminal can please be + * resized to (w,h) in characters. The front end MAY call + * term_size() in response to tell the terminal its new size + * (which MAY be the requested size, or some other size if the + * requested one can't be achieved). The front end MAY also not + * call term_size() at all. But the front end MUST reply to this + * request by calling term_resize_request_completed(), after the + * responding resize event has taken place (if any). + * + * The calls to term_size and term_resize_request_completed may be + * synchronous callbacks from within the call to request_resize(). */ void (*request_resize)(TermWin *, int w, int h); void (*set_title)(TermWin *, const char *title, int codepage); @@ -1778,6 +1830,8 @@ NORETURN void cleanup_exit(int); X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ X(INT, INT, ssh_cipherlist) \ X(FILENAME, NONE, keyfile) \ + X(FILENAME, NONE, detached_cert) \ + X(STR, NONE, auth_plugin) \ /* \ * Which SSH protocol to use. \ * For historical reasons, the current legal values for CONF_sshprot \ @@ -1973,6 +2027,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshbug_winadj) \ X(INT, NONE, sshbug_chanreq) \ X(INT, NONE, sshbug_dropstart) \ + X(INT, NONE, sshbug_filter_kexinit) \ /* \ * ssh_simple means that we promise never to open any channel \ * other than the main one, which means it can safely use a very \ @@ -2134,6 +2189,7 @@ FontSpec *platform_default_fontspec(const char *name); Terminal *term_init(Conf *, struct unicode_data *, TermWin *); void term_free(Terminal *); void term_size(Terminal *, int, int, int); +void term_resize_request_completed(Terminal *); void term_paint(Terminal *, int, int, int, int, bool); void term_scroll(Terminal *, int, int); void term_scroll_to_selection(Terminal *, int); @@ -2164,7 +2220,7 @@ char *term_get_ttymode(Terminal *term, const char *mode); SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); -void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); +void term_keyinputw(Terminal *, const wchar_t *widebuf, int len); void term_get_cursor_position(Terminal *term, int *x, int *y); void term_setup_window_titles(Terminal *term, const char *title_hostname); void term_notify_minimised(Terminal *term, bool minimised); @@ -2180,7 +2236,9 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, bool shift, bool ctrl, bool alt, bool *consumed_alt); int format_function_key(char *buf, Terminal *term, int key_number, bool shift, bool ctrl, bool alt, bool *consumed_alt); -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, + bool shift, bool ctrl, bool alt, + bool *consumed_alt); int format_numeric_keypad_key(char *buf, Terminal *term, char key, bool shift, bool ctrl); @@ -2425,14 +2483,13 @@ bool is_dbcs_leadbyte(int codepage, char byte); int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen); int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata); + char *mbstr, int mblen, const char *defchr); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); -int decode_codepage(char *cp_name); +int decode_codepage(const char *cp_name); const char *cp_enumerate (int index); const char *cp_name(int codepage); -void get_unitab(int codepage, wchar_t * unitab, int ftype); +void get_unitab(int codepage, wchar_t *unitab, int ftype); /* * Exports from wcwidth.c @@ -2576,22 +2633,69 @@ void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2); * Exports from config.c. */ struct controlbox; -union control; -void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, +void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_editbox_handler(union control *ctrl, dlgparam *dlg, +void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_filesel_handler(union control *ctrl, dlgparam *dlg, +void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, +void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); +struct conf_editbox_handler_type { + /* Structure passed as context2 to conf_editbox_handler */ + enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type; + union { + /* + * EDIT_STR means the edit box is connected to a string + * field in Conf. No further parameters needed. + */ + + /* + * EDIT_INT means the edit box is connected to an int field in + * Conf, and the input string is interpreted as decimal. No + * further parameters needed. (But we could add one here later + * if for some reason we wanted int fields in hex.) + */ + + /* + * EDIT_FIXEDPOINT means the edit box is connected to an int + * field in Conf, but the input string is interpreted as + * _floating point_, and converted to/from the output int by + * means of a fixed denominator. That is, + * + * (floating value in edit box) * denominator = value in Conf + */ + struct { + double denominator; + }; + }; +}; + +extern const struct conf_editbox_handler_type conf_editbox_str; +extern const struct conf_editbox_handler_type conf_editbox_int; +#define ED_STR CP(&conf_editbox_str) +#define ED_INT CP(&conf_editbox_int) + void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); +void setup_ca_config_box(struct controlbox *b); + +/* Platforms provide this to be called from config.c */ +void show_ca_config_box(dlgparam *dlg); +extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */ + +/* Visible outside config.c so that platforms can use it to recognise + * the proxy type control */ +void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, + void *data, int event); +/* And then they'll set this flag in its generic.context.i */ +#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */ + /* * Exports from bidi.c. */ @@ -2794,6 +2898,16 @@ typedef void (*toplevel_callback_notify_fn_t)(void *ctx); void request_callback_notifications(toplevel_callback_notify_fn_t notify, void *ctx); +/* + * Facility provided by the platform to spawn a parallel subprocess + * and present its stdio via a Socket. + * + * 'prefix' indicates the prefix that should appear on messages passed + * to plug_log to provide stderr output from the process. + */ +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix); + /* * Define no-op macros for the jump list functions, on platforms that * don't support them. (This is a bit of a hack, and it'd be nicer to diff --git a/code/release.pl b/code/release.pl index 122cc027..f9507a13 100644 --- a/code/release.pl +++ b/code/release.pl @@ -15,13 +15,11 @@ my $upload = 0; my $precheck = 0; my $postcheck = 0; -my $skip_ftp = 0; GetOptions("version=s" => \$version, "setver" => \$setver, "upload" => \$upload, "precheck" => \$precheck, - "postcheck" => \$postcheck, - "no-ftp" => \$skip_ftp) + "postcheck" => \$postcheck) or &usage(); # --setver: construct a local commit which updates the version @@ -76,8 +74,7 @@ or die "could not upload link maps"; for my $location (["thyestes", "www/putty/$version"], - ["the", "www/putty/$version"], - ["chiark", "ftp/putty-$version"]) { + ["the", "www/putty/$version"]) { my ($host, $path) = @$location; 0 == system("rsync", "-av", "putty/", "$host:$path") or die "could not upload release to $host"; @@ -109,7 +106,7 @@ } # --precheck and --postcheck: attempt to download the release from its -# various web and FTP locations. +# various locations. if ($precheck || $postcheck) { defined $version or die "use --version"; @@ -118,7 +115,6 @@ -d "putty" or die "no putty directory in cwd"; my $httpprefix = "https://the.earth.li/~sgtatham/putty/"; - my $ftpprefix = "ftp://ftp.chiark.greenend.org.uk/users/sgtatham/putty-"; # Go through all the files in build.out. find({ wanted => sub @@ -140,35 +136,23 @@ my $http_numbered = "${httpprefix}$version/$path"; my $http_latest = "${httpprefix}latest/$path"; - my $ftp_numbered = "${ftpprefix}$version/$path"; - my $ftp_latest = "${ftpprefix}latest/$path"; - my ($http_uri, $ftp_uri); + my $http_uri; if ($precheck) { # Before the 'latest' links/redirects update, # we just download from explicitly version- # numbered URLs. $http_uri = $http_numbered; - $ftp_uri = $ftp_numbered; } if ($postcheck) { # After 'latest' is updated, we're testing that # the redirects work, so we download from the # URLs with 'latest' in them. $http_uri = $http_latest; - $ftp_uri = $ftp_latest; } # Now test-download the files themselves. - unless ($skip_ftp) { - my $ftpdata = `curl -s $ftp_uri`; - printf " got %d bytes via FTP", length $ftpdata; - die "FTP download for $ftp_uri did not match" - if $ftpdata ne $real_content; - print ", ok\n"; - } - my $ua = LWP::UserAgent->new; my $httpresponse = $ua->get($http_uri); my $httpdata = $httpresponse->{_content}; diff --git a/code/settings.c b/code/settings.c index c23fe08c..f78ea301 100644 --- a/code/settings.c +++ b/code/settings.c @@ -20,6 +20,7 @@ static const struct keyvalwhere ciphernames[] = { { "aes", CIPHER_AES, -1, -1 }, { "chacha20", CIPHER_CHACHA20, CIPHER_AES, +1 }, + { "aesgcm", CIPHER_AESGCM, CIPHER_CHACHA20, +1 }, { "3des", CIPHER_3DES, -1, -1 }, { "WARN", CIPHER_WARN, -1, -1 }, { "des", CIPHER_DES, -1, -1 }, @@ -31,12 +32,24 @@ static const struct keyvalwhere ciphernames[] = { * compatibility warts in load_open_settings(), and should be kept * in sync with those. */ static const struct keyvalwhere kexnames[] = { + { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 }, { "ecdh", KEX_ECDH, -1, +1 }, /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, + /* Again, this covers both SHA-256 and SHA-1, despite the name: */ { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + /* This one really is only SHA-1, though: */ { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, { "rsa", KEX_RSA, KEX_WARN, -1 }, + /* Larger fixed DH groups: prefer the larger 15 and 16 over 14, + * but by default the even larger 17 and 18 go below 16. + * Rationale: diminishing returns of improving the DH strength are + * outweighed by increased CPU cost. Group 18 is painful on a slow + * machine. Users can override if they need to. */ + { "dh-group15-sha512", KEX_DHGROUP15, KEX_DHGROUP14, -1 }, + { "dh-group16-sha512", KEX_DHGROUP16, KEX_DHGROUP15, -1 }, + { "dh-group17-sha512", KEX_DHGROUP17, KEX_DHGROUP16, +1 }, + { "dh-group18-sha512", KEX_DHGROUP18, KEX_DHGROUP17, +1 }, { "WARN", KEX_WARN, -1, -1 } }; @@ -627,6 +640,8 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); + write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert)); + write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin)); write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); #ifdef PUTTY_CAC @@ -778,6 +793,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); + write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit)); write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell)); write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left)); @@ -975,9 +991,9 @@ void load_open_settings(settings_r *sesskey, Conf *conf) * a server which offered it then choked, but we never got * a server version string or any other reports. */ const char *default_kexes, - *normal_default = "ecdh,dh-gex-sha1,dh-group14-sha1,rsa," + *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," "WARN,dh-group1-sha1", - *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa," + *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," "WARN,dh-group1-sha1,dh-gex-sha1"; char *raw; i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); @@ -1048,6 +1064,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf) #endif gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); + gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert); + gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin); gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); @@ -1265,6 +1283,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); + i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i); conf_set_bool(conf, CONF_ssh_simple, false); gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); diff --git a/code/ssh.h b/code/ssh.h index a0d9a29e..003e77e6 100644 --- a/code/ssh.h +++ b/code/ssh.h @@ -515,7 +515,7 @@ struct ec_curve { }; const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve); + const struct ec_curve **curve); const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen); extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths; @@ -554,22 +554,34 @@ struct eddsa_key { WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); +typedef enum KeyComponentType { + KCT_TEXT, KCT_BINARY, KCT_MPINT +} KeyComponentType; +typedef struct key_component { + char *name; + KeyComponentType type; + union { + strbuf *str; /* used for KCT_TEXT and KCT_BINARY */ + mp_int *mp; /* used for KCT_MPINT */ + }; +} key_component; typedef struct key_components { size_t ncomponents, componentsize; - struct { - char *name; - bool is_mp_int; - union { - char *text; - mp_int *mp; - }; - } *components; + key_component *components; } key_components; key_components *key_components_new(void); void key_components_add_text(key_components *kc, const char *name, const char *value); +void key_components_add_text_pl(key_components *kc, + const char *name, ptrlen value); +void key_components_add_binary(key_components *kc, + const char *name, ptrlen value); void key_components_add_mp(key_components *kc, const char *name, mp_int *value); +void key_components_add_uint(key_components *kc, + const char *name, uintmax_t value); +void key_components_add_copy(key_components *kc, + const char *name, const key_component *value); void key_components_free(key_components *kc); /* @@ -597,6 +609,7 @@ bool rsa_verify(RSAKey *key); void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order); int rsa_ssh1_public_blob_len(ptrlen data); void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key); +void duprsakey(RSAKey *dst, const RSAKey *src); void freersapriv(RSAKey *key); void freersakey(RSAKey *key); key_components *rsa_components(RSAKey *key); @@ -627,21 +640,12 @@ strbuf *ssh_rsakex_encrypt( mp_int *ssh_rsakex_decrypt( RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext); -/* - * SSH2 ECDH key exchange functions - */ -const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex); -ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex); -void ssh_ecdhkex_freekey(ecdh_key *key); -void ssh_ecdhkex_getpublic(ecdh_key *key, BinarySink *bs); -mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey); - /* * Helper function for k generation in DSA, reused in ECDSA */ mp_int *dsa_gen_k(const char *id_string, - mp_int *modulus, mp_int *private_key, - unsigned char *digest, int digest_len); + mp_int *modulus, mp_int *private_key, + unsigned char *digest, int digest_len); struct ssh_cipher { const ssh_cipheralg *vt; @@ -659,6 +663,9 @@ struct ssh_cipheralg { unsigned long seq); void (*decrypt_length)(ssh_cipher *, void *blk, int len, unsigned long seq); + /* For ciphers that update their state per logical message + * (typically, per unit independently MACed) */ + void (*next_message)(ssh_cipher *); const char *ssh2_id; int blksize; /* real_keybits is the number of bits of entropy genuinely used by @@ -703,9 +710,13 @@ static inline void ssh_cipher_encrypt_length( static inline void ssh_cipher_decrypt_length( ssh_cipher *c, void *blk, int len, unsigned long seq) { c->vt->decrypt_length(c, blk, len, seq); } +static inline void ssh_cipher_next_message(ssh_cipher *c) +{ c->vt->next_message(c); } static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c) { return c->vt; } +void nullcipher_next_message(ssh_cipher *); + struct ssh2_ciphers { int nciphers; const ssh_cipheralg *const *list; @@ -723,6 +734,7 @@ struct ssh2_macalg { void (*setkey)(ssh2_mac *, ptrlen key); void (*start)(ssh2_mac *); void (*genresult)(ssh2_mac *, unsigned char *); + void (*next_message)(ssh2_mac *); const char *(*text_name)(ssh2_mac *); const char *name, *etm_name; int len, keylen; @@ -742,6 +754,8 @@ static inline void ssh2_mac_start(ssh2_mac *m) { m->vt->start(m); } static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out) { m->vt->genresult(m, out); } +static inline void ssh2_mac_next_message(ssh2_mac *m) +{ m->vt->next_message(m); } static inline const char *ssh2_mac_text_name(ssh2_mac *m) { return m->vt->text_name(m); } static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m) @@ -754,6 +768,8 @@ bool ssh2_mac_verresult(ssh2_mac *, const void *); void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq); bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq); +void nullmac_next_message(ssh2_mac *m); + /* Use a MAC in its raw form, outside SSH-2 context, to MAC a given * string with a given key in the most obvious way. */ void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output); @@ -816,11 +832,20 @@ void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output); struct ssh_kex { const char *name, *groupname; - enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; + enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, + KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type; const ssh_hashalg *hash; + union { /* publicly visible data for each type */ + const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */ + }; const void *extra; /* private to the kex methods */ }; +static inline bool kex_is_gss(const struct ssh_kex *kex) +{ + return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH; +} + struct ssh_kexes { int nkexes; const ssh_kex *const *list; @@ -847,17 +872,34 @@ struct ssh_keyalg { void (*public_blob)(ssh_key *key, BinarySink *); void (*private_blob)(ssh_key *key, BinarySink *); void (*openssh_blob) (ssh_key *key, BinarySink *); + bool (*has_private) (ssh_key *key); char *(*cache_str) (ssh_key *key); key_components *(*components) (ssh_key *key); + ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */ + /* The following methods can be NULL if !is_certificate */ + void (*ca_public_blob)(ssh_key *key, BinarySink *); + bool (*check_cert)(ssh_key *key, bool host, ptrlen principal, + uint64_t time, const ca_options *opts, + BinarySink *error); + void (*cert_id_string)(ssh_key *key, BinarySink *); + SeatDialogText *(*cert_info)(ssh_key *key); /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); + unsigned (*supported_flags) (const ssh_keyalg *self); + const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); + char *(*alg_desc)(const ssh_keyalg *self); + bool (*variable_size)(const ssh_keyalg *self); + /* The following methods can be NULL if !is_certificate */ + const ssh_keyalg *(*related_alg)(const ssh_keyalg *self, + const ssh_keyalg *base); /* Constant data fields giving information about the key type */ const char *ssh_id; /* string identifier in the SSH protocol */ const char *cache_id; /* identifier used in PuTTY's host key cache */ const void *extra; /* private to the public key methods */ - const unsigned supported_flags; /* signature-type flags we understand */ + bool is_certificate; /* is this a certified key type? */ + const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */ }; static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub) @@ -883,10 +925,24 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs) { key->vt->private_blob(key, bs); } static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) { key->vt->openssh_blob(key, bs); } +static inline bool ssh_key_has_private(ssh_key *key) +{ return key->vt->has_private(key); } static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } static inline key_components *ssh_key_components(ssh_key *key) { return key->vt->components(key); } +static inline ssh_key *ssh_key_base_key(ssh_key *key) +{ return key->vt->base_key(key); } +static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs) +{ key->vt->ca_public_blob(key, bs); } +static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs) +{ key->vt->cert_id_string(key, bs); } +static inline SeatDialogText *ssh_key_cert_info(ssh_key *key) +{ return key->vt->cert_info(key); } +static inline bool ssh_key_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + const ca_options *opts, BinarySink *error) +{ return key->vt->check_cert(key, host, principal, time, opts, error); } static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) { return self->pubkey_bits(self, blob); } static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) @@ -895,6 +951,73 @@ static inline const char *ssh_key_ssh_id(ssh_key *key) { return key->vt->ssh_id; } static inline const char *ssh_key_cache_id(ssh_key *key) { return key->vt->cache_id; } +static inline unsigned ssh_key_supported_flags(ssh_key *key) +{ return key->vt->supported_flags(key->vt); } +static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) +{ return self->supported_flags(self); } +static inline const char *ssh_keyalg_alternate_ssh_id( + const ssh_keyalg *self, unsigned flags) +{ return self->alternate_ssh_id(self, flags); } +static inline char *ssh_keyalg_desc(const ssh_keyalg *self) +{ return self->alg_desc(self); } +static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self) +{ return self->variable_size(self); } +static inline const ssh_keyalg *ssh_keyalg_related_alg( + const ssh_keyalg *self, const ssh_keyalg *base) +{ return self->related_alg(self, base); } + +/* Stub functions shared between multiple key types */ +unsigned nullkey_supported_flags(const ssh_keyalg *self); +const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +ssh_key *nullkey_base_key(ssh_key *key); +bool nullkey_variable_size_no(const ssh_keyalg *self); +bool nullkey_variable_size_yes(const ssh_keyalg *self); + +/* Utility functions implemented centrally */ +ssh_key *ssh_key_clone(ssh_key *key); + +/* + * SSH2 ECDH key exchange vtable + */ +struct ecdh_key { + const ecdh_keyalg *vt; +}; +struct ecdh_keyalg { + /* Unusually, the 'new' method here doesn't directly take a vt + * pointer, because it will also need the containing ssh_kex + * structure for top-level parameters, and since that contains a + * vt pointer anyway, we might as well _only_ pass that. */ + ecdh_key *(*new)(const ssh_kex *kex, bool is_server); + void (*free)(ecdh_key *key); + void (*getpublic)(ecdh_key *key, BinarySink *bs); + bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs); + char *(*description)(const ssh_kex *kex); +}; +static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server) +{ return kex->ecdh_vt->new(kex, is_server); } +static inline void ecdh_key_free(ecdh_key *key) +{ key->vt->free(key); } +static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs) +{ key->vt->getpublic(key, bs); } +static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, + BinarySink *bs) +{ return key->vt->getkey(key, remoteKey, bs); } +static inline char *ecdh_keyalg_description(const ssh_kex *kex) +{ return kex->ecdh_vt->description(kex); } + +/* + * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 + * as the mechanism. + * + * This suffix is the base64-encoded MD5 hash of the byte sequence + * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER + * encoding of the object ID 1.2.840.113554.1.2.2 which designates + * Kerberos v5. + * + * (The same encoded OID, minus the two-byte DER header, is defined in + * ssh/pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" /* * Enumeration of signature flags from draft-miller-ssh-agent-02 @@ -981,6 +1104,10 @@ extern const ssh_cipheralg ssh_aes256_sdctr; extern const ssh_cipheralg ssh_aes256_sdctr_ni; extern const ssh_cipheralg ssh_aes256_sdctr_neon; extern const ssh_cipheralg ssh_aes256_sdctr_sw; +extern const ssh_cipheralg ssh_aes256_gcm; +extern const ssh_cipheralg ssh_aes256_gcm_ni; +extern const ssh_cipheralg ssh_aes256_gcm_neon; +extern const ssh_cipheralg ssh_aes256_gcm_sw; extern const ssh_cipheralg ssh_aes256_cbc; extern const ssh_cipheralg ssh_aes256_cbc_ni; extern const ssh_cipheralg ssh_aes256_cbc_neon; @@ -989,6 +1116,10 @@ extern const ssh_cipheralg ssh_aes192_sdctr; extern const ssh_cipheralg ssh_aes192_sdctr_ni; extern const ssh_cipheralg ssh_aes192_sdctr_neon; extern const ssh_cipheralg ssh_aes192_sdctr_sw; +extern const ssh_cipheralg ssh_aes192_gcm; +extern const ssh_cipheralg ssh_aes192_gcm_ni; +extern const ssh_cipheralg ssh_aes192_gcm_neon; +extern const ssh_cipheralg ssh_aes192_gcm_sw; extern const ssh_cipheralg ssh_aes192_cbc; extern const ssh_cipheralg ssh_aes192_cbc_ni; extern const ssh_cipheralg ssh_aes192_cbc_neon; @@ -997,6 +1128,10 @@ extern const ssh_cipheralg ssh_aes128_sdctr; extern const ssh_cipheralg ssh_aes128_sdctr_ni; extern const ssh_cipheralg ssh_aes128_sdctr_neon; extern const ssh_cipheralg ssh_aes128_sdctr_sw; +extern const ssh_cipheralg ssh_aes128_gcm; +extern const ssh_cipheralg ssh_aes128_gcm_ni; +extern const ssh_cipheralg ssh_aes128_gcm_neon; +extern const ssh_cipheralg ssh_aes128_gcm_sw; extern const ssh_cipheralg ssh_aes128_cbc; extern const ssh_cipheralg ssh_aes128_cbc_ni; extern const ssh_cipheralg ssh_aes128_cbc_neon; @@ -1012,6 +1147,7 @@ extern const ssh2_ciphers ssh2_aes; extern const ssh2_ciphers ssh2_blowfish; extern const ssh2_ciphers ssh2_arcfour; extern const ssh2_ciphers ssh2_ccp; +extern const ssh2_ciphers ssh2_aesgcm; extern const ssh_hashalg ssh_md5; extern const ssh_hashalg ssh_sha1; extern const ssh_hashalg ssh_sha1_ni; @@ -1035,11 +1171,21 @@ extern const ssh_hashalg ssh_shake256_114bytes; extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; extern const ssh_kexes ssh_diffiehellman_group14; +extern const ssh_kexes ssh_diffiehellman_group15; +extern const ssh_kexes ssh_diffiehellman_group16; +extern const ssh_kexes ssh_diffiehellman_group17; +extern const ssh_kexes ssh_diffiehellman_group18; extern const ssh_kexes ssh_diffiehellman_gex; extern const ssh_kex ssh_diffiehellman_group1_sha1; extern const ssh_kex ssh_diffiehellman_group14_sha256; extern const ssh_kex ssh_diffiehellman_group14_sha1; +extern const ssh_kex ssh_diffiehellman_group15_sha512; +extern const ssh_kex ssh_diffiehellman_group16_sha512; +extern const ssh_kex ssh_diffiehellman_group17_sha512; +extern const ssh_kex ssh_diffiehellman_group18_sha512; extern const ssh_kexes ssh_gssk5_sha1_kex; +extern const ssh_kexes ssh_gssk5_sha2_kex; +extern const ssh_kexes ssh_gssk5_ecdh_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; extern const ssh_kex ssh_ec_kex_curve448; @@ -1047,6 +1193,7 @@ extern const ssh_kex ssh_ec_kex_nistp256; extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; +extern const ssh_kexes ssh_ntru_hybrid_kex; extern const ssh_keyalg ssh_dsa; extern const ssh_keyalg ssh_rsa; extern const ssh_keyalg ssh_rsa_sha256; @@ -1062,6 +1209,14 @@ extern const ssh_keyalg ssh_ecdsa_nistp384_sk; extern const ssh_keyalg ssh_ecdsa_nistp521_sk; extern const ssh_keyalg ssh_ecdsa_ed25519_sk; #endif +extern const ssh_keyalg opensshcert_ssh_dsa; +extern const ssh_keyalg opensshcert_ssh_rsa; +extern const ssh_keyalg opensshcert_ssh_rsa_sha256; +extern const ssh_keyalg opensshcert_ssh_rsa_sha512; +extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521; extern const ssh2_macalg ssh_hmac_md5; extern const ssh2_macalg ssh_hmac_sha1; extern const ssh2_macalg ssh_hmac_sha1_buggy; @@ -1069,12 +1224,20 @@ extern const ssh2_macalg ssh_hmac_sha1_96; extern const ssh2_macalg ssh_hmac_sha1_96_buggy; extern const ssh2_macalg ssh_hmac_sha256; extern const ssh2_macalg ssh2_poly1305; +extern const ssh2_macalg ssh2_aesgcm_mac; +extern const ssh2_macalg ssh2_aesgcm_mac_sw; +extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly; +extern const ssh2_macalg ssh2_aesgcm_mac_clmul; +extern const ssh2_macalg ssh2_aesgcm_mac_neon; extern const ssh_compression_alg ssh_zlib; /* Special constructor: BLAKE2b can be instantiated with any hash * length up to 128 bytes */ ssh_hash *blake2b_new_general(unsigned hashlen); +/* Special test function for AES-GCM */ +void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad); + /* * On some systems, you have to detect hardware crypto acceleration by * asking the local OS API rather than OS-agnostically asking the CPU @@ -1082,6 +1245,7 @@ ssh_hash *blake2b_new_general(unsigned hashlen); * platform subdirectory. */ bool platform_aes_neon_available(void); +bool platform_pmull_neon_available(void); bool platform_sha256_neon_available(void); bool platform_sha1_neon_available(void); bool platform_sha512_neon_available(void); @@ -1254,11 +1418,7 @@ static inline bool is_base64_char(char c) c == '+' || c == '/' || c == '='); } -extern int base64_decode_atom(const char *atom, unsigned char *out); extern int base64_lines(int datalen); -extern void base64_encode_atom(const unsigned char *data, int n, char *out); -extern void base64_encode(FILE *fp, const unsigned char *data, int datalen, - int cpl); /* ppk_load_* can return this as an error */ extern ssh2_userkey ssh2_wrong_passphrase; @@ -1325,6 +1485,9 @@ extern const size_t n_keyalgs; const ssh_keyalg *find_pubkey_alg(const char *name); const ssh_keyalg *find_pubkey_alg_len(ptrlen name); +ptrlen pubkey_blob_to_alg_name(ptrlen blob); +const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob); + /* Convenient wrappers on the LoadedFile mechanism suitable for key files */ LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr); LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr); @@ -1374,12 +1537,36 @@ enum { }; typedef enum { + /* Default fingerprint types strip off a certificate to show you + * the fingerprint of the underlying public key */ SSH_FPTYPE_MD5, SSH_FPTYPE_SHA256, + /* Non-default version of each fingerprint type which is 'raw', + * giving you the true hash of the public key blob even if it + * includes a certificate */ + SSH_FPTYPE_MD5_CERT, + SSH_FPTYPE_SHA256_CERT, } FingerprintType; +static inline bool ssh_fptype_is_cert(FingerprintType fptype) +{ + return fptype >= SSH_FPTYPE_MD5_CERT; +} +static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} +static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype) +{ + if (!ssh_fptype_is_cert(fptype)) + fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} + +#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1) #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 -#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1) FingerprintType ssh2_pick_fingerprint(char **fingerprints, FingerprintType preferred_type); @@ -1393,6 +1580,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment, int keytype); char *ssh2_fingerprint_blob(ptrlen, FingerprintType); char *ssh2_fingerprint(ssh_key *key, FingerprintType); +char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType); +char *ssh2_double_fingerprint(ssh_key *key, FingerprintType); char **ssh2_all_fingerprints_for_blob(ptrlen); char **ssh2_all_fingerprints(ssh_key *key); void ssh2_free_all_fingerprints(char **); @@ -1404,7 +1593,7 @@ bool import_possible(int type); int import_target_type(int type); bool import_encrypted(const Filename *filename, int type, char **comment); bool import_encrypted_s(const Filename *filename, BinarySource *src, - int type, char **comment); + int type, char **comment); int import_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase, const char **errmsg_p); int import_ssh1_s(BinarySource *src, int type, @@ -1412,7 +1601,7 @@ int import_ssh1_s(BinarySource *src, int type, ssh2_userkey *import_ssh2(const Filename *filename, int type, char *passphrase, const char **errmsg_p); ssh2_userkey *import_ssh2_s(BinarySource *src, int type, - char *passphrase, const char **errmsg_p); + char *passphrase, const char **errmsg_p); bool export_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase); bool export_ssh2(const Filename *filename, int type, @@ -1709,6 +1898,7 @@ void old_keyfile_warning(void); X(BUG_CHOKES_ON_WINADJ) \ X(BUG_SENDS_LATE_REQUEST_REPLY) \ X(BUG_SSH2_OLDGEX) \ + X(BUG_REQUIRES_FILTERED_KEXINIT) \ /* end of list */ #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; @@ -1726,13 +1916,14 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); alloc_channel_id_general(tree, offsetof(type, localid))) void add_to_commasep(strbuf *buf, const char *data); +void add_to_commasep_pl(strbuf *buf, ptrlen data); bool get_commasep_word(ptrlen *list, ptrlen *word); SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); @@ -1744,3 +1935,35 @@ bool ssh_transient_hostkey_cache_verify( bool ssh_transient_hostkey_cache_has( ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg); bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc); + +/* + * Protocol definitions for authentication helper plugins + */ + +#define AUTHPLUGIN_MSG_NAMES(X) \ + X(PLUGIN_INIT, 1) \ + X(PLUGIN_INIT_RESPONSE, 2) \ + X(PLUGIN_PROTOCOL, 3) \ + X(PLUGIN_PROTOCOL_ACCEPT, 4) \ + X(PLUGIN_PROTOCOL_REJECT, 5) \ + X(PLUGIN_AUTH_SUCCESS, 6) \ + X(PLUGIN_AUTH_FAILURE, 7) \ + X(PLUGIN_INIT_FAILURE, 8) \ + X(PLUGIN_KI_SERVER_REQUEST, 20) \ + X(PLUGIN_KI_SERVER_RESPONSE, 21) \ + X(PLUGIN_KI_USER_REQUEST, 22) \ + X(PLUGIN_KI_USER_RESPONSE, 23) \ + /* end of list */ + +#define PLUGIN_PROTOCOL_MAX_VERSION 2 /* the highest version we speak */ + +enum { + #define ENUMDECL(name, value) name = value, + AUTHPLUGIN_MSG_NAMES(ENUMDECL) + #undef ENUMDECL + + /* Error codes internal to this implementation, indicating failure + * to receive a meaningful packet at all */ + PLUGIN_NOTYPE = 256, /* packet too short to have a type */ + PLUGIN_EOF = 257 /* EOF from auth plugin */ +}; diff --git a/code/ssh/CMakeLists.txt b/code/ssh/CMakeLists.txt index 4b0e03fe..d2b35311 100644 --- a/code/ssh/CMakeLists.txt +++ b/code/ssh/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(sshcommon OBJECT bpp1.c bpp2.c bpp-bare.c + ca-config.c censor1.c censor2.c common.c diff --git a/code/ssh/bpp2.c b/code/ssh/bpp2.c index dc98e27c..e019dd2e 100644 --- a/code/ssh/bpp2.c +++ b/code/ssh/bpp2.c @@ -73,15 +73,6 @@ BinaryPacketProtocol *ssh2_bpp_new( static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) { - /* - * We must free the MAC before the cipher, because sometimes the - * MAC is not actually separately allocated but just a different - * facet of the same object as the cipher, in which case - * ssh2_mac_free does nothing and ssh_cipher_free does the actual - * freeing. So if we freed the cipher first and then tried to - * dereference the MAC's vtable pointer to find out how to free - * that too, we'd be accessing freed memory. - */ if (s->out.mac) ssh2_mac_free(s->out.mac); if (s->out.cipher) @@ -141,6 +132,12 @@ void ssh2_bpp_new_outgoing_crypto( s->out.etm_mode = etm_mode; if (mac) { s->out.mac = ssh2_mac_new(mac, s->out.cipher); + /* + * Important that mac_setkey comes after cipher_setkey, + * because in the case where the MAC makes use of the cipher + * (e.g. AES-GCM), it will need the cipher to be keyed + * already. + */ ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); bpp_logevent("Initialised %s outbound MAC algorithm%s%s", @@ -198,6 +195,7 @@ void ssh2_bpp_new_incoming_crypto( s->in.etm_mode = etm_mode; if (mac) { s->in.mac = ssh2_mac_new(mac, s->in.cipher); + /* MAC setkey has to follow cipher, just as in outgoing_crypto above */ ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); bpp_logevent("Initialised %s inbound MAC algorithm%s%s", @@ -522,6 +520,10 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) dts_consume(&s->stats->in, s->packetlen); s->pktin->sequence = s->in.sequence++; + if (s->in.cipher) + ssh_cipher_next_message(s->in.cipher); + if (s->in.mac) + ssh2_mac_next_message(s->in.mac); s->length = s->packetlen - s->pad; assert(s->length >= 0); @@ -828,6 +830,10 @@ static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) } s->out.sequence++; /* whether or not we MACed */ + if (s->out.cipher) + ssh_cipher_next_message(s->out.cipher); + if (s->out.mac) + ssh2_mac_next_message(s->out.mac); dts_consume(&s->stats->out, origlen + padding); } diff --git a/code/ssh/ca-config.c b/code/ssh/ca-config.c new file mode 100644 index 00000000..8c180b36 --- /dev/null +++ b/code/ssh/ca-config.c @@ -0,0 +1,497 @@ +/* + * Define and handle the configuration dialog box for SSH host CAs, + * using the same portable dialog specification API as config.c. + */ + +#include "putty.h" +#include "dialog.h" +#include "storage.h" +#include "tree234.h" +#include "ssh.h" + +const bool has_ca_config_box = true; + +#define NRSATYPES 3 + +struct ca_state { + dlgcontrol *ca_name_edit; + dlgcontrol *ca_reclist; + dlgcontrol *ca_pubkey_edit; + dlgcontrol *ca_pubkey_info; + dlgcontrol *ca_validity_edit; + dlgcontrol *rsa_type_checkboxes[NRSATYPES]; + char *name, *pubkey, *validity; + tree234 *ca_names; /* stores plain 'char *' */ + ca_options opts; + strbuf *ca_pubkey_blob; +}; + +static int ca_name_compare(void *av, void *bv) +{ + return strcmp((const char *)av, (const char *)bv); +} + +static inline void clear_string_tree(tree234 *t) +{ + char *p; + while ((p = delpos234(t, 0)) != NULL) + sfree(p); +} + +static void ca_state_free(void *vctx) +{ + struct ca_state *st = (struct ca_state *)vctx; + clear_string_tree(st->ca_names); + freetree234(st->ca_names); + sfree(st->name); + sfree(st->validity); + sfree(st); +} + +static void ca_refresh_name_list(struct ca_state *st) +{ + clear_string_tree(st->ca_names); + + host_ca_enum *hce = enum_host_ca_start(); + if (hce) { + strbuf *namebuf = strbuf_new(); + + while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) { + char *name = dupstr(namebuf->s); + char *added = add234(st->ca_names, name); + /* Just imaginable that concurrent filesystem access might + * cause a repetition; avoid leaking memory if so */ + if (added != name) + sfree(name); + } + + strbuf_free(namebuf); + enum_host_ca_finish(hce); + } +} + +static void set_from_hca(struct ca_state *st, host_ca *hca) +{ + sfree(st->name); + st->name = dupstr(hca->name ? hca->name : ""); + + sfree(st->pubkey); + if (hca->ca_public_key) + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); + else + st->pubkey = dupstr(""); + + st->validity = dupstr(hca->validity_expression ? + hca->validity_expression : ""); + + st->opts = hca->opts; /* structure copy */ +} + +static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp) +{ + char *text = NULL; + ssh_key *key = NULL; + strbuf *blob = strbuf_new(); + + ptrlen data = ptrlen_from_asciz(st->pubkey); + + if (st->ca_pubkey_blob) + strbuf_free(st->ca_pubkey_blob); + st->ca_pubkey_blob = NULL; + + if (!data.len) { + text = dupstr(" "); + goto out; + } + + /* + * See if we have a plain base64-encoded public key blob. + */ + if (base64_valid(data)) { + base64_decode_bs(BinarySink_UPCAST(blob), data); + } else { + /* + * Otherwise, try to decode as if it was a public key _file_. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, data); + const char *error; + if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) { + text = dupprintf("Cannot decode key: %s", error); + goto out; + } + } + + ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob)); + if (!alg_name.len) { + text = dupstr("Invalid key (no key type)"); + goto out; + } + + const ssh_keyalg *alg = find_pubkey_alg_len(alg_name); + if (!alg) { + text = dupprintf("Unrecognised key type '%.*s'", + PTRLEN_PRINTF(alg_name)); + goto out; + } + if (alg->is_certificate) { + text = dupprintf("CA key may not be a certificate (type is '%.*s')", + PTRLEN_PRINTF(alg_name)); + goto out; + } + + key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob)); + if (!key) { + text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name)); + goto out; + } + + text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT); + st->ca_pubkey_blob = blob; + blob = NULL; /* prevent free */ + + out: + dlg_text_set(st->ca_pubkey_info, dp, text); + if (key) + ssh_key_free(key); + sfree(text); + if (blob) + strbuf_free(blob); +} + +static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) +{ + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + host_ca *hca = host_ca_load(name); + if (!hca) { + char *msg = dupprintf("Unable to load host CA record '%s'", name); + dlg_error_msg(dp, msg); + sfree(msg); + return; + } + + set_from_hca(st, hca); + host_ca_free(hca); + + dlg_refresh(st->ca_name_edit, dp); + dlg_refresh(st->ca_pubkey_edit, dp); + dlg_refresh(st->ca_validity_edit, dp); + for (size_t i = 0; i < NRSATYPES; i++) + dlg_refresh(st->rsa_type_checkboxes[i], dp); + ca_refresh_pubkey_info(st, dp); +} + +static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dp, 0); +} + +static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->name); + } else if (event == EVENT_VALCHANGE) { + sfree(st->name); + st->name = dlg_editbox_get(ctrl, dp); + + /* + * Try to auto-select the typed name in the list. + */ + int index; + if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index)) + index = count234(st->ca_names) - 1; + if (index >= 0) + dlg_listbox_select(st->ca_reclist, dp, index); + } +} + +static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + const char *name; + for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++) + dlg_listbox_add(ctrl, dp, name); + dlg_update_done(ctrl, dp); + } else if (event == EVENT_ACTION) { + /* Double-clicking a session loads it */ + ca_load_selected_record(st, dp); + } +} + +static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + ca_load_selected_record(st, dp); + } +} + +static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + if (!*st->validity) { + dlg_error_msg(dp, "No validity expression configured " + "for this key"); + return; + } + + char *error_msg; + ptrlen error_loc; + if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) { + char *error_full = dupprintf("Error in expression: %s", error_msg); + dlg_error_msg(dp, error_full); + dlg_set_focus(st->ca_validity_edit, dp); + dlg_editbox_select_range( + st->ca_validity_edit, dp, + (const char *)error_loc.ptr - st->validity, error_loc.len); + sfree(error_msg); + sfree(error_full); + return; + } + + if (!st->ca_pubkey_blob) { + dlg_error_msg(dp, "No valid CA public key entered"); + return; + } + + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->name = dupstr(st->name); + hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf( + st->ca_pubkey_blob)); + hca->validity_expression = dupstr(st->validity); + hca->opts = st->opts; /* structure copy */ + + char *error = host_ca_save(hca); + host_ca_free(hca); + + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + + char *error = host_ca_delete(name); + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->pubkey); + } else if (event == EVENT_VALCHANGE) { + sfree(st->pubkey); + st->pubkey = dlg_editbox_get(ctrl, dp); + ca_refresh_pubkey_info(st, dp); + } +} + +static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + Filename *filename = dlg_filesel_get(ctrl, dp); + strbuf *keyblob = strbuf_new(); + const char *load_error; + bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob), + NULL, &load_error); + if (!ok) { + char *message = dupprintf( + "Unable to load public key from '%s': %s", + filename_to_str(filename), load_error); + dlg_error_msg(dp, message); + sfree(message); + } else { + sfree(st->pubkey); + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(keyblob), 0)); + dlg_refresh(st->ca_pubkey_edit, dp); + } + filename_free(filename); + strbuf_free(keyblob); + } +} + +static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->validity); + } else if (event == EVENT_VALCHANGE) { + sfree(st->validity); + st->validity = dlg_editbox_get(ctrl, dp); + } +} + +static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + size_t offset = ctrl->context2.i; + bool *option = (bool *)((char *)&st->opts + offset); + + if (event == EVENT_REFRESH) { + dlg_checkbox_set(ctrl, dp, *option); + } else if (event == EVENT_VALCHANGE) { + *option = dlg_checkbox_get(ctrl, dp); + } +} + +void setup_ca_config_box(struct controlbox *b) +{ + struct controlset *s; + dlgcontrol *c; + + /* Internal state for manipulating the host CA system */ + struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( + b, sizeof(struct ca_state), ca_state_free); + memset(st, 0, sizeof(*st)); + st->ca_names = newtree234(ca_name_compare); + st->validity = dupstr(""); + ca_refresh_name_list(st); + + /* Initialise the settings to a default blank host_ca */ + { + host_ca *hca = host_ca_new(); + set_from_hca(st, hca); + host_ca_free(hca); + } + + /* Action area, with the Done button in it */ + s = ctrl_getset(b, "", "", ""); + ctrl_columns(s, 5, 20, 20, 20, 20, 20); + c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert), + ca_ok_handler, P(st)); + c->button.iscancel = true; + c->column = 4; + + /* Load/save box, as similar as possible to the main saved sessions one */ + s = ctrl_getset(b, "Main", "loadsave", + "Load, save or delete a host CA record"); + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, "Name for this CA (shown in log messages)", + 'n', 100, HELPCTX(ssh_kex_cert), + ca_name_handler, P(st), P(NULL)); + c->column = 0; + st->ca_name_edit = c; + /* Reset columns so that the buttons are alongside the list, rather + * than alongside that edit box. */ + ctrl_columns(s, 1, 100); + ctrl_columns(s, 2, 75, 25); + c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert), + ca_reclist_handler, P(st)); + c->column = 0; + c->listbox.height = 6; + st->ca_reclist = c; + c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert), + ca_load_handler, P(st)); + c->column = 1; + c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert), + ca_save_handler, P(st)); + c->column = 1; + c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert), + ca_delete_handler, P(st)); + c->column = 1; + + s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record"); + + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, + HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler, + P(st), P(NULL)); + c->column = 0; + st->ca_pubkey_edit = c; + c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false, + "Select public key file of certification authority", + HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); + c->fileselect.just_button = true; + c->align_next_to = st->ca_pubkey_edit; + c->column = 1; + ctrl_columns(s, 1, 100); + st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert)); + c->text.wrap = false; + + s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do"); + + c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100, + HELPCTX(ssh_cert_valid_expr), ca_validity_handler, + P(st), P(NULL)); + st->ca_validity_edit = c; + + ctrl_columns(s, 4, 44, 18, 18, 18); + c = ctrl_text(s, "Signature types (RSA keys only):", + HELPCTX(ssh_cert_rsa_hash)); + c->column = 0; + dlgcontrol *sigtypelabel = c; + c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), + ca_rsa_type_handler, P(st)); + c->column = 1; + c->align_next_to = sigtypelabel; + c->context2 = I(offsetof(ca_options, permit_rsa_sha1)); + st->rsa_type_checkboxes[0] = c; + c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), + ca_rsa_type_handler, P(st)); + c->column = 2; + c->align_next_to = sigtypelabel; + c->context2 = I(offsetof(ca_options, permit_rsa_sha256)); + st->rsa_type_checkboxes[1] = c; + c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), + ca_rsa_type_handler, P(st)); + c->column = 3; + c->align_next_to = sigtypelabel; + c->context2 = I(offsetof(ca_options, permit_rsa_sha512)); + st->rsa_type_checkboxes[2] = c; + ctrl_columns(s, 1, 100); +} diff --git a/code/ssh/channel.h b/code/ssh/channel.h index 14dfccb5..d4eb78aa 100644 --- a/code/ssh/channel.h +++ b/code/ssh/channel.h @@ -84,10 +84,10 @@ static inline bool chan_want_close(Channel *ch, bool leof, bool reof) static inline bool chan_rcvd_exit_status(Channel *ch, int status) { return ch->vt->rcvd_exit_status(ch, status); } static inline bool chan_rcvd_exit_signal( - Channel *ch, ptrlen sig, bool core, ptrlen msg) + Channel *ch, ptrlen sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } static inline bool chan_rcvd_exit_signal_numeric( - Channel *ch, int sig, bool core, ptrlen msg) + Channel *ch, int sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } static inline bool chan_run_shell(Channel *ch) { return ch->vt->run_shell(ch); } diff --git a/code/ssh/common.c b/code/ssh/common.c index 0c9f51e3..a1b4d77d 100644 --- a/code/ssh/common.c +++ b/code/ssh/common.c @@ -607,11 +607,16 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) * lists of protocol identifiers in SSH-2. */ -void add_to_commasep(strbuf *buf, const char *data) +void add_to_commasep_pl(strbuf *buf, ptrlen data) { if (buf->len > 0) put_byte(buf, ','); - put_data(buf, data, strlen(data)); + put_datapl(buf, data); +} + +void add_to_commasep(strbuf *buf, const char *data) +{ + add_to_commasep_pl(buf, ptrlen_from_asciz(data)); } bool get_commasep_word(ptrlen *list, ptrlen *word) @@ -835,7 +840,7 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) #undef BITMAP_UNIVERSAL #undef BITMAP_CONDITIONAL -#undef SSH1_BITMAP_WORD +#undef SSH2_BITMAP_WORD /* ---------------------------------------------------------------------- * Centralised component of SSH host key verification. @@ -850,8 +855,8 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx) + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { /* * First, check if the Conf includes a manual specification of the @@ -919,10 +924,155 @@ SeatPromptResult verify_ssh_host_key( * The key is either missing from the cache, or does not match. * Either way, fall back to an interactive prompt from the Seat. */ - bool mismatch = (storage_status != 1); - return seat_confirm_ssh_host_key( - iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, - callback, ctx); + SeatDialogText *text = seat_dialog_text_new(); + const SeatDialogPromptDescriptions *pds = + seat_prompt_descriptions(iseat.seat); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + seat_dialog_text_append( + text, SDT_TITLE, "%s Security Alert", appname); + + HelpCtx helpctx; + + if (key && ssh_key_alg(key)->is_certificate) { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "This server presented a certified host key:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + if (ca_count) { + seat_dialog_text_append( + text, SDT_PARA, "which was signed by a different " + "certification authority from the %s %s is configured to " + "trust for this server.", ca_count > 1 ? "ones" : "one", + appname); + if (storage_status == 2) { + seat_dialog_text_append( + text, SDT_PARA, "ALSO, that key does not match the key " + "%s had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm AND " + "the server administrator has changed the host key, or " + "you have actually connected to another computer " + "pretending to be the server."); + } else { + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm, or " + "you have actually connected to another computer " + "pretending to be the server."); + } + } else { + assert(storage_status == 2); + seat_dialog_text_append( + text, SDT_PARA, "which does not match the certified key %s " + "had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "This means that either the server " + "administrator has changed the host key, or you have actually " + "connected to another computer pretending to be the server."); + } + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_cert_mismatch); + } else if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "The host key is not cached for this server:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "You have no guarantee that the server is the " + "computer you think it is."); + seat_dialog_text_append( + text, SDT_PARA, "The server's %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_hostkey_absent); + } else { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "The host key does not match the one %s has " + "cached for this server:", appname); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "This means that either the server administrator " + "has changed the host key, or you have actually connected to " + "another computer pretending to be the server."); + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_hostkey_changed); + } + + /* The above text is printed even in batch mode. Here's where we stop if + * we can't present interactive prompts. */ + seat_dialog_text_append( + text, SDT_BATCH_ABORT, "Connection abandoned."); + + if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "If you trust this host, %s to add the key to " + "%s's cache and carry on connecting.", + pds->hk_accept_action, appname); + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting just once, " + "without adding the key to the cache, %s.", + pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you do not trust this host, %s to abandon the " + "connection.", pds->hk_cancel_action); + seat_dialog_text_append( + text, SDT_PROMPT, "Store key in cache?"); + } else { + seat_dialog_text_append( + text, SDT_PARA, "If you were expecting this change and trust the " + "new key, %s to update %s's cache and carry on connecting.", + pds->hk_accept_action, appname); + if (key && ssh_key_alg(key)->is_certificate) { + seat_dialog_text_append( + text, SDT_PARA, "(Storing this certified key in the cache " + "will NOT cause its certification authority to be trusted " + "for any other key or host.)"); + } + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting but without " + "updating the cache, %s.", pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you want to abandon the connection " + "completely, %s to cancel. %s is the ONLY guaranteed safe choice.", + pds->hk_cancel_action, pds->hk_cancel_action_Participle); + seat_dialog_text_append( + text, SDT_PROMPT, "Update cached key?"); + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Full text of host's public key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp); + + if (fingerprints[SSH_FPTYPE_SHA256]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_SHA256]); + } + if (fingerprints[SSH_FPTYPE_MD5]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_MD5]); + } + + SeatPromptResult toret = seat_confirm_ssh_host_key( + iseat, host, port, keytype, keystr, text, helpctx, callback, ctx); + seat_dialog_text_free(text); + return toret; } /* ---------------------------------------------------------------------- diff --git a/code/ssh/connection1-server.c b/code/ssh/connection1-server.c index 1123327c..cc69bdb3 100644 --- a/code/ssh/connection1-server.c +++ b/code/ssh/connection1-server.c @@ -113,15 +113,15 @@ bool ssh1_handle_direction_specific_packet( BinarySource_UPCAST(pktin), 1); if (get_err(pktin)) { - ppl_logevent("Unable to decode pty request packet"); - success = false; + ppl_logevent("Unable to decode pty request packet"); + success = false; } else if (!chan_allocate_pty( s->mainchan_chan, termtype, width, height, pixwidth, pixheight, modes)) { - ppl_logevent("Unable to allocate a pty"); - success = false; + ppl_logevent("Unable to allocate a pty"); + success = false; } else { - success = true; + success = true; } pktout = ssh_bpp_new_pktout( diff --git a/code/ssh/connection2.c b/code/ssh/connection2.c index ec330927..77b8d0ec 100644 --- a/code/ssh/connection2.c +++ b/code/ssh/connection2.c @@ -989,7 +989,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) */ if (ssh2_connection_need_antispoof_prompt(s)) { s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); - s->antispoof_prompt->to_server = true; + s->antispoof_prompt->to_server = false; s->antispoof_prompt->from_server = false; s->antispoof_prompt->name = dupstr("Authentication successful"); add_prompt( @@ -1565,8 +1565,8 @@ static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) } static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *data, int datalen, const char *additional_log_text) + ConnectionLayer *cl, unsigned id, int type, + const void *data, int datalen, const char *additional_log_text) { struct ssh2_connection_state *s = container_of(cl, struct ssh2_connection_state, cl); diff --git a/code/ssh/gssc.c b/code/ssh/gssc.c index 0224afe2..d10caf8b 100644 --- a/code/ssh/gssc.c +++ b/code/ssh/gssc.c @@ -75,7 +75,7 @@ static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, gssctx->maj_stat = gss->inquire_cred_by_mech(&gssctx->min_stat, cred, (gss_OID) GSS_MECH_KRB5, - GSS_C_NO_NAME, + NULL, &time_rec, NULL, NULL); diff --git a/code/ssh/kex2-client.c b/code/ssh/kex2-client.c index 9a8f75e2..26159bb5 100644 --- a/code/ssh/kex2-client.c +++ b/code/ssh/kex2-client.c @@ -156,7 +156,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } } - s->K = dh_find_K(s->dh_ctx, s->f); + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -183,23 +185,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) mp_free(s->p); s->p = NULL; } } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing %s, using hash %s", desc, ssh_hash_alg(s->exhash)->text_name); + sfree(desc); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); - if (!s->ecdh_key) { - ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); - *aborted = true; - return; - } + s->ecdh_key = ecdh_key_new(s->kex_alg, false); pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_stringsb(pktout, pubpoint); } @@ -222,7 +220,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_string(s->exhash, pubpoint->u, pubpoint->len); strbuf_free(pubpoint); } @@ -230,8 +228,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { + bool ok = ecdh_key_getkey(s->ecdh_key, keydata, + BinarySink_UPCAST(s->kex_shared_secret)); + if (!get_err(pktin) && !ok) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH reply"); *aborted = true; @@ -246,10 +245,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; #ifndef NO_GSSAPI - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + } else if (kex_is_gss(s->kex_alg)) { ptrlen data; s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; @@ -277,14 +276,25 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (s->nbits > s->kex_alg->hash->hlen * 8) s->nbits = s->kex_alg->hash->hlen * 8; - if (dh_is_gex(s->kex_alg)) { + assert(!s->ecdh_key); + assert(!s->dh_ctx); + + if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) { + s->ecdh_key = ecdh_key_new(s->kex_alg, false); + + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s", + desc, ssh_hash_alg(s->exhash)->text_name); + sfree(desc); + } else if (dh_is_gex(s->kex_alg)) { /* * Work out how big a DH group we will need to allow that * much data. */ s->pbits = 512 << ((s->nbits - 1) / 64); ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits", s->pbits); + "group exchange, with minimum %d bits, and hash %s", + s->pbits, ssh_hash_alg(s->exhash)->text_name); pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); put_uint32(pktout, s->pbits); /* min */ put_uint32(pktout, s->pbits); /* preferred */ @@ -315,14 +325,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } else { s->dh_ctx = dh_setup_group(s->kex_alg); ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" - " standard group \"%s\"", s->kex_alg->groupname); + " standard group \"%s\" and hash %s", + s->kex_alg->groupname, + ssh_hash_alg(s->exhash)->text_name); } - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " - "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); /* Now generate e for Diffie-Hellman. */ seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx); + if (s->ecdh_key) { + s->ebuf = strbuf_new_nm(); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf)); + } else { + s->e = dh_create_e(s->dh_ctx); + } if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); @@ -386,7 +401,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } put_string(pktout, s->gss_sndtok.value, s->gss_sndtok.length); - put_mp_ssh2(pktout, s->e); + if (s->ecdh_key) { + put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf)); + } else { + put_mp_ssh2(pktout, s->e); + } pq_push(s->ppl.out_pq, pktout); s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); ppl_logevent("GSSAPI key exchange initialised"); @@ -413,7 +432,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) continue; case SSH2_MSG_KEXGSS_COMPLETE: s->complete_rcvd = true; - s->f = get_mp_ssh2(pktin); + if (s->ecdh_key) { + s->fbuf = strbuf_dup_nm(get_string(pktin)); + } else { + s->f = get_mp_ssh2(pktin); + } data = get_string(pktin); s->mic.value = (char *)data.ptr; s->mic.length = data.len; @@ -476,7 +499,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || !s->complete_rcvd); - { + if (s->ecdh_key) { + bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf), + BinarySink_UPCAST(s->kex_shared_secret)); + if (!ok) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in GSSAPI ECDH reply"); + *aborted = true; + return; + } + } else { const char *err = dh_validate_f(s->dh_ctx, s->f); if (err) { ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " @@ -484,8 +516,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) *aborted = true; return; } + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); } - s->K = dh_find_K(s->dh_ctx, s->f); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -493,29 +527,42 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (!s->hkey) put_stringz(s->exhash, ""); - if (dh_is_gex(s->kex_alg)) { - /* min, preferred, max */ - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits * 2); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); + if (s->ecdh_key) { + put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf)); + put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf)); + } else { + if (dh_is_gex(s->kex_alg)) { + /* min, preferred, max */ + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits * 2); + + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); /* * MIC verification is done below, after we compute the hash * used as the MIC input. */ - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; + if (s->ecdh_key) { + ecdh_key_free(s->ecdh_key); + s->ecdh_key = NULL; + strbuf_free(s->ebuf); s->ebuf = NULL; + strbuf_free(s->fbuf); s->fbuf = NULL; + } else { + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + mp_free(s->g); s->g = NULL; + mp_free(s->p); s->p = NULL; + } } #endif } else { @@ -584,15 +631,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) strbuf *buf, *outstr; mp_int *tmp = mp_random_bits(nbits - 1); - s->K = mp_power_2(nbits - 1); - mp_add_into(s->K, s->K, tmp); + mp_int *K = mp_power_2(nbits - 1); + mp_add_into(K, K, tmp); mp_free(tmp); /* * Encode this as an mpint. */ buf = strbuf_new_nm(); - put_mp_ssh2(buf, s->K); + put_mp_ssh2(buf, K); + + /* + * Store a copy as the output shared secret from the kex. + */ + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); /* * Encrypt it with the given RSA key. @@ -639,7 +692,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh2transport_finalise_exhash(s); #ifndef NO_GSSAPI - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { Ssh_gss_buf gss_buf; SSH_GSS_CLEAR_BUF(&s->gss_buf); @@ -668,7 +721,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * If this the first KEX, save the GSS context for "gssapi-keyex" * authentication. * - * http://tools.ietf.org/html/rfc4462#section-4 + * https://www.rfc-editor.org/rfc/rfc4462#section-4 * * This method may be used only if the initial key exchange was * performed using a GSS-API-based key exchange method defined in @@ -687,7 +740,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->dh_ctx = NULL; /* In GSS keyex there's no hostkey signature to verify */ - if (s->kex_alg->main_type != KEXTYPE_GSS) { + if (!kex_is_gss(s->kex_alg)) { if (!s->hkey) { ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); *aborted = true; @@ -706,21 +759,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } - s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); + s->keystr = s->hkey ? ssh_key_cache_str(s->hkey) : NULL; #ifndef NO_GSSAPI if (s->gss_kex_used) { /* * In a GSS-based session, check the host key (if any) against * the transient host key cache. */ - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { /* * We've just done a GSS key exchange. If it gave us a * host key, store it. */ if (s->hkey) { - char *fingerprint = ssh2_fingerprint( + char *fingerprint = ssh2_double_fingerprint( s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("GSS kex provided fallback host key:"); ppl_logevent("%s", fingerprint); @@ -778,8 +831,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * An exception is if this was the non-GSS key exchange we * triggered on purpose to populate the transient cache. */ - assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + assert(s->hkey); /* only KEXTYPE_GSS* lets this be null */ + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); if (s->need_gss_transient_hostkey) { ppl_logevent("Post-GSS rekey provided fallback host key:"); @@ -839,49 +893,130 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } + ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; + char **fingerprints = ssh2_all_fingerprints(s->hkey); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent("%s", fingerprints[fptype_default]); + /* - * Authenticate remote host: verify host key. (We've already - * checked the signature of the exchange hash.) + * Authenticate remote host: verify host key, either by + * certification or by the local host key cache. + * + * (We've already checked the signature of the exchange + * hash.) */ + if (ssh_key_alg(s->hkey)->is_certificate) { + char *base_fp = ssh2_fingerprint( + s->hkey, ssh_fptype_to_cert(fptype_default)); + ppl_logevent("Host key is a certificate. " + "Hash including certificate:"); + ppl_logevent("%s", base_fp); + sfree(base_fp); + + strbuf *id_string = strbuf_new(); + StripCtrlChars *id_string_scc = stripctrl_new( + BinarySink_UPCAST(id_string), false, L'\0'); + ssh_key_cert_id_string( + s->hkey, BinarySink_UPCAST(id_string_scc)); + stripctrl_free(id_string_scc); + ppl_logevent("Certificate ID string is \"%s\"", id_string->s); + strbuf_free(id_string); + + strbuf *ca_pub = strbuf_new(); + ssh_key_ca_public_blob(s->hkey, BinarySink_UPCAST(ca_pub)); + host_ca hca_search = { .ca_public_key = ca_pub }; + host_ca *hca_found = find234(s->host_cas, &hca_search, NULL); + + char *ca_fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ca_pub), + fptype_default); + ppl_logevent("Fingerprint of certification authority:"); + ppl_logevent("%s", ca_fp); + sfree(ca_fp); + + strbuf_free(ca_pub); + + strbuf *error = strbuf_new(); + bool cert_ok = false; + + if (!hca_found) { + put_fmt(error, "Certification authority is not trusted"); + } else { + ppl_logevent("Certification authority matches '%s'", + hca_found->name); + cert_ok = ssh_key_check_cert( + s->hkey, + true, /* host certificate */ + ptrlen_from_asciz(s->savedhost), + time(NULL), + &hca_found->opts, + BinarySink_UPCAST(error)); + } + if (cert_ok) { + strbuf_free(error); + ssh2_free_all_fingerprints(fingerprints); + ppl_logevent("Accepted certificate"); + goto host_key_ok; + } else { + ppl_logevent("Rejected host key certificate: %s", + error->s); + strbuf_free(error); + /* now fall through into normal host key checking */ + } + } + { - ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; char *keydisp = ssh2_pubkey_openssh_str(&uk); - char **fingerprints = ssh2_all_fingerprints(s->hkey); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprints[fptype_default]); + int ca_count = ssh_key_alg(s->hkey)->is_certificate ? + count234(s->host_cas) : 0; s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, - fingerprints, ssh2_transport_dialog_callback, s); + fingerprints, ca_count, ssh2_transport_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); sfree(keydisp); - } #ifdef FUZZING - s->spr = SPR_OK; + s->spr = SPR_OK; #endif - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - *aborted = true; - ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); - return; + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + *aborted = true; + ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); + return; + } + + if (ssh_key_alg(s->hkey)->is_certificate) { + /* + * Explain what's going on in the Event Log: if we + * got here by way of a certified key whose + * certificate we didn't like, then we should + * explain why we chose to continue with the + * connection anyway! + */ + ppl_logevent("Accepting certified host key anyway based " + "on cache"); + } } + host_key_ok: + /* * Save this host key, to check against the one presented in * subsequent rekeys. */ - s->hostkey_str = s->keystr; - s->keystr = NULL; + strbuf_clear(s->hostkeyblob); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); } else if (s->cross_certifying) { assert(s->hkey); assert(ssh_key_alg(s->hkey) == s->cross_certifying); - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("Storing additional host key for this host:"); ppl_logevent("%s", fingerprint); sfree(fingerprint); @@ -892,8 +1027,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Don't forget to store the new key as the one we'll be * re-checking in future normal rekeys. */ - s->hostkey_str = s->keystr; - s->keystr = NULL; + strbuf_clear(s->hostkeyblob); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); } else { /* * In a rekey, we never present an interactive host key @@ -901,8 +1036,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * enforce that the key we're seeing this time is identical to * the one we saw before. */ - assert(s->keystr); /* filled in by prior key exchange */ - if (strcmp(s->hostkey_str, s->keystr)) { + strbuf *thisblob = strbuf_new(); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(thisblob)); + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(thisblob), + ptrlen_from_strbuf(s->hostkeyblob)); + strbuf_free(thisblob); + if (!match) { #ifndef FUZZING ssh_sw_abort(s->ppl.ssh, "Host key was different in repeat key exchange"); diff --git a/code/ssh/kex2-server.c b/code/ssh/kex2-server.c index 3c017077..570d7750 100644 --- a/code/ssh/kex2-server.c +++ b/code/ssh/kex2-server.c @@ -161,7 +161,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } } - s->K = dh_find_K(s->dh_ctx, s->f); + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); if (dh_is_gex(s->kex_alg)) { if (s->dh_got_size_bounds) @@ -189,12 +191,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) mp_free(s->p); s->p = NULL; } } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing %s, using hash %s", desc, ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + sfree(desc); - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + s->ecdh_key = ecdh_key_new(s->kex_alg, true); if (!s->ecdh_key) { ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); *aborted = true; @@ -217,8 +219,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { + bool ok = ecdh_key_getkey(s->ecdh_key, keydata, + BinarySink_UPCAST(s->kex_shared_secret)); + if (!get_err(pktin) && !ok) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH initial packet"); *aborted = true; @@ -230,14 +233,14 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) put_stringpl(pktout, s->hostkeydata); { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_string(s->exhash, pubpoint->u, pubpoint->len); put_stringsb(pktout, pubpoint); } put_stringsb(pktout, finalise_and_sign_exhash(s)); pq_push(s->ppl.out_pq, pktout); - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; } else if (s->kex_alg->main_type == KEXTYPE_GSS) { ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); @@ -301,19 +304,23 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } + mp_int *K; { ptrlen encrypted_secret = get_string(pktin); put_stringpl(s->exhash, encrypted_secret); - s->K = ssh_rsakex_decrypt( + K = ssh_rsakex_decrypt( s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); } - if (!s->K) { + if (!K) { ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); *aborted = true; return; } + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); + if (s->rsa_kex_key_needs_freeing) { ssh_rsakex_freekey(s->rsa_kex_key); sfree(s->rsa_kex_key); diff --git a/code/ssh/login1-server.c b/code/ssh/login1-server.c index 30ff9026..d01c7f4c 100644 --- a/code/ssh/login1-server.c +++ b/code/ssh/login1-server.c @@ -50,7 +50,7 @@ static bool ssh1_login_server_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) { return false; } static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) {} + SessionSpecialCode code, int arg) {} static void ssh1_login_server_reconfigure( PacketProtocolLayer *ppl, Conf *conf) {} diff --git a/code/ssh/login1.c b/code/ssh/login1.c index 7b345c78..52aaea0b 100644 --- a/code/ssh/login1.c +++ b/code/ssh/login1.c @@ -243,7 +243,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, - "rsa", keystr, keydisp, fingerprints, + "rsa", keystr, keydisp, fingerprints, 0, ssh1_login_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); @@ -521,8 +521,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) RSA_SSH1_EXPONENT_FIRST); const char *blobend = get_ptr(s->asrc); - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); s->agent_keys[i].blob = make_ptrlen( blobstart, blobend - blobstart); @@ -867,8 +866,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) return; } } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { ssh1_login_setup_tis_scc(s); s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; ppl_logevent("Requested CryptoCard authentication"); diff --git a/code/ssh/mainchan.c b/code/ssh/mainchan.c index 5c1769ea..52492ff6 100644 --- a/code/ssh/mainchan.c +++ b/code/ssh/mainchan.c @@ -344,7 +344,7 @@ static void mainchan_open_failure(Channel *chan, const char *errtext) } static size_t mainchan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) + const void *data, size_t length) { assert(chan->vt == &mainchan_channelvt); mainchan *mc = container_of(chan, mainchan, chan); diff --git a/code/ssh/pgssapi.c b/code/ssh/pgssapi.c index 9d33220f..1730444d 100644 --- a/code/ssh/pgssapi.c +++ b/code/ssh/pgssapi.c @@ -9,38 +9,63 @@ #ifndef NO_LIBDL -/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */ -static const gss_OID_desc oids[] = { +/* Reserved static storage for GSS_oids. + * Constants of the form GSS_C_NT_* are specified by rfc 2744. + * Comments are quotes from RFC 2744 itself. + * + * These may be #defined to complex expressions by the local header + * file, if we're including one in static-GSSAPI mode. (For example, + * Heimdal defines them to things like + * (&__gss_c_nt_user_name_oid_desc).) So we only define them if + * needed. */ + +#ifndef GSS_C_NT_USER_NAME +static gss_OID_desc oid_GSS_C_NT_USER_NAME = { /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"}, + 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01", /* corresponding to an object-identifier value of * {iso(1) member-body(2) United States(840) mit(113554) * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant * GSS_C_NT_USER_NAME should be initialized to point - * to that gss_OID_desc. + * to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_USER_NAME = &oid_GSS_C_NT_USER_NAME; +#endif - * The implementation must reserve static storage for a +#ifndef GSS_C_NT_MACHINE_UID_NAME +static gss_OID_desc oid_GSS_C_NT_MACHINE_UID_NAME = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}, + 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02", /* corresponding to an object-identifier value of * {iso(1) member-body(2) United States(840) mit(113554) * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. * The constant GSS_C_NT_MACHINE_UID_NAME should be - * initialized to point to that gss_OID_desc. + * initialized to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_MACHINE_UID_NAME = &oid_GSS_C_NT_MACHINE_UID_NAME; +#endif - * The implementation must reserve static storage for a +#ifndef GSS_C_NT_STRING_UID_NAME +static gss_OID_desc oid_GSS_C_NT_STRING_UID_NAME = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"}, + 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03", /* corresponding to an object-identifier value of * {iso(1) member-body(2) United States(840) mit(113554) * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. * The constant GSS_C_NT_STRING_UID_NAME should be - * initialized to point to that gss_OID_desc. - * - * The implementation must reserve static storage for a + * initialized to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_STRING_UID_NAME = &oid_GSS_C_NT_STRING_UID_NAME; +#endif + +#ifndef GSS_C_NT_HOSTBASED_SERVICE_X +static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE_X = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + 6, "\x2b\x06\x01\x05\x06\x02", /* corresponding to an object-identifier value of * {iso(1) org(3) dod(6) internet(1) security(5) * nametypes(6) gss-host-based-services(2))}. The constant @@ -52,53 +77,58 @@ static const gss_OID_desc oids[] = { * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input * parameter, but should not be emitted by GSS-API - * implementations - * - * The implementation must reserve static storage for a + * implementations */ +}; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &oid_GSS_C_NT_HOSTBASED_SERVICE_X; +#endif + +#ifndef GSS_C_NT_HOSTBASED_SERVICE +static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}, + 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04", /* corresponding to an object-identifier value of {iso(1) * member-body(2) Unites States(840) mit(113554) infosys(1) * gssapi(2) generic(1) service_name(4)}. The constant * GSS_C_NT_HOSTBASED_SERVICE should be initialized - * to point to that gss_OID_desc. - * - * The implementation must reserve static storage for a + * to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = &oid_GSS_C_NT_HOSTBASED_SERVICE; +#endif + +#ifndef GSS_C_NT_ANONYMOUS +static gss_OID_desc oid_GSS_C_NT_ANONYMOUS = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + 6, "\x2b\x06\01\x05\x06\x03", /* corresponding to an object identifier value of * {1(iso), 3(org), 6(dod), 1(internet), 5(security), * 6(nametypes), 3(gss-anonymous-name)}. The constant * and GSS_C_NT_ANONYMOUS should be initialized to point - * to that gss_OID_desc. - * - * The implementation must reserve static storage for a + * to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_ANONYMOUS = &oid_GSS_C_NT_ANONYMOUS; +#endif + +#ifndef GSS_C_NT_EXPORT_NAME +static gss_OID_desc oid_GSS_C_NT_EXPORT_NAME = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, - /* corresponding to an object-identifier value of + 6, "\x2b\x06\x01\x05\x06\x04", + /* corresponding to an object-identifier value of * {1(iso), 3(org), 6(dod), 1(internet), 5(security), * 6(nametypes), 4(gss-api-exported-name)}. The constant * GSS_C_NT_EXPORT_NAME should be initialized to point * to that gss_OID_desc. */ }; - -/* Here are the constants which point to the static structure above. - * - * Constants of the form GSS_C_NT_* are specified by rfc 2744. - */ -const_gss_OID GSS_C_NT_USER_NAME = oids+0; -const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1; -const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2; -const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3; -const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4; -const_gss_OID GSS_C_NT_ANONYMOUS = oids+5; -const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6; +const_gss_OID GSS_C_NT_EXPORT_NAME = &oid_GSS_C_NT_EXPORT_NAME; +#endif #endif /* NO_LIBDL */ static gss_OID_desc gss_mech_krb5_desc = -{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +{ 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; /* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/ const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc; diff --git a/code/ssh/pgssapi.h b/code/ssh/pgssapi.h index 53d8cb61..3f21e0ff 100644 --- a/code/ssh/pgssapi.h +++ b/code/ssh/pgssapi.h @@ -53,9 +53,9 @@ typedef struct gss_channel_bindings_struct { gss_buffer_desc application_data; } *gss_channel_bindings_t; -typedef void * gss_ctx_id_t; -typedef void * gss_name_t; -typedef void * gss_cred_id_t; +typedef void *gss_ctx_id_t; +typedef void *gss_name_t; +typedef void *gss_cred_id_t; typedef OM_uint32 gss_qop_t; typedef int gss_cred_usage_t; diff --git a/code/ssh/portfwd.c b/code/ssh/portfwd.c index 11544564..b4eea3c9 100644 --- a/code/ssh/portfwd.c +++ b/code/ssh/portfwd.c @@ -514,7 +514,7 @@ void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc) } /* - called when someone connects to the local port + * called when someone connects to the local port */ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/code/ssh/ppl.h b/code/ssh/ppl.h index c3625166..78b08efa 100644 --- a/code/ssh/ppl.h +++ b/code/ssh/ppl.h @@ -109,12 +109,13 @@ PacketProtocolLayer *ssh2_transport_new( const SshServerConfig *ssc); PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, + const char *hostname, int port, const char *fullhostname, + Filename *keyfile, Filename *detached_cert, + bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, - bool try_ki_auth, - bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); + bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, + const char *auth_plugin); PacketProtocolLayer *ssh2_connection_new( Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, Conf *conf, const char *peer_verstring, bufchain *user_input, diff --git a/code/ssh/server.c b/code/ssh/server.c index f69bdd83..188426b3 100644 --- a/code/ssh/server.c +++ b/code/ssh/server.c @@ -122,6 +122,7 @@ static const SeatVtable server_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/code/ssh/sesschan.c b/code/ssh/sesschan.c index 978ffb9a..9776eaf2 100644 --- a/code/ssh/sesschan.c +++ b/code/ssh/sesschan.c @@ -199,6 +199,7 @@ static const SeatVtable sesschan_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -291,7 +292,11 @@ static char *sesschan_log_close_msg(Channel *chan) static void sesschan_set_input_wanted(Channel *chan, bool wanted) { - /* I don't think we need to do anything here */ + sesschan *sess = container_of(chan, sesschan, chan); + /* Request the back end to resume sending input, if it had become + * throttled by the channel window shortening */ + if (wanted && sess->backend) + backend_unthrottle(sess->backend, 0); } static void sesschan_start_backend(sesschan *sess, const char *cmd) diff --git a/code/ssh/sftp.c b/code/ssh/sftp.c index a76702f8..1f98c5ec 100644 --- a/code/ssh/sftp.c +++ b/code/ssh/sftp.c @@ -21,7 +21,7 @@ static void fxp_internal_error(const char *msg); * Client-specific parts of the send- and receive-packet system. */ -bool sftp_send(struct sftp_packet *pkt) +static bool sftp_send(struct sftp_packet *pkt) { bool ret; sftp_send_prepare(pkt); @@ -283,8 +283,7 @@ bool fxp_init(void) return false; } if (remotever > SFTP_PROTO_VERSION) { - fxp_internal_error - ("remote protocol is more advanced than we support"); + fxp_internal_error("remote protocol is more advanced than we support"); sftp_pkt_free(pktin); return false; } @@ -575,7 +574,7 @@ static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs) } bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) + struct fxp_attrs *attrs) { sfree(req); if (pktin->type == SSH_FXP_ATTRS) { diff --git a/code/ssh/sharing.c b/code/ssh/sharing.c index 7b52dc09..97337229 100644 --- a/code/ssh/sharing.c +++ b/code/ssh/sharing.c @@ -530,8 +530,8 @@ void sharestate_free(ssh_sharing_state *sharestate) sfree(sharestate); } -static struct share_halfchannel *share_add_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_halfchannel *share_add_halfchannel( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_halfchannel *hc = snew(struct share_halfchannel); hc->server_id = server_id; @@ -544,8 +544,8 @@ static struct share_halfchannel *share_add_halfchannel } } -static struct share_halfchannel *share_find_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_halfchannel *share_find_halfchannel( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_halfchannel dummyhc; dummyhc.server_id = server_id; @@ -559,9 +559,9 @@ static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, sfree(hc); } -static struct share_channel *share_add_channel - (struct ssh_sharing_connstate *cs, unsigned downstream_id, - unsigned upstream_id, unsigned server_id, int state, int maxpkt) +static struct share_channel *share_add_channel( + struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) { struct share_channel *chan = snew(struct share_channel); chan->downstream_id = downstream_id; @@ -598,16 +598,16 @@ static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, add234(cs->channels_by_server, chan); } -static struct share_channel *share_find_channel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) +static struct share_channel *share_find_channel_by_upstream( + struct ssh_sharing_connstate *cs, unsigned upstream_id) { struct share_channel dummychan; dummychan.upstream_id = upstream_id; return find234(cs->channels_by_us, &dummychan, NULL); } -static struct share_channel *share_find_channel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_channel *share_find_channel_by_server( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_channel dummychan; dummychan.server_id = server_id; @@ -626,9 +626,8 @@ static void share_remove_channel(struct ssh_sharing_connstate *cs, sfree(chan); } -static struct share_xchannel *share_add_xchannel - (struct ssh_sharing_connstate *cs, - unsigned upstream_id, unsigned server_id) +static struct share_xchannel *share_add_xchannel( + struct ssh_sharing_connstate *cs, unsigned upstream_id, unsigned server_id) { struct share_xchannel *xc = snew(struct share_xchannel); xc->upstream_id = upstream_id; @@ -647,16 +646,16 @@ static struct share_xchannel *share_add_xchannel return xc; } -static struct share_xchannel *share_find_xchannel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) +static struct share_xchannel *share_find_xchannel_by_upstream( + struct ssh_sharing_connstate *cs, unsigned upstream_id) { struct share_xchannel dummyxc; dummyxc.upstream_id = upstream_id; return find234(cs->xchannels_by_us, &dummyxc, NULL); } -static struct share_xchannel *share_find_xchannel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_xchannel *share_find_xchannel_by_server( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_xchannel dummyxc; dummyxc.server_id = server_id; @@ -664,16 +663,15 @@ static struct share_xchannel *share_find_xchannel_by_server } static void share_remove_xchannel(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) + struct share_xchannel *xc) { del234(cs->xchannels_by_us, xc); del234(cs->xchannels_by_server, xc); share_xchannel_free(xc); } -static struct share_forwarding *share_add_forwarding - (struct ssh_sharing_connstate *cs, - const char *host, int port) +static struct share_forwarding *share_add_forwarding( + struct ssh_sharing_connstate *cs, const char *host, int port) { struct share_forwarding *fwd = snew(struct share_forwarding); fwd->host = dupstr(host); @@ -687,8 +685,8 @@ static struct share_forwarding *share_add_forwarding return fwd; } -static struct share_forwarding *share_find_forwarding - (struct ssh_sharing_connstate *cs, const char *host, int port) +static struct share_forwarding *share_find_forwarding( + struct ssh_sharing_connstate *cs, const char *host, int port) { struct share_forwarding dummyfwd, *ret; dummyfwd.host = dupstr(host); @@ -989,8 +987,8 @@ static void share_xchannel_add_message( xc->msgtail = msg; } -void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) +static void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) { /* * Handle queued incoming messages from the server destined for an @@ -1013,10 +1011,10 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, if (get_bool(src)) { strbuf *packet = strbuf_new(); put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, + "downstream refused X channel open"); strbuf_free(packet); } } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { @@ -1035,10 +1033,9 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, } } -void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc, - struct share_channel *chan, - unsigned downstream_window) +static void share_xchannel_confirmation( + struct ssh_sharing_connstate *cs, struct share_xchannel *xc, + struct share_channel *chan, unsigned downstream_window) { strbuf *packet; @@ -1072,8 +1069,8 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, strbuf_free(packet); } -void share_xchannel_failure(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) +static void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) { /* * If downstream refuses to open our X channel at all for some @@ -1380,9 +1377,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * cleaned up if downstream goes away. */ pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); fwd = share_add_forwarding(cs, host, port); ssh_sharing_queue_global_request(cs->parent->cl, cs); @@ -1443,9 +1440,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * deleted even if downstream doesn't want to know. */ pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); ssh_sharing_queue_global_request(cs->parent->cl, cs); /* @@ -1988,7 +1985,7 @@ static int share_listen_accepting(Plug *plug, * configurations which return the same string from this function will * be treated as potentially shareable with each other. */ -char *ssh_share_sockname(const char *host, int port, Conf *conf) +static char *ssh_share_sockname(const char *host, int port, Conf *conf) { char *username = NULL; char *sockname; diff --git a/code/ssh/ssh.c b/code/ssh/ssh.c index 3820aea8..7a625971 100644 --- a/code/ssh/ssh.c +++ b/code/ssh/ssh.c @@ -268,8 +268,10 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, #endif // PUTTY_CAC userauth_layer = ssh2_userauth_new( - connection_layer, ssh->savedhost, ssh->fullhostname, + connection_layer, ssh->savedhost, ssh->savedport, + ssh->fullhostname, conf_get_filename(ssh->conf, CONF_keyfile), + conf_get_filename(ssh->conf, CONF_detached_cert), conf_get_bool(ssh->conf, CONF_ssh_show_banner), conf_get_bool(ssh->conf, CONF_tryagent), conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth), @@ -280,14 +282,14 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, conf_get_bool(ssh->conf, CONF_try_gssapi_auth), conf_get_bool(ssh->conf, CONF_try_gssapi_kex), conf_get_bool(ssh->conf, CONF_gssapifwd), - &ssh->gss_state + &ssh->gss_state, #else false, false, false, - NULL + NULL, #endif - ); + conf_get_str(ssh->conf, CONF_auth_plugin)); ssh_connect_ppl(ssh, userauth_layer); transport_child_layer = userauth_layer; diff --git a/code/ssh/transport2.c b/code/ssh/transport2.c index 2f9b02af..ce6318aa 100644 --- a/code/ssh/transport2.c +++ b/code/ssh/transport2.c @@ -113,6 +113,7 @@ static const char *const kexlist_descr[NKEXLIST] = { }; static int weak_algorithm_compare(void *av, void *bv); +static int ca_blob_compare(void *av, void *bv); PacketProtocolLayer *ssh2_transport_new( Conf *conf, const char *host, int port, const char *fullhostname, @@ -134,6 +135,7 @@ PacketProtocolLayer *ssh2_transport_new( s->server_greeting = dupstr(server_greeting); s->stats = stats; s->hostkeyblob = strbuf_new(); + s->host_cas = newtree234(ca_blob_compare); pq_in_init(&s->pq_in_higher); pq_out_init(&s->pq_out_higher); @@ -210,16 +212,25 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->client_greeting); sfree(s->server_greeting); sfree(s->keystr); - sfree(s->hostkey_str); strbuf_free(s->hostkeyblob); + { + host_ca *hca; + while ( (hca = delpos234(s->host_cas, 0)) ) + host_ca_free(hca); + freetree234(s->host_cas); + } if (s->hkey && !s->hostkeys) { ssh_key_free(s->hkey); s->hkey = NULL; } + for (size_t i = 0; i < NKEXLIST; i++) + sfree(s->kexlists[i].algs); if (s->f) mp_free(s->f); if (s->p) mp_free(s->p); if (s->g) mp_free(s->g); - if (s->K) mp_free(s->K); + if (s->ebuf) strbuf_free(s->ebuf); + if (s->fbuf) strbuf_free(s->fbuf); + if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret); if (s->dh_ctx) dh_cleanup(s->dh_ctx); if (s->rsa_kex_key_needs_freeing) { @@ -227,7 +238,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->rsa_kex_key); } if (s->ecdh_key) - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); if (s->exhash) ssh_hash_free(s->exhash); strbuf_free(s->outgoing_kexinit); @@ -245,7 +256,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) */ static void ssh2_mkkey( struct ssh2_transport_state *s, strbuf *out, - mp_int *K, unsigned char *H, char chr, int keylen) + strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen) { int hlen = s->kex_alg->hash->hlen; int keylen_padded; @@ -273,7 +284,7 @@ static void ssh2_mkkey( /* First hlen bytes. */ h = ssh_hash_new(s->kex_alg->hash); if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); + put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); put_data(h, H, hlen); put_byte(h, chr); put_data(h, s->session_id, s->session_id_len); @@ -285,7 +296,7 @@ static void ssh2_mkkey( ssh_hash_reset(h); if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); + put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); put_data(h, H, hlen); for (offset = hlen; offset < keylen_padded; offset += hlen) { @@ -302,22 +313,27 @@ static void ssh2_mkkey( * Find a slot in a KEXINIT algorithm list to use for a new algorithm. * If the algorithm is already in the list, return a pointer to its * entry, otherwise return an entry from the end of the list. - * This assumes that every time a particular name is passed in, it - * comes from the same string constant. If this isn't true, this - * function may need to be rewritten to use strcmp() instead. + * + * 'name' is expected to be a ptrlen which it's safe to keep a copy + * of. */ -static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm - *list, const char *name) +static struct kexinit_algorithm *ssh2_kexinit_addalg_pl( + struct kexinit_algorithm_list *list, ptrlen name) { - int i; - - for (i = 0; i < MAXKEXLIST; i++) - if (list[i].name == NULL || list[i].name == name) { - list[i].name = name; - return &list[i]; - } + for (size_t i = 0; i < list->nalgs; i++) + if (ptrlen_eq_ptrlen(list->algs[i].name, name)) + return &list->algs[i]; + + sgrowarray(list->algs, list->algsize, list->nalgs); + struct kexinit_algorithm *entry = &list->algs[list->nalgs++]; + entry->name = name; + return entry; +} - unreachable("Should never run out of space in KEXINIT list"); +static struct kexinit_algorithm *ssh2_kexinit_addalg( + struct kexinit_algorithm_list *list, const char *name) +{ + return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name)); } bool ssh2_common_filter_queue(PacketProtocolLayer *ppl) @@ -483,10 +499,10 @@ PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) static void ssh2_write_kexinit_lists( BinarySink *pktout, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + struct kexinit_algorithm_list kexlists[NKEXLIST], Conf *conf, const SshServerConfig *ssc, int remote_bugs, const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, - ssh_transient_hostkey_cache *thc, + ssh_transient_hostkey_cache *thc, tree234 *host_cas, ssh_key *const *our_hostkeys, int our_nhostkeys, bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode) { @@ -494,7 +510,7 @@ static void ssh2_write_kexinit_lists( bool warn; int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ + const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */ int n_preferred_hk; int preferred_hk[HK_MAX]; int n_preferred_ciphers; @@ -509,14 +525,33 @@ static void ssh2_write_kexinit_lists( * Set up the preferred key exchange. (NULL => warn below here) */ n_preferred_kex = 0; - if (can_gssapi_keyex) + if (can_gssapi_keyex) { + preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex; + preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; + } for (i = 0; i < KEX_MAX; i++) { switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) { case KEX_DHGEX: preferred_kex[n_preferred_kex++] = &ssh_diffiehellman_gex; break; + case KEX_DHGROUP18: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group18; + break; + case KEX_DHGROUP17: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group17; + break; + case KEX_DHGROUP16: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group16; + break; + case KEX_DHGROUP15: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group15; + break; case KEX_DHGROUP14: preferred_kex[n_preferred_kex++] = &ssh_diffiehellman_group14; @@ -533,6 +568,10 @@ static void ssh2_write_kexinit_lists( preferred_kex[n_preferred_kex++] = &ssh_ecdh_kex; break; + case KEX_NTRU_HYBRID: + preferred_kex[n_preferred_kex++] = + &ssh_ntru_hybrid_kex; + break; case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -582,6 +621,9 @@ static void ssh2_write_kexinit_lists( case CIPHER_CHACHA20: preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp; break; + case CIPHER_AESGCM: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm; + break; case CIPHER_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -601,15 +643,14 @@ static void ssh2_write_kexinit_lists( preferred_comp = &ssh_comp_none; for (i = 0; i < NKEXLIST; i++) - for (j = 0; j < MAXKEXLIST; j++) - kexlists[i][j].name = NULL; + kexlists[i].nalgs = 0; /* List key exchange algorithms. */ warn = false; for (i = 0; i < n_preferred_kex; i++) { const ssh_kexes *k = preferred_kex[i]; if (!k) warn = true; else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX], k->list[j]->name); alg->u.kex.kex = k->list[j]; alg->u.kex.warn = warn; @@ -624,23 +665,27 @@ static void ssh2_write_kexinit_lists( for (i = 0; i < our_nhostkeys; i++) { const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], keyalg->ssh_id); alg->u.hk.hostkey = keyalg; alg->u.hk.hkflags = 0; alg->u.hk.warn = false; - if (keyalg == &ssh_rsa) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-256"); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; - alg->u.hk.warn = false; + uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg); + static const uint32_t try_flags[] = { + SSH_AGENT_RSA_SHA2_256, + SSH_AGENT_RSA_SHA2_512, + }; + for (size_t i = 0; i < lenof(try_flags); i++) { + if (try_flags[i] & ~supported_flags) + continue; /* these flags not supported */ + + alg = ssh2_kexinit_addalg( + &kexlists[KEXLIST_HOSTKEY], + ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i])); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-512"); alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; + alg->u.hk.hkflags = try_flags[i]; alg->u.hk.warn = false; } } @@ -658,33 +703,91 @@ static void ssh2_write_kexinit_lists( * they surely _do_ want to be alerted that a server * they're actually connecting to is using it. */ + + bool accept_certs = false; + { + host_ca_enum *handle = enum_host_ca_start(); + if (handle) { + strbuf *name = strbuf_new(); + while (strbuf_clear(name), enum_host_ca_next(handle, name)) { + host_ca *hca = host_ca_load(name->s); + if (!hca) + continue; + + if (hca->ca_public_key && + cert_expr_match_str(hca->validity_expression, + hk_host, hk_port)) { + accept_certs = true; + add234(host_cas, hca); + } else { + host_ca_free(hca); + } + } + enum_host_ca_finish(handle); + strbuf_free(name); + } + } + + if (accept_certs) { + /* Add all the certificate algorithms first, in preference order */ + warn = false; + for (i = 0; i < n_preferred_hk; i++) { + if (preferred_hk[i] == HK_WARN) + warn = true; + for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (!a->alg->is_certificate) + continue; + if (a->id != preferred_hk[i]) + continue; + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; + alg->u.hk.warn = warn; + } + } + } + + /* Next, add algorithms we already know a key for (unless + * configured not to do that) */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) warn = true; for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (a->alg->is_certificate && accept_certs) + continue; /* already added this one */ + if (a->id != preferred_hk[i]) continue; if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && have_ssh_host_key(hk_host, hk_port, - ssh2_hostkey_algs[j].alg->cache_id)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + a->alg->cache_id)) { + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; alg->u.hk.warn = warn; } } } + + /* And finally, everything else */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) warn = true; for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (a->alg->is_certificate) + continue; + if (a->id != preferred_hk[i]) continue; - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; alg->u.hk.warn = warn; } } @@ -711,7 +814,7 @@ static void ssh2_write_kexinit_lists( continue; if (ssh_transient_hostkey_cache_has( thc, ssh2_hostkey_algs[j].alg)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; alg->u.hk.warn = warn; @@ -728,19 +831,19 @@ static void ssh2_write_kexinit_lists( * reverification. */ assert(hk_prev); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); alg->u.hk.hostkey = hk_prev; alg->u.hk.warn = false; } if (can_gssapi_keyex) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null"); + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null"); alg->u.hk.hostkey = NULL; } /* List encryption algorithms (client->server then server->client). */ for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { warn = false; #ifdef FUZZING - alg = ssh2_kexinit_addalg(kexlists[k], "none"); + alg = ssh2_kexinit_addalg(&kexlists[K], "none"); alg->u.cipher.cipher = NULL; alg->u.cipher.warn = warn; #endif /* FUZZING */ @@ -748,7 +851,7 @@ static void ssh2_write_kexinit_lists( const ssh2_ciphers *c = preferred_ciphers[i]; if (!c) warn = true; else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(kexlists[k], + alg = ssh2_kexinit_addalg(&kexlists[k], c->list[j]->ssh2_id); alg->u.cipher.cipher = c->list[j]; alg->u.cipher.warn = warn; @@ -775,7 +878,7 @@ static void ssh2_write_kexinit_lists( alg->u.mac.etm = false; #endif /* FUZZING */ for (i = 0; i < nmacs; i++) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name); + alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name); alg->u.mac.mac = maclist[i]; alg->u.mac.etm = false; } @@ -783,7 +886,7 @@ static void ssh2_write_kexinit_lists( /* For each MAC, there may also be an ETM version, * which we list second. */ if (maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name); + alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name); alg->u.mac.mac = maclist[i]; alg->u.mac.etm = true; } @@ -796,22 +899,22 @@ static void ssh2_write_kexinit_lists( for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { assert(lenof(compressions) > 1); /* Prefer non-delayed versions */ - alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name); + alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name); alg->u.comp.comp = preferred_comp; alg->u.comp.delayed = false; if (preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], + alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->delayed_name); alg->u.comp.comp = preferred_comp; alg->u.comp.delayed = true; } for (i = 0; i < lenof(compressions); i++) { const ssh_compression_alg *c = compressions[i]; - alg = ssh2_kexinit_addalg(kexlists[j], c->name); + alg = ssh2_kexinit_addalg(&kexlists[j], c->name); alg->u.comp.comp = c; alg->u.comp.delayed = false; if (c->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name); + alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name); alg->u.comp.comp = c; alg->u.comp.delayed = true; } @@ -827,10 +930,8 @@ static void ssh2_write_kexinit_lists( if (ssc && ssc->kex_override[i].ptr) { put_datapl(list, ssc->kex_override[i]); } else { - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name == NULL) break; - add_to_commasep(list, kexlists[i][j].name); - } + for (j = 0; j < kexlists[i].nalgs; j++) + add_to_commasep_pl(list, kexlists[i].algs[j].name); } if (i == KEXLIST_KEX && first_time) { if (our_hostkeys) /* we're the server */ @@ -846,14 +947,19 @@ static void ssh2_write_kexinit_lists( put_stringz(pktout, ""); } +struct server_hostkeys { + int *indices; + size_t n, size; +}; + static bool ssh2_scan_kexinits( ptrlen client_kexinit, ptrlen server_kexinit, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + struct kexinit_algorithm_list kexlists[NKEXLIST], const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg, transport_direction *cs, transport_direction *sc, bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, - int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags, + struct server_hostkeys *server_hostkeys, unsigned *hkflags, bool *can_send_ext_info) { BinarySource client[1], server[1]; @@ -918,10 +1024,9 @@ static bool ssh2_scan_kexinits( found_match: selected[i] = NULL; - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name && - ptrlen_eq_string(found, kexlists[i][j].name)) { - selected[i] = &kexlists[i][j]; + for (j = 0; j < kexlists[i].nalgs; j++) { + if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) { + selected[i] = &kexlists[i].algs[j]; break; } } @@ -1076,13 +1181,13 @@ static bool ssh2_scan_kexinits( * one or not. We return these as a list of indices into the * constant ssh2_hostkey_algs[] array. */ - *n_server_hostkeys = 0; - ptrlen list = slists[KEXLIST_HOSTKEY]; for (ptrlen word; get_commasep_word(&list, &word) ;) { for (i = 0; i < lenof(ssh2_hostkey_algs); i++) if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) { - server_hostkeys[(*n_server_hostkeys)++] = i; + sgrowarray(server_hostkeys->indices, server_hostkeys->size, + server_hostkeys->n); + server_hostkeys->indices[server_hostkeys->n++] = i; break; } } @@ -1091,9 +1196,95 @@ static bool ssh2_scan_kexinits( return true; } +static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s) +{ + if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT)) + return false; /* bug flag not enabled => no need to delay */ + if (s->incoming_kexinit->len) + return false; /* already got a remote KEXINIT we can filter against */ + return true; +} + +static void filter_outgoing_kexinit(struct ssh2_transport_state *s) +{ + strbuf *pktout = strbuf_new(); + BinarySource osrc[1], isrc[1]; + BinarySource_BARE_INIT( + osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len); + BinarySource_BARE_INIT( + isrc, s->incoming_kexinit->u, s->incoming_kexinit->len); + + /* Skip the packet type bytes from both packets */ + get_byte(osrc); + get_byte(isrc); + + /* Copy our cookie into the real output packet; skip their cookie */ + put_datapl(pktout, get_data(osrc, 16)); + get_data(isrc, 16); + + /* + * Now we expect NKEXLIST+2 name-lists. We write into the outgoing + * packet a subset of our intended outgoing one, containing only + * names mentioned in the incoming out. + * + * NKEXLIST+2 because for this purpose we treat the 'languages' + * lists the same as the rest. In the rest of this code base we + * ignore those. + */ + strbuf *out = strbuf_new(); + for (size_t i = 0; i < NKEXLIST+2; i++) { + strbuf_clear(out); + ptrlen olist = get_string(osrc), ilist = get_string(isrc); + for (ptrlen oword; get_commasep_word(&olist, &oword) ;) { + ptrlen ilist_copy = ilist; + bool add = false; + for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) { + if (ptrlen_eq_ptrlen(oword, iword)) { + /* Found this word in the incoming list. */ + add = true; + break; + } + } + + if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) { + /* Special case: this will _never_ match anything from the + * server, and we need it to enable SHA-2 based RSA. + * + * If this ever turns out to confuse any server all by + * itself then I suppose we'll need an even more + * draconian bug flag to exclude that too. (Obv, such + * a server wouldn't be able to speak SHA-2 RSA + * anyway.) */ + add = true; + } + + if (add) + add_to_commasep_pl(out, oword); + } + put_stringpl(pktout, ptrlen_from_strbuf(out)); + } + strbuf_free(out); + + /* + * Finally, copy the remaining parts of our intended KEXINIT. + */ + put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */ + put_uint32(pktout, get_uint32(osrc)); /* reserved word */ + + /* + * Dump this data into s->outgoing_kexinit in place of what we had + * there before. We need to remember the KEXINIT we _really_ sent, + * not the one we'd have liked to send, since the host key + * signature will be validated against the former. + */ + strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */ + put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout)); + strbuf_free(pktout); +} + void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) { - put_mp_ssh2(s->exhash, s->K); + put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret)); assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); ssh_hash_final(s->exhash, s->exchange_hash); s->exhash = NULL; @@ -1184,13 +1375,13 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * Construct our KEXINIT packet, in a strbuf so we can refer to it * later. */ - strbuf_clear(s->client_kexinit); + strbuf_clear(s->outgoing_kexinit); put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); random_read(strbuf_append(s->outgoing_kexinit, 16), 16); ssh2_write_kexinit_lists( BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, s->conf, s->ssc, s->ppl.remote_bugs, - s->savedhost, s->savedport, s->hostkey_alg, s->thc, + s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas, s->hostkeys, s->nhostkeys, !s->got_session_id, s->can_gssapi_keyex, s->gss_kex_used && !s->need_gss_transient_hostkey); @@ -1199,12 +1390,34 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) put_uint32(s->outgoing_kexinit, 0); /* reserved */ /* - * Send our KEXINIT. + * Send our KEXINIT, most of the time. + * + * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to + * have seen at least one KEXINIT from the server first, so that + * we can filter our own KEXINIT down to contain only algorithms + * the server mentioned. + * + * But we only need to do this on the _first_ key exchange, when + * we've never seen a KEXINIT from the server before. In rekeys, + * we still have the server's previous KEXINIT lying around, so we + * can filter based on that. + * + * (And a good thing too, since the way you _initiate_ a rekey is + * by sending your KEXINIT, so we'd have no way to prod the server + * into sending its first!) */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->outgoing_kexinit->u + 1, - s->outgoing_kexinit->len - 1); /* omit initial packet type byte */ - pq_push(s->ppl.out_pq, pktout); + s->kexinit_delayed = delay_outgoing_kexinit(s); + if (!s->kexinit_delayed) { + if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) { + /* Filter based on the KEXINIT from the previous exchange */ + filter_outgoing_kexinit(s); + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit type byte */ + pq_push(s->ppl.out_pq, pktout); + } /* * Flag that KEX is in progress. @@ -1226,21 +1439,35 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); + /* + * If we've delayed sending our KEXINIT so as to filter it down to + * only things the server won't choke on, send ours now. + */ + if (s->kexinit_delayed) { + filter_outgoing_kexinit(s); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit type byte */ + pq_push(s->ppl.out_pq, pktout); + } + /* * Work through the two KEXINIT packets in parallel to find the * selected algorithm identifiers. */ { - int nhk, hks[MAXKEXLIST], i, j; + struct server_hostkeys hks = { NULL, 0, 0 }; if (!ssh2_scan_kexinits( ptrlen_from_strbuf(s->client_kexinit), ptrlen_from_strbuf(s->server_kexinit), s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, - &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, - &s->hkflags, &s->can_send_ext_info)) + &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks, + &s->hkflags, &s->can_send_ext_info)) { + sfree(hks.indices); return; /* false means a fatal error function was called */ + } /* * In addition to deciding which host key we're actually going @@ -1254,14 +1481,17 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) */ s->n_uncert_hostkeys = 0; - for (i = 0; i < nhk; i++) { - j = hks[i]; + for (int i = 0; i < hks.n; i++) { + int j = hks.indices[i]; if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && + ssh2_hostkey_algs[j].alg->cache_id && !have_ssh_host_key(s->savedhost, s->savedport, ssh2_hostkey_algs[j].alg->cache_id)) { s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; } } + + sfree(hks.indices); } if (s->warn_kex) { @@ -1363,6 +1593,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * Actually perform the key exchange. */ s->exhash = ssh_hash_new(s->kex_alg->hash); + if (s->kex_shared_secret) + strbuf_free(s->kex_shared_secret); + s->kex_shared_secret = strbuf_new_nm(); put_stringz(s->exhash, s->client_greeting); put_stringz(s->exhash, s->server_greeting); put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len); @@ -1416,14 +1649,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new_nm(); if (s->out.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, 'C' + s->out.mkkey_adjust, s->out.cipher->padded_keybytes); } if (s->out.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, 'E' + s->out.mkkey_adjust, s->out.mac->keylen); } @@ -1508,14 +1741,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new_nm(); if (s->in.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, 'C' + s->in.mkkey_adjust, s->in.cipher->padded_keybytes); } if (s->in.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, 'E' + s->in.mkkey_adjust, s->in.mac->keylen); } @@ -1533,7 +1766,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) /* * Free shared secret. */ - mp_free(s->K); s->K = NULL; + strbuf_free(s->kex_shared_secret); + s->kex_shared_secret = NULL; /* * Update the specials menu to list the remaining uncertified host @@ -2102,9 +2336,9 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) for (i = 0; i < CIPHER_MAX; i++) if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } + rekey_reason = "cipher settings changed"; + rekey_mandatory = true; + } if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != conf_get_bool(conf, CONF_ssh2_des_cbc)) { rekey_reason = "cipher settings changed"; @@ -2134,6 +2368,18 @@ static int weak_algorithm_compare(void *av, void *bv) return a < b ? -1 : a > b ? +1 : 0; } +static int ca_blob_compare(void *av, void *bv) +{ + host_ca *a = (host_ca *)av, *b = (host_ca *)bv; + strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key; + /* Ordering by public key is arbitrary here, so do whatever is easiest */ + if (apk->len < bpk->len) + return -1; + if (apk->len > bpk->len) + return +1; + return memcmp(apk->u, bpk->u, apk->len); +} + /* * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the * tree234 s->weak_algorithms_consented_to to ensure we ask at most diff --git a/code/ssh/transport2.h b/code/ssh/transport2.h index 5f0df80e..204573fb 100644 --- a/code/ssh/transport2.h +++ b/code/ssh/transport2.h @@ -18,9 +18,8 @@ #define DH_MIN_SIZE 1024 #define DH_MAX_SIZE 8192 -#define MAXKEXLIST 16 struct kexinit_algorithm { - const char *name; + ptrlen name; union { struct { const ssh_kex *kex; @@ -45,17 +44,30 @@ struct kexinit_algorithm { } comp; } u; }; +struct kexinit_algorithm_list { + struct kexinit_algorithm *algs; + size_t nalgs, algsize; +}; -#define HOSTKEY_ALGORITHMS(X) \ - X(HK_ED25519, ssh_ecdsa_ed25519) \ - X(HK_ED448, ssh_ecdsa_ed448) \ - X(HK_ECDSA, ssh_ecdsa_nistp256) \ - X(HK_ECDSA, ssh_ecdsa_nistp384) \ - X(HK_ECDSA, ssh_ecdsa_nistp521) \ - X(HK_DSA, ssh_dsa) \ - X(HK_RSA, ssh_rsa_sha512) \ - X(HK_RSA, ssh_rsa_sha256) \ - X(HK_RSA, ssh_rsa) \ +#define HOSTKEY_ALGORITHMS(X) \ + X(HK_ED25519, ssh_ecdsa_ed25519) \ + X(HK_ED448, ssh_ecdsa_ed448) \ + X(HK_ECDSA, ssh_ecdsa_nistp256) \ + X(HK_ECDSA, ssh_ecdsa_nistp384) \ + X(HK_ECDSA, ssh_ecdsa_nistp521) \ + X(HK_DSA, ssh_dsa) \ + X(HK_RSA, ssh_rsa_sha512) \ + X(HK_RSA, ssh_rsa_sha256) \ + X(HK_RSA, ssh_rsa) \ + X(HK_ED25519, opensshcert_ssh_ecdsa_ed25519) \ + /* OpenSSH defines no certified version of Ed448 */ \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp256) \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp384) \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp521) \ + X(HK_DSA, opensshcert_ssh_dsa) \ + X(HK_RSA, opensshcert_ssh_rsa_sha512) \ + X(HK_RSA, opensshcert_ssh_rsa_sha256) \ + X(HK_RSA, opensshcert_ssh_rsa) \ /* end of list */ #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1 #define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM)) @@ -128,7 +140,6 @@ struct ssh2_transport_state { const ssh_kex *kex_alg; const ssh_keyalg *hostkey_alg; - char *hostkey_str; /* string representation, for easy checking in rekeys */ unsigned char session_id[MAX_HASH_LEN]; int session_id_len; int dh_min_size, dh_max_size; @@ -142,7 +153,7 @@ struct ssh2_transport_state { char *client_greeting, *server_greeting; - bool kex_in_progress; + bool kex_in_progress, kexinit_delayed; unsigned long next_rekey, last_rekey; const char *deferred_rekey_reason; bool higher_layer_ok; @@ -165,15 +176,20 @@ struct ssh2_transport_state { bool gss_kex_used; + tree234 *host_cas; + int nbits, pbits; bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; - mp_int *p, *g, *e, *f, *K; + mp_int *p, *g, *e, *f; + strbuf *ebuf, *fbuf; + strbuf *kex_shared_secret; strbuf *outgoing_kexinit, *incoming_kexinit; strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ int kex_init_value, kex_reply_value; transport_direction in, out, *cstrans, *sctrans; ptrlen hostkeydata, sigdata; - strbuf *hostkeyblob; + strbuf *hostkeyblob; /* used in server to construct host key to + * send to client; in client to check in rekeys */ char *keystr; ssh_key *hkey; /* actual host key */ unsigned hkflags; /* signing flags, used in server */ @@ -189,7 +205,7 @@ struct ssh2_transport_state { SeatPromptResult spr; bool guessok; bool ignorepkt; - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; + struct kexinit_algorithm_list kexlists[NKEXLIST]; #ifndef NO_GSSAPI Ssh_gss_buf gss_buf; Ssh_gss_buf gss_rcvtok, gss_sndtok; diff --git a/code/ssh/userauth2-client.c b/code/ssh/userauth2-client.c index 3dd2e554..8a6b72a6 100644 --- a/code/ssh/userauth2-client.c +++ b/code/ssh/userauth2-client.c @@ -19,7 +19,6 @@ #ifdef PUTTY_CAC #include "cert_common.h" #endif // PUTTY_CAC - #define BANNER_LIMIT 131072 typedef struct agent_key { @@ -31,9 +30,10 @@ struct ssh2_userauth_state { int crState; PacketProtocolLayer *transport_layer, *successor_layer; - Filename *keyfile; + Filename *keyfile, *detached_cert_file; bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; + int port; char *default_username; bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; @@ -71,7 +71,7 @@ struct ssh2_userauth_state { char *locally_allocated_username; char *password; bool got_username; - strbuf *publickey_blob; + strbuf *publickey_blob, *detached_cert_blob, *cert_pubkey_diagnosed; bool privatekey_available, privatekey_encrypted; char *publickey_algorithm; char *publickey_comment; @@ -93,6 +93,16 @@ struct ssh2_userauth_state { StripCtrlChars *banner_scc; bool banner_scc_initialised; + char *authplugin_cmd; + Socket *authplugin; + uint32_t authplugin_version; + Plug authplugin_plug; + bufchain authplugin_bc; + strbuf *authplugin_incoming_msg; + size_t authplugin_backlog; + bool authplugin_eof; + bool authplugin_ki_active; + StripCtrlChars *ki_scc; bool ki_scc_initialised; bool ki_printed_header; @@ -112,12 +122,19 @@ static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); static void ssh2_userauth_agent_callback(void *, void *, int); static void ssh2_userauth_add_sigblob( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); +static void ssh2_userauth_add_alg_and_publickey( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob); static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata); #ifndef NO_GSSAPI static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype); #endif +static bool ssh2_userauth_ki_setup_prompts( + struct ssh2_userauth_state *s, BinarySource *src, bool plugin); +static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s); +static void ssh2_userauth_ki_write_responses( + struct ssh2_userauth_state *s, BinarySink *bs); static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .free = ssh2_userauth_free, @@ -131,11 +148,13 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, + const char *hostname, int port, const char *fullhostname, + Filename *keyfile, Filename *detached_cert_file, + bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, + const char *authplugin_cmd) { struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); memset(s, 0, sizeof(*s)); @@ -143,8 +162,10 @@ PacketProtocolLayer *ssh2_userauth_new( s->successor_layer = successor_layer; s->hostname = dupstr(hostname); + s->port = port; s->fullhostname = dupstr(fullhostname); s->keyfile = filename_copy(keyfile); + s->detached_cert_file = filename_copy(detached_cert_file); s->show_banner = show_banner; s->tryagent = tryagent; s->notrivialauth = notrivialauth; @@ -159,6 +180,8 @@ PacketProtocolLayer *ssh2_userauth_new( s->is_trivial_auth = true; bufchain_init(&s->banner); bufchain_sink_init(&s->banner_bs, &s->banner); + s->authplugin_cmd = dupstr(authplugin_cmd); + bufchain_init(&s->authplugin_bc); return &s->ppl; } @@ -191,6 +214,7 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); filename_free(s->keyfile); + filename_free(s->detached_cert_file); sfree(s->default_username); sfree(s->locally_allocated_username); sfree(s->hostname); @@ -201,11 +225,21 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) sfree(s->publickey_algorithm); if (s->publickey_blob) strbuf_free(s->publickey_blob); + if (s->detached_cert_blob) + strbuf_free(s->detached_cert_blob); + if (s->cert_pubkey_diagnosed) + strbuf_free(s->cert_pubkey_diagnosed); strbuf_free(s->last_methods_string); if (s->banner_scc) stripctrl_free(s->banner_scc); if (s->ki_scc) stripctrl_free(s->ki_scc); + sfree(s->authplugin_cmd); + if (s->authplugin) + sk_close(s->authplugin); + bufchain_clear(&s->authplugin_bc); + if (s->authplugin_incoming_msg) + strbuf_free(s->authplugin_incoming_msg); sfree(s); } @@ -251,6 +285,164 @@ static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) return pq_pop(s->ppl.in_pq); } +static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, + unsigned *signflags, const char **algname) +{ + *signflags = 0; /* default */ + + const ssh_keyalg *alg = find_pubkey_alg(*algname); + if (!alg) + return false; /* we don't know how to upgrade this */ + + unsigned supported_flags = ssh_keyalg_supported_flags(alg); + +#ifdef PUTTY_CAC + const char* comment = (s->agent_keys_len > 0 && s->publickey_algorithm != *algname) ? s->agent_keys[s->agent_key_index].comment->s : s->publickey_comment; + s->ppl.bpp->ext_info_rsa_sha256_ok = s->ppl.bpp->ext_info_rsa_sha256_ok && cert_test_hash(comment, SSH_AGENT_RSA_SHA2_256); + s->ppl.bpp->ext_info_rsa_sha512_ok = s->ppl.bpp->ext_info_rsa_sha512_ok && cert_test_hash(comment, SSH_AGENT_RSA_SHA2_512); +#endif // PUTTY_CAC + + if (s->ppl.bpp->ext_info_rsa_sha512_ok && + (supported_flags & SSH_AGENT_RSA_SHA2_512)) { + *signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok && + (supported_flags & SSH_AGENT_RSA_SHA2_256)) { + *signflags = SSH_AGENT_RSA_SHA2_256; + } else { + return false; + } + + *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags); + return true; +} + +static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + if (type == PLUGLOG_PROXY_MSG) + ppl_logevent("%s", err_msg); +} + +static void authplugin_plug_closing( + Plug *plug, PlugCloseType type, const char *error_msg) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + s->authplugin_eof = true; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void authplugin_plug_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + bufchain_add(&s->authplugin_bc, data, len); + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void authplugin_plug_sent(Plug *plug, size_t bufsize) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + s->authplugin_backlog = bufsize; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static const PlugVtable authplugin_plugvt = { + .log = authplugin_plug_log, + .closing = authplugin_plug_closing, + .receive = authplugin_plug_receive, + .sent = authplugin_plug_sent, +}; + +static strbuf *authplugin_newmsg(uint8_t type) +{ + strbuf *amsg = strbuf_new_nm(); + put_uint32(amsg, 0); /* fill in later */ + put_byte(amsg, type); + return amsg; +} + +static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg) +{ + PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4); + assert(s->authplugin); + s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len); + strbuf_free(amsg); +} + +static bool authplugin_expect_msg(struct ssh2_userauth_state *s, + unsigned *type, BinarySource *src) +{ + if (s->authplugin_eof) { + *type = PLUGIN_EOF; + return true; + } + uint8_t len[4]; + if (!bufchain_try_fetch(&s->authplugin_bc, len, 4)) + return false; + size_t size = GET_32BIT_MSB_FIRST(len); + if (bufchain_size(&s->authplugin_bc) - 4 < size) + return false; + if (s->authplugin_incoming_msg) { + strbuf_clear(s->authplugin_incoming_msg); + } else { + s->authplugin_incoming_msg = strbuf_new_nm(); + } + bufchain_consume(&s->authplugin_bc, 4); /* eat length field */ + bufchain_fetch_consume( + &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size), + size); + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(s->authplugin_incoming_msg)); + *type = get_byte(src); + if (get_err(src)) + *type = PLUGIN_NOTYPE; + return true; +} + +static void authplugin_bad_packet(struct ssh2_userauth_state *s, + unsigned type, const char *fmt, ...) +{ + strbuf *msg = strbuf_new(); + switch (type) { + case PLUGIN_EOF: + put_dataz(msg, "Unexpected end of file from auth helper plugin"); + break; + case PLUGIN_NOTYPE: + put_dataz(msg, "Received malformed packet from auth helper plugin " + "(too short to have a type code)"); + break; + default: + put_fmt(msg, "Received unknown message type %u " + "from auth helper plugin", type); + break; + + #define CASEDECL(name, value) \ + case name: \ + put_fmt(msg, "Received unexpected %s message from auth helper " \ + "plugin", #name); \ + break; + AUTHPLUGIN_MSG_NAMES(CASEDECL); + #undef CASEDECL + } + if (fmt) { + put_dataz(msg, " ("); + va_list ap; + va_start(ap, fmt); + put_fmt(msg, fmt, ap); + va_end(ap); + put_dataz(msg, ")"); + } + ssh_sw_abort(s->ppl.ssh, "%s", msg->s); + strbuf_free(msg); +} + static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) { struct ssh2_userauth_state *s = @@ -310,6 +502,70 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } } + /* + * If the user provided a detached certificate file, load that. + */ + if (!filename_is_null(s->detached_cert_file)) { + char *cert_error = NULL; + strbuf *cert_blob = strbuf_new(); + char *algname = NULL; + char *comment = NULL; + + ppl_logevent("Reading certificate file \"%s\"", + filename_to_str(s->detached_cert_file)); + int keytype = key_type(s->detached_cert_file); + if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) { + cert_error = dupstr(key_type_to_str(keytype)); + goto cert_load_done; + } + + const char *error; + bool success = ppk_loadpub_f( + s->detached_cert_file, &algname, + BinarySink_UPCAST(cert_blob), &comment, &error); + + if (!success) { + cert_error = dupstr(error); + goto cert_load_done; + } + + const ssh_keyalg *certalg = find_pubkey_alg(algname); + if (!certalg) { + cert_error = dupprintf( + "unrecognised certificate type '%s'", algname); + goto cert_load_done; + } + + if (!certalg->is_certificate) { + cert_error = dupprintf( + "key type '%s' is not a certificate", certalg->ssh_id); + goto cert_load_done; + } + + /* OK, store the certificate blob to substitute for the + * public blob in all publickey auth packets. */ + if (s->detached_cert_blob) + strbuf_free(s->detached_cert_blob); + s->detached_cert_blob = cert_blob; + cert_blob = NULL; /* prevent free */ + + cert_load_done: + if (cert_error) { + ppl_logevent("Unable to use this certificate file (%s)", + cert_error); + ppl_printf( + "Unable to use certificate file \"%s\" (%s)\r\n", + filename_to_str(s->detached_cert_file), cert_error); + sfree(cert_error); + } + + if (cert_blob) + strbuf_free(cert_blob); + sfree(algname); + sfree(comment); + } + /* * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). @@ -352,17 +608,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->agent_keys_len = nkeys; s->agent_keys = snewn(s->agent_keys_len, agent_key); for (size_t i = 0; i < nkeys; i++) { - s->agent_keys[i].blob = strbuf_new(); - put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + s->agent_keys[i].blob = strbuf_dup(get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); /* Also, extract the algorithm string from the start * of the public-key blob. */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - s->agent_keys[i].blob)); - s->agent_keys[i].algorithm = get_string(src); + s->agent_keys[i].algorithm = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(s->agent_keys[i].blob)); } ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); @@ -405,6 +657,74 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) done_agent_query:; } + s->got_username = false; + + if (*s->authplugin_cmd) { + s->authplugin_plug.vt = &authplugin_plugvt; + s->authplugin = platform_start_subprocess( + s->authplugin_cmd, &s->authplugin_plug, "plugin"); + ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd); + } + + if (s->authplugin) { + strbuf *amsg = authplugin_newmsg(PLUGIN_INIT); + put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION); + put_stringz(amsg, s->hostname); + put_uint32(amsg, s->port); + put_stringz(amsg, s->username ? s->username : ""); + authplugin_send_free(s, amsg); + + BinarySource src[1]; + unsigned type; + crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); + switch (type) { + case PLUGIN_INIT_RESPONSE: { + s->authplugin_version = get_uint32(src); + ptrlen username = get_string(src); + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_INIT_RESPONSE from auth helper plugin"); + return; + } + if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) { + ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced " + "unsupported version number %"PRIu32, + s->authplugin_version); + return; + } + if (username.len) { + sfree(s->default_username); + s->default_username = mkstr(username); + ppl_logevent("Authentication plugin set username '%s'", + s->default_username); + } + break; + } + case PLUGIN_INIT_FAILURE: { + ptrlen message = get_string(src); + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_INIT_FAILURE from auth helper plugin"); + return; + } + /* This is a controlled error, so we need not completely + * abandon the connection. Instead, inform the user, and + * proceed as if the plugin was not present */ + ppl_printf("Authentication plugin failed to initialise:\r\n"); + seat_set_trust_status(s->ppl.seat, false); + ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); + seat_set_trust_status(s->ppl.seat, true); + sk_close(s->authplugin); + s->authplugin = NULL; + break; + } + default: + authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or " + "PLUGIN_INIT_FAILURE"); + return; + } + } + /* * We repeat this whole loop, including the username prompt, * until we manage a successful authentication. If the user @@ -429,7 +749,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * the username they will want to be able to get back and * retype it! */ - s->got_username = false; while (1) { /* * Get a username. @@ -720,23 +1039,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Attempt public-key authentication using a key from Pageant. */ s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; - s->signflags = 0; - if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) { -#ifdef PUTTY_CAC - const char* comment = s->agent_keys[s->agent_key_index].comment->s; - s->ppl.bpp->ext_info_rsa_sha256_ok = s->ppl.bpp->ext_info_rsa_sha256_ok && cert_test_hash(comment, SSH_AGENT_RSA_SHA2_256); - s->ppl.bpp->ext_info_rsa_sha512_ok = s->ppl.bpp->ext_info_rsa_sha512_ok && cert_test_hash(comment, SSH_AGENT_RSA_SHA2_512); -#endif // PUTTY_CAC - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } - } + char *alg_tmp = mkstr(s->agent_keyalg); + const char *newalg = alg_tmp; + if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) + s->agent_keyalg = ptrlen_from_asciz(newalg); + sfree(alg_tmp); s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; @@ -750,9 +1057,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; @@ -784,8 +1091,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob)); #ifdef PUTTY_CAC @@ -800,7 +1107,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); put_stringpl(agentreq, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); + s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); @@ -866,22 +1173,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * * First, try to upgrade its algorithm. */ - if (!strcmp(s->publickey_algorithm, "ssh-rsa")) { - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ -#ifdef PUTTY_CAC - s->ppl.bpp->ext_info_rsa_sha256_ok = s->ppl.bpp->ext_info_rsa_sha256_ok && cert_test_hash(s->publickey_comment, SSH_AGENT_RSA_SHA2_256); - s->ppl.bpp->ext_info_rsa_sha512_ok = s->ppl.bpp->ext_info_rsa_sha512_ok && cert_test_hash(s->publickey_comment, SSH_AGENT_RSA_SHA2_512); -#endif // PUTTY_CAC - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } + const char *newalg = s->publickey_algorithm; + if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr(newalg); } /* @@ -895,9 +1190,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringz(s->pktout, s->publickey_algorithm); - put_string(s->pktout, s->publickey_blob->s, - s->publickey_blob->len); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm), + ptrlen_from_strbuf(s->publickey_blob)); pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Offered public key"); @@ -1014,10 +1309,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature follows */ - put_stringz(s->pktout, s->publickey_algorithm); pkblob = strbuf_new(); ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); - put_string(s->pktout, pkblob->s, pkblob->len); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, + ptrlen_from_asciz(s->publickey_algorithm), + ptrlen_from_strbuf(pkblob)); /* * The data to be signed is: @@ -1156,23 +1453,24 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * When acquire_cred yields no useful expiration, go with * the service ticket expiration. */ - s->gss_stat = s->shgss->lib->init_sec_context - (s->shgss->lib, - &s->shgss->ctx, - s->shgss->srv_name, - s->gssapi_fwd, - &s->gss_rcvtok, - &s->gss_sndtok, - NULL, - NULL); + s->gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, + &s->shgss->ctx, + s->shgss->srv_name, + s->gssapi_fwd, + &s->gss_rcvtok, + &s->gss_sndtok, + NULL, + NULL); if (s->gss_stat!=SSH_GSS_S_COMPLETE && s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { ppl_logevent("GSSAPI authentication initialisation " "failed"); - if (s->shgss->lib->display_status(s->shgss->lib, - s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, &s->gss_buf) + == SSH_GSS_OK) { ppl_logevent("%s", (char *)s->gss_buf.value); sfree(s->gss_buf.value); } @@ -1281,6 +1579,64 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ppl_logevent("Attempting keyboard-interactive authentication"); + if (s->authplugin) { + strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL); + put_stringz(amsg, "keyboard-interactive"); + authplugin_send_free(s, amsg); + + BinarySource src[1]; + unsigned type; + crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); + switch (type) { + case PLUGIN_PROTOCOL_REJECT: { + ptrlen message = PTRLEN_LITERAL(""); + if (s->authplugin_version >= 2) { + /* draft protocol didn't include a message here */ + message = get_string(src); + } + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_PROTOCOL_REJECT from auth " + "helper plugin"); + return; + } + if (message.len) { + /* If the plugin sent a message about + * _why_ it didn't want to do k-i, pass + * that message on to the user. (It might + * say, for example, what went wrong when + * it tried to open its config file.) */ + ppl_printf("Authentication plugin failed to set " + "up keyboard-interactive " + "authentication:\r\n"); + seat_set_trust_status(s->ppl.seat, false); + ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); + seat_set_trust_status(s->ppl.seat, true); + ppl_logevent("Authentication plugin declined to " + "help with keyboard-interactive: " + "%.*s", PTRLEN_PRINTF(message)); + } else { + ppl_logevent("Authentication plugin declined to " + "help with keyboard-interactive"); + } + s->authplugin_ki_active = false; + break; + } + case PLUGIN_PROTOCOL_ACCEPT: + s->authplugin_ki_active = true; + ppl_logevent("Authentication plugin agreed to help " + "with keyboard-interactive"); + break; + default: + authplugin_bad_packet( + s, type, "expected PLUGIN_PROTOCOL_ACCEPT or " + "PLUGIN_PROTOCOL_REJECT"); + return; + } + } else { + s->authplugin_ki_active = false; + } + if (!s->ki_scc_initialised) { s->ki_scc = seat_stripctrl_new( s->ppl.seat, NULL, SIC_KI_PROMPTS); @@ -1304,170 +1660,123 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ki_printed_header = false; /* - * Loop while the server continues to send INFO_REQUESTs. + * Loop while we still have prompts to send to the user. */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - ptrlen name, inst; - strbuf *sb; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. - */ - name = get_string(pktin); - inst = get_string(pktin); - get_string(pktin); /* skip language tag */ - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - + if (!s->authplugin_ki_active) { /* - * Get any prompt(s) from the packet. + * The simple case: INFO_REQUESTs are passed on to + * the user, and responses are sent straight back + * to the SSH server. */ - s->num_prompts = get_uint32(pktin); - for (uint32_t i = 0; i < s->num_prompts; i++) { - s->is_trivial_auth = false; - ptrlen prompt = get_string(pktin); - bool echo = get_bool(pktin); + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + if (!ssh2_userauth_ki_setup_prompts( + s, BinarySource_UPCAST(pktin), false)) + return; + crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); - if (get_err(pktin)) { - ssh_proto_error( - s->ppl.ssh, "Server sent truncated " - "SSH_MSG_USERAUTH_INFO_REQUEST packet"); + if (spr_is_abort(s->spr)) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" + "interactive authentication prompt"); return; } - sb = strbuf_new(); - if (!prompt.len) { - put_datapl(sb, PTRLEN_LITERAL( - ": ")); - } else if (s->ki_scc) { - stripctrl_retarget( - s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, prompt); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, prompt); - } - add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); - } + /* + * Send the response(s) to the server. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_userauth_ki_write_responses( + s, BinarySink_UPCAST(s->pktout)); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crMaybeWaitUntilV( + (pktin = ssh2_userauth_pop(s)) != NULL); + } + } else { /* - * Make the header strings. This includes the - * 'name' (optional dialog-box title) and - * 'instruction' from the server. - * - * First, display our disambiguating header line - * if this is the first time round the loop - - * _unless_ the server has sent a completely empty - * k-i packet with no prompts _or_ text, which - * apparently some do. In that situation there's - * no need to alert the user that the following - * text is server- supplied, because, well, _what_ - * text? - * - * We also only do this if we got a stripctrl, - * because if we didn't, that suggests this is all - * being done via dialog boxes anyway. + * The case where a plugin is involved: + * INFO_REQUEST from the server is sent to the + * plugin, which sends responses that we hand back + * to the server. But in the meantime, the plugin + * might send USER_REQUEST for us to pass to the + * user, and then we send responses to that. */ - if (!s->ki_printed_header && s->ki_scc && - (s->num_prompts || name.len || inst.len)) { - seat_antispoof_msg( - ppl_get_iseat(&s->ppl), "Keyboard-interactive " - "authentication prompts from server:"); - s->ki_printed_header = true; - seat_set_trust_status(s->ppl.seat, false); - } + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + strbuf *amsg = authplugin_newmsg( + PLUGIN_KI_SERVER_REQUEST); + put_datapl(amsg, get_data(pktin, get_avail(pktin))); + authplugin_send_free(s, amsg); - sb = strbuf_new(); - if (name.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, name); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, name); + BinarySource src[1]; + unsigned type; + while (true) { + crMaybeWaitUntilV(authplugin_expect_msg( + s, &type, src)); + if (type != PLUGIN_KI_USER_REQUEST) + break; + + if (!ssh2_userauth_ki_setup_prompts(s, src, true)) + return; + crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); + + if (spr_is_abort(s->spr)) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_spr_close( + s->ppl.ssh, s->spr, "keyboard-" + "interactive authentication prompt"); + return; + } + + /* + * Send the responses on to the plugin. + */ + strbuf *amsg = authplugin_newmsg( + PLUGIN_KI_USER_RESPONSE); + ssh2_userauth_ki_write_responses( + s, BinarySink_UPCAST(amsg)); + authplugin_send_free(s, amsg); } - s->cur_prompt->name_reqd = true; - } else { - put_datapl(sb, PTRLEN_LITERAL( - "SSH server authentication")); - s->cur_prompt->name_reqd = false; - } - s->cur_prompt->name = strbuf_to_str(sb); - - sb = strbuf_new(); - if (inst.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, inst); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, inst); + + if (type != PLUGIN_KI_SERVER_RESPONSE) { + authplugin_bad_packet( + s, type, "expected PLUGIN_KI_SERVER_RESPONSE " + "or PLUGIN_PROTOCOL_USER_REQUEST"); + return; } - s->cur_prompt->instr_reqd = true; - } else { - s->cur_prompt->instr_reqd = false; - } - if (sb->len) - s->cur_prompt->instruction = strbuf_to_str(sb); - else - strbuf_free(sb); - /* - * Our prompts_t is fully constructed now. Get the - * user's response(s). - */ - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + put_datapl(s->pktout, get_data(src, get_avail(src))); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + /* - * Failed to get responses. Terminate. + * Get the next packet in case it's another + * INFO_REQUEST. */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" - "interactive authentication prompt"); - return; - } - - /* - * Send the response(s) to the server. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - put_uint32(s->pktout, s->num_prompts); - for (uint32_t i = 0; i < s->num_prompts; i++) { - put_stringz(s->pktout, prompt_get_result_ref( - s->cur_prompt->prompts[i])); + crMaybeWaitUntilV( + (pktin = ssh2_userauth_pop(s)) != NULL); } - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - - /* - * Free the prompts structure from this iteration. - * If there's another, a new one will be allocated - * when we return to the top of this while loop. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - } /* @@ -1477,7 +1786,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) seat_set_trust_status(s->ppl.seat, true); seat_antispoof_msg( ppl_get_iseat(&s->ppl), - "End of keyboard-interactive prompts from server"); + (s->authplugin_ki_active ? + "End of keyboard-interactive prompts from plugin" : + "End of keyboard-interactive prompts from server")); } /* @@ -1485,6 +1796,41 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ pq_push_front(s->ppl.in_pq, pktin); + if (s->authplugin_ki_active) { + /* + * As our last communication with the plugin, tell + * it whether the k-i authentication succeeded. + */ + int plugin_msg = -1; + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + plugin_msg = PLUGIN_AUTH_SUCCESS; + } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + /* + * Peek in the failure packet to see if it's a + * partial success. + */ + BinarySource src[1]; + BinarySource_BARE_INIT( + src, get_ptr(pktin), get_avail(pktin)); + get_string(pktin); /* skip methods */ + bool partial_success = get_bool(pktin); + if (!get_err(src)) { + plugin_msg = partial_success ? + PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE; + } + } + + if (plugin_msg >= 0) { + strbuf *amsg = authplugin_newmsg(plugin_msg); + authplugin_send_free(s, amsg); + + /* Wait until we've actually sent it, in case + * we close the connection to the plugin + * before that outgoing message has left our + * own buffers */ + crMaybeWaitUntilV(s->authplugin_backlog == 0); + } + } } else if (s->can_passwd) { s->is_trivial_auth = false; /* @@ -1760,6 +2106,144 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) crFinishV; } +static bool ssh2_userauth_ki_setup_prompts( + struct ssh2_userauth_state *s, BinarySource *src, bool plugin) +{ + ptrlen name, inst; + strbuf *sb; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and + * start building a prompt. + */ + name = get_string(src); + inst = get_string(src); + get_string(src); /* skip language tag */ + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(src); + for (uint32_t i = 0; i < s->num_prompts; i++) { + s->is_trivial_auth = false; + ptrlen prompt = get_string(src); + bool echo = get_bool(src); + + if (get_err(src)) { + ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet", + plugin ? "Plugin" : "Server", + plugin ? "PLUGIN_KI_USER_REQUEST" : + "SSH_MSG_USERAUTH_INFO_REQUEST"); + return false; + } + + sb = strbuf_new(); + if (!prompt.len) { + put_fmt(sb, "<%s failed to send prompt>: ", + plugin ? "plugin" : "server"); + } else if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, prompt); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, prompt); + } + add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); + } + + /* + * Make the header strings. This includes the 'name' (optional + * dialog-box title) and 'instruction' from the server. + * + * First, display our disambiguating header line if this is the + * first time round the loop - _unless_ the server has sent a + * completely empty k-i packet with no prompts _or_ text, which + * apparently some do. In that situation there's no need to alert + * the user that the following text is server- supplied, because, + * well, _what_ text? + * + * We also only do this if we got a stripctrl, because if we + * didn't, that suggests this is all being done via dialog boxes + * anyway. + */ + if (!s->ki_printed_header && s->ki_scc && + (s->num_prompts || name.len || inst.len)) { + seat_antispoof_msg( + ppl_get_iseat(&s->ppl), + (plugin ? + "Keyboard-interactive authentication prompts from plugin:" : + "Keyboard-interactive authentication prompts from server:")); + s->ki_printed_header = true; + seat_set_trust_status(s->ppl.seat, false); + } + + sb = strbuf_new(); + if (name.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, name); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, name); + } + s->cur_prompt->name_reqd = true; + } else { + if (plugin) + put_datapl(sb, PTRLEN_LITERAL( + "Communication with authentication plugin")); + else + put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); + s->cur_prompt->name_reqd = false; + } + s->cur_prompt->name = strbuf_to_str(sb); + + sb = strbuf_new(); + if (inst.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, inst); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, inst); + } + s->cur_prompt->instr_reqd = true; + } else { + s->cur_prompt->instr_reqd = false; + } + if (sb->len) + s->cur_prompt->instruction = strbuf_to_str(sb); + else + strbuf_free(sb); + + return true; +} + +static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s) +{ + s->spr = seat_get_userpass_input( + ppl_get_iseat(&s->ppl), s->cur_prompt); + return s->spr.kind != SPRK_INCOMPLETE; +} + +static void ssh2_userauth_ki_write_responses( + struct ssh2_userauth_state *s, BinarySink *bs) +{ + put_uint32(bs, s->num_prompts); + for (uint32_t i = 0; i < s->num_prompts; i++) + put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i])); + + /* + * Free the prompts structure from this iteration. If there's + * another, a new one will be allocated when we return to the top + * of this while loop. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; +} + static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata) { @@ -1796,6 +2280,161 @@ static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) queue_idempotent_callback(&s->ppl.ic_process_queue); } +/* + * Helper function to add the algorithm and public key strings to a + * "publickey" auth packet. Deals with overriding both strings if the + * user has provided a detached certificate which matches the public + * key in question. + */ +static void ssh2_userauth_add_alg_and_publickey( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + if (s->detached_cert_blob) { + ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob); + strbuf *certbase = NULL, *pkbase = NULL; + bool done = false; + const ssh_keyalg *pkalg = find_pubkey_alg_len(alg); + ssh_key *certkey = NULL, *pk = NULL; + strbuf *fail_reason = strbuf_new(); + bool verbose = true; + + /* + * Whether or not we send the certificate, we're likely to + * generate a log message about it. But we don't want to log + * once for the offer and once for the real auth attempt, so + * we de-duplicate by remembering the last public key this + * function saw. */ + if (!s->cert_pubkey_diagnosed) + s->cert_pubkey_diagnosed = strbuf_new(); + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed), + pkblob)) { + verbose = false; + } else { + /* Log this time, but arrange that we don't mention it next time */ + strbuf_clear(s->cert_pubkey_diagnosed); + put_datapl(s->cert_pubkey_diagnosed, pkblob); + } + + /* + * Check that the public key we're replacing is compatible + * with the certificate, in that they should have the same + * base public key. + */ + + const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl); + assert(certalg); /* we checked this before setting s->detached_blob */ + assert(certalg->is_certificate); /* and this too */ + + certkey = ssh_key_new_pub(certalg, detached_cert_pl); + if (!certkey) { + put_fmt(fail_reason, "certificate key file is invalid"); + goto no_match; + } + + certbase = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(certkey), + BinarySink_UPCAST(certbase)); + if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase))) + goto match; /* yes, a match! */ + + /* + * If we reach here, the certificate's base key was not + * identical to the key we're given. But it might still be + * identical to the _base_ key of the key we're given, if we + * were using a differently certified version of the same key. + * In that situation, the detached cert should still override. + */ + if (!pkalg) { + put_fmt(fail_reason, "unable to identify algorithm of base key"); + goto no_match; + } + + pk = ssh_key_new_pub(pkalg, pkblob); + if (!pk) { + put_fmt(fail_reason, "base public key is invalid"); + goto no_match; + } + + pkbase = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase)); + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase), + ptrlen_from_strbuf(certbase))) + goto match; /* yes, a match on 2nd attempt! */ + + /* Give up; we've tried to match these keys up and failed. */ + put_fmt(fail_reason, "base public key does not match certificate"); + goto no_match; + + match: + /* + * The two keys match, so insert the detached certificate into + * the output packet in place of the public key we were given. + * + * However, we need to be a bit careful with the algorithm + * name: we might need to upgrade it to one that matches the + * original algorithm name. (If we were asked to add an + * ssh-rsa key but were given algorithm name "rsa-sha2-512", + * then instead of the certificate's algorithm name + * ssh-rsa-cert-v01@... we need to write the corresponding + * SHA-512 name rsa-sha2-512-cert-v01@... .) + */ + if (verbose) { + ppl_logevent("Sending public key with certificate from \"%s\"", + filename_to_str(s->detached_cert_file)); + } + put_stringz(pkt, ssh_keyalg_related_alg(certalg, pkalg)->ssh_id); + put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob)); + done = true; + goto out; + + no_match: + /* Log that we didn't send the certificate, if this public key + * isn't the same one as last call to this function. (Need to + * avoid verbosely logging once for the offer and once for the + * real auth attempt.) */ + if (verbose) { + ppl_logevent("Not substituting certificate \"%s\" for public " + "key: %s", filename_to_str(s->detached_cert_file), + fail_reason->s); + if (s->publickey_blob) { + /* If the user provided a specific key file to use (i.e. + * this wasn't just a key we picked opportunistically out + * of an agent), then they probably _care_ that we didn't + * send the certificate, so we should make a loud error + * message about it as well as just commenting in the + * Event Log. */ + ppl_printf("Unable to use certificate \"%s\" with public " + "key \"%s\": %s\r\n", + filename_to_str(s->detached_cert_file), + filename_to_str(s->keyfile), + fail_reason->s); + } + } + + out: + /* Whether we did that or not, free our stuff. */ + if (certbase) + strbuf_free(certbase); + if (pkbase) + strbuf_free(pkbase); + if (certkey) + ssh_key_free(certkey); + if (pk) + ssh_key_free(pk); + strbuf_free(fail_reason); + + /* And if we did, don't fall through to the alternative below */ + if (done) + return; + } + + /* In all other cases, just put in what we were given. */ + put_stringpl(pkt, alg); + put_stringpl(pkt, pkblob); +} + /* * Helper function to add an SSH-2 signature blob to a packet. Expects * to be shown the public key blob as well as the signature blob. diff --git a/code/ssh/verstring.c b/code/ssh/verstring.c index 90814bc1..aa4c2c20 100644 --- a/code/ssh/verstring.c +++ b/code/ssh/verstring.c @@ -43,7 +43,7 @@ static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); static PktOut *ssh_verstring_new_pktout(int type); static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); + const char *msg, int category); static const BinaryPacketProtocolVtable ssh_verstring_vtable = { .free = ssh_verstring_free, @@ -309,8 +309,8 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) * a NUL terminator. */ while (s->vstring->len > 0 && - (s->vstring->s[s->vstring->len-1] == '\r' || - s->vstring->s[s->vstring->len-1] == '\n')) + (s->vstring->s[s->vstring->len-1] == '\015' || + s->vstring->s[s->vstring->len-1] == '\012')) strbuf_shrink_by(s->vstring, 1); bpp_logevent("Remote version: %s", s->vstring->s); @@ -606,6 +606,12 @@ static void ssh_detect_bugs(struct ssh_verstring_state *s) bpp_logevent("We believe remote version has SSH-2 " "channel request bug"); } + + if (conf_get_int(s->conf, CONF_sshbug_filter_kexinit) == FORCE_ON) { + s->remote_bugs |= BUG_REQUIRES_FILTERED_KEXINIT; + bpp_logevent("We believe remote version requires us to " + "filter our KEXINIT"); + } } const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) diff --git a/code/ssh/x11fwd.c b/code/ssh/x11fwd.c index 4a2073e6..c5698f9b 100644 --- a/code/ssh/x11fwd.c +++ b/code/ssh/x11fwd.c @@ -495,7 +495,7 @@ static size_t x11_send( while (len > 0 && xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) xconn->auth_data[xconn->data_read++ - 12 - - xconn->auth_psize] = (unsigned char) (len--, *data++); + xconn->auth_psize] = (unsigned char) (len--, *data++); if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) return 0; diff --git a/code/ssh/zlib.c b/code/ssh/zlib.c index 9ad04ed2..0841faa3 100644 --- a/code/ssh/zlib.c +++ b/code/ssh/zlib.c @@ -54,8 +54,8 @@ struct LZ77InternalContext; struct LZ77Context { struct LZ77InternalContext *ictx; void *userdata; - void (*literal) (struct LZ77Context * ctx, unsigned char c); - void (*match) (struct LZ77Context * ctx, int distance, int len); + void (*literal) (struct LZ77Context *ctx, unsigned char c); + void (*match) (struct LZ77Context *ctx, int distance, int len); }; /* @@ -573,7 +573,7 @@ struct ssh_zlib_compressor { ssh_compressor sc; }; -ssh_compressor *zlib_compress_init(void) +static ssh_compressor *zlib_compress_init(void) { struct Outbuf *out; struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); @@ -592,7 +592,7 @@ ssh_compressor *zlib_compress_init(void) return &comp->sc; } -void zlib_compress_cleanup(ssh_compressor *sc) +static void zlib_compress_cleanup(ssh_compressor *sc) { struct ssh_zlib_compressor *comp = container_of(sc, struct ssh_zlib_compressor, sc); @@ -604,10 +604,9 @@ void zlib_compress_cleanup(ssh_compressor *sc) sfree(comp); } -void zlib_compress_block(ssh_compressor *sc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) +static void zlib_compress_block( + ssh_compressor *sc, const unsigned char *block, int len, + unsigned char **outblock, int *outlen, int minlen) { struct ssh_zlib_compressor *comp = container_of(sc, struct ssh_zlib_compressor, sc); @@ -904,7 +903,7 @@ struct zlib_decompress_ctx { ssh_decompressor dc; }; -ssh_decompressor *zlib_decompress_init(void) +static ssh_decompressor *zlib_decompress_init(void) { struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx); unsigned char lengths[288]; @@ -927,7 +926,7 @@ ssh_decompressor *zlib_decompress_init(void) return &dctx->dc; } -void zlib_decompress_cleanup(ssh_decompressor *dc) +static void zlib_decompress_cleanup(ssh_decompressor *dc) { struct zlib_decompress_ctx *dctx = container_of(dc, struct zlib_decompress_ctx, dc); @@ -946,7 +945,7 @@ void zlib_decompress_cleanup(ssh_decompressor *dc) } static int zlib_huflookup(unsigned long *bitsp, int *nbitsp, - struct zlib_table *tab) + struct zlib_table *tab) { unsigned long bits = *bitsp; int nbits = *nbitsp; @@ -986,9 +985,9 @@ static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c) #define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) -bool zlib_decompress_block(ssh_decompressor *dc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen) +static bool zlib_decompress_block( + ssh_decompressor *dc, const unsigned char *block, int len, + unsigned char **outblock, int *outlen) { struct zlib_decompress_ctx *dctx = container_of(dc, struct zlib_decompress_ctx, dc); @@ -1094,7 +1093,7 @@ bool zlib_decompress_block(ssh_decompressor *dc, if (dctx->lenptr >= dctx->hlit + dctx->hdist) { dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit); dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit, - dctx->hdist); + dctx->hdist); zlib_freetable(&dctx->lenlentable); dctx->lenlentable = NULL; dctx->state = INBLK; @@ -1112,7 +1111,7 @@ bool zlib_decompress_block(ssh_decompressor *dc, dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7); dctx->lenaddon = (code == 18 ? 11 : 3); dctx->lenrep = (code == 16 && dctx->lenptr > 0 ? - dctx->lengths[dctx->lenptr - 1] : 0); + dctx->lengths[dctx->lenptr - 1] : 0); dctx->state = TREES_LENREP; } break; diff --git a/code/sshpubk.c b/code/sshpubk.c index f1c3503d..1e34b790 100644 --- a/code/sshpubk.c +++ b/code/sshpubk.c @@ -202,8 +202,7 @@ static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only, if (enclen & 7) goto end; - buf = strbuf_new_nm(); - put_datapl(buf, get_data(src, enclen)); + buf = strbuf_dup_nm(get_data(src, enclen)); unsigned char keybuf[16]; hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf); @@ -578,6 +577,14 @@ const ssh_keyalg *const all_keyalgs[] = { #endif &ssh_ecdsa_ed25519, &ssh_ecdsa_ed448, + &opensshcert_ssh_dsa, + &opensshcert_ssh_rsa, + &opensshcert_ssh_rsa_sha256, + &opensshcert_ssh_rsa_sha512, + &opensshcert_ssh_ecdsa_ed25519, + &opensshcert_ssh_ecdsa_nistp256, + &opensshcert_ssh_ecdsa_nistp384, + &opensshcert_ssh_ecdsa_nistp521, }; const size_t n_keyalgs = lenof(all_keyalgs); @@ -595,6 +602,18 @@ const ssh_keyalg *find_pubkey_alg(const char *name) return find_pubkey_alg_len(ptrlen_from_asciz(name)); } +ptrlen pubkey_blob_to_alg_name(ptrlen blob) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + return get_string(src); +} + +const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob) +{ + return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob)); +} + struct ppk_cipher { const char *name; size_t blocklen, keylen, ivlen; @@ -1245,7 +1264,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr); return ret; } else if (type != SSH_KEYTYPE_SSH2) { - error = "not a PuTTY SSH-2 private key"; + error = "not a public key or a PuTTY SSH-2 private key"; goto error; } @@ -1257,7 +1276,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) error = "PuTTY key format too new"; else - error = "not a PuTTY SSH-2 private key"; + error = "not a public key or a PuTTY SSH-2 private key"; goto error; } error = "file format error"; @@ -1321,7 +1340,7 @@ bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs, char **commentptr, const char **errorstr) { #ifdef PUTTY_CAC - cert_convert_legacy(filename->path); + //cert_convert_legacy(filename->path); if (cert_is_certpath(filename->path)) { struct ssh2_userkey* userkey = cert_load_key(filename->path); if (userkey == NULL || userkey->key == NULL) { @@ -1332,7 +1351,7 @@ bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs, if (commentptr) { *commentptr = userkey->comment; } userkey->key->vt->public_blob(userkey->key, bs); userkey->key->vt->freekey(userkey->key); - sfree(userkey); + sfree(userkey); return true; } #endif // PUTTY_CAC @@ -1416,37 +1435,6 @@ int base64_lines(int datalen) return (datalen + 47) / 48; } -static void base64_encode_s(BinarySink *bs, const unsigned char *data, - int datalen, int cpl) -{ - int linelen = 0; - char out[4]; - int n, i; - - while (datalen > 0) { - n = (datalen < 3 ? datalen : 3); - base64_encode_atom(data, n, out); - data += n; - datalen -= n; - for (i = 0; i < 4; i++) { - if (linelen >= cpl) { - linelen = 0; - put_byte(bs, '\n'); - } - put_byte(bs, out[i]); - linelen++; - } - } - put_byte(bs, '\n'); -} - -void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - base64_encode_s(BinarySink_UPCAST(&ss), data, datalen, cpl); -} - const ppk_save_parameters ppk_save_default_parameters = { .fmt_version = 3, @@ -1591,7 +1579,7 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, put_fmt(out, "Encryption: %s\n", cipherstr); put_fmt(out, "Comment: %s\n", key->comment); put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); - base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64); + base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64); if (params.fmt_version == 3 && ciphertype->keylen != 0) { put_fmt(out, "Key-Derivation: %s\n", params.argon2_flavour == Argon2d ? "Argon2d" : @@ -1607,8 +1595,8 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, put_fmt(out, "\n"); } put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); - base64_encode_s(BinarySink_UPCAST(out), - priv_blob_encrypted, priv_encrypted_len, 64); + base64_encode_bs(BinarySink_UPCAST(out), + make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64); put_fmt(out, "Private-MAC: "); for (i = 0; i < macalg->len; i++) put_fmt(out, "%02x", priv_mac[i]); @@ -1809,6 +1797,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb) char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) { strbuf *sb = strbuf_new(); + strbuf *tmp = NULL; /* * Identify the key algorithm, if possible. @@ -1824,23 +1813,62 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) if (alg) { int bits = ssh_key_public_bits(alg, blob); put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); + + if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) { + ssh_key *key = ssh_key_new_pub(alg, blob); + if (key) { + tmp = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), + BinarySink_UPCAST(tmp)); + blob = ptrlen_from_strbuf(tmp); + ssh_key_free(key); + } + } } else { put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname)); } } - switch (fptype) { + switch (ssh_fptype_from_cert(fptype)) { case SSH_FPTYPE_MD5: ssh2_fingerprint_blob_md5(blob, sb); break; case SSH_FPTYPE_SHA256: ssh2_fingerprint_blob_sha256(blob, sb); break; + default: + unreachable("ssh_fptype_from_cert ruled out the other values"); } + if (tmp) + strbuf_free(tmp); + return strbuf_to_str(sb); } +char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype = ssh_fptype_from_cert(fptype); + + char *fp = ssh2_fingerprint_blob(blob, fptype); + char *p = strrchr(fp, ' '); + char *hash = p ? p + 1 : fp; + + char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype)); + char *pc = strrchr(fpc, ' '); + char *hashc = pc ? pc + 1 : fpc; + + if (strcmp(hash, hashc)) { + char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc); + sfree(fp); + fp = tmp; + } + + sfree(fpc); + return fp; +} + char **ssh2_all_fingerprints_for_blob(ptrlen blob) { char **fps = snewn(SSH_N_FPTYPES, char *); @@ -1858,6 +1886,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype) return ret; } +char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(data, BinarySink_UPCAST(blob)); + char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype); + strbuf_free(blob); + return ret; +} + char **ssh2_all_fingerprints(ssh_key *data) { strbuf *blob = strbuf_new(); @@ -1914,7 +1951,7 @@ static int key_type_s_internal(BinarySource *src) if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 && get_chars(src, " ").len == 1 && get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV" - "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 && + "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 && get_nonchars(src, " \n").len == 0) return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH; @@ -1988,47 +2025,3 @@ const char *key_type_to_str(int type) unreachable("bad key type in key_type_to_str"); } } - -key_components *key_components_new(void) -{ - key_components *kc = snew(key_components); - kc->ncomponents = 0; - kc->componentsize = 0; - kc->components = NULL; - return kc; -} - -void key_components_add_text(key_components *kc, - const char *name, const char *value) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = false; - kc->components[n].text = dupstr(value); -} - -void key_components_add_mp(key_components *kc, - const char *name, mp_int *value) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = true; - kc->components[n].mp = mp_copy(value); -} - -void key_components_free(key_components *kc) -{ - for (size_t i = 0; i < kc->ncomponents; i++) { - sfree(kc->components[i].name); - if (kc->components[i].is_mp_int) { - mp_free(kc->components[i].mp); - } else { - smemclr(kc->components[i].text, strlen(kc->components[i].text)); - sfree(kc->components[i].text); - } - } - sfree(kc->components); - sfree(kc); -} diff --git a/code/storage.h b/code/storage.h index 3e03181a..e9138f40 100644 --- a/code/storage.h +++ b/code/storage.h @@ -6,6 +6,8 @@ #ifndef PUTTY_STORAGE_H #define PUTTY_STORAGE_H +#include "defs.h" + /* ---------------------------------------------------------------------- * Functions to save and restore PuTTY sessions. Note that this is * only the low-level code to do the reading and writing. The @@ -91,6 +93,31 @@ int check_stored_host_key(const char *hostname, int port, void store_host_key(const char *hostname, int port, const char *keytype, const char *key); +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's configuration for trusted host + * certification authorities. This must be stored separately from the + * saved-session data, because the whole point is to avoid having to + * configure CAs separately per session. + */ + +struct host_ca { + char *name; + strbuf *ca_public_key; + char *validity_expression; + ca_options opts; +}; + +host_ca_enum *enum_host_ca_start(void); +bool enum_host_ca_next(host_ca_enum *handle, strbuf *out); +void enum_host_ca_finish(host_ca_enum *handle); + +host_ca *host_ca_load(const char *name); +char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */ +char *host_ca_delete(const char *name); /* likewise */ + +host_ca *host_ca_new(void); /* initialises to default settings */ +void host_ca_free(host_ca *); + /* ---------------------------------------------------------------------- * Functions to access PuTTY's random number seed file. */ diff --git a/code/stubs/CMakeLists.txt b/code/stubs/CMakeLists.txt new file mode 100644 index 00000000..dc02aca3 --- /dev/null +++ b/code/stubs/CMakeLists.txt @@ -0,0 +1,31 @@ +# This subdirectory is generally full of 'stubs' in the sense of +# functions and types that don't do anything interesting, and are +# substituted in some contexts for ones that do. +# +# Some of the files here, with names beginning 'no-', are substituted +# at link time, conditional on the application. For example, a program +# that doesn't use the timing subsystem but still includes a module +# that makes a passing reference to it (say, in a context that never +# turns out to be called) can link against no-timing.c in place of the +# real timing.c. +# +# Other files, with names beginning 'null-', provide non-functional +# implementations of a particular internal API, or a selection of +# non-functional methods for that API that real implementations can +# selectively use. Those are linked in to a program _alongside_ real +# implementations of the same API. +# +# So the cmake setup for this directory puts all the 'null-' files +# into the utils library (at the end of the link, where they'll be +# available everywhere), but doesn't mention the 'no-' files, because +# those will be selected manually by add_executable() commands +# elsewhere. + +add_sources_from_current_dir(utils + null-lp.c + null-cipher.c + null-key.c + null-mac.c + null-opener.c + null-plug.c + null-seat.c) diff --git a/code/stubs/no-ca-config.c b/code/stubs/no-ca-config.c new file mode 100644 index 00000000..573f770f --- /dev/null +++ b/code/stubs/no-ca-config.c @@ -0,0 +1,14 @@ +/* + * Stub version of setup_ca_config_box, for tools that don't have SSH + * code linked in. + */ + +#include "putty.h" +#include "dialog.h" + +const bool has_ca_config_box = false; + +void setup_ca_config_box(struct controlbox *b) +{ + unreachable("should never call setup_ca_config_box in this application"); +} diff --git a/code/stubs/nocmdline.c b/code/stubs/no-cmdline.c similarity index 90% rename from code/stubs/nocmdline.c rename to code/stubs/no-cmdline.c index 60e2cb6b..2476354e 100644 --- a/code/stubs/nocmdline.c +++ b/code/stubs/no-cmdline.c @@ -1,5 +1,5 @@ /* - * nocmdline.c - stubs in applications which don't do the + * no-cmdline.c - stubs in applications which don't do the * standard(ish) PuTTY tools' command-line parsing */ diff --git a/code/stubs/nogss.c b/code/stubs/no-gss.c similarity index 100% rename from code/stubs/nogss.c rename to code/stubs/no-gss.c diff --git a/code/stubs/noprint.c b/code/stubs/no-print.c similarity index 100% rename from code/stubs/noprint.c rename to code/stubs/no-print.c diff --git a/code/stubs/norand.c b/code/stubs/no-rand.c similarity index 100% rename from code/stubs/norand.c rename to code/stubs/no-rand.c diff --git a/code/stubs/noterm.c b/code/stubs/no-term.c similarity index 100% rename from code/stubs/noterm.c rename to code/stubs/no-term.c diff --git a/code/stubs/notiming.c b/code/stubs/no-timing.c similarity index 92% rename from code/stubs/notiming.c rename to code/stubs/no-timing.c index 3feb5cdf..d1a0ef9f 100644 --- a/code/stubs/notiming.c +++ b/code/stubs/no-timing.c @@ -1,5 +1,5 @@ /* - * notiming.c: stub version of timing API. + * no-timing.c: stub version of timing API. * * Used in any tool which needs a subsystem linked against the * timing API but doesn't want to actually provide timing. For diff --git a/code/stubs/null-cipher.c b/code/stubs/null-cipher.c new file mode 100644 index 00000000..e11c7bbc --- /dev/null +++ b/code/stubs/null-cipher.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh_cipher + * implementations might use. + */ + +#include "ssh.h" + +void nullcipher_next_message(ssh_cipher *cipher) +{ + /* Most ciphers don't do anything at all with this */ +} diff --git a/code/stubs/null-key.c b/code/stubs/null-key.c new file mode 100644 index 00000000..dae5c1bb --- /dev/null +++ b/code/stubs/null-key.c @@ -0,0 +1,22 @@ +#include "misc.h" +#include "ssh.h" + +unsigned nullkey_supported_flags(const ssh_keyalg *self) +{ + return 0; +} + +const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) +{ + /* There are no alternate ids */ + return self->ssh_id; +} + +ssh_key *nullkey_base_key(ssh_key *key) +{ + /* When a key is not certified, it is its own base */ + return key; +} + +bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; } +bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; } diff --git a/code/utils/null_lp.c b/code/stubs/null-lp.c similarity index 100% rename from code/utils/null_lp.c rename to code/stubs/null-lp.c diff --git a/code/stubs/null-mac.c b/code/stubs/null-mac.c new file mode 100644 index 00000000..4d836704 --- /dev/null +++ b/code/stubs/null-mac.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh2_mac + * implementations might use. + */ + +#include "ssh.h" + +void nullmac_next_message(ssh2_mac *m) +{ + /* Most MACs don't do anything at all with this */ +} diff --git a/code/stubs/null-opener.c b/code/stubs/null-opener.c new file mode 100644 index 00000000..6fdb7c28 --- /dev/null +++ b/code/stubs/null-opener.c @@ -0,0 +1,20 @@ +/* + * Null implementation of DeferredSocketOpener. Doesn't even bother to + * allocate and free itself: there's just one static implementation + * which we hand out to any caller. + */ + +#include "putty.h" + +static void null_opener_free(DeferredSocketOpener *opener) {} + +static const DeferredSocketOpenerVtable NullOpener_vt = { + .free = null_opener_free, +}; + +static DeferredSocketOpener null_opener = { .vt = &NullOpener_vt }; + +DeferredSocketOpener *null_deferred_socket_opener(void) +{ + return &null_opener; +} diff --git a/code/stubs/nullplug.c b/code/stubs/null-plug.c similarity index 100% rename from code/stubs/nullplug.c rename to code/stubs/null-plug.c diff --git a/code/utils/nullseat.c b/code/stubs/null-seat.c similarity index 89% rename from code/utils/nullseat.c rename to code/stubs/null-seat.c index ba09839e..37cb0f4c 100644 --- a/code/utils/nullseat.c +++ b/code/stubs/null-seat.c @@ -22,7 +22,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_SW_ABORT("this seat can't handle interactive prompts"); } SeatPromptResult nullseat_confirm_weak_crypto_primitive( @@ -52,3 +52,14 @@ bool nullseat_verbose_yes(Seat *seat) { return true; } bool nullseat_interactive_no(Seat *seat) { return false; } bool nullseat_interactive_yes(Seat *seat) { return true; } bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } + +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "", + .hk_connect_once_action = "", + .hk_cancel_action = "", + .hk_cancel_action_Participle = "", + }; + return &descs; +} diff --git a/code/terminal/bidi.c b/code/terminal/bidi.c index 128f84c5..c17671b6 100644 --- a/code/terminal/bidi.c +++ b/code/terminal/bidi.c @@ -3558,13 +3558,14 @@ static void reverse_sequences(BidiContext *ctx) } /* - * The Main Bidi Function, and the only function that should be used - * by the outside world. + * The Main Bidi Function. The two wrappers below it present different + * external APIs for different purposes, but everything comes through + * here. * * text: a buffer of size textlen containing text to apply the * Bidirectional algorithm to. */ -void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) +static void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) { ensure_arrays(ctx, textlen); ctx->text = text; diff --git a/code/terminal/terminal.c b/code/terminal/terminal.c index 59a3a96a..37fa3513 100644 --- a/code/terminal/terminal.c +++ b/code/terminal/terminal.c @@ -647,7 +647,7 @@ static void makeliteral_truecolour(strbuf *b, termchar *c, unsigned long *state) * Put the used parts of the colour info into the buffer. */ put_byte(b, ((c->truecolour.fg.enabled ? 1 : 0) | - (c->truecolour.bg.enabled ? 2 : 0))); + (c->truecolour.bg.enabled ? 2 : 0))); if (c->truecolour.fg.enabled) { put_byte(b, c->truecolour.fg.r); put_byte(b, c->truecolour.fg.g); @@ -1073,7 +1073,7 @@ static int sblines(Terminal *term) int sblines = count234(term->scrollback); if (term->erase_to_scrollback && term->alt_which && term->alt_screen) { - sblines += term->alt_sblines; + sblines += term->alt_sblines; } return sblines; } @@ -1220,12 +1220,6 @@ static void term_timer(void *ctx, unsigned long now) if (term->window_update_pending) term_update_callback(term); - - if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY && - now == term->win_resize_timeout) { - term->win_resize_pending = WIN_RESIZE_NO; - queue_toplevel_callback(term_out_cb, term); - } } static void term_update_callback(void *ctx) @@ -1426,8 +1420,6 @@ void term_update(Terminal *term) term->win_resize_pending = WIN_RESIZE_AWAIT_REPLY; win_request_resize(term->win, term->win_resize_pending_w, term->win_resize_pending_h); - term->win_resize_timeout = schedule_timer( - WIN_RESIZE_TIMEOUT, term_timer, term); } if (term->win_zorder_pending) { win_set_zorder(term->win, term->win_zorder_top); @@ -1545,7 +1537,7 @@ static void set_erase_char(Terminal *term) * lookups which would be involved in fetching them from the former * every time. */ -void term_copy_stuff_from_conf(Terminal *term) +static void term_copy_stuff_from_conf(Terminal *term) { term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping); @@ -2151,14 +2143,6 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) int sblen; int save_alt_which = term->alt_which; - /* If we were holding buffered terminal data because we were - * waiting for confirmation of a resize, queue a callback to start - * processing it again. */ - if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY) { - term->win_resize_pending = WIN_RESIZE_NO; - queue_toplevel_callback(term_out_cb, term); - } - if (newrows == term->rows && newcols == term->cols && newsavelines == term->savelines) return; /* nothing to do */ @@ -2336,6 +2320,13 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) backend_size(term->backend, term->cols, term->rows); } +void term_resize_request_completed(Terminal *term) +{ + assert(term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY); + term->win_resize_pending = WIN_RESIZE_NO; + queue_toplevel_callback(term_out_cb, term); +} + /* * Hand a backend to the terminal, so it can be notified of resizes. */ @@ -2350,7 +2341,7 @@ void term_provide_backend(Terminal *term, Backend *backend) * If only the top line has content, returns 0. * If no lines have content, return -1. */ -static int find_last_nonempty_line(Terminal * term, tree234 * screen) +static int find_last_nonempty_line(Terminal *term, tree234 *screen) { int i; for (i = count234(screen) - 1; i >= 0; i--) { @@ -3137,8 +3128,8 @@ static void do_osc(Terminal *term) { if (term->osc_w) { while (term->osc_strlen--) - term->wordness[(unsigned char) - term->osc_string[term->osc_strlen]] = term->esc_args[0]; + term->wordness[(unsigned char)term->osc_string[term->osc_strlen]] = + term->esc_args[0]; } else { term->osc_string[term->osc_strlen] = '\0'; switch (term->esc_args[0]) { @@ -3425,7 +3416,7 @@ static strbuf *term_input_data_from_unicode( char *bufptr = strbuf_append(buf, len + 1); int rv; rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, - bufptr, len + 1, NULL, term->ucsdata); + bufptr, len + 1, NULL); strbuf_shrink_to(buf, rv < 0 ? 0 : rv); } @@ -3920,7 +3911,11 @@ static void term_out(Terminal *term, bool called_from_term_data) case '\033': /* ESC: Escape */ if (term->vt52_mode) term->termstate = VT52_ESC; - else { + else if (term->termstate == SEEN_OSC || + term->termstate == SEEN_OSC_W) { + /* Be prepared to terminate an OSC early */ + term->termstate = OSC_MAYBE_ST; + } else { compatibility(ANSIMIN); term->termstate = SEEN_ESC; term->esc_query = 0; @@ -4027,6 +4022,7 @@ static void term_out(Terminal *term, bool called_from_term_data) /* Compatibility is nasty here, xterm, linux, decterm yuk! */ compatibility(OTHER); term->termstate = SEEN_OSC; + term->osc_strlen = 0; term->esc_args[0] = 0; term->esc_nargs = 1; break; @@ -5169,6 +5165,17 @@ static void term_out(Terminal *term, bool called_from_term_data) else term->esc_args[term->esc_nargs-1] = UINT_MAX; break; + case 0x9C: + /* Terminate even though we aren't in OSC_STRING yet */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + case 0xC2: + if (in_utf(term)) { + /* Or be prepared for the UTF-8 version of that */ + term->termstate = OSC_MAYBE_ST_UTF8; + } + break; default: /* * _Most_ other characters here terminate the @@ -5338,6 +5345,17 @@ static void term_out(Terminal *term, bool called_from_term_data) else term->esc_args[0] = UINT_MAX; break; + case 0x9C: + /* Terminate even though we aren't in OSC_STRING yet */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + case 0xC2: + if (in_utf(term)) { + /* Or be prepared for the UTF-8 version of that */ + term->termstate = OSC_MAYBE_ST_UTF8; + } + break; default: term->termstate = OSC_STRING; term->osc_strlen = 0; @@ -6011,7 +6029,7 @@ static void do_paint(Terminal *term) if (!term->ansi_colour) tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | - ATTR_DEFFG | ATTR_DEFBG; + ATTR_DEFFG | ATTR_DEFBG; if (!term->xterm_256_colour) { int colour; @@ -7460,7 +7478,9 @@ int format_function_key(char *buf, Terminal *term, int key_number, return p - buf; } -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, + bool shift, bool ctrl, bool alt, + bool *consumed_alt) { char *p = buf; @@ -7491,7 +7511,17 @@ int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) } else if ((code == 1 || code == 4) && term->rxvt_homeend) { p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); } else { - p += sprintf(p, "\x1B[%d~", code); + if (term->vt52_mode) { + p += sprintf(p, "\x1B[%d~", code); + } else { + int bitmap = 0; + if (term->funky_type == FUNKY_XTERM_216) + bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); + if (bitmap) + p += sprintf(p, "\x1B[%d;%d~", code, bitmap); + else + p += sprintf(p, "\x1B[%d~", code); + } } return p - buf; diff --git a/code/terminal/terminal.h b/code/terminal/terminal.h index b2347f9a..3f918b22 100644 --- a/code/terminal/terminal.h +++ b/code/terminal/terminal.h @@ -427,24 +427,6 @@ struct terminal_tag { WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY } win_resize_pending; int win_resize_pending_w, win_resize_pending_h; - - /* - * Not every frontend / TermWin implementation can be relied on - * 100% to reply to a resize request in a timely manner. (In X11 - * it's all asynchronous and goes via the window manager, and if - * your window manager is seriously unwell, you'd rather not have - * terminal windows start becoming unusable as a knock-on effect, - * since those are just the thing you might need to use for - * emergency WM maintenance!) So when we enter AWAIT_REPLY status, - * we also set a 5-second timer, after which we'll regretfully - * conclude that a resize is probably not going to happen after - * all. - * - * However, in non-emergency cases, the plan is that this - * shouldn't be needed, for one reason or another. - */ - long win_resize_timeout; - #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) }; static inline bool in_utf(Terminal *term) diff --git a/code/test/ca.py b/code/test/ca.py new file mode 100644 index 00000000..ebd80599 --- /dev/null +++ b/code/test/ca.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +# +# Implementation of OpenSSH certificate creation. Used in +# cryptsuite.py to construct certificates for test purposes. +# +# Can also be run standalone to function as an actual CA, though I +# don't currently know of any reason you'd want to use it in place of +# ssh-keygen. In that mode, it depends on having an SSH agent +# available to do the signing. + +import argparse +import base64 +import enum +import hashlib +import io +import os + +import ssh + +class Container: + pass + +class CertType(enum.Enum): + user = 1 + host = 2 + +def maybe_encode(s): + if isinstance(s, bytes): + return s + return s.encode('UTF-8') + +def make_signature_preimage( + key_to_certify, ca_key, certtype, keyid, serial, principals, + valid_after=0, valid_before=0xFFFFFFFFFFFFFFFF, + critical_options={}, extensions={}, + reserved=b'', nonce=None): + + alg, pubkeydata = ssh.ssh_decode_string(key_to_certify, True) + + if nonce is None: + nonce = os.urandom(32) + + buf = io.BytesIO() + buf.write(ssh.ssh_string(alg + b"-cert-v01@openssh.com")) + buf.write(ssh.ssh_string(nonce)) + buf.write(pubkeydata) + buf.write(ssh.ssh_uint64(serial)) + buf.write(ssh.ssh_uint32(certtype.value if isinstance(certtype, CertType) + else certtype)) + buf.write(ssh.ssh_string(maybe_encode(keyid))) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(maybe_encode(principal)) + for principal in principals))) + buf.write(ssh.ssh_uint64(valid_after)) + buf.write(ssh.ssh_uint64(valid_before)) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(opt) + ssh.ssh_string(val) + for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) + for opt, val in critical_options.items()])))) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(opt) + ssh.ssh_string(val) + for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) + for opt, val in extensions.items()])))) + buf.write(ssh.ssh_string(reserved)) + # The CA key here can be a raw 'bytes', or an ssh_key object + # exposed via testcrypt + if type(ca_key) != bytes: + ca_key = ca_key.public_blob() + buf.write(ssh.ssh_string(ca_key)) + + return buf.getvalue() + +def make_full_cert(preimage, signature): + return preimage + ssh.ssh_string(signature) + +def sign_cert_via_testcrypt(preimage, ca_key, signflags=None): + # Expects ca_key to be a testcrypt ssh_key object + signature = ca_key.sign(preimage, 0 if signflags is None else signflags) + return make_full_cert(preimage, signature) + +def sign_cert_via_agent(preimage, ca_key, signflags=None): + # Expects ca_key to be a binary public key blob, and for a + # currently running SSH agent to contain the corresponding private + # key. + import agenttest + sign_request = (ssh.ssh_byte(ssh.SSH2_AGENTC_SIGN_REQUEST) + + ssh.ssh_string(ca_key) + ssh.ssh_string(preimage)) + if signflags is not None: + sign_request += ssh.ssh_uint32(signflags) + sign_response = agenttest.agent_query(sign_request) + msgtype, sign_response = ssh.ssh_decode_byte(sign_response, True) + if msgtype == ssh.SSH2_AGENT_SIGN_RESPONSE: + signature, sign_response = ssh.ssh_decode_string(sign_response, True) + return make_full_cert(preimage, signature) + elif msgtype == ssh.SSH2_AGENT_FAILURE: + raise IOError("Agent refused to return a signature") + else: + raise IOError("Agent returned unexpecteed message type {:d}" + .format(msgtype)) + +def read_pubkey_file(fh): + b64buf = io.StringIO() + comment = None + + lines = (line.rstrip("\r\n") for line in iter(fh.readline, "")) + line = next(lines) + + if line == "---- BEGIN SSH2 PUBLIC KEY ----": + # RFC 4716 public key. Read headers like Comment: + line = next(lines) + while ":" in line: + key, val = line.split(":", 1) + if key == "Comment": + comment = val.strip("\r\n") + line = next(lines) + # Now expect lines of base64 data. + while line != "---- END SSH2 PUBLIC KEY ----": + b64buf.write(line) + line = next(lines) + + else: + # OpenSSH public key. Expect the b64buf blob to be the second word. + fields = line.split(" ", 2) + b64buf.write(fields[1]) + if len(fields) > 1: + comment = fields[2] + + return base64.b64decode(b64buf.getvalue()), comment + +def write_pubkey_file(fh, key, comment=None): + alg = ssh.ssh_decode_string(key) + fh.write(alg.decode('ASCII')) + fh.write(" " + base64.b64encode(key).decode('ASCII')) + if comment is not None: + fh.write(" " + comment) + fh.write("\n") + +def default_signflags(key): + alg = ssh.ssh_decode_string(key) + if alg == b'ssh-rsa': + return 4 # RSA-SHA-512 + +def main(): + parser = argparse.ArgumentParser( + description='Create and sign OpenSSH certificates.') + parser.add_argument("key_to_certify", help="Public key to be certified.") + parser.add_argument("--ca-key", required=True, + help="Public key of the CA. Must be present in a " + "currently accessible SSH agent.") + parser.add_argument("-o", "--output", required=True, + help="File to write output OpenSSH key to.") + parser.add_argument("--type", required=True, choices={'user', 'host'}, + help="Type of certificate to make.") + parser.add_argument("--principal", "--user", "--host", + required=True, action="append", + help="User names or host names to authorise.") + parser.add_argument("--key-id", "--keyid", required=True, + help="Human-readable key ID string for log files.") + parser.add_argument("--serial", type=int, required=True, + help="Serial number to write into certificate.") + parser.add_argument("--signflags", type=int, help="Signature flags " + "(e.g. 2 = RSA-SHA-256, 4 = RSA-SHA-512).") + args = parser.parse_args() + + with open(args.key_to_certify) as fh: + key_to_certify, comment = read_pubkey_file(fh) + with open(args.ca_key) as fh: + ca_key, _ = read_pubkey_file(fh) + + extensions = { + 'permit-X11-forwarding': '', + 'permit-agent-forwarding': '', + 'permit-port-forwarding': '', + 'permit-pty': '', + 'permit-user-rc': '', + } + + # FIXME: for a full-featured command-line CA we'd need to add + # command-line options for crit opts, extensions and validity + # period + preimage = make_signature_preimage( + key_to_certify = key_to_certify, + ca_key = ca_key, + certtype = getattr(CertType, args.type), + keyid = args.key_id, + serial = args.serial, + principals = args.principal, + extensions = extensions) + + signflags = (args.signflags if args.signflags is not None + else default_signflags(ca_key)) + cert = sign_cert_via_agent(preimage, ca_key, signflags) + + with open(args.output, "w") as fh: + write_pubkey_file(fh, cert, comment) + +if __name__ == '__main__': + main() diff --git a/code/test/cryptsuite.py b/code/test/cryptsuite.py index 4971a599..69b492e8 100644 --- a/code/test/cryptsuite.py +++ b/code/test/cryptsuite.py @@ -8,7 +8,7 @@ import contextlib import hashlib import binascii -import base64 +from base64 import b64decode as b64 import json try: from math import gcd @@ -18,14 +18,10 @@ from eccref import * from testcrypt import * from ssh import * +from ca import CertType, make_signature_preimage, sign_cert_via_testcrypt assert sys.version_info[:2] >= (3,0), "This is Python 3 code" -try: - base64decode = base64.decodebytes -except AttributeError: - base64decode = base64.decodestring - def unhex(s): return binascii.unhexlify(s.replace(" ", "").replace("\n", "")) @@ -149,6 +145,11 @@ def get_aes_impls(): for impl in get_implementations("aes128_cbc") if impl.startswith("aes128_cbc_")] +def get_aesgcm_impls(): + return [impl.split("_", 1)[1] + for impl in get_implementations("aesgcm") + if impl.startswith("aesgcm_")] + class MyTestBase(unittest.TestCase): "Intermediate class that adds useful helper methods." def assertEqualBin(self, x, y): @@ -1274,6 +1275,107 @@ def testMillerRabin(self): mr = miller_rabin_new(n) self.assertEqual(miller_rabin_test(mr, 0x251), "failed") +class ntru(MyTestBase): + def testMultiply(self): + self.assertEqual( + ntru_ring_multiply([1,1,1,1,1,1], [1,1,1,1,1,1], 11, 59), + [1,2,3,4,5,6,5,4,3,2,1]) + self.assertEqual(ntru_ring_multiply( + [1,0,1,2,0,0,1,2,0,1,2], [2,0,0,1,0,1,2,2,2,0,2], 11, 3), + [1,0,0,0,0,0,0,0,0,0,0]) + + def testInvert(self): + # Over GF(3), x^11-x-1 factorises as + # (x^3+x^2+2) * (x^8+2*x^7+x^6+2*x^4+2*x^3+x^2+x+1) + # so we expect that 2,0,1,1 has no inverse, being one of those factors. + self.assertEqual(ntru_ring_invert([0], 11, 3), None) + self.assertEqual(ntru_ring_invert([1], 11, 3), + [1,0,0,0,0,0,0,0,0,0,0]) + self.assertEqual(ntru_ring_invert([2,0,1,1], 11, 3), None) + self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 3), + [2,0,0,1,0,1,2,2,2,0,2]) + + self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 59), + [1,26,10,1,38,48,34,37,53,3,53]) + + def testMod3Round3(self): + # Try a prime congruent to 1 mod 3 + self.assertEqual(ntru_mod3([4,5,6,0,1,2,3], 7, 7), + [0,1,-1,0,1,-1,0]) + self.assertEqual(ntru_round3([4,5,6,0,1,2,3], 7, 7), + [-3,-3,0,0,0,3,3]) + + # And one congruent to 2 mod 3 + self.assertEqual(ntru_mod3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), + [1,-1,0,1,-1,0,1,-1,0,1,-1]) + self.assertEqual(ntru_round3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), + [-6,-3,-3,-3,0,0,0,3,3,3,6]) + + def testBiasScale(self): + self.assertEqual(ntru_bias([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), + [4,5,6,7,8,9,10,0,1,2,3]) + self.assertEqual(ntru_scale([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), + [0,4,8,1,5,9,2,6,10,3,7]) + + def testEncode(self): + # Test a small case. Worked through in detail: + # + # Pass 1: + # Input list is (89:123, 90:234, 344:345, 432:456, 222:567) + # (89:123, 90:234) -> (89+123*90 : 123*234) = (11159:28782) + # Emit low byte of 11159 = 0x97, and get (43:113) + # (344:345, 432:456) -> (344+345*432 : 345*456) = (149384:157320) + # Emit low byte of 149384 = 0x88, and get (583:615) + # Odd pair (222:567) is copied to end of new list + # Final list is (43:113, 583:615, 222:567) + # Pass 2: + # Input list is (43:113, 583:615, 222:567) + # (43:113, 583:615) -> (43+113*583, 113*615) = (65922:69495) + # Emit low byte of 65922 = 0x82, and get (257:272) + # Odd pair (222:567) is copied to end of new list + # Final list is (257:272, 222:567) + # Pass 3: + # Input list is (257:272, 222:567) + # (257:272, 222:567) -> (257+272*222, 272*567) = (60641:154224) + # Emit low byte of 60641 = 0xe1, and get (236:603) + # Final list is (236:603) + # Cleanup: + # Emit low byte of 236 = 0xec, and get (0:3) + # Emit low byte of 0 = 0x00, and get (0:1) + + ms = [123,234,345,456,567] + rs = [89,90,344,432,222] + encoding = unhex('978882e1ec00') + sched = ntru_encode_schedule(ms) + self.assertEqual(sched.encode(rs), encoding) + self.assertEqual(sched.decode(encoding), rs) + + # Encode schedules for sntrup761 public keys and ciphertexts + pubsched = ntru_encode_schedule([4591]*761) + self.assertEqual(pubsched.length(), 1158) + ciphersched = ntru_encode_schedule([1531]*761) + self.assertEqual(ciphersched.length(), 1007) + + # Test round-trip encoding using those schedules + testlist = list(range(761)) + pubtext = pubsched.encode(testlist) + self.assertEqual(pubsched.decode(pubtext), testlist) + ciphertext = ciphersched.encode(testlist) + self.assertEqual(ciphersched.decode(ciphertext), testlist) + + def testCore(self): + # My own set of NTRU Prime parameters, satisfying all the + # requirements and tiny enough for convenient testing + p, q, w = 11, 59, 3 + + with random_prng('ntru keygen seed'): + keypair = ntru_keygen(p, q, w) + plaintext = ntru_gen_short(p, w) + + ciphertext = ntru_encrypt(plaintext, ntru_pubkey(keypair), p, q) + recovered = ntru_decrypt(ciphertext, keypair) + self.assertEqual(plaintext, recovered) + class crypt(MyTestBase): def testSSH1Fingerprint(self): # Example key and reference fingerprint value generated by @@ -1285,9 +1387,9 @@ def testSSH1Fingerprint(self): def testSSH2Fingerprints(self): # A sensible key blob that we can make sense of. - sensible_blob = base64.decodebytes( - b'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc' - b'SLJBW5ubn6ZINwCOzpn3') + sensible_blob = b64( + 'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc' + 'SLJBW5ubn6ZINwCOzpn3') self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "sha256"), b'ssh-ed25519 255 SHA256:' b'E4VmaHW0sUF7SUgSEOmMJ8WBtt0e/j3zbsKvyqfFnu4') @@ -1313,6 +1415,35 @@ def testSSH2Fingerprints(self): self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "md5"), b'ac:bd:18:db:4c:c2:f8:5c:ed:ef:65:4f:cc:c4:a4:d8') + # A certified key. + cert_blob = b64( + 'AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ4Ds9YwRHxs' + 'xdtUitRbZGz0MgKGZSBVrTHI1AbvetofAAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86' + 'i9bOHhMJYbDbxEJfAAAAAAAAAG8AAAABAAAAAmlkAAAADAAAAAh1c2VybmFtZQAA' + 'AAAAAAPoAAAAAAAAB9AAAAAAAAAAAAAAAAAAAAE+AAAAIHNzaC1lZDI1NTE5LWNl' + 'cnQtdjAxQG9wZW5zc2guY29tAAAAICl5MiUNt8hoAAHT0v00JYOkWe2UW31+Qq5Q' + 'HYKWGyVjAAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAAAAA' + 'AG8AAAABAAAAAmlkAAAAEgAAAA5kb2Vzbid0IG1hdHRlcgAAAAAAAAPoAAAAAAAA' + 'B9AAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxS' + 'mVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAXbRz3' + 'lBmoU4FVge29jn04MfubF6U0CoPG1nbeZSgDN2iz7qtZ75XIk5O/Z/W9nA8jwsiz' + 'iSEMItjvR7HEN8MIAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECszhkY8bUbSCjmHEMP' + 'LjcOX6OaeBzPIYYYXJzpLn+m+CIwDXRIxyvON5/d/TomgAFNJutfOEsqIzy5OAvl' + 'p5IO') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:42JaqhHUNa5CoKxGWqtKXF0Awz7b0aPrtgBZ9VLLHfY') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'8e:40:00:e0:1f:4a:9c:b3:c8:e9:05:59:04:03:44:b3') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:W/+SDEg7S+/dAn4SrodJ2c8bYvt13XXA7YYlQ6E8R5U') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'03:cf:aa:8e:aa:c3:a0:97:bb:2e:7e:57:9d:08:b5:be') + + def testAES(self): # My own test cases, generated by a mostly independent # reference implementation of AES in Python. ('Mostly' @@ -1673,6 +1804,66 @@ def testSSHCiphers(self): ssh_cipher_decrypt(cipher, iv[:ivlen]) self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) + def testChaCha20Poly1305(self): + # A test case of this cipher taken from a real connection to + # OpenSSH. + key = unhex('49e67c5ae596ea7f230e266538d0e373' + '177cc8fe08ff7b642c22d736ca975655' + 'c3fb639010fd297ca03c36b20a182ef4' + '0e1272f0c54251c175546ee00b150805') + len_p = unhex('00000128') + len_c = unhex('3ff3677b') + msg_p = unhex('0807000000020000000f736572766572' + '2d7369672d616c6773000000db737368' + '2d656432353531392c736b2d7373682d' + '65643235353139406f70656e7373682e' + '636f6d2c7373682d7273612c7273612d' + '736861322d3235362c7273612d736861' + '322d3531322c7373682d6473732c6563' + '6473612d736861322d6e697374703235' + '362c65636473612d736861322d6e6973' + '74703338342c65636473612d73686132' + '2d6e697374703532312c736b2d656364' + '73612d736861322d6e69737470323536' + '406f70656e7373682e636f6d2c776562' + '617574686e2d736b2d65636473612d73' + '6861322d6e69737470323536406f7065' + '6e7373682e636f6d0000001f7075626c' + '69636b65792d686f7374626f756e6440' + '6f70656e7373682e636f6d0000000130' + 'c34aaefcafae6fc2') + msg_c = unhex('bf587eabf385b1281fa9c755d8515dfd' + 'c40cb5e993b346e722dce48b1741b4e5' + 'ce9ae075f6df0a1d2f72f94f73570125' + '7011630bbb0c7febd767184c0d5aa810' + '47cbce82972129a234b8ac5fc5f2b5be' + '9264baca6d13ff3c9813a61e1f23468f' + '31964b60fc3f0888a227f02c737b2d27' + 'b7ae3cd60ede17533863a5bb6bb2d60a' + 'c998ccd27e8ba56259f676ed04749fad' + '4114678fb871add3a40625110637947c' + 'e91459811622fd3d1fa7eb7efad4b1e8' + '97f3e860473935d3d8df0679a8b0df85' + 'aa4124f2d9ac7207abd10719f465c9ed' + '859d2b03bde55315b9024f660ba8d63a' + '64e0beb81e532201df830a52cf221484' + '18d0c4c7da242346161d7320ac534cb5' + 'c6b6fec905ee5f424becb9f97c3afbc5' + '5ef4ba369e61bce847158f0dc5bd7227' + '3b8693642db36f87') + mac = unhex('09757178642dfc9f2c38ac5999e0fcfd') + seqno = 3 + c = ssh_cipher_new('chacha20_poly1305') + m = ssh2_mac_new('poly1305', c) + c.setkey(key) + self.assertEqualBin(c.encrypt_length(len_p, seqno), len_c) + self.assertEqualBin(c.encrypt(msg_p), msg_c) + m.start() + m.update(ssh_uint32(seqno) + len_c + msg_c) + self.assertEqualBin(m.genresult(), mac) + self.assertEqualBin(c.decrypt_length(len_c, seqno), len_p) + self.assertEqualBin(c.decrypt(msg_c), msg_p) + def testRSAKex(self): # Round-trip test of the RSA key exchange functions, plus a # hardcoded plain/ciphertext pair to guard against the @@ -1772,13 +1963,13 @@ def testMontgomeryKexLowOrderPoints(self): ] with random_prng("doesn't matter"): - ecdh25519 = ssh_ecdhkex_newkey('curve25519') - ecdh448 = ssh_ecdhkex_newkey('curve448') + ecdh25519 = ecdh_key_new('curve25519', False) + ecdh448 = ecdh_key_new('curve448', False) for pub in bad_keys_25519: - key = ssh_ecdhkex_getkey(ecdh25519, unhex(pub)) + key = ecdh_key_getkey(ecdh25519, unhex(pub)) self.assertEqual(key, None) for pub in bad_keys_448: - key = ssh_ecdhkex_getkey(ecdh448, unhex(pub)) + key = ecdh_key_getkey(ecdh448, unhex(pub)) self.assertEqual(key, None) def testPRNG(self): @@ -2199,8 +2390,8 @@ def testKeyMethods(self): for alg, pubb64, privb64, bits, cachestr, siglist in test_keys: # Decode the blobs in the above test data. - pubblob = base64decode(pubb64.encode('ASCII')) - privblob = base64decode(privb64.encode('ASCII')) + pubblob = b64(pubb64) + privblob = b64(privb64) # Check the method that examines a public blob directly # and returns an integer showing the key size. @@ -2234,7 +2425,7 @@ def testKeyMethods(self): # value. for flags, sigb64 in siglist: # Decode the signature blob from the test data. - sigblob = base64decode(sigb64.encode('ASCII')) + sigblob = b64(sigb64) # Sign our test message, and check it produces exactly # the expected signature blob. @@ -2322,7 +2513,7 @@ def testPPKLoadSave(self): (True, algorithm, public_blob, comment, None)) self.assertEqual(ppk_loadpub_s("not a key file"), (False, None, b'', None, - b'not a PuTTY SSH-2 private key')) + b'not a public key or a PuTTY SSH-2 private key')) k1, c, e = ppk_load_s(input_clear_key, None) self.assertEqual((c, e), (comment, None)) @@ -2384,7 +2575,7 @@ def testPPKLoadSave(self): (True, algorithm, public_blob, comment, None)) self.assertEqual(ppk_loadpub_s("not a key file"), (False, None, b'', None, - b'not a PuTTY SSH-2 private key')) + b'not a public key or a PuTTY SSH-2 private key')) k1, c, e = ppk_load_s(v2_clear_key, None) self.assertEqual((c, e), (comment, None)) @@ -2461,6 +2652,470 @@ def testRSA1LoadSave(self): self.assertEqual(rsa1_save_sb(k2, comment, pp), input_encrypted_key) + def testOpenSSHCert(self): + def per_base_keytype_tests(alg, run_validation_tests=False, + run_ca_rsa_tests=False, ca_signflags=None): + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check the simple certificate methods + self.assertEqual(certified_key.cert_id_string(), b'id') + self.assertEqual(certified_key.ca_public_blob(), + ca_key.public_blob()) + recovered_base_key = certified_key.base_key() + self.assertEqual(recovered_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(recovered_base_key.private_blob(), + base_key.private_blob()) + + # Check that an ordinary key also supports base_key() + redundant_base_key = base_key.base_key() + self.assertEqual(redundant_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(redundant_base_key.private_blob(), + base_key.private_blob()) + + # Test signing and verifying using the certified key type + test_string = b'hello, world' + base_sig = base_key.sign(test_string, 0) + certified_sig = certified_key.sign(test_string, 0) + self.assertEqual(base_sig, certified_sig) + self.assertEqual(certified_key.verify(base_sig, test_string), True) + + # Check a successful certificate verification + result, err = certified_key.check_cert( + False, b'username', 1000, '') + self.assertEqual(result, True) + + # If the key type is RSA, check that the validator rejects + # wrong kinds of CA signature + if run_ca_rsa_tests: + forbid_all = ",".join(["permit_rsa_sha1=false", + "permit_rsa_sha256=false," + "permit_rsa_sha512=false"]) + result, err = certified_key.check_cert( + False, b'username', 1000, forbid_all) + self.assertEqual(result, False) + + algname = ("rsa-sha2-512" if ca_signflags == 4 else + "rsa-sha2-256" if ca_signflags == 2 else + "ssh-rsa") + self.assertEqual(err, ( + "Certificate signature uses '{}' signature type " + "(forbidden by user configuration)".format(algname) + .encode("ASCII"))) + + permitflag = ("permit_rsa_sha512" if ca_signflags == 4 else + "permit_rsa_sha256" if ca_signflags == 2 else + "permit_rsa_sha1") + result, err = certified_key.check_cert( + False, b'username', 1000, "{},{}=true".format( + forbid_all, permitflag)) + self.assertEqual(result, True) + + # That's the end of the tests we need to repeat for all + # the key types. Now we move on to detailed tests of the + # validation, which are independent of key type, so we + # only need to test this part once. + if not run_validation_tests: + return + + # Check cert verification at the other end of the valid + # time range + result, err = certified_key.check_cert( + False, b'username', 1999, '') + self.assertEqual(result, True) + + # Oops, wrong certificate type + result, err = certified_key.check_cert( + True, b'username', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is user; expected host') + + # Oops, wrong username + result, err = certified_key.check_cert( + False, b'someoneelse', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate\'s username list ["username"] ' + b'does not contain expected username "someoneelse"') + + # Oops, time is wrong. (But we can't check the full error + # message including the translated start/end times, because + # those vary with LC_TIME.) + result, err = certified_key.check_cert( + False, b'someoneelse', 999, '') + self.assertEqual(result, False) + self.assertEqual(err[:30], b'Certificate is not valid until') + result, err = certified_key.check_cert( + False, b'someoneelse', 2000, '') + self.assertEqual(result, False) + self.assertEqual(err[:22], b'Certificate expired at') + + # Modify the certificate so that the signature doesn't validate + username_position = cert_pub.index(b'username') + bytelist = list(cert_pub) + bytelist[username_position] ^= 1 + miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist), + base_key.private_blob()) + result, err = miscertified_key.check_cert( + False, b'username', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b"Certificate's signature is invalid") + + # Make a certificate containing a critical option, to test we + # reject it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 112, + principals = [b'username'], + critical_options = {b'unknown-option': b'yikes!'}), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate specifies an unsupported ' + b'critical option "unknown-option"') + + # Make a certificate containing a non-critical extension, to + # test we _accept_ it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 113, + principals = [b'username'], + extensions = {b'unknown-ext': b'whatever, dude'}), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1000, '') + self.assertEqual(result, True) + + # Make a certificate on the CA key, and re-sign the main + # key using that, to ensure that two-level certs are rejected + ca_self_certificate = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = ca_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b"doesn't matter"], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + import base64 + self_signed_ca_key = ssh_key_new_pub( + alg + '-cert', ca_self_certificate) + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = self_signed_ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1500, '') + self.assertEqual(result, False) + self.assertEqual( + err, b'Certificate is signed with a certified key ' + b'(forbidden by OpenSSH certificate specification)') + + # Now try a host certificate. We don't need to do _all_ the + # checks over again, but at least make sure that setting + # CertType.host leads to the certificate validating with + # host=True and not with host=False. + # + # Also, in this test, give two hostnames. + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.host, + keyid = b'id', + serial = 114, + principals = [b'hostname.example.com', + b'hostname2.example.com'], + valid_after = 1000, + valid_before = 2000), ca_key) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check certificate type + result, err = certified_key.check_cert( + True, b'hostname.example.com', 1000, '') + self.assertEqual(result, True) + result, err = certified_key.check_cert( + False, b'hostname.example.com', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is host; expected user') + + # Check the second hostname and an unknown one + result, err = certified_key.check_cert( + True, b'hostname2.example.com', 1000, '') + self.assertEqual(result, True) + result, err = certified_key.check_cert( + True, b'hostname3.example.com', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate\'s hostname list [' + b'"hostname.example.com", "hostname2.example.com"] ' + b'does not contain expected hostname ' + b'"hostname3.example.com"') + + # And just for luck, try a totally unknown certificate type, + # making sure that it's rejected in both modes and gives the + # right error message + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = 12345, + keyid = b'id', + serial = 114, + principals = [b'username', b'hostname.example.com'], + valid_after = 1000, + valid_before = 2000), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is unknown value 12345; ' + b'expected user') + result, err = certified_key.check_cert( + True, b'hostname.example.com', 1000, '') + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is unknown value 12345; ' + b'expected host') + + ca_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7'), b64('AAAAIK4STyaf63xHidqhvUop9/OKiYqSh/YEWLCp1lL5Vs4u')) + + base_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86i9bOHhMJYbDbxEJf'), b64('AAAAIB38jy02ZWYb4EXrJG9RIljEhqidrG5DdhZvMvoeOTZs')) + per_base_keytype_tests('ed25519', run_validation_tests=True) + + base_key = ssh_key_new_priv('p256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGc8VXplXScdWckJgAw6Hag5PP7g0JEVdLY5lP2ujvVxU5GwwquYLbX3yyj1zY5h2n9GoXrnRxzR5+5g8wsNjTA='), b64('AAAAICVRicPD5MyOHfKdnC/8IP84t+nQ4bqmMUyX7NHyCKjS')) + per_base_keytype_tests('p256') + + base_key = ssh_key_new_priv('p384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLITujAbKwHDEzVDFqWtA+CleAhN/Y+53mHbEoTpU0aof9L+2lHeUshXdxHDLxY69wO5+WfqWJCwSY58PuXIZzIisQkvIKq6LhpzK6C5JpWJ8Kbv7su+qZPf5sYoxx0xZg=='), b64('AAAAMHyQTQYcIA/bR4ZvWS86ohb5Lu0MhzjD8bUb3q8jnROOe3BrE9I8oJcx+l1lddPouA==')) + per_base_keytype_tests('p384') + + base_key = ssh_key_new_priv('p521', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADButwMRGdLkFhWcSDsLhRhgyrLQq1/A0M8x4GgEmesh4iydo4tGKZR14GhHvx150IWTE1Tre4wyH+1FsTfAlpUBgBDQjsZE0D3u3SLp4qjjhzyrJGhEUDd9J6lsr6JrXbTefz5+LkM9m5l86y9PoAgT+F25OiTYlfvR5qx/pzIPoCnpA=='), b64('AAAAQgFV8xBXC7XZNxdW1oWg6yCZjys2AX4beZVehE9A2R/4m11dHnfqoE1FzbRxj9xqwKvHZRhMOJ//DYuhtcG6+6yHsA==')) + per_base_keytype_tests('p521') + + base_key = ssh_key_new_priv('dsa', b64('AAAAB3NzaC1kc3MAAABCAXgDrF9Fw/Ty+QcoljAGjGL/Ph5+NBQqUYADm4wxF+aazjQXLuZ0VW9OdYBisgDZlYDj/w7y9NxCBgax2BSkhDNxAAAAFQC/YwnFzcom6cRRHPXtOUDLi2I29QAAAEIAqGOUYpfFPwzhgAmYXwWKdK8ouSUplNE29FOpv6NYjyf7k+tLSWF3b8oZdtw6XP8lr4vcKXC9Ik0YpKYKM7iKfb8AAABCAUDCcojlDLQmLHg8HhFCtT/CpayNh4OfmSrP8XOwJnFD/eBaSGuPB5EvGd+m6gr+Pc0RSAlWP1aIzUbYkQ33Yk58'), b64('AAAAFQChVuOTNrCwLSJygxlRQhDwHozwSg==')) + per_base_keytype_tests('dsa') + + base_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQDXLnqGPQLL9byoHFQWPiF5Uzcd0KedMRRJmuwyCAWprlh8EN43mL2F7q27Uv54m/ztqW4DsVtiCN6cDYvB9QPNYFR5npwsEAJ06Ro4s9ZpFsZVOvitqeoYIs+jkS8vq5V8X4hwLlJ8vXYPD6rHJhOz6HFpImHmVu40Mu5lq+MCQQ=='), b64('AAAAgH5dBwrJzVilKHK4oBCnz9SFr7pMjAHdjoJi/g2rdFfe0IubBEQ16CY8sb1t0Y5WXEPc2YRFpNp/RurxcX8nOWFPzgNJXEtkKpKO9Juqu5hL4xcf8QKC2aJFk3EXrn/M6dXEdjqN4UhsT6iFTsHKU4b8T6VTtgKzwkOdic/YotaBAAAAQQD6liDTlzTKzLhbypI6l+y2BGA3Kkzz71Y2o7XH/6bZ6HJOFgHuJeL3eNQptzd8Q+ctfvR0fa2PItYydDOlVUeZAAAAQQDb1IsO1/fkflDZhPQT2XOxtrjgQhotKjr6CSmJtDNmo1mOCN+mOgxtDfJ0PNEEM1P9CO2Ia3njtkxt4Ep2EpjpAAAAQQClRxLEHsRK9nMPZ4HW45iyw5dHhYar9pYUql2VnixWQxrHy13ZIaWxi6xwWjuPglrdBgEQfYwH9KGmlFmZXT/Z')) + per_base_keytype_tests('rsa') + + # Now switch to an RSA certifying key, and test different RSA + # signature subtypes being used to sign the certificate + ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM=')) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4) + + def testAESGCMBlockBoundaries(self): + # For standard AES-GCM test vectors, see the separate tests in + # standard_test_vectors.testAESGCM. This function will test + # the local interface, including the skip length and the + # machinery for incremental MAC update. + + def aesgcm(key, iv, aes_impl, gcm_impl): + c = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), aes_impl)) + m = ssh2_mac_new('aesgcm_{}'.format(gcm_impl), c) + if m is None: return # skip test if HW GCM not available + c.setkey(key) + c.setiv(iv + b'\0'*4) + m.setkey(b'') + return c, m + + def test_one(aes_impl, gcm_impl): + # An actual test from a session with OpenSSH, which + # demonstrates that the implementation in practice matches up + # to what the test vectors say. This is its SSH2_MSG_EXT_INFO + # packet. + key = unhex('dbf98b2f56c83fb2f9476aa876511225') + iv = unhex('9af15ecccf2bacaaa9625a6a') + plain = unhex('1007000000020000000f736572766572' + '2d7369672d616c6773000000db737368' + '2d656432353531392c736b2d7373682d' + '65643235353139406f70656e7373682e' + '636f6d2c7373682d7273612c7273612d' + '736861322d3235362c7273612d736861' + '322d3531322c7373682d6473732c6563' + '6473612d736861322d6e697374703235' + '362c65636473612d736861322d6e6973' + '74703338342c65636473612d73686132' + '2d6e697374703532312c736b2d656364' + '73612d736861322d6e69737470323536' + '406f70656e7373682e636f6d2c776562' + '617574686e2d736b2d65636473612d73' + '6861322d6e69737470323536406f7065' + '6e7373682e636f6d0000001f7075626c' + '69636b65792d686f7374626f756e6440' + '6f70656e7373682e636f6d0000000130' + '5935130804ad4b19ed2789210290c438') + aad = unhex('00000130') + cipher = unhex('c4b88f35c1ef8aa6225033c3f185d648' + '3c485d84930d5846f7851daacbff49d5' + '8cf72169fca7ab3c170376df65dd69de' + 'c40a94c6b8e3da6d61161ab19be27466' + '02e0dfa3330faae291ef4173a20e87a4' + 'd40728c645baa72916c1958531ef7b54' + '27228513e53005e6d17b9bb384b8d8c1' + '92b8a10b731459eed5a0fb120c283412' + 'e34445981df1257f1c35a06196731fed' + '1b3115f419e754de0b634bf68768cb02' + '29e70bb2259cedb5101ff6a4ac19aaad' + '46f1c30697361b45d6c152c3069cee6b' + 'd46e9785d65ea6bf7fca41f0ac3c8e93' + 'ce940b0059c39d51e49c17f60d48d633' + '5bae4402faab61d8d65221b24b400e65' + '89f941ff48310231a42641851ea00832' + '2c2d188f4cc6a4ec6002161c407d0a92' + 'f1697bb319fbec1ca63fa8e7ac171c85' + '5b60142bfcf4e5b0a9ada3451799866e') + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + len_dec = c.decrypt_length(aad, 123) + self.assertEqual(len_dec, aad) # length not actually encrypted + m.start() + # We expect 4 bytes skipped (the sequence number that + # ChaCha20-Poly1305 wants at the start of its MAC), and 4 + # bytes AAD. These were initialised by the call to + # encrypt_length. + m.update(b'fake' + aad + cipher) + self.assertEqualBin(m.genresult(), + unhex('4a5a6d57d54888b4e58c57a96e00b73a')) + self.assertEqualBin(c.decrypt(cipher), plain) + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + len_enc = c.encrypt_length(aad, 123) + self.assertEqual(len_enc, aad) # length not actually encrypted + self.assertEqualBin(c.encrypt(plain), cipher) + + # Test incremental update. + def testIncremental(skiplen, aad, plain): + key, iv = b'SomeRandomKeyVal', b'SomeRandomIV' + mac_input = b'x' * skiplen + aad + plain + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + + m.start() + m.update(mac_input) + reference_mac = m.genresult() + + # Break the input just once, at each possible byte + # position. + for i in range(1, len(mac_input)): + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + m.start() + m.update(mac_input[:i]) + m.update(mac_input[i:]) + self.assertEqualBin(m.genresult(), reference_mac) + + # Feed the entire input in a byte at a time. + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + m.start() + for i in range(len(mac_input)): + m.update(mac_input[i:i+1]) + self.assertEqualBin(m.genresult(), reference_mac) + + # Incremental test with more than a full block of each thing + testIncremental(23, b'abcdefghijklmnopqrst', + b'Lorem ipsum dolor sit amet') + + # Incremental test with exactly a full block of each thing + testIncremental(16, b'abcdefghijklmnop', + b'Lorem ipsum dolo') + + # Incremental test with less than a full block of each thing + testIncremental(7, b'abcdefghij', + b'Lorem ipsum') + + for aes_impl in get_aes_impls(): + for gcm_impl in get_aesgcm_impls(): + with self.subTest(aes_impl=aes_impl, gcm_impl=gcm_impl): + test_one(aes_impl, gcm_impl) + + def testAESGCMIV(self): + key = b'SomeRandomKeyVal' + + def test(gcm, cbc, iv_fixed, iv_msg): + gcm.setiv(ssh_uint32(iv_fixed) + ssh_uint64(iv_msg) + b'fake') + + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(1)) + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(2)) + + gcm.next_message() + iv_msg = (iv_msg + 1) & ((1<<64)-1) + + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(1)) + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(2)) + + + for impl in get_aes_impls(): + with self.subTest(aes_impl=impl): + gcm = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), impl)) + gcm.setkey(key) + + cbc = ssh_cipher_new('aes{:d}_cbc_{}'.format(8*len(key), impl)) + cbc.setkey(key) + + # A simple test to ensure the low word gets + # incremented and that the whole IV looks basically + # the way we expect it to + test(gcm, cbc, 0x27182818, 0x3141592653589793) + + # Test that carries are propagated into the high word + test(gcm, cbc, 0x27182818, 0x00000000FFFFFFFF) + + # Test that carries _aren't_ propagated out of the + # high word of the message counter into the fixed word + # at the top + test(gcm, cbc, 0x27182818, 0xFFFFFFFFFFFFFFFF) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): @@ -3107,9 +3762,9 @@ def testMontgomeryKex(self): for method, priv, pub, expected in rfc7748s5_2: with queued_specific_random_data(unhex(priv)): - ecdh = ssh_ecdhkex_newkey(method) - key = ssh_ecdhkex_getkey(ecdh, unhex(pub)) - self.assertEqual(int(key), expected) + ecdh = ecdh_key_new(method, False) + key = ecdh_key_getkey(ecdh, unhex(pub)) + self.assertEqual(key, ssh2_mpint(expected)) # Bidirectional tests, consisting of the input random number # strings for both parties, and the expected public values and @@ -3131,15 +3786,15 @@ def testMontgomeryKex(self): for method, apriv, apub, bpriv, bpub, expected in rfc7748s6: with queued_specific_random_data(unhex(apriv)): - alice = ssh_ecdhkex_newkey(method) + alice = ecdh_key_new(method, False) with queued_specific_random_data(unhex(bpriv)): - bob = ssh_ecdhkex_newkey(method) - self.assertEqualBin(ssh_ecdhkex_getpublic(alice), unhex(apub)) - self.assertEqualBin(ssh_ecdhkex_getpublic(bob), unhex(bpub)) - akey = ssh_ecdhkex_getkey(alice, unhex(bpub)) - bkey = ssh_ecdhkex_getkey(bob, unhex(apub)) - self.assertEqual(int(akey), expected) - self.assertEqual(int(bkey), expected) + bob = ecdh_key_new(method, False) + self.assertEqualBin(ecdh_key_getpublic(alice), unhex(apub)) + self.assertEqualBin(ecdh_key_getpublic(bob), unhex(bpub)) + akey = ecdh_key_getkey(alice, unhex(bpub)) + bkey = ecdh_key_getkey(bob, unhex(apub)) + self.assertEqual(akey, ssh2_mpint(expected)) + self.assertEqual(bkey, ssh2_mpint(expected)) def testCRC32(self): self.assertEqual(crc32_rfc1662("123456789"), 0xCBF43926) @@ -3192,8 +3847,7 @@ def testHttpDigest(self): "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", "FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", 1, "MD5", False] - cnonce = base64.decodebytes( - b'f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') + cnonce = b64('f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') with queued_specific_random_data(cnonce): self.assertEqual(http_digest_response(*params), b'username="Mufasa", ' @@ -3240,8 +3894,7 @@ def testHttpDigest(self): "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", 1, "SHA-512-256", True] - cnonce = base64.decodebytes( - b'NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') + cnonce = b64('NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') with queued_specific_random_data(cnonce): self.assertEqual(http_digest_response(*params), b'username="488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec", ' @@ -3256,6 +3909,178 @@ def testHttpDigest(self): b'opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", ' b'userhash=true') + def testAESGCM(self): + def test(key, iv, plaintext, aad, ciphertext, mac): + c = ssh_cipher_new('aes{:d}_gcm'.format(8*len(key))) + m = ssh2_mac_new('aesgcm_{}'.format(impl), c) + if m is None: return # skip test if HW GCM not available + c.setkey(key) + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, 0, len(aad)) + + # Some test cases have plaintext/ciphertext that is not a + # multiple of the cipher block size. Our MAC + # implementation supports this, but the cipher + # implementation expects block-granular input. + padlen = 15 & -len(plaintext) + ciphertext_got = c.encrypt(plaintext + b'0' * padlen)[ + :len(plaintext)] + + m.start() + m.update(aad + ciphertext) + mac_got = m.genresult() + + self.assertEqualBin(ciphertext_got, ciphertext) + self.assertEqualBin(mac_got, mac) + + c.setiv(iv + b'\0'*4) + + for impl in get_aesgcm_impls(): + # 'The Galois/Counter Mode of Operation', McGrew and + # Viega, Appendix B. All the tests except the ones whose + # IV is the wrong length, because handling that requires + # an extra evaluation of the polynomial hash, which is + # never used in an SSH context, so I didn't implement it + # just for the sake of test vectors. + + # Test Case 1 + test(unhex('00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('58e2fccefa7e3061367f1d57a4e7455a')) + + # Test Case 2 + test(unhex('00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('0388dace60b6a392f328c2b971b2fe78'), + unhex('ab6e47d42cec13bdf53a67b21257bddf')) + + # Test Case 3 + test(unhex('feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('42831ec2217774244b7221b784d0d49c' + 'e3aa212f2c02a4e035c17e2329aca12e' + '21d514b25466931c7d8f6a5aac84aa05' + '1ba30b396a0aac973d58e091473f5985'), + unhex('4d5c2af327cd64a62cf35abd2ba6fab4')) + + # Test Case 4 + test(unhex('feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('42831ec2217774244b7221b784d0d49c' + 'e3aa212f2c02a4e035c17e2329aca12e' + '21d514b25466931c7d8f6a5aac84aa05' + '1ba30b396a0aac973d58e091'), + unhex('5bc94fbc3221a5db94fae95ae7121a47')) + + # Test Case 7 + test(unhex('00000000000000000000000000000000' + '0000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('cd33b28ac773f74ba00ed1f312572435')) + + # Test Case 8 + test(unhex('00000000000000000000000000000000' + '0000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('98e7247c07f0fe411c267e4384b0f600'), + unhex('2ff58d80033927ab8ef4d4587514f0fb')) + + # Test Case 9 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('3980ca0b3c00e841eb06fac4872a2757' + '859e1ceaa6efd984628593b40ca1e19c' + '7d773d00c144c525ac619d18c84a3f47' + '18e2448b2fe324d9ccda2710acade256'), + unhex('9924a7c8587336bfb118024db8674a14')) + + # Test Case 10 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('3980ca0b3c00e841eb06fac4872a2757' + '859e1ceaa6efd984628593b40ca1e19c' + '7d773d00c144c525ac619d18c84a3f47' + '18e2448b2fe324d9ccda2710'), + unhex('2519498e80f1478f37ba55bd6d27618c')) + + # Test Case 13 + test(unhex('00000000000000000000000000000000' + '00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('530f8afbc74536b9a963b4f1c4cb738b')) + + # Test Case 14 + test(unhex('00000000000000000000000000000000' + '00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('cea7403d4d606b6e074ec5d3baf39d18'), + unhex('d0d1c8a799996bf0265b98b5d48ab919')) + + # Test Case 15 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('522dc1f099567d07f47f37a32a84427d' + '643a8cdcbfe5c0c97598a2bd2555d1aa' + '8cb08e48590dbb3da7b08b1056828838' + 'c5f61e6393ba7a0abcc9f662898015ad'), + unhex('b094dac5d93471bdec1a502270e3cc6c')) + + # Test Case 16 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('522dc1f099567d07f47f37a32a84427d' + '643a8cdcbfe5c0c97598a2bd2555d1aa' + '8cb08e48590dbb3da7b08b1056828838' + 'c5f61e6393ba7a0abcc9f662'), + unhex('76fc6ece0f4e1768cddf8853bb2d551b')) + if __name__ == "__main__": # Run the tests, suppressing automatic sys.exit and collecting the # unittest.TestProgram instance returned by unittest.main instead. diff --git a/code/test/fuzzterm.c b/code/test/fuzzterm.c index faea2dd7..0d4597b1 100644 --- a/code/test/fuzzterm.c +++ b/code/test/fuzzterm.c @@ -10,34 +10,34 @@ static const TermWinVtable fuzz_termwin_vt; int main(int argc, char **argv) { - char blk[512]; - size_t len; - Terminal *term; - Conf *conf; - struct unicode_data ucsdata; - TermWin termwin; - - termwin.vt = &fuzz_termwin_vt; - - conf = conf_new(); - do_defaults(NULL, conf); - init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); - - term = term_init(conf, &ucsdata, &termwin); - term_size(term, 24, 80, 10000); - term->ldisc = NULL; - /* Tell american fuzzy lop that this is a good place to fork. */ + char blk[512]; + size_t len; + Terminal *term; + Conf *conf; + struct unicode_data ucsdata; + TermWin termwin; + + termwin.vt = &fuzz_termwin_vt; + + conf = conf_new(); + do_defaults(NULL, conf); + init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); + + term = term_init(conf, &ucsdata, &termwin); + term_size(term, 24, 80, 10000); + term->ldisc = NULL; + /* Tell american fuzzy lop that this is a good place to fork. */ #ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); + __AFL_INIT(); #endif - while (!feof(stdin)) { - len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, blk, len); - } - term_update(term); - return 0; + while (!feof(stdin)) { + len = fread(blk, 1, sizeof(blk), stdin); + term_data(term, blk, len); + } + term_update(term); + return 0; } /* functions required by terminal.c */ @@ -134,44 +134,44 @@ void timer_change_notify(unsigned long next) { } /* needed by config.c */ -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { } +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { return 0; } +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { } +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { return false; } +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { return dupstr("moo"); } -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { } +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { return 0; } -int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { return -1; } +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { return false; } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_update_start(union control *ctrl, dlgparam *dp) { } -void dlg_update_done(union control *ctrl, dlgparam *dp) { } -void dlg_set_focus(union control *ctrl, dlgparam *dp) { } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { } +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { } +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn) { } +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } void dlg_beep(dlgparam *dp) { } void dlg_error_msg(dlgparam *dp, const char *msg) { } void dlg_end(dlgparam *dp, int value) { } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { return false; } -void dlg_refresh(union control *ctrl, dlgparam *dp) { } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { } +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { return false; } const int ngsslibs = 0; const char *const gsslibnames[0] = { }; diff --git a/code/test/list-accel.py b/code/test/list-accel.py index af93d420..ac92d376 100644 --- a/code/test/list-accel.py +++ b/code/test/list-accel.py @@ -25,10 +25,14 @@ def list_implementations(alg, checkfn): def list_cipher_implementations(alg): list_implementations(alg, lambda impl: ssh_cipher_new(impl) is not None) +def list_mac_implementations(alg): + list_implementations(alg, lambda impl: ssh2_mac_new(impl, None) is not None) + def list_hash_implementations(alg): list_implementations(alg, lambda impl: ssh_hash_new(impl) is not None) list_cipher_implementations("aes256_cbc") +list_mac_implementations("aesgcm") list_hash_implementations("sha1") list_hash_implementations("sha256") list_hash_implementations("sha512") diff --git a/code/test/sclog/sclog.c b/code/test/sclog/sclog.c index f12a0280..d5304a01 100644 --- a/code/test/sclog/sclog.c +++ b/code/test/sclog/sclog.c @@ -479,20 +479,20 @@ static dr_emit_flags_t instrument_instr( */ opnd_t shiftcount = instr_get_src(instr, 0); if (!opnd_is_immed(shiftcount)) { - reg_id_t r0; - drreg_status_t st; - st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); - DR_ASSERT(st == DRREG_SUCCESS); - opnd_t op_r0 = opnd_create_reg(r0); - instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount); - instr_set_translation(movzx, instr_get_app_pc(instr)); - instrlist_preinsert(bb, instr, movzx); - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_var_shift, false, - 2, op_r0, OPND_CREATE_INTPTR(loc)); - st = drreg_unreserve_register(drcontext, bb, instr, r0); - DR_ASSERT(st == DRREG_SUCCESS); + reg_id_t r0; + drreg_status_t st; + st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); + DR_ASSERT(st == DRREG_SUCCESS); + opnd_t op_r0 = opnd_create_reg(r0); + instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount); + instr_set_translation(movzx, instr_get_app_pc(instr)); + instrlist_preinsert(bb, instr, movzx); + instr_format_location(instr, &loc); + dr_insert_clean_call( + drcontext, bb, instr, (void *)log_var_shift, false, + 2, op_r0, OPND_CREATE_INTPTR(loc)); + st = drreg_unreserve_register(drcontext, bb, instr, r0); + DR_ASSERT(st == DRREG_SUCCESS); } break; } @@ -586,7 +586,7 @@ static void load_module( #define TRY_WRAP(fn, pre, post) do \ { \ static bool done_this_one = false; \ - try_wrap_fn(module, fn, pre, post, &done_this_one); \ + try_wrap_fn(module, fn, pre, post, &done_this_one); \ } while (0) if (loaded) { diff --git a/code/test/ssh.py b/code/test/ssh.py index c4f2531f..53b15183 100644 --- a/code/test/ssh.py +++ b/code/test/ssh.py @@ -24,6 +24,9 @@ def ssh_byte(n): def ssh_uint32(n): return struct.pack(">L", n) +def ssh_uint64(n): + return struct.pack(">Q", n) + def ssh_string(s): return ssh_uint32(len(s)) + s @@ -53,6 +56,10 @@ def ssh_decode_byte(s): def ssh_decode_uint32(s): return struct.unpack_from(">L", s, 0)[0], 4 +@decoder +def ssh_decode_uint64(s): + return struct.unpack_from(">Q", s, 0)[0], 8 + @decoder def ssh_decode_string(s): length = ssh_decode_uint32(s) diff --git a/code/test/testcrypt-enum.h b/code/test/testcrypt-enum.h index cf8f00c5..d81f46c7 100644 --- a/code/test/testcrypt-enum.h +++ b/code/test/testcrypt-enum.h @@ -36,6 +36,16 @@ BEGIN_ENUM_TYPE(macalg) ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) ENUM_VALUE("poly1305", &ssh2_poly1305) + ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) + ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) + ENUM_VALUE("aesgcm_sw", &ssh2_aesgcm_mac_sw) + ENUM_VALUE("aesgcm_ref_poly", &ssh2_aesgcm_mac_ref_poly) +#if HAVE_CLMUL + ENUM_VALUE("aesgcm_clmul", &ssh2_aesgcm_mac_clmul) +#endif +#if HAVE_NEON_PMULL + ENUM_VALUE("aesgcm_neon", &ssh2_aesgcm_mac_neon) +#endif END_ENUM_TYPE(macalg) BEGIN_ENUM_TYPE(keyalg) @@ -46,6 +56,12 @@ BEGIN_ENUM_TYPE(keyalg) ENUM_VALUE("p256", &ssh_ecdsa_nistp256) ENUM_VALUE("p384", &ssh_ecdsa_nistp384) ENUM_VALUE("p521", &ssh_ecdsa_nistp521) + ENUM_VALUE("dsa-cert", &opensshcert_ssh_dsa) + ENUM_VALUE("rsa-cert", &opensshcert_ssh_rsa) + ENUM_VALUE("ed25519-cert", &opensshcert_ssh_ecdsa_ed25519) + ENUM_VALUE("p256-cert", &opensshcert_ssh_ecdsa_nistp256) + ENUM_VALUE("p384-cert", &opensshcert_ssh_ecdsa_nistp384) + ENUM_VALUE("p521-cert", &opensshcert_ssh_ecdsa_nistp521) END_ENUM_TYPE(keyalg) BEGIN_ENUM_TYPE(cipheralg) @@ -54,31 +70,43 @@ BEGIN_ENUM_TYPE(cipheralg) ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) ENUM_VALUE("des_cbc", &ssh_des) ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) + ENUM_VALUE("aes256_gcm", &ssh_aes256_gcm) ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) + ENUM_VALUE("aes192_gcm", &ssh_aes192_gcm) ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) + ENUM_VALUE("aes128_gcm", &ssh_aes128_gcm) ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) + ENUM_VALUE("aes256_gcm_sw", &ssh_aes256_gcm_sw) ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) + ENUM_VALUE("aes192_gcm_sw", &ssh_aes192_gcm_sw) ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) + ENUM_VALUE("aes128_gcm_sw", &ssh_aes128_gcm_sw) ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) #if HAVE_AES_NI ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) + ENUM_VALUE("aes256_gcm_ni", &ssh_aes256_gcm_ni) ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) + ENUM_VALUE("aes192_gcm_ni", &ssh_aes192_gcm_ni) ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) + ENUM_VALUE("aes128_gcm_ni", &ssh_aes128_gcm_ni) ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) #endif #if HAVE_NEON_CRYPTO ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) + ENUM_VALUE("aes256_gcm_neon", &ssh_aes256_gcm_neon) ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) + ENUM_VALUE("aes192_gcm_neon", &ssh_aes192_gcm_neon) ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) + ENUM_VALUE("aes128_gcm_neon", &ssh_aes128_gcm_neon) ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) #endif ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) @@ -92,6 +120,10 @@ END_ENUM_TYPE(cipheralg) BEGIN_ENUM_TYPE(dh_group) ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) + ENUM_VALUE("group15", &ssh_diffiehellman_group15_sha512) + ENUM_VALUE("group16", &ssh_diffiehellman_group16_sha512) + ENUM_VALUE("group17", &ssh_diffiehellman_group17_sha512) + ENUM_VALUE("group18", &ssh_diffiehellman_group18_sha512) END_ENUM_TYPE(dh_group) BEGIN_ENUM_TYPE(ecdh_alg) @@ -130,6 +162,8 @@ END_ENUM_TYPE(argon2flavour) BEGIN_ENUM_TYPE(fptype) ENUM_VALUE("md5", SSH_FPTYPE_MD5) ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) + ENUM_VALUE("md5-cert", SSH_FPTYPE_MD5_CERT) + ENUM_VALUE("sha256-cert", SSH_FPTYPE_SHA256_CERT) END_ENUM_TYPE(fptype) /* diff --git a/code/test/testcrypt-func.h b/code/test/testcrypt-func.h index 2cb0b3dc..bd007293 100644 --- a/code/test/testcrypt-func.h +++ b/code/test/testcrypt-func.h @@ -273,9 +273,13 @@ FUNC(val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key)) FUNC(void, ssh2_mac_start, ARG(val_mac, m)) FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data)) +FUNC(void, ssh2_mac_next_message, ARG(val_mac, m)) FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m)) FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m)) +FUNC(void, aesgcm_set_prefix_lengths, + ARG(val_mac, m), ARG(uint, skip), ARG(uint, aad)) + /* * The ssh_key abstraction. All the uses of BinarySink and * BinarySource in parameters are replaced with ordinary strings for @@ -302,6 +306,15 @@ FUNC(void, ssh_key_openssh_blob, ARG(val_key, key), FUNC(val_string_asciz, ssh_key_cache_str, ARG(val_key, key)) FUNC(val_keycomponents, ssh_key_components, ARG(val_key, key)) FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) +FUNC_WRAPPED(val_key, ssh_key_base_key, ARG(val_key, key)) +FUNC_WRAPPED(void, ssh_key_ca_public_blob, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key), + ARG(boolean, host), ARG(val_string_ptrlen, principal), + ARG(uint, time), ARG(val_string_ptrlen, options), + ARG(out_val_string_binarysink, error)) /* * Accessors to retrieve the innards of a 'key_components'. @@ -309,7 +322,7 @@ FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) FUNC(uint, key_components_count, ARG(val_keycomponents, kc)) FUNC(opt_val_string_asciz_const, key_components_nth_name, ARG(val_keycomponents, kc), ARG(uint, n)) -FUNC(opt_val_string_asciz_const, key_components_nth_str, +FUNC(opt_val_string, key_components_nth_str, ARG(val_keycomponents, kc), ARG(uint, n)) FUNC(opt_val_mpint, key_components_nth_mp, ARG(val_keycomponents, kc), ARG(uint, n)) @@ -332,6 +345,7 @@ FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq)) FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq)) +FUNC(void, ssh_cipher_next_message, ARG(val_cipher, c)) /* * Integer Diffie-Hellman. @@ -346,11 +360,40 @@ FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f)) /* * Elliptic-curve Diffie-Hellman. */ -FUNC(val_ecdh, ssh_ecdhkex_newkey, ARG(ecdh_alg, alg)) -FUNC(void, ssh_ecdhkex_getpublic, ARG(val_ecdh, key), +FUNC(val_ecdh, ecdh_key_new, ARG(ecdh_alg, alg), ARG(boolean, is_server)) +FUNC(void, ecdh_key_getpublic, ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub)) -FUNC(opt_val_mpint, ssh_ecdhkex_getkey, ARG(val_ecdh, key), - ARG(val_string_ptrlen, pub)) +FUNC_WRAPPED(opt_val_string, ecdh_key_getkey, ARG(val_ecdh, key), + ARG(val_string_ptrlen, pub)) + +/* + * NTRU and its subroutines. + */ +FUNC_WRAPPED(int16_list, ntru_ring_multiply, ARG(int16_list, a), + ARG(int16_list, b), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(opt_int16_list, ntru_ring_invert, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_mod3, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_round3, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_bias, ARG(int16_list, r), + ARG(uint, bias), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_scale, ARG(int16_list, r), + ARG(uint, scale), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(val_ntruencodeschedule, ntru_encode_schedule, ARG(int16_list, ms)) +FUNC(uint, ntru_encode_schedule_length, ARG(val_ntruencodeschedule, sched)) +FUNC_WRAPPED(void, ntru_encode, ARG(val_ntruencodeschedule, sched), + ARG(int16_list, rs), ARG(out_val_string_binarysink, data)) +FUNC_WRAPPED(opt_int16_list, ntru_decode, ARG(val_ntruencodeschedule, sched), + ARG(val_string_ptrlen, data)) +FUNC_WRAPPED(int16_list, ntru_gen_short, ARG(uint, p), ARG(uint, w)) +FUNC(val_ntrukeypair, ntru_keygen, ARG(uint, p), ARG(uint, q), ARG(uint, w)) +FUNC_WRAPPED(int16_list, ntru_pubkey, ARG(val_ntrukeypair, keypair)) +FUNC_WRAPPED(int16_list, ntru_encrypt, ARG(int16_list, plaintext), + ARG(int16_list, pubkey), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_decrypt, ARG(int16_list, ciphertext), + ARG(val_ntrukeypair, keypair)) /* * RSA key exchange, and also the BinarySource get function diff --git a/code/test/testcrypt.c b/code/test/testcrypt.c index 5e0b73db..3755ae72 100644 --- a/code/test/testcrypt.c +++ b/code/test/testcrypt.c @@ -35,6 +35,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "crypto/ntru.h" #include "proxy/cproxy.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) @@ -87,7 +88,7 @@ uint64_t prng_reseed_time_ms(void) X(cipher, ssh_cipher *, ssh_cipher_free(v)) \ X(mac, ssh2_mac *, ssh2_mac_free(v)) \ X(dh, dh_ctx *, dh_cleanup(v)) \ - X(ecdh, ecdh_key *, ssh_ecdhkex_freekey(v)) \ + X(ecdh, ecdh_key *, ecdh_key_free(v)) \ X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ X(rsa, RSAKey *, rsa_free(v)) \ X(prng, prng *, prng_free(v)) \ @@ -96,6 +97,8 @@ uint64_t prng_reseed_time_ms(void) X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ X(pockle, Pockle *, pockle_free(v)) \ X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ + X(ntrukeypair, NTRUKeyPair *, ntru_keypair_free(v)) \ + X(ntruencodeschedule, NTRUEncodeSchedule *, ntru_encode_schedule_free(v)) \ /* end of list */ typedef struct Value Value; @@ -211,7 +214,6 @@ typedef const char *TD_opt_val_string_asciz; typedef char **TD_out_val_string_asciz; typedef char **TD_out_opt_val_string_asciz; typedef const char **TD_out_opt_val_string_asciz_const; -typedef ssh_hash *TD_consumed_val_hash; typedef const ssh_hashalg *TD_hashalg; typedef const ssh2_macalg *TD_macalg; typedef const ssh_keyalg *TD_keyalg; @@ -222,6 +224,7 @@ typedef RsaSsh1Order TD_rsaorder; typedef key_components *TD_keycomponents; typedef const PrimeGenerationPolicy *TD_primegenpolicy; typedef struct mpint_list TD_mpint_list; +typedef struct int16_list *TD_int16_list; typedef PockleStatus TD_pocklestatus; typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; @@ -386,6 +389,46 @@ static struct mpint_list get_mpint_list(BinarySource *in) return mpl; } +typedef struct int16_list { + size_t n; + uint16_t *integers; +} int16_list; + +static void finaliser_int16_list_free(strbuf *out, void *vlist) +{ + int16_list *list = (int16_list *)vlist; + sfree(list->integers); + sfree(list); +} + +static int16_list *make_int16_list(size_t n) +{ + int16_list *list = snew(int16_list); + list->n = n; + list->integers = snewn(n, uint16_t); + add_finaliser(finaliser_int16_list_free, list); + return list; +} + +static int16_list *get_int16_list(BinarySource *in) +{ + size_t n = get_uint(in); + int16_list *list = make_int16_list(n); + for (size_t i = 0; i < n; i++) + list->integers[i] = get_uint(in); + return list; +} + +static void return_int16_list(strbuf *out, int16_list *list) +{ + for (size_t i = 0; i < list->n; i++) { + if (i > 0) + put_byte(out, ','); + put_fmt(out, "%d", (int)(int16_t)list->integers[i]); + } + put_byte(out, '\n'); +} + static void finaliser_return_uint(strbuf *out, void *ctx) { unsigned *uval = (unsigned *)ctx; @@ -544,6 +587,7 @@ NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) +NULLABLE_RETURN_WRAPPER(int16_list, int16_list *) static void handle_hello(BinarySource *in, strbuf *out) { @@ -719,8 +763,7 @@ strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes", ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_encrypt(c, sb->u, sb->len); return sb; } @@ -730,30 +773,27 @@ strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes", ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_decrypt(c, sb->u, sb->len); return sb; } strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) + unsigned long seq) { if (input.len != 4) fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); return sb; } strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) + unsigned long seq) { - if (input.len % ssh_cipher_alg(c)->blksize) + if (input.len != 4) fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); return sb; } @@ -766,6 +806,63 @@ strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) return sb; } +ssh_key *ssh_key_base_key_wrapper(ssh_key *key) +{ + /* To avoid having to explain the borrowed reference to Python, + * just clone the key unconditionally */ + return ssh_key_clone(ssh_key_base_key(key)); +} + +void ssh_key_ca_public_blob_wrapper(ssh_key *key, BinarySink *out) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_ca_public_blob: needs a certificate"); + ssh_key_ca_public_blob(key, out); +} + +void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_cert_id_string: needs a certificate"); + ssh_key_cert_id_string(key, out); +} + +static bool ssh_key_check_cert_wrapper( + ssh_key *key, bool host, ptrlen principal, uint64_t time, ptrlen optstr, + BinarySink *error) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_cert_id_string: needs a certificate"); + + ca_options opts; + opts.permit_rsa_sha1 = true; + opts.permit_rsa_sha256 = true; + opts.permit_rsa_sha512 = true; + + while (optstr.len) { + ptrlen word = ptrlen_get_word(&optstr, ","); + ptrlen key = word, value = PTRLEN_LITERAL(""); + const char *comma = memchr(word.ptr, '=', word.len); + if (comma) { + key.len = comma - (const char *)word.ptr; + value.ptr = comma + 1; + value.len = word.len - key.len - 1; + } + + if (ptrlen_eq_string(key, "permit_rsa_sha1")) + opts.permit_rsa_sha1 = ptrlen_eq_string(value, "true"); + if (ptrlen_eq_string(key, "permit_rsa_sha256")) + opts.permit_rsa_sha256 = ptrlen_eq_string(value, "true"); + if (ptrlen_eq_string(key, "permit_rsa_sha512")) + opts.permit_rsa_sha512 = ptrlen_eq_string(value, "true"); + } + + return ssh_key_check_cert(key, host, principal, time, &opts, error); +} + bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) { return dh_validate_f(dh, f) == NULL; @@ -788,6 +885,142 @@ static RSAKey *rsa_new(void) return rsa; } +strbuf *ecdh_key_getkey_wrapper(ecdh_key *ek, ptrlen remoteKey) +{ + /* Fold the boolean return value in C into the string return value + * for this purpose, by returning NULL on failure */ + strbuf *sb = strbuf_new(); + if (!ecdh_key_getkey(ek, remoteKey, BinarySink_UPCAST(sb))) { + strbuf_free(sb); + return NULL; + } + return sb; +} + +static void int16_list_resize(int16_list *list, unsigned p) +{ + list->integers = sresize(list->integers, p, uint16_t); + for (size_t i = list->n; i < p; i++) + list->integers[i] = 0; +} + +#if 0 +static int16_list ntru_ring_to_list_and_free(uint16_t *out, unsigned p) +{ + struct mpint_list mpl; + mpl.n = p; + mpl->integers = snewn(p, mp_int *); + for (unsigned i = 0; i < p; i++) + mpl->integers[i] = mp_from_integer((int16_t)out[i]); + sfree(out); + add_finaliser(finaliser_sfree, mpl->integers); + return mpl; +} +#endif + +int16_list *ntru_ring_multiply_wrapper( + int16_list *a, int16_list *b, unsigned p, unsigned q) +{ + int16_list_resize(a, p); + int16_list_resize(b, p); + int16_list *out = make_int16_list(p); + ntru_ring_multiply(out->integers, a->integers, b->integers, p, q); + return out; +} + +int16_list *ntru_ring_invert_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + unsigned success = ntru_ring_invert(out->integers, in->integers, p, q); + if (!success) + return NULL; + return out; +} + +int16_list *ntru_mod3_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_mod3(out->integers, in->integers, p, q); + return out; +} + +int16_list *ntru_round3_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_round3(out->integers, in->integers, p, q); + return out; +} + +int16_list *ntru_bias_wrapper(int16_list *in, unsigned bias, + unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_bias(out->integers, in->integers, bias, p, q); + return out; +} + +int16_list *ntru_scale_wrapper(int16_list *in, unsigned scale, + unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_scale(out->integers, in->integers, scale, p, q); + return out; +} + +NTRUEncodeSchedule *ntru_encode_schedule_wrapper(int16_list *in) +{ + return ntru_encode_schedule(in->integers, in->n); +} + +void ntru_encode_wrapper(NTRUEncodeSchedule *sched, int16_list *rs, + BinarySink *bs) +{ + ntru_encode(sched, rs->integers, bs); +} + +int16_list *ntru_decode_wrapper(NTRUEncodeSchedule *sched, ptrlen data) +{ + int16_list *out = make_int16_list(ntru_encode_schedule_nvals(sched)); + ntru_decode(sched, out->integers, data); + return out; +} + +int16_list *ntru_gen_short_wrapper(unsigned p, unsigned w) +{ + int16_list *out = make_int16_list(p); + ntru_gen_short(out->integers, p, w); + return out; +} + +int16_list *ntru_pubkey_wrapper(NTRUKeyPair *keypair) +{ + unsigned p = ntru_keypair_p(keypair); + int16_list *out = make_int16_list(p); + memcpy(out->integers, ntru_pubkey(keypair), p*sizeof(uint16_t)); + return out; +} + +int16_list *ntru_encrypt_wrapper(int16_list *plaintext, int16_list *pubkey, + unsigned p, unsigned q) +{ + int16_list *out = make_int16_list(p); + ntru_encrypt(out->integers, plaintext->integers, pubkey->integers, p, q); + return out; +} + +int16_list *ntru_decrypt_wrapper(int16_list *ciphertext, NTRUKeyPair *keypair) +{ + unsigned p = ntru_keypair_p(keypair); + int16_list *out = make_int16_list(p); + ntru_decrypt(out->integers, ciphertext->integers, keypair); + return out; +} + strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) { /* Fold the boolean return value in C into the string return value @@ -817,8 +1050,7 @@ strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) fatal_error("des_encrypt_xdmauth: key must be 7 bytes long"); if (data.len % 8 != 0) fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des_encrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } @@ -829,8 +1061,7 @@ strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) fatal_error("des_decrypt_xdmauth: key must be 7 bytes long"); if (data.len % 8 != 0) fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des_decrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } @@ -841,8 +1072,7 @@ strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) fatal_error("des3_encrypt_pubkey: key must be 16 bytes long"); if (data.len % 8 != 0) fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_encrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } @@ -853,8 +1083,7 @@ strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) fatal_error("des3_decrypt_pubkey: key must be 16 bytes long"); if (data.len % 8 != 0) fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_decrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } @@ -867,8 +1096,7 @@ strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); if (data.len % 8 != 0) fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -881,8 +1109,7 @@ strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); if (data.len % 8 != 0) fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -895,8 +1122,7 @@ strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); if (data.len % 16 != 0) fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -909,8 +1135,7 @@ strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); if (data.len % 16 != 0) fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -1059,16 +1284,19 @@ const char *key_components_nth_name(key_components *kc, size_t n) return (n >= kc->ncomponents ? NULL : kc->components[n].name); } -const char *key_components_nth_str(key_components *kc, size_t n) +strbuf *key_components_nth_str(key_components *kc, size_t n) { - return (n >= kc->ncomponents ? NULL : - kc->components[n].is_mp_int ? NULL : - kc->components[n].text); + if (n >= kc->ncomponents) + return NULL; + if (kc->components[n].type != KCT_TEXT && + kc->components[n].type != KCT_BINARY) + return NULL; + return strbuf_dup(ptrlen_from_strbuf(kc->components[n].str)); } mp_int *key_components_nth_mp(key_components *kc, size_t n) { return (n >= kc->ncomponents ? NULL : - !kc->components[n].is_mp_int ? NULL : + kc->components[n].type != KCT_MPINT ? NULL : mp_copy(kc->components[n].mp)); } @@ -1101,7 +1329,16 @@ strbuf *get_implementations_commasep(ptrlen alg) strbuf *out = strbuf_new(); put_datapl(out, alg); - if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { + if (ptrlen_startswith(alg, PTRLEN_LITERAL("aesgcm"), NULL)) { + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_ref_poly", PTRLEN_PRINTF(alg)); +#if HAVE_CLMUL + put_fmt(out, ",%.*s_clmul", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_PMULL + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); #if HAVE_AES_NI put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); @@ -1347,7 +1584,7 @@ OPTIONAL_PTR_FUNC(string) static void handle_##fname(BinarySource *_in, strbuf *_out) { \ ARGS_##fname _args = get_args_##fname(_in); \ (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ + return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ } #include "testcrypt-func.h" #undef FUNC_INNER diff --git a/code/test/testcrypt.py b/code/test/testcrypt.py index 61196446..66f63d5c 100644 --- a/code/test/testcrypt.py +++ b/code/test/testcrypt.py @@ -87,19 +87,20 @@ def check_return_status(self): childprocess = ChildProcess() method_prefixes = { - 'val_wpoint': 'ecc_weierstrass_', - 'val_mpoint': 'ecc_montgomery_', - 'val_epoint': 'ecc_edwards_', - 'val_hash': 'ssh_hash_', - 'val_mac': 'ssh2_mac_', - 'val_key': 'ssh_key_', - 'val_cipher': 'ssh_cipher_', - 'val_dh': 'dh_', - 'val_ecdh': 'ssh_ecdhkex_', - 'val_rsakex': 'ssh_rsakex_', - 'val_prng': 'prng_', - 'val_pcs': 'pcs_', - 'val_pockle': 'pockle_', + 'val_wpoint': ['ecc_weierstrass_'], + 'val_mpoint': ['ecc_montgomery_'], + 'val_epoint': ['ecc_edwards_'], + 'val_hash': ['ssh_hash_'], + 'val_mac': ['ssh2_mac_'], + 'val_key': ['ssh_key_'], + 'val_cipher': ['ssh_cipher_'], + 'val_dh': ['dh_'], + 'val_ecdh': ['ssh_ecdhkex_'], + 'val_rsakex': ['ssh_rsakex_'], + 'val_prng': ['prng_'], + 'val_pcs': ['pcs_'], + 'val_pockle': ['pockle_'], + 'val_ntruencodeschedule': ['ntru_encode_schedule_', 'ntru_'], } method_lists = {t: [] for t in method_prefixes} @@ -198,6 +199,13 @@ def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): sublist.append(make_argword(val, ("val_mpint", False), fnname, argindex, argname, to_preserve)) return b" ".join(coerce_to_bytes(sub) for sub in sublist) + if typename == "int16_list": + sublist = [make_argword(len(arg), ("uint", False), + fnname, argindex, argname, to_preserve)] + for val in arg: + sublist.append(make_argword(val & 0xFFFF, ("uint", False), + fnname, argindex, argname, to_preserve)) + return b" ".join(coerce_to_bytes(sub) for sub in sublist) raise TypeError( "Can't convert {}() argument #{:d} ({}) to {} (value was {!r})".format( fnname, argindex, argname, typename, arg)) @@ -246,6 +254,8 @@ def make_retval(rettype, word, unpack_strings): return word == b"true" elif rettype in {"pocklestatus", "mr_result"}: return word.decode("ASCII") + elif rettype == "int16_list": + return list(map(int, word.split(b','))) raise TypeError("Can't deal with return value {!r} of type {!r}" .format(word, rettype)) @@ -420,10 +430,12 @@ def trim_argtype(arg): scope[function] = func if len(argtypes) > 0: t = argtypes[0][0] - if (t in method_prefixes and - function.startswith(method_prefixes[t])): - methodname = function[len(method_prefixes[t]):] - method_lists[t].append((methodname, func)) + if t in method_prefixes: + for prefix in method_prefixes[t]: + if function.startswith(prefix): + methodname = function[len(prefix):] + method_lists[t].append((methodname, func)) + break _setup(globals()) del _setup diff --git a/code/test/testsc.c b/code/test/testsc.c index c9029662..0a643e97 100644 --- a/code/test/testsc.c +++ b/code/test/testsc.c @@ -81,6 +81,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "crypto/ntru.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { @@ -114,19 +115,23 @@ static void random_seed(const char *seedstr) random_buf_limit = 0; } +static void random_advance_counter(void) +{ + ssh_hash_reset(random_hash); + put_asciz(random_hash, random_seedstr); + put_uint64(random_hash, random_counter); + random_counter++; + random_buf_limit = ssh_hash_alg(random_hash)->hlen; + ssh_hash_digest(random_hash, random_buf); +} + void random_read(void *vbuf, size_t size) { assert(random_seedstr); uint8_t *buf = (uint8_t *)vbuf; while (size-- > 0) { - if (random_buf_limit == 0) { - ssh_hash_reset(random_hash); - put_asciz(random_hash, random_seedstr); - put_uint64(random_hash, random_counter); - random_counter++; - random_buf_limit = ssh_hash_alg(random_hash)->hlen; - ssh_hash_digest(random_hash, random_buf); - } + if (random_buf_limit == 0) + random_advance_counter(); *buf++ = random_buf[random_buf_limit--]; } } @@ -243,28 +248,39 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) } #if HAVE_AES_NI -#define CIPHERS_AES_NI(X, Y) \ - X(Y, ssh_aes256_sdctr_ni) \ - X(Y, ssh_aes256_cbc_ni) \ - X(Y, ssh_aes192_sdctr_ni) \ - X(Y, ssh_aes192_cbc_ni) \ - X(Y, ssh_aes128_sdctr_ni) \ - X(Y, ssh_aes128_cbc_ni) \ - /* end of list */ +#define IF_AES_NI(x) x #else -#define CIPHERS_AES_NI(X, Y) +#define IF_AES_NI(x) #endif + +#if HAVE_SHA_NI +#define IF_SHA_NI(x) x +#else +#define IF_SHA_NI(x) +#endif + +#if HAVE_CLMUL +#define IF_CLMUL(x) x +#else +#define IF_CLMUL(x) +#endif + #if HAVE_NEON_CRYPTO -#define CIPHERS_AES_NEON(X, Y) \ - X(Y, ssh_aes256_sdctr_neon) \ - X(Y, ssh_aes256_cbc_neon) \ - X(Y, ssh_aes192_sdctr_neon) \ - X(Y, ssh_aes192_cbc_neon) \ - X(Y, ssh_aes128_sdctr_neon) \ - X(Y, ssh_aes128_cbc_neon) \ - /* end of list */ +#define IF_NEON_CRYPTO(x) x +#else +#define IF_NEON_CRYPTO(x) +#endif + +#if HAVE_NEON_SHA512 +#define IF_NEON_SHA512(x) x +#else +#define IF_NEON_SHA512(x) +#endif + +#if HAVE_NEON_PMULL +#define IF_NEON_PMULL(x) x #else -#define CIPHERS_AES_NEON(X, Y) +#define IF_NEON_PMULL(x) #endif /* Ciphers that we expect to pass this test. Blowfish and Arcfour are @@ -276,25 +292,47 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_des) \ X(Y, ssh_des_sshcom_ssh2) \ X(Y, ssh_aes256_sdctr) \ + X(Y, ssh_aes256_gcm) \ X(Y, ssh_aes256_cbc) \ X(Y, ssh_aes192_sdctr) \ + X(Y, ssh_aes192_gcm) \ X(Y, ssh_aes192_cbc) \ X(Y, ssh_aes128_sdctr) \ + X(Y, ssh_aes128_gcm) \ X(Y, ssh_aes128_cbc) \ X(Y, ssh_aes256_sdctr_sw) \ + X(Y, ssh_aes256_gcm_sw) \ X(Y, ssh_aes256_cbc_sw) \ X(Y, ssh_aes192_sdctr_sw) \ + X(Y, ssh_aes192_gcm_sw) \ X(Y, ssh_aes192_cbc_sw) \ X(Y, ssh_aes128_sdctr_sw) \ + X(Y, ssh_aes128_gcm_sw) \ X(Y, ssh_aes128_cbc_sw) \ - CIPHERS_AES_NI(X, Y) \ - CIPHERS_AES_NEON(X, Y) \ + IF_AES_NI(X(Y, ssh_aes256_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes256_gcm_ni)) \ + IF_AES_NI(X(Y, ssh_aes256_cbc_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_gcm_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_cbc_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_gcm_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_cbc_ni)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_gcm_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_cbc_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_gcm_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_cbc_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_gcm_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_cbc_neon)) \ X(Y, ssh2_chacha20_poly1305) \ /* end of list */ #define CIPHER_TESTLIST(X, name) X(cipher_ ## name) -#define MACS(X, Y) \ +#define SIMPLE_MACS(X, Y) \ X(Y, ssh_hmac_md5) \ X(Y, ssh_hmac_sha1) \ X(Y, ssh_hmac_sha1_buggy) \ @@ -303,23 +341,20 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_hmac_sha256) \ /* end of list */ -#define MAC_TESTLIST(X, name) X(mac_ ## name) +#define ALL_MACS(X, Y) \ + SIMPLE_MACS(X, Y) \ + X(Y, poly1305) \ + X(Y, aesgcm_sw_sw) \ + X(Y, aesgcm_sw_refpoly) \ + IF_AES_NI(X(Y, aesgcm_ni_sw)) \ + IF_NEON_CRYPTO(X(Y, aesgcm_neon_sw)) \ + IF_CLMUL(X(Y, aesgcm_sw_clmul)) \ + IF_NEON_PMULL(X(Y, aesgcm_sw_neon)) \ + IF_AES_NI(IF_CLMUL(X(Y, aesgcm_ni_clmul))) \ + IF_NEON_CRYPTO(IF_NEON_PMULL(X(Y, aesgcm_neon_neon))) \ + /* end of list */ -#if HAVE_SHA_NI -#define HASH_SHA_NI(X, Y) X(Y, ssh_sha256_ni) X(Y, ssh_sha1_ni) -#else -#define HASH_SHA_NI(X, Y) -#endif -#if HAVE_NEON_CRYPTO -#define HASH_SHA_NEON(X, Y) X(Y, ssh_sha256_neon) X(Y, ssh_sha1_neon) -#else -#define HASH_SHA_NEON(X, Y) -#endif -#if HAVE_NEON_SHA512 -#define HASH_SHA512_NEON(X, Y) X(Y, ssh_sha384_neon) X(Y, ssh_sha512_neon) -#else -#define HASH_SHA512_NEON(X, Y) -#endif +#define MAC_TESTLIST(X, name) X(mac_ ## name) #define HASHES(X, Y) \ X(Y, ssh_md5) \ @@ -331,9 +366,12 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_sha512) \ X(Y, ssh_sha384_sw) \ X(Y, ssh_sha512_sw) \ - HASH_SHA_NI(X, Y) \ - HASH_SHA_NEON(X, Y) \ - HASH_SHA512_NEON(X, Y) \ + IF_SHA_NI(X(Y, ssh_sha256_ni)) \ + IF_SHA_NI(X(Y, ssh_sha1_ni)) \ + IF_NEON_CRYPTO(X(Y, ssh_sha256_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_sha1_neon)) \ + IF_NEON_SHA512(X(Y, ssh_sha384_neon)) \ + IF_NEON_SHA512(X(Y, ssh_sha512_neon)) \ X(Y, ssh_sha3_224) \ X(Y, ssh_sha3_256) \ X(Y, ssh_sha3_384) \ @@ -387,10 +425,11 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(ecc_edwards_get_affine) \ X(ecc_edwards_decompress) \ CIPHERS(CIPHER_TESTLIST, X) \ - MACS(MAC_TESTLIST, X) \ + ALL_MACS(MAC_TESTLIST, X) \ HASHES(HASH_TESTLIST, X) \ X(argon2) \ X(primegen_probabilistic) \ + X(ntru) \ /* end of list */ static void test_mp_get_nbits(void) @@ -1400,20 +1439,37 @@ static void test_cipher(const ssh_cipheralg *calg) static void test_cipher_##cipher(void) { test_cipher(&cipher); } CIPHERS(CIPHER_TESTFN, Y_unused) -static void test_mac(const ssh2_macalg *malg) +static void test_mac(const ssh2_macalg *malg, const ssh_cipheralg *calg) { - ssh2_mac *m = ssh2_mac_new(malg, NULL); + ssh_cipher *c = NULL; + if (calg) { + c = ssh_cipher_new(calg); + if (!c) { + test_skipped = true; + return; + } + } + + ssh2_mac *m = ssh2_mac_new(malg, c); if (!m) { test_skipped = true; + if (c) + ssh_cipher_free(c); return; } + size_t ckeylen = calg ? calg->padded_keybytes : 0; + size_t civlen = calg ? calg->blksize : 0; + uint8_t *ckey = snewn(ckeylen, uint8_t); + uint8_t *civ = snewn(civlen, uint8_t); uint8_t *mkey = snewn(malg->keylen, uint8_t); size_t datalen = 256; size_t maclen = malg->len; uint8_t *data = snewn(datalen + maclen, uint8_t); for (size_t i = 0; i < looplimit(16); i++) { + random_read(ckey, ckeylen); + random_read(civ, civlen); random_read(mkey, malg->keylen); random_read(data, datalen); uint8_t seqbuf[4]; @@ -1421,20 +1477,85 @@ static void test_mac(const ssh2_macalg *malg) uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); log_start(); + if (c) { + ssh_cipher_setkey(c, ckey); + ssh_cipher_setiv(c, civ); + } ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); ssh2_mac_generate(m, data, datalen, seq); ssh2_mac_verify(m, data, datalen, seq); log_end(); } + sfree(ckey); + sfree(civ); sfree(mkey); sfree(data); ssh2_mac_free(m); + if (c) + ssh_cipher_free(c); } #define MAC_TESTFN(Y_unused, mac) \ - static void test_mac_##mac(void) { test_mac(&mac); } -MACS(MAC_TESTFN, Y_unused) + static void test_mac_##mac(void) { test_mac(&mac, NULL); } +SIMPLE_MACS(MAC_TESTFN, Y_unused) + +static void test_mac_poly1305(void) +{ + test_mac(&ssh2_poly1305, &ssh2_chacha20_poly1305); +} + +static void test_mac_aesgcm_sw_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_sw); +} + +static void test_mac_aesgcm_sw_refpoly(void) +{ + test_mac(&ssh2_aesgcm_mac_ref_poly, &ssh_aes128_gcm_sw); +} + +#if HAVE_AES_NI +static void test_mac_aesgcm_ni_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_ni); +} +#endif + +#if HAVE_NEON_CRYPTO +static void test_mac_aesgcm_neon_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_neon); +} +#endif + +#if HAVE_CLMUL +static void test_mac_aesgcm_sw_clmul(void) +{ + test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_sw); +} +#endif + +#if HAVE_NEON_PMULL +static void test_mac_aesgcm_sw_neon(void) +{ + test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_sw); +} +#endif + +#if HAVE_AES_NI && HAVE_CLMUL +static void test_mac_aesgcm_ni_clmul(void) +{ + test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_ni); +} +#endif + +#if HAVE_NEON_CRYPTO && HAVE_NEON_PMULL +static void test_mac_aesgcm_neon_neon(void) +{ + test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_neon); +} +#endif static void test_hash(const ssh_hashalg *halg) { @@ -1514,6 +1635,7 @@ static void test_primegen(const PrimeGenerationPolicy *policy) for (size_t i = 0; i < looplimit(2); i++) { while (true) { + random_advance_counter(); struct random_state st = random_get_state(); PrimeCandidateSource *pcs = pcs_new(128); @@ -1551,6 +1673,76 @@ static void test_primegen_probabilistic(void) test_primegen(&primegen_probabilistic); } +static void test_ntru(void) +{ + unsigned p = 11, q = 59, w = 3; + uint16_t *pubkey_orig = snewn(p, uint16_t); + uint16_t *pubkey_check = snewn(p, uint16_t); + uint16_t *pubkey = snewn(p, uint16_t); + uint16_t *plaintext = snewn(p, uint16_t); + uint16_t *ciphertext = snewn(p, uint16_t); + + strbuf *buffer = strbuf_new(); + strbuf_append(buffer, 16384); + BinarySource src[1]; + + for (size_t i = 0; i < looplimit(32); i++) { + while (true) { + random_advance_counter(); + struct random_state st = random_get_state(); + + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + + if (keypair) { + memcpy(pubkey_orig, ntru_pubkey(keypair), + p*sizeof(*pubkey_orig)); + ntru_keypair_free(keypair); + + random_set_state(st); + + log_start(); + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + memcpy(pubkey_check, ntru_pubkey(keypair), + p*sizeof(*pubkey_check)); + + ntru_gen_short(plaintext, p, w); + ntru_encrypt(ciphertext, plaintext, pubkey, p, w); + ntru_decrypt(plaintext, ciphertext, keypair); + + strbuf_clear(buffer); + ntru_encode_pubkey(ntru_pubkey(keypair), p, q, + BinarySink_UPCAST(buffer)); + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); + ntru_decode_pubkey(pubkey, p, q, src); + + strbuf_clear(buffer); + ntru_encode_ciphertext(ciphertext, p, q, + BinarySink_UPCAST(buffer)); + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); + ntru_decode_ciphertext(ciphertext, keypair, src); + + strbuf_clear(buffer); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(buffer)); + log_end(); + + ntru_keypair_free(keypair); + + break; + } + + assert(!memcmp(pubkey_orig, pubkey_check, + p*sizeof(*pubkey_check))); + } + } + + sfree(pubkey_orig); + sfree(pubkey_check); + sfree(pubkey); + sfree(plaintext); + sfree(ciphertext); + strbuf_free(buffer); +} + static const struct test tests[] = { #define STRUCT_TEST(X) { #X, test_##X }, TESTLIST(STRUCT_TEST) diff --git a/code/tree234.h b/code/tree234.h index 0f2012d0..c7e3f641 100644 --- a/code/tree234.h +++ b/code/tree234.h @@ -45,13 +45,13 @@ tree234 *newtree234(cmpfn234 cmp); /* * Free a 2-3-4 tree (not including freeing the elements). */ -void freetree234(tree234 * t); +void freetree234(tree234 *t); /* * Add an element e to a sorted 2-3-4 tree t. Returns e on success, * or if an existing element compares equal, returns that. */ -void *add234(tree234 * t, void *e); +void *add234(tree234 *t, void *e); /* * Add an element e to an unsorted 2-3-4 tree t. Returns e on @@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e); * Index range can be from 0 to the tree's current element count, * inclusive. */ -void *addpos234(tree234 * t, void *e, int index); +void *addpos234(tree234 *t, void *e, int index); /* * Look up the element at a given numeric index in a 2-3-4 tree. @@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index); * consume(p); * } */ -void *index234(tree234 * t, int index); +void *index234(tree234 *t, int index); /* * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not @@ -126,10 +126,10 @@ void *index234(tree234 * t, int index); enum { REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE }; -void *find234(tree234 * t, void *e, cmpfn234 cmp); -void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation); -void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index); -void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, +void *find234(tree234 *t, void *e, cmpfn234 cmp); +void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation); +void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index); +void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation, int *index); /* @@ -184,12 +184,12 @@ void search234_step(search234_state *state, int direction); * is out of range (delpos234) or the element is already not in the * tree (del234) then they return NULL. */ -void *del234(tree234 * t, void *e); -void *delpos234(tree234 * t, int index); +void *del234(tree234 *t, void *e); +void *delpos234(tree234 *t, int index); /* * Return the total element count of a tree234. */ -int count234(tree234 * t); +int count234(tree234 *t); #endif /* TREE234_H */ diff --git a/code/unix/CMakeLists.txt b/code/unix/CMakeLists.txt index c2d5e2eb..d4de28df 100644 --- a/code/unix/CMakeLists.txt +++ b/code/unix/CMakeLists.txt @@ -53,7 +53,7 @@ add_sources_from_current_dir(agent add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/test/fuzzterm.c ${CMAKE_SOURCE_DIR}/logging.c - ${CMAKE_SOURCE_DIR}/stubs/noprint.c + ${CMAKE_SOURCE_DIR}/stubs/no-print.c unicode.c no-gtk.c) be_list(fuzzterm FuZZterm) @@ -71,7 +71,7 @@ add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c) @@ -81,7 +81,7 @@ target_link_libraries(psusan installed_program(psusan) add_library(puttygen-common OBJECT - ${CMAKE_SOURCE_DIR}/stubs/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c keygen-noise.c no-gtk.c noise.c @@ -114,7 +114,7 @@ add_executable(uppity ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c) + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c) be_list(uppity Uppity) target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) @@ -132,24 +132,11 @@ if(GTK_FOUND) window.c unifont.c dialog.c config-gtk.c gtk-common.c config-unix.c unicode.c printing.c) add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h - add_executable(pageant - pageant.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - askpass.c - x11.c - noise.c - ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) - be_list(pageant Pageant) - target_link_libraries(pageant - eventloop console agent settings network crypto utils - ${GTK_LIBRARIES}) - installed_program(pageant) - add_executable(pterm pterm.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(pterm pterm) @@ -162,8 +149,9 @@ if(GTK_FOUND) add_executable(ptermapp pterm.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(ptermapp pterm) @@ -188,7 +176,7 @@ if(GTK_FOUND) add_executable(puttyapp putty.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c) + ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c) be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings @@ -199,8 +187,9 @@ if(GTK_FOUND) add_executable(puttytel putty.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) @@ -209,3 +198,26 @@ if(GTK_FOUND) puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() + +# Pageant is built whether we have GTK or not; in its absence we +# degrade to a version that doesn't provide the GTK askpass. +if(GTK_FOUND) + set(pageant_conditional_sources askpass.c) + set(pageant_libs ${GTK_LIBRARIES}) +else() + set(pageant_conditional_sources noaskpass.c no-gtk.c) + set(pageant_libs) +endif() +add_executable(pageant + pageant.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + x11.c + noise.c + ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + ${pageant_conditional_sources}) +be_list(pageant Pageant) +target_link_libraries(pageant + eventloop console agent settings network crypto utils + ${pageant_libs}) +installed_program(pageant) diff --git a/code/unix/agent-client.c b/code/unix/agent-client.c index e4d671d2..6d7a3662 100644 --- a/code/unix/agent-client.c +++ b/code/unix/agent-client.c @@ -219,7 +219,7 @@ agent_pending_query *agent_query( uxsel_set(sock, SELECT_R, agent_select_result); return conn; - failure: + failure: *out = NULL; *outlen = 0; return NULL; diff --git a/code/unix/askpass.c b/code/unix/askpass.c index 841912d9..b45a1c23 100644 --- a/code/unix/askpass.c +++ b/code/unix/askpass.c @@ -292,8 +292,8 @@ static gboolean try_grab_keyboard(gpointer vctx) if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw)) goto fail; - seat = gdk_display_get_default_seat - (gtk_widget_get_display(ctx->dialog)); + seat = gdk_display_get_default_seat( + gtk_widget_get_display(ctx->dialog)); if (!seat) goto fail; @@ -317,8 +317,8 @@ static gboolean try_grab_keyboard(gpointer vctx) GdkDeviceManager *dm; GdkDevice *pointer, *keyboard; - dm = gdk_display_get_device_manager - (gtk_widget_get_display(ctx->dialog)); + dm = gdk_display_get_device_manager( + gtk_widget_get_display(ctx->dialog)); if (!dm) goto fail; @@ -438,7 +438,7 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); g_signal_connect(G_OBJECT(ctx->dialog), "delete-event", - G_CALLBACK(askpass_dialog_closed), ctx); + G_CALLBACK(askpass_dialog_closed), ctx); ctx->promptlabel = gtk_label_new(prompt_text); align_label_left(GTK_LABEL(ctx->promptlabel)); gtk_widget_show(ctx->promptlabel); diff --git a/code/unix/columns.c b/code/unix/columns.c index a1049b47..3dbed9c3 100644 --- a/code/unix/columns.c +++ b/code/unix/columns.c @@ -332,7 +332,6 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) ColumnsChild *child; GtkWidget *childw; GList *children; - bool was_visible; g_return_if_fail(container != NULL); g_return_if_fail(IS_COLUMNS(container)); @@ -346,23 +345,28 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) if (child->widget != widget) continue; - was_visible = gtk_widget_get_visible(widget); + bool need_layout = false; + if (gtk_widget_get_visible(widget)) + need_layout = true; gtk_widget_unparent(widget); cols->children = g_list_remove_link(cols->children, children); g_list_free(children); - if (child->same_height_as) { - g_return_if_fail(child->same_height_as->same_height_as == child); - child->same_height_as->same_height_as = NULL; - if (gtk_widget_get_visible(child->same_height_as->widget)) - gtk_widget_queue_resize(GTK_WIDGET(container)); - } + /* Unlink this widget from its valign list, and if anything + * else on the list is still visible, ensure we recompute our + * layout */ + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) + if (gtk_widget_get_visible(ch->widget)) + need_layout = true; + child->valign_next->valign_prev = child->valign_prev; + child->valign_prev->valign_next = child->valign_next; if (cols->vexpand == child) cols->vexpand = NULL; g_free(child); - if (was_visible) + if (need_layout) gtk_widget_queue_resize(GTK_WIDGET(container)); break; } @@ -465,7 +469,8 @@ void columns_add(Columns *cols, GtkWidget *child, childdata->colstart = colstart; childdata->colspan = colspan; childdata->force_left = false; - childdata->same_height_as = NULL; + childdata->valign_next = childdata; + childdata->valign_prev = childdata; childdata->percentages = NULL; cols->children = g_list_append(cols->children, childdata); @@ -516,7 +521,7 @@ void columns_force_left_align(Columns *cols, GtkWidget *widget) gtk_widget_queue_resize(GTK_WIDGET(cols)); } -void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) +void columns_align_next_to(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) { ColumnsChild *child1, *child2; @@ -530,8 +535,13 @@ void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) child2 = columns_find_child(cols, cw2); g_return_if_fail(child2 != NULL); - child1->same_height_as = child2; - child2->same_height_as = child1; + ColumnsChild *child1prev = child1->valign_prev; + ColumnsChild *child2prev = child2->valign_prev; + child1prev->valign_next = child2; + child2->valign_prev = child1prev; + child2prev->valign_next = child1; + child1->valign_prev = child2prev; + if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2)) gtk_widget_queue_resize(GTK_WIDGET(cols)); } @@ -843,8 +853,9 @@ static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height) continue; childheight = get_height(child); - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + gint childheight2 = get_height(ch); if (childheight < childheight2) childheight = childheight2; } @@ -902,6 +913,17 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, colypos = g_new(gint, 1); colypos[0] = 0; + for (children = cols->children; + children && (child = children->data); + children = children->next) + child->visited = false; + + /* + * Main layout loop. In this loop, vertically aligned controls are + * only half dealt with: we assign each one enough _height_ to + * match the others in its group, but we don't adjust its y + * coordinates yet. + */ for (children = cols->children; children && (child = children->data); children = children->next) { @@ -922,14 +944,19 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, if (!gtk_widget_get_visible(child->widget)) continue; + int ymin = 0; + realheight = get_height(child); if (child == cols->vexpand) realheight += vexpand_extra; fakeheight = realheight; - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + gint childheight2 = get_height(ch); if (fakeheight < childheight2) fakeheight = childheight2; + if (ch->visited && ymin < ch->y) + ymin = ch->y; } colspan = child->colspan ? child->colspan : ncols-child->colstart; @@ -943,13 +970,14 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, { int topy, boty; - topy = 0; + topy = ymin; for (i = 0; i < colspan; i++) { if (topy < colypos[child->colstart+i]) topy = colypos[child->colstart+i]; } - child->y = topy + fakeheight/2 - realheight/2; + child->y = topy; child->h = realheight; + child->visited = true; boty = topy + fakeheight + cols->spacing; for (i = 0; i < colspan; i++) { colypos[child->colstart+i] = boty; @@ -957,6 +985,28 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, } } + /* + * Now make a separate pass that deals with vertical alignment by + * moving controls downwards based on the difference between their + * own height and the largest height of anything in their group. + */ + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (!child->widget) + continue; + if (!gtk_widget_get_visible(child->widget)) + continue; + + fakeheight = realheight = child->h; + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + if (fakeheight < ch->h) + fakeheight = ch->h; + } + child->y += fakeheight/2 - realheight/2; + } + g_free(colypos); } diff --git a/code/unix/columns.h b/code/unix/columns.h index d2d58c4a..bca306b4 100644 --- a/code/unix/columns.h +++ b/code/unix/columns.h @@ -43,11 +43,17 @@ struct ColumnsChild_tag { GtkWidget *widget; gint colstart, colspan; bool force_left; /* for recalcitrant GtkLabels */ - ColumnsChild *same_height_as; /* Otherwise, this entry represents a change in the column setup. */ gint ncols; gint *percentages; gint x, y, w, h; /* used during an individual size computation */ + + /* Circularly linked list of children that are vertically aligned + * with each other. */ + ColumnsChild *valign_next, *valign_prev; + + /* Temporary space used within some methods */ + bool visited; }; GType columns_get_type(void); @@ -57,7 +63,7 @@ void columns_add(Columns *cols, GtkWidget *child, gint colstart, gint colspan); void columns_taborder_last(Columns *cols, GtkWidget *child); void columns_force_left_align(Columns *cols, GtkWidget *child); -void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); +void columns_align_next_to(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); void columns_vexpand(Columns *cols, GtkWidget *child); #ifdef __cplusplus diff --git a/code/unix/config-gtk.c b/code/unix/config-gtk.c index 52e6e181..be70f5cb 100644 --- a/code/unix/config-gtk.c +++ b/code/unix/config-gtk.c @@ -10,18 +10,18 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, dlgparam *dlg, +static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_ACTION) { - about_box(ctrl->generic.context.p); + about_box(ctrl->context.p); } } void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) { struct controlset *s, *s2; - union control *c; + dlgcontrol *c; int i; if (!midsession) { @@ -31,7 +31,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) s = ctrl_getset(b, "", "", ""); c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), about_handler, P(win)); - c->generic.column = 0; + c->column = 0; } /* @@ -50,8 +50,8 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) */ for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_scrollbar) { + if (c->type == CTRL_CHECKBOX && + c->context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-on-left one. @@ -59,7 +59,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) if (i < s->ncontrols-2) { c = s->ctrls[s->ncontrols-1]; memmove(s->ctrls+i+2, s->ctrls+i+1, - (s->ncontrols-i-2)*sizeof(union control *)); + (s->ncontrols-i-2)*sizeof(dlgcontrol *)); s->ctrls[i+1] = c; } break; @@ -112,7 +112,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) HELPCTX(no_help)); ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, HELPCTX(no_help), conf_editbox_handler, - I(CONF_shadowboldoffset), I(-1)); + I(CONF_shadowboldoffset), ED_INT); /* * Markus Kuhn feels, not totally unreasonably, that it's good @@ -155,6 +155,6 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) "X Window System settings"); ctrl_editbox(s, "Window class name:", 'z', 50, HELPCTX(no_help), conf_editbox_handler, - I(CONF_winclass), I(1)); + I(CONF_winclass), ED_STR); } } diff --git a/code/unix/config-unix.c b/code/unix/config-unix.c index ee1b953d..179d8a9c 100644 --- a/code/unix/config-unix.c +++ b/code/unix/config-unix.c @@ -13,7 +13,7 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) { struct controlset *s; - union control *c; + dlgcontrol *c; /* * The Conf structure contains two Unix-specific elements which @@ -28,43 +28,20 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) * control. */ s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); - assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX); + assert(s->ncontrols == 1 && s->ctrls[0]->type == CTRL_EDITBOX); s->ctrls[0]->editbox.has_list = false; /* - * Unix supports a local-command proxy. This also means we must - * adjust the text on the `Telnet command' control. + * Unix supports a local-command proxy. */ if (!midsession) { int i; s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); - if (c->radio.ncolumns < 4) - c->radio.ncolumns = 4; - break; - } - } - - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); + if (c->type == CTRL_LISTBOX && + c->handler == proxy_type_handler) { + c->context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/code/unix/console.c b/code/unix/console.c index cc6c9853..286ecf29 100644 --- a/code/unix/console.c +++ b/code/unix/console.c @@ -104,43 +104,54 @@ static int block_and_read(int fd, void *buf, size_t len) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; - char *common; - const char *intro, *prompt; - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } + const char *prompt = NULL; + + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); premsg(&cf); - fputs(common, stderr); - sfree(common); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + postmsg(&cf); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } + assert(prompt); /* something in the SeatDialogText should have set this */ - fputs(intro, stderr); - fflush(stderr); while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); struct termios oldmode, newmode; @@ -154,13 +165,22 @@ SeatPromptResult console_confirm_ssh_host_key( tcsetattr(0, TCSANOW, &oldmode); if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } } else { break; } diff --git a/code/unix/dialog.c b/code/unix/dialog.c index 7d1514fd..5846466a 100644 --- a/code/unix/dialog.c +++ b/code/unix/dialog.c @@ -51,7 +51,7 @@ struct Shortcuts { struct selparam; struct uctrl { - union control *ctrl; + dlgcontrol *ctrl; GtkWidget *toplevel; GtkWidget **buttons; int nbuttons; /* for radio buttons */ GtkWidget *entry; /* for editbox, filesel, fontsel */ @@ -71,9 +71,9 @@ struct uctrl { GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ struct selparam *sp; /* which switchable pane of the box we're in */ - guint entrysig; guint textsig; int nclicks; + const char *textvalue; /* temporary, for button-only file selectors */ }; struct dlgparam { @@ -88,7 +88,7 @@ struct dlgparam { int flags; struct Shortcuts *shortcuts; GtkWidget *window, *cancelbutton; - union control *currfocus, *lastfocus; + dlgcontrol *currfocus, *lastfocus; #if !GTK_CHECK_VERSION(2,0,0) GtkWidget *currtreeitem, **treeitems; int ntreeitems; @@ -166,7 +166,7 @@ static int uctrl_cmp_byctrl(void *av, void *bv) static int uctrl_cmp_byctrl_find(void *av, void *bv) { - union control *a = (union control *)av; + dlgcontrol *a = (dlgcontrol *)av; struct uctrl *b = (struct uctrl *)bv; if (a < b->ctrl) return -1; @@ -236,7 +236,7 @@ static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc) add234(dp->bywidget, uc); } -static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl) +static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, dlgcontrol *ctrl) { if (!dp->byctrl) return NULL; @@ -257,7 +257,7 @@ static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w) return ret; } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { if (dp->currfocus != ctrl) return dp->currfocus; @@ -265,20 +265,20 @@ union control *dlg_last_focused(union control *ctrl, dlgparam *dp) return dp->lastfocus; } -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->ctrl->type == CTRL_RADIO); assert(uc->buttons != NULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true); } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); int i; - assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->ctrl->type == CTRL_RADIO); assert(uc->buttons != NULL); for (i = 0; i < uc->nbuttons; i++) if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i]))) @@ -286,33 +286,33 @@ int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) return 0; /* got to return something */ } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + assert(uc->ctrl->type == CTRL_CHECKBOX); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + assert(uc->ctrl->type == CTRL_CHECKBOX); return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); GtkWidget *entry; char *tmpstring; - assert(uc->ctrl->generic.type == CTRL_EDITBOX); + assert(uc->ctrl->type == CTRL_EDITBOX); #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) entry = gtk_bin_get_child(GTK_BIN(uc->combo)); else #endif - entry = uc->entry; + entry = uc->entry; assert(entry != NULL); @@ -338,15 +338,15 @@ void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) sfree(tmpstring); } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX); + assert(uc->ctrl->type == CTRL_EDITBOX); #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) { - return dupstr(gtk_entry_get_text - (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); + return dupstr(gtk_entry_get_text( + GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); } #endif @@ -357,6 +357,27 @@ char *dlg_editbox_get(union control *ctrl, dlgparam *dp) unreachable("bad control type in editbox_get"); } +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->type == CTRL_EDITBOX); + + GtkWidget *entry = NULL; + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) + entry = gtk_bin_get_child(GTK_BIN(uc->combo)); +#endif + + if (uc->entry) + entry = uc->entry; + + assert(entry && "we should have a GtkEntry one way or another"); + + gtk_editable_select_region(GTK_EDITABLE(entry), start, start + len); +} + #if !GTK_CHECK_VERSION(2,4,0) static void container_remove_and_destroy(GtkWidget *w, gpointer data) { @@ -367,12 +388,12 @@ static void container_remove_and_destroy(GtkWidget *w, gpointer data) #endif /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { @@ -395,18 +416,18 @@ void dlg_listbox_clear(union control *ctrl, dlgparam *dp) unreachable("bad control type in listbox_clear"); } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { - gtk_container_remove - (GTK_CONTAINER(uc->menu), - g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); + gtk_container_remove( + GTK_CONTAINER(uc->menu), + g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); return; } if (uc->list) { @@ -429,7 +450,7 @@ void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) unreachable("bad control type in listbox_del"); } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { dlg_listbox_addwithid(ctrl, dp, text, 0); } @@ -441,13 +462,13 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) * strings in any listbox then you MUST not assign them different * IDs and expect to get meaningful results back. */ -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); /* * This routine is long and complicated in both GTK 1 and 2, @@ -569,7 +590,7 @@ void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, * Now go through text and divide it into columns at the tabs, * as necessary. */ - cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); + cols = (uc->ctrl->type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); cols = cols ? cols : 1; for (i = 0; i < cols; i++) { int collen = strcspn(text, "\t"); @@ -585,16 +606,16 @@ void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, } #endif unreachable("bad control type in listbox_addwithid"); - done: + done: dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu || uc->list) { @@ -628,12 +649,12 @@ int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu || uc->list) { @@ -712,20 +733,20 @@ int dlg_listbox_index(union control *ctrl, dlgparam *dp) return -1; /* placate dataflow analysis */ } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu || uc->list) { GList *children; GtkWidget *item, *activeitem; - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); assert(uc->menu != NULL || uc->list != NULL); children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : @@ -769,12 +790,12 @@ bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) return false; /* placate dataflow analysis */ } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->optmenu) { @@ -837,21 +858,21 @@ void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) unreachable("bad control type in listbox_select"); } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_TEXT); + assert(uc->ctrl->type == CTRL_TEXT); assert(uc->text != NULL); gtk_label_set_text(GTK_LABEL(uc->text), text); } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - switch (uc->ctrl->generic.type) { + switch (uc->ctrl->type) { case CTRL_BUTTON: gtk_label_set_text(GTK_LABEL(uc->toplevel), text); shortcut_highlight(uc->toplevel, ctrl->button.shortcut); @@ -869,8 +890,10 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) shortcut_highlight(uc->label, ctrl->editbox.shortcut); break; case CTRL_FILESELECT: - gtk_label_set_text(GTK_LABEL(uc->label), text); - shortcut_highlight(uc->label, ctrl->fileselect.shortcut); + if (uc->label) { + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fileselect.shortcut); + } break; case CTRL_FONTSELECT: gtk_label_set_text(GTK_LABEL(uc->label), text); @@ -885,42 +908,46 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) } } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fn->path before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ char *duppath = dupstr(fn->path); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->ctrl->type == CTRL_FILESELECT); assert(uc->entry != NULL); gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); sfree(duppath); } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); - assert(uc->entry != NULL); - return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + assert(uc->ctrl->type == CTRL_FILESELECT); + if (!uc->entry) { + assert(uc->textvalue); + return filename_from_str(uc->textvalue); + } else { + return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + } } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fs->name before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ char *dupname = dupstr(fs->name); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->ctrl->type == CTRL_FONTSELECT); assert(uc->entry != NULL); gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); sfree(dupname); } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->ctrl->type == CTRL_FONTSELECT); assert(uc->entry != NULL); return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } @@ -930,7 +957,7 @@ FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, dlgparam *dp) +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -938,7 +965,7 @@ void dlg_update_start(union control *ctrl, dlgparam *dp) */ } -void dlg_update_done(union control *ctrl, dlgparam *dp) +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -946,11 +973,11 @@ void dlg_update_done(union control *ctrl, dlgparam *dp) */ } -void dlg_set_focus(union control *ctrl, dlgparam *dp) +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_CHECKBOX: case CTRL_BUTTON: /* Check boxes and buttons get the focus _and_ get toggled. */ @@ -976,9 +1003,9 @@ void dlg_set_focus(union control *ctrl, dlgparam *dp) * focus it. */ for (int i = 0; i < ctrl->radio.nbuttons; i++) - if (gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(uc->buttons[i]))) { - gtk_widget_grab_focus(uc->buttons[i]); + if (gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(uc->buttons[i]))) { + gtk_widget_grab_focus(uc->buttons[i]); } break; case CTRL_LISTBOX: @@ -1077,26 +1104,26 @@ void dlg_end(dlgparam *dp, int value) gtk_widget_destroy(dp->window); } -void dlg_refresh(union control *ctrl, dlgparam *dp) +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc; if (ctrl) { - if (ctrl->generic.handler != NULL) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + if (ctrl->handler != NULL) + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } else { int i; for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) { assert(uc->ctrl != NULL); - if (uc->ctrl->generic.handler != NULL) - uc->ctrl->generic.handler(uc->ctrl, dp, - dp->data, EVENT_REFRESH); + if (uc->ctrl->handler != NULL) + uc->ctrl->handler(uc->ctrl, dp, + dp->data, EVENT_REFRESH); } } } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -1110,8 +1137,8 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) GtkWidget *coloursel = gtk_color_selection_dialog_new("Select a colour"); GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection(ccs)); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection(ccs)); gtk_color_selection_set_has_opacity_control(cs, false); #endif @@ -1164,9 +1191,9 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) cancelbutton = ccs->cancel_button; #endif g_object_set_data(G_OBJECT(okbutton), "user-data", - (gpointer)coloursel); + (gpointer)coloursel); g_object_set_data(G_OBJECT(cancelbutton), "user-data", - (gpointer)coloursel); + (gpointer)coloursel); g_signal_connect(G_OBJECT(okbutton), "clicked", G_CALLBACK(coloursel_ok), (gpointer)dp); g_signal_connect(G_OBJECT(cancelbutton), "clicked", @@ -1181,7 +1208,7 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) gtk_widget_show(coloursel); } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { if (dp->coloursel_result.ok) { @@ -1202,7 +1229,7 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, widget); - union control *focus; + dlgcontrol *focus; if (uc && uc->ctrl) focus = uc->ctrl; @@ -1221,14 +1248,14 @@ static void button_clicked(GtkButton *button, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); } static void button_toggled(GtkToggleButton *tb, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, @@ -1259,7 +1286,7 @@ static void editbox_changed(GtkEditable *ed, gpointer data) struct dlgparam *dp = (struct dlgparam *)data; if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) { struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } } @@ -1268,7 +1295,7 @@ static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); return false; } @@ -1310,7 +1337,7 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, if (!multiple && GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { - gtk_list_select_child(GTK_LIST(list), item); + gtk_list_select_child(GTK_LIST(list), item); } else { int direction = (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || @@ -1358,10 +1385,10 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, gtk_list_select_child(GTK_LIST(list), GTK_WIDGET(children->data)); gtk_widget_grab_focus(GTK_WIDGET(children->data)); - gtk_adjustment_clamp_page - (adj, - adj->lower + (adj->upper-adj->lower) * i / n, - adj->lower + (adj->upper-adj->lower) * (i+1) / n); + gtk_adjustment_clamp_page( + adj, + adj->lower + (adj->upper-adj->lower) * i / n, + adj->lower + (adj->upper-adj->lower) * (i+1) / n); } g_list_free(chead); @@ -1390,10 +1417,10 @@ static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); switch (event->type) { - default: - case GDK_BUTTON_PRESS: uc->nclicks = 1; break; - case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; - case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; + default: + case GDK_BUTTON_PRESS: uc->nclicks = 1; break; + case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; + case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; } return false; } @@ -1404,7 +1431,7 @@ static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); if (uc->nclicks>1) { - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); return true; } return false; @@ -1415,7 +1442,7 @@ static void list_selchange(GtkList *list, gpointer data) struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list)); if (!uc) return; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) @@ -1440,7 +1467,7 @@ static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) children = g_list_append(children, child); gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction); gtk_list_select_item(GTK_LIST(uc->list), index + direction); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } static void draglist_up(GtkButton *button, gpointer data) @@ -1469,7 +1496,7 @@ static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview)); if (uc) - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); } static void listbox_selchange(GtkTreeSelection *treeselection, @@ -1479,7 +1506,7 @@ static void listbox_selchange(GtkTreeSelection *treeselection, GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection); struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); if (uc) - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } struct draglist_valchange_ctx { @@ -1492,8 +1519,8 @@ static gboolean draglist_valchange(gpointer data) struct draglist_valchange_ctx *ctx = (struct draglist_valchange_ctx *)data; - ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp, - ctx->dp->data, EVENT_VALCHANGE); + ctx->uc->ctrl->handler(ctx->uc->ctrl, ctx->dp, + ctx->dp->data, EVENT_VALCHANGE); sfree(ctx); @@ -1547,7 +1574,7 @@ static void menuitem_activate(GtkMenuItem *item, gpointer data) GtkWidget *menushell = GTK_WIDGET(item)->parent; gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data"); struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } #else @@ -1557,20 +1584,32 @@ static void droplist_selchange(GtkComboBox *combo, gpointer data) struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); if (uc) - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } #endif /* !GTK_CHECK_VERSION(2,4,0) */ +static void filechoose_emit_value(struct dlgparam *dp, struct uctrl *uc, + const char *name) +{ + if (uc->entry) { + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + } else { + uc->textvalue = name; + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + uc->textvalue = NULL; + } +} + #ifdef USE_GTK_FILE_CHOOSER_DIALOG static void filechoose_response(GtkDialog *dialog, gint response, gpointer data) { - /* struct dlgparam *dp = (struct dlgparam *)data; */ + struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); if (response == GTK_RESPONSE_ACCEPT) { gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + filechoose_emit_value(dp, uc, name); g_free(name); } gtk_widget_destroy(GTK_WIDGET(dialog)); @@ -1578,12 +1617,12 @@ static void filechoose_response(GtkDialog *dialog, gint response, #else static void filesel_ok(GtkButton *button, gpointer data) { - /* struct dlgparam *dp = (struct dlgparam *)data; */ + struct dlgparam *dp = (struct dlgparam *)data; gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data"); - const char *name = gtk_file_selection_get_filename - (GTK_FILE_SELECTION(filesel)); - gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + const char *name = gtk_file_selection_get_filename( + GTK_FILE_SELECTION(filesel)); + filechoose_emit_value(dp, uc, name); } #endif @@ -1595,14 +1634,14 @@ static void fontsel_ok(GtkButton *button, gpointer data) gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data"); - const char *name = gtk_font_selection_dialog_get_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel)); + const char *name = gtk_font_selection_dialog_get_font_name( + GTK_FONT_SELECTION_DIALOG(fontsel)); gtk_entry_set_text(GTK_ENTRY(uc->entry), name); #else - unifontsel *fontsel = (unifontsel *)g_object_get_data - (G_OBJECT(button), "user-data"); + unifontsel *fontsel = (unifontsel *)g_object_get_data( + G_OBJECT(button), "user-data"); struct uctrl *uc = (struct uctrl *)fontsel->user_data; char *name = unifontsel_get_name(fontsel); assert(name); /* should always be ok after OK pressed */ @@ -1631,7 +1670,7 @@ static void colourchoose_response(GtkDialog *dialog, dp->coloursel_result.ok = false; } - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); gtk_widget_destroy(GTK_WIDGET(dialog)); } @@ -1646,9 +1685,9 @@ static void coloursel_ok(GtkButton *button, gpointer data) #if GTK_CHECK_VERSION(2,0,0) { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection( + GTK_COLOR_SELECTION_DIALOG(coloursel))); GdkColor col; gtk_color_selection_get_current_color(cs, &col); dp->coloursel_result.r = col.red / 0x0100; @@ -1657,9 +1696,9 @@ static void coloursel_ok(GtkButton *button, gpointer data) } #else { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection( + GTK_COLOR_SELECTION_DIALOG(coloursel))); gdouble cvals[4]; gtk_color_selection_get_color(cs, cvals); dp->coloursel_result.r = (int) (255 * cvals[0]); @@ -1668,7 +1707,7 @@ static void coloursel_ok(GtkButton *button, gpointer data) } #endif dp->coloursel_result.ok = true; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } static void coloursel_cancel(GtkButton *button, gpointer data) @@ -1677,7 +1716,7 @@ static void coloursel_cancel(GtkButton *button, gpointer data) gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); dp->coloursel_result.ok = false; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } #endif /* end of coloursel response handlers */ @@ -1687,16 +1726,16 @@ static void filefont_clicked(GtkButton *button, gpointer data) struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); - if (uc->ctrl->generic.type == CTRL_FILESELECT) { + if (uc->ctrl->type == CTRL_FILESELECT) { #ifdef USE_GTK_FILE_CHOOSER_DIALOG - GtkWidget *filechoose = gtk_file_chooser_dialog_new - (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), - (uc->ctrl->fileselect.for_writing ? - GTK_FILE_CHOOSER_ACTION_SAVE : - GTK_FILE_CHOOSER_ACTION_OPEN), - STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, - STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, - (const gchar *)NULL); + GtkWidget *filechoose = gtk_file_chooser_dialog_new( + uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), + (uc->ctrl->fileselect.for_writing ? + GTK_FILE_CHOOSER_ACTION_SAVE : + GTK_FILE_CHOOSER_ACTION_OPEN), + STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, + STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, + (const gchar *)NULL); gtk_window_set_modal(GTK_WINDOW(filechoose), true); g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc); g_signal_connect(G_OBJECT(filechoose), "response", @@ -1706,24 +1745,24 @@ static void filefont_clicked(GtkButton *button, gpointer data) GtkWidget *filesel = gtk_file_selection_new(uc->ctrl->fileselect.title); gtk_window_set_modal(GTK_WINDOW(filesel), true); - g_object_set_data - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", - (gpointer)filesel); + g_object_set_data( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", + (gpointer)filesel); g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(filesel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + g_signal_connect( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(filesel_ok), (gpointer)dp); + g_signal_connect_swapped( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + g_signal_connect_swapped( + G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); gtk_widget_show(filesel); #endif } - if (uc->ctrl->generic.type == CTRL_FONTSELECT) { + if (uc->ctrl->type == CTRL_FONTSELECT) { const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); #if !GTK_CHECK_VERSION(2,0,0) @@ -1736,12 +1775,12 @@ static void filefont_clicked(GtkButton *button, gpointer data) GtkWidget *fontsel = gtk_font_selection_dialog_new("Select a font"); gtk_window_set_modal(GTK_WINDOW(fontsel), true); - gtk_font_selection_dialog_set_filter - (GTK_FONT_SELECTION_DIALOG(fontsel), - GTK_FONT_FILTER_BASE, GTK_FONT_ALL, - NULL, NULL, NULL, NULL, spacings, NULL); - if (!gtk_font_selection_dialog_set_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { + gtk_font_selection_dialog_set_filter( + GTK_FONT_SELECTION_DIALOG(fontsel), + GTK_FONT_FILTER_BASE, GTK_FONT_ALL, + NULL, NULL, NULL, NULL, spacings, NULL); + if (!gtk_font_selection_dialog_set_font_name( + GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { /* * If the font name wasn't found as it was, try opening * it and extracting its FONT property. This should @@ -1761,27 +1800,27 @@ static void filefont_clicked(GtkButton *button, gpointer data) if (XGetFontProperty(xfs, fontprop, &ret)) { char *name = XGetAtomName(disp, (Atom)ret); if (name) - gtk_font_selection_dialog_set_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel), name); + gtk_font_selection_dialog_set_font_name( + GTK_FONT_SELECTION_DIALOG(fontsel), name); } gdk_font_unref(font); } } - g_object_set_data - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "user-data", (gpointer)fontsel); + g_object_set_data( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); + g_signal_connect( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); + g_signal_connect_swapped( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer)fontsel); + g_signal_connect_swapped( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer)fontsel); gtk_widget_show(fontsel); #else /* !GTK_CHECK_VERSION(2,0,0) */ @@ -1822,7 +1861,7 @@ static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc, struct uctrl *uc = dlg_find_bywidget(dp, widget); gtk_widget_set_size_request(uc->text, alloc->width, -1); - gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); + gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label); g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig); } #endif @@ -1877,12 +1916,12 @@ GtkWidget *layout_ctrls( * and add them to the Columns. */ for (i = 0; i < s->ncontrols; i++) { - union control *ctrl = s->ctrls[i]; + dlgcontrol *ctrl = s->ctrls[i]; struct uctrl *uc; bool left = false; GtkWidget *w = NULL; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_COLUMNS: { static const int simplecols[1] = { 100 }; columns_set_cols(cols, ctrl->columns.ncols, @@ -1916,9 +1955,9 @@ GtkWidget *layout_ctrls( uc->label = NULL; uc->nclicks = 0; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_BUTTON: - w = gtk_button_new_with_label(ctrl->generic.label); + w = gtk_button_new_with_label(ctrl->label); if (win) { gtk_widget_set_can_default(w, true); if (ctrl->button.isdefault) @@ -1934,7 +1973,7 @@ GtkWidget *layout_ctrls( ctrl->button.shortcut, SHORTCUT_UCTRL, uc); break; case CTRL_CHECKBOX: - w = gtk_check_button_new_with_label(ctrl->generic.label); + w = gtk_check_button_new_with_label(ctrl->label); g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(button_toggled), dp); g_signal_connect(G_OBJECT(w), "focus_in_event", @@ -1952,20 +1991,20 @@ GtkWidget *layout_ctrls( GSList *group; w = columns_new(0); - if (ctrl->generic.label) { - GtkWidget *label = gtk_label_new(ctrl->generic.label); - columns_add(COLUMNS(w), label, 0, 1); - columns_force_left_align(COLUMNS(w), label); - gtk_widget_show(label); - shortcut_add(scs, label, ctrl->radio.shortcut, - SHORTCUT_UCTRL, uc); - uc->label = label; + if (ctrl->label) { + GtkWidget *label = gtk_label_new(ctrl->label); + columns_add(COLUMNS(w), label, 0, 1); + columns_force_left_align(COLUMNS(w), label); + gtk_widget_show(label); + shortcut_add(scs, label, ctrl->radio.shortcut, + SHORTCUT_UCTRL, uc); + uc->label = label; } percentages = g_new(gint, ctrl->radio.ncolumns); for (i = 0; i < ctrl->radio.ncolumns; i++) { - percentages[i] = - ((100 * (i+1) / ctrl->radio.ncolumns) - - 100 * i / ctrl->radio.ncolumns); + percentages[i] = + ((100 * (i+1) / ctrl->radio.ncolumns) - + 100 * i / ctrl->radio.ncolumns); } columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns, percentages); @@ -1976,28 +2015,28 @@ GtkWidget *layout_ctrls( uc->buttons = snewn(uc->nbuttons, GtkWidget *); for (i = 0; i < ctrl->radio.nbuttons; i++) { - GtkWidget *b; - gint colstart; - - b = (gtk_radio_button_new_with_label - (group, ctrl->radio.buttons[i])); - uc->buttons[i] = b; - group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b)); - colstart = i % ctrl->radio.ncolumns; - columns_add(COLUMNS(w), b, colstart, - (i == ctrl->radio.nbuttons-1 ? - ctrl->radio.ncolumns - colstart : 1)); - columns_force_left_align(COLUMNS(w), b); - gtk_widget_show(b); - g_signal_connect(G_OBJECT(b), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(b), "focus_in_event", - G_CALLBACK(widget_focus), dp); - if (ctrl->radio.shortcuts) { - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), - ctrl->radio.shortcuts[i], - SHORTCUT_UCTRL, uc); - } + GtkWidget *b; + gint colstart; + + b = gtk_radio_button_new_with_label( + group, ctrl->radio.buttons[i]); + uc->buttons[i] = b; + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b)); + colstart = i % ctrl->radio.ncolumns; + columns_add(COLUMNS(w), b, colstart, + (i == ctrl->radio.nbuttons-1 ? + ctrl->radio.ncolumns - colstart : 1)); + columns_force_left_align(COLUMNS(w), b); + gtk_widget_show(b); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(button_toggled), dp); + g_signal_connect(G_OBJECT(b), "focus_in_event", + G_CALLBACK(widget_focus), dp); + if (ctrl->radio.shortcuts) { + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), + ctrl->radio.shortcuts[i], + SHORTCUT_UCTRL, uc); + } } break; } @@ -2006,39 +2045,38 @@ GtkWidget *layout_ctrls( if (ctrl->editbox.has_list) { #if !GTK_CHECK_VERSION(2,4,0) - /* - * GTK 1 combo box. - */ - w = gtk_combo_new(); - gtk_combo_set_value_in_list(GTK_COMBO(w), false, true); - uc->entry = GTK_COMBO(w)->entry; - uc->list = GTK_COMBO(w)->list; - signalobject = uc->entry; + /* + * GTK 1 combo box. + */ + w = gtk_combo_new(); + gtk_combo_set_value_in_list(GTK_COMBO(w), false, true); + uc->entry = GTK_COMBO(w)->entry; + uc->list = GTK_COMBO(w)->list; + signalobject = uc->entry; #else - /* - * GTK 2 combo box. - */ - uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, - G_TYPE_STRING); - w = gtk_combo_box_new_with_model_and_entry - (GTK_TREE_MODEL(uc->listmodel)); - g_object_set(G_OBJECT(w), "entry-text-column", 1, - (const char *)NULL); - /* We cannot support password combo boxes. */ - assert(!ctrl->editbox.password); - uc->combo = w; - signalobject = uc->combo; + /* + * GTK 2 combo box. + */ + uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, + G_TYPE_STRING); + w = gtk_combo_box_new_with_model_and_entry( + GTK_TREE_MODEL(uc->listmodel)); + g_object_set(G_OBJECT(w), "entry-text-column", 1, + (const char *)NULL); + /* We cannot support password combo boxes. */ + assert(!ctrl->editbox.password); + uc->combo = w; + signalobject = uc->combo; #endif } else { - w = gtk_entry_new(); - if (ctrl->editbox.password) - gtk_entry_set_visibility(GTK_ENTRY(w), false); - uc->entry = w; - signalobject = w; + w = gtk_entry_new(); + if (ctrl->editbox.password) + gtk_entry_set_visibility(GTK_ENTRY(w), false); + uc->entry = w; + signalobject = w; } - uc->entrysig = - g_signal_connect(G_OBJECT(signalobject), "changed", - G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(signalobject), "changed", + G_CALLBACK(editbox_changed), dp); g_signal_connect(G_OBJECT(signalobject), "key_press_event", G_CALLBACK(editbox_key), dp); g_signal_connect(G_OBJECT(signalobject), "focus_in_event", @@ -2056,9 +2094,9 @@ GtkWidget *layout_ctrls( * from the column layout of the rest of the box. */ { - GtkRequisition req; - gtk_widget_size_request(w, &req); - gtk_widget_set_size_request(w, 10, req.height); + GtkRequisition req; + gtk_widget_size_request(w, &req); + gtk_widget_set_size_request(w, 10, req.height); } #else /* @@ -2069,91 +2107,102 @@ GtkWidget *layout_ctrls( gtk_entry_set_width_chars(GTK_ENTRY(w), 1); #endif - if (ctrl->generic.label) { - GtkWidget *label, *container; - - label = gtk_label_new(ctrl->generic.label); - - shortcut_add(scs, label, ctrl->editbox.shortcut, - SHORTCUT_FOCUS, uc->entry); - - container = columns_new(4); - if (ctrl->editbox.percentwidth == 100) { - columns_add(COLUMNS(container), label, 0, 1); - columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 0, 1); - } else { - gint percentages[2]; - percentages[1] = ctrl->editbox.percentwidth; - percentages[0] = 100 - ctrl->editbox.percentwidth; - columns_set_cols(COLUMNS(container), 2, percentages); - columns_add(COLUMNS(container), label, 0, 1); - columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 1, 1); - columns_force_same_height(COLUMNS(container), - label, w); - } - gtk_widget_show(label); - gtk_widget_show(w); - - w = container; - uc->label = label; + if (ctrl->label) { + GtkWidget *label; + + label = gtk_label_new(ctrl->label); + + shortcut_add(scs, label, ctrl->editbox.shortcut, + SHORTCUT_FOCUS, uc->entry); + + if (ctrl->editbox.percentwidth == 100) { + columns_add(cols, label, + COLUMN_START(ctrl->column), + COLUMN_SPAN(ctrl->column)); + columns_force_left_align(cols, label); + } else { + GtkWidget *container = columns_new(4); + gint percentages[2]; + percentages[1] = ctrl->editbox.percentwidth; + percentages[0] = 100 - ctrl->editbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + columns_align_next_to(COLUMNS(container), label, w); + gtk_widget_show(w); + w = container; + } + + gtk_widget_show(label); + uc->label = label; } break; } case CTRL_FILESELECT: case CTRL_FONTSELECT: { GtkWidget *ww; - const char *browsebtn = - (ctrl->generic.type == CTRL_FILESELECT ? - "Browse..." : "Change..."); - - gint percentages[] = { 75, 25 }; - w = columns_new(4); - columns_set_cols(COLUMNS(w), 2, percentages); - - if (ctrl->generic.label) { - ww = gtk_label_new(ctrl->generic.label); - columns_add(COLUMNS(w), ww, 0, 2); - columns_force_left_align(COLUMNS(w), ww); - gtk_widget_show(ww); - shortcut_add(scs, ww, - (ctrl->generic.type == CTRL_FILESELECT ? - ctrl->fileselect.shortcut : - ctrl->fontselect.shortcut), - SHORTCUT_UCTRL, uc); - uc->label = ww; - } - uc->entry = ww = gtk_entry_new(); + bool just_button = (ctrl->type == CTRL_FILESELECT && + ctrl->fileselect.just_button); + + if (!just_button) { + const char *browsebtn = + (ctrl->type == CTRL_FILESELECT ? + "Browse..." : "Change..."); + + gint percentages[] = { 75, 25 }; + w = columns_new(4); + columns_set_cols(COLUMNS(w), 2, percentages); + + if (ctrl->label) { + ww = gtk_label_new(ctrl->label); + columns_add(COLUMNS(w), ww, 0, 2); + columns_force_left_align(COLUMNS(w), ww); + gtk_widget_show(ww); + shortcut_add(scs, ww, + (ctrl->type == CTRL_FILESELECT ? + ctrl->fileselect.shortcut : + ctrl->fontselect.shortcut), + SHORTCUT_UCTRL, uc); + uc->label = ww; + } + + uc->entry = ww = gtk_entry_new(); #if !GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - gtk_widget_size_request(ww, &req); - gtk_widget_set_size_request(ww, 10, req.height); - } + { + GtkRequisition req; + gtk_widget_size_request(ww, &req); + gtk_widget_set_size_request(ww, 10, req.height); + } #else - gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); + gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); #endif - columns_add(COLUMNS(w), ww, 0, 1); - gtk_widget_show(ww); + columns_add(COLUMNS(w), ww, 0, 1); + gtk_widget_show(ww); - uc->button = ww = gtk_button_new_with_label(browsebtn); - columns_add(COLUMNS(w), ww, 1, 1); - gtk_widget_show(ww); + uc->button = ww = gtk_button_new_with_label(browsebtn); + columns_add(COLUMNS(w), ww, 1, 1); + gtk_widget_show(ww); - columns_force_same_height(COLUMNS(w), uc->entry, uc->button); + columns_align_next_to(COLUMNS(w), uc->entry, uc->button); - g_signal_connect(G_OBJECT(uc->entry), "key_press_event", - G_CALLBACK(editbox_key), dp); - uc->entrysig = + g_signal_connect(G_OBJECT(uc->entry), "key_press_event", + G_CALLBACK(editbox_key), dp); g_signal_connect(G_OBJECT(uc->entry), "changed", G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", - G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", + G_CALLBACK(widget_focus), dp); + } else { + uc->button = w = gtk_button_new_with_label(ctrl->label); + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), + ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc); + gtk_widget_show(w); + + } g_signal_connect(G_OBJECT(uc->button), "focus_in_event", G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(ww), "clicked", + g_signal_connect(G_OBJECT(uc->button), "clicked", G_CALLBACK(filefont_clicked), dp); break; } @@ -2215,8 +2264,8 @@ GtkWidget *layout_ctrls( * Create a non-editable GtkComboBox (that is, not * its subclass GtkComboBoxEntry). */ - w = gtk_combo_box_new_with_model - (GTK_TREE_MODEL(uc->listmodel)); + w = gtk_combo_box_new_with_model( + GTK_TREE_MODEL(uc->listmodel)); uc->combo = w; /* @@ -2260,8 +2309,8 @@ GtkWidget *layout_ctrls( gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - uc->adj = gtk_scrolled_window_get_vadjustment - (GTK_SCROLLED_WINDOW(w)); + uc->adj = gtk_scrolled_window_get_vadjustment( + GTK_SCROLLED_WINDOW(w)); gtk_widget_show(uc->list); g_signal_connect(G_OBJECT(uc->list), "selection-changed", @@ -2284,9 +2333,9 @@ GtkWidget *layout_ctrls( { int edge; edge = GTK_WIDGET(uc->list)->style->klass->ythickness; - gtk_widget_set_size_request(w, 10, - 2*edge + (ctrl->listbox.height * - get_listitemheight(w))); + gtk_widget_set_size_request( + w, 10, 2*edge + (ctrl->listbox.height * + get_listitemheight(w))); } if (ctrl->listbox.draglist) { @@ -2331,15 +2380,15 @@ GtkWidget *layout_ctrls( * Create the list box itself, its columns, and * its containing scrolled window. */ - w = gtk_tree_view_new_with_model - (GTK_TREE_MODEL(uc->listmodel)); + w = gtk_tree_view_new_with_model( + GTK_TREE_MODEL(uc->listmodel)); g_object_set_data(G_OBJECT(uc->listmodel), "user-data", (gpointer)w); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false); sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); - gtk_tree_selection_set_mode - (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : - GTK_SELECTION_SINGLE); + gtk_tree_selection_set_mode( + sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : + GTK_SELECTION_SINGLE); uc->treeview = w; g_signal_connect(G_OBJECT(w), "row-activated", G_CALLBACK(listbox_doubleclick), dp); @@ -2376,10 +2425,10 @@ GtkWidget *layout_ctrls( "ellipsize-set", true, (const char *)NULL); } - column = gtk_tree_view_column_new_with_attributes - ("heading", cellrend, "text", i+1, (char *)NULL); - gtk_tree_view_column_set_sizing - (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + column = gtk_tree_view_column_new_with_attributes( + "heading", cellrend, "text", i+1, (char *)NULL); + gtk_tree_view_column_set_sizing( + column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); } } @@ -2388,26 +2437,26 @@ GtkWidget *layout_ctrls( GtkWidget *scroll; scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_shadow_type - (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); + gtk_scrolled_window_set_shadow_type( + GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); gtk_widget_show(w); gtk_container_add(GTK_CONTAINER(scroll), w); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); - gtk_widget_set_size_request - (scroll, -1, - ctrl->listbox.height * get_listitemheight(w)); + gtk_widget_set_size_request( + scroll, -1, + ctrl->listbox.height * get_listitemheight(w)); w = scroll; } #endif } - if (ctrl->generic.label) { + if (ctrl->label) { GtkWidget *label, *container; - label = gtk_label_new(ctrl->generic.label); + label = gtk_label_new(ctrl->label); #if GTK_CHECK_VERSION(3,0,0) gtk_label_set_width_chars(GTK_LABEL(label), 3); #endif @@ -2428,8 +2477,7 @@ GtkWidget *layout_ctrls( columns_add(COLUMNS(container), label, 0, 1); columns_force_left_align(COLUMNS(container), label); columns_add(COLUMNS(container), w, 1, 1); - columns_force_same_height(COLUMNS(container), - label, w); + columns_align_next_to(COLUMNS(container), label, w); } gtk_widget_show(label); gtk_widget_show(w); @@ -2477,34 +2525,41 @@ GtkWidget *layout_ctrls( * wrapping labels behave sensibly. So now we can just do * the obvious thing. */ - uc->text = w = gtk_label_new(uc->ctrl->generic.label); + uc->text = w = gtk_label_new(uc->ctrl->label); +#endif +#if GTK_CHECK_VERSION(2,0,0) + gtk_label_set_selectable(GTK_LABEL(w), true); + gtk_widget_set_can_focus(w, false); #endif align_label_left(GTK_LABEL(w)); - gtk_label_set_line_wrap(GTK_LABEL(w), true); + gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap); + if (!ctrl->text.wrap) { + gtk_widget_show(uc->text); + w = gtk_scrolled_window_new(NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER(w), 0); + gtk_container_add(GTK_CONTAINER(w), uc->text); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_NEVER); +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_can_focus(w, false); +#endif + } break; } assert(w != NULL); columns_add(cols, w, - COLUMN_START(ctrl->generic.column), - COLUMN_SPAN(ctrl->generic.column)); + COLUMN_START(ctrl->column), + COLUMN_SPAN(ctrl->column)); if (left) columns_force_left_align(cols, w); - if (ctrl->generic.align_next_to) { - /* - * Implement align_next_to by simply forcing the two - * controls to have the same height of size allocation. At - * least for the controls we're currently doing this with, - * the GTK layout system will automatically vertically - * centre each control within its allocation, which will - * get the two controls aligned alongside each other - * reasonably well. - */ + if (ctrl->align_next_to) { struct uctrl *uc2 = dlg_find_byctrl( - dp, ctrl->generic.align_next_to); + dp, ctrl->align_next_to); assert(uc2); - columns_force_same_height(cols, w, uc2->toplevel); + columns_align_next_to(cols, w, uc2->toplevel); #if GTK_CHECK_VERSION(3, 10, 0) /* Slightly nicer to align baselines than just vertically @@ -2576,7 +2631,7 @@ static void treeitem_sel(GtkItem *item, gpointer data) } #endif -bool dlg_is_visible(union control *ctrl, dlgparam *dp) +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* @@ -2657,7 +2712,7 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) * Precisely what this is depends on the type of * control. */ - switch (sc->uc->ctrl->generic.type) { + switch (sc->uc->ctrl->type) { case CTRL_CHECKBOX: case CTRL_BUTTON: /* Check boxes and buttons get the focus _and_ get toggled. */ @@ -2669,7 +2724,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) /* File/font selectors have their buttons pressed (ooer), * and focus transferred to the edit box. */ g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked"); - gtk_widget_grab_focus(sc->uc->entry); + if (sc->uc->entry) + gtk_widget_grab_focus(sc->uc->entry); break; case CTRL_RADIO: /* @@ -2684,8 +2740,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) if (schr == sc->uc->ctrl->radio.shortcut) { int i; for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) - if (gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) { + if (gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) { gtk_widget_grab_focus(sc->uc->buttons[i]); } } else if (sc->uc->ctrl->radio.shortcuts) { @@ -2693,8 +2749,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) if (schr == sc->uc->ctrl->radio.shortcuts[i]) { gtk_widget_grab_focus(sc->uc->buttons[i]); - g_signal_emit_by_name - (G_OBJECT(sc->uc->buttons[i]), "clicked"); + g_signal_emit_by_name( + G_OBJECT(sc->uc->buttons[i]), "clicked"); } } break; @@ -2983,13 +3039,13 @@ GtkWidget *create_config_box(const char *title, Conf *conf, gtk_widget_show(label); treescroll = gtk_scrolled_window_new(NULL, NULL); #if GTK_CHECK_VERSION(2,0,0) - treestore = gtk_tree_store_new - (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT); + treestore = gtk_tree_store_new( + TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT); tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore)); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false); treerenderer = gtk_cell_renderer_text_new(); - treecolumn = gtk_tree_view_column_new_with_attributes - ("Label", treerenderer, "text", 0, NULL); + treecolumn = gtk_tree_view_column_new_with_attributes( + "Label", treerenderer, "text", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn); treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE); @@ -3117,9 +3173,8 @@ GtkWidget *create_config_box(const char *title, Conf *conf, if (j > 0) { if (!treelevels[j-1]) { treelevels[j-1] = GTK_TREE(gtk_tree_new()); - gtk_tree_item_set_subtree - (treeitemlevels[j-1], - GTK_WIDGET(treelevels[j-1])); + gtk_tree_item_set_subtree( + treeitemlevels[j-1], GTK_WIDGET(treelevels[j-1])); if (j < 2) gtk_tree_item_expand(treeitemlevels[j-1]); else @@ -3243,9 +3298,9 @@ GtkWidget *create_config_box(const char *title, Conf *conf, if (*s->pathname) { for (j = 0; j < s->ncontrols; j++) - if (s->ctrls[j]->generic.type != CTRL_TABDELAY && - s->ctrls[j]->generic.type != CTRL_COLUMNS && - s->ctrls[j]->generic.type != CTRL_TEXT) { + if (s->ctrls[j]->type != CTRL_TABDELAY && + s->ctrls[j]->type != CTRL_COLUMNS && + s->ctrls[j]->type != CTRL_TEXT) { dlg_set_focus(s->ctrls[j], dp); dp->lastfocus = s->ctrls[j]; done = true; @@ -3283,11 +3338,11 @@ static void dlgparam_destroy(GtkWidget *widget, gpointer data) sfree(dp); } -static void messagebox_handler(union control *ctrl, dlgparam *dp, +static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) - dlg_end(dp, ctrl->generic.context.i); + dlg_end(dp, ctrl->context.i); } static const struct message_box_button button_array_yn[] = { @@ -3312,7 +3367,7 @@ static GtkWidget *create_message_box_general( { GtkWidget *window, *w0, *w1; struct controlset *s0, *s1; - union control *c, *textctrl; + dlgcontrol *c, *textctrl; struct dlgparam *dp; struct Shortcuts scs; int i, index, ncols, min_type; @@ -3355,7 +3410,7 @@ static GtkWidget *create_message_box_general( c = ctrl_pushbutton(s0, button->title, button->shortcut, HELPCTX(no_help), messagebox_handler, I(button->value)); - c->generic.column = index++; + c->column = index++; if (button->type > 0) c->button.isdefault = true; @@ -3539,41 +3594,22 @@ static void more_info_button_clicked(GtkButton *button, gpointer vctx) &buttons_ok, more_info_closed, ctx); } +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char absenttxt[] = - "The host key is not cached for this server:\n\n" - "%s (port %d)\n\n" - "You have no guarantee that the server is the computer " - "you think it is.\n" - "The server's %s key fingerprint is:\n\n" - "%s\n\n" - "If you trust this host, press \"Accept\" to add the key to " - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without " - "adding the key to the cache, press \"Connect Once\".\n" - "If you do not trust this host, press \"Cancel\" to abandon the " - "connection."; - static const char wrongtxt[] = - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The host key does not match the one PuTTY has cached " - "for this server:\n\n" - "%s (port %d)\n\n" - "This means that either the server administrator has " - "changed the host key, or you have actually connected " - "to another computer pretending to be the server.\n" - "The new %s key fingerprint is:\n\n" - "%s\n\n" - "If you were expecting this change and trust the new key, " - "press \"Accept\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating " - "the cache, press \"Connect Once\".\n" - "If you want to abandon the connection completely, press " - "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " - "safe choice."; static const struct message_box_button button_array_hostkey[] = { {"Accept", 'a', 0, 2}, {"Connect Once", 'o', 0, 1}, @@ -3583,17 +3619,40 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( button_array_hostkey, lenof(button_array_hostkey), }; - char *text; - struct confirm_ssh_host_key_dialog_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; + const char *dlg_title = NULL; + strbuf *dlg_text = strbuf_new(); + int width = string_width("default dialog width determination string"); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_DISPLAY: { + put_fmt(dlg_text, "%s\n\n", item->text); + int thiswidth = string_width(item->text); + if (width < thiswidth) + width = thiswidth; + break; + } + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: + break; + } + } + while (strbuf_chomp(dlg_text, '\n')); - text = dupprintf((mismatch ? wrongtxt : absenttxt), host, port, - keytype, fingerprints[fptype_default]); + GtkWidget *mainwin, *msgbox; - result_ctx = snew(struct confirm_ssh_host_key_dialog_ctx); + struct confirm_ssh_host_key_dialog_ctx *result_ctx = + snew(struct confirm_ssh_host_key_dialog_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->host = dupstr(host); @@ -3605,41 +3664,50 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); GtkWidget *more_info_button = NULL; msgbox = create_message_box_general( - mainwin, "PuTTY Security Alert", text, - string_width(fingerprints[fptype_default]), true, + mainwin, dlg_title, dlg_text->s, width, true, &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx, add_more_info_button, &more_info_button); result_ctx->main_dialog = msgbox; result_ctx->more_info_dialog = NULL; - strbuf *sb = strbuf_new(); - if (fingerprints[SSH_FPTYPE_SHA256]) - put_fmt(sb, "SHA256 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - put_fmt(sb, "MD5 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_MD5]); - put_fmt(sb, "Full text of host's public key:"); - /* We have to manually wrap the public key, or else the GtkLabel - * will resize itself to accommodate the longest word, which will - * lead to a hilariously wide message box. */ - for (const char *p = keydisp, *q = p + strlen(p); p < q ;) { - size_t linelen = q-p; - if (linelen > 72) - linelen = 72; - put_byte(sb, '\n'); - put_data(sb, p, linelen); - p += linelen; + strbuf *moreinfo = strbuf_new(); + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + put_fmt(moreinfo, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + put_fmt(moreinfo, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + /* We have to manually wrap the public key, or else the GtkLabel + * will resize itself to accommodate the longest word, which will + * lead to a hilariously wide message box. */ + put_byte(moreinfo, ':'); + for (const char *p = item->text, *q = p + strlen(p); p < q ;) { + size_t linelen = q-p; + if (linelen > 72) + linelen = 72; + put_byte(moreinfo, '\n'); + put_data(moreinfo, p, linelen); + p += linelen; + } + put_byte(moreinfo, '\n'); + break; + default: + break; + } } - result_ctx->more_info = strbuf_to_str(sb); + result_ctx->more_info = strbuf_to_str(moreinfo); g_signal_connect(G_OBJECT(more_info_button), "clicked", G_CALLBACK(more_info_button_clicked), result_ctx); register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); - sfree(text); + strbuf_free(dlg_text); return SPR_INCOMPLETE; /* dialog still in progress */ } @@ -3846,10 +3914,10 @@ void about_box(void *window) { char *buildinfo_text = buildinfo("\n"); - char *label_text = dupprintf - ("%s\n\n%s\n\n%s\n\n%s", - appname, ver, buildinfo_text, - "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); + char *label_text = dupprintf( + "%s\n\n%s\n\n%s\n\n%s", + appname, ver, buildinfo_text, + "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); w = gtk_label_new(label_text); gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER); #if GTK_CHECK_VERSION(2,0,0) @@ -3888,7 +3956,7 @@ struct eventlog_stuff { struct controlbox *eventbox; struct Shortcuts scs; struct dlgparam dp; - union control *listctrl; + dlgcontrol *listctrl; char **events_initial; char **events_circular; int ninitial, ncircular, circular_first; @@ -3905,13 +3973,13 @@ static void eventlog_destroy(GtkWidget *widget, gpointer data) dlg_cleanup(&es->dp); ctrl_free_box(es->eventbox); } -static void eventlog_ok_handler(union control *ctrl, dlgparam *dp, +static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) dlg_end(dp, 0); } -static void eventlog_list_handler(union control *ctrl, dlgparam *dp, +static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { eventlog_stuff *es = (eventlog_stuff *)data; @@ -3991,8 +4059,8 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gtk_list_unselect_all(GTK_LIST(uc->list)); #else assert(uc->treeview); - gtk_tree_selection_unselect_all - (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); + gtk_tree_selection_unselect_all( + gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); #endif es->ignore_selchange = false; @@ -4004,7 +4072,7 @@ void showeventlog(eventlog_stuff *es, void *parentwin) GtkWidget *window, *w0, *w1; GtkWidget *parent = GTK_WIDGET(parentwin); struct controlset *s0, *s1; - union control *c; + dlgcontrol *c; int index; char *title; @@ -4025,7 +4093,7 @@ void showeventlog(eventlog_stuff *es, void *parentwin) ctrl_columns(s0, 3, 33, 34, 33); c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help), eventlog_ok_handler, P(NULL)); - c->button.column = 1; + c->column = 1; c->button.isdefault = true; s1 = ctrl_getset(es->eventbox, "x", "", ""); @@ -4048,10 +4116,10 @@ void showeventlog(eventlog_stuff *es, void *parentwin) gtk_widget_show(w0); w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request(w1, 20 + string_width - ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " - "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), - -1); + gtk_widget_set_size_request( + w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " + "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), + -1); our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); { struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl); @@ -4219,3 +4287,99 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, return -1; /* dialog still in progress */ } + +struct ca_config_box { + GtkWidget *window; + struct controlbox *cb; + struct Shortcuts scs; + bool quit_main; + dlgparam dp; +}; +static struct ca_config_box *cacfg; /* one of these, cross-instance */ + +static void cacfg_destroy(GtkWidget *widget, gpointer data) +{ + cacfg->window = NULL; + dlg_cleanup(&cacfg->dp); + ctrl_free_box(cacfg->cb); + cacfg->cb = NULL; + if (cacfg->quit_main) + gtk_main_quit(); +} +static void make_ca_config_box(GtkWidget *spawning_window) +{ + if (!cacfg) { + cacfg = snew(struct ca_config_box); + memset(cacfg, 0, sizeof(*cacfg)); + } + + if (cacfg->window) { + /* This dialog box is already displayed; re-focus it */ + gtk_widget_grab_focus(cacfg->window); + return; + } + + dlg_init(&cacfg->dp); + for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) { + cacfg->scs.sc[i].action = SHORTCUT_EMPTY; + } + + cacfg->cb = ctrl_new_box(); + setup_ca_config_box(cacfg->cb); + + cacfg->window = our_dialog_new(); + gtk_window_set_title(GTK_WINDOW(cacfg->window), + "PuTTY trusted host certification authorities"); + gtk_widget_set_size_request( + cacfg->window, string_width( + "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5" + "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1); + + /* Set up everything else */ + for (int i = 0; i < cacfg->cb->nctrlsets; i++) { + struct controlset *s = cacfg->cb->ctrlsets[i]; + GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s, + GTK_WINDOW(cacfg->window)); + gtk_container_set_border_width(GTK_CONTAINER(w), 10); + gtk_widget_show(w); + + if (!*s->pathname) { + our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w); + } else { + our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w, + true, true, 0); + } + } + + cacfg->dp.data = cacfg; + cacfg->dp.shortcuts = &cacfg->scs; + cacfg->dp.lastfocus = NULL; + cacfg->dp.retval = 0; + cacfg->dp.window = cacfg->window; + + dlg_refresh(NULL, &cacfg->dp); + + if (spawning_window) { + set_transient_window_pos(spawning_window, cacfg->window); + } else { + gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER); + } + gtk_widget_show(cacfg->window); + + g_signal_connect(G_OBJECT(cacfg->window), "destroy", + G_CALLBACK(cacfg_destroy), NULL); + g_signal_connect(G_OBJECT(cacfg->window), "key_press_event", + G_CALLBACK(win_key_press), &cacfg->dp); +} + +void show_ca_config_box(dlgparam *dp) +{ + make_ca_config_box(dp ? dp->window : NULL); +} + +void show_ca_config_box_synchronously(void) +{ + make_ca_config_box(NULL); + cacfg->quit_main = true; + gtk_main(); +} diff --git a/code/unix/fd-socket.c b/code/unix/fd-socket.c index 036979d3..9758a17b 100644 --- a/code/unix/fd-socket.c +++ b/code/unix/fd-socket.c @@ -371,6 +371,13 @@ void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) queue_toplevel_callback(fdsocket_connect_success_callback, fds); } +void fd_socket_set_psb_prefix(Socket *s, const char *prefix) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + assert(fds->sock.vt == &FdSocket_sockvt); + psb_set_prefix(&fds->psb, prefix); +} + static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug) { FdSocket *fds; diff --git a/code/unix/gss.c b/code/unix/gss.c index cd9971c7..bd599fcc 100644 --- a/code/unix/gss.c +++ b/code/unix/gss.c @@ -140,6 +140,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) list->libraries = snew(struct ssh_gss_library); list->nlibraries = 1; + list->libraries[0].id = 0; list->libraries[0].gsslogmsg = "Using statically linked GSSAPI"; #define BIND_GSS_FN(name) \ diff --git a/code/unix/local-proxy.c b/code/unix/local-proxy.c index 92f2a501..157f9207 100644 --- a/code/unix/local-proxy.c +++ b/code/unix/local-proxy.c @@ -96,3 +96,21 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return NULL; } } + +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix) +{ + Socket *socket = make_deferred_fd_socket( + null_deferred_socket_opener(), + sk_nonamelookup(""), 0, plug); + char *err = platform_setup_local_proxy(socket, cmd); + fd_socket_set_psb_prefix(socket, prefix); + + if (err) { + sk_close(socket); + socket = new_error_socket_fmt(plug, "%s", err); + sfree(err); + } + + return socket; +} diff --git a/code/unix/main-gtk-simple.c b/code/unix/main-gtk-simple.c index e52a18d0..4cbb5a31 100644 --- a/code/unix/main-gtk-simple.c +++ b/code/unix/main-gtk-simple.c @@ -468,7 +468,7 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) break; /* finished command-line processing */ } else err = true, fprintf(stderr, "%s: -e expects an argument\n", - appname); + appname); } else if (!strcmp(p, "-title")) { EXPECTS_ARG; @@ -532,6 +532,12 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) pgp_fingerprints(); exit(1); + } else if (has_ca_config_box && + (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { + show_ca_config_box_synchronously(); + exit(0); + } else if (p[0] != '-') { /* Non-option arguments not handled by cmdline.c are errors. */ if (do_everything) { diff --git a/code/unix/network.c b/code/unix/network.c index 0f15796b..31c754bd 100644 --- a/code/unix/network.c +++ b/code/unix/network.c @@ -150,9 +150,9 @@ static int cmpfortree(void *av, void *bv) if (as > bs) return +1; if (a < b) - return -1; + return -1; if (a > b) - return +1; + return +1; return 0; } @@ -184,103 +184,89 @@ void sk_cleanup(void) } } -SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) { + *canonicalname = NULL; + if (host[0] == '/') { *canonicalname = dupstr(host); return unix_sock_addr(host); } - SockAddr *ret = snew(SockAddr); -#ifndef NO_IPV6 - struct addrinfo hints; - int err; -#else - unsigned long a; - struct hostent *h = NULL; - int n; -#endif - strbuf *realhost = strbuf_new(); - - /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(SockAddr)); - ret->superfamily = UNRESOLVED; - ret->error = NULL; - ret->refcount = 1; + SockAddr *addr = snew(SockAddr); + memset(addr, 0, sizeof(SockAddr)); + addr->superfamily = UNRESOLVED; + addr->refcount = 1; #ifndef NO_IPV6 - hints.ai_flags = AI_CANONNAME; - hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : - address_family == ADDRTYPE_IPV6 ? AF_INET6 : - AF_UNSPEC); - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - hints.ai_addrlen = 0; - hints.ai_addr = NULL; - hints.ai_canonname = NULL; - hints.ai_next = NULL; + /* + * Use getaddrinfo, as long as it's available. This should handle + * both IPv4 and IPv6 address literals, and hostnames, in one + * unified API. + */ { - char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ - err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + int err = getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); sfree(trimmed_host); + + if (addr->ais) { + addr->superfamily = IP; + if (addr->ais->ai_canonname) + *canonicalname = dupstr(addr->ais->ai_canonname); + else + *canonicalname = dupstr(host); + } else { + addr->error = gai_strerror(err); + } + return addr; } - if (err != 0) { - ret->error = gai_strerror(err); - strbuf_free(realhost); - return ret; - } - ret->superfamily = IP; - if (ret->ais->ai_canonname != NULL) - put_fmt(realhost, "%s", ret->ais->ai_canonname); - else - put_fmt(realhost, "%s", host); #else - if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) { - /* - * Otherwise use the IPv4-only gethostbyname... (NOTE: - * we don't use gethostbyname as a fallback!) - */ - if (ret->superfamily == UNRESOLVED) { - /*debug("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host); */ - if ( (h = gethostbyname(host)) ) - ret->superfamily = IP; - } - if (ret->superfamily == UNRESOLVED) { - ret->error = (h_errno == HOST_NOT_FOUND || - h_errno == NO_DATA || - h_errno == NO_ADDRESS ? "Host does not exist" : - h_errno == TRY_AGAIN ? - "Temporary name service failure" : - "gethostbyname: unknown error"); - strbuf_free(realhost); - return ret; - } - /* This way we are always sure the h->h_name is valid :) */ - strbuf_clear(realhost); - put_fmt(realhost, "%s", h->h_name); + /* + * Failing that (if IPv6 support was not compiled in), try the + * old-fashioned approach, which is to start by manually checking + * for an IPv4 literal and then use gethostbyname. + */ + unsigned long a = inet_addr(host); + if (a != (unsigned long) INADDR_NONE) { + addr->addresses = snew(unsigned long); + addr->naddresses = 1; + addr->addresses[0] = ntohl(a); + addr->superfamily = IP; + *canonicalname = dupstr(host); + return addr; + } + + struct hostent *h = gethostbyname(host); + if (h) { + addr->superfamily = IP; + + size_t n; for (n = 0; h->h_addr_list[n]; n++); - ret->addresses = snewn(n, unsigned long); - ret->naddresses = n; - for (n = 0; n < ret->naddresses; n++) { + addr->addresses = snewn(n, unsigned long); + addr->naddresses = n; + for (n = 0; n < addr->naddresses; n++) { + uint32_t a; memcpy(&a, h->h_addr_list[n], sizeof(a)); - ret->addresses[n] = ntohl(a); + addr->addresses[n] = ntohl(a); } + + *canonicalname = dupstr(h->h_name); } else { - /* - * This must be a numeric IPv4 address because it caused a - * success return from inet_addr. - */ - ret->superfamily = IP; - strbuf_clear(realhost); - put_fmt(realhost, "%s", host); - ret->addresses = snew(unsigned long); - ret->naddresses = 1; - ret->addresses[0] = ntohl(a); + addr->error = hstrerror(h_errno); } + return addr; #endif - *canonicalname = strbuf_to_str(realhost); - return ret; } SockAddr *sk_nonamelookup(const char *host) @@ -669,14 +655,14 @@ static int try_connect(NetSocket *sock) } else { err = errno; if (err != EADDRINUSE) /* failed, for a bad reason */ - break; + break; } if (localport == 0) - break; /* we're only looping once */ + break; /* we're only looping once */ localport--; if (localport == 0) - break; /* we might have got to the end */ + break; /* we might have got to the end */ } if (err) @@ -745,7 +731,7 @@ static int try_connect(NetSocket *sock) uxsel_tell(sock); - ret: + ret: /* * No matter what happened, put the socket back in the tree. @@ -1057,7 +1043,7 @@ void *sk_getxdmdata(Socket *sock, int *lenp) PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port)); break; #ifndef NO_IPV6 - case AF_INET6: + case AF_INET6: *lenp = 6; buf = snewn(*lenp, char); if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) { diff --git a/code/unix/noaskpass.c b/code/unix/noaskpass.c new file mode 100644 index 00000000..de646c55 --- /dev/null +++ b/code/unix/noaskpass.c @@ -0,0 +1,19 @@ +/* + * Dummy (lack-of-)implementation of a GUI password/passphrase prompt. + */ + +#include "putty.h" + +void random_add_noise(NoiseSourceId source, const void *noise, int length) +{ + /* We have no keypress_prng here, so no need to implement this */ +} + +const bool buildinfo_gtk_relevant = false; + +char *gtk_askpass_main(const char *display, const char *wintitle, + const char *prompt, bool *success) +{ + *success = false; + return dupstr("this Pageant was built without GTK"); +} diff --git a/code/unix/pageant.c b/code/unix/pageant.c index 5db797a8..4558cd37 100644 --- a/code/unix/pageant.c +++ b/code/unix/pageant.c @@ -330,7 +330,11 @@ void pageant_fork_and_print_env(bool retain_tty) /* Get out of our previous process group, to avoid being * blasted by passing signals. But keep our controlling tty, * so we can keep checking to see if we still have one. */ +#if HAVE_NULLARY_SETPGRP setpgrp(); +#elif HAVE_BINARY_SETPGRP + setpgrp(0, 0); +#endif } else { /* Do that, but also leave our entire session and detach from * the controlling tty (if any). */ @@ -612,6 +616,7 @@ static bool match_fingerprint_string( switch (fptype) { case SSH_FPTYPE_MD5: + case SSH_FPTYPE_MD5_CERT: /* MD5 fingerprints are in hex, so disregard case differences. */ case_sensitive = false; /* And we don't really need to force the user to type the @@ -620,6 +625,7 @@ static bool match_fingerprint_string( ignore_chars = ":"; break; case SSH_FPTYPE_SHA256: + case SSH_FPTYPE_SHA256_CERT: /* Skip over the "SHA256:" prefix, which we don't really * want to force the user to type. On the other hand, * tolerate it on the input string. */ @@ -713,6 +719,18 @@ struct pageant_pubkey *find_key(const char *string, char **retstr) try_comment = false; try_all_fptypes = false; fptype = SSH_FPTYPE_SHA256; + } else if (!strnicmp(string, "md5-cert:", 9)) { + string += 9; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_MD5_CERT; + } else if (!strncmp(string, "sha256-cert:", 12)) { + string += 12; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_SHA256_CERT; } /* @@ -1402,6 +1420,10 @@ int main(int argc, char **argv) key_list_fptype = SSH_FPTYPE_MD5; else if (!strcmp(keyword, "sha256")) key_list_fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(keyword, "md5-cert")) + key_list_fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(keyword, "sha256-cert")) + key_list_fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", keyword); diff --git a/code/unix/platform.h b/code/unix/platform.h index 7cdf4d11..3b1db9ba 100644 --- a/code/unix/platform.h +++ b/code/unix/platform.h @@ -78,7 +78,9 @@ extern const struct BackendVtable pty_backend; /* * Under GTK, there is no context help available. */ -#define HELPCTX(x) P(NULL) +typedef void *HelpCtx; +#define NULL_HELPCTX ((HelpCtx)NULL) +#define HELPCTX(x) NULL #define FILTER_KEY_FILES NULL /* FIXME */ #define FILTER_DYNLIB_FILES NULL /* FIXME */ @@ -220,7 +222,7 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -228,6 +230,7 @@ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat); #ifdef MAY_REFER_TO_GTK_IN_HEADERS struct message_box_button { const char *title; @@ -245,6 +248,7 @@ GtkWidget *create_message_box( bool selectable, const struct message_box_buttons *buttons, post_dialog_fn_t after, void *afterctx); #endif +void show_ca_config_box_synchronously(void); /* window.c needs this special function in utils */ int keysym_to_unicode(int keysym); @@ -385,6 +389,7 @@ Socket *make_fd_socket(int infd, int outfd, int inerrfd, Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, SockAddr *addr, int port, Plug *plug); void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); +void fd_socket_set_psb_prefix(Socket *s, const char *prefix); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. diff --git a/code/unix/plink.c b/code/unix/plink.c index 9e109f01..f0c73754 100644 --- a/code/unix/plink.c +++ b/code/unix/plink.c @@ -322,7 +322,12 @@ static BinarySink *stdout_bs, *stderr_bs; static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; -size_t try_output(bool is_stderr) +static size_t output_backlog(void) +{ + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); +} + +void try_output(bool is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); @@ -343,12 +348,13 @@ size_t try_output(bool is_stderr) perror(is_stderr ? "stderr: write" : "stdout: write"); exit(1); } + + backend_unthrottle(backend, output_backlog()); } if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { close(STDOUT_FILENO); outgoingeof = EOF_SENT; } - return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); } static size_t plink_output( @@ -360,7 +366,8 @@ static size_t plink_output( BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; put_data(bs, data, len); - return try_output(is_stderr); + try_output(is_stderr); + return output_backlog(); } static bool plink_eof(Seat *seat) @@ -408,6 +415,7 @@ static const SeatVtable plink_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -434,57 +442,57 @@ static void from_tty(void *vbuf, unsigned len) p = buf; end = buf + len; while (p < end) { switch (state) { - case NORMAL: - if (*p == '\xff') { - p++; - state = FF; - } else { - q = memchr(p, '\xff', end - p); - if (q == NULL) q = end; - backend_send(backend, p, q - p); - p = q; - } - break; - case FF: - if (*p == '\xff') { - backend_send(backend, p, 1); - p++; - state = NORMAL; - } else if (*p == '\0') { - p++; - state = FF00; - } else abort(); - break; - case FF00: - if (*p == '\0') { - backend_special(backend, SS_BRK, 0); - } else { - /* - * Pretend that PARMRK wasn't set. This involves - * faking what INPCK and IGNPAR would have done if - * we hadn't overridden them. Unfortunately, we - * can't do this entirely correctly because INPCK - * distinguishes between framing and parity - * errors, but PARMRK format represents both in - * the same way. We assume that parity errors are - * more common than framing errors, and hence - * treat all input errors as being subject to - * INPCK. - */ - if (orig_termios.c_iflag & INPCK) { - /* If IGNPAR is set, we throw away the character. */ - if (!(orig_termios.c_iflag & IGNPAR)) { - /* PE/FE get passed on as NUL. */ - *p = 0; - backend_send(backend, p, 1); - } - } else { - /* INPCK not set. Assume we got a parity error. */ + case NORMAL: + if (*p == '\xff') { + p++; + state = FF; + } else { + q = memchr(p, '\xff', end - p); + if (q == NULL) q = end; + backend_send(backend, p, q - p); + p = q; + } + break; + case FF: + if (*p == '\xff') { + backend_send(backend, p, 1); + p++; + state = NORMAL; + } else if (*p == '\0') { + p++; + state = FF00; + } else abort(); + break; + case FF00: + if (*p == '\0') { + backend_special(backend, SS_BRK, 0); + } else { + /* + * Pretend that PARMRK wasn't set. This involves + * faking what INPCK and IGNPAR would have done if + * we hadn't overridden them. Unfortunately, we + * can't do this entirely correctly because INPCK + * distinguishes between framing and parity + * errors, but PARMRK format represents both in + * the same way. We assume that parity errors are + * more common than framing errors, and hence + * treat all input errors as being subject to + * INPCK. + */ + if (orig_termios.c_iflag & INPCK) { + /* If IGNPAR is set, we throw away the character. */ + if (!(orig_termios.c_iflag & IGNPAR)) { + /* PE/FE get passed on as NUL. */ + *p = 0; backend_send(backend, p, 1); } + } else { + /* INPCK not set. Assume we got a parity error. */ + backend_send(backend, p, 1); } - p++; - state = NORMAL; + } + p++; + state = NORMAL; } } } @@ -649,13 +657,11 @@ static void plink_pw_check(void *vctx, pollwrapper *pw) } } - if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(false)); - } + if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) + try_output(false); - if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(true)); - } + if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) + try_output(true); } static bool plink_continue(void *vctx, bool found_any_fd, diff --git a/code/unix/pty.c b/code/unix/pty.c index 625f1bb1..05c58929 100644 --- a/code/unix/pty.c +++ b/code/unix/pty.c @@ -354,7 +354,7 @@ static void pty_open_master(Pty *pty) fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n"); exit(1); - got_one: + got_one: /* We need to chown/chmod the /dev/ttyXX device. */ gp = getgrnam("tty"); diff --git a/code/unix/sftp.c b/code/unix/sftp.c index 9d099f55..023f41ee 100644 --- a/code/unix/sftp.c +++ b/code/unix/sftp.c @@ -265,16 +265,16 @@ int seek_file(WFile *f, uint64_t offset, int whence) int lseek_whence; switch (whence) { - case FROM_START: + case FROM_START: lseek_whence = SEEK_SET; break; - case FROM_CURRENT: + case FROM_CURRENT: lseek_whence = SEEK_CUR; break; - case FROM_END: + case FROM_END: lseek_whence = SEEK_END; break; - default: + default: return -1; } diff --git a/code/unix/sftpserver.c b/code/unix/sftpserver.c index 7257c5c9..453a3ee0 100644 --- a/code/unix/sftpserver.c +++ b/code/unix/sftpserver.c @@ -545,7 +545,7 @@ static void uss_read(SftpServer *srv, SftpReplyBuilder *reply, } static void uss_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) + ptrlen handle, uint64_t offset, ptrlen data) { UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); int fd; diff --git a/code/unix/storage.c b/code/unix/storage.c index c61fb526..ca225732 100644 --- a/code/unix/storage.c +++ b/code/unix/storage.c @@ -28,7 +28,7 @@ enum { INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED, - INDEX_SESSIONDIR, INDEX_SESSION, + INDEX_SESSIONDIR, INDEX_SESSION, INDEX_HOSTCADIR, INDEX_HOSTCA }; static const char hex[16] = "0123456789ABCDEF"; @@ -202,6 +202,23 @@ static char *make_filename(int index, const char *subname) sfree(tmp); return ret; } + if (index == INDEX_HOSTCADIR) { + env = getenv("PUTTYSSHHOSTCAS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sshhostcas", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_HOSTCA) { + strbuf *sb = strbuf_new(); + tmp = make_filename(INDEX_HOSTCADIR, NULL); + put_fmt(sb, "%s/", tmp); + sfree(tmp); + make_session_filename(subname, sb); + return strbuf_to_str(sb); + } tmp = make_filename(INDEX_DIR, NULL); ret = dupprintf("%s/ERROR", tmp); sfree(tmp); @@ -545,25 +562,25 @@ settings_e *enum_settings_start(void) return toret; } -bool enum_settings_next(settings_e *handle, strbuf *out) +static bool enum_dir_next(DIR *dp, int index, strbuf *out) { struct dirent *de; struct stat st; strbuf *fullpath; - if (!handle->dp) - return NULL; + if (!dp) + return false; fullpath = strbuf_new(); - char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL); + char *sessiondir = make_filename(index, NULL); put_dataz(fullpath, sessiondir); sfree(sessiondir); put_byte(fullpath, '/'); size_t baselen = fullpath->len; - while ( (de = readdir(handle->dp)) != NULL ) { + while ( (de = readdir(dp)) != NULL ) { strbuf_shrink_to(fullpath, baselen); put_dataz(fullpath, de->d_name); @@ -579,6 +596,11 @@ bool enum_settings_next(settings_e *handle, strbuf *out) return false; } +bool enum_settings_next(settings_e *handle, strbuf *out) +{ + return enum_dir_next(handle->dp, INDEX_SESSIONDIR, out); +} + void enum_settings_finish(settings_e *handle) { if (handle->dp) @@ -586,6 +608,138 @@ void enum_settings_finish(settings_e *handle) sfree(handle); } +struct host_ca_enum { + DIR *dp; +}; + +host_ca_enum *enum_host_ca_start(void) +{ + host_ca_enum *handle = snew(host_ca_enum); + + char *filename = make_filename(INDEX_HOSTCADIR, NULL); + handle->dp = opendir(filename); + sfree(filename); + + return handle; +} + +bool enum_host_ca_next(host_ca_enum *handle, strbuf *out) +{ + return enum_dir_next(handle->dp, INDEX_HOSTCADIR, out); +} + +void enum_host_ca_finish(host_ca_enum *handle) +{ + if (handle->dp) + closedir(handle->dp); + sfree(handle); +} + +host_ca *host_ca_load(const char *name) +{ + char *filename = make_filename(INDEX_HOSTCA, name); + FILE *fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return NULL; + + host_ca *hca = host_ca_new(); + hca->name = dupstr(name); + + char *line; + CertExprBuilder *eb = NULL; + + while ( (line = fgetline(fp)) ) { + char *value = strchr(line, '='); + + if (!value) { + sfree(line); + continue; + } + *value++ = '\0'; + value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ + + if (!strcmp(line, "PublicKey")) { + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value)); + } else if (!strcmp(line, "MatchHosts")) { + if (!eb) + eb = cert_expr_builder_new(); + cert_expr_builder_add(eb, value); + } else if (!strcmp(line, "Validity")) { + hca->validity_expression = strbuf_to_str( + percent_decode_sb(ptrlen_from_asciz(value))); + } else if (!strcmp(line, "PermitRSASHA1")) { + hca->opts.permit_rsa_sha1 = atoi(value); + } else if (!strcmp(line, "PermitRSASHA256")) { + hca->opts.permit_rsa_sha256 = atoi(value); + } else if (!strcmp(line, "PermitRSASHA512")) { + hca->opts.permit_rsa_sha512 = atoi(value); + } + + sfree(line); + } + + fclose(fp); + + if (eb) { + if (!hca->validity_expression) { + hca->validity_expression = cert_expr_expression(eb); + } + cert_expr_builder_free(eb); + } + + return hca; +} + +char *host_ca_save(host_ca *hca) +{ + if (!*hca->name) + return dupstr("CA record must have a name"); + + char *filename = make_filename(INDEX_HOSTCA, hca->name); + FILE *fp = fopen(filename, "w"); + if (!fp) + return dupprintf("Unable to open file '%s'", filename); + + fprintf(fp, "PublicKey="); + base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0); + fprintf(fp, "\n"); + + fprintf(fp, "Validity="); + percent_encode_fp(fp, ptrlen_from_asciz(hca->validity_expression), NULL); + fprintf(fp, "\n"); + + fprintf(fp, "PermitRSASHA1=%d\n", (int)hca->opts.permit_rsa_sha1); + fprintf(fp, "PermitRSASHA256=%d\n", (int)hca->opts.permit_rsa_sha256); + fprintf(fp, "PermitRSASHA512=%d\n", (int)hca->opts.permit_rsa_sha512); + + bool bad = ferror(fp); + if (fclose(fp) < 0) + bad = true; + + char *err = NULL; + if (bad) + err = dupprintf("Unable to write file '%s'", filename); + + sfree(filename); + return err; +} + +char *host_ca_delete(const char *name) +{ + if (!*name) + return dupstr("CA record must have a name"); + char *filename = make_filename(INDEX_HOSTCA, name); + bool bad = remove(filename) < 0; + + char *err = NULL; + if (bad) + err = dupprintf("Unable to delete file '%s'", filename); + + sfree(filename); + return err; +} + /* * Lines in the host keys file are of the form * @@ -654,7 +808,7 @@ int check_stored_host_key(const char *hostname, int port, else ret = 2; /* key mismatch */ - done: + done: sfree(line); if (ret != 1) break; diff --git a/code/unix/unicode.c b/code/unix/unicode.c index 4eaa45f4..a98c8d3b 100644 --- a/code/unix/unicode.c +++ b/code/unix/unicode.c @@ -61,8 +61,7 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, } int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata) + char *mbstr, int mblen, const char *defchr) { if (codepage == DEFAULT_CODEPAGE) { char output[MB_LEN_MAX]; @@ -264,7 +263,7 @@ const char *cp_enumerate(int index) return charset_to_localenc(charset); } -int decode_codepage(char *cp_name) +int decode_codepage(const char *cp_name) { if (!cp_name || !*cp_name) return CS_UTF8; diff --git a/code/unix/unifont.c b/code/unix/unifont.c index 62445db2..e9f8623a 100644 --- a/code/unix/unifont.c +++ b/code/unix/unifont.c @@ -574,8 +574,8 @@ static void x11font_destroy(unifont *font) static void x11_alloc_subfont(struct x11font *xfont, int sfid) { Display *disp = xfont->disp; - char *derived_name = x11_guess_derived_font_name - (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); + char *derived_name = x11_guess_derived_font_name( + disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); xfont->fonts[sfid].allocated = true; sfree(derived_name); @@ -600,7 +600,7 @@ static bool x11font_has_glyph(unifont *font, wchar_t glyph) */ char sbstring[2]; int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, - sbstring, 2, "", NULL); + sbstring, 2, ""); if (sblen == 0 || !sbstring[0]) return false; /* not even in the charset */ @@ -691,10 +691,9 @@ static void x11font_cairo_setup( xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7; } - xfi->pixmap = XCreatePixmap - (disp, - GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), - xfi->pixwidth, xfi->pixheight, 1); + xfi->pixmap = XCreatePixmap( + disp, GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), + xfi->pixwidth, xfi->pixheight, 1); gcvals.foreground = WhitePixel(disp, widgetscr); gcvals.background = BlackPixel(disp, widgetscr); gcvals.font = xfi->xfs->fid; @@ -747,8 +746,8 @@ static void x11font_cairo_cache_glyph( } } xfi->glyphcache[glyphindex].bitmap = bitmap; - xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data - (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); + xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data( + bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); } static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, @@ -956,7 +955,7 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, */ char *sbstring = snewn(len+1, char); int sblen = wc_to_mb(xfont->real_charset, 0, string, len, - sbstring, len+1, ".", NULL); + sbstring, len+1, "."); x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, &xfont->fonts[sfid], xfont->disp, x, y, sbstring, sblen, shadowoffset, @@ -1644,8 +1643,7 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, * string to UTF-8. */ utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ - utflen = wc_to_mb(CS_UTF8, 0, string, len, - utfstring, len*6+1, ".", NULL); + utflen = wc_to_mb(CS_UTF8, 0, string, len, utfstring, len*6+1, "."); utfptr = utfstring; while (utflen > 0) { @@ -2227,9 +2225,9 @@ unifont *multifont_create(GtkWidget *widget, const char *name, if (font->want_fallback) { for (i = 0; i < lenof(unifont_types); i++) { if (unifont_types[i]->create_fallback) { - fallback = unifont_types[i]->create_fallback - (widget, font->height, wide, bold, - shadowoffset, shadowalways); + fallback = unifont_types[i]->create_fallback( + widget, font->height, wide, bold, + shadowoffset, shadowalways); if (fallback) break; } @@ -2574,7 +2572,7 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs, continue; /* we're filtering out this font */ } if (!info || !started || strnullcasecmp(currcs, info->charset) || - strnullcasecmp(currstyle, info->style)) { + strnullcasecmp(currstyle, info->style)) { /* * We've either finished a style/charset, or started a * new one, or both. @@ -2664,9 +2662,9 @@ static void unifontsel_set_filter_buttons(unifontsel_internal *fs) int i; for (i = 0; i < fs->n_filter_buttons; i++) { - int flagbit = GPOINTER_TO_INT(g_object_get_data - (G_OBJECT(fs->filter_buttons[i]), - "user-data")); + int flagbit = GPOINTER_TO_INT(g_object_get_data( + G_OBJECT(fs->filter_buttons[i]), + "user-data")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), !!(fs->filter_flags & flagbit)); } @@ -2680,8 +2678,8 @@ static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx, fontinfo *info = fs->selected; if (info) { - sizename = info->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + sizename = info->fontclass->scale_fontname( + GTK_WIDGET(fs->u.window), info->realname, fs->selsize); font = info->fontclass->create(GTK_WIDGET(fs->u.window), sizename ? sizename : info->realname, false, false, 0, 0); @@ -2868,9 +2866,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, */ assert(info->familyindex >= 0); treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); - gtk_tree_selection_select_path - (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), - treepath); + gtk_tree_selection_select_path( + gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), + treepath); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), treepath, NULL, false, 0.0, 0.0); success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), @@ -2892,9 +2890,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, if (info->style) { assert(info->styleindex >= 0); treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); - gtk_tree_selection_select_path - (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), - treepath); + gtk_tree_selection_select_path( + gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), + treepath); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), treepath, NULL, false, 0.0, 0.0); gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), @@ -2915,9 +2913,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, if (info->size) { assert(info->sizeindex >= 0); treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); - gtk_tree_selection_select_path - (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), - treepath); + gtk_tree_selection_select_path( + gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), + treepath); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), treepath, NULL, false, 0.0, 0.0); gtk_tree_path_free(treepath); @@ -3237,8 +3235,8 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, int flags; struct fontinfo_realname_find f; - newname = info->fontclass->canonify_fontname - (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true); + newname = info->fontclass->canonify_fontname( + GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true); f.realname = newname; f.flags = flags; @@ -3364,10 +3362,10 @@ unifontsel *unifontsel_new(const char *wintitle) fs->u.user_data = NULL; fs->u.window = GTK_WINDOW(gtk_dialog_new()); gtk_window_set_title(fs->u.window, wintitle); - fs->u.cancel_button = gtk_dialog_add_button - (GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); - fs->u.ok_button = gtk_dialog_add_button - (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK); + fs->u.cancel_button = gtk_dialog_add_button( + GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button( + GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK); gtk_widget_grab_default(fs->u.ok_button); /* @@ -3399,8 +3397,8 @@ unifontsel *unifontsel_new(const char *wintitle) w = table; #endif - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area - (GTK_DIALOG(fs->u.window))), + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area( + GTK_DIALOG(fs->u.window))), w, true, true, 0); label = gtk_label_new_with_mnemonic("_Font:"); @@ -3423,9 +3421,9 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false); gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); gtk_widget_show(w); - column = gtk_tree_view_column_new_with_attributes - ("Font", gtk_cell_renderer_text_new(), - "text", 0, (char *)NULL); + column = gtk_tree_view_column_new_with_attributes( + "Font", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), @@ -3474,9 +3472,9 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false); gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); gtk_widget_show(w); - column = gtk_tree_view_column_new_with_attributes - ("Style", gtk_cell_renderer_text_new(), - "text", 0, "sensitive", 3, "weight", 4, (char *)NULL); + column = gtk_tree_view_column_new_with_attributes( + "Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, "weight", 4, (char *)NULL); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), @@ -3532,9 +3530,9 @@ unifontsel *unifontsel_new(const char *wintitle) w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false); gtk_widget_show(w); - column = gtk_tree_view_column_new_with_attributes - ("Size", gtk_cell_renderer_text_new(), - "text", 0, (char *)NULL); + column = gtk_tree_view_column_new_with_attributes( + "Size", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), @@ -3741,8 +3739,8 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) */ fontname = unifont_do_prefix(fontname, &start, &end); for (i = start; i < end; i++) { - fontname2 = unifont_types[i]->canonify_fontname - (GTK_WIDGET(fs->u.window), fontname, &size, &flags, false); + fontname2 = unifont_types[i]->canonify_fontname( + GTK_WIDGET(fs->u.window), fontname, &size, &flags, false); if (fontname2) break; } @@ -3792,8 +3790,8 @@ char *unifontsel_get_name(unifontsel *fontsel) return NULL; if (fs->selected->size == 0) { - name = fs->selected->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + name = fs->selected->fontclass->scale_fontname( + GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); if (name) { char *ret = dupcat(fs->selected->fontclass->prefix, ":", name); sfree(name); diff --git a/code/unix/uppity.c b/code/unix/uppity.c index 84d332c0..11fead95 100644 --- a/code/unix/uppity.c +++ b/code/unix/uppity.c @@ -353,7 +353,7 @@ static void show_help(FILE *fp) " --rsakexkey KEY key for SSH-2 RSA key exchange " "(in SSH-1 format)\n" " --userkey KEY public key" - " acceptable for user authentication\n" + " acceptable for user authentication\n" " --sessiondir DIR cwd for session subprocess (default $HOME)\n" " --bannertext TEXT send TEXT as SSH-2 auth banner\n" " --bannerfile FILE send contents of FILE as SSH-2 auth " diff --git a/code/unix/utils/arm_arch_queries.c b/code/unix/utils/arm_arch_queries.c index cc3e4125..c3dc286b 100644 --- a/code/unix/utils/arm_arch_queries.c +++ b/code/unix/utils/arm_arch_queries.c @@ -17,10 +17,26 @@ bool platform_aes_neon_available(void) #elif defined HWCAP2_AES return getauxval(AT_HWCAP2) & HWCAP2_AES; #elif defined __APPLE__ - /* M1 macOS defines no optional sysctl flag indicating presence of - * the AES extension, which I assume to be because it's always - * present */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_AES"); + /* Older M1 macOS didn't provide this flag, but as far as I know + * implemented the crypto extension anyway, so treat 'feature + * missing' as 'implemented' */ + return res != SYSCTL_OFF; +#else + return false; +#endif +} + +bool platform_pmull_neon_available(void) +{ +#if defined HWCAP_PMULL + return getauxval(AT_HWCAP) & HWCAP_PMULL; +#elif defined HWCAP2_PMULL + return getauxval(AT_HWCAP2) & HWCAP2_PMULL; +#elif defined __APPLE__ + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_PMULL"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; #else return false; #endif @@ -33,8 +49,9 @@ bool platform_sha256_neon_available(void) #elif defined HWCAP2_SHA2 return getauxval(AT_HWCAP2) & HWCAP2_SHA2; #elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA256"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; #else return false; #endif @@ -47,8 +64,9 @@ bool platform_sha1_neon_available(void) #elif defined HWCAP2_SHA1 return getauxval(AT_HWCAP2) & HWCAP2_SHA1; #elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA1"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; #else return false; #endif @@ -61,7 +79,14 @@ bool platform_sha512_neon_available(void) #elif defined HWCAP2_SHA512 return getauxval(AT_HWCAP2) & HWCAP2_SHA512; #elif defined __APPLE__ - return test_sysctl_flag("hw.optional.armv8_2_sha512"); + /* There are two sysctl flags for this, apparently invented at + * different times. Try both, falling back to the older one. */ + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA512"); + if (res != SYSCTL_MISSING) + return res == SYSCTL_ON; + + res = test_sysctl_flag("hw.optional.armv8_2_sha512"); + return res == SYSCTL_ON; #else return false; #endif diff --git a/code/unix/utils/arm_arch_queries.h b/code/unix/utils/arm_arch_queries.h index bd055687..fa46c622 100644 --- a/code/unix/utils/arm_arch_queries.h +++ b/code/unix/utils/arm_arch_queries.h @@ -49,15 +49,19 @@ static inline u_long getauxval(int which) { return 0; } #endif /* defined __arm__ || defined __aarch64__ */ #if defined __APPLE__ -static inline bool test_sysctl_flag(const char *flagname) +typedef enum { SYSCTL_MISSING, SYSCTL_OFF, SYSCTL_ON } SysctlResult; + +static inline SysctlResult test_sysctl_flag(const char *flagname) { #if HAVE_SYSCTLBYNAME int value; size_t size = sizeof(value); - return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && - size == sizeof(value) && value != 0); + if (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && + size == sizeof(value)) { + return value != 0 ? SYSCTL_ON : SYSCTL_OFF; + } #else /* HAVE_SYSCTLBYNAME */ - return false; + return SYSCTL_MISSING; #endif /* HAVE_SYSCTLBYNAME */ } #endif /* defined __APPLE__ */ diff --git a/code/unix/utils/our_dialog.c b/code/unix/utils/our_dialog.c index 5b9995d4..0ca8a7d2 100644 --- a/code/unix/utils/our_dialog.c +++ b/code/unix/utils/our_dialog.c @@ -128,8 +128,8 @@ void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, gtk_box_pack_start(vbox, w, expand, fill, padding); #else - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, expand, fill, padding); + gtk_box_pack_start( + GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, expand, fill, padding); #endif } diff --git a/code/unix/window.c b/code/unix/window.c index 173943cb..18cce475 100644 --- a/code/unix/window.c +++ b/code/unix/window.c @@ -190,6 +190,24 @@ struct GtkFrontend { #endif int trust_sigil_w, trust_sigil_h; + /* + * Not every GDK backend can be relied on 100% to reply to a + * resize request in a timely manner. (In X11 it's all + * asynchronous and goes via the window manager, and if your + * window manager is seriously unwell, you'd rather not have + * terminal windows start becoming unusable as a knock-on effect, + * since those are just the thing you might need to use for + * emergency WM maintenance!) + * + * So when we ask GTK to resize our terminal window, we also set a + * 5-second timer, after which we'll regretfully conclude that a + * resize (or ConfigureNotify telling us no resize took place) is + * probably not going to happen after all. + */ + bool win_resize_pending, term_resize_notification_required; + long win_resize_timeout; + #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) + Seat seat; TermWin termwin; LogPolicy logpolicy; @@ -411,6 +429,7 @@ static const SeatVtable gtk_seat_vt = { .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, + .prompt_descriptions = gtk_seat_prompt_descriptions, .is_utf8 = gtk_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = gtk_seat_get_x_display, @@ -760,6 +779,10 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) inst->drawing_area_setup_called = true; if (inst->term) term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); + if (inst->term_resize_notification_required) + term_resize_request_completed(inst->term); + if (inst->win_resize_pending) + inst->win_resize_pending = false; if (!inst->drawing_area_setup_needed) return; @@ -1257,7 +1280,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug(" - Ctrl->: increase font size\n"); #endif - change_font_size(inst, +1); + if (!inst->win_resize_pending) + change_font_size(inst, +1); return true; } if (event->keyval == GDK_KEY_less && @@ -1265,7 +1289,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug(" - Ctrl-<: increase font size\n"); #endif - change_font_size(inst, -1); + if (!inst->win_resize_pending) + change_font_size(inst, -1); return true; } @@ -1650,10 +1675,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) */ guint new_keyval; GdkModifierType consumed; - if (gdk_keymap_translate_keyboard_state - (gdk_keymap_get_for_display(gdk_display_get_default()), - event->hardware_keycode, event->state & ~META_MANUAL_MASK, - 0, &new_keyval, NULL, NULL, &consumed)) { + if (gdk_keymap_translate_keyboard_state( + gdk_keymap_get_for_display(gdk_display_get_default()), + event->hardware_keycode, + event->state & ~META_MANUAL_MASK, + 0, &new_keyval, NULL, NULL, &consumed)) { ucsoutput[0] = '\033'; ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); #ifdef KEY_EVENT_DIAGNOSTICS @@ -1889,7 +1915,12 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->state & GDK_CONTROL_MASK) break; - end = 1 + format_small_keypad_key(output+1, inst->term, sk_key); + end = 1 + format_small_keypad_key( + output+1, inst->term, sk_key, event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, &consumed_meta_key); + if (consumed_meta_key) + start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS debug(" - small keypad key"); #endif @@ -1909,11 +1940,10 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) xkey = 'G'; goto arrow_key; arrow_key: consumed_meta_key = false; - end = 1 + format_arrow_key(output+1, inst->term, xkey, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, - &consumed_meta_key); + end = 1 + format_arrow_key( + output+1, inst->term, xkey, event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, &consumed_meta_key); if (consumed_meta_key) start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS @@ -1947,7 +1977,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) goto done; } - done: + done: if (end-start > 0) { if (special) { @@ -2481,10 +2511,20 @@ static void gtkwin_deny_term_resize(void *vctx) drawing_area_setup_simple(inst); } -static void gtkwin_request_resize(TermWin *tw, int w, int h) +static void gtkwin_timer(void *vctx, unsigned long now) { - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + GtkFrontend *inst = (GtkFrontend *)vctx; + + if (inst->win_resize_pending && now == inst->win_resize_timeout) { + if (inst->term_resize_notification_required) + term_resize_request_completed(inst->term); + inst->win_resize_pending = false; + } +} +static void request_resize_internal(GtkFrontend *inst, bool from_terminal, + int w, int h) +{ #if GTK_CHECK_VERSION(2,0,0) /* * Initial check: don't even try to resize a window if it's in one @@ -2517,8 +2557,10 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) GdkWindowState state = gdk_window_get_state(gdkwin); if (state & (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN | -#if GTK_CHECK_VERSION(3,0,0) +#if GTK_CHECK_VERSION(3,10,0) GDK_WINDOW_STATE_TILED | +#endif +#if GTK_CHECK_VERSION(3,22,23) GDK_WINDOW_STATE_TOP_TILED | GDK_WINDOW_STATE_RIGHT_TILED | GDK_WINDOW_STATE_BOTTOM_TILED | @@ -2526,6 +2568,7 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) #endif 0)) { queue_toplevel_callback(gtkwin_deny_term_resize, inst); + term_resize_request_completed(inst->term); return; } } @@ -2614,6 +2657,16 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) #endif + inst->win_resize_pending = true; + inst->term_resize_notification_required = from_terminal; + inst->win_resize_timeout = schedule_timer( + WIN_RESIZE_TIMEOUT, gtkwin_timer, inst); +} + +static void gtkwin_request_resize(TermWin *tw, int w, int h) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + request_resize_internal(inst, true, w, h); } #if GTK_CHECK_VERSION(3,0,0) @@ -3010,9 +3063,8 @@ static void gtkwin_clip_write( state->pasteout_data = snewn(len*6, char); state->pasteout_data_len = len*6; state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, - NULL, NULL); + data, len, state->pasteout_data, + state->pasteout_data_len, NULL); if (state->pasteout_data_len == 0) { sfree(state->pasteout_data); state->pasteout_data = NULL; @@ -4797,9 +4849,9 @@ static void after_change_settings_dialog(void *vctx, int retval) conf_get_int(newconf, CONF_window_border) || need_size) { set_geom_hints(inst); - win_request_resize(&inst->termwin, - conf_get_int(newconf, CONF_width), - conf_get_int(newconf, CONF_height)); + request_resize_internal(inst, false, + conf_get_int(newconf, CONF_width), + conf_get_int(newconf, CONF_height)); } else { /* * The above will have caused a call to term_size() for @@ -4872,8 +4924,8 @@ static void change_font_size(GtkFrontend *inst, int increment) } set_geom_hints(inst); - win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); + request_resize_internal(inst, false, conf_get_int(inst->conf, CONF_width), + conf_get_int(inst->conf, CONF_height)); term_invalidate(inst->term); gtk_widget_queue_draw(inst->area); @@ -5120,9 +5172,16 @@ static void start_backend(GtkFrontend *inst) conf_get_bool(inst->conf, CONF_tcp_keepalives)); if (error) { - seat_connection_fatal(&inst->seat, - "Unable to open connection to %s:\n%s", - conf_dest(inst->conf), error); + if (cmdline_tooltype & TOOLTYPE_NONNETWORK) { + /* Special case for pterm. */ + seat_connection_fatal(&inst->seat, + "Unable to open terminal:\n%s", + error); + } else { + seat_connection_fatal(&inst->seat, + "Unable to open connection to %s:\n%s", + conf_dest(inst->conf), error); + } sfree(error); inst->exited = true; return; diff --git a/code/utils/CMakeLists.txt b/code/utils/CMakeLists.txt index 2e1296eb..6ef33c8d 100644 --- a/code/utils/CMakeLists.txt +++ b/code/utils/CMakeLists.txt @@ -2,16 +2,21 @@ add_sources_from_current_dir(utils antispoof.c backend_socket_log.c base64_decode_atom.c + base64_decode.c base64_encode_atom.c + base64_encode.c + base64_valid.c bufchain.c buildinfo.c burnstr.c + cert-expr.c chomp.c cmdline_get_passwd_input_state_new.c conf.c conf_dest.c conf_launchable.c ctrlparse.c + ctrlset_normalise.c debug.c decode_utf8.c decode_utf8_to_wchar.c @@ -24,30 +29,34 @@ add_sources_from_current_dir(utils encode_utf8.c encode_wide_string_as_utf8.c fgetline.c + host_ca_new_free.c host_strchr.c host_strchr_internal.c host_strcspn.c host_strduptrim.c host_strrchr.c + key_components.c log_proxy_stderr.c make_spr_sw_abort_static.c marshal.c memory.c memxor.c - null_lp.c - nullseat.c nullstrcmp.c out_of_memory.c parse_blocksize.c + percent_decode.c + percent_encode.c prompts.c ptrlen.c read_file_into.c seat_connection_fatal.c + seat_dialog_text.c sessprep.c sk_free_peer_info.c smemclr.c smemeq.c spr_get_error_message.c + ssh_key_clone.c ssh2_pick_fingerprint.c sshutils.c strbuf.c @@ -59,6 +68,7 @@ add_sources_from_current_dir(utils version.c wcwidth.c wildcard.c + wordwrap.c write_c_string_literal.c x11authfile.c x11authnames.c diff --git a/code/utils/base64_decode.c b/code/utils/base64_decode.c new file mode 100644 index 00000000..4f00f196 --- /dev/null +++ b/code/utils/base64_decode.c @@ -0,0 +1,37 @@ +#include "misc.h" + +void base64_decode_bs(BinarySink *bs, ptrlen input) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + + while (get_avail(src)) { + char b64atom[4]; + unsigned char binatom[3]; + + for (size_t i = 0; i < 4 ;) { + char c = get_byte(src); + if (get_err(src)) + c = '='; + if (c == '\n' || c == '\r') + continue; + b64atom[i++] = c; + } + + put_data(bs, binatom, base64_decode_atom(b64atom, binatom)); + } +} + +void base64_decode_fp(FILE *fp, ptrlen input) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + base64_decode_bs(BinarySink_UPCAST(&ss), input); +} + +strbuf *base64_decode_sb(ptrlen input) +{ + strbuf *sb = strbuf_new_nm(); + base64_decode_bs(BinarySink_UPCAST(sb), input); + return sb; +} diff --git a/code/utils/base64_encode.c b/code/utils/base64_encode.c new file mode 100644 index 00000000..3db85571 --- /dev/null +++ b/code/utils/base64_encode.c @@ -0,0 +1,40 @@ +#include "misc.h" + +void base64_encode_bs(BinarySink *bs, ptrlen input, int cpl) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + int linelen = 0; + + while (get_avail(src)) { + size_t n = get_avail(src) < 3 ? get_avail(src) : 3; + ptrlen binatom = get_data(src, n); + + char b64atom[4]; + base64_encode_atom(binatom.ptr, binatom.len, b64atom); + for (size_t i = 0; i < 4; i++) { + if (cpl > 0 && linelen >= cpl) { + linelen = 0; + put_byte(bs, '\n'); + } + put_byte(bs, b64atom[i]); + linelen++; + } + } + if (cpl > 0) + put_byte(bs, '\n'); +} + +void base64_encode_fp(FILE *fp, ptrlen input, int cpl) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + base64_encode_bs(BinarySink_UPCAST(&ss), input, cpl); +} + +strbuf *base64_encode_sb(ptrlen input, int cpl) +{ + strbuf *sb = strbuf_new_nm(); + base64_encode_bs(BinarySink_UPCAST(sb), input, cpl); + return sb; +} diff --git a/code/utils/base64_valid.c b/code/utils/base64_valid.c new file mode 100644 index 00000000..8eb1f3a0 --- /dev/null +++ b/code/utils/base64_valid.c @@ -0,0 +1,54 @@ +/* + * Determine whether a string looks like valid base64-encoded data. + */ + +#include "misc.h" + +static inline bool valid_char_main(char c) +{ + return ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '+' || c == '/'); +} + +bool base64_valid(ptrlen data) +{ + size_t blocklen = 0, nequals = 0; + + for (size_t i = 0; i < data.len; i++) { + char c = ((const char *)data.ptr)[i]; + + if (c == '\n' || c == '\r') + continue; + + if (valid_char_main(c)) { + if (nequals) /* can't go back to data after = */ + return false; + blocklen++; + if (blocklen == 4) + blocklen = 0; + continue; + } + + if (c == '=') { + if (blocklen == 0 && nequals) /* started a fresh block */ + return false; + + nequals++; + blocklen++; + if (blocklen == 4) { + if (nequals > 2) + return false; /* nonsensical final block */ + blocklen = 0; + } + continue; + } + + return false; /* bad character */ + } + + if (blocklen == 0 || blocklen == 2 || blocklen == 3) + return true; /* permit eliding the trailing = */ + return false; +} diff --git a/code/utils/buildinfo.c b/code/utils/buildinfo.c index f8225564..5a837e01 100644 --- a/code/utils/buildinfo.c +++ b/code/utils/buildinfo.c @@ -44,14 +44,14 @@ char *buildinfo(const char *newline) * cases, two different compiler versions have the same _MSC_VER * value, and have to be distinguished by _MSC_FULL_VER. */ -#elif PUTTY_CAC && _MSC_VER == 1930 - put_fmt(buf, " 2022 (17.0)"); -#elif PUTTY_CAC && _MSC_VER == 1931 - put_fmt(buf, " 2022 (17.1)"); -#elif PUTTY_CAC && _MSC_VER == 1932 +#elif _MSC_VER == 1933 + put_fmt(buf, " 2022 (17.3)"); +#elif _MSC_VER == 1932 put_fmt(buf, " 2022 (17.2)"); -#elif PUTTY_CAC && _MSC_VER == 1933 - put_fmt(buf, " 2022 (17.3)"); +#elif _MSC_VER == 1931 + put_fmt(buf, " 2022 (17.1)"); +#elif _MSC_VER == 1930 + put_fmt(buf, " 2022 (17.0)"); #elif _MSC_VER == 1929 && _MSC_FULL_VER >= 192930100 put_fmt(buf, " 2019 (16.11)"); #elif _MSC_VER == 1929 diff --git a/code/utils/cert-expr.c b/code/utils/cert-expr.c new file mode 100644 index 00000000..b22b380c --- /dev/null +++ b/code/utils/cert-expr.c @@ -0,0 +1,968 @@ +/* + * Parser for the boolean expression language used to configure what + * host names an OpenSSH certificate will be trusted to sign for. + */ + +/* + +Language specification +====================== + +Outer lexical layer: the input expression is broken up into tokens, +with any whitespace between them discarded and ignored. The following +tokens are special: + + ( ) && || ! + +and the remaining token type is an 'atom', which is any non-empty +sequence of characters from the following set: + + ABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyz + 0123456789 + .-_*?[]/: + +Inner lexical layer: once the boundaries of an 'atom' token have been +determined by the outer lex layer, each atom is further classified +into one of the following subtypes: + + - If it contains no ':' or '/', it's taken to be a wildcard matching + hostnames, e.g. "*.example.com". + + - If it begins with 'port:' followed by digits, it's taken to be a + single port number specification, e.g. "port:22". + + - If it begins with 'port:' followed by two digit sequences separated + by '-', it's taken to be a port number range, e.g. "port:0-1023". + + - Any other atom is reserved for future expansion. (See Rationale.) + +Syntax layer: all of those types of atom are interpreted as predicates +applied to the (hostname, port) data configured for the SSH connection +for which the certificate is being validated. + +Wildcards are handled using the syntax in wildcard.c. The dot- +separated structure of hostnames is thus not special; the '*' in +"*.example.com" will match any number of subdomains under example.com. + +More complex boolean expressions can be made by combining those +predicates using the boolean operators and parentheses, in the obvious +way: && and || are infix operators representing logical AND and OR, ! +is a prefix operator representing logical NOT, and parentheses +indicate grouping. + +Each of && and || can associate freely with itself (that is, you can +write "a && b && c" without having to parenthesise one or the other +subexpression). But they are forbidden to associate with _each other_. +That is, if you write "a && b || c" or "a || b && c", it's a syntax +error, and you must add parentheses to indicate which operator was +intended to have the higher priority. + +Rationale +========= + +Atoms: restrictions +------------------- + +The characters permitted in the 'atom' token don't include \, even +though it's a special character defined by wildcard.c. That's because +in this restricted context wildcards will never need it: no hostname +contains a literal \, and neither does any hostname contain a literal +instance of any of the wildcard characters that wildcard.c allows you +to use \ to escape. + +Atoms: future extension +----------------------- + +The specification of the 'atom' token is intended to leave space for +more than one kind of future extension. + +Most obviously, additional special predicates similar to "port:", with +different disambiguating prefixes. I don't know what things of that +kind we might need, but space is left for them just in case. + +Also, the unused '/' in the permitted-characters spec is intended to +leave open the possibility of allowing certificate acceptance to be +based on IP address, because the usual CIDR syntax for specifying IP +ranges (e.g. "192.168.1.0/24" or "2345:6789:abcd:ef01::/128") would be +lexed as a single atom under these rules. + +For the moment, certificate acceptance rules based on IP address are +not supported, because it's not clear what the semantics ought to be. +There are two problems with using IP addresses for this purpose: + + 1. Sometimes they come from the DNS, which means you can't trust + them. The whole idea of SSH is to end-to-end authenticate the host + key against only the input given _by the user_ to the client. Any + additional data provided by the network, such as the result of a + DNS lookup, is suspect. + + On the other hand, sometimes the IP address *is* part of the user + input, because the user can provide an IP address rather than a + hostname as the intended connection destination. So there are two + kinds of IP address, and they should very likely be treated + differently. + + 2. Sometimes the server's IP address is not even *known* by the + client, if you're connecting via a proxy and leaving DNS lookups + to the proxy. + +So, what should a boolean expression do if it's asked to accept or +reject based on an IP address, and the IP address is unknown or +untrustworthy? I'm not sure, and therefore, in the initial version of +this expression system, I haven't implemented them at all. + +But the syntax is still available for a future extension to use, if we +come up with good answers to these questions. + +(One possibility would be to evaluate the whole expression in Kleene +three-valued logic, so that every subexpression has the possible +answers TRUE, FALSE and UNKNOWN. If a definite IP address is not +available, IP address predicates evaluate to UNKNOWN. Then, once the +expression as a whole is evaluated, fail closed, by interpreting +UNKNOWN as 'reject'. The effect would be that a positive _or_ negative +constraint on the IP address would cause rejection if the IP address +is not reliably known, because once the predicate itself has returned +UNKNOWN, negating it still gives UNKNOWN. The only way you could still +accept a certificate in that situation would be if the overall +structure of the expression meant that the test of the IP address +couldn't affect the result anyway, e.g. if it was ANDed with another +subexpression that definitely evaluated to FALSE, or ORed with one +that evaluated to TRUE. This system seems conceptually elegant to me, +but the argument against it is that it's complicated and +counterintuitive, which is not a property you want in something a user +is writing for security purposes!) + +Operator precedence +------------------- + +Why did I choose to make && and || refuse to associate with each +other, instead of applying the usual C precedence rule that && beats +||? Because I think the C precedence rule is essentially arbitrary, in +the sense that when people are writing boolean expressions in practice +based on predicates from the rest of their program, it's about equally +common to want to nest an && within an || and vice versa. So the +default precedence rule only gives the user what they actually wanted +about 50% of the time, and leads to absent-minded errors about as +often as it conveniently allows you to omit a pair of parens. + +With my mathematician hat on, it's not so arbitrary. I agree that if +you're *going* to give || and && a relative priority then it makes +more sense to make && the higher-priority one, because if you're +thinking algebraically, && is more multiplicative and || is more +additive. But the pure-maths contexts in which that's convenient have +nothing to do with general boolean expressions in if statements. + +This boolean syntax is still close enough to that of C and its +derivatives to allow easy enough expression interchange (not counting +the fact that atoms would need rewriting). Any boolean expression +structure accepted by this syntax is also legal C and means the same +thing; any expression structure accepted by C is either legal and +equivalent in this syntax, or will fail with an error. In no case is +anything accepted but mapped to a different meaning. + + */ + +#include "putty.h" + +typedef enum Token { + TOK_LPAR, TOK_RPAR, + TOK_AND, TOK_OR, TOK_NOT, + TOK_ATOM, + TOK_END, TOK_ERROR +} Token; + +static inline bool is_space(char c) +{ + return (c == ' ' || c == '\n' || c == '\r' || c == '\t' || + c == '\f' || c == '\v'); +} + +static inline bool is_operator_char(char c) +{ + return (c == '(' || c == ')' || c == '&' || c == '|' || c == '!'); +} + +static inline bool is_atom_char(char c) +{ + return (('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + ('0' <= c && c <= '9') || + c == '.' || c == '-' || c == '_' || c == '*' || c == '?' || + c == '[' || c == ']' || c == '/' || c == ':'); +} + +static Token lex(ptrlen *text, ptrlen *token, char **err) +{ + const char *p = text->ptr, *e = p + text->len; + Token type = TOK_ERROR; + + /* Skip whitespace */ + while (p < e && is_space(*p)) + p++; + + const char *start = p; + + if (!(p < e)) { + type = TOK_END; + goto out; + } + + if (is_operator_char(*p)) { + /* Match boolean-expression tokens */ + static const struct operator { + ptrlen text; + Token type; + } operators[] = { + {PTRLEN_DECL_LITERAL("("), TOK_LPAR}, + {PTRLEN_DECL_LITERAL(")"), TOK_RPAR}, + {PTRLEN_DECL_LITERAL("&&"), TOK_AND}, + {PTRLEN_DECL_LITERAL("||"), TOK_OR}, + {PTRLEN_DECL_LITERAL("!"), TOK_NOT}, + }; + + for (size_t i = 0; i < lenof(operators); i++) { + const struct operator *op = &operators[i]; + if (e - p >= op->text.len && + ptrlen_eq_ptrlen(op->text, make_ptrlen(p, op->text.len))) { + p += op->text.len; + type = op->type; + goto out; + } + } + + /* + * Report an error if one of the operator characters is used + * in a way that doesn't match something in that table (e.g. a + * single &). + */ + p++; + type = TOK_ERROR; + *err = dupstr("unrecognised boolean operator"); + goto out; + } else if (is_atom_char(*p)) { + /* + * Match an 'atom' token, which is any non-empty sequence of + * characters from the combined set that allows hostname + * wildcards, IP address ranges and special predicates like + * port numbers. + */ + do { + p++; + } while (p < e && is_atom_char(*p)); + + type = TOK_ATOM; + goto out; + } else { + /* + * Otherwise, report an error. + */ + p++; + type = TOK_ERROR; + *err = dupstr("unexpected character in expression"); + goto out; + } + + out: + *token = make_ptrlen(start, p - start); + text->ptr = p; + text->len = e - p; + return type; +} + +typedef enum Operator { + OP_AND, OP_OR, OP_NOT, + OP_HOSTNAME_WC, OP_PORT_RANGE +} Operator; + +typedef struct ExprNode ExprNode; +struct ExprNode { + Operator op; + ptrlen text; + union { + struct { + /* OP_AND, OP_OR */ + ExprNode **subexprs; + size_t nsubexprs; + }; + struct { + /* OP_NOT */ + ExprNode *subexpr; + }; + struct { + /* OP_HOSTNAME_WC */ + char *wc; + }; + struct { + /* OP_PORT_RANGE */ + unsigned lo, hi; /* both inclusive */ + }; + }; +}; + +static ExprNode *exprnode_new(Operator op, ptrlen text) +{ + ExprNode *en = snew(ExprNode); + memset(en, 0, sizeof(*en)); + en->op = op; + en->text = text; + return en; +} + +static void exprnode_free(ExprNode *en) +{ + switch (en->op) { + case OP_AND: + case OP_OR: + for (size_t i = 0; i < en->nsubexprs; i++) + exprnode_free(en->subexprs[i]); + sfree(en->subexprs); + break; + case OP_NOT: + exprnode_free(en->subexpr); + break; + case OP_HOSTNAME_WC: + sfree(en->wc); + break; + case OP_PORT_RANGE: + break; + default: + unreachable("unhandled node type in exprnode_free"); + } + + sfree(en); +} + +static unsigned ptrlen_to_port_number(ptrlen input) +{ + unsigned val = 0; + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) { + assert('0' <= *p && *p <= '9'); /* expect parser to have checked */ + val = 10 * val + (*p - '0'); + if (val >= 65536) + val = 65536; /* normalise 'too large' to avoid integer overflow */ + } + return val; +} + +typedef struct ParserState ParserState; +struct ParserState { + ptrlen currtext; + Token tok; + ptrlen toktext; + char *err; + ptrlen errloc; +}; + +static void error(ParserState *ps, char *errtext, ptrlen errloc) +{ + if (!ps->err) { + ps->err = errtext; + ps->errloc = errloc; + } else { + sfree(errtext); + } +} + +static void advance(ParserState *ps) +{ + char *err = NULL; + ps->tok = lex(&ps->currtext, &ps->toktext, &err); + if (ps->tok == TOK_ERROR) + error(ps, err, ps->toktext); +} + +static ExprNode *parse_atom(ParserState *ps); +static ExprNode *parse_expr(ParserState *ps); + +static bool atom_is_hostname_wc(ptrlen toktext) +{ + return !ptrlen_contains(toktext, ":/"); +} + +static ExprNode *parse_atom(ParserState *ps) +{ + if (ps->tok == TOK_LPAR) { + ptrlen openpar = ps->toktext; + advance(ps); /* eat the ( */ + + ExprNode *subexpr = parse_expr(ps); + if (!subexpr) + return NULL; + + if (ps->tok != TOK_RPAR) { + error(ps, dupstr("expected ')' after parenthesised subexpression"), + subexpr->text); + exprnode_free(subexpr); + return NULL; + } + + ptrlen closepar = ps->toktext; + advance(ps); /* eat the ) */ + + /* We can reuse the existing AST node, but we need to extend + * its bounds within the input expression to include the + * parentheses */ + subexpr->text = make_ptrlen_startend( + openpar.ptr, ptrlen_end(closepar)); + return subexpr; + } + + if (ps->tok == TOK_NOT) { + ptrlen notloc = ps->toktext; + advance(ps); /* eat the ! */ + + ExprNode *subexpr = parse_atom(ps); + if (!subexpr) + return NULL; + + ExprNode *en = exprnode_new( + OP_NOT, make_ptrlen_startend( + notloc.ptr, ptrlen_end(subexpr->text))); + en->subexpr = subexpr; + return en; + } + + if (ps->tok == TOK_ATOM) { + if (atom_is_hostname_wc(ps->toktext)) { + /* Hostname wildcard. */ + ExprNode *en = exprnode_new(OP_HOSTNAME_WC, ps->toktext); + en->wc = mkstr(ps->toktext); + advance(ps); + return en; + } + + ptrlen tail; + if (ptrlen_startswith(ps->toktext, PTRLEN_LITERAL("port:"), &tail)) { + /* Port number (single or range). */ + unsigned lo, hi; + char *minus; + static const char DIGITS[] = "0123456789\0"; + bool parse_ok = false; + + if (tail.len > 0 && ptrlen_contains_only(tail, DIGITS)) { + lo = ptrlen_to_port_number(tail); + if (lo >= 65536) { + error(ps, dupstr("port number too large"), tail); + return NULL; + } + hi = lo; + parse_ok = true; + } else if ((minus = memchr(tail.ptr, '-', tail.len)) != NULL) { + ptrlen pl_lo = make_ptrlen_startend(tail.ptr, minus); + ptrlen pl_hi = make_ptrlen_startend(minus+1, ptrlen_end(tail)); + if (pl_lo.len > 0 && ptrlen_contains_only(pl_lo, DIGITS) && + pl_hi.len > 0 && ptrlen_contains_only(pl_hi, DIGITS)) { + + lo = ptrlen_to_port_number(pl_lo); + if (lo >= 65536) { + error(ps, dupstr("port number too large"), pl_lo); + return NULL; + } + + hi = ptrlen_to_port_number(pl_hi); + if (hi >= 65536) { + error(ps, dupstr("port number too large"), pl_hi); + return NULL; + } + + if (hi < lo) { + error(ps, dupstr("port number range is backwards"), + make_ptrlen_startend(pl_lo.ptr, + ptrlen_end(pl_hi))); + return NULL; + } + + parse_ok = true; + } + } + + if (!parse_ok) { + error(ps, dupstr("unable to parse port number specification"), + ps->toktext); + return NULL; + } + + + ExprNode *en = exprnode_new(OP_PORT_RANGE, ps->toktext); + en->lo = lo; + en->hi = hi; + advance(ps); + return en; + } + } + + error(ps, dupstr("expected a predicate or a parenthesised subexpression"), + ps->toktext); + return NULL; +} + +static ExprNode *parse_expr(ParserState *ps) +{ + ExprNode *subexpr = parse_atom(ps); + if (!subexpr) + return NULL; + + if (ps->tok != TOK_AND && ps->tok != TOK_OR) + return subexpr; + + Token operator = ps->tok; + ExprNode *en = exprnode_new(ps->tok == TOK_AND ? OP_AND : OP_OR, + subexpr->text); + size_t subexprs_size = 0; + + sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); + en->subexprs[en->nsubexprs++] = subexpr; + + while (true) { + advance(ps); /* eat the operator */ + + subexpr = parse_atom(ps); + if (!subexpr) { + exprnode_free(en); + return NULL; + } + sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); + en->subexprs[en->nsubexprs++] = subexpr; + en->text = make_ptrlen_startend( + en->text.ptr, ptrlen_end(subexpr->text)); + + if (ps->tok != TOK_AND && ps->tok != TOK_OR) + return en; + + if (ps->tok != operator) { + error(ps, dupstr("expected parentheses to disambiguate && and || " + "on either side of expression"), subexpr->text); + exprnode_free(en); + return NULL; + } + } +} + +static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc) +{ + ParserState ps[1]; + ps->currtext = expr; + ps->err = NULL; + advance(ps); + + ExprNode *en = parse_expr(ps); + if (en && ps->tok != TOK_END) { + error(ps, dupstr("unexpected text at end of expression"), + make_ptrlen_startend(ps->toktext.ptr, ptrlen_end(expr))); + exprnode_free(en); + en = NULL; + } + + if (!en) { + if (error_msg) + *error_msg = ps->err; + else + sfree(ps->err); + if (error_loc) + *error_loc = ps->errloc; + return NULL; + } + + return en; +} + +static bool eval(ExprNode *en, const char *hostname, unsigned port) +{ + switch (en->op) { + case OP_AND: + for (size_t i = 0; i < en->nsubexprs; i++) + if (!eval(en->subexprs[i], hostname, port)) + return false; + return true; + + case OP_OR: + for (size_t i = 0; i < en->nsubexprs; i++) + if (eval(en->subexprs[i], hostname, port)) + return true; + return false; + + case OP_NOT: + return !eval(en->subexpr, hostname, port); + + case OP_HOSTNAME_WC: + return wc_match(en->wc, hostname); + + case OP_PORT_RANGE: + return en->lo <= port && port <= en->hi; + + default: + unreachable("unhandled node type in eval"); + } +} + +bool cert_expr_match_str(const char *expression, + const char *hostname, unsigned port) +{ + ExprNode *en = parse(ptrlen_from_asciz(expression), NULL, NULL); + if (!en) + return false; + + bool matched = eval(en, hostname, port); + exprnode_free(en); + return matched; +} + +bool cert_expr_valid(const char *expression, + char **error_msg, ptrlen *error_loc) +{ + ExprNode *en = parse(ptrlen_from_asciz(expression), error_msg, error_loc); + if (en) { + exprnode_free(en); + return true; + } else { + return false; + } +} + +struct CertExprBuilder { + char **wcs; + size_t nwcs, wcsize; +}; + +CertExprBuilder *cert_expr_builder_new(void) +{ + CertExprBuilder *eb = snew(CertExprBuilder); + eb->wcs = NULL; + eb->nwcs = eb->wcsize = 0; + return eb; +} + +void cert_expr_builder_free(CertExprBuilder *eb) +{ + for (size_t i = 0; i < eb->nwcs; i++) + sfree(eb->wcs[i]); + sfree(eb->wcs); + sfree(eb); +} + +void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard) +{ + /* Check this wildcard is lexically valid as an atom */ + ptrlen orig = ptrlen_from_asciz(wildcard), pl = orig; + ptrlen toktext; + char *err; + Token tok = lex(&pl, &toktext, &err); + if (!(tok == TOK_ATOM && + toktext.ptr == orig.ptr && + toktext.len == orig.len && + atom_is_hostname_wc(toktext))) { + if (tok == TOK_ERROR) + sfree(err); + return; + } + + sgrowarray(eb->wcs, eb->wcsize, eb->nwcs); + eb->wcs[eb->nwcs++] = mkstr(orig); +} + +char *cert_expr_expression(CertExprBuilder *eb) +{ + strbuf *sb = strbuf_new(); + for (size_t i = 0; i < eb->nwcs; i++) { + if (i) + put_dataz(sb, " || "); + put_dataz(sb, eb->wcs[i]); + } + return strbuf_to_str(sb); +} + +#ifdef TEST + +void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } + +static void exprnode_dump(BinarySink *bs, ExprNode *en, const char *origtext) +{ + put_fmt(bs, "(%zu:%zu ", + (size_t)((const char *)en->text.ptr - origtext), + (size_t)((const char *)ptrlen_end(en->text) - origtext)); + switch (en->op) { + case OP_AND: + case OP_OR: + put_dataz(bs, en->op == OP_AND ? "and" : "or"); + for (size_t i = 0; i < en->nsubexprs; i++) { + put_byte(bs, ' '); + exprnode_dump(bs, en->subexprs[i], origtext); + } + break; + case OP_NOT: + put_dataz(bs, "not "); + exprnode_dump(bs, en->subexpr, origtext); + break; + case OP_HOSTNAME_WC: + put_dataz(bs, "host-wc '"); + put_dataz(bs, en->wc); + put_byte(bs, '\''); + break; + case OP_PORT_RANGE: + put_fmt(bs, "port-range %u %u", en->lo, en->hi); + break; + default: + unreachable("unhandled node type in exprnode_dump"); + } + put_byte(bs, ')'); +} + +static const struct ParseTest { + const char *file; + int line; + const char *expr, *output; +} parsetests[] = { +#define T(expr_, output_) { \ + .file=__FILE__, .line=__LINE__, .expr=expr_, .output=output_} + + T("*.example.com", "(0:13 host-wc '*.example.com')"), + T("port:0", "(0:6 port-range 0 0)"), + T("port:22", "(0:7 port-range 22 22)"), + T("port:22-22", "(0:10 port-range 22 22)"), + T("port:65535", "(0:10 port-range 65535 65535)"), + T("port:0-1023", "(0:11 port-range 0 1023)"), + + T("&", "ERR:0:1:unrecognised boolean operator"), + T("|", "ERR:0:1:unrecognised boolean operator"), + T(";", "ERR:0:1:unexpected character in expression"), + T("port:", "ERR:0:5:unable to parse port number specification"), + T("port:abc", "ERR:0:8:unable to parse port number specification"), + T("port:65536", "ERR:5:10:port number too large"), + T("port:65536-65537", "ERR:5:10:port number too large"), + T("port:0-65536", "ERR:7:12:port number too large"), + T("port:23-22", "ERR:5:10:port number range is backwards"), + + T("a", "(0:1 host-wc 'a')"), + T("(a)", "(0:3 host-wc 'a')"), + T("((a))", "(0:5 host-wc 'a')"), + T(" (\n(\ra\t)\f)\v", "(1:10 host-wc 'a')"), + T("a&&b", "(0:4 and (0:1 host-wc 'a') (3:4 host-wc 'b'))"), + T("a||b", "(0:4 or (0:1 host-wc 'a') (3:4 host-wc 'b'))"), + T("a&&b&&c", "(0:7 and (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), + T("a||b||c", "(0:7 or (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), + T("a&&(b||c)", "(0:9 and (0:1 host-wc 'a') (3:9 or (4:5 host-wc 'b') (7:8 host-wc 'c')))"), + T("a||(b&&c)", "(0:9 or (0:1 host-wc 'a') (3:9 and (4:5 host-wc 'b') (7:8 host-wc 'c')))"), + T("(a&&b)||c", "(0:9 or (0:6 and (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), + T("(a||b)&&c", "(0:9 and (0:6 or (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), + T("!a&&b", "(0:5 and (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), + T("a&&!b&&c", "(0:8 and (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), + T("!a||b", "(0:5 or (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), + T("a||!b||c", "(0:8 or (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), + + T("", "ERR:0:0:expected a predicate or a parenthesised subexpression"), + T("a &&", "ERR:4:4:expected a predicate or a parenthesised subexpression"), + T("a ||", "ERR:4:4:expected a predicate or a parenthesised subexpression"), + T("a b c d", "ERR:2:7:unexpected text at end of expression"), + T("(", "ERR:1:1:expected a predicate or a parenthesised subexpression"), + T("(a", "ERR:1:2:expected ')' after parenthesised subexpression"), + T("(a b", "ERR:1:2:expected ')' after parenthesised subexpression"), + T("a&&b&&c||d||e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), + T("a||b||c&&d&&e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), + T("!", "ERR:1:1:expected a predicate or a parenthesised subexpression"), + + T("!a", "(0:2 not (1:2 host-wc 'a'))"), + +#undef T +}; + +static const struct EvalTest { + const char *file; + int line; + const char *expr; + const char *host; + unsigned port; + bool output; +} evaltests[] = { +#define T(expr_, host_, port_, output_) { \ + .file=__FILE__, .line=__LINE__, \ + .expr=expr_, .host=host_, .port=port_, .output=output_} + + T("*.example.com", "hostname.example.com", 22, true), + T("*.example.com", "hostname.example.org", 22, false), + T("*.example.com", "hostname.dept.example.com", 22, true), + T("*.example.com && port:22", "hostname.example.com", 21, false), + T("*.example.com && port:22", "hostname.example.com", 22, true), + T("*.example.com && port:22", "hostname.example.com", 23, false), + T("*.example.com && port:22-24", "hostname.example.com", 21, false), + T("*.example.com && port:22-24", "hostname.example.com", 22, true), + T("*.example.com && port:22-24", "hostname.example.com", 23, true), + T("*.example.com && port:22-24", "hostname.example.com", 24, true), + T("*.example.com && port:22-24", "hostname.example.com", 25, false), + + T("*a* && *b* && *c*", "", 22, false), + T("*a* && *b* && *c*", "a", 22, false), + T("*a* && *b* && *c*", "b", 22, false), + T("*a* && *b* && *c*", "c", 22, false), + T("*a* && *b* && *c*", "ab", 22, false), + T("*a* && *b* && *c*", "ac", 22, false), + T("*a* && *b* && *c*", "bc", 22, false), + T("*a* && *b* && *c*", "abc", 22, true), + + T("*a* || *b* || *c*", "", 22, false), + T("*a* || *b* || *c*", "a", 22, true), + T("*a* || *b* || *c*", "b", 22, true), + T("*a* || *b* || *c*", "c", 22, true), + T("*a* || *b* || *c*", "ab", 22, true), + T("*a* || *b* || *c*", "ac", 22, true), + T("*a* || *b* || *c*", "bc", 22, true), + T("*a* || *b* || *c*", "abc", 22, true), + + T("*a* && !*b* && *c*", "", 22, false), + T("*a* && !*b* && *c*", "a", 22, false), + T("*a* && !*b* && *c*", "b", 22, false), + T("*a* && !*b* && *c*", "c", 22, false), + T("*a* && !*b* && *c*", "ab", 22, false), + T("*a* && !*b* && *c*", "ac", 22, true), + T("*a* && !*b* && *c*", "bc", 22, false), + T("*a* && !*b* && *c*", "abc", 22, false), + + T("*a* || !*b* || *c*", "", 22, true), + T("*a* || !*b* || *c*", "a", 22, true), + T("*a* || !*b* || *c*", "b", 22, false), + T("*a* || !*b* || *c*", "c", 22, true), + T("*a* || !*b* || *c*", "ab", 22, true), + T("*a* || !*b* || *c*", "ac", 22, true), + T("*a* || !*b* || *c*", "bc", 22, true), + T("*a* || !*b* || *c*", "abc", 22, true), + +#undef T +}; + +int main(int argc, char **argv) +{ + if (argc > 1) { + /* + * Parse an expression from the command line. + */ + + ptrlen expr = ptrlen_from_asciz(argv[1]); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + if (!en) { + fprintf(stderr, "ERR:%zu:%zu:%s\n", + (size_t)((const char *)error_loc.ptr - argv[1]), + (size_t)((const char *)ptrlen_end(error_loc) - argv[1]), + error_msg); + fprintf(stderr, "%.*s\n", PTRLEN_PRINTF(expr)); + for (const char *p = expr.ptr, *e = error_loc.ptr; p 2) { + /* + * Test-evaluate against a host/port pair given on the + * command line. + */ + const char *host = argv[2]; + unsigned port = (argc > 3 ? strtoul(argv[3], NULL, 0) : 22); + bool result = eval(en, host, port); + printf("%s\n", result ? "accept" : "reject"); + } else { + /* + * Just dump the result of parsing the expression. + */ + stdio_sink ss[1]; + stdio_sink_init(ss, stdout); + exprnode_dump(BinarySink_UPCAST(ss), en, expr.ptr); + put_byte(ss, '\n'); + } + + exprnode_free(en); + + return 0; + } else { + /* + * Run our automated tests. + */ + size_t pass = 0, fail = 0; + + for (size_t i = 0; i < lenof(parsetests); i++) { + const struct ParseTest *test = &parsetests[i]; + + ptrlen expr = ptrlen_from_asciz(test->expr); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + + strbuf *output = strbuf_new(); + if (!en) { + put_fmt(output, "ERR:%zu:%zu:%s", + (size_t)((const char *)error_loc.ptr - test->expr), + (size_t)((const char *)ptrlen_end(error_loc) - + test->expr), + error_msg); + sfree(error_msg); + } else { + exprnode_dump(BinarySink_UPCAST(output), en, expr.ptr); + exprnode_free(en); + } + + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(output), + ptrlen_from_asciz(test->output))) { + pass++; + } else { + fprintf(stderr, "FAIL: parsetests[%zu] @ %s:%d:\n" + " expression: %s\n" + " expected: %s\n" + " actual: %s\n", + i, test->file, test->line, test->expr, + test->output, output->s); + fail++; + } + + strbuf_free(output); + } + + for (size_t i = 0; i < lenof(evaltests); i++) { + const struct EvalTest *test = &evaltests[i]; + + ptrlen expr = ptrlen_from_asciz(test->expr); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + + if (!en) { + fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" + " expression: %s\n" + " parse error: %zu:%zu:%s\n", + i, test->file, test->line, test->expr, + (size_t)((const char *)error_loc.ptr - test->expr), + (size_t)((const char *)ptrlen_end(error_loc) - + test->expr), + error_msg); + sfree(error_msg); + } else { + bool output = eval(en, test->host, test->port); + if (output == test->output) { + pass++; + } else { + fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" + " expression: %s\n" + " host: %s\n" + " port: %u\n" + " expected: %s\n" + " actual: %s\n", + i, test->file, test->line, test->expr, + test->host, test->port, + test->output ? "accept" : "reject", + output ? "accept" : "reject"); + fail++; + } + exprnode_free(en); + } + } + + fprintf(stderr, "pass %zu fail %zu total %zu\n", + pass, fail, pass+fail); + return fail != 0; + } +} + +#endif // TEST diff --git a/code/utils/conf.c b/code/utils/conf.c index ecd26cd0..53195180 100644 --- a/code/utils/conf.c +++ b/code/utils/conf.c @@ -336,7 +336,7 @@ char *conf_get_str_str(Conf *conf, int primary, const char *secondary) } char *conf_get_str_strs(Conf *conf, int primary, - char *subkeyin, char **subkeyout) + char *subkeyin, char **subkeyout) { struct constkey key; struct conf_entry *entry; @@ -476,7 +476,7 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary) del234(conf->tree, entry); free_entry(entry); } - } +} void conf_set_filename(Conf *conf, int primary, const Filename *value) { diff --git a/code/utils/ctrlset_normalise.c b/code/utils/ctrlset_normalise.c new file mode 100644 index 00000000..3d922ebb --- /dev/null +++ b/code/utils/ctrlset_normalise.c @@ -0,0 +1,60 @@ +/* + * Helper function from the dialog.h mechanism. + */ + +#include "putty.h" +#include "misc.h" +#include "dialog.h" + +void ctrlset_normalise_aligns(struct controlset *s) +{ + /* + * The algorithm in here is quadratic time. Never on very much data, but + * even so, let's avoid bothering to use it where possible. In most + * controlsets, there's no use of align_next_to in any case, so we have + * nothing to do. + */ + for (size_t j = 0; j < s->ncontrols; j++) + if (s->ctrls[j]->align_next_to) + goto must_do_something; + /* If we fell out of this loop, there's nothing to do here */ + return; + must_do_something:; + + size_t *idx = snewn(s->ncontrols, size_t); + + /* + * Follow align_next_to links to identify, for each control, the least + * index within this controlset of things it's linked to. That way, + * controls with the same idx[j] will be in the same alignment class. + */ + for (size_t j = 0; j < s->ncontrols; j++) { + dlgcontrol *c = s->ctrls[j]; + idx[j] = j; + if (c->align_next_to) { + for (size_t k = 0; k < j; k++) { + if (s->ctrls[k] == c->align_next_to) { + idx[j] = idx[k]; + break; + } + } + } + } + + /* + * Having done that, re-link each control to the most recent one in its + * class, so that the links form a backward linked list. + */ + for (size_t j = 0; j < s->ncontrols; j++) { + dlgcontrol *c = s->ctrls[j]; + c->align_next_to = NULL; + for (size_t k = j; k-- > 0 ;) { + if (idx[k] == idx[j]) { + c->align_next_to = s->ctrls[k]; + break; + } + } + } + + sfree(idx); +} diff --git a/code/utils/debug.c b/code/utils/debug.c index 806b250a..79e437a2 100644 --- a/code/utils/debug.c +++ b/code/utils/debug.c @@ -43,8 +43,8 @@ void debug_memdump(const void *buf, int len, bool L) foo[i] = ' '; } else { debug_printf("%c%2.2x", - &p[i] != (unsigned char *) buf - && i % 4 ? '.' : ' ', p[i] + &p[i] != (unsigned char *) buf + && i % 4 ? '.' : ' ', p[i] ); if (p[i] >= ' ' && p[i] <= '~') foo[i] = (char) p[i]; diff --git a/code/utils/dup_wc_to_mb.c b/code/utils/dup_wc_to_mb.c index e91a8916..36088196 100644 --- a/code/utils/dup_wc_to_mb.c +++ b/code/utils/dup_wc_to_mb.c @@ -11,14 +11,14 @@ #include "misc.h" char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr, struct unicode_data *ucsdata) + const char *defchr) { size_t outsize = len+1; char *out = snewn(outsize, char); while (true) { size_t outlen = wc_to_mb(codepage, flags, string, len, out, outsize, - defchr, ucsdata); + defchr); /* We can only be sure we've consumed the whole input if the * output is not within a multibyte-character-length of the * end of the buffer! */ @@ -32,8 +32,7 @@ char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, } char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr, struct unicode_data *ucsdata) + const char *defchr) { - return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), - defchr, ucsdata); + return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), defchr); } diff --git a/code/utils/host_ca_new_free.c b/code/utils/host_ca_new_free.c new file mode 100644 index 00000000..4c91c320 --- /dev/null +++ b/code/utils/host_ca_new_free.c @@ -0,0 +1,22 @@ +#include "defs.h" +#include "misc.h" +#include "storage.h" + +host_ca *host_ca_new(void) +{ + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->opts.permit_rsa_sha1 = false; + hca->opts.permit_rsa_sha256 = true; + hca->opts.permit_rsa_sha512 = true; + return hca; +} + +void host_ca_free(host_ca *hca) +{ + sfree(hca->name); + sfree(hca->validity_expression); + if (hca->ca_public_key) + strbuf_free(hca->ca_public_key); + sfree(hca); +} diff --git a/code/utils/key_components.c b/code/utils/key_components.c new file mode 100644 index 00000000..b072ff51 --- /dev/null +++ b/code/utils/key_components.c @@ -0,0 +1,93 @@ +#include "ssh.h" +#include "mpint.h" + +key_components *key_components_new(void) +{ + key_components *kc = snew(key_components); + kc->ncomponents = 0; + kc->componentsize = 0; + kc->components = NULL; + return kc; +} + +static void key_components_add_str(key_components *kc, const char *name, + KeyComponentType type, ptrlen data) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].type = type; + kc->components[n].str = strbuf_dup_nm(data); +} + +void key_components_add_text(key_components *kc, + const char *name, const char *value) +{ + key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value)); +} + +void key_components_add_text_pl(key_components *kc, + const char *name, ptrlen value) +{ + key_components_add_str(kc, name, KCT_TEXT, value); +} + +void key_components_add_binary(key_components *kc, + const char *name, ptrlen value) +{ + key_components_add_str(kc, name, KCT_BINARY, value); +} + +void key_components_add_mp(key_components *kc, + const char *name, mp_int *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].type = KCT_MPINT; + kc->components[n].mp = mp_copy(value); +} + +void key_components_add_uint(key_components *kc, + const char *name, uintmax_t value) +{ + mp_int *mpvalue = mp_from_integer(value); + key_components_add_mp(kc, name, mpvalue); + mp_free(mpvalue); +} + +void key_components_add_copy(key_components *kc, + const char *name, const key_component *value) +{ + switch (value->type) { + case KCT_TEXT: + case KCT_BINARY: + key_components_add_str(kc, name, value->type, + ptrlen_from_strbuf(value->str)); + break; + case KCT_MPINT: + key_components_add_mp(kc, name, value->mp); + break; + } +} + +void key_components_free(key_components *kc) +{ + for (size_t i = 0; i < kc->ncomponents; i++) { + key_component *comp = &kc->components[i]; + sfree(comp->name); + switch (comp->type) { + case KCT_MPINT: + mp_free(comp->mp); + break; + case KCT_TEXT: + case KCT_BINARY: + strbuf_free(comp->str); + break; + default: + unreachable("bad key component type"); + } + } + sfree(kc->components); + sfree(kc); +} diff --git a/code/utils/log_proxy_stderr.c b/code/utils/log_proxy_stderr.c index 90025e08..2a84173a 100644 --- a/code/utils/log_proxy_stderr.c +++ b/code/utils/log_proxy_stderr.c @@ -7,6 +7,12 @@ void psb_init(ProxyStderrBuf *psb) { psb->size = 0; + psb->prefix = "proxy"; +} + +void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix) +{ + psb->prefix = prefix; } void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, @@ -58,7 +64,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, psb->buf[endpos-1] == '\r')) endpos--; char *msg = dupprintf( - "proxy: %.*s", (int)(endpos - pos), psb->buf + pos); + "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); @@ -73,7 +79,8 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, */ if (pos == 0 && psb->size == lenof(psb->buf)) { char *msg = dupprintf( - "proxy (partial line): %.*s", (int)psb->size, psb->buf); + "%s (partial line): %.*s", psb->prefix, (int)psb->size, + psb->buf); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); diff --git a/code/utils/percent_decode.c b/code/utils/percent_decode.c new file mode 100644 index 00000000..dff2c233 --- /dev/null +++ b/code/utils/percent_decode.c @@ -0,0 +1,41 @@ +/* + * Decode %-encoding in URL style. + */ + +#include + +#include "misc.h" + +void percent_decode_bs(BinarySink *bs, ptrlen data) +{ + for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { + char c = *p; + if (c == '%' && e-p >= 3 && + isxdigit((unsigned char)p[1]) && + isxdigit((unsigned char)p[2])) { + char hex[3]; + hex[0] = p[1]; + hex[1] = p[2]; + hex[2] = '\0'; + put_byte(bs, strtoul(hex, NULL, 16)); + p += 2; + } else { + put_byte(bs, c); + } + } + +} + +void percent_decode_fp(FILE *fp, ptrlen data) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + percent_decode_bs(BinarySink_UPCAST(&ss), data); +} + +strbuf *percent_decode_sb(ptrlen data) +{ + strbuf *sb = strbuf_new(); + percent_decode_bs(BinarySink_UPCAST(sb), data); + return sb; +} diff --git a/code/utils/percent_encode.c b/code/utils/percent_encode.c new file mode 100644 index 00000000..ea04b0ac --- /dev/null +++ b/code/utils/percent_encode.c @@ -0,0 +1,34 @@ +/* + * %-encoding in URL style. + * + * Defaults to escaping % itself (necessary for decoding to even + * work), and any C0 escape character. Further bad characters can be + * provided in 'badchars'. + */ + +#include "misc.h" + +void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars) +{ + for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { + char c = *p; + if (c == '%' || c < ' ' || (badchars && strchr(badchars, c))) + put_fmt(bs, "%%%02X", (unsigned char)c); + else + put_byte(bs, c); + } +} + +void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + percent_encode_bs(BinarySink_UPCAST(&ss), data, badchars); +} + +strbuf *percent_encode_sb(ptrlen data, const char *badchars) +{ + strbuf *sb = strbuf_new(); + percent_encode_bs(BinarySink_UPCAST(sb), data, badchars); + return sb; +} diff --git a/code/utils/ptrlen.c b/code/utils/ptrlen.c index 7d4e12ec..37972e49 100644 --- a/code/utils/ptrlen.c +++ b/code/utils/ptrlen.c @@ -54,6 +54,22 @@ bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) return false; } +bool ptrlen_contains(ptrlen input, const char *characters) +{ + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) + if (strchr(characters, *p)) + return true; + return false; +} + +bool ptrlen_contains_only(ptrlen input, const char *characters) +{ + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) + if (!strchr(characters, *p)) + return false; + return true; +} + ptrlen ptrlen_get_word(ptrlen *input, const char *separators) { const char *p = input->ptr, *end = p + input->len; diff --git a/code/utils/seat_dialog_text.c b/code/utils/seat_dialog_text.c new file mode 100644 index 00000000..03890b93 --- /dev/null +++ b/code/utils/seat_dialog_text.c @@ -0,0 +1,41 @@ +/* + * Helper routines for dealing with SeatDialogText structures. + */ + +#include + +#include "putty.h" + +SeatDialogText *seat_dialog_text_new(void) +{ + SeatDialogText *sdt = snew(SeatDialogText); + sdt->nitems = sdt->itemsize = 0; + sdt->items = NULL; + return sdt; +} + +void seat_dialog_text_free(SeatDialogText *sdt) +{ + for (size_t i = 0; i < sdt->nitems; i++) + sfree(sdt->items[i].text); + sfree(sdt->items); + sfree(sdt); +} + +static void seat_dialog_text_append_v( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap) +{ + sgrowarray(sdt->items, sdt->itemsize, sdt->nitems); + SeatDialogTextItem *item = &sdt->items[sdt->nitems++]; + item->type = type; + item->text = dupvprintf(fmt, ap); +} + +void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + seat_dialog_text_append_v(sdt, type, fmt, ap); + va_end(ap); +} diff --git a/code/utils/smemeq.c b/code/utils/smemeq.c index 2692d134..ce3761ec 100644 --- a/code/utils/smemeq.c +++ b/code/utils/smemeq.c @@ -8,7 +8,7 @@ #include "defs.h" #include "misc.h" -bool smemeq(const void *av, const void *bv, size_t len) +unsigned smemeq(const void *av, const void *bv, size_t len) { const unsigned char *a = (const unsigned char *)av; const unsigned char *b = (const unsigned char *)bv; diff --git a/code/utils/ssh_key_clone.c b/code/utils/ssh_key_clone.c new file mode 100644 index 00000000..5824bdbf --- /dev/null +++ b/code/utils/ssh_key_clone.c @@ -0,0 +1,32 @@ +/* + * Make a copy of an existing ssh_key object, e.g. to survive after + * the original is freed. + */ + +#include "misc.h" +#include "ssh.h" + +ssh_key *ssh_key_clone(ssh_key *key) +{ + /* + * To avoid having to add a special method in the vtable API, we + * clone by round-tripping through public and private blobs. + */ + strbuf *pub = strbuf_new_nm(); + ssh_key_public_blob(key, BinarySink_UPCAST(pub)); + + ssh_key *copy; + + if (ssh_key_has_private(key)) { + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(key, BinarySink_UPCAST(priv)); + copy = ssh_key_new_priv(ssh_key_alg(key), ptrlen_from_strbuf(pub), + ptrlen_from_strbuf(priv)); + strbuf_free(priv); + } else { + copy = ssh_key_new_pub(ssh_key_alg(key), ptrlen_from_strbuf(pub)); + } + + strbuf_free(pub); + return copy; +} diff --git a/code/utils/strbuf.c b/code/utils/strbuf.c index 636467a4..c636de47 100644 --- a/code/utils/strbuf.c +++ b/code/utils/strbuf.c @@ -112,3 +112,17 @@ void strbuf_finalise_agent_query(strbuf *buf_o) assert(buf->visible.len >= 5); PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); } + +strbuf *strbuf_dup(ptrlen string) +{ + strbuf *buf = strbuf_new(); + put_datapl(buf, string); + return buf; +} + +strbuf *strbuf_dup_nm(ptrlen string) +{ + strbuf *buf = strbuf_new_nm(); + put_datapl(buf, string); + return buf; +} diff --git a/code/utils/tempseat.c b/code/utils/tempseat.c index 785c00c2..ad37b0e3 100644 --- a/code/utils/tempseat.c +++ b/code/utils/tempseat.c @@ -204,6 +204,17 @@ static bool tempseat_has_mixed_input_stream(Seat *seat) return seat_has_mixed_input_stream(ts->realseat); } +static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions( + Seat *seat) +{ + /* It might be OK to put this in the 'unreachable' category, but I + * think it's equally good to put it here, which allows for + * someone _preparing_ a prompt right now that they intend to + * present once the TempSeat has given way to the real one. */ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_prompt_descriptions(ts->realseat); +} + /* ---------------------------------------------------------------------- * Methods that should never be called on a TempSeat, so we can put an * unreachable() in them. @@ -237,7 +248,7 @@ static size_t tempseat_banner(Seat *seat, const void *data, size_t len) static SeatPromptResult tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_ssh_host_key should never be called on TempSeat"); @@ -322,6 +333,7 @@ static const struct SeatVtable tempseat_vt = { .confirm_ssh_host_key = tempseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, + .prompt_descriptions = tempseat_prompt_descriptions, .is_utf8 = tempseat_is_utf8, .echoedit_update = tempseat_echoedit_update, .get_x_display = tempseat_get_x_display, diff --git a/code/utils/tree234.c b/code/utils/tree234.c index 83683f13..6440f871 100644 --- a/code/utils/tree234.c +++ b/code/utils/tree234.c @@ -73,7 +73,7 @@ tree234 *newtree234(cmpfn234 cmp) /* * Free a 2-3-4 tree (not including freeing the elements). */ -static void freenode234(node234 * n) +static void freenode234(node234 *n) { if (!n) return; @@ -84,7 +84,7 @@ static void freenode234(node234 * n) sfree(n); } -void freetree234(tree234 * t) +void freetree234(tree234 *t) { freenode234(t->root); sfree(t); @@ -93,7 +93,7 @@ void freetree234(tree234 * t) /* * Internal function to count a node. */ -static int countnode234(node234 * n) +static int countnode234(node234 *n) { int count = 0; int i; @@ -122,7 +122,7 @@ static int elements234(node234 *n) /* * Count the elements in a tree. */ -int count234(tree234 * t) +int count234(tree234 *t) { if (t->root) return countnode234(t->root); @@ -134,7 +134,7 @@ int count234(tree234 * t) * Add an element e to a 2-3-4 tree t. Returns e on success, or if * an existing element compares equal, returns that. */ -static void *add234_internal(tree234 * t, void *e, int index) +static void *add234_internal(tree234 *t, void *e, int index) { node234 *n, **np, *left, *right; void *orig_e = e; @@ -463,14 +463,14 @@ static void *add234_internal(tree234 * t, void *e, int index) return orig_e; } -void *add234(tree234 * t, void *e) +void *add234(tree234 *t, void *e) { if (!t->cmp) /* tree is unsorted */ return NULL; return add234_internal(t, e, -1); } -void *addpos234(tree234 * t, void *e, int index) +void *addpos234(tree234 *t, void *e, int index) { if (index < 0 || /* index out of range */ t->cmp) /* tree is sorted */ @@ -483,7 +483,7 @@ void *addpos234(tree234 * t, void *e, int index) * Look up the element at a given numeric index in a 2-3-4 tree. * Returns NULL if the index is out of range. */ -void *index234(tree234 * t, int index) +void *index234(tree234 *t, int index) { node234 *n; @@ -523,7 +523,7 @@ void *index234(tree234 * t, int index) * as NULL, in which case the compare function from the tree proper * will be used. */ -void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, +void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation, int *index) { search234_state ss; @@ -598,15 +598,15 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, *index = ss.index; return toret; } -void *find234(tree234 * t, void *e, cmpfn234 cmp) +void *find234(tree234 *t, void *e, cmpfn234 cmp) { return findrelpos234(t, e, cmp, REL234_EQ, NULL); } -void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation) +void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation) { return findrelpos234(t, e, cmp, relation, NULL); } -void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index) +void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index) { return findrelpos234(t, e, cmp, REL234_EQ, index); } @@ -686,7 +686,7 @@ void search234_step(search234_state *state, int direction) * Delete an element e in a 2-3-4 tree. Does not free the element, * merely removes all links to it from the tree nodes. */ -static void *delpos234_internal(tree234 * t, int index) +static void *delpos234_internal(tree234 *t, int index) { node234 *n; void *retval; @@ -1024,13 +1024,13 @@ static void *delpos234_internal(tree234 * t, int index) } } } -void *delpos234(tree234 * t, int index) +void *delpos234(tree234 *t, int index) { if (index < 0 || index >= countnode234(t->root)) return NULL; return delpos234_internal(t, index); } -void *del234(tree234 * t, void *e) +void *del234(tree234 *t, void *e) { int index; if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) @@ -1095,7 +1095,7 @@ typedef struct { int elemcount; } chkctx; -int chknode(chkctx * ctx, int level, node234 * node, +int chknode(chkctx *ctx, int level, node234 *node, void *lowbound, void *highbound) { int nkids, nelems; diff --git a/code/utils/wordwrap.c b/code/utils/wordwrap.c new file mode 100644 index 00000000..330b6a03 --- /dev/null +++ b/code/utils/wordwrap.c @@ -0,0 +1,36 @@ +/* + * Function to wrap text to a fixed number of columns. + * + * Currently, assumes the text is in a single-byte character set, + * because it's only used to display host key prompt messages. + * Extending to Unicode and using wcwidth() could be an extension. + */ + +#include "misc.h" + +void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid) +{ + size_t col = 0; + while (true) { + ptrlen word = ptrlen_get_word(&input, " "); + if (!word.len) + break; + + /* At the start of a line, any word is legal, even if it's + * overlong, because we have to display it _somehow_ and + * wrapping to the next line won't make it any better. */ + if (col > 0) { + size_t newcol = col + 1 + word.len; + if (newcol <= maxwid) { + put_byte(bs, ' '); + col++; + } else { + put_byte(bs, '\n'); + col = 0; + } + } + + put_datapl(bs, word); + col += word.len; + } +} diff --git a/code/utils/x11_dehexify.c b/code/utils/x11_dehexify.c index 13ffb0d0..080f8c91 100644 --- a/code/utils/x11_dehexify.c +++ b/code/utils/x11_dehexify.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" void *x11_dehexify(ptrlen hexpl, int *outlen) { diff --git a/code/utils/x11_identify_auth_proto.c b/code/utils/x11_identify_auth_proto.c index 14075a3f..46e26dc0 100644 --- a/code/utils/x11_identify_auth_proto.c +++ b/code/utils/x11_identify_auth_proto.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" int x11_identify_auth_proto(ptrlen protoname) { diff --git a/code/utils/x11_parse_ip.c b/code/utils/x11_parse_ip.c index 12649b49..8f6a7bfb 100644 --- a/code/utils/x11_parse_ip.c +++ b/code/utils/x11_parse_ip.c @@ -6,6 +6,7 @@ #include #include "putty.h" +#include "ssh.h" bool x11_parse_ip(const char *addr_string, unsigned long *ip) { diff --git a/code/utils/x11authfile.c b/code/utils/x11authfile.c index 4fc84ab5..e2358a9d 100644 --- a/code/utils/x11authfile.c +++ b/code/utils/x11authfile.c @@ -9,7 +9,7 @@ #include "putty.h" #include "ssh.h" -ptrlen BinarySource_get_string_xauth(BinarySource *src) +static ptrlen BinarySource_get_string_xauth(BinarySource *src) { size_t len = get_uint16(src); return get_data(src, len); @@ -17,7 +17,7 @@ ptrlen BinarySource_get_string_xauth(BinarySource *src) #define get_string_xauth(src) \ BinarySource_get_string_xauth(BinarySource_UPCAST(src)) -void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) +static void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) { assert((pl.len >> 16) == 0); put_uint16(bs, pl.len); diff --git a/code/version.h b/code/version.h index 5cc40ab2..b10eb33c 100644 --- a/code/version.h +++ b/code/version.h @@ -1,6 +1,6 @@ /* Generated by automated build script */ -#define RELEASE 0.77-2 -#define TEXTVER "Release 0.77-2" -#define SSHVER "-Release-0.77-2" -#define BINARY_VERSION 0,77,0,2 +#define RELEASE 0.78 +#define TEXTVER "Release 0.78" +#define SSHVER "-Release-0.78" +#define BINARY_VERSION 0,78,0,0 #define SOURCE_COMMIT "See https://github.com/NoMoreFood/putty-cac" diff --git a/code/windows/CMakeLists.txt b/code/windows/CMakeLists.txt index 0e957263..32037f78 100644 --- a/code/windows/CMakeLists.txt +++ b/code/windows/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources_from_current_dir(utils utils/agent_named_pipe_name.c utils/arm_arch_queries.c utils/aux_match_opt.c + utils/centre_window.c utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c @@ -26,10 +27,11 @@ add_sources_from_current_dir(utils utils/open_for_write_would_lose_data.c utils/pgp_fingerprints_msgbox.c utils/platform_get_x_display.c - utils/registry_get_string.c + utils/registry.c utils/request_file.c utils/screenshot.c utils/security.c + utils/shinydialogbox.c utils/split_into_argv.c utils/version.c utils/win_strerror.c @@ -114,8 +116,9 @@ add_executable(puttytel window.c putty.c help.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) @@ -131,7 +134,7 @@ installed_program(puttytel) add_executable(puttygen puttygen.c - ${CMAKE_SOURCE_DIR}/stubs/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c noise.c no-jump-list.c storage.c @@ -155,8 +158,9 @@ if(HAVE_CONPTY) pterm.c help.c conpty.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) be_list(pterm pterm) @@ -177,6 +181,9 @@ add_executable(test_split_into_argv target_compile_definitions(test_split_into_argv PRIVATE TEST) target_link_libraries(test_split_into_argv utils ${platform_libraries}) +add_executable(test_screenshot + test_screenshot.c) +target_link_libraries(test_screenshot utils ${platform_libraries}) if(PUTTY_CAC) target_link_libraries(puttytel crypto utils network sshclient) target_link_libraries(pterm crypto utils network sshclient) diff --git a/code/windows/agent-client.c b/code/windows/agent-client.c index 1183bff8..168465e5 100644 --- a/code/windows/agent-client.c +++ b/code/windows/agent-client.c @@ -62,8 +62,8 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (psd) { - if (p_InitializeSecurityDescriptor - (psd, SECURITY_DESCRIPTOR_REVISION) && + if (p_InitializeSecurityDescriptor( + psd, SECURITY_DESCRIPTOR_REVISION) && p_SetSecurityDescriptorOwner(psd, usersid, false)) { sa.nLength = sizeof(sa); sa.bInheritHandle = true; diff --git a/code/windows/config.c b/code/windows/config.c index 9c107edd..fc9070bf 100644 --- a/code/windows/config.c +++ b/code/windows/config.c @@ -10,27 +10,27 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, dlgparam *dlg, +static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { modal_about_box(*hwndp); } } -static void help_handler(union control *ctrl, dlgparam *dlg, +static void help_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { show_help(*hwndp); } } -static void variable_pitch_handler(union control *ctrl, dlgparam *dlg, +static void variable_pitch_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_REFRESH) { @@ -46,7 +46,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, const struct BackendVtable *backvt; bool resize_forbidden = false; struct controlset *s; - union control *c; + dlgcontrol *c; char *str; if (!midsession) { @@ -56,11 +56,11 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, s = ctrl_getset(b, "", "", ""); c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), about_handler, P(hwndp)); - c->generic.column = 0; + c->column = 0; if (has_help) { c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), help_handler, P(hwndp)); - c->generic.column = 1; + c->column = 1; } } @@ -82,8 +82,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_scrollbar) { + if (c->type == CTRL_CHECKBOX && + c->context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-in-FS one. @@ -91,7 +91,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, if (i < s->ncontrols-2) { c = s->ctrls[s->ncontrols-1]; memmove(s->ctrls+i+2, s->ctrls+i+1, - (s->ncontrols-i-2)*sizeof(union control *)); + (s->ncontrols-i-2)*sizeof(dlgcontrol *)); s->ctrls[i+1] = c; } break; @@ -134,9 +134,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_beep) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_beep) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 2; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -173,7 +173,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_beep_ind), "Disabled", I(B_IND_DISABLED), "Flashing", I(B_IND_FLASH), - "Steady", I(B_IND_STEADY), NULL); + "Steady", I(B_IND_STEADY)); /* * The sunken-edge border is a Windows GUI feature. @@ -198,7 +198,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "Antialiased", I(FQ_ANTIALIASED), "Non-Antialiased", I(FQ_NONANTIALIASED), "ClearType", I(FQ_CLEARTYPE), - "Default", I(FQ_DEFAULT), NULL); + "Default", I(FQ_DEFAULT)); /* * Cyrillic Lock is a horrid misfeature even on Windows, and @@ -233,9 +233,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_vtmode) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_vtmode) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 3; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -289,14 +289,14 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_mouse_is_xterm), "Windows (Middle extends, Right brings up menu)", I(2), "Compromise (Middle extends, Right pastes)", I(0), - "xterm (Right extends, Middle pastes)", I(1), NULL); + "xterm (Right extends, Middle pastes)", I(1)); /* * This really ought to go at the _top_ of its box, not the * bottom, so we'll just do some shuffling now we've set it * up... */ c = s->ctrls[s->ncontrols-1]; /* this should be the new control */ - memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(union control *)); + memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(dlgcontrol *)); s->ctrls[0] = c; /* @@ -328,7 +328,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "Change the number of rows and columns", I(RESIZE_TERM), "Change the size of the font", I(RESIZE_FONT), "Change font size only when maximised", I(RESIZE_EITHER), - "Forbid resizing completely", I(RESIZE_DISABLED), NULL); + "Forbid resizing completely", I(RESIZE_DISABLED)); } /* @@ -355,39 +355,16 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_fullscreenonaltenter)); /* - * Windows supports a local-command proxy. This also means we - * must adjust the text on the `Telnet command' control. + * Windows supports a local-command proxy. */ if (!midsession) { int i; s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); - if (c->radio.ncolumns < 4) - c->radio.ncolumns = 4; - break; - } - } - - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); + if (c->type == CTRL_LISTBOX && + c->handler == proxy_type_handler) { + c->context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/code/windows/console.c b/code/windows/console.c index 81ab7d06..2fd572b4 100644 --- a/code/windows/console.c +++ b/code/windows/console.c @@ -34,44 +34,53 @@ void console_print_error_msg(const char *prefix, const char *msg) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; - char *common; - const char *intro, *prompt; + const char *prompt = NULL; - char line[32]; - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); - fputs(common, stderr); - sfree(common); + char line[32]; - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } - - fputs(intro, stderr); - fflush(stderr); + assert(prompt); /* something in the SeatDialogText should have set this */ while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); line[0] = '\0'; /* fail safe if ReadFile returns no data */ @@ -84,13 +93,22 @@ SeatPromptResult console_confirm_ssh_host_key( SetConsoleMode(hin, savemode); if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } } else { break; } diff --git a/code/windows/controls.c b/code/windows/controls.c index 3c7896a4..ce3638e4 100644 --- a/code/windows/controls.c +++ b/code/windows/controls.c @@ -71,8 +71,8 @@ void ctlposinit(struct ctlpos *cp, HWND hwnd, cp->width -= leftborder + rightborder; } -HWND doctl(struct ctlpos *cp, RECT r, - char *wclass, int wstyle, int exstyle, char *wtext, int wid) +HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, + int exstyle, const char *wtext, int wid) { HWND ctl; /* @@ -115,7 +115,7 @@ HWND doctl(struct ctlpos *cp, RECT r, /* * A title bar across the top of a sub-dialog. */ -void bartitle(struct ctlpos *cp, char *name, int id) +void bartitle(struct ctlpos *cp, const char *name, int id) { RECT r; @@ -130,7 +130,7 @@ void bartitle(struct ctlpos *cp, char *name, int id) /* * Begin a grouping box, with or without a group title. */ -void beginbox(struct ctlpos *cp, char *name, int idbox) +void beginbox(struct ctlpos *cp, const char *name, int idbox) { cp->boxystart = cp->ypos; if (!name) @@ -165,8 +165,8 @@ void endbox(struct ctlpos *cp) /* * A static line, followed by a full-width edit box. */ -void editboxfw(struct ctlpos *cp, bool password, char *text, - int staticid, int editid) +void editboxfw(struct ctlpos *cp, bool password, bool readonly, + const char *text, int staticid, int editid) { RECT r; @@ -183,7 +183,8 @@ void editboxfw(struct ctlpos *cp, bool password, char *text, r.bottom = EDITHEIGHT; doctl(cp, r, "EDIT", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | - (password ? ES_PASSWORD : 0), + (password ? ES_PASSWORD : 0) | + (readonly ? ES_READONLY : 0), WS_EX_CLIENTEDGE, "", editid); cp->ypos += EDITHEIGHT + GAPBETWEEN; } @@ -191,7 +192,7 @@ void editboxfw(struct ctlpos *cp, bool password, char *text, /* * A static line, followed by a full-width combo box. */ -void combobox(struct ctlpos *cp, char *text, int staticid, int listid) +void combobox(struct ctlpos *cp, const char *text, int staticid, int listid) { RECT r; @@ -212,9 +213,9 @@ void combobox(struct ctlpos *cp, char *text, int staticid, int listid) cp->ypos += COMBOHEIGHT + GAPBETWEEN; } -struct radio { char *text; int id; }; +struct radio { const char *text; int id; }; -static void radioline_common(struct ctlpos *cp, char *text, int id, +static void radioline_common(struct ctlpos *cp, const char *text, int id, int nacross, struct radio *buttons, int nbuttons) { RECT r; @@ -236,7 +237,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, group = WS_GROUP; i = 0; for (j = 0; j < nbuttons; j++) { - char *btext = buttons[j].text; + const char *btext = buttons[j].text; int bid = buttons[j].id; if (i == nacross) { @@ -274,7 +275,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, * * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle */ -void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) +void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...) { va_list ap; struct radio *buttons; @@ -283,7 +284,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -293,7 +294,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -314,7 +315,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -324,7 +325,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -336,7 +337,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) * A set of radio buttons on multiple lines, with a static above * them. */ -void radiobig(struct ctlpos *cp, char *text, int id, ...) +void radiobig(struct ctlpos *cp, const char *text, int id, ...) { va_list ap; struct radio *buttons; @@ -345,7 +346,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) va_start(ap, id); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -355,7 +356,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, id); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -366,7 +367,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) /* * A single standalone checkbox. */ -void checkbox(struct ctlpos *cp, char *text, int id) +void checkbox(struct ctlpos *cp, const char *text, int id) { RECT r; @@ -385,13 +386,14 @@ void checkbox(struct ctlpos *cp, char *text, int id) * wrapped text (a malloc'ed string containing \ns), and also * returns the number of lines required. */ -char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines) +char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) { HDC hdc = GetDC(hwnd); int width, nlines, j; INT *pwidths, nfit; SIZE size; - char *ret, *p, *q; + const char *p; + char *ret, *q; RECT r; HFONT oldfont, newfont; @@ -471,7 +473,7 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines) /* * A single standalone static text control. */ -void statictext(struct ctlpos *cp, char *text, int lines, int id) +void statictext(struct ctlpos *cp, const char *text, int lines, int id) { RECT r; @@ -504,8 +506,8 @@ void paneltitle(struct ctlpos *cp, int id) /* * A button on the right hand side, with a static to its left. */ -void staticbtn(struct ctlpos *cp, char *stext, int sid, - char *btext, int bid) +void staticbtn(struct ctlpos *cp, const char *stext, int sid, + const char *btext, int bid) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -536,7 +538,7 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid, /* * A simple push button. */ -void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn) { RECT r; @@ -561,8 +563,8 @@ void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) /* * Like staticbtn, but two buttons. */ -void static2btn(struct ctlpos *cp, char *stext, int sid, - char *btext1, int bid1, char *btext2, int bid2) +void static2btn(struct ctlpos *cp, const char *stext, int sid, + const char *btext1, int bid1, const char *btext2, int bid2) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -603,7 +605,7 @@ void static2btn(struct ctlpos *cp, char *stext, int sid, /* * An edit control on the right hand side, with a static to its left. */ -static void staticedit_internal(struct ctlpos *cp, char *stext, +static void staticedit_internal(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit, int style) { @@ -634,13 +636,13 @@ static void staticedit_internal(struct ctlpos *cp, char *stext, cp->ypos += height + GAPBETWEEN; } -void staticedit(struct ctlpos *cp, char *stext, +void staticedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, 0); } -void staticpassedit(struct ctlpos *cp, char *stext, +void staticpassedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD); @@ -650,7 +652,7 @@ void staticpassedit(struct ctlpos *cp, char *stext, * A drop-down list box on the right hand side, with a static to * its left. */ -void staticddl(struct ctlpos *cp, char *stext, +void staticddl(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -683,7 +685,7 @@ void staticddl(struct ctlpos *cp, char *stext, /* * A combo box on the right hand side, with a static to its left. */ -void staticcombo(struct ctlpos *cp, char *stext, +void staticcombo(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -716,7 +718,7 @@ void staticcombo(struct ctlpos *cp, char *stext, /* * A static, with a full-width drop-down list box below it. */ -void staticddlbig(struct ctlpos *cp, char *stext, +void staticddlbig(struct ctlpos *cp, const char *stext, int sid, int lid) { RECT r; @@ -743,7 +745,7 @@ void staticddlbig(struct ctlpos *cp, char *stext, /* * A big multiline edit control with a static labelling it. */ -void bigeditctrl(struct ctlpos *cp, char *stext, +void bigeditctrl(struct ctlpos *cp, const char *stext, int sid, int eid, int lines) { RECT r; @@ -770,7 +772,7 @@ void bigeditctrl(struct ctlpos *cp, char *stext, /* * A list box with a static labelling it. */ -void listbox(struct ctlpos *cp, char *stext, +void listbox(struct ctlpos *cp, const char *stext, int sid, int lid, int lines, bool multi) { RECT r; @@ -799,7 +801,8 @@ void listbox(struct ctlpos *cp, char *stext, /* * A tab-control substitute when a real tab control is unavailable. */ -void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id) +void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, + int s2id) { const int height = (COMBOHEIGHT > STATICHEIGHT ? COMBOHEIGHT : STATICHEIGHT); @@ -842,8 +845,8 @@ void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id) * A static line, followed by an edit control on the left hand side * and a button on the right. */ -void editbutton(struct ctlpos *cp, char *stext, int sid, - int eid, char *btext, int bid) +void editbutton(struct ctlpos *cp, const char *stext, int sid, + int eid, const char *btext, int bid) { const int height = (EDITHEIGHT > PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT); @@ -886,7 +889,7 @@ void editbutton(struct ctlpos *cp, char *stext, int sid, * XXX: this is a rough hack and could be improved. */ void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - char *stext, int sid, int listid, int upbid, int dnbid) + const char *stext, int sid, int listid, int upbid, int dnbid) { const static int percents[] = { 5, 75, 20 }; RECT r; @@ -1250,7 +1253,7 @@ static int winctrl_cmp_byid(void *av, void *bv) } static int winctrl_cmp_byctrl_find(void *av, void *bv) { - union control *a = (union control *)av; + dlgcontrol *a = (dlgcontrol *)av; struct winctrl *b = (struct winctrl *)bv; if (a < b->ctrl) return -1; @@ -1310,7 +1313,7 @@ void winctrl_remove(struct winctrls *wc, struct winctrl *c) assert(ret == c); } -struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl) +struct winctrl *winctrl_findbyctrl(struct winctrls *wc, dlgcontrol *ctrl) { return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find); } @@ -1354,7 +1357,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, int ncols, colstart, colspan; struct ctlpos tabdelays[16]; - union control *tabdelayed[16]; + dlgcontrol *tabdelayed[16]; int ntabdelays; struct ctlpos pos; @@ -1367,6 +1370,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, base_id = *id; + ctrlset_normalise_aligns(s); + /* Start a containing box, if we have a boxname. */ if (s->boxname && *s->boxname) { struct winctrl *c = snew(struct winctrl); @@ -1402,7 +1407,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, /* Loop over each control in the controlset. */ for (i = 0; i < s->ncontrols; i++) { - union control *ctrl = s->ctrls[i]; + dlgcontrol *ctrl = s->ctrls[i]; /* * Generic processing that pertains to all control types. @@ -1415,7 +1420,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * CTRL_COLUMNS and doesn't require any control creation at * all. */ - if (ctrl->generic.type == CTRL_COLUMNS) { + if (ctrl->type == CTRL_COLUMNS) { assert((ctrl->columns.ncols == 1) ^ (ncols == 1)); if (ncols == 1) { @@ -1455,10 +1460,10 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } continue; - } else if (ctrl->generic.type == CTRL_TABDELAY) { + } else if (ctrl->type == CTRL_TABDELAY) { int i; - assert(!ctrl->generic.tabdelay); + assert(!ctrl->delay_taborder); ctrl = ctrl->tabdelay.ctrl; for (i = 0; i < ntabdelays; i++) @@ -1478,8 +1483,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, */ int col; - colstart = COLUMN_START(ctrl->generic.column); - colspan = COLUMN_SPAN(ctrl->generic.column); + colstart = COLUMN_START(ctrl->column); + colspan = COLUMN_SPAN(ctrl->column); pos = columns[colstart]; /* structure copy */ pos.width = columns[colstart+colspan-1].width + @@ -1494,7 +1499,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * tabdelay list, and unset pos.hwnd to inhibit actual * control creation. */ - if (ctrl->generic.tabdelay) { + if (ctrl->delay_taborder) { assert(ntabdelays < lenof(tabdelays)); tabdelays[ntabdelays] = pos; /* structure copy */ tabdelayed[ntabdelays] = ctrl; @@ -1522,22 +1527,28 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * Now we're ready to actually create the control, by * switching on its type. */ - switch (ctrl->generic.type) { - case CTRL_TEXT: { - char *wrapped, *escaped; - int lines; - num_ids = 1; - wrapped = staticwrap(&pos, cp->hwnd, - ctrl->generic.label, &lines); - escaped = shortcut_escape(wrapped, NO_SHORTCUT); - statictext(&pos, escaped, lines, base_id); - sfree(escaped); - sfree(wrapped); + switch (ctrl->type) { + case CTRL_TEXT: + if (ctrl->text.wrap) { + char *wrapped, *escaped; + int lines; + num_ids = 1; + wrapped = staticwrap(&pos, cp->hwnd, + ctrl->label, &lines); + escaped = shortcut_escape(wrapped, NO_SHORTCUT); + statictext(&pos, escaped, lines, base_id); + sfree(escaped); + sfree(wrapped); + } else { + num_ids = 1; + editboxfw(&pos, false, true, NULL, 0, base_id); + SetDlgItemText(pos.hwnd, base_id, ctrl->label); + MakeDlgItemBorderless(pos.hwnd, base_id); + } break; - } case CTRL_EDITBOX: num_ids = 2; /* static, edit */ - escaped = shortcut_escape(ctrl->editbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->editbox.shortcut); shortcuts[nshortcuts++] = ctrl->editbox.shortcut; if (ctrl->editbox.percentwidth == 100) { @@ -1545,7 +1556,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, combobox(&pos, escaped, base_id, base_id+1); else - editboxfw(&pos, ctrl->editbox.password, escaped, + editboxfw(&pos, ctrl->editbox.password, false, escaped, base_id, base_id+1); } else { if (ctrl->editbox.has_list) { @@ -1564,23 +1575,23 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, struct radio *buttons; int i; - escaped = shortcut_escape(ctrl->radio.label, + escaped = shortcut_escape(ctrl->label, ctrl->radio.shortcut); shortcuts[nshortcuts++] = ctrl->radio.shortcut; buttons = snewn(ctrl->radio.nbuttons, struct radio); for (i = 0; i < ctrl->radio.nbuttons; i++) { - buttons[i].text = - shortcut_escape(ctrl->radio.buttons[i], - (char)(ctrl->radio.shortcuts ? - ctrl->radio.shortcuts[i] : - NO_SHORTCUT)); - buttons[i].id = base_id + 1 + i; - if (ctrl->radio.shortcuts) { - assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL); - shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i]; - } + buttons[i].text = + shortcut_escape(ctrl->radio.buttons[i], + (char)(ctrl->radio.shortcuts ? + ctrl->radio.shortcuts[i] : + NO_SHORTCUT)); + buttons[i].id = base_id + 1 + i; + if (ctrl->radio.shortcuts) { + assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL); + shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i]; + } } radioline_common(&pos, escaped, base_id, @@ -1588,7 +1599,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, buttons, ctrl->radio.nbuttons); for (i = 0; i < ctrl->radio.nbuttons; i++) { - sfree(buttons[i].text); + sfree((char *)buttons[i].text); } sfree(buttons); sfree(escaped); @@ -1596,14 +1607,14 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } case CTRL_CHECKBOX: num_ids = 1; - escaped = shortcut_escape(ctrl->checkbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->checkbox.shortcut); shortcuts[nshortcuts++] = ctrl->checkbox.shortcut; checkbox(&pos, escaped, base_id); sfree(escaped); break; case CTRL_BUTTON: - escaped = shortcut_escape(ctrl->button.label, + escaped = shortcut_escape(ctrl->label, ctrl->button.shortcut); shortcuts[nshortcuts++] = ctrl->button.shortcut; if (ctrl->button.iscancel) @@ -1614,7 +1625,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, break; case CTRL_LISTBOX: num_ids = 2; - escaped = shortcut_escape(ctrl->listbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->listbox.shortcut); shortcuts[nshortcuts++] = ctrl->listbox.shortcut; if (ctrl->listbox.draglist) { @@ -1663,18 +1674,20 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, sfree(escaped); break; case CTRL_FILESELECT: - num_ids = 3; - escaped = shortcut_escape(ctrl->fileselect.label, - ctrl->fileselect.shortcut); + escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut); shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; - editbutton(&pos, escaped, base_id, base_id+1, - "Bro&wse...", base_id+2); - shortcuts[nshortcuts++] = 'w'; + num_ids = 3; + if (!ctrl->fileselect.just_button) { + editbutton(&pos, escaped, base_id, base_id+1, + "Browse...", base_id+2); + } else { + button(&pos, escaped, base_id+2, false); + } sfree(escaped); break; case CTRL_FONTSELECT: num_ids = 3; - escaped = shortcut_escape(ctrl->fontselect.label, + escaped = shortcut_escape(ctrl->label, ctrl->fontselect.shortcut); shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); @@ -1709,28 +1722,43 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, if (actual_base_id == base_id) base_id += num_ids; - if (ctrl->generic.align_next_to) { + if (ctrl->align_next_to) { /* * Implement align_next_to by looking at the y extents * of the two controls now that both are created, and * moving one or the other downwards so that they're * centred on a common horizontal line. */ - struct winctrl *c2 = winctrl_findbyctrl( - wc, ctrl->generic.align_next_to); - HWND win1 = GetDlgItem(pos.hwnd, c->align_id); - HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); - RECT rect1, rect2; - if (win1 && win2 && - GetWindowRect(win1, &rect1) && - GetWindowRect(win2, &rect2)) { - LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); - LONG bottom = (rect1.bottom > rect2.bottom ? - rect1.bottom : rect2.bottom); - move_windows(pos.hwnd, c->base_id, c->num_ids, - (top + bottom - rect1.top - rect1.bottom)/2); - move_windows(pos.hwnd, c2->base_id, c2->num_ids, - (top + bottom - rect2.top - rect2.bottom)/2); + LONG mid2 = 0; + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + if (mid2 < rect.top + rect.bottom) + mid2 = rect.top + rect.bottom; + } + + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + + LONG dy = (mid2 - (rect.top + rect.bottom)) / 2; + move_windows(pos.hwnd, thisc->base_id, thisc->num_ids, dy); } } } else { @@ -1762,7 +1790,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, endbox(cp); } -static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, +static void winctrl_set_focus(dlgcontrol *ctrl, struct dlgparam *dp, bool has_focus) { if (has_focus) { @@ -1775,7 +1803,7 @@ static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, } } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { return dp->focused == ctrl ? dp->lastfocused : dp->focused; } @@ -1788,7 +1816,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, WPARAM wParam, LPARAM lParam) { struct winctrl *c; - union control *ctrl; + dlgcontrol *ctrl; int i, id; bool ret; static UINT draglistmsg = WM_NULL; @@ -1827,7 +1855,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */ GetTextExtentPoint32(hdc, (char *)c->data, - strlen((char *)c->data), &s); + strlen((char *)c->data), &s); DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT); TextOut(hdc, r.left + (r.right-r.left-s.cx)/2, @@ -1840,7 +1868,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, ctrl = c->ctrl; id = LOWORD(wParam) - c->base_id; - if (!ctrl || !ctrl->generic.handler) + if (!ctrl || !ctrl->handler) return false; /* nothing we can do here */ /* @@ -1856,7 +1884,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, /* * Now switch on the control type and the message. */ - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: if (msg == WM_COMMAND && !ctrl->editbox.has_list && (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS)) @@ -1867,7 +1895,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && !ctrl->editbox.has_list && HIWORD(wParam) == EN_CHANGE) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (msg == WM_COMMAND && ctrl->editbox.has_list) { if (HIWORD(wParam) == CBN_SELCHANGE) { @@ -1883,11 +1911,11 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, index, (LPARAM)text); SetDlgItemText(dp->hwnd, c->base_id+1, text); sfree(text); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_EDITCHANGE) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_KILLFOCUS) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } @@ -1907,7 +1935,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) && IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_CHECKBOX: @@ -1917,7 +1945,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_BUTTON: @@ -1927,7 +1955,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); } break; case CTRL_LISTBOX: @@ -1945,14 +1973,14 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND), dp->hwnd, wParam, lParam); if (pret & 2) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); ret = pret & 1; } else { if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) { SetCapture(dp->hwnd); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_SELCHANGE); } } break; @@ -1964,7 +1992,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (id == 2 && (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || @@ -1981,15 +2009,27 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = filename; - GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename)); - filename[lenof(filename)-1] = '\0'; + if (!ctrl->fileselect.just_button) { + GetDlgItemText(dp->hwnd, c->base_id+1, + filename, lenof(filename)); + filename[lenof(filename)-1] = '\0'; + } else { + *filename = '\0'; + } of.nMaxFile = lenof(filename); of.lpstrFileTitle = NULL; of.lpstrTitle = ctrl->fileselect.title; of.Flags = 0; if (request_file(NULL, &of, false, ctrl->fileselect.for_writing)) { - SetDlgItemText(dp->hwnd, c->base_id + 1, filename); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + if (!ctrl->fileselect.just_button) { + SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } else { + assert(!c->data); + c->data = filename; + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); + c->data = NULL; + } } } break; @@ -2034,7 +2074,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dlg_fontsel_set(ctrl, dp, fs); fontspec_free(fs); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } } break; @@ -2065,7 +2105,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dp->coloursel_result.ok = true; } else dp->coloursel_result.ok = false; - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK); + ctrl->handler(ctrl, dp, dp->data, EVENT_CALLBACK); } return ret; @@ -2094,12 +2134,12 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) /* * This is the Windows front end, so we're allowed to assume - * `helpctx.p' is a context string. + * `helpctx' is a context string. */ - if (!c->ctrl || !c->ctrl->generic.helpctx.p) + if (!c->ctrl || !c->ctrl->helpctx) return false; /* no help available for this ctrl */ - launch_help(hwnd, c->ctrl->generic.helpctx.p); + launch_help(hwnd, c->ctrl->helpctx); return true; } @@ -2108,7 +2148,7 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) * mechanism can call to access the dialog box entries. */ -static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) +static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, dlgcontrol *ctrl) { int i; @@ -2120,7 +2160,7 @@ static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) return NULL; } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { /* * In this implementation of the dialog box, we physically @@ -2131,91 +2171,99 @@ bool dlg_is_visible(union control *ctrl, dlgparam *dp) return dlg_findbyctrl(dp, ctrl) != NULL; } -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); CheckRadioButton(dp->hwnd, c->base_id + 1, c->base_id + c->ctrl->radio.nbuttons, c->base_id + 1 + whichbutton); } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int i; - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); for (i = 0; i < c->ctrl->radio.nbuttons; i++) if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i)) return i; unreachable("no radio button was checked"); } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); CheckDlgButton(dp->hwnd, c->base_id, checked); } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->type == CTRL_EDITBOX); + SendDlgItemMessage(dp->hwnd, c->base_id+1, EM_SETSEL, start, start+len); +} + /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_RESETCONTENT : CB_RESETCONTENT); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_DELETESTRING : CB_DELETESTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); } @@ -2227,39 +2275,39 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) * strings in any listbox then you MUST not assign them different * IDs and expect to get meaningful results back. */ -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, msg2, index; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); - msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_SETITEMDATA : CB_SETITEMDATA); + msg2 = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + LB_SETITEMDATA : CB_SETITEMDATA); index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id); } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->type == CTRL_LISTBOX); msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA); return SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, ret; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->type == CTRL_LISTBOX); if (c->ctrl->listbox.multisel) { assert(c->ctrl->listbox.height != 0); /* not combo box */ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0); @@ -2274,41 +2322,41 @@ int dlg_listbox_index(union control *ctrl, dlgparam *dp) return ret; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->type == CTRL_LISTBOX && c->ctrl->listbox.multisel && c->ctrl->listbox.height != 0); return SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0); } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->type == CTRL_LISTBOX && !c->ctrl->listbox.multisel); msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_TEXT); + assert(c && c->ctrl->type == CTRL_TEXT); SetDlgItemText(dp->hwnd, c->base_id, text); } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *escaped = NULL; int id = -1; assert(c); - switch (c->ctrl->generic.type) { + switch (c->ctrl->type) { case CTRL_EDITBOX: escaped = shortcut_escape(text, c->ctrl->editbox.shortcut); id = c->base_id; @@ -2331,7 +2379,10 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) break; case CTRL_FILESELECT: escaped = shortcut_escape(text, ctrl->fileselect.shortcut); - id = c->base_id; + if (ctrl->fileselect.just_button) + id = c->base_id + 2; /* the button */ + else + id = c->base_id; /* the label */ break; case CTRL_FONTSELECT: escaped = shortcut_escape(text, ctrl->fontselect.shortcut); @@ -2346,30 +2397,37 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) } } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + assert(!c->ctrl->fileselect.just_button); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; Filename *ret; - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); - ret = filename_from_str(tmp); - sfree(tmp); - return ret; + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + if (!c->ctrl->fileselect.just_button) { + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else { + return filename_from_str(c->data); + } } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { char *buf, *boldstr; struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); fontspec_free((FontSpec *)c->data); c->data = fontspec_copy(fs); @@ -2387,10 +2445,10 @@ void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) dlg_auto_set_fixed_pitch_flag(dp); } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); return fontspec_copy((FontSpec *)c->data); } @@ -2399,32 +2457,32 @@ FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, dlgparam *dp) +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); } } -void dlg_update_done(union control *ctrl, dlgparam *dp) +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { HWND hw = GetDlgItem(dp->hwnd, c->base_id+1); SendMessage(hw, WM_SETREDRAW, true, 0); InvalidateRect(hw, NULL, true); } } -void dlg_set_focus(union control *ctrl, dlgparam *dp) +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int id; HWND ctl; if (!c) return; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: id = c->base_id + 1; break; case CTRL_RADIO: for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) @@ -2475,7 +2533,7 @@ void dlg_end(dlgparam *dp, int value) dp->endresult = value; } -void dlg_refresh(union control *ctrl, dlgparam *dp) +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { int i, j; struct winctrl *c; @@ -2488,21 +2546,21 @@ void dlg_refresh(union control *ctrl, dlgparam *dp) for (i = 0; (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL; i++) { - if (c->ctrl && c->ctrl->generic.handler != NULL) - c->ctrl->generic.handler(c->ctrl, dp, - dp->data, EVENT_REFRESH); + if (c->ctrl && c->ctrl->handler != NULL) + c->ctrl->handler(c->ctrl, dp, + dp->data, EVENT_REFRESH); } } } else { /* * Send EVENT_REFRESH to a specific control. */ - if (ctrl->generic.handler != NULL) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + if (ctrl->handler != NULL) + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { dp->coloursel_wanted = true; dp->coloursel_result.r = r; @@ -2510,7 +2568,7 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) dp->coloursel_result.b = b; } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { if (dp->coloursel_result.ok) { diff --git a/code/windows/dialog.c b/code/windows/dialog.c index 38af0f9b..5e49cca9 100644 --- a/code/windows/dialog.c +++ b/code/windows/dialog.c @@ -27,21 +27,190 @@ #define ICON_BIG 1 #endif +typedef struct PortableDialogStuff { + /* + * These are the various bits of data required to handle a dialog + * box that's been built up from the cross-platform dialog.c + * system. + */ + + /* The 'controlbox' that was returned from the portable setup function */ + struct controlbox *ctrlbox; + + /* The dlgparam that's passed to all the runtime dlg_* functions. + * Declared as an array of 1 so it's convenient to pass it as a pointer. */ + struct dlgparam dp[1]; + + /* + * Collections of instantiated controls. There can be more than + * one of these, because sometimes you want to destroy and + * recreate a subset of them - e.g. when switching panes in the + * main PuTTY config box, you delete and recreate _most_ of the + * controls, but not the OK and Cancel buttons at the bottom. + */ + size_t nctrltrees; + struct winctrls *ctrltrees; + + /* + * Flag indicating whether the dialog box has been initialised. + * Used to suppresss spurious firing of message handlers during + * setup. + */ + bool initialised; +} PortableDialogStuff; + /* - * These are the various bits of data required to handle the - * portable-dialog stuff in the config box. Having them at file - * scope in here isn't too bad a place to put them; if we were ever - * to need more than one config box per process we could always - * shift them to a per-config-box structure stored in GWL_USERDATA. + * Initialise a PortableDialogStuff, before launching the dialog box. */ -static struct controlbox *ctrlbox; +static PortableDialogStuff *pds_new(size_t nctrltrees) +{ + PortableDialogStuff *pds = snew(PortableDialogStuff); + memset(pds, 0, sizeof(*pds)); + + pds->ctrlbox = ctrl_new_box(); + + dp_init(pds->dp); + + pds->nctrltrees = nctrltrees; + pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls); + for (size_t i = 0; i < pds->nctrltrees; i++) { + winctrl_init(&pds->ctrltrees[i]); + dp_add_tree(pds->dp, &pds->ctrltrees[i]); + } + + pds->dp->errtitle = dupprintf("%s Error", appname); + + pds->initialised = false; + + return pds; +} + +static void pds_free(PortableDialogStuff *pds) +{ + ctrl_free_box(pds->ctrlbox); + + dp_cleanup(pds->dp); + + for (size_t i = 0; i < pds->nctrltrees; i++) + winctrl_cleanup(&pds->ctrltrees[i]); + sfree(pds->ctrltrees); + + sfree(pds); +} + +static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_LBUTTONUP: + /* + * Button release should trigger WM_OK if there was a + * previous double click on the host CA list. + */ + ReleaseCapture(); + if (pds->dp->ended) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); + break; + case WM_COMMAND: + case WM_DRAWITEM: + default: { /* also handle drag list msg here */ + /* + * Only process WM_COMMAND once the dialog is fully formed. + */ + int ret; + if (pds->initialised) { + ret = winctrl_handle_command(pds->dp, msg, wParam, lParam); + if (pds->dp->ended && GetCapture() != hwnd) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); + } else + ret = 0; + return ret; + } + case WM_HELP: + if (!winctrl_context_help(pds->dp, + hwnd, ((LPHELPINFO)lParam)->iCtrlId)) + MessageBeep(0); + break; + case WM_CLOSE: + quit_help(hwnd); + ShinyEndDialog(hwnd, 0); + return 0; + + /* Grrr Explorer will maximize Dialogs! */ + case WM_SIZE: + if (wParam == SIZE_MAXIMIZED) + force_normal(hwnd); + return 0; + + } + return 0; +} + +static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd) +{ + pds->dp->hwnd = hwnd; + + if (pds->dp->wintitle) /* apply override title, if provided */ + SetWindowText(hwnd, pds->dp->wintitle); + + /* The portable dialog system generally includes the ability to + * handle context help for particular controls. Enable the + * relevant window styles if we have a help file available. */ + if (has_help()) { + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP); + } else { + /* If not, and if the dialog template provided a top-level + * Help button, delete it */ + HWND item = GetDlgItem(hwnd, IDC_HELPBTN); + if (item) + DestroyWindow(item); + } +} + /* - * ctrls_base holds the OK and Cancel buttons: the controls which - * are present in all dialog panels. ctrls_panel holds the ones - * which change from panel to panel. + * Create the panelfuls of controls in the configuration box. */ -static struct winctrls ctrls_base, ctrls_panel; -static struct dlgparam dp; +static void pds_create_controls( + PortableDialogStuff *pds, size_t which_tree, int base_id, + int left, int right, int top, char *path) +{ + struct ctlpos cp; + + ctlposinit(&cp, pds->dp->hwnd, left, right, top); + + for (int index = -1; (index = ctrl_find_path( + pds->ctrlbox, path, index)) >= 0 ;) { + struct controlset *s = pds->ctrlbox->ctrlsets[index]; + winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id); + } +} + +static void pds_initdialog_finish(PortableDialogStuff *pds) +{ + /* + * Set focus into the first available control in ctrltree #0, + * which the caller was expected to set up to be the one + * containing the dialog controls likely to be used first. + */ + struct winctrl *c; + for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL; + i++) { + if (c->ctrl) { + dlg_set_focus(c->ctrl, pds->dp); + break; + } + } + + /* + * Now we've finished creating our initial set of controls, + * it's safe to actually show the window without risking setup + * flicker. + */ + ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL); + + pds->initialised = true; +} #define LOGEVENT_INITIAL_MAX 128 #define LOGEVENT_CIRCULAR_MAX 128 @@ -216,10 +385,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, SetWindowText(hwnd, str); sfree(str); char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - appname, ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + appname, ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, IDA_TEXT, text); MakeDlgItemBorderless(hwnd, IDA_TEXT); @@ -255,57 +424,6 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, return 0; } -static int SaneDialogBox(HINSTANCE hinst, - LPCTSTR tmpl, - HWND hwndparent, - DLGPROC lpDialogFunc) -{ - WNDCLASS wc; - HWND hwnd; - MSG msg; - int flags; - int ret; - int gm; - - wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; - wc.lpfnWndProc = DefDlgProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR); - wc.hInstance = hinst; - wc.hIcon = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); - wc.lpszMenuName = NULL; - wc.lpszClassName = "PuTTYConfigBox"; - RegisterClass(&wc); - - hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc); - - SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */ - SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */ - - while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) { - flags=GetWindowLongPtr(hwnd, BOXFLAGS); - if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg)) - DispatchMessage(&msg); - if (flags & DF_END) - break; - } - - if (gm == 0) - PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ - - ret=GetWindowLongPtr(hwnd, BOXRESULT); - DestroyWindow(hwnd); - return ret; -} - -static void SaneEndDialog(HWND hwnd, int ret) -{ - SetWindowLongPtr(hwnd, BOXRESULT, ret); - SetWindowLongPtr(hwnd, BOXFLAGS, DF_END); -} - /* * Null dialog procedure. */ @@ -355,84 +473,37 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff, return newitem; } -/* - * Create the panelfuls of controls in the configuration box. - */ -static void create_controls(HWND hwnd, char *path) -{ - struct ctlpos cp; - int index; - int base_id; - struct winctrls *wc; - - if (!path[0]) { - /* - * Here we must create the basic standard controls. - */ - ctlposinit(&cp, hwnd, 3, 3, 235); - wc = &ctrls_base; - base_id = IDCX_STDBASE; - } else { - /* - * Otherwise, we're creating the controls for a particular - * panel. - */ - ctlposinit(&cp, hwnd, 100, 3, 13); - wc = &ctrls_panel; - base_id = IDCX_PANELBASE; - } - - for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) { - struct controlset *s = ctrlbox->ctrlsets[index]; - winctrl_layout(&dp, wc, &cp, s, &base_id); - } -} - const char *dialog_box_demo_screenshot_filename = NULL; +/* ctrltrees indices for the main dialog box */ +enum { + TREE_PANEL, /* things we swap out every time treeview selects a new pane */ + TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */ +}; + /* * This function is the configuration box. * (Being a dialog procedure, in general it returns 0 if the default * dialog processing should be performed, and 1 if it should not.) */ -static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *ctx) { + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; const int DEMO_SCREENSHOT_TIMER_ID = 1230; - HWND hw, treeview; + HWND treeview; struct treeview_faff tvfaff; - int ret; switch (msg) { case WM_INITDIALOG: - dp.hwnd = hwnd; - create_controls(hwnd, ""); /* Open and Cancel buttons etc */ - SetWindowText(hwnd, dp.wintitle); - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - else { - HWND item = GetDlgItem(hwnd, IDC_HELPBTN); - if (item) - DestroyWindow(item); - } + pds_initdialog_start(pds, hwnd); + + pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, ""); + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } + + centre_window(hwnd); /* * Create the tree view. @@ -485,8 +556,8 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, char *path = NULL; char *firstpath = NULL; - for (i = 0; i < ctrlbox->nctrlsets; i++) { - struct controlset *s = ctrlbox->ctrlsets[i]; + for (i = 0; i < pds->ctrlbox->nctrlsets; i++) { + struct controlset *s = pds->ctrlbox->ctrlsets[i]; HTREEITEM item; int j; char *c; @@ -511,9 +582,9 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, c = strrchr(s->pathname, '/'); if (!c) - c = s->pathname; + c = s->pathname; else - c++; + c++; item = treeview_insert(&tvfaff, j, c, s->pathname); if (!hfirst) { @@ -535,64 +606,32 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, * match the initial treeview selection. */ assert(firstpath); /* config.c must have given us _something_ */ - create_controls(hwnd, firstpath); - dlg_refresh(NULL, &dp); /* and set up control values */ - } - - /* - * Set focus into the first available control. - */ - { - int i; - struct winctrl *c; - - for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL; - i++) { - if (c->ctrl) { - dlg_set_focus(c->ctrl, &dp); - break; - } - } + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, firstpath); + dlg_refresh(NULL, pds->dp); /* and set up control values */ } - /* - * Now we've finished creating our initial set of controls, - * it's safe to actually show the window without risking setup - * flicker. - */ - ShowWindow(hwnd, SW_SHOWNORMAL); - - /* - * Set the flag that activates a couple of the other message - * handlers below, which were disabled until now to avoid - * spurious firing during the above setup procedure. - */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); - if (dialog_box_demo_screenshot_filename) SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); + + pds_initdialog_finish(pds); return 0; + case WM_TIMER: if (dialog_box_demo_screenshot_filename && (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); - const char *err = save_screenshot( + char *err = save_screenshot( hwnd, dialog_box_demo_screenshot_filename); - if (err) + if (err) { MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); - SaneEndDialog(hwnd, 0); + sfree(err); + } + ShinyEndDialog(hwnd, 0); } return 0; - case WM_LBUTTONUP: - /* - * Button release should trigger WM_OK if there was a - * previous double click on the session list. - */ - ReleaseCapture(); - if (dp.ended) - SaneEndDialog(hwnd, dp.endresult ? 1 : 0); - break; + case WM_NOTIFY: if (LOWORD(wParam) == IDCX_TREEVIEW && ((LPNMHDR) lParam)->code == TVN_SELCHANGED) { @@ -607,7 +646,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, TVITEM item; char buffer[64]; - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) + if (!pds->initialised) return 0; i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); @@ -625,60 +664,34 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, HWND item; struct winctrl *c; - while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) { + while ((c = winctrl_findbyindex( + &pds->ctrltrees[TREE_PANEL], 0)) != NULL) { for (k = 0; k < c->num_ids; k++) { item = GetDlgItem(hwnd, c->base_id + k); if (item) DestroyWindow(item); } - winctrl_rem_shortcuts(&dp, c); - winctrl_remove(&ctrls_panel, c); + winctrl_rem_shortcuts(pds->dp, c); + winctrl_remove(&pds->ctrltrees[TREE_PANEL], c); sfree(c->data); sfree(c); } } - create_controls(hwnd, (char *)item.lParam); + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, (char *)item.lParam); - dlg_refresh(NULL, &dp); /* set up control values */ + dlg_refresh(NULL, pds->dp); /* set up control values */ SendMessage (hwnd, WM_SETREDRAW, true, 0); InvalidateRect (hwnd, NULL, true); SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ - return 0; } - break; - case WM_COMMAND: - case WM_DRAWITEM: - default: /* also handle drag list msg here */ - /* - * Only process WM_COMMAND once the dialog is fully formed. - */ - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) { - ret = winctrl_handle_command(&dp, msg, wParam, lParam); - if (dp.ended && GetCapture() != hwnd) - SaneEndDialog(hwnd, dp.endresult ? 1 : 0); - } else - ret = 0; - return ret; - case WM_HELP: - if (!winctrl_context_help(&dp, hwnd, - ((LPHELPINFO)lParam)->iCtrlId)) - MessageBeep(0); - break; - case WM_CLOSE: - quit_help(hwnd); - SaneEndDialog(hwnd, 0); - return 0; - - /* Grrr Explorer will maximize Dialogs! */ - case WM_SIZE: - if (wParam == SIZE_MAXIMIZED) - force_normal(hwnd); return 0; + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); } - return 0; } void modal_about_box(HWND hwnd) @@ -714,29 +727,22 @@ void defuse_showwindow(void) bool do_config(Conf *conf) { bool ret; + PortableDialogStuff *pds = pds_new(2); + + setup_config_box(pds->ctrlbox, false, 0, 0); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0); + + pds->dp->wintitle = dupprintf("%s Configuration", appname); + pds->dp->data = conf; - ctrlbox = ctrl_new_box(); - setup_config_box(ctrlbox, false, 0, 0); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), false, 0); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Configuration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = - SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); - - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_panel); - winctrl_cleanup(&ctrls_base); - dp_cleanup(&dp); + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, pds); + + pds_free(pds); return ret; } @@ -746,31 +752,26 @@ bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) Conf *backup_conf; bool ret; int protocol; + PortableDialogStuff *pds = pds_new(2); backup_conf = conf_copy(conf); - ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, true, protocol, protcfginfo); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Reconfiguration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); - - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_base); - winctrl_cleanup(&ctrls_panel); - dp_cleanup(&dp); + setup_config_box(pds->ctrlbox, true, protocol, protcfginfo); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), + true, protocol); + + pds->dp->wintitle = dupprintf("%s Reconfiguration", appname); + pds->dp->data = conf; + + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, pds); + + pds_free(pds); if (!ret) conf_copy_into(conf, backup_conf); @@ -841,97 +842,232 @@ void showabout(HWND hwnd) } struct hostkey_dialog_ctx { - const char *const *keywords; - const char *const *values; - const char *host; - int port; - FingerprintType fptype_default; - char **fingerprints; - const char *keydisp; - LPCTSTR iconid; + SeatDialogText *text; + bool has_title; const char *helpctx; }; -static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + int index = 100, y = 12; + + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); + + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 376; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 376; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 80; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 100; + rv.right = 288; + rv.top = y; + rv.bottom = 12; + + y += 16; + } - if (ctx->fingerprints[SSH_FPTYPE_SHA256]) - SetDlgItemText(hwnd, IDC_HKI_SHA256, - ctx->fingerprints[SSH_FPTYPE_SHA256]); - if (ctx->fingerprints[SSH_FPTYPE_MD5]) - SetDlgItemText(hwnd, IDC_HKI_MD5, - ctx->fingerprints[SSH_FPTYPE_MD5]); + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } - SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + ShowWindow(hwnd, SW_SHOWNORMAL); return 1; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; case WM_CLOSE: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; } -static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { - strbuf *sb = strbuf_new(); - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); - for (int id = 100;; id++) { - char buf[256]; - - if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = ""; + ctx->has_title = false; + LPCTSTR iconid = IDI_QUESTION; + + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_DISPLAY: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_SCARY_HEADING: + SetDlgItemText(hwnd, IDC_HK_TITLE, item->text); + iconid = IDI_WARNING; + ctx->has_title = true; + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: break; - - strbuf_clear(sb); - for (const char *p = buf; *p ;) { - if (*p == '{') { - for (size_t i = 0; ctx->keywords[i]; i++) { - if (strstartswith(p, ctx->keywords[i])) { - p += strlen(ctx->keywords[i]); - put_dataz(sb, ctx->values[i]); - goto matched; - } - } - } else { - put_byte(sb, *p++); - } - matched:; } + } + while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); + + SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); + MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); + strbuf_free(dlg_text); - SetDlgItemText(hwnd, id, sb->s); + SetWindowText(hwnd, dlg_title); + + if (!ctx->has_title) { + HWND item = GetDlgItem(hwnd, IDC_HK_TITLE); + if (item) + DestroyWindow(item); + } + + /* + * Find out how tall the text in the edit control really ended + * up (after line wrapping), and adjust the height of the + * whole box to match it. + */ + int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT, + EM_GETLINECOUNT, 0, 0); + height *= 8; /* height of a text line, by definition of dialog units */ + + int edittop = ctx->has_title ? 40 : 20; + + RECT r; + r.left = 40; + r.top = edittop; + r.right = 290; + r.bottom = height; + MapDialogRect(hwnd, &r); + SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL, + r.left, r.top, r.right, r.bottom, + SWP_NOREDRAW | SWP_NOZORDER); + + static const struct { + int id, x; + } buttons[] = { + { IDCANCEL, 288 }, + { IDC_HK_ACCEPT, 168 }, + { IDC_HK_ONCE, 216 }, + { IDC_HK_MOREINFO, 60 }, + { IDHELP, 12 }, + }; + for (size_t i = 0; i < lenof(buttons); i++) { + HWND ctl = GetDlgItem(hwnd, buttons[i].id); + r.left = buttons[i].x; + r.top = edittop + height + 20; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); } - strbuf_free(sb); - char *hostport = dupprintf("%s (port %d)", ctx->host, ctx->port); - SetDlgItemText(hwnd, IDC_HK_HOST, hostport); - sfree(hostport); - MakeDlgItemBorderless(hwnd, IDC_HK_HOST); + r.left = r.top = r.right = 0; + r.bottom = 240; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; - SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, - ctx->fingerprints[ctx->fptype_default]); - MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + r.left = r.top = r.right = 0; + r.bottom = edittop + height + 40; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); HANDLE icon = LoadImage( - NULL, ctx->iconid, IMAGE_ICON, + NULL, iconid, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED); SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); @@ -942,13 +1078,16 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, DestroyWindow(item); } + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; HWND control = (HWND)lParam; - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE && + ctx->has_title) { SetBkMode(hdc, TRANSPARENT); HFONT prev_font = (HFONT)SelectObject( hdc, (HFONT)GetStockObject(SYSTEM_FONT)); @@ -969,60 +1108,51 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, case IDC_HK_ACCEPT: case IDC_HK_ONCE: case IDCANCEL: - EndDialog(hwnd, LOWORD(wParam)); + ShinyEndDialog(hwnd, LOWORD(wParam)); return 0; case IDHELP: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); launch_help(hwnd, ctx->helpctx); return 0; } case IDC_HK_MOREINFO: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), - hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), + "PuTTYHostKeyMoreInfo", hwnd, + HostKeyMoreInfoProc, ctx); } } return 0; case WM_CLOSE: - EndDialog(hwnd, IDCANCEL); + ShinyEndDialog(hwnd, IDCANCEL); return 0; } return 0; } +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, SeatPromptResult result), void *vctx) + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *cbctx) { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (mismatch ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (mismatch ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - ctx->host = host; - ctx->port = port; - int dlgid = (mismatch ? IDD_HK_WRONG : IDD_HK_ABSENT); - int mbret = DialogBoxParam( - hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, - HostKeyDialogProc, (LPARAM)ctx); + ctx->text = text; + ctx->helpctx = helpctx; + + int mbret = ShinyDialogBox( + hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog", + wgs->term_hwnd, HostKeyDialogProc, ctx); assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { store_host_key(host, port, keytype, keystr); @@ -1172,3 +1302,41 @@ void old_keyfile_warning(void) sfree(msg); sfree(title); } + +static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, + void *ctx) +{ + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; + + switch (msg) { + case WM_INITDIALOG: + pds_initdialog_start(pds, hwnd); + + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); + + centre_window(hwnd); + + pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main"); + pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, ""); + dlg_refresh(NULL, pds->dp); /* and set up control values */ + + pds_initdialog_finish(pds); + return 0; + + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); + } +} + +void show_ca_config_box(dlgparam *dp) +{ + PortableDialogStuff *pds = pds_new(1); + + setup_ca_config_box(pds->ctrlbox); + + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox", + dp ? dp->hwnd : NULL, CAConfigProc, pds); + + pds_free(pds); +} diff --git a/code/windows/gss.c b/code/windows/gss.c index cadb1d5b..724eeec1 100644 --- a/code/windows/gss.c +++ b/code/windows/gss.c @@ -91,8 +91,6 @@ typedef struct winSsh_gss_ctx { const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; -const char *gsslogmsg = NULL; - static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); static tree234 *libraries_to_never_unload; @@ -227,7 +225,8 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; lib->handle = (void *)module; - GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + /* No typecheck because Winelib thinks one PVOID is a PLUID */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, AcquireCredentialsHandleA); GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); GET_WINDOWS_FUNCTION(module, FreeContextBuffer); GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); @@ -664,8 +663,8 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, InputSecurityToken[1].pvBuffer = mic->value; winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); + &InputBufferDescriptor, + 0, &qop); return winctx->maj_stat; } diff --git a/code/windows/handle-socket.c b/code/windows/handle-socket.c index 0b0e60a9..2820975c 100644 --- a/code/windows/handle-socket.c +++ b/code/windows/handle-socket.c @@ -388,6 +388,13 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, return &hs->sock; } +void handle_socket_set_psb_prefix(Socket *s, const char *prefix) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_sockvt); + psb_set_prefix(&hs->psb, prefix); +} + static void sk_handle_deferred_close(Socket *s) { HandleSocket *hs = container_of(s, HandleSocket, sock); diff --git a/code/windows/help.c b/code/windows/help.c index bff2ee5c..d087c722 100644 --- a/code/windows/help.c +++ b/code/windows/help.c @@ -149,7 +149,7 @@ static bool find_chm_from_installation(void) }; for (size_t i = 0; i < lenof(reg_paths); i++) { - char *filename = registry_get_string( + char *filename = get_reg_sz_simple( HKEY_LOCAL_MACHINE, reg_paths[i], NULL); if (filename) { diff --git a/code/windows/help.h b/code/windows/help.h index 0ab0e050..de6ec0be 100644 --- a/code/windows/help.h +++ b/code/windows/help.h @@ -9,7 +9,9 @@ /* These are used in the cross-platform configuration dialog code. */ -#define HELPCTX(x) P(WINHELP_CTX_ ## x) +typedef const char *HelpCtx; +#define NULL_HELPCTX NULL +#define HELPCTX(x) WINHELP_CTX_ ## x #define WINHELP_CTX_no_help NULL @@ -111,10 +113,15 @@ #define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" +#define WINHELP_CTX_ssh_kex_cert "config-ssh-kex-cert" +#define WINHELP_CTX_ssh_cert_valid_expr "config-ssh-cert-valid-expr" +#define WINHELP_CTX_ssh_cert_rsa_hash "config-ssh-cert-rsa-hash" #define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" #define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_plugin "config-ssh-authplugin" +#define WINHELP_CTX_ssh_auth_cert "config-ssh-cert" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" #define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" #define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" @@ -164,6 +171,7 @@ #define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" #define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" #define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart" +#define WINHELP_CTX_ssh_bugs_filter_kexinit "config-ssh-bug-filter-kexinit" #define WINHELP_CTX_serial_line "config-serial-line" #define WINHELP_CTX_serial_speed "config-serial-speed" #define WINHELP_CTX_serial_databits "config-serial-databits" @@ -191,6 +199,7 @@ #define WINHELP_CTX_puttygen_conversions "puttygen-conversions" #define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" #define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" +#define WINHELP_CTX_errors_cert_mismatch "errors-cert-mismatch" /* These are used in Windows-specific bits of the frontend. * We (ab)use "help context identifiers" (dwContextId) to identify them. */ diff --git a/code/windows/installer.wxs b/code/windows/installer.wxs index bfcc06dd..acd3bf26 100644 --- a/code/windows/installer.wxs +++ b/code/windows/installer.wxs @@ -100,7 +100,6 @@ -