From f5610c888f9080ca0f08aaa8516f27ba42c236d8 Mon Sep 17 00:00:00 2001 From: thb-sb Date: Wed, 17 Sep 2025 08:18:22 +0200 Subject: [PATCH] Add support for post-quantum hybrid key exchange --- .../client_kex_init.raw | Bin 0 -> 1296 bytes .../init.raw | Bin 0 -> 880 bytes .../reply.raw | Bin 0 -> 1756 bytes .../server_kex_init.raw | Bin 0 -> 3112 bytes .../client_kex_init.raw | Bin 0 -> 1296 bytes .../init.raw | Bin 0 -> 1296 bytes .../reply.raw | Bin 0 -> 2108 bytes .../server_kex_init.raw | Bin 0 -> 3112 bytes .../client_kex_init.raw | Bin 0 -> 1296 bytes .../init.raw | Bin 0 -> 1720 bytes .../reply.raw | Bin 0 -> 2628 bytes .../server_kex_init.raw | Bin 0 -> 3112 bytes src/kex.rs | 179 +++++++++++++++++- src/lib.rs | 3 +- tests/tests_kex.rs | 79 ++++++++ 15 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/client_kex_init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/reply.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/server_kex_init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/client_kex_init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/reply.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/server_kex_init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/client_kex_init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/init.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/reply.raw create mode 100644 assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/server_kex_init.raw diff --git a/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/client_kex_init.raw b/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/client_kex_init.raw new file mode 100644 index 0000000000000000000000000000000000000000..8af5c6c1b046311db770c95b2dca28ac7f5a8262 GIT binary patch literal 1296 zcmds0F;2rU6g5L97@0VOe-)=q)h-Cplen(iL`~viJBYF|Z~z7-ZiJASI7hF*cWBc( zNlP~*q73%?@B6>^;tK>SwJ7^jNH)<*KyMpJo$PAy!uF`WVA1oyKOo1Te?9-SS8L5alzF`1Mm9AJXh`d%V4GV`Ltfp=`!X$o{rR5EZgt^G- zeU4Fs|6tx%k#PZoK1k~;EOFSRSnm0%%N1CHJkQfu>oP!#=43j~v z0(ZN>g(dQJSp&9ah`Bws=3gQ}{rHOmc#5PT)lxG72+cgKWAIfl_g z&*8Z=!H7c;c3FBhAU4uxnbb!S!Y@gxMBlb_fWfgmVrj1sR+cpzIT;WiyU#{Mst$pj zkrWjVB4+LDv}hzbv*o}V@Ks|NBqe1H{B&HeYb}#2d%{B%J29iM83my-C-spS=e$py zv`B8yS{Ok>sL-Xk79-LEufJlpU#A;6M?m>ZMuSVM4&lxPhh801%`|{1wboAS(4yBH zj4w+GU0`Ckq>`V3P_QIc(F2@t*o)*A7xh6&emS3*OdU0tlu7`RXG~uz1}(NGA5C$9 zd)HO9m|4?;P?ZuMW%8FkhMzB)9oE9XhPZn2S7=`Kch|(M`&_+S46=_0pSQ(>=76OD{RqM4aqP~^~G}*f;+MKzuekx zK|)_f*dL>um%E1{d+=UflCCRe$Jl|S7b~i2i>Fk%8hJ~g)A=_f7Q6W2h{TLlW71-S*ZLF zsH#E(DH|D_kx1!=aM zNwT#C+T|j~F(ZRrJ-g%}8-KX?sg=+ah_(b)>jMq6>__UP0Cj9Fy9~_IKYlst!4Cic G0002m4xh3B literal 0 HcmV?d00001 diff --git a/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/reply.raw b/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/reply.raw new file mode 100644 index 0000000000000000000000000000000000000000..1e24d6ea974b16b921acaaefaf699e063ce2580e GIT binary patch literal 1756 zcmV<21|#_Z00Z0y9{>OVGXMYp3v+X5EoEdfH8n9g0000WJI?K!@g+XRF0K-11lL`? z(4CAFlZ9ODo9-N;nvqyO2}%yovy%1;Qly#kdCYn`v%7ZUU z3789!OX}<>X|cH8e=8|nUQ)tRVr2($sl*|5Az0G4Cd(CarAL}^@SaPtXVXJ%2GLGJ z?H(+Aa>XpGbzDup5m}%Be#Qso6$ZXo+_xd`HXog6<9gGNK9A}ynw;;aBE%ChGR>q) zL>~dQRTd5-9`CyHhxQ2ne=lpSw~c9NL44I=p&hwBgJ$SMRg}tAV~*mSIAe{Cc6oRf zygfXVCZtgnCq$%TAzb)Eh_PI^*jZ>Wx)f>&|31SvyEC8WlSUx6eXp;`M=dr zx)0c2w1?qYN=M_*D`b%$WBj}X}}4fE5rD3r+|0UN3;6W_~ixpPPlz zB_B*ecIwU;83wm_W#N^z&eOU`d>zNhhNXeB2mASE)5`4Acr>+tz7|AyWQHtTw+!#h zn9lEOi|O|KcreG4$zDvVh6kk$q>XaOz4M1UAs%6WP22oM9qU9sf;&Xrwm*Yh<{>V; zTKvg-LuJnb6IlW<&m@30*UU~UgtSTkc=y0;*BFZbBb-K}?Kk?W1d@y%*&m;`d>>Ij zEfQ+Qifc9Hin!@xQ!q1UOyG~ z&j)fy`IwW11Yi3_&WpL~O$`ad_G@6|1;H@ph^||kg^0CpNDL}AeYbCDRIX|-T(jqQvd(}3v+X5EoEdfH8n9g0000$Y+D-q9&}hH zLoW3W65R7;Gaa^;YhWln#wWs0K%{rWNBfi(ZZ)*CVhafwc+n+p7GROLW)RoyuogEm zxE2=&0000000000000aM6#xJL000000001=vemmw7Xz^>CD(Nii66XwT)Fpsgz#a@ z&=m*O-V=wNQh;%RuWhiC-&W^5)(n3ab2B2b<6#Mt?fyFRDmEK5OR^?Nem-7$fD!0< zRTpNkfH6ZH;c}{`_@1ZFMG>v^zdVq~Qm|gx65hU?rX}XOyee_m>ft^f{>I}>a`6l5 zPg?~xYMRAo2K>Dhz{rt40;Q3xfLm|}B~wB~$OSf`2q*Su44b;EP{UW%3SG4jqi(PM4W+@@;=;5d)Ds~RBg2|{g5hg^#KT%zG%e#5X911AA?oz za4oI zT(o>Kl)QUc0vPaWbTfq~_U-=mN6@G<{h`%&BH)3mM*-eC!CI!^Sn~eqAN`Z>l%$$S yvE{mZ!P-d0q%93w>XnU)>b)CVr!J3A?~OIQqOEsZR{8%N1uMrCP45|VV1?~wTtfW- literal 0 HcmV?d00001 diff --git a/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/server_kex_init.raw b/assets/kex/kex-hybrid/ecdh-nistp256-kyber-512r3-sha256-d00/server_kex_init.raw new file mode 100644 index 0000000000000000000000000000000000000000..1a66080dd0c6c1d6b28f551ba23c89cb76195983 GIT binary patch literal 3112 zcmds3yOPr|6m>(f110r{}W|tXi3h4O>{)7q!K81eo z)ywN=;%%5AlOonRI`^I zBhsX?>j(B=YdHmHWfrtX3g<{{NB(i^!h`-OYNpS)~CZdptq)&Pu=V4 zz)RF-Rbg+lB*{2Qd7c+>Np^L0IuL%#(zx7l-Q5_828e>(s00mXQBNx;b+xVbydZ(+ zkeD}mT=>&&NuO1_iYKhxoZ|9$Dw;UqA}+&@)`8YE0SQfbYBd#0Y63E^NmrhyMbUiQ zC4+9fR1kf z1J^4>AX~>7T`5Uj2Vw&bEgM)W;ZB%WqK+$e>(Hc>LR@NL_fuM%4AwTM*nC$Amsf#C zWz?4>3Hx0Iw4lIw1(wH6lQB{-o@bnKvhu<(Ft1{F%T17LO&{%XVp1}`s8j5PHN3zH zBXbzFE-;rt2}za}AjZpF$VtM*F#3rS$-){z5Qfw>r)FydkCj0U#eQ6k5hHU&7CVUo zhsuApZd&6-Bzvh7IqPC4ioUQ)NF(?1HBs!x)wtA&DspB`(i=upSn}bVAZCbOt-+?e+=Zd;LTq$p6;Zi?S-Y{Sof!4|4&>lK^1-&po5v B6SV*U literal 0 HcmV?d00001 diff --git a/assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/client_kex_init.raw b/assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/client_kex_init.raw new file mode 100644 index 0000000000000000000000000000000000000000..0120d737c7c142f2c1deacff2ac67ddbe0083dbd GIT binary patch literal 1296 zcmdr~F;2rU6g3jv7-39(<>5 zTDK`=Ln88EzyH4fdoL!WbJRJ0IUj$1y*}UEnv=t`yZ1{%$hDHA2(s7|Gr!-1>3pbi zxCr~X4<-^88hPH8o~i7<6j@QGMvPTg=M$NMs~%aSvuXnKR!DoK~R><9(1tPVIJpNi4RN-m)r>76j>sohI|_gcxKM~U zUmoId1X8NAR*uGaKb)dRI}a?v@itc1%nYnzp8(`AWo&oUdKtx%}F2 z|07?Of~7+Rvc#nf!*X#odm+uyoL1E_(W*u~sLoX?87Wf>@9BB^%q97_X+m|Zd8sE6 z6(FW5c)bxUoK%mgnxAi3g#-*k7!@ezT&6}7H&tX%3`#DTS~J$_j|^#D^z?JCDq%%J zUty?)*}^pGsf&%lsm=O2YDFAZ7YX%Ry=ky)jEQp1+P}HTyI^&}f#pLAI6b9xVSs#H zOV*q&Ib8vyBuu#szH%NjIA`*SMi#X=V(~(nEO=V%ZbHO|v)~F|pglw8%r2LbOb|JO zg)|@*iNyJzu$xg?F>y*5NBv}4S-=%_f-FW_mT$q8nu9bZ@-(wiC(SZ>(fl@d8?6hd z5mKdQt-~K;Gl+?supf4spoq0m#+k0CF)hMVWh}_O7#D_mQfN4_qr@(0gAmMlfR}t= zzif=m2(7^jX)@Mqso*)dak6h$tCaS#Ty!;e6%lEMTq(?fOZCn>MICq728I)!li^PC zcs=mik8b3ckKmS7+h>01FuU3mwRBMLykSb~t}lg42upKUY0hzEk`icsn5)AH*mk4{ z9McpPj3{#^*JWY}&%Py$zqWdRhCM3*uE9$?VOeQJWu^t%1gP^V5_A-em8qkAh`S4| zJkWt)6dM9bd=WXM8Kx9dVGh-Ii&QEd(zK0@V?XWaVilo4d&hz_T7_b!5~8_-)-XHW z#A)fcT;S=#vsq%?wyccV3^Kben^tB_vy(Nsg0VP)h*?5dL3)P6D%3Wqo1wt6fNL}X zLy34;3)Cudj0ECUwl4M z7KG7a#dc%~8e|_rya%0Pm|LPjtvR@Bu%$_sSn3Rf@8dvWO1bS48U)B)b`)%V$h}4( zHW))@);cHPBW8b4Q|DC|-RuiPbZXT$dQ6oBh8KubGqbS7q>X%}7@(I#FeN|fbdxP6+iaZ0Ryo=bz)p#@Cp02uibf&@PjQE z2akV83g+ibbn1W_+r3ohfZM<|W1O`9h&TX~ud|Dh@L@Gj>WZ2Ok90e*{o1{}SungR zci<5SJmDcUArt&)D<=}3Ttq~S2dIl-a?Zgy-zjo7M1`8Fpq)^8SsX0rD?q{&Gp4{W zSh^ZyV1y0W8#$Sl$L52WwQ!9v2YLolbr&_hp_l%QPa8I#*(R?gz)_?JGccr!a)&v{ zB3D@fK27v`1IbxPv;@v!1IqNBM|-8-dx-G%pb!D7G9k^$bZX{coJB{EUHG|GlvOZ^ zMKrpD9TOLW+*ATUC6TaAW%mGxihfC#B}Vfj$t9?cP8*8M_{>TG9%g6+;V!iowo|z$@e{H#aKkF`a8a4>IIATs7}PkEWcvUB G0002LNmf4q literal 0 HcmV?d00001 diff --git a/assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/reply.raw b/assets/kex/kex-hybrid/ecdh-nistp384-kyber-768r3-sha384-d00/reply.raw new file mode 100644 index 0000000000000000000000000000000000000000..11be790ef4dff8265b235fcb41a5616bf06fc88e GIT binary patch literal 2108 zcmV-C2*dXP00le;9{>OVGXMYp3v+X5EoEdfH8n9g0000WJI?K!@g+XRF0K-11lL`? z(4CAFlZ9ODM-GZOIWddZX&MlrEsMuJkO~P)1ltMks)3&Q}mFbD&11^iC$gwTHsJ^-e9NSI*w6+NHz3=U6LZ zuKXNLleP1NgX-|_Yj(zZB(k}o-qm-F7IJ{N)@~!O7}@vHZ8;~*Rdr3Jso{UKmoSWP z(}#Gs*{7z^VB3f)TQ*lX?duYFa?Dbtq1SzLt{6}IsU}8(7t+(YG!3;gDYUWbgdUrt zTF4{+1IehD#Wmv0hy;AZH$giG$}^3D0?c@{p*`555N+jYv{faTOyYz;Zf9<(t}KK| zsWg~p0C$Mb?tyh3L9md)fI&Q-(;|;w%q9GtBqglm9mrmy;gL=MB1m-?3Qq2ec1zm zS3P>sx?|CDnc44&CWGpruWBy$<)MP5) z$e#76x9VT^!*_nSNDS#EMFpUZln&ErOIlm4H?iz#K*vxj7+>DIZDLY$S~1~LzyIgB z$npH%RNqT#b~<%#RjotlC~Tl%^F@Vs&HVcB6627)!gV?mPdXJ#^6-)V+6LNk*-BI` zFhp_fbjLEdA9YX135@9)wO?@FX5;MN4H~bLgb@3lz|+~7qOe5W2j(!aCTJ5M_<+uQ zTaYuU(^=M+ui8(04cPsUxvnA^`^%v^)eAF!_UJmiZa6ZU&P^>_M|*9diXG=EWNZYf z<{K9?{`PY!5(5a2K_E%JkqVhkJlhG0y$^F%i-IMITL#Tyz@i^ej$-AWN-S!o`Y7Fd zZSKY;c&mkL_tfjMo3*0Zg%yh#2F&|Rj?5`eyqj50#@ZZ@ELAum!|@+aBSF>i_Ff;J zU4>$ap{Rxu6VkB>Z6T-+UL+jVWZK8?a#c2eP(G;r^hD@~d+JI4q06RQ=%e04?^P<5 zk&eE}yLZZJ3oDd&KPYqA7OxeY-8NY2r-hwnRl-%&z}OKHNM+O)6Rl*feX5!p5?7XD ztVA>0$D}aYLNTCqOD~nFI{s9vt9dVbY;J{OJN&k&Bv$D2h9yg}#7D~DvL5Un@08k? z$22kgqL9;1do36f;I-6M&c25|EdI-X#l+DR6ngb5pa?thFJc?Z>Q(CI z+vEy(%SYzfMomMj%(I7vy_w$i1@XT~jI@mXkSiXD;`bmiKP*8S+4^PLpi9ow_u ziGz5|s|nr_wg3PCQvd(}3v+X5EoEdfH8n9g0000$co#!`S=mwRBGtB`?%F}YyW^qu z+}7K7t6(%WD?jHcW@4mjPZXB@TXCcTUd^L_f}dY~9=w&>eb|0A8g7f>K znVJpif5FI8G@U*HqNOPcg@QqNPVR%@IM9X(u8tB>U538EjTe=ztU9Xy%Gu-AZ+6fx|R~Se_iNCJ5q#E8b zFk1~z5|-&5z2qRG@+DE!YTi;VOTHVLZK=UZ8T*zDke|faw3>^5(mmK2J}#TvA4fM^ zbGxkd^evJD2V4hCo$5?|$91Et?r0GJ8kse(~XFbJClKk>Z2NcInANgP%Y3`9#pG`v>POadFPLae41&ng+7*2K=Bu?^6viOOlL}l;?R7m*h}a?TPRQmd53Q>+YsVG(Z&OCM9UNhO-iic_?u85lZ%qnD_)-o}l;@H-KHl+~06M(^3|wy% zf$SY;a-}478;A`!wrpUjggaqbi8`*>tz(l?3UR50-Op*=Ww5q6#pe4$xV#E9DwDn> zN!afzpalgkE3iCnnv9Wx@jT;tlxN=UM-05M+YLQWDc#?jA|NLJPef-t16IW;>Qc&v*@kM2Q zm4WL6nr7$P#-Gr=`iX7mQ6L;xF`S2t!SFw*_p2>p1~(zX&;1U?IC>ibDNcZdT}*QU yg%@!ar6WYO(F;kp&>QTPx4S2N@AVUfApcuqugj|B_7}LTKg|Uk&jNrAKYszZ)Dk5C literal 0 HcmV?d00001 diff --git a/assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/client_kex_init.raw b/assets/kex/kex-hybrid/ecdh-nistp521-kyber-1024r3-sha512-d00/client_kex_init.raw new file mode 100644 index 0000000000000000000000000000000000000000..6d5598be0cec81ca82729361fd8bae39a7e938fd GIT binary patch literal 1296 zcmdr~yHdk25EL_^qoAPT8*~{TwHgzO)*jvj9B-p)QA?+;ry@2@YP*MyK$twtfFk+Hep45ss; zE`SBRTXevLQZNpqAUMf#ou1ERYG;X&W8Kb*3DuVtqI8^rB4qE$m=N@c3&94UbYbBl zVC5J#+A2#ZE;7=6lP}hB(c6vK>34S{Is=WX%@lr>hn$@i7L^efPvgcFW2?(GK~2%r zFifF_p<(KK^Ac~ipOutU`$|4W%|UVsR`j zFHs~+RI)ke7#r{(?0cRiQbDf^+WHDh+n@4dko}+Ak9j zg<=Gj3=5dID;m1Nr1eE?M@5}igMO@oEMmNNPkSL@5he%=xq-Jlu5_iCK%E_$4ldGa z>4TugW~!uBtsil#$mnzLDG-04&P!%8RX_wUrMFScNDG>q^0<5LA~p==ZCiV;%}TvP zLO6W4Flad`lQhOhvq)DIkw}H7J)0Y0Hx91Gohm%JdR2Or!$>t|5;gfnDJGJoLyzk> zj%gqlbHSy`^{dDR zH@2o!1J1W|p;*n)DhA-pVr@{H8(9`0*&dTdl{m6J%#bUsU`j6}W%s8uQHgxtfu>gi zyfuu?U+N0#!VggtpOoqL%=MMeAA$-+_ znpn(9<*O|lfpQ?WR3Q&ymBg`omw>h^W&D9oOP)QbTNx^$Qe;|RD8_{NWa@Nd9jgl~ z6{eFbM|RRz#w)qn0HLrmm%^iHo^_%POr?M}40|(pi<@j(!J7<}$!`(J80CparHATV zCLM^MBj#X)x+Sx!E}yU#?Z%8;{93{!lUGD5XklyDyOM4fD{pwHzQ?_)7&mN;u;L(_ z0tSNLGytCF&spGdb7s0C@|J}FYLB>;F=u)Of{}IBOquMFx=5&F>4OpcJMwFdbZ5l6 zIX%WuZxI%k`Qlad_9B7U8Lx&EzIp{gzIk-^C?VoEO_1CREf<1N zQxg7nr<04N-vnx0*MctLYJP~o9rdPUB_mO%2Y~Rfd6Jsum0*`faXA{ndwHhvX{6?8 z59u=$Bx1z{0+gu(T6pn(aD*0+@MR%unc_?l*N9sc0|)ghM8F$h_>i9-D#12~Kr=dI z*W4+E^jxy1Iihte^^hZUCmi&f4h@nU{6!vuuwwMO3m|ACfF`WEg3jOCBV15eR$ya` z1HV~VaUN_ibfK~!XLE@P4V74~m_u9?NE|T|uGR&SuwaIDMYm(H2=yavC(IiXs#KO` zy##p`{>66jJC|k~S35Cul-fK=8nLT&OzClt`9mw-8lasUj|L1py3!e?kU#|lrv!^- zEC`v2Tn&ObYBiTM=bOx?(V-kd$lWNmJuGCW_bM20hJ!4ke1R`3Y%L9kFD}}5IwBOS zFgwL)7UQFc+*xqZ`3rq_U`7XWq@V)RW*$Q{46@R*7!(c*B?Tq?NkrPjN`t+ZbU|wx ziA6dg+w{F*gPUkv4#7l%xqMMGwO%9eY*FrNVcOUlohl~k`(E!}!-y>M7H=R34|2k@Lwf+f76xtY}}6sgf+rg|XY zCJ$N?S3m`76!y(h8l!&vL~?g!V5w%5!EM=pPlu8irfK+l1 zsdZt>!Y9yBXy2-g|TchN#ieDo)&GC)aMWNvFQN z1gqtmj#;-IrmFF*DcOV-h5SjnbY2F!l@V4_*fY)o%gY1u9~c1+j5tRHSwiT#dK>t2 z$5$VnD~Je&k`2tv)&?+VkYZ#&hw&>YI$53KTACms5%Y&G?3Bj|Y@va?gb~z)dsMpT zt}^S^C+wGc!t>3XKo^R0eINPGr%lpZt^@(0M8y+QZ!OVGXMYp3v+X5EoEdfH8n9g0000WJI?K!@g+XRF0K-11lL`? z(4CAFlZ9OD(yEfl*I5vYA!v>rU6&4DE;F+dtpU1 zUKn_BebKMtr*Ewle?%V39UjcS0AmGA^5j}ugx-#i>w}LHi`3qp#R+f&*`eldfQ`zZ zx-vjR&F-|xetcc1v@j{9_7Um)mJLaZHCwO(R>6`Sf(4Lh`bN5^cg^ZHy$h>UoH_^r z;q?&v{gyS7&KcR;R@Izc7C0(8N;!A+(78^S@SJNX+NwPZSG;=akee_qQY4KNE;qwl zCew)-VY~MEYA7efUN1jR(!-i$J>|m&Biq;ZI`}Cnn1&Z?(CDVM&2b2{t&)lu2HHIH z_fhA-0rg$q6yf|>C4r^_s@j<#dOEkPP_~3V!l?3KTu<*q9%dNCm!vhLpJ4SJqisb$1eB^iKH@)uV4Qxu+v9KF}s`a)0{Or^L2@6gAa-(Fk;(Pj;j71|j7 zrv6y;xqylg<9hN`iR#d$zom0cEeVxI3)tGP96!G&i3OB#mb}kV>apI$`dccsEo__Z z!RBqW!9u3KC}aU2VKkR89<=QJ7I9d1(C>-#Ypbfd>@a26B5;yD;YLjm2jAD+l}E%Q zYf0PX%3gkH+X`y3IBnI2^^g{f{OAV1h_S3U5G|RPOKBu|`|aSIaWn+*DaXotYi-$}{}5WQ6NBFa1iBCL zki^o_H>JOSY3NsZEb;UBup*1o!w0eL1ty$&msD}}ai`Q!#9cX1IkfI7RdOD9&kWBG z2cB!mAq);F>l7=?UQ!_j0?m2DW4t4Azvo+{vM6X6+4QR2fF0<9av9xcDn!&_woW@> za6Gt|$o7CSSu}yic%z}~N_>IX>k? z(wj(*hbd+1NnW1l9uNt$OCEN@HY88C_com$UP@mb>MO7^oZeZPm8Zw^1Yx?N)#D3w z2L7F1|1(YWf~|tGYg?Dj0(p${9Hty~s2}=Dv=O@ZMh+h;NS7Il8lkKXv7YO!vaem_ z!oks(5+-;rY+xcCH^~scZARvdVjxBYVn6E9kX__67}2t)NgY(EU96*q{d44c%xkEF zWK_|gM}yUsr~1%!1@orm%hgCg(4v(sX*`cEd2;(Zqk1@tRXr{3rsNUIhVrLctZBSn zfkc2i`Oe+{{jcA37M@Q>&Yend|ArukN-}Pj^AC7?z}pvk3RToTl%>KBX%li6Xxjs| zKlz@=%Aqg0Jrtw<$)ZCZ(EIEyc?hvo!T7cDd{9Ckt>=V=siY@4;wfl}3*;dCjY~$$ z&y+MskCG{?oYbjEngsuD)3L^&JwV{pVM%mjgp(Rt6fbJ)r1&XZAw&?6>7|U1o zK_w+^KmX-@;#)H;`fxD~{HXM;)8IvVAOnxe@4*6_71F8r8?^zD;x^Mk32-PH89om9 z`B^Ec>6~kR6+zvX?VoztU?~Q>vUh$1WjGo)UKF$xG^sO91bl#RGs;UmG`oK-XGqjuqje|)3E19Zz|{pi9q^~Y6oRf+1Oae9eqm>ztSC9rUgLy%s&D&6JMC{y z7zAnzofKzKhae&#l|BLG22Z!aa9j1_a#O|^F`9Li#M=T%;6MDBzhc5oL^sD41~25E!! zzNMmjKmY&$Qvd(}3v+X5EoEdfH8n9g0000$90jWVcMk*2rxC4x0kA;UZkDS#@vl)? zPD@SH3vwW$B|A+1`pOv@v_8?dhv&f7A000000000000000 z000aM6#xJL000000001m1(fE3+Y(lUc}Lw%9z#br_rA6(h0S%B`ovz&a&?V*E2_d4|B$~S2vh!ab;aXc%{np6%-LhYP}QykdQcgxx9OJAmG?{$Ya>!j;sTY&YCq*O2k;!NM_}MP#DUcgHC8Vj7k{vL};A@ z*0K*0K9+^ z|0zlWcAg)~n5bUp716|3TFi#i`Lu zhiu6{Yz&hfjc807t+j~!2hXU3xt>&hS}reHrTfIGO$E_l0ugT~NUoSlkS)|5OwH&= zQh#AH&RpEeBdg=@NA}PacUH+m9e|iwafm}^?DS}|=EF?#ZL!|?mvqp69~leU&!%3eIdT8p={15)HD$IM1EkIqXa$S9QHt)$LAO){D1SFF);nzy9|9^Rpixe*Ssiy!mKZSMOL`ec`U} z(;cak$n^tzG;))r`?^l9%j#%zc1Q$~6lvX@kUtpOgx+w-cd1<$P1T;Z0lhcHeCpnm zCtjd7D|7ph#&OC?!m})o3UaK<_C)wSOQPb)b$3%F8XyXClM*ysL_Mt>SLLC+^K~0d#r; z7`WOf0y#L&5_Md$TgN7)6yj0~yPwnAr?9pe#pe4$xV#E9 zDwDn>N!afzpalgkE3ho8>y(k4@hs(xlZ_XKfq51CTW*G2Y5HW3Gn3-!MV(`>tlqr3INY4;Ndnp@o*cU0)UI>m1SAa;g&-9L0n$oF@swPd|ii}%Itw| z9A8w{R~fiIplQ}rHvWX})lY0gj{@Py^5HyW42J(fyZ usize { + match self { + Self::ECDHNistP256Kyber512r3Sha256D00OQS => 800, + Self::ECDHNistP384Kyber768r3Sha384D00OQS => 1184, + Self::ECDHNistP521Kyber1024r3Sha512D00OQS => 1568, + } + } + + /// Returns the length in bytes of the ciphertext produced by the KEM algorithm. + pub fn pq_ciphertext_len(self) -> usize { + match self { + Self::ECDHNistP256Kyber512r3Sha256D00OQS => 768, + Self::ECDHNistP384Kyber768r3Sha384D00OQS => 1088, + Self::ECDHNistP521Kyber1024r3Sha512D00OQS => 1568, + } + } +} + #[cfg(feature = "integers")] fn parse_mpint(i: &[u8]) -> IResult<&[u8], BigInt> { - nom::combinator::map_parser(parse_string, crate::mpint::parse_ssh_mpint)(i) + map_parser(parse_string, crate::mpint::parse_ssh_mpint)(i) } #[cfg(not(feature = "integers"))] @@ -507,6 +549,100 @@ pub struct SshKEXDiffieHellmanKEXGEX<'a> { pub reply: Option>, } +/// SSH Hybrid Key Exchange init. +/// +/// The message code is `SSH_MSG_KEX_HYBRID_INIT`, defined in +/// [draft RFC `draft-kampanakis-curdle-ssh-pq-ke-02` section 2.2](https://www.ietf.org/archive/id/draft-kampanakis-curdle-ssh-pq-ke-02.html#section-2.2) +#[derive(Debug, PartialEq)] +pub struct SshPacketHybridKEXInit<'a> { + /// The post-quantum KEM's public key (`C_PK2`). + pub pq_pub_key: &'a [u8], + + /// The traditional / classical KEX public key. + pub classical_pub_key: &'a [u8], +} + +impl<'a> SshPacketHybridKEXInit<'a> { + /// Parses a SSH PQ/T Hybrid Key Exchange Init. + pub fn parse(i: &'a [u8], alg: SupportedHybridKEXAlgorithm) -> IResult<&'a [u8], Self> { + let pq_len = alg.pq_pub_key_len(); + let (i, (pq_pub_key, classical_pub_key)) = + map_parser(parse_string, tuple((take(pq_len), rest)))(i)?; + Ok(( + i, + Self { + pq_pub_key, + classical_pub_key, + }, + )) + } +} + +/// SSH Hybrid Key Exchange reply. +/// +/// The message code is `SSH_MSG_KEX_HYBRID_REPLY`, defined in +/// [draft RFC `draft-kampanakis-curdle-ssh-pq-ke-02` section 2.2](https://www.ietf.org/archive/id/draft-kampanakis-curdle-ssh-pq-ke-02.html#section-2.2) +#[derive(Debug, PartialEq)] +pub struct SshPacketHybridKEXReply<'a> { + /// K_S, server's public host key. + pub pubkey_and_cert: &'a [u8], + + /// S_CT2, the ciphertext 'ct' output of the corresponding KEM's 'Encaps' algorithm. + pub pq_ciphertext: &'a [u8], + + /// S_PK1, ephemeral (EC)DH server public key. + pub classical_pub_key: &'a [u8], + + /// Signature. + pub signature: &'a [u8], +} + +impl<'a> SshPacketHybridKEXReply<'a> { + /// Parses a SSH PQ/T Hybrid Key Exchange reply. + pub fn parse(i: &'a [u8], alg: SupportedHybridKEXAlgorithm) -> IResult<&'a [u8], Self> { + let ct_len = alg.pq_ciphertext_len(); + let (i, (pubkey_and_cert, (pq_ciphertext, classical_pub_key), signature)) = tuple(( + parse_string, + map_parser(parse_string, tuple((take(ct_len), rest))), + parse_string, + ))(i)?; + Ok(( + i, + Self { + pubkey_and_cert, + pq_ciphertext, + classical_pub_key, + signature, + }, + )) + } +} + +/// The key exchange protocol using PQ/T Key Exchange, defined in +/// [draft RFC `draft-kampanakis-curdle-ssh-pq-ke-02`](https://www.ietf.org/archive/id/draft-kampanakis-curdle-ssh-pq-ke-02.html). +#[derive(Debug, PartialEq)] +pub struct SshHybridKEX<'a> { + /// The init message, i.e. `SSH_MSG_KEX_HYBRID_INIT`. + pub init: Option>, + + /// The reply message, i.e. `SSH_MSG_KEX_HYBRID_REPLY`. + pub reply: Option>, + + /// The algorithm. + pub alg: SupportedHybridKEXAlgorithm, +} + +impl SshHybridKEX<'_> { + /// Initializes a new [`SshHybridKEX`] using the given algorithm. + pub fn new(alg: SupportedHybridKEXAlgorithm) -> Self { + Self { + init: None, + reply: None, + alg, + } + } +} + /// An error occurring in the KEX parser. #[derive(Debug)] pub enum SshKEXError<'a> { @@ -575,6 +711,23 @@ macro_rules! parse_match_and_assign { }; } +/// Parses a hybrid KEX message, matches its owner and assign the parsed +/// object to it. +/// +/// We use a macro here because we take a field of `SshHybridKEX` as a parameter +/// (the receiver). +macro_rules! parse_match_and_assign_hybrid { + ($variant:ident, $field:ident, $struct:ident, $payload:ident) => { + if $variant.$field.is_some() { + Err(SshKEXError::DuplicatedMessage) + } else { + let alg = $variant.alg; + $variant.$field = Some(all_consuming(|i| $struct::parse(i, alg))($payload)?.1); + Ok(()) + } + }; +} + /// Negociates the KEX algorithm. pub fn ssh_kex_negociate_algorithm<'a, 'b, 'c, S1, S2>( client_kex_algs: impl IntoIterator, @@ -607,6 +760,10 @@ pub enum SshKEX<'a> { /// Diffie Hellman Group and Key, defined in RFC4419. DiffieHellmanKEXGEX(SshKEXDiffieHellmanKEXGEX<'a>), + + /// PQ/T Hybrid Key Exchange, defined in + /// [draft RFC `draft-kampanakis-curdle-ssh-pq-ke-02`](https://www.ietf.org/archive/id/draft-kampanakis-curdle-ssh-pq-ke-02.html). + HybridKEX(SshHybridKEX<'a>), } impl<'a> SshKEX<'a> { @@ -642,6 +799,15 @@ impl<'a> SshKEX<'a> { "diffie-hellman-group-exchange-sha1" | "diffie-hellman-group-exchange-sha256" => Ok( Self::DiffieHellmanKEXGEX(SshKEXDiffieHellmanKEXGEX::default()), ), + "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org" => Ok(Self::HybridKEX( + SshHybridKEX::new(SupportedHybridKEXAlgorithm::ECDHNistP256Kyber512r3Sha256D00OQS), + )), + "ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org" => Ok(Self::HybridKEX( + SshHybridKEX::new(SupportedHybridKEXAlgorithm::ECDHNistP384Kyber768r3Sha384D00OQS), + )), + "ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org" => Ok(Self::HybridKEX( + SshHybridKEX::new(SupportedHybridKEXAlgorithm::ECDHNistP521Kyber1024r3Sha512D00OQS), + )), _ => Err(SshKEXError::UnknownProtocol), } .map(|kex| (kex, negociated_alg)) @@ -695,6 +861,15 @@ impl<'a> SshKEX<'a> { } _ => Err(SshKEXError::UnexpectedMessage), }, + Self::HybridKEX(hk) => match unparsed_ssh_packet.message_code { + SSH_MSG_KEX_HYBRID_INIT => { + parse_match_and_assign_hybrid!(hk, init, SshPacketHybridKEXInit, payload) + } + SSH_MSG_KEX_HYBRID_REPLY => { + parse_match_and_assign_hybrid!(hk, reply, SshPacketHybridKEXReply, payload) + } + _ => Err(SshKEXError::UnexpectedMessage), + }, } } } diff --git a/src/lib.rs b/src/lib.rs index 52d5dbf..3148136 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub use kex::{ SshKEXDiffieHellmanKEXGEX, SshKEXECDiffieHellman, SshKEXError, SshPacketDHKEXInit, SshPacketDHKEXReply, SshPacketDhKEXGEXGroup, SshPacketDhKEXGEXInit, SshPacketDhKEXGEXReply, SshPacketDhKEXGEXRequest, SshPacketDhKEXGEXRequestOld, SshPacketECDHKEXInit, - SshPacketECDHKEXReply, + SshPacketECDHKEXReply, SshPacketHybridKEXInit, SshPacketHybridKEXReply, + SupportedHybridKEXAlgorithm, }; pub use ssh::*; diff --git a/tests/tests_kex.rs b/tests/tests_kex.rs index 43f77e3..784ee7e 100644 --- a/tests/tests_kex.rs +++ b/tests/tests_kex.rs @@ -179,6 +179,85 @@ mod dh_kex_gex { } } +mod kex_hybrid_oqs { + use std::fs; + use std::path::Path; + + use super::*; + + /// Path to assets. + const ASSETS_PATH: &str = "assets/kex/kex-hybrid"; + + fn read_test_file(directory: &Path, filename: &str) -> &'static [u8] { + let data = Box::new(fs::read(directory.join(filename)).unwrap()); + Box::leak(data) + } + + /// Tests an hybrid algorithm with a directory containing its assets. + fn test_alg_with_directory(directory: impl AsRef, expected_algorithm: impl AsRef) { + let directory = Path::new(ASSETS_PATH).join(directory); + println!("dir={}", directory.display()); + let client_kex_init = read_test_file(&directory, "client_kex_init.raw"); + let server_kex_init = read_test_file(&directory, "server_kex_init.raw"); + + let init_msg = fs::read(directory.join("init.raw")).unwrap(); + let reply_msg = fs::read(directory.join("reply.raw")).unwrap(); + + let (client_kex, server_kex) = + load_client_server_key_exchange_init(client_kex_init, server_kex_init); + + let (mut kex, negotiated_alg) = SshKEX::init(&client_kex, &server_kex).unwrap(); + assert_eq!(negotiated_alg, expected_algorithm.as_ref()); + assert!(matches!(kex, SshKEX::HybridKEX(_))); + + let init_packet = load_kex_packet(&init_msg); + assert!(matches!(kex.parse_ssh_packet(&init_packet), Ok(()))); + assert!(matches!( + kex.parse_ssh_packet(&init_packet), + Err(SshKEXError::DuplicatedMessage) + )); + + let reply_packet = load_kex_packet(&reply_msg); + assert!(matches!(kex.parse_ssh_packet(&reply_packet), Ok(()))); + assert!(matches!( + kex.parse_ssh_packet(&reply_packet), + Err(SshKEXError::DuplicatedMessage) + )); + + let kex = match kex { + SshKEX::HybridKEX(kex) => kex, + _ => unreachable!(), + }; + + assert!(kex.init.is_some()); + assert!(kex.reply.is_some()); + } + + #[test] + fn ecdh_nistp256_kyber_512r3_sha256_d00_openquantumsafe_org_test() { + test_alg_with_directory( + "ecdh-nistp256-kyber-512r3-sha256-d00", + "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org", + ); + } + + #[test] + fn ecdh_nistp384_kyber_768r3_sha384_d00_openquantumsafe_org_test() { + test_alg_with_directory( + "ecdh-nistp384-kyber-768r3-sha384-d00", + "ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org", + ); + } + + #[test] + fn ecdh_nistp521_kyber_1024r3_sha512_d00_openquantumsafe_org_test() { + test_alg_with_directory( + "ecdh-nistp521-kyber-1024r3-sha512-d00", + "ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org", + ); + } +} + mod kex_algorithm_negociation { use super::ssh_kex_negociate_algorithm;