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