kex-hybrid.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /*
  2. * Centralised machinery for hybridised post-quantum + classical key
  3. * exchange setups, using the same message structure as ECDH but the
  4. * strings sent each way are the concatenation of a key or ciphertext
  5. * of each type, and the output shared secret is obtained by hashing
  6. * together both of the sub-methods' outputs.
  7. */
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <assert.h>
  11. #include "putty.h"
  12. #include "ssh.h"
  13. #include "mpint.h"
  14. /* ----------------------------------------------------------------------
  15. * Common definitions between client and server sides.
  16. */
  17. typedef struct hybrid_alg hybrid_alg;
  18. struct hybrid_alg {
  19. const ssh_hashalg *combining_hash;
  20. const pq_kemalg *pq_alg;
  21. const ssh_kex *classical_alg;
  22. void (*reformat)(ptrlen input, BinarySink *output);
  23. };
  24. static char *hybrid_description(const ssh_kex *kex)
  25. {
  26. const struct hybrid_alg *alg = kex->extra;
  27. /* Bit of a bodge, but think up a short name to describe the
  28. * classical algorithm */
  29. const char *classical_name;
  30. if (alg->classical_alg == &ssh_ec_kex_curve25519)
  31. classical_name = "Curve25519";
  32. else if (alg->classical_alg == &ssh_ec_kex_nistp256)
  33. classical_name = "NIST P256";
  34. else if (alg->classical_alg == &ssh_ec_kex_nistp384)
  35. classical_name = "NIST P384";
  36. else
  37. unreachable("don't have a name for this classical alg");
  38. return dupprintf("%s / %s hybrid key exchange",
  39. alg->pq_alg->description, classical_name);
  40. }
  41. static void reformat_mpint_be(ptrlen input, BinarySink *output, size_t bytes)
  42. {
  43. BinarySource src[1];
  44. BinarySource_BARE_INIT_PL(src, input);
  45. mp_int *mp = get_mp_ssh2(src);
  46. assert(!get_err(src));
  47. assert(get_avail(src) == 0);
  48. for (size_t i = bytes; i-- > 0 ;)
  49. put_byte(output, mp_get_byte(mp, i));
  50. mp_free(mp);
  51. }
  52. static void reformat_mpint_be_32(ptrlen input, BinarySink *output)
  53. {
  54. reformat_mpint_be(input, output, 32);
  55. }
  56. static void reformat_mpint_be_48(ptrlen input, BinarySink *output)
  57. {
  58. reformat_mpint_be(input, output, 48);
  59. }
  60. /* ----------------------------------------------------------------------
  61. * Client side.
  62. */
  63. typedef struct hybrid_client_state hybrid_client_state;
  64. static const ecdh_keyalg hybrid_client_vt;
  65. struct hybrid_client_state {
  66. const hybrid_alg *alg;
  67. strbuf *pq_ek;
  68. pq_kem_dk *pq_dk;
  69. ecdh_key *classical;
  70. ecdh_key ek;
  71. };
  72. static ecdh_key *hybrid_client_new(const ssh_kex *kex, bool is_server)
  73. {
  74. assert(!is_server);
  75. hybrid_client_state *s = snew(hybrid_client_state);
  76. s->alg = kex->extra;
  77. s->ek.vt = &hybrid_client_vt;
  78. s->pq_ek = strbuf_new();
  79. s->pq_dk = pq_kem_keygen(s->alg->pq_alg, BinarySink_UPCAST(s->pq_ek));
  80. s->classical = ecdh_key_new(s->alg->classical_alg, is_server);
  81. return &s->ek;
  82. }
  83. static void hybrid_client_free(ecdh_key *ek)
  84. {
  85. hybrid_client_state *s = container_of(ek, hybrid_client_state, ek);
  86. strbuf_free(s->pq_ek);
  87. pq_kem_free_dk(s->pq_dk);
  88. ecdh_key_free(s->classical);
  89. sfree(s);
  90. }
  91. /*
  92. * In the client, getpublic is called first: we make up a KEM key
  93. * pair, and send the public key along with a classical DH value.
  94. */
  95. static void hybrid_client_getpublic(ecdh_key *ek, BinarySink *bs)
  96. {
  97. hybrid_client_state *s = container_of(ek, hybrid_client_state, ek);
  98. put_datapl(bs, ptrlen_from_strbuf(s->pq_ek));
  99. ecdh_key_getpublic(s->classical, bs);
  100. }
  101. /*
  102. * In the client, getkey is called second, after the server sends its
  103. * response: we use our KEM private key to decapsulate the server's
  104. * ciphertext.
  105. */
  106. static bool hybrid_client_getkey(ecdh_key *ek, ptrlen remoteKey, BinarySink *bs)
  107. {
  108. hybrid_client_state *s = container_of(ek, hybrid_client_state, ek);
  109. BinarySource src[1];
  110. BinarySource_BARE_INIT_PL(src, remoteKey);
  111. ssh_hash *h = ssh_hash_new(s->alg->combining_hash);
  112. ptrlen pq_ciphertext = get_data(src, s->alg->pq_alg->c_len);
  113. if (get_err(src)) {
  114. ssh_hash_free(h);
  115. return false; /* not enough data */
  116. }
  117. if (!pq_kem_decaps(s->pq_dk, BinarySink_UPCAST(h), pq_ciphertext)) {
  118. ssh_hash_free(h);
  119. return false; /* pq ciphertext didn't validate */
  120. }
  121. ptrlen classical_data = get_data(src, get_avail(src));
  122. strbuf *classical_key = strbuf_new();
  123. if (!ecdh_key_getkey(s->classical, classical_data,
  124. BinarySink_UPCAST(classical_key))) {
  125. ssh_hash_free(h);
  126. return false; /* classical DH key didn't validate */
  127. }
  128. s->alg->reformat(ptrlen_from_strbuf(classical_key), BinarySink_UPCAST(h));
  129. strbuf_free(classical_key);
  130. /*
  131. * Finish up: compute the final output hash and return it encoded
  132. * as a string.
  133. */
  134. unsigned char hashdata[MAX_HASH_LEN];
  135. ssh_hash_final(h, hashdata);
  136. put_stringpl(bs, make_ptrlen(hashdata, s->alg->combining_hash->hlen));
  137. smemclr(hashdata, sizeof(hashdata));
  138. return true;
  139. }
  140. static const ecdh_keyalg hybrid_client_vt = {
  141. .new = hybrid_client_new, /* but normally the selector calls this */
  142. .free = hybrid_client_free,
  143. .getpublic = hybrid_client_getpublic,
  144. .getkey = hybrid_client_getkey,
  145. .description = hybrid_description,
  146. .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX,
  147. };
  148. /* ----------------------------------------------------------------------
  149. * Server side.
  150. */
  151. typedef struct hybrid_server_state hybrid_server_state;
  152. static const ecdh_keyalg hybrid_server_vt;
  153. struct hybrid_server_state {
  154. const hybrid_alg *alg;
  155. strbuf *pq_ciphertext;
  156. ecdh_key *classical;
  157. ecdh_key ek;
  158. };
  159. static ecdh_key *hybrid_server_new(const ssh_kex *kex, bool is_server)
  160. {
  161. assert(is_server);
  162. hybrid_server_state *s = snew(hybrid_server_state);
  163. s->alg = kex->extra;
  164. s->ek.vt = &hybrid_server_vt;
  165. s->pq_ciphertext = strbuf_new_nm();
  166. s->classical = ecdh_key_new(s->alg->classical_alg, is_server);
  167. return &s->ek;
  168. }
  169. static void hybrid_server_free(ecdh_key *ek)
  170. {
  171. hybrid_server_state *s = container_of(ek, hybrid_server_state, ek);
  172. strbuf_free(s->pq_ciphertext);
  173. ecdh_key_free(s->classical);
  174. sfree(s);
  175. }
  176. /*
  177. * In the server, getkey is called first: we receive a KEM encryption
  178. * key from the client and encapsulate a secret with it. We write the
  179. * output secret to bs; the data we'll send to the client is saved to
  180. * return from getpublic.
  181. */
  182. static bool hybrid_server_getkey(ecdh_key *ek, ptrlen remoteKey, BinarySink *bs)
  183. {
  184. hybrid_server_state *s = container_of(ek, hybrid_server_state, ek);
  185. BinarySource src[1];
  186. BinarySource_BARE_INIT_PL(src, remoteKey);
  187. ssh_hash *h = ssh_hash_new(s->alg->combining_hash);
  188. ptrlen pq_ek = get_data(src, s->alg->pq_alg->ek_len);
  189. if (get_err(src)) {
  190. ssh_hash_free(h);
  191. return false; /* not enough data */
  192. }
  193. if (!pq_kem_encaps(s->alg->pq_alg,
  194. BinarySink_UPCAST(s->pq_ciphertext),
  195. BinarySink_UPCAST(h), pq_ek)) {
  196. ssh_hash_free(h);
  197. return false; /* pq encryption key didn't validate */
  198. }
  199. ptrlen classical_data = get_data(src, get_avail(src));
  200. strbuf *classical_key = strbuf_new();
  201. if (!ecdh_key_getkey(s->classical, classical_data,
  202. BinarySink_UPCAST(classical_key))) {
  203. ssh_hash_free(h);
  204. return false; /* classical DH key didn't validate */
  205. }
  206. s->alg->reformat(ptrlen_from_strbuf(classical_key), BinarySink_UPCAST(h));
  207. strbuf_free(classical_key);
  208. /*
  209. * Finish up: compute the final output hash and return it encoded
  210. * as a string.
  211. */
  212. unsigned char hashdata[MAX_HASH_LEN];
  213. ssh_hash_final(h, hashdata);
  214. put_stringpl(bs, make_ptrlen(hashdata, s->alg->combining_hash->hlen));
  215. smemclr(hashdata, sizeof(hashdata));
  216. return true;
  217. }
  218. static void hybrid_server_getpublic(ecdh_key *ek, BinarySink *bs)
  219. {
  220. hybrid_server_state *s = container_of(ek, hybrid_server_state, ek);
  221. put_datapl(bs, ptrlen_from_strbuf(s->pq_ciphertext));
  222. ecdh_key_getpublic(s->classical, bs);
  223. }
  224. static const ecdh_keyalg hybrid_server_vt = {
  225. .new = hybrid_server_new, /* but normally the selector calls this */
  226. .free = hybrid_server_free,
  227. .getkey = hybrid_server_getkey,
  228. .getpublic = hybrid_server_getpublic,
  229. .description = hybrid_description,
  230. .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX,
  231. };
  232. /* ----------------------------------------------------------------------
  233. * Selector vtable that instantiates the appropriate one of the above,
  234. * depending on is_server.
  235. */
  236. static ecdh_key *hybrid_selector_new(const ssh_kex *kex, bool is_server)
  237. {
  238. if (is_server)
  239. return hybrid_server_new(kex, is_server);
  240. else
  241. return hybrid_client_new(kex, is_server);
  242. }
  243. static const ecdh_keyalg hybrid_selector_vt = {
  244. /* This is a never-instantiated vtable which only implements the
  245. * functions that don't require an instance. */
  246. .new = hybrid_selector_new,
  247. .description = hybrid_description,
  248. .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX,
  249. };
  250. /* ----------------------------------------------------------------------
  251. * Actual KEX methods.
  252. */
  253. static const hybrid_alg ssh_ntru_curve25519_hybrid = {
  254. .combining_hash = &ssh_sha512,
  255. .pq_alg = &ssh_ntru,
  256. .classical_alg = &ssh_ec_kex_curve25519,
  257. .reformat = reformat_mpint_be_32,
  258. };
  259. static const ssh_kex ssh_ntru_curve25519 = {
  260. .name = "sntrup761x25519-sha512",
  261. .main_type = KEXTYPE_ECDH,
  262. .hash = &ssh_sha512,
  263. .ecdh_vt = &hybrid_selector_vt,
  264. .extra = &ssh_ntru_curve25519_hybrid,
  265. };
  266. static const ssh_kex ssh_ntru_curve25519_openssh = {
  267. .name = "[email protected]",
  268. .main_type = KEXTYPE_ECDH,
  269. .hash = &ssh_sha512,
  270. .ecdh_vt = &hybrid_selector_vt,
  271. .extra = &ssh_ntru_curve25519_hybrid,
  272. };
  273. static const ssh_kex *const ntru_hybrid_list[] = {
  274. &ssh_ntru_curve25519,
  275. &ssh_ntru_curve25519_openssh,
  276. };
  277. const ssh_kexes ssh_ntru_hybrid_kex = {
  278. lenof(ntru_hybrid_list), ntru_hybrid_list,
  279. };
  280. static const hybrid_alg ssh_mlkem768_curve25519_hybrid = {
  281. .combining_hash = &ssh_sha256,
  282. .pq_alg = &ssh_mlkem768,
  283. .classical_alg = &ssh_ec_kex_curve25519,
  284. .reformat = reformat_mpint_be_32,
  285. };
  286. static const ssh_kex ssh_mlkem768_curve25519 = {
  287. .name = "mlkem768x25519-sha256",
  288. .main_type = KEXTYPE_ECDH,
  289. .hash = &ssh_sha256,
  290. .ecdh_vt = &hybrid_selector_vt,
  291. .extra = &ssh_mlkem768_curve25519_hybrid,
  292. };
  293. static const ssh_kex *const mlkem_curve25519_hybrid_list[] = {
  294. &ssh_mlkem768_curve25519,
  295. };
  296. const ssh_kexes ssh_mlkem_curve25519_hybrid_kex = {
  297. lenof(mlkem_curve25519_hybrid_list), mlkem_curve25519_hybrid_list,
  298. };
  299. static const hybrid_alg ssh_mlkem768_p256_hybrid = {
  300. .combining_hash = &ssh_sha256,
  301. .pq_alg = &ssh_mlkem768,
  302. .classical_alg = &ssh_ec_kex_nistp256,
  303. .reformat = reformat_mpint_be_32,
  304. };
  305. static const ssh_kex ssh_mlkem768_p256 = {
  306. .name = "mlkem768nistp256-sha256",
  307. .main_type = KEXTYPE_ECDH,
  308. .hash = &ssh_sha256,
  309. .ecdh_vt = &hybrid_selector_vt,
  310. .extra = &ssh_mlkem768_p256_hybrid,
  311. };
  312. static const hybrid_alg ssh_mlkem1024_p384_hybrid = {
  313. .combining_hash = &ssh_sha384,
  314. .pq_alg = &ssh_mlkem1024,
  315. .classical_alg = &ssh_ec_kex_nistp384,
  316. .reformat = reformat_mpint_be_48,
  317. };
  318. static const ssh_kex ssh_mlkem1024_p384 = {
  319. .name = "mlkem1024nistp384-sha384",
  320. .main_type = KEXTYPE_ECDH,
  321. .hash = &ssh_sha384,
  322. .ecdh_vt = &hybrid_selector_vt,
  323. .extra = &ssh_mlkem1024_p384_hybrid,
  324. };
  325. static const ssh_kex *const mlkem_nist_hybrid_list[] = {
  326. &ssh_mlkem1024_p384,
  327. &ssh_mlkem768_p256,
  328. };
  329. const ssh_kexes ssh_mlkem_nist_hybrid_kex = {
  330. lenof(mlkem_nist_hybrid_list), mlkem_nist_hybrid_list,
  331. };