diff --git a/Buildscr b/Buildscr index 497430db..c007da05 100644 --- a/Buildscr +++ b/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 19052 # update this at every release +set Epoch 19120 # 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 diff --git a/CHECKLST.txt b/CHECKLST.txt index 92ef67e5..01f57c30 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -133,7 +133,9 @@ Making a release candidate build + on at least a reasonably current stable Linux distro, and also try Debian sid + test-build with all of GTK 1, 2 and 3 - + test-build with -DNOT_X_WINDOWS + + test-build with -DNOT_X_WINDOWS (compile flag) + + test-build with -DPUTTY_GSSAPI=OFF (cmake flag) + + test-build with -DPUTTY_IPV6=OFF (cmake flag) * test that the Windows source builds with Visual Studio (just in case there's an unguarded clangism that would prevent it) * quick check of the outlying network protocols (Telnet, SUPDUP diff --git a/CMakeLists.txt b/CMakeLists.txt index 314b7f6a..dad26e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.7...3.28) project(putty LANGUAGES C) set(CMAKE_C_STANDARD 99) diff --git a/LATEST.VER b/LATEST.VER index e6e9cf41..08fec888 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.82 +0.83 diff --git a/LICENCE b/LICENCE index c46b80a3..ed6b2403 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -PuTTY is copyright 1997-2024 Simon Tatham. +PuTTY is copyright 1997-2025 Simon Tatham. 部分版权属于 Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, @@ -14,4 +14,4 @@ Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, 该软件是作为 "AS IS" 提供,没有任何保证、表示或暗示,包括但不限于适销性、适用性和不侵权。在任何情况下作者不对任何声明、损坏或其他责任负责,无论是发生在合同行为、侵权或其它任何来自软件的、与软件相关或无关的以及软件的使用。 -PuTTY 中文版的版权属于 2003-2024 李峰,保留所有权利。 +PuTTY 中文版的版权属于 2003-2025 李峰,保留所有权利。 diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index f8fcbc58..cb389cb6 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -15,6 +15,7 @@ #cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID #cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES #cmakedefine01 HAVE_STRTOUMAX +#cmakedefine01 HAVE_WMEMCHR #cmakedefine01 HAVE_DWMAPI_H #cmakedefine NOT_X_WINDOWS @@ -60,3 +61,4 @@ #cmakedefine01 HAVE_NEON_SHA512 #cmakedefine01 HAVE_NEON_SHA512_INTRINSICS #cmakedefine01 USE_ARM64_NEON_H +#cmakedefine01 HAVE_ARM_DIT diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 337a09c5..1de924ff 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -53,6 +53,7 @@ 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(wmemchr "wchar.h" HAVE_WMEMCHR) check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) check_symbol_exists(SetDefaultDllDirectories "windows.h" HAVE_SETDEFAULTDLLDIRECTORIES) @@ -103,14 +104,12 @@ endif() if(WINELIB) enable_language(RC) - set(LFLAG_MANIFEST_NO "") elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} /nologo /C65001") - set(LFLAG_MANIFEST_NO "/manifest:no") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no") else() set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -c65001") - set(LFLAG_MANIFEST_NO "") endif() if(STRICT) diff --git a/cmdgen.c b/cmdgen.c index 40ea710b..fd3ab989 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -263,6 +263,8 @@ int main(int argc, char **argv) ppk_save_parameters params = ppk_save_default_parameters; FingerprintType fptype = SSH_FPTYPE_DEFAULT; + enable_dit(); + if (is_interactive()) progress_fp = stderr; diff --git a/config.c b/config.c index 66e0f990..6efa10f9 100644 --- a/config.c +++ b/config.c @@ -578,6 +578,8 @@ static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg, { "RSA-based key exchange", KEX_RSA }, { "ECDH key exchange", KEX_ECDH }, { "NTRU Prime / Curve25519 hybrid kex", KEX_NTRU_HYBRID }, + { "ML-KEM / Curve25519 hybrid kex", KEX_MLKEM_25519_HYBRID }, + { "ML-KEM / NIST ECDH hybrid kex", KEX_MLKEM_NIST_HYBRID }, { "-- 下面为警告选项 --", KEX_WARN } }; @@ -1985,7 +1987,7 @@ void setup_config_box(struct controlbox *b, bool midsession, sshrawlogname, 'r', I(LGTYP_SSHRAW)); } ctrl_filesel(s, "日志文件名(F):", 'f', - NULL, true, "选择会话的日志文件名", + FILTER_ALL_FILES, true, "选择会话的日志文件名", HELPCTX(logging_filename), conf_filesel_handler, I(CONF_logfilename)); ctrl_text(s, "(日志文件名可以包含 &Y &M &D 表示年月日," @@ -2747,7 +2749,7 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_draglist(s, "算法选择顺序(S):", 's', HELPCTX(ssh_kexlist), kexlist_handler, P(NULL)); - c->listbox.height = KEX_MAX; + c->listbox.height = 10; #ifndef NO_GSSAPI ctrl_checkbox(s, "尝试 GSSAPI 密钥交换", 'k', HELPCTX(ssh_gssapi), @@ -2929,7 +2931,7 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_privkey), conf_filesel_handler, I(CONF_keyfile)); ctrl_filesel(s, "与私钥一起使用的证书(可选) (E):", 'e', - NULL, false, "选择证书文件", + FILTER_ALL_FILES, false, "选择证书文件", HELPCTX(ssh_auth_cert), conf_filesel_handler, I(CONF_detached_cert)); diff --git a/contrib/logparse.pl b/contrib/logparse.pl index eb429302..034aa72d 100755 --- a/contrib/logparse.pl +++ b/contrib/logparse.pl @@ -172,6 +172,16 @@ sub usage { my ($direction, $seq, $data) = @_; print "\n"; }, +#define SSH2_MSG_KEX_HYBRID_INIT 30 /* 0x1e */ + 'SSH2_MSG_KEX_HYBRID_INIT' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEX_HYBRID_REPLY 31 /* 0x1f */ + 'SSH2_MSG_KEX_HYBRID_REPLY' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, #define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ 'SSH2_MSG_USERAUTH_REQUEST' => sub { my ($direction, $seq, $data) = @_; @@ -657,6 +667,16 @@ sub usage { # curve is. So the best we can do is just dump the raw data. printf " client public value: %s\n", (unpack "H*", $cpv); }, + 'SSH2_MSG_KEX_HYBRID_INIT' => sub { + my ($data) = @_; + my ($cpv) = &parse("s", $data); + # Hybrid post-quantum + classical KEX is even more confusing, + # since two separate pieces of data are glomphed together into + # this string without any obvious dividing line. The best we + # can sensibly do is to announce that in the log. + printf " client PQ encryption key + public ECDH value: %s\n", + (unpack "H*", $cpv); + }, 'SSH2_MSG_KEXDH_REPLY' => sub { my ($data) = @_; my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data); @@ -708,6 +728,26 @@ sub usage { printf " $key: $value\n"; } }, + 'SSH2_MSG_KEX_HYBRID_REPLY' => sub { + my ($data) = @_; + my ($hostkeyblob, $spv, $sigblob) = &parse("sss", $data); + my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); + printf " host key: %s\n", $hktype; + while (@hostkey) { + my ($key, $value) = splice @hostkey, 0, 2; + printf " $key: $value\n"; + } + # Similarly to HYBRID_INIT, warn the reader that this string + # contains two separate things glomphed together + printf " server PQ KEM ciphertext + public ECDH value: %s\n", + (unpack "H*", $spv); + printf " signature:\n"; + my @signature = &parse_signature($sigblob, $hktype); + while (@signature) { + my ($key, $value) = splice @signature, 0, 2; + printf " $key: $value\n"; + } + }, 'SSH2_MSG_NEWKEYS' => sub {}, 'SSH2_MSG_SERVICE_REQUEST' => sub { my ($data) = @_; diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index edb02ce4..e57bb600 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -20,9 +20,11 @@ add_sources_from_current_dir(crypto ecc-ssh.c hash_simple.c hmac.c + kex-hybrid.c mac.c mac_simple.c md5.c + mlkem.c mpint.c ntru.c openssh-certs.c @@ -234,6 +236,12 @@ if(neon) endif() endif() +test_compile_with_flags(HAVE_ARM_DIT + GNU_FLAGS -march=armv8.4-a + TEST_SOURCE " + int main(void) { asm volatile(\"msr dit, %0\" :: \"r\"(1)); }" + ADD_SOURCES_IF_SUCCESSFUL enable_dit.c) + set(HAVE_AES_NI ${HAVE_AES_NI} PARENT_SCOPE) set(HAVE_SHA_NI ${HAVE_SHA_NI} PARENT_SCOPE) set(HAVE_SHAINTRIN_H ${HAVE_SHAINTRIN_H} PARENT_SCOPE) @@ -241,3 +249,4 @@ set(HAVE_NEON_CRYPTO ${HAVE_NEON_CRYPTO} PARENT_SCOPE) set(HAVE_NEON_SHA512 ${HAVE_NEON_SHA512} PARENT_SCOPE) set(HAVE_NEON_SHA512_INTRINSICS ${HAVE_NEON_SHA512_INTRINSICS} PARENT_SCOPE) set(USE_ARM64_NEON_H ${USE_ARM64_NEON_H} PARENT_SCOPE) +set(HAVE_ARM_DIT ${HAVE_ARM_DIT} PARENT_SCOPE) diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index ca712c31..e524dfc4 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1615,6 +1615,7 @@ static const ecdh_keyalg ssh_ecdhkex_m_alg = { .getpublic = ssh_ecdhkex_m_getpublic, .getkey = ssh_ecdhkex_m_getkey, .description = ssh_ecdhkex_description, + .packet_naming_ctx = SSH2_PKTCTX_ECDHKEX, }; const ssh_kex ssh_ec_kex_curve25519 = { .name = "curve25519-sha256", @@ -1655,6 +1656,7 @@ static const ecdh_keyalg ssh_ecdhkex_w_alg = { .getpublic = ssh_ecdhkex_w_getpublic, .getkey = ssh_ecdhkex_w_getkey, .description = ssh_ecdhkex_description, + .packet_naming_ctx = SSH2_PKTCTX_ECDHKEX, }; static const struct eckex_extra kex_extra_nistp256 = { ec_p256 }; const ssh_kex ssh_ec_kex_nistp256 = { diff --git a/crypto/enable_dit.c b/crypto/enable_dit.c new file mode 100644 index 00000000..7c9ec4b4 --- /dev/null +++ b/crypto/enable_dit.c @@ -0,0 +1,24 @@ +/* + * Enable the PSTATE.DIT flag in AArch64, if available. + * + * This guarantees that data-processing instructions (or rather, a + * long list of specific ones) will have data-independent timing + * (hence the name). In other words, you want to turn this bit on if + * you're trying to do constant-time crypto. + * + * For maximum performance you'd want to turn this bit back off when + * doing any CPU-intensive stuff that _isn't_ cryptographic. That + * seems like a small concern in this code base, and carries the risk + * of losing track of whether it was on or not, so here we just enable + * it for the whole process. That's why there's only an enable_dit() + * function in this file and not a disable_dit() to go with it. + */ + +#include "ssh.h" + +void enable_dit(void) +{ + if (!platform_dit_available()) + return; + asm volatile("msr dit, %0" :: "r"(1)); +} diff --git a/crypto/kex-hybrid.c b/crypto/kex-hybrid.c new file mode 100644 index 00000000..8732432e --- /dev/null +++ b/crypto/kex-hybrid.c @@ -0,0 +1,393 @@ +/* + * Centralised machinery for hybridised post-quantum + classical key + * exchange setups, using the same message structure as ECDH but the + * strings sent each way are the concatenation of a key or ciphertext + * of each type, and the output shared secret is obtained by hashing + * together both of the sub-methods' outputs. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" + +/* ---------------------------------------------------------------------- + * Common definitions between client and server sides. + */ + +typedef struct hybrid_alg hybrid_alg; + +struct hybrid_alg { + const ssh_hashalg *combining_hash; + const pq_kemalg *pq_alg; + const ssh_kex *classical_alg; + void (*reformat)(ptrlen input, BinarySink *output); +}; + +static char *hybrid_description(const ssh_kex *kex) +{ + const struct hybrid_alg *alg = kex->extra; + + /* Bit of a bodge, but think up a short name to describe the + * classical algorithm */ + const char *classical_name; + if (alg->classical_alg == &ssh_ec_kex_curve25519) + classical_name = "Curve25519"; + else if (alg->classical_alg == &ssh_ec_kex_nistp256) + classical_name = "NIST P256"; + else if (alg->classical_alg == &ssh_ec_kex_nistp384) + classical_name = "NIST P384"; + else + unreachable("don't have a name for this classical alg"); + + return dupprintf("%s / %s hybrid key exchange", + alg->pq_alg->description, classical_name); +} + +static void reformat_mpint_be(ptrlen input, BinarySink *output, size_t bytes) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + mp_int *mp = get_mp_ssh2(src); + assert(!get_err(src)); + assert(get_avail(src) == 0); + for (size_t i = bytes; i-- > 0 ;) + put_byte(output, mp_get_byte(mp, i)); + mp_free(mp); +} + +static void reformat_mpint_be_32(ptrlen input, BinarySink *output) +{ + reformat_mpint_be(input, output, 32); +} + +static void reformat_mpint_be_48(ptrlen input, BinarySink *output) +{ + reformat_mpint_be(input, output, 48); +} + +/* ---------------------------------------------------------------------- + * Client side. + */ + +typedef struct hybrid_client_state hybrid_client_state; + +static const ecdh_keyalg hybrid_client_vt; + +struct hybrid_client_state { + const hybrid_alg *alg; + strbuf *pq_ek; + pq_kem_dk *pq_dk; + ecdh_key *classical; + ecdh_key ek; +}; + +static ecdh_key *hybrid_client_new(const ssh_kex *kex, bool is_server) +{ + assert(!is_server); + hybrid_client_state *s = snew(hybrid_client_state); + s->alg = kex->extra; + s->ek.vt = &hybrid_client_vt; + s->pq_ek = strbuf_new(); + s->pq_dk = pq_kem_keygen(s->alg->pq_alg, BinarySink_UPCAST(s->pq_ek)); + s->classical = ecdh_key_new(s->alg->classical_alg, is_server); + return &s->ek; +} + +static void hybrid_client_free(ecdh_key *ek) +{ + hybrid_client_state *s = container_of(ek, hybrid_client_state, ek); + strbuf_free(s->pq_ek); + pq_kem_free_dk(s->pq_dk); + ecdh_key_free(s->classical); + sfree(s); +} + +/* + * In the client, getpublic is called first: we make up a KEM key + * pair, and send the public key along with a classical DH value. + */ +static void hybrid_client_getpublic(ecdh_key *ek, BinarySink *bs) +{ + hybrid_client_state *s = container_of(ek, hybrid_client_state, ek); + put_datapl(bs, ptrlen_from_strbuf(s->pq_ek)); + ecdh_key_getpublic(s->classical, bs); +} + +/* + * In the client, getkey is called second, after the server sends its + * response: we use our KEM private key to decapsulate the server's + * ciphertext. + */ +static bool hybrid_client_getkey(ecdh_key *ek, ptrlen remoteKey, BinarySink *bs) +{ + hybrid_client_state *s = container_of(ek, hybrid_client_state, ek); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + ssh_hash *h = ssh_hash_new(s->alg->combining_hash); + + ptrlen pq_ciphertext = get_data(src, s->alg->pq_alg->c_len); + if (get_err(src)) { + ssh_hash_free(h); + return false; /* not enough data */ + } + if (!pq_kem_decaps(s->pq_dk, BinarySink_UPCAST(h), pq_ciphertext)) { + ssh_hash_free(h); + return false; /* pq ciphertext didn't validate */ + } + + ptrlen classical_data = get_data(src, get_avail(src)); + strbuf *classical_key = strbuf_new(); + if (!ecdh_key_getkey(s->classical, classical_data, + BinarySink_UPCAST(classical_key))) { + ssh_hash_free(h); + strbuf_free(classical_key); + return false; /* classical DH key didn't validate */ + } + s->alg->reformat(ptrlen_from_strbuf(classical_key), BinarySink_UPCAST(h)); + strbuf_free(classical_key); + + /* + * Finish up: compute the final output hash and return it encoded + * as a string. + */ + unsigned char hashdata[MAX_HASH_LEN]; + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, s->alg->combining_hash->hlen)); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +static const ecdh_keyalg hybrid_client_vt = { + .new = hybrid_client_new, /* but normally the selector calls this */ + .free = hybrid_client_free, + .getpublic = hybrid_client_getpublic, + .getkey = hybrid_client_getkey, + .description = hybrid_description, + .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX, +}; + +/* ---------------------------------------------------------------------- + * Server side. + */ + +typedef struct hybrid_server_state hybrid_server_state; + +static const ecdh_keyalg hybrid_server_vt; + +struct hybrid_server_state { + const hybrid_alg *alg; + strbuf *pq_ciphertext; + ecdh_key *classical; + ecdh_key ek; +}; + +static ecdh_key *hybrid_server_new(const ssh_kex *kex, bool is_server) +{ + assert(is_server); + hybrid_server_state *s = snew(hybrid_server_state); + s->alg = kex->extra; + s->ek.vt = &hybrid_server_vt; + s->pq_ciphertext = strbuf_new_nm(); + s->classical = ecdh_key_new(s->alg->classical_alg, is_server); + return &s->ek; +} + +static void hybrid_server_free(ecdh_key *ek) +{ + hybrid_server_state *s = container_of(ek, hybrid_server_state, ek); + strbuf_free(s->pq_ciphertext); + ecdh_key_free(s->classical); + sfree(s); +} + +/* + * In the server, getkey is called first: we receive a KEM encryption + * key from the client and encapsulate a secret with it. We write the + * output secret to bs; the data we'll send to the client is saved to + * return from getpublic. + */ +static bool hybrid_server_getkey(ecdh_key *ek, ptrlen remoteKey, BinarySink *bs) +{ + hybrid_server_state *s = container_of(ek, hybrid_server_state, ek); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + ssh_hash *h = ssh_hash_new(s->alg->combining_hash); + + ptrlen pq_ek = get_data(src, s->alg->pq_alg->ek_len); + if (get_err(src)) { + ssh_hash_free(h); + return false; /* not enough data */ + } + if (!pq_kem_encaps(s->alg->pq_alg, + BinarySink_UPCAST(s->pq_ciphertext), + BinarySink_UPCAST(h), pq_ek)) { + ssh_hash_free(h); + return false; /* pq encryption key didn't validate */ + } + + ptrlen classical_data = get_data(src, get_avail(src)); + strbuf *classical_key = strbuf_new(); + if (!ecdh_key_getkey(s->classical, classical_data, + BinarySink_UPCAST(classical_key))) { + ssh_hash_free(h); + strbuf_free(classical_key); + return false; /* classical DH key didn't validate */ + } + s->alg->reformat(ptrlen_from_strbuf(classical_key), BinarySink_UPCAST(h)); + strbuf_free(classical_key); + + /* + * Finish up: compute the final output hash and return it encoded + * as a string. + */ + unsigned char hashdata[MAX_HASH_LEN]; + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, s->alg->combining_hash->hlen)); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +static void hybrid_server_getpublic(ecdh_key *ek, BinarySink *bs) +{ + hybrid_server_state *s = container_of(ek, hybrid_server_state, ek); + put_datapl(bs, ptrlen_from_strbuf(s->pq_ciphertext)); + ecdh_key_getpublic(s->classical, bs); +} + +static const ecdh_keyalg hybrid_server_vt = { + .new = hybrid_server_new, /* but normally the selector calls this */ + .free = hybrid_server_free, + .getkey = hybrid_server_getkey, + .getpublic = hybrid_server_getpublic, + .description = hybrid_description, + .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX, +}; + +/* ---------------------------------------------------------------------- + * Selector vtable that instantiates the appropriate one of the above, + * depending on is_server. + */ + +static ecdh_key *hybrid_selector_new(const ssh_kex *kex, bool is_server) +{ + if (is_server) + return hybrid_server_new(kex, is_server); + else + return hybrid_client_new(kex, is_server); +} + +static const ecdh_keyalg hybrid_selector_vt = { + /* This is a never-instantiated vtable which only implements the + * functions that don't require an instance. */ + .new = hybrid_selector_new, + .description = hybrid_description, + .packet_naming_ctx = SSH2_PKTCTX_HYBRIDKEX, +}; + +/* ---------------------------------------------------------------------- + * Actual KEX methods. + */ + +static const hybrid_alg ssh_ntru_curve25519_hybrid = { + .combining_hash = &ssh_sha512, + .pq_alg = &ssh_ntru, + .classical_alg = &ssh_ec_kex_curve25519, + .reformat = reformat_mpint_be_32, +}; + +static const ssh_kex ssh_ntru_curve25519 = { + .name = "sntrup761x25519-sha512", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &hybrid_selector_vt, + .extra = &ssh_ntru_curve25519_hybrid, +}; + +static const ssh_kex ssh_ntru_curve25519_openssh = { + .name = "sntrup761x25519-sha512@openssh.com", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &hybrid_selector_vt, + .extra = &ssh_ntru_curve25519_hybrid, +}; + +static const ssh_kex *const ntru_hybrid_list[] = { + &ssh_ntru_curve25519, + &ssh_ntru_curve25519_openssh, +}; + +const ssh_kexes ssh_ntru_hybrid_kex = { + lenof(ntru_hybrid_list), ntru_hybrid_list, +}; + +static const hybrid_alg ssh_mlkem768_curve25519_hybrid = { + .combining_hash = &ssh_sha256, + .pq_alg = &ssh_mlkem768, + .classical_alg = &ssh_ec_kex_curve25519, + .reformat = reformat_mpint_be_32, +}; + +static const ssh_kex ssh_mlkem768_curve25519 = { + .name = "mlkem768x25519-sha256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &hybrid_selector_vt, + .extra = &ssh_mlkem768_curve25519_hybrid, +}; + +static const ssh_kex *const mlkem_curve25519_hybrid_list[] = { + &ssh_mlkem768_curve25519, +}; + +const ssh_kexes ssh_mlkem_curve25519_hybrid_kex = { + lenof(mlkem_curve25519_hybrid_list), mlkem_curve25519_hybrid_list, +}; + +static const hybrid_alg ssh_mlkem768_p256_hybrid = { + .combining_hash = &ssh_sha256, + .pq_alg = &ssh_mlkem768, + .classical_alg = &ssh_ec_kex_nistp256, + .reformat = reformat_mpint_be_32, +}; + +static const ssh_kex ssh_mlkem768_p256 = { + .name = "mlkem768nistp256-sha256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &hybrid_selector_vt, + .extra = &ssh_mlkem768_p256_hybrid, +}; + +static const hybrid_alg ssh_mlkem1024_p384_hybrid = { + .combining_hash = &ssh_sha384, + .pq_alg = &ssh_mlkem1024, + .classical_alg = &ssh_ec_kex_nistp384, + .reformat = reformat_mpint_be_48, +}; + +static const ssh_kex ssh_mlkem1024_p384 = { + .name = "mlkem1024nistp384-sha384", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha384, + .ecdh_vt = &hybrid_selector_vt, + .extra = &ssh_mlkem1024_p384_hybrid, +}; + +static const ssh_kex *const mlkem_nist_hybrid_list[] = { + &ssh_mlkem1024_p384, + &ssh_mlkem768_p256, +}; + +const ssh_kexes ssh_mlkem_nist_hybrid_kex = { + lenof(mlkem_nist_hybrid_list), mlkem_nist_hybrid_list, +}; diff --git a/crypto/mlkem.c b/crypto/mlkem.c new file mode 100644 index 00000000..2074613a --- /dev/null +++ b/crypto/mlkem.c @@ -0,0 +1,1090 @@ +/* + * Implementation of ML-KEM, previously known as 'Crystals: Kyber'. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "mlkem.h" +#include "smallmoduli.h" + +/* ---------------------------------------------------------------------- + * General definitions. + */ + +/* + * Arithmetic in this system works mod 3329, which is prime, and + * congruent to 1 mod 256 (in fact it's 13*256 + 1), meaning that + * 256th roots of unity exist. + */ +#define Q 3329 + +/* + * Parameter structure describing a particular instance of ML-KEM. + */ +struct mlkem_params { + int k; /* dimensions of the matrices used */ + int eta_1, eta_2; /* parameters for mlkem_matrix_poly_cbd calls */ + int d_u, d_v; /* bit counts to use in lossy compressed encoding */ +}; + +/* + * Specific parameter sets. + */ +const mlkem_params mlkem_params_512 = { + .k = 2, .eta_1 = 3, .eta_2 = 2, .d_u = 10, .d_v = 4, +}; +const mlkem_params mlkem_params_768 = { + .k = 3, .eta_1 = 2, .eta_2 = 2, .d_u = 10, .d_v = 4, +}; +const mlkem_params mlkem_params_1024 = { + .k = 4, .eta_1 = 2, .eta_2 = 2, .d_u = 11, .d_v = 5, +}; +#define KMAX 4 + +/* ---------------------------------------------------------------------- + * Number-theoretic transform on ring elements. + * + * The ring R used by ML-KEM is (Z/qZ)[X] / (where q=3329 as + * above). If the quotient polynomial were X^256-1 then it would split + * into 256 linear factors, so that R could be expressed as the direct + * sum of 256 rings (Z/qZ)[X] / (where zeta is some fixed + * primitive 256th root of unity mod q), each isomorphic to Z/qZ + * itself. But X^256+1 only splits into 128 _quadratic_ factors, and + * hence we can only decompose R as the direct sum of rings of the + * form (Z/qZ)[X] / for odd j, each a quadratic extension + * of Z/qZ, and all mutually nonisomorphic. This means the NTT runs + * one pass fewer than you'd "normally" expect, and also, multiplying + * two elements of R in their NTT representation is not quite as + * trivial as it would normally be - within each component ring of the + * direct sum you have to do the multiplication slightly differently + * depending on the power of zeta in its quotient polynomial. + * + * We take zeta=17 to be the canonical primitive 256th root of unity + * for NTT purposes. + */ + +/* + * First 128 powers of zeta, reordered by bit-reversing the 7-bit + * index. That is, the nth element of this array contains + * zeta^(bitrev7(n)). Used by the NTT itself. + */ +static const uint16_t powers_reversed_order[128] = { + 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, 2786, + 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, 1426, 2094, + 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, 1197, 2304, 2277, + 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, 2319, 1435, 807, 452, + 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, 2474, 3110, 1227, 910, 17, + 2761, 583, 2649, 1637, 723, 2288, 1100, 1409, 2662, 3281, 233, 756, 2156, + 3015, 3050, 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687, 939, 2308, 2437, + 2388, 733, 2337, 268, 641, 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, + 1063, 319, 2773, 757, 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, + 2150, 2775, 886, 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, +}; + +/* + * First 128 _odd_ powers of zeta: the nth element is + * zeta^(2*bitrev7(n)+1). Each of these is used for multiplication in + * one of the 128 quadratic-extension rings in the NTT decomposition. + */ +static const uint16_t powers_odd_reversed_order[128] = { + 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, 2288, + 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, 756, 2573, + 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, 2789, 540, 1789, + 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, 939, 2390, 2308, 1021, + 2437, 892, 2388, 941, 733, 2596, 2337, 992, 268, 3061, 641, 2688, 1584, + 1745, 2298, 1031, 2037, 1292, 3220, 109, 375, 2954, 2549, 780, 2090, 1239, + 1645, 1684, 1063, 2266, 319, 3010, 2773, 556, 757, 2572, 2099, 1230, 561, + 2768, 2466, 863, 2594, 735, 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, + 1143, 2186, 2150, 1179, 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, + 1455, 1029, 2300, 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, +}; + +/* + * Convert a ring element into NTT representation. + * + * The input v is an array of 256 uint16_t, giving the coefficients of + * a polynomial in X, with v[i] being the coefficient of X^i. + * + * v is modified in place. On output, adjacent pairs of elements of v + * give the coefficients of a smaller polynomial in X, with the pair + * v[2i],v[2i+1] being the coefficients of X^0 and X^1 respectively in + * the ring (Z/qZ)[X] / , where k = powers_odd_reversed_order[i]. + */ +static void mlkem_ntt(uint16_t *v) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + size_t next_power = 1; + + for (size_t len = 128; len >= 2; len /= 2) { + for (size_t start = 0; start < 256; start += 2*len) { + uint16_t mult = powers_reversed_order[next_power++]; + for (size_t j = start; j < start + len; j++) { + uint16_t t = reduce(mult * v[j + len], Q, Qrecip); + v[j + len] = reduce(v[j] + Q - t, Q, Qrecip); + v[j] = reduce(v[j] + t, Q, Qrecip); + } + } + } +} + +/* + * Convert back from NTT representation. Exactly inverts mlkem_ntt(). + */ +static void mlkem_inverse_ntt(uint16_t *v) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + size_t next_power = 127; + + for (size_t len = 2; len <= 128; len *= 2) { + for (size_t start = 0; start < 256; start += 2*len) { + uint16_t mult = powers_reversed_order[next_power--]; + for (size_t j = start; j < start + len; j++) { + uint16_t t = v[j]; + v[j] = reduce(t + v[j + len], Q, Qrecip); + v[j + len] = reduce(mult * (v[j + len] + Q - t), Q, Qrecip); + } + } + } + + for (size_t i = 0; i < 256; i++) + v[i] = reduce(v[i] * 3303, Q, Qrecip); +} + +/* + * Multiply two elements of R in NTT representation. + * + * The output can alias an input completely, but mustn't alias one + * partially. + */ +static void mlkem_multiply_ntts( + uint16_t *out, const uint16_t *a, const uint16_t *b) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + + for (size_t i = 0; i < 128; i++) { + uint16_t a0 = a[2*i], a1 = a[2*i+1]; + uint16_t b0 = b[2*i], b1 = b[2*i+1]; + uint16_t mult = powers_odd_reversed_order[i]; + uint16_t a1b1 = reduce(a1 * b1, Q, Qrecip); + out[2*i] = reduce(a0 * b0 + a1b1 * mult, Q, Qrecip); + out[2*i+1] = reduce(a0 * b1 + a1 * b0, Q, Qrecip); + } +} + +/* ---------------------------------------------------------------------- + * Operations on matrices over the ring R. + * + * Most of these don't mind whether the matrix contains ring elements + * represented directly as polynomials, or in NTT form. The exception + * is that mlkem_matrix_mul requires it to be in NTT form (because + * multiplying is a huge pain in the ordinary representation). + */ + +typedef struct mlkem_matrix mlkem_matrix; +struct mlkem_matrix { + unsigned nrows, ncols; + + /* + * (nrows * ncols * 256) 16-bit integers. Each 256-word block + * contains an element of R; the blocks are in in row-major order, + * so that (data + 256*(ncols*y + x)) points at the start of the + * element in row y column x. + */ + uint16_t *data; +}; + +/* Storage used for multiple matrices, to free all at once afterwards */ +typedef struct mlkem_matrix_storage mlkem_matrix_storage; +struct mlkem_matrix_storage { + uint16_t *data; + size_t n; /* number of ring elements */ +}; + +/* + * Allocate space for multiple matrices. All the arrays of uint16_t + * are allocated as a single big array. This makes it easy to free the + * whole lot in one go afterwards. + * + * It also means that the arrays have a fixed memory relationship to + * each other, which matters not at all during live use, but + * eliminates spurious control-flow divergences in testsc based on + * accidents of memory allocation when vectorised code checks two + * memory regions to see if they alias. (The compiler-generated + * aliasing check must do two comparisons, one for each direction, and + * the order of those two regions in memory affects whether the first + * comparison decides the second one is necessary.) + * + * The variadic arguments for this function consist of a sequence of + * triples (mlkem_matrix *m, int nrows, int ncols), terminated by a + * null matrix pointer. + */ +static void mlkem_matrix_alloc(mlkem_matrix_storage *storage, ...) +{ + va_list ap; + mlkem_matrix *m; + + storage->n = 0; + va_start(ap, storage); + while ((m = va_arg(ap, mlkem_matrix *)) != NULL) { + int nrows = va_arg(ap, int), ncols = va_arg(ap, int); + storage->n += nrows * ncols; + } + va_end(ap); + + storage->data = snewn(256 * storage->n, uint16_t); + size_t pos = 0; + va_start(ap, storage); + while ((m = va_arg(ap, mlkem_matrix *)) != NULL) { + int nrows = va_arg(ap, int), ncols = va_arg(ap, int); + m->nrows = nrows; + m->ncols = ncols; + m->data = storage->data + 256 * pos; + pos += nrows * ncols; + } + va_end(ap); +} + +/* Clear and free the storage allocated by mlkem_matrix_alloc. */ +static void mlkem_matrix_storage_free(mlkem_matrix_storage *storage) +{ + smemclr(storage->data, 256 * storage->n * sizeof(uint16_t)); + sfree(storage->data); +} + +/* Add two matrices. */ +static void mlkem_matrix_add(mlkem_matrix *out, const mlkem_matrix *left, + const mlkem_matrix *right) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + + assert(out->nrows == left->nrows); + assert(out->ncols == left->ncols); + assert(out->nrows == right->nrows); + assert(out->ncols == right->ncols); + + for (size_t i = 0; i < out->nrows; i++) { + for (size_t j = 0; j < out->ncols; j++) { + const uint16_t *lv = left->data + 256*(i * left->ncols + j); + const uint16_t *rv = right->data + 256*(i * right->ncols + j); + uint16_t *ov = out->data + 256*(i * out->ncols + j); + for (size_t p = 0; p < 256; p++) + ov[p] = reduce(lv[p] + rv[p] , Q, Qrecip); + } + } +} + +/* Subtract matrices. */ +static void mlkem_matrix_sub(mlkem_matrix *out, const mlkem_matrix *left, + const mlkem_matrix *right) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + + assert(out->nrows == left->nrows); + assert(out->ncols == left->ncols); + assert(out->nrows == right->nrows); + assert(out->ncols == right->ncols); + + for (size_t i = 0; i < out->nrows; i++) { + for (size_t j = 0; j < out->ncols; j++) { + const uint16_t *lv = left->data + 256*(i * left->ncols + j); + const uint16_t *rv = right->data + 256*(i * right->ncols + j); + uint16_t *ov = out->data + 256*(i * out->ncols + j); + for (size_t p = 0; p < 256; p++) + ov[p] = reduce(lv[p] + Q - rv[p] , Q, Qrecip); + } + } +} + +/* Convert every element of a matrix into NTT representation. */ +static void mlkem_matrix_ntt(mlkem_matrix *m) +{ + for (size_t i = 0; i < m->nrows * m->ncols; i++) + mlkem_ntt(m->data + i * 256); +} + +/* Convert every element of a matrix out of NTT representation. */ +static void mlkem_matrix_inverse_ntt(mlkem_matrix *m) +{ + for (size_t i = 0; i < m->nrows * m->ncols; i++) + mlkem_inverse_ntt(m->data + i * 256); +} + +/* + * Multiply two matrices, assuming their elements to be currently in + * NTT representation. + * + * The left input must have the same number of columns as the right + * has rows, in the usual fashion. The output matrix is overwritten. + * + * If 'left_transposed' is true then the left matrix is used as if + * transposed. + */ +static void mlkem_matrix_mul(mlkem_matrix *out, const mlkem_matrix *left, + const mlkem_matrix *right, bool left_transposed) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + size_t left_nrows = (left_transposed ? left->ncols : left->nrows); + size_t left_ncols = (left_transposed ? left->nrows : left->ncols); + + assert(out->nrows == left_nrows); + assert(left_ncols == right->nrows); + assert(right->ncols == out->ncols); + + uint16_t work[256]; + + for (size_t i = 0; i < out->nrows; i++) { + for (size_t j = 0; j < out->ncols; j++) { + uint16_t *thisout = out->data + 256 * (i * out->ncols + j); + memset(thisout, 0, 256 * sizeof(uint16_t)); + for (size_t k = 0; k < right->nrows; k++) { + size_t left_index = left_transposed ? + k * left->ncols + i : i * left->ncols + k; + const uint16_t *lv = left->data + 256*left_index; + const uint16_t *rv = right->data + 256*(k * right->ncols + j); + mlkem_multiply_ntts(work, lv, rv); + for (size_t p = 0; p < 256; p++) + thisout[p] = reduce(thisout[p] + work[p], Q, Qrecip); + } + } + } + + smemclr(work, sizeof(work)); +} + +/* ---------------------------------------------------------------------- + * Random sampling functions to make up various kinds of randomised + * matrix and vector. + */ + +static void mlkem_sample_ntt(uint16_t *output, ptrlen seed); /* forward ref */ + +/* + * Invent a matrix based on a 32-bit random seed rho. + * + * This matrix is logically part of the public (encryption) key: it's + * not transmitted explicitly, but the seed is, so that the receiver + * can reconstruct the same matrix. As a result, this function + * _doesn't_ have to worry about side channel resistance, or even + * leaving data lying around in arrays. + */ +static void mlkem_matrix_from_seed(mlkem_matrix *m, const void *rho) +{ + for (unsigned r = 0; r < m->nrows; r++) { + for (unsigned c = 0; c < m->ncols; c++) { + unsigned char seedbuf[34]; + memcpy(seedbuf, rho, 32); + seedbuf[32] = c; + seedbuf[33] = r; + mlkem_sample_ntt(m->data + 256 * (r * m->nrows + c), + make_ptrlen(seedbuf, sizeof(seedbuf))); + } + } +} + +/* + * Invent a single element of the ring R, uniformly at random, derived + * in a specified way from the input random seed. + * + * Used as a subroutine of mlkem_matrix_from_seed() above. So, for the + * same reasons, this doesn't have to worry about side channels, + * making the 'rejection sampling' generation technique easy. + * + * The name SampleNTT (in the official spec) reflects the fact that + * the output elements are regarded as being in NTT representation. + * But since the NTT is a bijection, and the sampling is from the + * uniform probability distribution over R, nothing in this function + * actually needs to worry about that. + */ +static void mlkem_sample_ntt(uint16_t *output, ptrlen seed) +{ + ShakeXOF *sx = shake128_xof_from_input(seed); + unsigned char bytebuf[4]; + bytebuf[3] = '\0'; + + for (size_t pos = 0; pos < 256 ;) { + /* Read 3 bytes into the low-order end of bytebuf. The fourth + * byte is always 0, so this gives us a random 24-bit integer. */ + shake_xof_read(sx, &bytebuf, 3); + uint32_t random24 = GET_32BIT_LSB_FIRST(bytebuf); + + /* + * Split that integer up into two 12-bit ones, and use each + * one if it's in range (taking care for the second one that + * we didn't just reach the end of the buffer). + * + * This function is only used for generating matrices from an + * element of the public key, so we can use data-dependent + * control flow here without worrying about giving away + * secrets. + */ + uint16_t d1 = random24 & 0xFFF; + uint16_t d2 = random24 >> 12; + if (d1 < Q) + output[pos++] = d1; + if (d2 < Q && pos < 256) + output[pos++] = d2; + } + + shake_xof_free(sx); +} + +/* + * Invent a random vector, with its elements _not_ in NTT + * representation, and all the coefficients very small integers (a lot + * smaller than q) of one sign or the other. + * + * eta is a parameter of the probability distribution, sigma is an + * input 32-byte random seed. Each element of the vector is made by a + * separate hash operation based on sigma plus a distinguishing + * integer suffix; 'offset' indicates the starting point for those + * suffixes, so that the ith output value has suffix (offset+i). + */ +static void mlkem_matrix_poly_cbd( + mlkem_matrix *v, int eta, const void *sigma, int offset) +{ + const uint64_t Qrecip = reciprocal_for_reduction(Q); + + unsigned char seedbuf[33]; + memcpy(seedbuf, sigma, 32); + + unsigned char *randombuf = snewn(eta * 64, unsigned char); + + for (unsigned r = 0; r < v->nrows * v->ncols; r++) { + seedbuf[32] = r + offset; + ShakeXOF *sx = shake256_xof_from_input(make_ptrlen(seedbuf, 33)); + shake_xof_read(sx, randombuf, eta * 64); + shake_xof_free(sx); + + for (size_t i = 0; i < 256; i++) { + unsigned x = 0, y = 0; + for (size_t j = 0; j < eta; j++) { + size_t bitpos = 2 * i * eta + j; + x += 1 & ((randombuf[bitpos >> 3]) >> (bitpos & 7)); + } + for (size_t j = 0; j < eta; j++) { + size_t bitpos = 2 * i * eta + eta + j; + y += 1 & ((randombuf[bitpos >> 3]) >> (bitpos & 7)); + } + v->data[256 * r + i] = reduce(x + Q - y, Q, Qrecip); + } + } + smemclr(seedbuf, sizeof(seedbuf)); + smemclr(randombuf, eta * 64); + sfree(randombuf); +} + +/* ---------------------------------------------------------------------- + * Byte-encoding and decoding functions. + */ + +/* + * Losslessly encode one or more elements of the ring R. + * + * Each polynomial coefficient, in the range [0,q), is represented as + * a 12-bit integer. So encoding an entire ring element requires + * (256*12)/8 = 384 bytes, and if that 384-byte string were + * interpreted as a little-endian 3072-bit integer D, then the + * coefficient of X^i could be recovered as (D >> (12*i)) & 0xFFF. + * + * The input is expected to be an array of 256*n uint16_t (often the + * 'data' pointer in an mlkem_matrix). The output is 384*n bytes. + */ +static void mlkem_byte_encode_lossless( + void *outv, const uint16_t *in, size_t n) +{ + unsigned char *out = (unsigned char *)outv; + uint32_t buffer = 0, bufbits = 0; + for (size_t i = 0; i < 256*n; i++) { + buffer |= (uint32_t) in[i] << bufbits; + bufbits += 12; + while (bufbits >= 8) { + *out++ = buffer & 0xFF; + buffer >>= 8; + bufbits -= 8; + } + } +} + +/* + * Decode a string written by mlkem_byte_encode_lossless. + * + * Each 12-bit value extracted from the input data is checked to make + * sure it's in the range [0,q); if it's out of range, the whole + * function fails and returns false. (But it need not do so in + * constant time, because that's an "abandon the whole connection" + * error, not a "subtly make things not work for the attacker" error.) + */ +static bool mlkem_byte_decode_lossless( + uint16_t *out, const void *inv, size_t n) +{ + const unsigned char *in = (const unsigned char *)inv; + uint32_t buffer = 0, bufbits = 0; + for (size_t i = 0; i < 384*n; i++) { + buffer |= (uint32_t) in[i] << bufbits; + bufbits += 8; + while (bufbits >= 12) { + uint16_t value = buffer & 0xFFF; + if (value >= Q) + return false; + *out++ = value; + buffer >>= 12; + bufbits -= 12; + } + } + + return true; +} + +/* + * Lossily encode one or more elements of R, using d bits for each + * polynomial coefficient, for some d < 12. Each output d-bit value is + * obtained as if by regarding the input coefficient as an integer in + * the range [0,q), multiplying by 2^d/q, and rounding to the nearest + * integer. (Since q is odd, 'round to nearest' can't have a tie.) + * + * This means that a large enough input coefficient can round up to + * 2^d itself. In that situation the output d-bit value is 0. + */ +static void mlkem_byte_encode_compressed( + void *outv, const uint16_t *in, unsigned d, size_t n) +{ + const uint64_t Qrecip = reciprocal_for_reduction(2*Q); + + unsigned char *out = (unsigned char *)outv; + uint32_t buffer = 0, bufbits = 0; + for (size_t i = 0; i < 256*n; i++) { + uint32_t dividend = ((uint32_t)in[i] << (d+1)) + Q; + uint32_t quotient; + reduce_with_quot(dividend, "ient, 2*Q, Qrecip); + buffer |= (uint32_t) (quotient & ((1 << d) - 1)) << bufbits; + bufbits += d; + while (bufbits >= 8) { + *out++ = buffer & 0xFF; + buffer >>= 8; + bufbits -= 8; + } + } +} + +/* + * Decode the lossily encoded output of mlkem_byte_encode_compressed. + * + * Each d-bit chunk of the encoding is converted back into a + * polynomial coefficient as if by multiplying by q/2^d and then + * rounding to nearest. Unlike the rounding in the encode step, this + * _can_ have a tie when an unrounded value is half way between two + * integers. Ties are broken by rounding up (as if the whole rounding + * were performed by the simple rounding method of adding 1/2 and then + * truncating). + * + * Unlike the lossless decode function, this one can't fail input + * validation, because any d-bit value generates some legal + * coefficient. + */ +static void mlkem_byte_decode_compressed( + uint16_t *out, const void *inv, unsigned d, size_t n) +{ + const unsigned char *in = (const unsigned char *)inv; + uint32_t buffer = 0, bufbits = 0; + for (size_t i = 0; i < 32*d*n; i++) { + buffer |= (uint32_t) in[i] << bufbits; + bufbits += 8; + while (bufbits >= d) { + uint32_t value = buffer & ((1 << d) - 1); + *out++ = (value * (2*Q) + (1 << d)) >> (d + 1);; + buffer >>= d; + bufbits -= d; + } + } +} + +/* ---------------------------------------------------------------------- + * The top-level ML-KEM functions. + */ + +/* + * Innermost keygen function, exposed for side-channel testing, with + * separate random values rho (public) and sigma (private), so that + * testsc can vary sigma while leaving rho the same. + */ +void mlkem_keygen_rho_sigma( + BinarySink *ek_out, BinarySink *dk_out, const mlkem_params *params, + const void *rho, const void *sigma, const void *z) +{ + mlkem_matrix_storage storage[1]; + mlkem_matrix a[1], s[1], e[1], t[1]; + mlkem_matrix_alloc(storage, + a, params->k, params->k, + s, params->k, 1, + e, params->k, 1, + t, params->k, 1, + (mlkem_matrix *)NULL); + + /* + * Make a random k x k matrix A (regarded as in NTT form). + */ + mlkem_matrix_from_seed(a, rho); + + /* + * Make two column vectors s and e, with all components having + * small polynomial coefficients, and then convert them _into_ NTT + * form. + */ + mlkem_matrix_poly_cbd(s, params->eta_1, sigma, 0); + mlkem_matrix_poly_cbd(e, params->eta_1, sigma, params->k); + mlkem_matrix_ntt(s); + mlkem_matrix_ntt(e); + + /* + * Compute the vector t = As + e. + */ + mlkem_matrix_mul(t, a, s, false); + mlkem_matrix_add(t, t, e); + + /* + * The encryption key is the vector t, plus the random seed rho + * from which anyone can reconstruct the matrix A. + */ + unsigned char ek[1568]; + mlkem_byte_encode_lossless(ek, t->data, params->k); + memcpy(ek + 384 * params->k, rho, 32); + size_t eklen = 384 * params->k + 32; + put_data(ek_out, ek, eklen); + + /* + * The decryption key (for the internal "K-PKE" public-key system) + * is the vector s. + */ + unsigned char dk[1536]; + mlkem_byte_encode_lossless(dk, s->data, params->k); + size_t dklen = 384 * params->k; + + /* + * The decapsulation key, for the full ML-KEM, consists of + * - the decryption key as above + * - the encryption key + * - an extra hash of the encryption key + * - the random value z used for "implicit rejection", aka + * constructing a useless output value if tampering is + * detected. (I think so an attacker can't tell the difference + * between "I was rumbled" and "I was undetected but my attempt + * didn't generate the right key">) + */ + put_data(dk_out, dk, dklen); + put_data(dk_out, ek, eklen); + ssh_hash *h = ssh_hash_new(&ssh_sha3_256); + put_data(h, ek, eklen); + unsigned char ekhash[32]; + ssh_hash_final(h, ekhash); + put_data(dk_out, ekhash, 32); + put_data(dk_out, z, 32); + + mlkem_matrix_storage_free(storage); + smemclr(ek, sizeof(ek)); + smemclr(ekhash, sizeof(ekhash)); + smemclr(dk, sizeof(dk)); +} + +/* + * Internal keygen function as described in the official spec, taking + * random values d and z and deterministically constructing a key from + * them. The test vectors are expressed in terms of this. + */ +void mlkem_keygen_internal( + BinarySink *ek, BinarySink *dk, const mlkem_params *params, + const void *d, const void *z) +{ + /* Hash the input randomness d to make two 32-byte values rho and sigma */ + unsigned char rho_sigma[64]; + ssh_hash *h = ssh_hash_new(&ssh_sha3_512); + put_data(h, d, 32); + put_byte(h, params->k); + ssh_hash_final(h, rho_sigma); + mlkem_keygen_rho_sigma(ek, dk, params, rho_sigma, rho_sigma + 32, z); + smemclr(rho_sigma, sizeof(rho_sigma)); +} + +/* + * Keygen function for live use, making up the values at random. + */ +void mlkem_keygen( + BinarySink *ek, BinarySink *dk, const mlkem_params *params) +{ + unsigned char dz[64]; + random_read(dz, 64); + mlkem_keygen_internal(ek, dk, params, dz, dz + 32); + smemclr(dz, sizeof(dz)); +} + +/* + * Internal encapsulation function from the official spec, taking a + * random value m as input and behaving deterministically. Again used + * for test vectors. + */ +bool mlkem_encaps_internal( + BinarySink *c_out, BinarySink *k_out, + const mlkem_params *params, ptrlen ek, const void *m) +{ + mlkem_matrix_storage storage[1]; + mlkem_matrix t[1], a[1], y[1], e1[1], e2[1], mu[1], u[1], v[1]; + mlkem_matrix_alloc(storage, + t, params->k, 1, + a, params->k, params->k, + y, params->k, 1, + e1, params->k, 1, + e2, 1, 1, + mu, 1, 1, + u, params->k, 1, + v, 1, 1, + (mlkem_matrix *)NULL); + + /* + * Validate input: ek must be the correct length, and its encoded + * ring elements must not include any 16-bit integer intended to + * represent a value mod q which is not in fact in the range [0,q). + * + * We test the latter property by decoding the matrix t, and + * checking the success status returned by the decode. + */ + if (ek.len != 384 * params->k + 32 || + !mlkem_byte_decode_lossless(t->data, ek.ptr, params->k)) { + mlkem_matrix_storage_free(storage); + return false; + } + + /* + * Regenerate the same matrix A used by key generation, from the + * seed string rho at the end of ek. + */ + mlkem_matrix_from_seed(a, (const unsigned char *)ek.ptr + 384 * params->k); + + /* + * Hash the input randomness m, to get the value k we'll use as + * the output shared secret, plus some randomness for making up + * the vectors below. + */ + unsigned char kr[64]; + unsigned char ekhash[32]; + ssh_hash *h; + /* Hash the encryption key */ + h = ssh_hash_new(&ssh_sha3_256); + put_datapl(h, ek); + ssh_hash_final(h, ekhash); + /* Hash the input randomness m with that hash */ + h = ssh_hash_new(&ssh_sha3_512); + put_data(h, m, 32); + put_data(h, ekhash, 32); + ssh_hash_final(h, kr); + const unsigned char *k = kr, *r = kr + 32; + + /* + * Invent random k-element vectors y and e1, and a random scalar + * e2 (here represented as a 1x1 matrix for the sake of not + * proliferating internal helper functions). All are generated by + * poly_cbd (i.e. their ring elements have polynomial coefficients + * of small magnitude). y needs to be in NTT form. + * + * These generations all use r as their seed, which was the second + * half of the 64-byte hash of the input m. We pass different + * 'offset' values to mlkem_matrix_poly_cbd() to ensure the + * generations are probabilistically independent. + */ + mlkem_matrix_poly_cbd(y, params->eta_1, r, 0); + mlkem_matrix_ntt(y); + + mlkem_matrix_poly_cbd(e1, params->eta_2, r, params->k); + mlkem_matrix_poly_cbd(e2, params->eta_2, r, 2 * params->k); + + /* + * Invent a random scalar mu (again imagined as a 1x1 matrix), + * this time by doing lossy decompression of the random value m at + * 1 bit per polynomial coefficient. That is, all the polynomial + * coefficients of mu are either 0 or 1665 = (q+1)/2. + * + * This generation reuses the _input_ random value m, not either + * half of the hash we made of it. + */ + mlkem_byte_decode_compressed(mu->data, m, 1, 1); + + /* + * Calculate a k-element vector u = A^T y + e1. + * + * A and y are in NTT representation, but e1 is not, and we don't + * want the output to be in NTT form either. So we perform an + * inverse NTT after the multiplication. + */ + mlkem_matrix_mul(u, a, y, true); /* regard a as transposed */ + mlkem_matrix_inverse_ntt(u); + mlkem_matrix_add(u, u, e1); + + /* + * Calculate a scalar v = t^T y + e2 + mu. + * + * (t and y are column vectors, so t^T y is just a scalar - you + * could think of it as the dot product t.y if you preferred.) + * + * Similarly to above, we multiply t and y which are in NTT + * representation, and then perform an inverse NTT before adding + * e2 and mu, which aren't. + */ + mlkem_matrix_mul(v, t, y, true); /* regard t as transposed */ + mlkem_matrix_inverse_ntt(v); + mlkem_matrix_add(v, v, e2); + mlkem_matrix_add(v, v, mu); + + /* + * The ciphertext consists of u and v, both encoded lossily, with + * different numbers of bits retained per element. + */ + char c[1568]; + mlkem_byte_encode_compressed(c, u->data, params->d_u, params->k); + mlkem_byte_encode_compressed(c + 32 * params->k * params->d_u, + v->data, params->d_v, 1); + put_data(c_out, c, 32 * (params->k * params->d_u + params->d_v)); + + /* + * The output shared secret is just half of the hash of m (the + * first half, which we didn't use for generating vectors above). + */ + put_data(k_out, k, 32); + + smemclr(kr, sizeof(kr)); + mlkem_matrix_storage_free(storage); + + return true; +} + +/* + * Encapsulation function for live use, using the real RNG.. + */ +bool mlkem_encaps(BinarySink *ciphertext, BinarySink *kout, + const mlkem_params *params, ptrlen ek) +{ + unsigned char m[32]; + random_read(m, 32); + bool success = mlkem_encaps_internal(ciphertext, kout, params, ek, m); + smemclr(m, sizeof(m)); + return success; +} + +/* + * Decapsulation. + */ +bool mlkem_decaps(BinarySink *k_out, const mlkem_params *params, + ptrlen dk, ptrlen c) +{ + /* + * Validation: check the input strings are the right lengths. + */ + if (dk.len != 768 * params->k + 96) + return false; + if (c.len != 32 * (params->d_u * params->k + params->d_v)) + return false; + + /* + * Further validation: extract the encryption key from the middle + * of dk, hash it, and check the hash matches. + */ + const unsigned char *dkp = (const unsigned char *)dk.ptr; + const unsigned char *cp = (const unsigned char *)c.ptr; + ptrlen ek = make_ptrlen(dkp + 384*params->k, 384*params->k + 32); + ssh_hash *h; + unsigned char ekhash[32]; + h = ssh_hash_new(&ssh_sha3_256); + put_datapl(h, ek); + ssh_hash_final(h, ekhash); + if (!smemeq(ekhash, dkp + 768*params->k + 32, 32)) + return false; + + mlkem_matrix_storage storage[1]; + mlkem_matrix u[1], v[1], s[1], w[1]; + mlkem_matrix_alloc(storage, + u, params->k, 1, + v, 1, 1, + s, params->k, 1, + w, 1, 1, + (mlkem_matrix *)NULL); + /* + * Decode the vector u and the scalar v from the ciphertext. These + * won't come out exactly the same as the originals, because of + * the lossy compression. + */ + mlkem_byte_decode_compressed(u->data, cp, params->d_u, params->k); + mlkem_matrix_ntt(u); + mlkem_byte_decode_compressed(v->data, cp + 32 * params->d_u * params->k, + params->d_v, 1); + + /* + * Decode the vector s from the private key. + */ + mlkem_byte_decode_lossless(s->data, dkp, params->k); + + /* + * Calculate the scalar w = v - s^T u. + * + * s and u are in NTT representation, but v isn't, so we + * inverse-NTT the product before doing the subtraction. Therefore + * w is not in NTT form either. + */ + mlkem_matrix_mul(w, s, u, true); /* regard s as transposed */ + mlkem_matrix_inverse_ntt(w); + mlkem_matrix_sub(w, v, w); + + /* + * The aim is that this reconstructs something close enough to the + * random vector mu that was made from the input secret m to + * encapsulation, on the grounds that mu's polynomial coefficients + * were very widely separated (on opposite sides of the cyclic + * additive group of Z/qZ) and the noise added during encryption + * all had _small_ polynomial coefficients. + * + * So we now re-encode this lossily at 1 bit per polynomial + * coefficient, and hope that it reconstructs the actual string m. + * + * However, this _is_ only a hope! The ML-KEM decryption is not a + * true mathematical inverse to encryption. With extreme bad luck, + * the noise can add up enough that it flips a bit of m, and + * everything fails. The parameters are chosen to make this happen + * with negligible probability (the same kind of low probability + * that makes you not worry about spontaneous hash collisions), + * but it's not actually impossible. + */ + unsigned char m[32]; + mlkem_byte_encode_compressed(m, w->data, 1, 1); + + /* + * Now do the key _encapsulation_ again from scratch, using that + * secret m as input, and check that it generates the identical + * ciphertext. This should catch the above theoretical failure, + * but also, it's a defence against malicious intervention in the + * key exchange. + * + * This is also where we get the output secret k from: the + * encapsulation function creates it as half of the hash of m. + */ + unsigned char c_regen[1568], k[32]; + buffer_sink c_sink[1], k_sink[1]; + buffer_sink_init(c_sink, c_regen, sizeof(c_regen)); + buffer_sink_init(k_sink, k, sizeof(k)); + bool success = mlkem_encaps_internal( + BinarySink_UPCAST(c_sink), BinarySink_UPCAST(k_sink), params, ek, m); + /* If any application of ML-KEM uses a dk given to it by someone + * else, then perhaps they have to worry about being given an + * invalid one? But in our application we always expect this to + * succeed, because dk is generated and used at the same end of + * the SSH connection, within the same process, and nobody is + * interfering with it. */ + assert(success && "We generated this dk ourselves, how can it be bad?"); + + /* + * If mlkem_encaps_internal returned success but delivered the + * wrong ciphertext, that's a failure, but we must be careful not + * to let the attacker know exactly what went wrong. So we + * generate a plausible but wrong substitute output secret. + * + * k_reject is that secret; for constant-time reasons we generate + * it unconditionally. + */ + unsigned char k_reject[32]; + h = ssh_hash_new(&ssh_shake256_32bytes); + put_data(h, dkp + 768 * params->k + 64, 32); + put_datapl(h, c); + ssh_hash_final(h, k_reject); + + /* + * Now replace k with k_reject if the ciphertexts didn't match. + */ + assert((void *)c_sink->out == (void *)(c_regen + c.len)); + unsigned match = smemeq(c.ptr, c_regen, c.len); + unsigned mask = match - 1; + for (size_t i = 0; i < 32; i++) + k[i] ^= mask & (k[i] ^ k_reject[i]); + + /* + * And we're done! Free everything and return whichever secret we + * chose. + */ + put_data(k_out, k, 32); + mlkem_matrix_storage_free(storage); + smemclr(m, sizeof(m)); + smemclr(c_regen, sizeof(c_regen)); + smemclr(k, sizeof(k)); + smemclr(k_reject, sizeof(k_reject)); + return true; +} + +/* ---------------------------------------------------------------------- + * Implement the pq_kemalg vtable in terms of the above functions. + */ + +struct mlkem_dk { + strbuf *encoded; + pq_kem_dk dk; +}; + +static pq_kem_dk *mlkem_vt_keygen(const pq_kemalg *alg, BinarySink *ek) +{ + struct mlkem_dk *mdk = snew(struct mlkem_dk); + mdk->dk.vt = alg; + mdk->encoded = strbuf_new_nm(); + mlkem_keygen(ek, BinarySink_UPCAST(mdk->encoded), alg->extra); + return &mdk->dk; +} + +static bool mlkem_vt_encaps(const pq_kemalg *alg, BinarySink *c, BinarySink *k, + ptrlen ek) +{ + return mlkem_encaps(c, k, alg->extra, ek); +} + +static bool mlkem_vt_decaps(pq_kem_dk *dk, BinarySink *k, ptrlen c) +{ + struct mlkem_dk *mdk = container_of(dk, struct mlkem_dk, dk); + return mlkem_decaps(k, mdk->dk.vt->extra, + ptrlen_from_strbuf(mdk->encoded), c); +} + +static void mlkem_vt_free_dk(pq_kem_dk *dk) +{ + struct mlkem_dk *mdk = container_of(dk, struct mlkem_dk, dk); + strbuf_free(mdk->encoded); + sfree(mdk); +} + +const pq_kemalg ssh_mlkem512 = { + .keygen = mlkem_vt_keygen, + .encaps = mlkem_vt_encaps, + .decaps = mlkem_vt_decaps, + .free_dk = mlkem_vt_free_dk, + .extra = &mlkem_params_512, + .description = "ML-KEM-512", + .ek_len = 384 * 2 + 32, + .c_len = 32 * (10 * 2 + 4), +}; + +const pq_kemalg ssh_mlkem768 = { + .keygen = mlkem_vt_keygen, + .encaps = mlkem_vt_encaps, + .decaps = mlkem_vt_decaps, + .free_dk = mlkem_vt_free_dk, + .extra = &mlkem_params_768, + .description = "ML-KEM-768", + .ek_len = 384 * 3 + 32, + .c_len = 32 * (10 * 3 + 4), +}; + +const pq_kemalg ssh_mlkem1024 = { + .keygen = mlkem_vt_keygen, + .encaps = mlkem_vt_encaps, + .decaps = mlkem_vt_decaps, + .free_dk = mlkem_vt_free_dk, + .extra = &mlkem_params_1024, + .description = "ML-KEM-1024", + .ek_len = 384 * 4 + 32, + .c_len = 32 * (11 * 4 + 5), +}; diff --git a/crypto/mlkem.h b/crypto/mlkem.h new file mode 100644 index 00000000..65a677bc --- /dev/null +++ b/crypto/mlkem.h @@ -0,0 +1,89 @@ +/* + * Internal functions for the ML-KEM cryptosystem, exposed in a header + * that is expected to be included only by mlkem.c and test programs. + */ + +#ifndef PUTTY_CRYPTO_MLKEM_H +#define PUTTY_CRYPTO_MLKEM_H + +typedef struct mlkem_params mlkem_params; + +extern const mlkem_params mlkem_params_512; +extern const mlkem_params mlkem_params_768; +extern const mlkem_params mlkem_params_1024; + +/* + * ML-KEM key generation. + * + * The official spec gives two APIs for this function: an outer one + * that invents random data from an implicit PRNG parameter, and an + * inner one that takes the randomness as explicit input for running + * test vectors. + * + * To make side-channel testing easier, I introduce a third API inside + * the latter. The spec's "inner" function takes a parameter 'd' + * containing 32 bytes of randomness, which it immediately expands + * into a 64-byte hash and then uses the two halves of that hash for + * different purposes. My even-more-inner function expects the caller + * to have done that hashing already, and to present the two 32-byte + * half-hashes rho and sigma separately. + * + * Rationale: it would be difficult to make the keygen running time + * independent of rho, becase the required technique for constructing + * a matrix from rho uses rejection sampling, so timing will depend on + * how many samples were rejected. Happily, it's also not _necessary_ + * to make the timing independent of rho, because rho is part of the + * _public_ key, so it's sent in clear over the wire anyway. So for + * testsc purposes, it's convenient to regard rho as fixed and vary + * sigma, so that the timing variations due to rho don't show up as + * failures in the test. + * + * Inputs: 'd', 'z', 'rho' and 'sigma' are all 32-byte random strings. + * + * Return: the encryption and decryption keys are written to the two + * provided BinarySinks. + */ +void mlkem_keygen( + BinarySink *ek, BinarySink *dk, const mlkem_params *params); +void mlkem_keygen_internal( + BinarySink *ek, BinarySink *dk, const mlkem_params *params, + const void *d, const void *z); +void mlkem_keygen_rho_sigma( + BinarySink *ek, BinarySink *dk, const mlkem_params *params, + const void *rho, const void *sigma, const void *z); + +/* + * ML-KEM key encapsulation, with only two forms, the outer (random) + * and inner (for test vectors) versions from the spec. + * + * Inputs: the encryption key from keygen. 'm' should be a 32-byte + * random string if provided. + * + * Returns: if successful, returns true, and writes to the two + * BinarySinks a ciphertext to send to the other side, and our copy of + * the output shared secret k. If failure, returns false, and the + * strbuf pointers aren't filled in at all. + */ +bool mlkem_encaps(BinarySink *ciphertext, BinarySink *kout, + const mlkem_params *params, ptrlen ek); +bool mlkem_encaps_internal(BinarySink *ciphertext, BinarySink *kout, + const mlkem_params *params, ptrlen ek, + const void *m); + +/* + * ML-KEM key decapsulation. This doesn't use any randomness, so even + * the official spec only presents one version of it. (Actually it + * defines two functions, but the outer one adds nothing over the + * inner one.) + * + * Inputs: the decryption key from keygen, and the ciphertext output + * from encapsulation. + * + * Returns: false on validation failure, and true otherwise + * (regardless of whether the ciphertext was implicitly rejected). The + * shared secret k is written to the provided BinarySink. + */ +bool mlkem_decaps(BinarySink *k, const mlkem_params *params, + ptrlen dk, ptrlen c); + +#endif /* PUTTY_CRYPTO_MLKEM_H */ diff --git a/crypto/ntru.c b/crypto/ntru.c index 60c37e2e..80c35179 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -79,58 +79,14 @@ #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; -} +#include "smallmoduli.h" /* Invert x mod q, assuming it's nonzero. (For time-safety, no check - * is made for zero; it just returns 0.) */ + * is made for zero; it just returns 0.) + * + * Expects qrecip == reciprocal_for_reduction(q). (But it's passed in + * as a parameter to save recomputing it, on the theory that the + * caller will have had it lying around already in most cases.) */ static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip) { /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */ @@ -1476,14 +1432,7 @@ static void ntru_session_hash( } /* ---------------------------------------------------------------------- - * 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. + * Top-level KEM functions. */ /* @@ -1495,399 +1444,138 @@ static void ntru_session_hash( #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 { +struct ntru_dk { 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, + strbuf *encoded; + pq_kem_dk dk; }; -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) +static pq_kem_dk *ntru_vt_keygen(const pq_kemalg *alg, BinarySink *ek) { - ntru_client_key *nk = container_of(dh, ntru_client_key, ek); - ntru_keypair_free(nk->keypair); - ecdh_key_free(nk->curve25519); - sfree(nk); + struct ntru_dk *ndk = snew(struct ntru_dk); + ndk->dk.vt = alg; + ndk->encoded = strbuf_new_nm(); + ndk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE); + ntru_encode_pubkey(ndk->keypair->h, p_LIVE, q_LIVE, ek); + return &ndk->dk; } -static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs) +static bool ntru_vt_encaps(const pq_kemalg *alg, BinarySink *c, BinarySink *k, + ptrlen ek) { - 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); + BinarySource_BARE_INIT_PL(src, ek); - 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); + uint16_t *pubkey = snewn(p_LIVE, uint16_t); + ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); if (get_err(src) || get_avail(src)) { - /* Hard-fail if the input wasn't exactly the right length */ - ring_free(ciphertext, p_LIVE); + /* 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. - */ - { - /* 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)); + /* Invent a valid NTRU plaintext. */ + uint16_t *plaintext = snewn(p_LIVE, uint16_t); + ntru_gen_short(plaintext, p_LIVE, w_LIVE); - 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)); + /* Encrypt the plaintext, 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, plaintext, pubkey, p_LIVE, q_LIVE); + strbuf *ciphertext_encoded = strbuf_new_nm(); + ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, + BinarySink_UPCAST(ciphertext_encoded)); + put_datapl(c, ptrlen_from_strbuf(ciphertext_encoded)); + + /* Compute the confirmation hash, and append that to the data sent + * to the other side. */ + uint8_t confhash[32]; + ntru_confirmation_hash(confhash, plaintext, pubkey, p_LIVE, q_LIVE); + put_data(c, confhash, 32); + + /* Compute the session hash, i.e. the output shared secret. */ + uint8_t sesshash[32]; + ntru_session_hash(sesshash, 1, plaintext, p_LIVE, + ptrlen_from_strbuf(ciphertext_encoded), + make_ptrlen(confhash, 32)); + put_data(k, sesshash, 32); + + ring_free(pubkey, p_LIVE); + ring_free(plaintext, p_LIVE); + ring_free(ciphertext, p_LIVE); + strbuf_free(ciphertext_encoded); + smemclr(confhash, sizeof(confhash)); + smemclr(sesshash, sizeof(sesshash)); 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) +static bool ntru_vt_decaps(pq_kem_dk *dk, BinarySink *k, ptrlen c) { - 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); -} + struct ntru_dk *ndk = container_of(dk, struct ntru_dk, dk); -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. - */ + /* Expect a string containing a ciphertext and a confirmation hash. */ BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, remoteKey); + BinarySource_BARE_INIT_PL(src, c); - uint16_t *pubkey = snewn(p_LIVE, uint16_t); - ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); - ptrlen curve25519_remoteKey = get_data(src, 32); + uint16_t *ciphertext = snewn(p_LIVE, uint16_t); + ptrlen ciphertext_encoded = ntru_decode_ciphertext( + ciphertext, ndk->keypair, src); + ptrlen confirmation_hash = 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); + /* 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]; + /* Decrypt the ciphertext to recover the sender's plaintext */ + uint16_t *plaintext = snewn(p_LIVE, uint16_t); + ntru_decrypt(plaintext, ciphertext, ndk->keypair); - /* - * 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); + /* Make the confirmation hash */ + uint8_t confhash[32]; + ntru_confirmation_hash(confhash, plaintext, ndk->keypair->h, + p_LIVE, q_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); + /* Check it matches the one the server sent */ + unsigned ok = smemeq(confhash, confirmation_hash.ptr, 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)); + /* 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] ^ ndk->keypair->rho[i]); - /* And put the NTRU session hash into the main hash object. */ - put_data(h, hashdata, 32); + /* Compute the session hash, whether or not we did that */ + uint8_t sesshash[32]; + ntru_session_hash(sesshash, ok, plaintext, p_LIVE, ciphertext_encoded, + confirmation_hash); + put_data(k, sesshash, 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)); + ring_free(plaintext, p_LIVE); + ring_free(ciphertext, p_LIVE); + smemclr(confhash, sizeof(confhash)); + smemclr(sesshash, sizeof(sesshash)); 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) +static void ntru_vt_free_dk(pq_kem_dk *dk) { - if (is_server) - return ssh_ntru_server_new(); - else - return ssh_ntru_client_new(); + struct ntru_dk *ndk = container_of(dk, struct ntru_dk, dk); + strbuf_free(ndk->encoded); + ntru_keypair_free(ndk->keypair); + sfree(ndk); } -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_openssh = { - .name = "sntrup761x25519-sha512@openssh.com", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ntru_selector_vt, -}; - -static const ssh_kex ssh_ntru_curve25519 = { - /* Same as sntrup761x25519-sha512@openssh.com but with an - * IANA-assigned name */ - .name = "sntrup761x25519-sha512", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ntru_selector_vt, -}; - -static const ssh_kex *const hybrid_list[] = { - &ssh_ntru_curve25519, - &ssh_ntru_curve25519_openssh, +const pq_kemalg ssh_ntru = { + .keygen = ntru_vt_keygen, + .encaps = ntru_vt_encaps, + .decaps = ntru_vt_decaps, + .free_dk = ntru_vt_free_dk, + .description = "NTRU Prime", + .ek_len = 1158, + .c_len = 1039, }; - -const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list }; diff --git a/crypto/sha3.c b/crypto/sha3.c index 83d136bf..efd8f894 100644 --- a/crypto/sha3.c +++ b/crypto/sha3.c @@ -326,4 +326,76 @@ static void shake256_reset(ssh_hash *hash) HASHALG_NAMES_BARE("SHAKE" #param), \ } -DEFINE_SHAKE(256, 114); +DEFINE_SHAKE(256, 114); /* used by Ed448 */ +DEFINE_SHAKE(256, 32); /* used by ML-KEM */ + +struct ShakeXOF { + keccak_state state; + unsigned char *buf; + size_t bytes_per_transform, pos; +}; + +static ShakeXOF *shake_xof_from_input(unsigned bits, ptrlen data) +{ + ShakeXOF *sx = snew_plus(ShakeXOF, 200 * 64); + sx->buf = snew_plus_get_aux(sx); + + /* Initialise as if we were generating 0 bytes of hash. That way, + * keccak_output will do the final accumulation but generate no data. */ + keccak_shake_init(&sx->state, bits, 0); + keccak_accumulate(&sx->state, data.ptr, data.len); + keccak_output(&sx->state, NULL); + + sx->bytes_per_transform = 200 - bits/4; + sx->pos = 0; + + return sx; +} + +ShakeXOF *shake128_xof_from_input(ptrlen data) +{ + return shake_xof_from_input(128, data); +} + +ShakeXOF *shake256_xof_from_input(ptrlen data) +{ + return shake_xof_from_input(256, data); +} + +void shake_xof_read(ShakeXOF *sx, void *output_v, size_t size) +{ + unsigned char *output = (unsigned char *)output_v; + + while (size > 0) { + if (sx->pos == 0) { + /* Copy the 64-bit words from the Keccak state into the + * output buffer of bytes */ + for (unsigned y = 0; y < 5; y++) + for (unsigned x = 0; x < 5; x++) + PUT_64BIT_LSB_FIRST(sx->buf + 8 * (5*y+x), + sx->state.A[x][y]); + } + + /* Read a chunk from the byte buffer */ + size_t this_size = sx->bytes_per_transform - sx->pos; + if (this_size > size) + this_size = size; + memcpy(output, sx->buf + sx->pos, this_size); + sx->pos += this_size; + output += this_size; + size -= this_size; + + /* Retransform the Keccak state if we've run out of data */ + if (sx->pos >= sx->bytes_per_transform) { + keccak_transform(sx->state.A); + sx->pos = 0; + } + } +} + +void shake_xof_free(ShakeXOF *sx) +{ + smemclr(sx->buf, 200 * 64); + smemclr(sx, sizeof(*sx)); + sfree(sx); +} diff --git a/crypto/smallmoduli.h b/crypto/smallmoduli.h new file mode 100644 index 00000000..b452b410 --- /dev/null +++ b/crypto/smallmoduli.h @@ -0,0 +1,54 @@ +/* + * Shared code between algorithms whose state consists of a large + * collection of residues mod a small prime. + */ + +/* + * We 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. Input values might not fit + * in a 16-bit int, because we'll also be multiplying mod q. + * + * 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 inline 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 inline 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 inline 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; +} diff --git a/defs.h b/defs.h index d8bfe02a..0e56eb78 100644 --- a/defs.h +++ b/defs.h @@ -53,6 +53,11 @@ uintmax_t strtoumax(const char *nptr, char **endptr, int base); #define SIZEu "zu" #endif +#if !HAVE_WMEMCHR +/* Work around lack of wmemchr in older MSVC */ +wchar_t *wmemchr(const wchar_t *s, wchar_t c, size_t n); +#endif + #if defined __GNUC__ || defined __clang__ /* * On MinGW, the correct compiler format checking for vsnprintf() etc @@ -187,10 +192,13 @@ 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 pq_kemalg pq_kemalg; +typedef struct pq_kem_dk pq_kem_dk; typedef struct NTRUKeyPair NTRUKeyPair; typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; typedef struct RFC6979 RFC6979; typedef struct RFC6979Result RFC6979Result; +typedef struct ShakeXOF ShakeXOF; typedef struct dlgparam dlgparam; typedef struct dlgcontrol dlgcontrol; diff --git a/dialog.c b/dialog.c index 511331d3..0ad34d25 100644 --- a/dialog.c +++ b/dialog.c @@ -392,7 +392,7 @@ dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, } dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, FILESELECT_FILTER_TYPE filter, + char shortcut, FilereqFilter filter, bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context) { diff --git a/dialog.h b/dialog.h index 19e0695d..4e8e2ae1 100644 --- a/dialog.h +++ b/dialog.h @@ -348,21 +348,8 @@ struct dlgcontrol { * 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... */ - FILESELECT_FILTER_TYPE filter; + FilereqFilter filter; /* * Some systems like to know whether a file selector is * choosing a file to read or one to write (and possibly @@ -552,7 +539,7 @@ 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, FILESELECT_FILTER_TYPE filter, + char shortcut, FilereqFilter filter, bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index ef77eb12..35673996 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.7...3.28) project(putty-documentation LANGUAGES) # This build script can be run standalone, or included as a diff --git a/doc/config.but b/doc/config.but index a28e866c..fb836ec2 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2385,6 +2385,12 @@ 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{ML-KEM / Curve25519 hybrid} and \q{ML-KEM NIST ECDH hybrid}: +similar hybrid constructs of \i{ML-KEM}, another lattice-based key +exchange method intended to be \i{quantum-resistant}. In the former, +ML-KEM is hybridised with Curve25519; in the latter, with NIST P384 +or P256. + \b \q{\i{ECDH}}: elliptic curve Diffie-Hellman key exchange, with a variety of standard curves and hash algorithms. diff --git a/doc/index.but b/doc/index.but index 35c479ff..94e2a477 100644 --- a/doc/index.but +++ b/doc/index.but @@ -699,7 +699,8 @@ saved sessions from \IM{Streamlined NTRU Prime} Streamlined NTRU Prime \IM{Streamlined NTRU Prime} NTRU Prime -\IM{quantum attacks} quantum attacks, resistance to +\IM{quantum attacks}{quantum-resistant} quantum attacks, resistance to +\IM{quantum attacks}{quantum-resistant} post-quantum algorithm \IM{repeat key exchange} repeat key exchange \IM{repeat key exchange} key exchange, repeat diff --git a/doc/plink.but b/doc/plink.but index 8d2849b2..a985218f 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -34,14 +34,14 @@ to include a \c{set} command like the one above. This section describes the basics of how to use Plink for interactive logins and for automated processes. -Once you've got a console window to type into, you can just type -\c{plink} on its own to bring up a usage message. This tells you the +Once you've got a console window to type into, you can type +\c{plink --help} to bring up a usage message. This tells you the version of Plink you're using, and gives you a brief summary of how to use Plink: -\c C:\>plink +\c C:\>plink --help \c Plink: command-line connection utility -\c Release 0.82 +\c Release 0.83 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: @@ -243,6 +243,10 @@ This may help Plink's behaviour when it is used in automated scripts: using \c{-batch}, if something goes wrong at connection time, the batch job will fail rather than hang. +If another program is invoking Plink on your behalf, then you might +need to arrange that that program passes \c{-batch} to Plink. See +\k{plink-git} for an example involving Git. + \S2{plink-option-s} \I{-s-plink}\c{-s}: remote command is SSH subsystem If you specify the \c{-s} option, Plink passes the specified command @@ -395,6 +399,38 @@ particular web area: Any non-interactive command you could usefully run on the server command line, you can run in a batch file using Plink in this way. +\H{plink-git} Using Plink with \i{Git} + +To use Plink for Git operations performed over SSH, you can set the +environment variable \i\c{GIT_SSH_COMMAND} to point to Plink. + +For example, if you've run PuTTY's full Windows installer and it has +installed Plink in the default location, you might do this: + +\c set GIT_SSH_COMMAND="C:\Program Files\PuTTY\plink.exe" + +or if you've put Plink somewhere else then you can do a similar thing +with a different path. + +This environment variable accepts a whole command line, not just an +executable file name. So you can add Plink options to the end of it if +you like. For example, if you're using Git in a batch-mode context, +where your Git jobs are running unattended and nobody is available to +answer interactive prompts, you might also append the \cq{-batch} +option (\k{plink-option-batch}): + +\c set GIT_SSH_COMMAND="C:\Program Files\PuTTY\plink.exe" -batch + +and then if Plink unexpectedly prints a prompt of some kind (for +example, because the SSH server's host key has changed), your batch +job will terminate with an error message, instead of stopping and +waiting for user input that will never arrive. + +(However, you don't \e{always} want to do this with Git. If you're +using Git interactively, you might \e{want} Plink to stop for +interactive prompts \dash for example, to let you enter a password for +the SSH server.) + \H{plink-cvs} Using Plink with \i{CVS} To use Plink with CVS, you need to set the environment variable diff --git a/doc/pscp.but b/doc/pscp.but index dbc04c85..dd959121 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -32,14 +32,14 @@ to include a \c{set} command like the one above. \H{pscp-usage} PSCP Usage -Once you've got a console window to type into, you can just type -\c{pscp} on its own to bring up a usage message. This tells you the +Once you've got a console window to type into, you can type +\c{pscp -h} to bring up a usage message. This tells you the version of PSCP you're using, and gives you a brief summary of how to use PSCP: -\c C:\>pscp +\c C:\>pscp -h \c PuTTY Secure Copy client -\c Release 0.82 +\c Release 0.83 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/ldisc.c b/ldisc.c index 899f5c82..eb7222c0 100644 --- a/ldisc.c +++ b/ldisc.c @@ -272,8 +272,10 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) */ len = strlen(vbuf); type = DEDICATED; - } else { + } else if (len > 0) { type = interactive ? NORMAL : NONINTERACTIVE; + } else { + return; /* nothing to do anyway */ } /* diff --git a/misc.h b/misc.h index 7d94e7e6..c64eb01b 100644 --- a/misc.h +++ b/misc.h @@ -29,6 +29,8 @@ char *dupstr(const char *s); wchar_t *dupwcs(const wchar_t *s); char *dupcat_fn(const char *s1, ...); #define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL) +wchar_t *dupwcscat_fn(const wchar_t *s1, ...); +#define dupwcscat(...) dupwcscat_fn(__VA_ARGS__, (const wchar_t *)NULL) char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2); char *dupvprintf(const char *fmt, va_list ap); void burnstr(char *string); diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 6f574c9f..f9680f30 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -339,7 +339,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) that line. If the cursor is at the bottom line, scroll up. */ - put_fmt(outbuf, "\015\012"); + put_fmt(outbuf, "\015\012\033[K"); break; case TDNOP: diff --git a/pageant.c b/pageant.c index e61ffc35..6f68cb45 100644 --- a/pageant.c +++ b/pageant.c @@ -648,6 +648,7 @@ static void signop_unlink(PageantSignOp *so) static void signop_free(PageantAsyncOp *pao) { PageantSignOp *so = container_of(pao, PageantSignOp, pao); + signop_unlink(so); strbuf_free(so->data_to_sign); sfree(so); } diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 40a2f609..4203eb5d 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -22,6 +22,7 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) { strbuf *sb = strbuf_new_nm(); const ssh2_macalg *alg = &ssh_hmac_md5; + enable_dit(); /* just in case main() forgot */ mac_simple(alg, password, challenge, strbuf_append(sb, alg->len)); return sb; } @@ -75,6 +76,8 @@ void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, const ssh_hashalg *alg = httphashalgs[hash]; size_t hashlen = httphashlengths[hash]; + enable_dit(); /* just in case main() forgot */ + unsigned char ncbuf[4]; PUT_32BIT_MSB_FIRST(ncbuf, nonce_count); diff --git a/pscp.c b/pscp.c index 0f4b2d6c..fdfa367e 100644 --- a/pscp.c +++ b/pscp.c @@ -2235,7 +2235,6 @@ static void usage(void) printf(" -logoverwrite\n"); printf(" -logappend\n"); printf(" 记录文件已存在时覆盖文件还是在文件末尾添加内容\n"); - cleanup_exit(1); } void version(void) @@ -2274,6 +2273,7 @@ int psftp_main(CmdlineArgList *arglist) bool sanitise_stderr = true; sk_init(); + enable_dit(); /* Load Default Settings before doing anything else. */ conf = conf_new(); diff --git a/psftp.c b/psftp.c index b3d526c8..cbe7e685 100644 --- a/psftp.c +++ b/psftp.c @@ -2393,7 +2393,7 @@ static void do_sftp_cleanup(void) } } -int do_sftp(int mode, int modeflags, const char *batchfile) +int do_sftp(int mode, int modeflags, Filename *batchfile) { FILE *fp; int ret; @@ -2417,9 +2417,9 @@ int do_sftp(int mode, int modeflags, const char *batchfile) break; } } else { - fp = fopen(batchfile, "r"); + fp = f_open(batchfile, "r", false); if (!fp) { - printf("Fatal: unable to open %s\n", batchfile); + printf("Fatal: unable to open %s\n", filename_to_str(batchfile)); return 1; } ret = 0; @@ -2564,7 +2564,6 @@ static void usage(void) printf(" -logoverwrite\n"); printf(" -logappend\n"); printf(" 记录文件已存在时覆盖文件还是在文件末尾添加内容\n"); - cleanup_exit(1); } static void version(void) @@ -2800,9 +2799,10 @@ int psftp_main(CmdlineArgList *arglist) int mode = 0; int modeflags = 0; bool sanitise_stderr = true; - const char *batchfile = NULL; + Filename *batchfile = NULL; sk_init(); + enable_dit(); userhost = user = NULL; @@ -2845,7 +2845,7 @@ int psftp_main(CmdlineArgList *arglist) version(); } else if (strcmp(argstr, "-b") == 0 && nextarg) { mode = 1; - batchfile = cmdline_arg_to_str(nextarg); + batchfile = cmdline_arg_to_filename(nextarg); arglistpos++; } else if (strcmp(argstr, "-bc") == 0) { modeflags = modeflags | 1; diff --git a/putty.h b/putty.h index 9aadf04b..18c74804 100644 --- a/putty.h +++ b/putty.h @@ -4,6 +4,21 @@ #include /* for wchar_t */ #include /* for INT_MAX */ +/* + * Declared before including platform.h, because that will refer to it + * + * An enum for different types of file that a GUI file requester might + * focus on. (Our requesters never _insist_ on a particular file type + * or extension - there's always an escape hatch to select any file + * you want - but the default can be configured.) + */ +typedef enum { + FILTER_ALL_FILES, /* no particular focus */ + FILTER_KEY_FILES, /* .ppk */ + FILTER_DYNLIB_FILES, /* whatever the host platform uses as shared libs */ + FILTER_SOUND_FILES, /* whatever kind of sound file we can use as bell */ +} FilereqFilter; + #include "defs.h" #include "platform.h" #include "network.h" @@ -388,6 +403,8 @@ enum { KEX_RSA, KEX_ECDH, KEX_NTRU_HYBRID, + KEX_MLKEM_25519_HYBRID, + KEX_MLKEM_NIST_HYBRID, KEX_MAX }; diff --git a/readme.md b/readme.md index d03b264a..f072fc2d 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ PuTTY 是自由的跨平台 Telnet/SSH 客户端,同时在 Win32 和 Unix 系统下模拟 xterm 终端。其主要作者是 Simon Tatham。 -当前版本为 0.82 测试版,请访问 [PuTTY 网站](http://www.chiark.greenend.org.uk/~sgtatham/putty/)获得更多信息。如果发现英文版本有更新,[请及时通知](https://github.com/larryli/PuTTY/issues/new)。 +当前版本为 0.83 测试版,请访问 [PuTTY 网站](http://www.chiark.greenend.org.uk/~sgtatham/putty/)获得更多信息。如果发现英文版本有更新,[请及时通知](https://github.com/larryli/PuTTY/issues/new)。 ## MIT 许可证 diff --git a/release.pl b/release.pl index f9507a13..b924d11d 100755 --- a/release.pl +++ b/release.pl @@ -43,13 +43,13 @@ &transform("LATEST.VER", sub { s/^\d+\.\d+$/$version/ }); our $transforming = 0; &transform("doc/pscp.but", sub { - if (/^\\c.*>pscp$/) { $transforming = 1; $_ .= $pscp_transcript; } + if (/^\\c.*>pscp -h$/) { $transforming = 1; $_ .= $pscp_transcript; } elsif (!/^\\c/) { $transforming = 0; } elsif ($transforming) { $_=""; } }); $transforming = 0; &transform("doc/plink.but", sub { - if (/^\\c.*>plink$/) { $transforming = 1; $_ .= $plink_transcript; } + if (/^\\c.*>plink --help$/) { $transforming = 1; $_ .= $plink_transcript; } elsif (!/^\\c/) { $transforming = 0; } elsif ($transforming) { $_=""; } }); diff --git a/settings.c b/settings.c index 590320a6..874ac4fa 100644 --- a/settings.c +++ b/settings.c @@ -30,6 +30,8 @@ static const struct keyvalwhere ciphernames[] = { * in sync with those. */ static const struct keyvalwhere kexnames[] = { { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 }, + { "mlkem-curve25519", KEX_MLKEM_25519_HYBRID, KEX_NTRU_HYBRID, +1 }, + { "mlkem-nist", KEX_MLKEM_NIST_HYBRID, KEX_MLKEM_25519_HYBRID, +1 }, { "ecdh", KEX_ECDH, -1, +1 }, /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, diff --git a/ssh.h b/ssh.h index 46356f0c..0d31a4bc 100644 --- a/ssh.h +++ b/ssh.h @@ -136,6 +136,7 @@ typedef enum { SSH2_PKTCTX_DHGROUP, SSH2_PKTCTX_DHGEX, SSH2_PKTCTX_ECDHKEX, + SSH2_PKTCTX_HYBRIDKEX, SSH2_PKTCTX_GSSKEX, SSH2_PKTCTX_RSAKEX } Pkt_KCtx; @@ -992,6 +993,12 @@ struct ecdh_keyalg { void (*getpublic)(ecdh_key *key, BinarySink *bs); bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs); char *(*description)(const ssh_kex *kex); + + /* Some things that use this vtable are genuinely elliptic-curve + * Diffie-Hellman. Others are hybrid PQ + classical kex methods. + * Provide a packet-naming context for use in the SSH log. (Purely + * cosmetic.) */ + Pkt_KCtx packet_naming_ctx; }; static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server) { return kex->ecdh_vt->new(kex, is_server); } @@ -1005,6 +1012,52 @@ static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, static inline char *ecdh_keyalg_description(const ssh_kex *kex) { return kex->ecdh_vt->description(kex); } +/* + * vtable for post-quantum key encapsulation methods (things like NTRU + * and ML-KEM). + * + * These work in an asymmetric way that's conceptually more like the + * old RSA kex (either SSH-1 or SSH-2) than like Diffie-Hellman. One + * party generates a keypair and sends the public key; the other party + * invents a secret and encrypts it with the public key; the first + * party receives the ciphertext and decrypts it, and now both parties + * have the secret. + */ +struct pq_kem_dk { + const pq_kemalg *vt; +}; +struct pq_kemalg { + /* Generate a key pair, writing the public encryption key in wire + * format to ek. Return the decryption key. */ + pq_kem_dk *(*keygen)(const pq_kemalg *alg, BinarySink *ek); + /* Invent and encrypt a secret, writing the ciphertext in wire + * format to c and the secret itself to k. Returns false on any + * kind of really obvious validation failure of the encryption key. */ + bool (*encaps)(const pq_kemalg *alg, BinarySink *c, BinarySink *k, + ptrlen ek); + /* Decrypt the secret and write it to k. Returns false on + * validation failure. However, more competent cryptographic + * attacks are rejected in a way that's not obvious, returning a + * useless pseudorandom secret. */ + bool (*decaps)(pq_kem_dk *dk, BinarySink *k, ptrlen c); + /* Free a decryption key. */ + void (*free_dk)(pq_kem_dk *dk); + + const void *extra; + const char *description; + size_t ek_len, c_len; +}; + +static inline pq_kem_dk *pq_kem_keygen(const pq_kemalg *alg, BinarySink *ek) +{ return alg->keygen(alg, ek); } +static inline bool pq_kem_encaps(const pq_kemalg *alg, BinarySink *c, + BinarySink *k, ptrlen ek) +{ return alg->encaps(alg, c, k, ek); } +static inline bool pq_kem_decaps(pq_kem_dk *dk, BinarySink *k, ptrlen c) +{ return dk->vt->decaps(dk, k, c); } +static inline void pq_kem_free_dk(pq_kem_dk *dk) +{ dk->vt->free_dk(dk); } + /* * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 * as the mechanism. @@ -1167,6 +1220,7 @@ extern const ssh_hashalg ssh_sha3_224; extern const ssh_hashalg ssh_sha3_256; extern const ssh_hashalg ssh_sha3_384; extern const ssh_hashalg ssh_sha3_512; +extern const ssh_hashalg ssh_shake256_32bytes; extern const ssh_hashalg ssh_shake256_114bytes; extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; @@ -1194,6 +1248,12 @@ extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; extern const ssh_kexes ssh_ntru_hybrid_kex; +extern const pq_kemalg ssh_ntru; +extern const ssh_kexes ssh_mlkem_curve25519_hybrid_kex; +extern const ssh_kexes ssh_mlkem_nist_hybrid_kex; +extern const pq_kemalg ssh_mlkem512; +extern const pq_kemalg ssh_mlkem768; +extern const pq_kemalg ssh_mlkem1024; extern const ssh_keyalg ssh_dsa; extern const ssh_keyalg ssh_rsa; extern const ssh_keyalg ssh_rsa_sha256; @@ -1234,6 +1294,13 @@ ssh_hash *blake2b_new_general(unsigned hashlen); /* Special test function for AES-GCM */ void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad); +/* Shake128/256 extendable output functions (like a hash except you don't + * commit up front to how much data you want to get out of it) */ +ShakeXOF *shake128_xof_from_input(ptrlen data); +ShakeXOF *shake256_xof_from_input(ptrlen data); +void shake_xof_read(ShakeXOF *sx, void *output_v, size_t size); +void shake_xof_free(ShakeXOF *sx); + /* * On some systems, you have to detect hardware crypto acceleration by * asking the local OS API rather than OS-agnostically asking the CPU @@ -1245,6 +1312,7 @@ bool platform_pmull_neon_available(void); bool platform_sha256_neon_available(void); bool platform_sha1_neon_available(void); bool platform_sha512_neon_available(void); +bool platform_dit_available(void); /* * PuTTY version number formatted as an SSH version string. @@ -1728,6 +1796,8 @@ void platform_ssh_share_cleanup(const char *name); K(y, SSH2_MSG_KEXRSA_DONE, 32, SSH2_PKTCTX_RSAKEX) \ K(y, SSH2_MSG_KEX_ECDH_INIT, 30, SSH2_PKTCTX_ECDHKEX) \ K(y, SSH2_MSG_KEX_ECDH_REPLY, 31, SSH2_PKTCTX_ECDHKEX) \ + K(y, SSH2_MSG_KEX_HYBRID_INIT, 30, SSH2_PKTCTX_HYBRIDKEX) \ + K(y, SSH2_MSG_KEX_HYBRID_REPLY, 31, SSH2_PKTCTX_HYBRIDKEX) \ X(y, SSH2_MSG_USERAUTH_REQUEST, 50) \ X(y, SSH2_MSG_USERAUTH_FAILURE, 51) \ X(y, SSH2_MSG_USERAUTH_SUCCESS, 52) \ @@ -1979,3 +2049,13 @@ enum { PLUGIN_NOTYPE = 256, /* packet too short to have a type */ PLUGIN_EOF = 257 /* EOF from auth plugin */ }; + +/* + * CPU features for security + */ + +#if HAVE_ARM_DIT +void enable_dit(void); +#else +#define enable_dit() ((void)0) +#endif diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 264bb49c..90bd6f3f 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -453,9 +453,10 @@ void setup_ca_config_box(struct controlbox *b) P(st), P(NULL)); c->column = 0; st->ca_pubkey_edit = c; - c = ctrl_filesel(s, "从文件中读取", NO_SHORTCUT, NULL, false, - "选择证书颁发机构的公钥文件", - HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); + c = ctrl_filesel( + s, "从文件中读取", NO_SHORTCUT, FILTER_ALL_FILES, false, + "选择证书颁发机构的公钥文件", + HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); c->fileselect.just_button = true; c->align_next_to = st->ca_pubkey_edit; c->column = 1; diff --git a/ssh/gss.h b/ssh/gss.h index c819d48b..d11a359f 100644 --- a/ssh/gss.h +++ b/ssh/gss.h @@ -3,6 +3,13 @@ #include "putty.h" #include "pgssapi.h" +/* This struct is defined even in NO_GSSAPI mode, so that stubs/no-gss.c can + * return an instance of it containing no libraries */ +struct ssh_gss_liblist { + struct ssh_gss_library *libraries; + int nlibraries; +}; + #ifndef NO_GSSAPI #define SSH2_GSS_OIDTYPE 0x06 @@ -49,10 +56,6 @@ struct ssh_gss_library; * The free function cleans up the structure, and its associated * libraries (if any). */ -struct ssh_gss_liblist { - struct ssh_gss_library *libraries; - int nlibraries; -}; struct ssh_gss_liblist *ssh_gss_setup(Conf *conf); void ssh_gss_cleanup(struct ssh_gss_liblist *list); diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index d5425237..108cf549 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -190,7 +190,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh_hash_alg(s->exhash)->text_name); sfree(desc); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + s->ppl.bpp->pls->kctx = s->kex_alg->ecdh_vt->packet_naming_ctx; s->ecdh_key = ecdh_key_new(s->kex_alg, false); diff --git a/ssh/ssh.c b/ssh/ssh.c index 8f9bddd7..3cece849 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -954,6 +954,8 @@ static char *ssh_init(const BackendVtable *vt, Seat *seat, { Ssh *ssh; + enable_dit(); /* just in case main() forgot */ + ssh = snew(Ssh); memset(ssh, 0, sizeof(Ssh)); diff --git a/ssh/transport2.c b/ssh/transport2.c index 1d30ebd8..7db060be 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -615,6 +615,14 @@ static void ssh2_write_kexinit_lists( preferred_kex[n_preferred_kex++] = &ssh_ntru_hybrid_kex; break; + case KEX_MLKEM_25519_HYBRID: + preferred_kex[n_preferred_kex++] = + &ssh_mlkem_curve25519_hybrid_kex; + break; + case KEX_MLKEM_NIST_HYBRID: + preferred_kex[n_preferred_kex++] = + &ssh_mlkem_nist_hybrid_kex; + break; case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -1166,7 +1174,7 @@ static ScanKexinitsResult ssh2_scan_kexinits( * Otherwise, any match failure _is_ a fatal error. */ ScanKexinitsResult skr = { - .success = false, .error = SKR_UNKNOWN_ID, + .success = false, .error = SKR_NO_AGREEMENT, .kind = kexlist_descr[i], .desc = slists[i], }; return skr; diff --git a/sshrand.c b/sshrand.c index 7f3d6411..18720adc 100644 --- a/sshrand.c +++ b/sshrand.c @@ -93,8 +93,10 @@ void random_save_seed(void) void random_ref(void) { - if (!random_active++) + if (!random_active++) { + enable_dit(); /* just in case main() forgot */ random_create(&ssh_sha256); + } } void random_setup_custom(const ssh_hashalg *hash) diff --git a/stubs/no-dit.c b/stubs/no-dit.c new file mode 100644 index 00000000..8ba3b23d --- /dev/null +++ b/stubs/no-dit.c @@ -0,0 +1,15 @@ +/* + * Stub version of enable_dit(), included in applications like + * PuTTYtel and pterm which completely leave out the 'crypto' source + * directory. + */ + +#include "ssh.h" + +#if HAVE_ARM_DIT + +void enable_dit(void) +{ +} + +#endif diff --git a/terminal/lineedit.c b/terminal/lineedit.c index eaaa3a18..807c24db 100644 --- a/terminal/lineedit.c +++ b/terminal/lineedit.c @@ -157,8 +157,19 @@ static void lineedit_delete_line(TermLineEditor *le) void lineedit_send_line(TermLineEditor *le) { + bufchain output; + bufchain_init(&output); + for (BufChar *bc = le->head; bc; bc = bc->next) - lineedit_send_data(le, make_ptrlen(bc->wire, bc->nwire)); + bufchain_add(&output, bc->wire, bc->nwire); + + while (bufchain_size(&output) > 0) { + ptrlen data = bufchain_prefix(&output); + lineedit_send_data(le, data); + bufchain_consume(&output, data.len); + } + bufchain_clear(&output); + lineedit_free_buffer(le); le->quote_next_char = false; } @@ -443,6 +454,24 @@ void lineedit_input(TermLineEditor *le, char ch, bool dedicated) lineedit_complete_line(le); return; } + } else { + /* If we're not in LE_CRLF_NEWLINE mode, then ^J by + * itself acts as a full newline character */ + lineedit_complete_line(le); + return; + } + + + case CTRL('M'): + if (le->flags & LE_CRLF_NEWLINE) { + /* In this mode, ^M is literal, and can combine with + * ^J (see case above). So do nothing, and fall + * through into the 'treat it literally' code, */ + } else { + /* If we're not in LE_CRLF_NEWLINE mode, then ^M by + * itself acts as a full newline character */ + lineedit_complete_line(le); + return; } } } diff --git a/terminal/terminal.c b/terminal/terminal.c index e127ff6e..ad309436 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -2374,6 +2374,8 @@ void term_resize_request_completed(Terminal *term) void term_provide_backend(Terminal *term, Backend *backend) { term->backend = backend; + if (term->userpass_state) + term_userpass_state_free(term->userpass_state); if (term->backend && term->cols > 0 && term->rows > 0) backend_size(term->backend, term->cols, term->rows); } @@ -4125,9 +4127,11 @@ static void term_out(Terminal *term, bool called_from_term_data) term->esc_args[0] = 0; term->esc_nargs = 1; break; + case 'X': /* SOS: Start of String */ + case '^': /* PM: privacy message */ case '_': /* APC: application program command */ - /* APC sequences are just a string, terminated by - * ST or (I've observed in practice) ^G. That is, + /* SOS, PM, and APC sequences are just a string, terminated by + * ST or (I've observed in practice for APC) ^G. That is, * they have the same termination convention as * OSC. So we handle them by going straight into * OSC_STRING state and setting a flag indicating diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 5cdba58e..0add9bec 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -3447,6 +3447,71 @@ def test(gcm, cbc, iv_fixed, iv_msg): # at the top test(gcm, cbc, 0x27182818, 0xFFFFFFFFFFFFFFFF) + def testMLKEMValidation(self): + # Test validation of hostile inputs (wrong length, + # out-of-range mod q values, mismatching hashes). + for params in 'mlkem512', 'mlkem768', 'mlkem1024': + with self.subTest(params=params): + ek, dk = mlkem_keygen_internal( + params, + b'arbitrary 32-byte test d string!', + b'and another for z, wibbly-wobbly') + + m = b'I suppose we need m as well, ooh' + + # Baseline test: without anything changed, encaps succeeds. + success, c, k = mlkem_encaps_internal(params, ek, m) + self.assertTrue(success) + + # We must check ek has the right length + success, _, _ = mlkem_encaps_internal(params, ek[:-1], m) + self.assertFalse(success) + success, _, _ = mlkem_encaps_internal(params, ek + b'!', m) + self.assertFalse(success) + + # Must reject if a polynomial coefficient is replaced + # with something out of range. Even if it's _only + # just_ out of range, the modulus 3329 itself. So + # replace the first coefficient (first 12 bits) with + # 3329. + ek_bytes = list(ek) + ek_bytes[0] = 3329 & 0xFF + ek_bytes[1] = (ek_bytes[1] & 0xF0) | (3329 >> 8) + success, _, _ = mlkem_encaps_internal( + params, bytes(ek_bytes), m) + self.assertFalse(success) + + # Now do the same with the last polynomial + # coefficient, which occurs 32 bytes before the end of + # ek. (The last 32 bytes are the matrix seed, which + # can be anything.) + ek_bytes = list(ek) + ek_bytes[-33] = 3329 >> 4 + ek_bytes[-34] = (ek_bytes[-34] & 0x0F) | ((3329 << 4) & 0xF0) + success, _, _ = mlkem_encaps_internal( + params, bytes(ek_bytes), m) + self.assertFalse(success) + + # Baseline test of decaps. + self.assertEqual(mlkem_decaps(params, dk, c), (True, k)) + + fail = (False, b'') # expected return value on validation fail + # Modify the length of dk or c, and make sure decaps fails + self.assertEqual(mlkem_decaps(params, dk[:-1], c), fail) + self.assertEqual(mlkem_decaps(params, dk + b'?', c), fail) + self.assertEqual(mlkem_decaps(params, dk, c[:-1]), fail) + self.assertEqual(mlkem_decaps(params, dk, c + b'*'), fail) + + # Tinker with the enclosed copy of ek, and ensure + # that's detected. + eklen = len(ek) + ekstart = len(dk) - 64 - eklen + self.assertEqualBin(dk[ekstart:ekstart+eklen], ek) + dk_bytes = list(dk) + dk_bytes[ekstart] ^= 1 + self.assertEqual( + mlkem_decaps(params, bytes(dk_bytes), c), fail) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): @@ -3853,6 +3918,22 @@ def testSHA3(self): self.assertEqualBin(hash_str('shake256_114bytes', ''), unhex("46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762fd75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be141e96616fb13957692cc7edd0b45ae3dc07223c8e92937bef84bc0eab862853349ec75546f58fb7c2775c38462c5010d846")) self.assertEqualBin(hash_str('shake256_114bytes', unhex('a3')*200), unhex("cd8a920ed141aa0407a22d59288652e9d9f1a7ee0c1e7c1ca699424da84a904d2d700caae7396ece96604440577da4f3aa22aeb8857f961c4cd8e06f0ae6610b1048a7f64e1074cd629e85ad7566048efc4fb500b486a3309a8f26724c0ed628001a1099422468de726f1061d99eb9e93604")) + def testSHA3XOF(self): + # Cherry-picked examples from CAVS 19.0, testing both SHAKE128 + # and SHAKE256, each with a long input and a long output. + + xof = shake128_xof_from_input(unhex('a6fe00064257aa318b621c5eb311d32bb8004c2fa1a969d205d71762cc5d2e633907992629d1b69d9557ff6d5e8deb454ab00f6e497c89a4fea09e257a6fa2074bd818ceb5981b3e3faefd6e720f2d1edd9c5e4a5c51e5009abf636ed5bca53fe159c8287014a1bd904f5c8a7501625f79ac81eb618f478ce21cae6664acffb30572f059e1ad0fc2912264e8f1ca52af26c8bf78e09d75f3dd9fc734afa8770abe0bd78c90cc2ff448105fb16dd2c5b7edd8611a62e537db9331f5023e16d6ec150cc6e706d7c7fcbfff930c7281831fd5c4aff86ece57ed0db882f59a5fe403105d0592ca38a081fed84922873f538ee774f13b8cc09bd0521db4374aec69f4bae6dcb66455822c0b84c91a3474ffac2ad06f0a4423cd2c6a49d4f0d6242d6a1890937b5d9835a5f0ea5b1d01884d22a6c1718e1f60b3ab5e232947c76ef70b344171083c688093b5f1475377e3069863')) + self.assertEqualBin(shake_xof_read(xof, 128//8), unhex("3109d9472ca436e805c6b3db2251a9bc")) + + xof = shake128_xof_from_input(unhex('0a13ad2c7a239b4ba73ea6592ae84ea9')) + self.assertEqualBin(shake_xof_read(xof, 1120//8), unhex("5feaf99c15f48851943ff9baa6e5055d8377f0dd347aa4dbece51ad3a6d9ce0c01aee9fe2260b80a4673a909b532adcdd1e421c32d6460535b5fe392a58d2634979a5a104d6c470aa3306c400b061db91c463b2848297bca2bc26d1864ba49d7ff949ebca50fbf79a5e63716dc82b600bd52ca7437ed774d169f6bf02e46487956fba2230f34cd2a0485484d")) + + xof = shake256_xof_from_input(unhex('dc5a100fa16df1583c79722a0d72833d3bf22c109b8889dbd35213c6bfce205813edae3242695cfd9f59b9a1c203c1b72ef1a5423147cb990b5316a85266675894e2644c3f9578cebe451a09e58c53788fe77a9e850943f8a275f830354b0593a762bac55e984db3e0661eca3cb83f67a6fb348e6177f7dee2df40c4322602f094953905681be3954fe44c4c902c8f6bba565a788b38f13411ba76ce0f9f6756a2a2687424c5435a51e62df7a8934b6e141f74c6ccf539e3782d22b5955d3baf1ab2cf7b5c3f74ec2f9447344e937957fd7f0bdfec56d5d25f61cde18c0986e244ecf780d6307e313117256948d4230ebb9ea62bb302cfe80d7dfebabc4a51d7687967ed5b416a139e974c005fff507a96')) + self.assertEqualBin(shake_xof_read(xof, 256//8), unhex("2bac5716803a9cda8f9e84365ab0a681327b5ba34fdedfb1c12e6e807f45284b")) + + xof = shake256_xof_from_input(unhex('8d8001e2c096f1b88e7c9224a086efd4797fbf74a8033a2d422a2b6b8f6747e4')) + self.assertEqualBin(shake_xof_read(xof, 2000//8), unhex("2e975f6a8a14f0704d51b13667d8195c219f71e6345696c49fa4b9d08e9225d3d39393425152c97e71dd24601c11abcfa0f12f53c680bd3ae757b8134a9c10d429615869217fdd5885c4db174985703a6d6de94a667eac3023443a8337ae1bc601b76d7d38ec3c34463105f0d3949d78e562a039e4469548b609395de5a4fd43c46ca9fd6ee29ada5efc07d84d553249450dab4a49c483ded250c9338f85cd937ae66bb436f3b4026e859fda1ca571432f3bfc09e7c03ca4d183b741111ca0483d0edabc03feb23b17ee48e844ba2408d9dcfd0139d2e8c7310125aee801c61ab7900d1efc47c078281766f361c5e6111346235e1dc38325666c")) + def testBLAKE2b(self): # Test case from RFC 7693 appendix A. self.assertEqualBin(hash_str('blake2b', b'abc'), unhex( @@ -4428,6 +4509,153 @@ def test(key, iv, plaintext, aad, ciphertext, mac): 'c5f61e6393ba7a0abcc9f662'), unhex('76fc6ece0f4e1768cddf8853bb2d551b')) + def testMLKEM(self): + # As of 2024-12-04, a set of ML-KEM test vectors live in a git + # repository at https://github.com/usnistgov/ACVP-Server + # + # Within that repository, the two useful files (as of commit + # 3a7333f638a031c6ed35b6ee31064686eb88c1ec) are: + # gen-val/json-files/ML-KEM-keyGen-FIPS203/internalProjection.json + # gen-val/json-files/ML-KEM-encapDecap-FIPS203/internalProjection.json + # + # The first contains tests of key generation (input randomness + # and the expected output key). The second contains tests of + # encapsulation and decapsulation. + # + # The full set of test cases is too large to transcribe into + # here. But you can run them in full by setting the variable + # names below to local pathnames where those two files can be + # found. + keygen_json_path = None + encapdecap_json_path = None + + def keygen_test(params, d, z, ek_expected, dk_expected): + ek_got, dk_got = mlkem_keygen_internal(params, d, z) + self.assertEqualBin(ek_got, ek_expected) + self.assertEqualBin(dk_got, dk_expected) + + def encaps_test(params, ek, m, c_expected, k_expected): + success, c_got, k_got = mlkem_encaps_internal(params, ek, m) + self.assertTrue(success) + self.assertEqualBin(c_got, c_expected) + self.assertEqualBin(k_got, k_expected) + + def decaps_test(params, dk, c, k_expected): + success, k_got = mlkem_decaps(params, dk, c) + self.assertTrue(success) + self.assertEqualBin(k_got, k_expected) + + if keygen_json_path is not None: + with open(keygen_json_path) as fh: + keygen_json_data = json.load(fh) + for testgroup in keygen_json_data['testGroups']: + # Convert "ML-KEM-768" from the JSON to "mlkem768" + params = testgroup['parameterSet'].lower().replace('-', '') + for testcase in testgroup['tests']: + with self.subTest(testgroup=testgroup['tgId'], + testcase=testcase['tcId']): + keygen_test( + params, + unhex(testcase['d']), unhex(testcase['z']), + unhex(testcase['ek']), unhex(testcase['dk'])) + + if encapdecap_json_path is not None: + with open(encapdecap_json_path) as fh: + encapdecap_json_data = json.load(fh) + for testgroup in encapdecap_json_data['testGroups']: + params = testgroup['parameterSet'].lower().replace('-', '') + for testcase in testgroup['tests']: + with self.subTest(testgroup=testgroup['tgId'], + testcase=testcase['tcId']): + ek = unhex(testcase['ek'] if 'ek' in testcase + else testgroup['ek']) + dk = unhex(testcase['dk'] if 'dk' in testcase + else testgroup['dk']) + c = unhex(testcase['c']) + k = unhex(testcase['k']) + if testgroup["function"] == "encapsulation": + # This is a full test that encapsulates a + # key, decapsulates it at the other end, + # and checks both sides end up with the + # same shared secret. + m = unhex(testcase['m']) + encaps_test(params, ek, m, c, k) + + # All tests include decapsulation. The ones + # that don't also include encapsulation might + # provide _bad_ ciphertexts, to test the + # implicit rejection system. + decaps_test(params, dk, c, k) + + # We replicate a small number of those test cases here, for + # ongoing checks that nothing has broken. + # Keygen test group 1, test case 1 + keygen_test('mlkem512', + d=unhex('2CB843A02EF02EE109305F39119FABF49AB90A57FFECB3A0E75E179450F52761'), + z=unhex('84CC9121AE56FBF39E67ADBD83AD2D3E3BB80843645206BDD9F2F629E3CC49B7'), + ek_expected=unhex('A32439F85A3C21D21A71B9B92A9B64EA0AB84312C77023694FD64EAAB907A43539DDB27BA0A853CC9069EAC8508C653E600B2AC018381B4BB4A879ACDAD342F91179CA8249525CB1968BBE52F755B7F5B43D6663D7A3BF0F3357D8A21D15B52DB3818ECE5B402A60C993E7CF436487B8D2AE91E6C5B88275E75824B0007EF3123C0AB51B5CC61B9B22380DE66C5B20B060CBB986F8123D94060049CDF8036873A7BE109444A0A1CD87A48CAE54192484AF844429C1C58C29AC624CD504F1C44F1E1347822B6F221323859A7F6F754BFE710BDA60276240A4FF2A5350703786F5671F449F20C2A95AE7C2903A42CB3B303FF4C427C08B11B4CD31C418C6D18D0861873BFA0332F11271552ED7C035F0E4BC428C43720B39A65166BA9C2D3D770E130360CC2384E83095B1A159495533F116C7B558B650DB04D5A26EAAA08C3EE57DE45A7F88C6A3CEB24DC5397B88C3CEF003319BB0233FD692FDA1524475B351F3C782182DECF590B7723BE400BE14809C44329963FC46959211D6A623339537848C251669941D90B130258ADF55A720A724E8B6A6CAE3C2264B1624CCBE7B456B30C8C7393294CA5180BC837DD2E45DBD59B6E17B24FE93052EB7C43B27AC3DC249CA0CBCA4FB5897C0B744088A8A0779D32233826A01DD6489952A4825E5358A700BE0E179AC197710D83ECC853E52695E9BF87BB1F6CBD05B02D4E679E3B88DD483B0749B11BD37B383DCCA71F9091834A1695502C4B95FC9118C1CFC34C84C2265BBBC563C282666B60AE5C7F3851D25ECBB5021CC38CB73EB6A3411B1C29046CA66540667D136954460C6FCBC4BC7C049BB047FA67A63B3CC1111C1D8AC27E8058BCCA4A15455858A58358F7A61020BC9C4C17F8B95C268CCB404B9AAB4A272A21A70DAF6B6F15121EE01C156A354AA17087E07702EAB38B3241FDB553F657339D5E29DC5D91B7A5A828EE959FEBB90B07229F6E49D23C3A190297042FB43986955B69C28E1016F77A58B431514D21B888899C3608276081B75F568097CDC1748F32307885815F3AEC9651819AA6873D1A4EB83B1953843B93422519483FEF0059D36BB2DB1F3D468FB068C86E8973733C398EAF00E1702C6734AD8EB3B'), + dk_expected=unhex('7FE4206F26BEDB64C1ED0009615245DC98483F663ACC617E65898D596A8836C49FBD3B4A849759AA1546BDA835CAF175642C28280892A7878CC318BCC75B834CB29FDF5360D7F982A52C88AE914DBF02B58BEB8BA887AE8FAB5EB78731C6757805471EBCEC2E38DB1F4B8310D288920D8A492795A390A74BCD55CD8557B4DAABA82C28CB3F152C5231196193A66A8CCF34B80E1F6942C32BCFF96A6E3CF3939B7B942498CC5E4CB8E8468E702759852AA229C0257F02982097338607C0F0F45446FAB4267993B8A5908CAB9C46780134804AE18815B1020527A222EC4B39A3194E661737791714122662D8B9769F6C67DE625C0D483C3D420FF1BB889A727E756281513A70047648D29C0C30F9BE52EC0DEB977CF0F34FC2078483456964743410638C57B5539577BF85669078C356B3462E9FA5807D49591AFA41C1969F65E3405CB64DDF163F26734CE348B9CF4567A33A5969EB326CFB5ADC695DCA0C8B2A7B1F4F404CC7A0981E2CC24C1C23D16AA9B4392415E26C22F4A934D794C1FB4E5A67051123CCD153764DEC99D553529053C3DA550BCEA3AC54136A26A676D2BA8421067068C6381C2A62A727C933702EE5804A31CA865A45588FB74DE7E2223D88C0608A16BFEC4FAD6752DB56B48B8872BF26BA2FFA0CEDE5343BE8143689265E065F41A6925B86C892E62EB0772734F5A357C75CA1AC6DF78AB1B8885AD0819615376D33EBB98F8733A6755803D977BF51C12740424B2B49C28382A6917CBFA034C3F126A38C216C03C35770AD481B9084B5588DA65FF118A74F932C7E537ABE5863FB29A10C09701B441F8399C1F8A637825ACEA3E93180574FDEB88076661AB46951716A500184A040557266598CAF76105E1C1870B43969C3BCC1A04927638017498BB62CAFD3A6B082B7BF7A23450E191799619B925112D072025CA888548C791AA42251504D5D1C1CDDB213303B049E7346E8D83AD587836F35284E109727E66BBCC9521FE0B191630047D158F75640FFEB5456072740021AFD15A45469C583829DAAC8A7DEB05B24F0567E4317B3E3B33389B5C5F8B04B099FB4D103A32439F85A3C21D21A71B9B92A9B64EA0AB84312C77023694FD64EAAB907A43539DDB27BA0A853CC9069EAC8508C653E600B2AC018381B4BB4A879ACDAD342F91179CA8249525CB1968BBE52F755B7F5B43D6663D7A3BF0F3357D8A21D15B52DB3818ECE5B402A60C993E7CF436487B8D2AE91E6C5B88275E75824B0007EF3123C0AB51B5CC61B9B22380DE66C5B20B060CBB986F8123D94060049CDF8036873A7BE109444A0A1CD87A48CAE54192484AF844429C1C58C29AC624CD504F1C44F1E1347822B6F221323859A7F6F754BFE710BDA60276240A4FF2A5350703786F5671F449F20C2A95AE7C2903A42CB3B303FF4C427C08B11B4CD31C418C6D18D0861873BFA0332F11271552ED7C035F0E4BC428C43720B39A65166BA9C2D3D770E130360CC2384E83095B1A159495533F116C7B558B650DB04D5A26EAAA08C3EE57DE45A7F88C6A3CEB24DC5397B88C3CEF003319BB0233FD692FDA1524475B351F3C782182DECF590B7723BE400BE14809C44329963FC46959211D6A623339537848C251669941D90B130258ADF55A720A724E8B6A6CAE3C2264B1624CCBE7B456B30C8C7393294CA5180BC837DD2E45DBD59B6E17B24FE93052EB7C43B27AC3DC249CA0CBCA4FB5897C0B744088A8A0779D32233826A01DD6489952A4825E5358A700BE0E179AC197710D83ECC853E52695E9BF87BB1F6CBD05B02D4E679E3B88DD483B0749B11BD37B383DCCA71F9091834A1695502C4B95FC9118C1CFC34C84C2265BBBC563C282666B60AE5C7F3851D25ECBB5021CC38CB73EB6A3411B1C29046CA66540667D136954460C6FCBC4BC7C049BB047FA67A63B3CC1111C1D8AC27E8058BCCA4A15455858A58358F7A61020BC9C4C17F8B95C268CCB404B9AAB4A272A21A70DAF6B6F15121EE01C156A354AA17087E07702EAB38B3241FDB553F657339D5E29DC5D91B7A5A828EE959FEBB90B07229F6E49D23C3A190297042FB43986955B69C28E1016F77A58B431514D21B888899C3608276081B75F568097CDC1748F32307885815F3AEC9651819AA6873D1A4EB83B1953843B93422519483FEF0059D36BB2DB1F3D468FB068C86E8973733C398EAF00E1702C6734AD8EB3B620130D6C2B8C904A3BB9307BE5103F8D814505FB6A60AF7937EA6CAA117315E84CC9121AE56FBF39E67ADBD83AD2D3E3BB80843645206BDD9F2F629E3CC49B7')) + # Keygen test group 2, test case 26 + keygen_test('mlkem768', + d=unhex('E34A701C4C87582F42264EE422D3C684D97611F2523EFE0C998AF05056D693DC'), + z=unhex('A85768F3486BD32A01BF9A8F21EA938E648EAE4E5448C34C3EB88820B159EEDD'), + ek_expected=unhex('6D14A071F7CC452558D5E71A7B087062ECB1386844588246126402B1FA1637733CD5F60CC84BCB646A7892614D7C51B1C7F1A2799132F13427DC482158DA254470A59E00A4E49686FDC077559367270C2153F11007592C9C4310CF8A12C6A8713BD6BB51F3124F989BA0D54073CC242E0968780B875A869EFB851586B9A868A384B9E6821B201B932C455369A739EC22569C977C212B381871813656AF5B567EF893B584624C863A259000F17B254B98B185097C50EBB68B244342E05D4DE520125B8E1033B1436093ACE7CE8E71B458D525673363045A3B3EEA9455428A398705A42327ADB3774B7057F42B017EC0739A983F19E8214D09195FA24D2D571DB73C19A6F8460E50830D415F627B88E94A7B153791A0C0C7E9484C74D53C714889F0E321B6660A532A5BC0E557FBCA35E29BC611200ED3C633077A4D873C5CC67006B753BF6D6B7AF6CA402AB618236C0AFFBC801F8222FBC36CE0984E2B18C944BBCBEF03B1E1361C1F44B0D734AFB1566CFF8744DA8B9943D6B45A3C09030702CA201FFE20CB7EC5B0D4149EE2C28E8B23374F471B57150D0EC9336261A2D5CB84A3ACACC4289473A4C0ABC617C9ABC178734434C82E1685588A5C2EA2678F6B3C2228733130C466E5B86EF491153E48662247B875D201020B566B81B64D839AB4633BAA8ACE202BAAB4496297F9807ADBBB1E332C6F8022B2A18CFDD4A82530B6D3F007C3353898D966CC2C21CB4244BD00443F209870ACC42BC33068C724EC17223619C1093CCA6AEB29500664D1225036B4B81091906969481F1C723C140B9D6C168F5B64BEA69C5FD6385DF7364B8723BCC85E038C7E464A900D68A2127818994217AEC8BDB39A970A9963DE93688E2AC82ABCC22FB9277BA22009E878381A38163901C7D4C85019538D35CAAE9C41AF8C929EE20BB08CA619E72C2F2262C1C9938572551AC02DC9268FBCC35D79011C3C090AD40A4F111C9BE55C427EB796C1932D8673579AF1B4C638B0944489012A2559A3B02481B01AC30BA8960F80C0C2B3947D36A12C080498BEE448716C973416C8242804A3DA099EE137B0BA90FE4A5C6A89200276A0CFB643EC2C56A2D708D7B4373E44C1502A763A600586E6CDA6273897D44448287DC2E602DC39200BF6166236559FD12A60892AEB153DD651BB469910B4B34669F91DA8654D1EB72EB6E02800B3B0A7D0A48C836854D3A83E65569CB7230BB44F3F143A6DEC5F2C39AB90F274F2088BD3D6A6FCA0070273BEDC84777FB52E3C558B0AE06183D5A48D452F68E15207F861627ACA14279630F82EC3A0CA078633B600AFA79743A600215BE5637458CE2CE8AFF5A08EB5017B2C766577479F8DC6BF9F5CC75089932161B96CEA406620AEDB630407F7687EBBB4814C7981637A48A90DE68031E062A7AF7612B4F5C7A6DA86BD136529E64295A5613EA73BD3D4448CB81F243135C0A660BEB9C17E651DEF469A7D90A15D3481090BCBF227012328941FA46F39C5006AD93D458AA6ADD655862B418C3094F551460DF2153A5810A7DA74F0614C2588BE49DC6F5E88154642BD1D3762563326433507156A57C57694BDD26E7A246FEB723AED67B04887C8E476B48CAB59E5362F26A9EF50C2BC80BA146226216FE62968A60D04E8C170D741C7A2B0E1ABDAC968'), + dk_expected=unhex('98A1B2DA4A65CFB5845EA7311E6A06DB731F1590C41EE74BA10782715B35A3102DF637872BE65BAB37A1DE2511D703C70247B35EF27435485024D93FD9E77C43804F371749BA00B20A8C5C588BC9ABE068AEAAA938517EBFE53B6B663282903DCD189736D7296816C733A1C77C6375E5397C0F189BBFE47643A61F58F8A3C6911BE4611A8C7BC050021163D0A404DC14065748FF29BE60D2B9FDCC8FFD98C587F38C67115786464BDB342B17E897D64617CBFB117973A5458977A7D7617A1B4D83BA03C611138A4673B1EB34B078033F97CFFE80C146A26943F842B976327BF1CBC60119525BB9A3C03493349000DD8F51BA21A2E92361762324600E0C13AAA6CB69BFB24276483F6B02421259B7585263C1A028D682C508BBC2801A56E98B8F620B0483D79B5AD8585AC0A475BAC77865194196338791B7985A05D109395CCA8932722A91950D37E12B891420A52B62CBFA815DF6174CE00E68BCA75D4838CA280F713C7E6924AFD95BAA0D01ADA637B158347034C0AB1A7183331A820ACBCB83193A1A94C8F7E384AED0C35ED3CB3397BB638086E7A35A6408A3A4B90CE953707C19BC46C3B2DA3B2EE32319C56B928032B5ED1256D0753D341423E9DB139DE7714FF075CAF58FD9F57D1A54019B5926406830DAE29A875302A81256F4D6CF5E74034EA614BF70C2764B20C9589CDB5C25761A04E58292907C578A94A35836BEE3112DC2C3AE2192C9DEAA304B29C7FEA1BDF47B3B6BCBA2C0E55C9CDB6DE7149E9CB17917718F12C8032DE1ADE0648D405519C70719BECC701845CF9F4B912FE71983CA34F9018C7CA7BB2F6C5D7F8C5B297359EC75209C2543FF11C4244977C5969524EC454D44C323FCCA94ACAC273A0EC49B4A8A585BCE7A5B305C04C3506422580357016A850C3F7EE17205A77B291C7731C9836C02AEE5406F63C6A07A214382AA15336C05D1045588107645EA7DE6870FC0E55E1540974301C42EC14105518680F688ABE4CE453738FE471B87FC31F5C68A39E68AF51B0240B90E0364B04BAC43D6FB68AB65AE028B62BD683B7D28AD38806BEE725B5B2416A8D79C16EC2A99EA4A8D92A2F5052E67F97352289761C5C39FC5C742E9C0A740CA59FC0182F709D01B5187F00063DAAB397596EEA4A31BDBCBD4C1BB0C55BE7C6850FDA9326B353E288C5013226C3C3923A791609E8002E73A5F7B6BB4A877B1FDF53BB2BAB3DD424D31BBB448E609A66B0E343C286E8760312B6D37AA5201D21F53503D88389ADCA21C70FB6C0FC9C69D6616C9EA3780E35565C0C97C15179C95343ECC5E1C2A24DE4699F6875EA2FA2DD3E357BC43914795207E026B850A2237950C108A512FC88C22488112607088185FB0E09C2C4197A83687266BAB2E583E21C40F4CC008FE652804D8223F1520A90B0D5385C7553CC767C58D120CCD3EF5B5D1A6CD7BC00DFF1321B2F2C432B64EFB8A3F5D0064B3F34293026C851C2DED68B9DFF4A28F6A8D225535E0477084430CFFDA0AC0552F9A212785B749913A06FA2274C0D15BAD325458D323EF6BAE13C0010D525C1D5269973AC29BDA7C983746918BA0E002588E30375D78329E6B8BA8C4462A692FB6083842B8C8C92C60F252726D14A071F7CC452558D5E71A7B087062ECB1386844588246126402B1FA1637733CD5F60CC84BCB646A7892614D7C51B1C7F1A2799132F13427DC482158DA254470A59E00A4E49686FDC077559367270C2153F11007592C9C4310CF8A12C6A8713BD6BB51F3124F989BA0D54073CC242E0968780B875A869EFB851586B9A868A384B9E6821B201B932C455369A739EC22569C977C212B381871813656AF5B567EF893B584624C863A259000F17B254B98B185097C50EBB68B244342E05D4DE520125B8E1033B1436093ACE7CE8E71B458D525673363045A3B3EEA9455428A398705A42327ADB3774B7057F42B017EC0739A983F19E8214D09195FA24D2D571DB73C19A6F8460E50830D415F627B88E94A7B153791A0C0C7E9484C74D53C714889F0E321B6660A532A5BC0E557FBCA35E29BC611200ED3C633077A4D873C5CC67006B753BF6D6B7AF6CA402AB618236C0AFFBC801F8222FBC36CE0984E2B18C944BBCBEF03B1E1361C1F44B0D734AFB1566CFF8744DA8B9943D6B45A3C09030702CA201FFE20CB7EC5B0D4149EE2C28E8B23374F471B57150D0EC9336261A2D5CB84A3ACACC4289473A4C0ABC617C9ABC178734434C82E1685588A5C2EA2678F6B3C2228733130C466E5B86EF491153E48662247B875D201020B566B81B64D839AB4633BAA8ACE202BAAB4496297F9807ADBBB1E332C6F8022B2A18CFDD4A82530B6D3F007C3353898D966CC2C21CB4244BD00443F209870ACC42BC33068C724EC17223619C1093CCA6AEB29500664D1225036B4B81091906969481F1C723C140B9D6C168F5B64BEA69C5FD6385DF7364B8723BCC85E038C7E464A900D68A2127818994217AEC8BDB39A970A9963DE93688E2AC82ABCC22FB9277BA22009E878381A38163901C7D4C85019538D35CAAE9C41AF8C929EE20BB08CA619E72C2F2262C1C9938572551AC02DC9268FBCC35D79011C3C090AD40A4F111C9BE55C427EB796C1932D8673579AF1B4C638B0944489012A2559A3B02481B01AC30BA8960F80C0C2B3947D36A12C080498BEE448716C973416C8242804A3DA099EE137B0BA90FE4A5C6A89200276A0CFB643EC2C56A2D708D7B4373E44C1502A763A600586E6CDA6273897D44448287DC2E602DC39200BF6166236559FD12A60892AEB153DD651BB469910B4B34669F91DA8654D1EB72EB6E02800B3B0A7D0A48C836854D3A83E65569CB7230BB44F3F143A6DEC5F2C39AB90F274F2088BD3D6A6FCA0070273BEDC84777FB52E3C558B0AE06183D5A48D452F68E15207F861627ACA14279630F82EC3A0CA078633B600AFA79743A600215BE5637458CE2CE8AFF5A08EB5017B2C766577479F8DC6BF9F5CC75089932161B96CEA406620AEDB630407F7687EBBB4814C7981637A48A90DE68031E062A7AF7612B4F5C7A6DA86BD136529E64295A5613EA73BD3D4448CB81F243135C0A660BEB9C17E651DEF469A7D90A15D3481090BCBF227012328941FA46F39C5006AD93D458AA6ADD655862B418C3094F551460DF2153A5810A7DA74F0614C2588BE49DC6F5E88154642BD1D3762563326433507156A57C57694BDD26E7A246FEB723AED67B04887C8E476B48CAB59E5362F26A9EF50C2BC80BA146226216FE62968A60D04E8C170D741C7A2B0E1ABDAC968E29020839D052FA372585627F8B59EE312AE414C979D825F06A6929A79625718A85768F3486BD32A01BF9A8F21EA938E648EAE4E5448C34C3EB88820B159EEDD')) + # Keygen test group 3, test case 51 + keygen_test('mlkem1024', + d=unhex('49AC8B99BB1E6A8EA818261F8BE68BDEAA52897E7EC6C40B530BC760AB77DCE3'), + z=unhex('99E3246884181F8E1DD44E0C7629093330221FD67D9B7D6E1510B2DBAD8762F7'), + ek_expected=unhex('A04184D4BC7B532A0F70A54D7757CDE6175A6843B861CB2BC4830C0012554CFC5D2C8A2027AA3CD967130E9B96241B11C4320C7649CC23A71BAFE691AFC08E680BCEF42907000718E4EACE8DA28214197BE1C269DA9CB541E1A3CE97CFADF9C6058780FE6793DBFA8218A2760B802B8DA2AA271A38772523A76736A7A31B9D3037AD21CEBB11A472B8792EB17558B940E70883F264592C689B240BB43D5408BF446432F412F4B9A5F6865CC252A43CF40A320391555591D67561FDD05353AB6B019B3A08A73353D51B6113AB2FA51D975648EE254AF89A230504A236A4658257740BDCBBE1708AB022C3C588A410DB3B9C308A06275BDF5B4859D3A2617A295E1A22F90198BAD0166F4A943417C5B831736CB2C8580ABFDE5714B586ABEEC0A175A08BC710C7A2895DE93AC438061BF7765D0D21CD418167CAF89D1EFC3448BCBB96D69B3E010C82D15CAB6CACC6799D3639669A5B21A633C865F8593B5B7BC800262BB837A924A6C5440E4FC73B41B23092C3912F4C6BEBB4C7B4C62908B03775666C22220DF9C88823E344C7308332345C8B795D34E8C051F21F5A21C214B69841358709B1C305B32CC2C3806AE9CCD3819FFF4507FE520FBFC27199BC23BE6B9B2D2AC1717579AC769279E2A7AAC68A371A47BA3A7DBE016F14E1A727333663C4A5CD1A0F8836CF7B5C49AC51485CA60345C990E06888720003731322C5B8CD5E6907FDA1157F468FD3FC20FA8175EEC95C291A262BA8C5BE990872418930852339D88A19B37FEFA3CFE82175C224407CA414BAEB37923B4D2D83134AE154E490A9B45A0563B06C953C3301450A2176A07C614A74E3478E48509F9A60AE945A8EBC7815121D90A3B0E07091A096CF02C57B25BCA58126AD0C629CE166A7EDB4B33221A0D3F72B85D562EC698B7D0A913D73806F1C5C87B38EC003CB303A3DC51B4B35356A67826D6EDAA8FEB93B98493B2D1C11B676A6AD9506A1AAAE13A824C7C08D1C6C2C4DBA9642C76EA7F6C8264B64A23CCCA9A74635FCBF03E00F1B5722B214376790793B2C4F0A13B5C40760B4218E1D2594DCB30A70D9C1782A5DD30576FA4144BFC8416EDA8118FC6472F56A979586F33BB070FB0F1B0B10BC4897EBE01BCA3893D4E16ADB25093A7417D0708C83A26322E22E6330091E30152BF823597C04CCF4CFC7331578F43A2726CCB428289A90C863259DD180C5FF142BEF41C7717094BE07856DA2B140FA67710967356AA47DFBC8D255B4722AB86D439B7E0A6090251D2D4C1ED5F20BBE6807BF65A90B7CB2EC0102AF02809DC9AC7D0A3ABC69C18365BCFF59185F33996887746185906C0191AED4407E139446459BE29C6822717644353D24AB6339156A9C424909F0A9025BB74720779BE43F16D81C8CC666E99710D8C68BB5CC4E12F314E925A551F09CC59003A1F88103C254BB978D75F394D3540E31E771CDA36E39EC54A62B5832664D821A72F1E6AFBBA27F84295B2694C498498E812BC8E9378FE541CEC5891B25062901CB7212E3CDC46179EC5BCEC10BC0B9311DE05074290687FD6A5392671654284CD9C8CC3EBA80EB3B662EB53EB75116704A1FEB5C2D056338532868DDF24EB8992AB8565D9E490CADF14804360DAA90718EAB616BAB0765D33987B47EFB6599C5563235E61E4BE670E97955AB292D9732CB8930948AC82DF230AC72297A23679D6B94C17F1359483254FEDC2F05819F0D069A443B78E3FC6C3EF4714B05A3FCA81CBBA60242A7060CD885D8F39981BB18092B23DAA59FD9578388688A09BBA079BC809A54843A60385E2310BBCBCC0213CE3DFAAB33B47F9D6305BC95C6107813C585C4B657BF30542833B14949F573C0612AD524BAAE69590C1277B86C286571BF66B3CFF46A3858C09906A794DF4A06E9D4B0A2E43F10F72A6C6C47E5646E2C799B71C33ED2F01EEB45938EB7A4E2E2908C53558A540D350369FA189C616943F7981D7618CF02A5B0A2BCC422E857D1A47871253D08293C1C179BCDC0437069107418205FDB9856623B8CA6B694C96C084B17F13BB6DF12B2CFBBC2B0E0C34B00D0FCD0AECFB27924F6984E747BE2A09D83A8664590A8077331491A4F7D720843F23E652C6FA840308DB4020337AAD37967034A9FB523B67CA70330F02D9EA20C1E84CB8E5757C9E1896B60581441ED618AA5B26DA56C0A5A73C4DCFD755E610B4FC81FF84E21'), + dk_expected=unhex('8C8B3722A82E550565521611EBBC63079944C9B1ABB3B0020FF12F631891A9C468D3A67BF6271280DA58D03CB042B3A461441637F929C273469AD15311E910DE18CB9537BA1BE42E98BB59E498A13FD440D0E69EE832B45CD95C382177D67096A18C07F1781663651BDCAC90DEDA3DDD143485864181C91FA2080F6DAB3F86204CEB64A7B4446895C03987A031CB4B6D9E0462FDA829172B6C012C638B29B5CD75A2C930A5596A3181C33A22D574D30261196BC350738D4FD9183A763336243ACED99B3221C71D8866895C4E52C119BF3280DAF80A95E15209A795C4435FBB3570FDB8AA9BF9AEFD43B094B781D5A81136DAB88B8799696556FEC6AE14B0BB8BE4695E9A124C2AB8FF4AB1229B8AAA8C6F41A60C34C7B56182C55C2C685E737C6CA00A23FB8A68C1CD61F30D3993A1653C1675AC5F0901A7160A73966408B8876B715396CFA4903FC69D60491F8146808C97CD5C533E71017909E97B835B86FF847B42A696375435E006061CF7A479463272114A89EB3EAF2246F0F8C104A14986828E0AD20420C9B37EA23F5C514949E77AD9E9AD12290DD1215E11DA274457AC86B1CE6864B122677F3718AA31B02580E64317178D38F25F609BC6C55BC374A1BF78EA8ECC219B30B74CBB3272A599238C93985170048F176775FB19962AC3B135AA59DB104F7114DBC2C2D42949ADECA6A85B323EE2B2B23A77D9DB235979A8E2D67CF7D2136BBBA71F269574B38888E1541340C19284074F9B7C8CF37EB01384E6E3822EC4882DFBBEC4E6098EF2B2FC177A1F0BCB65A57FDAA89315461BEB7885FB68B3CD096EDA596AC0E61DD7A9C507BC6345E0827DFCC8A3AC2DCE51AD731AA0EB932A6D0983992347CBEB3CD0D9C9719797CC21CF0062B0AD94CAD734C63E6B5D859CBE19F0368245351BF464D7505569790D2BB724D8659A9FEB1C7C473DC4D061E29863A2714BAC42ADCD1A8372776556F7928A7A44E94B6A25322D03C0A1622A7FD261522B7358F085BDFB60758762CB901031901B5EECF4920C81020A9B1781BCB9DD19A9DFB66458E7757C52CEC75B4BA740A24099CB56BB60A76B6901AA3E0169C9E83496D73C4C99435A28D613E97A1177F58B6CC595D3B2331E9CA7B57B74DC2C5277D26F2FE19240A55C35D6CFCA26C73E9A2D7C980D97960AE1A04698C16B398A5F20C35A0914145CE1674B71ABC6066A909A3E4B911E69D5A849430361F731B07246A6329B52361904225082D0AAC5B21D6B34862481A890C3C360766F04263603A6B73E802B1F70B2EB00046836B8F493BF10B90B8737C6C548449B294C47253BE26CA72336A632063AD3D0B48C8B0F4A34447EF13B764020DE739EB79ABA20E2BE1951825F293BEDD1089FCB0A91F560C8E17CDF52541DC2B81F972A7375B201F10C08D9B5BC8B95100054A3D0AAFF89BD08D6A0E7F2115A435231290460C9AD435A3B3CF35E52091EDD1890047BCC0AABB1ACEBC75F4A32BC1451ACC4969940788E89412188946C9143C5046BD1B458DF617C5DF533B052CD6038B7754034A23C2F7720134C7B4EACE01FAC0A2853A9285847ABBD06A3343A778AC6062E458BC5E61ECE1C0DE0206E6FE8A84034A7C5F1B005FB0A584051D3229B86C909AC5647B3D75569E05A88279D80E5C30F574DC327512C6BBE8101239EC62861F4BE67B05B9CDA9C545C13E7EB53CFF260AD9870199C21F8C63D64F0458A7141285023FEB829290872389644B0C3B73AC2C8E121A29BB1C43C19A233D56BED82740EB021C97B8EBBA40FF328B541760FCC372B52D3BC4FCBC06F424EAF253804D4CB46F41FF254C0C5BA483B44A87C219654555EC7C163C79B9CB760A2AD9BB722B93E0C28BD4B1685949C496EAB1AFF90919E3761B346838ABB2F01A91E554375AFDAAAF3826E6DB79FE7353A7A578A7C0598CE28B6D9915214236BBFFA6D45B6376A07924A39A7BE818286715C8A3C110CD76C02E0417AF138BDB95C3CCA798AC809ED69CFB672B6FDDC24D89C06A6558814AB0C21C62B2F84C0E3E0803DB337A4E0C7127A6B4C8C08B1D1A76BF07EB6E5B5BB47A16C74BC548375FB29CD789A5CFF91BDBD071859F4846E355BB0D29484E264DFF36C9177A7ACA78908879695CA87F25436BC12630724BB22F0CB64897FE5C41195280DA04184D4BC7B532A0F70A54D7757CDE6175A6843B861CB2BC4830C0012554CFC5D2C8A2027AA3CD967130E9B96241B11C4320C7649CC23A71BAFE691AFC08E680BCEF42907000718E4EACE8DA28214197BE1C269DA9CB541E1A3CE97CFADF9C6058780FE6793DBFA8218A2760B802B8DA2AA271A38772523A76736A7A31B9D3037AD21CEBB11A472B8792EB17558B940E70883F264592C689B240BB43D5408BF446432F412F4B9A5F6865CC252A43CF40A320391555591D67561FDD05353AB6B019B3A08A73353D51B6113AB2FA51D975648EE254AF89A230504A236A4658257740BDCBBE1708AB022C3C588A410DB3B9C308A06275BDF5B4859D3A2617A295E1A22F90198BAD0166F4A943417C5B831736CB2C8580ABFDE5714B586ABEEC0A175A08BC710C7A2895DE93AC438061BF7765D0D21CD418167CAF89D1EFC3448BCBB96D69B3E010C82D15CAB6CACC6799D3639669A5B21A633C865F8593B5B7BC800262BB837A924A6C5440E4FC73B41B23092C3912F4C6BEBB4C7B4C62908B03775666C22220DF9C88823E344C7308332345C8B795D34E8C051F21F5A21C214B69841358709B1C305B32CC2C3806AE9CCD3819FFF4507FE520FBFC27199BC23BE6B9B2D2AC1717579AC769279E2A7AAC68A371A47BA3A7DBE016F14E1A727333663C4A5CD1A0F8836CF7B5C49AC51485CA60345C990E06888720003731322C5B8CD5E6907FDA1157F468FD3FC20FA8175EEC95C291A262BA8C5BE990872418930852339D88A19B37FEFA3CFE82175C224407CA414BAEB37923B4D2D83134AE154E490A9B45A0563B06C953C3301450A2176A07C614A74E3478E48509F9A60AE945A8EBC7815121D90A3B0E07091A096CF02C57B25BCA58126AD0C629CE166A7EDB4B33221A0D3F72B85D562EC698B7D0A913D73806F1C5C87B38EC003CB303A3DC51B4B35356A67826D6EDAA8FEB93B98493B2D1C11B676A6AD9506A1AAAE13A824C7C08D1C6C2C4DBA9642C76EA7F6C8264B64A23CCCA9A74635FCBF03E00F1B5722B214376790793B2C4F0A13B5C40760B4218E1D2594DCB30A70D9C1782A5DD30576FA4144BFC8416EDA8118FC6472F56A979586F33BB070FB0F1B0B10BC4897EBE01BCA3893D4E16ADB25093A7417D0708C83A26322E22E6330091E30152BF823597C04CCF4CFC7331578F43A2726CCB428289A90C863259DD180C5FF142BEF41C7717094BE07856DA2B140FA67710967356AA47DFBC8D255B4722AB86D439B7E0A6090251D2D4C1ED5F20BBE6807BF65A90B7CB2EC0102AF02809DC9AC7D0A3ABC69C18365BCFF59185F33996887746185906C0191AED4407E139446459BE29C6822717644353D24AB6339156A9C424909F0A9025BB74720779BE43F16D81C8CC666E99710D8C68BB5CC4E12F314E925A551F09CC59003A1F88103C254BB978D75F394D3540E31E771CDA36E39EC54A62B5832664D821A72F1E6AFBBA27F84295B2694C498498E812BC8E9378FE541CEC5891B25062901CB7212E3CDC46179EC5BCEC10BC0B9311DE05074290687FD6A5392671654284CD9C8CC3EBA80EB3B662EB53EB75116704A1FEB5C2D056338532868DDF24EB8992AB8565D9E490CADF14804360DAA90718EAB616BAB0765D33987B47EFB6599C5563235E61E4BE670E97955AB292D9732CB8930948AC82DF230AC72297A23679D6B94C17F1359483254FEDC2F05819F0D069A443B78E3FC6C3EF4714B05A3FCA81CBBA60242A7060CD885D8F39981BB18092B23DAA59FD9578388688A09BBA079BC809A54843A60385E2310BBCBCC0213CE3DFAAB33B47F9D6305BC95C6107813C585C4B657BF30542833B14949F573C0612AD524BAAE69590C1277B86C286571BF66B3CFF46A3858C09906A794DF4A06E9D4B0A2E43F10F72A6C6C47E5646E2C799B71C33ED2F01EEB45938EB7A4E2E2908C53558A540D350369FA189C616943F7981D7618CF02A5B0A2BCC422E857D1A47871253D08293C1C179BCDC0437069107418205FDB9856623B8CA6B694C96C084B17F13BB6DF12B2CFBBC2B0E0C34B00D0FCD0AECFB27924F6984E747BE2A09D83A8664590A8077331491A4F7D720843F23E652C6FA840308DB4020337AAD37967034A9FB523B67CA70330F02D9EA20C1E84CB8E5757C9E1896B60581441ED618AA5B26DA56C0A5A73C4DCFD755E610B4FC81FF84E21D2E574DFD8CD0AE893AA7E125B44B924F45223EC09F2AD1141EA93A68050DBF699E3246884181F8E1DD44E0C7629093330221FD67D9B7D6E1510B2DBAD8762F7')) + # Encaps test from test group 1, test case 1 + encaps_test('mlkem512', + ek=unhex('dd1924935aa8e617af18b5a065ac45727767ee897cf4f9442b2ace30c0237b307d3e76bf8eeb78addc4aacd16463d8602fd5487b63c88bb66027f37d0d614d6f9c24603c42947664ac4398c6c52383469b4f9777e5ec7206210f3e5a796bf45c53268e25f39ac261af3bfa2ee755beb8b67ab3ac8df6c629c1176e9e3b965e9369f9b3b92ad7c20955641d99526fe7b9fe8c850820275cd964849250090733ce124ecf316624374bd18b7c358c06e9c136ee1259a9245abc55b964d689f5a08292d28265658ebb40cbfe488a2228275590ab9f32a34109709c1c291d4a23337274c7a5a5991c7a87b81c974ab18ce77859e4995e7c14f0371748b7712fb52c5966cd63063c4f3b81b47c45dde83fb3a2724029b10b3230214c04fa0577fc29ac9086ae18c53b3ed44e507412fca04b4f538a51588ec1f1029d152d9ae7735f76a077aa9484380aed9189e5912487fcc5b7c7012d9223dd967eecdac3008a8931b648243537f548c171698c5b381d846a72e5c92d4226c5a8909884f1c4a3404c1720a5279414d7f27b2b982652b6740219c56d217780d7a5e5ba59836349f726881dea18ef75c0772a8b922766953718cacc14ccbacb5fc412a2d0be521817645ab2bf6a4785e92bc94caf477a967876796c0a5190315ac0885671a4c749564c3b2c7aed9064eba299ef214ba2f40493667c8bd032aec5621711b41a3852c5c2bab4a349ce4b7f085a812bbbc820b81befe63a05b8bcdfe9c2a70a8b1aca9bf9816481907ff4432461111287303f0bd817c05726bfa18a2e24c7724921028032f622bd960a317d83b356b57f4a8004499cbc73c97d1eb7745972631c0561c1a3ab6ef91bd363280a10545da693e6d58aed6845e7cc5f0d08ca7905052c77366d1972ccfcc1a27610cb543665aa798e20940128b9567a7edb7a900407c70d359438435e13961608d552a94c5cda7859220509b483c5c52a210e9c812bc0c2328ca00e789a56b2606b90292e3543dacaa2431841d61a22ca90c1ccf0b5b4e0a6f640536d1a26ab5b8d2151327928ce02904cf1d15e32788a95f62d3c270b6fa1508f97b9155a2726d80a1afa3c5387a276a4d031a08abf4f2e74f1a0bb8a0fd3cb'), + m=unhex('6ff02e1dc7fd911beee0c692c8bd100c3e5c48964d31df92994218e80664a6ca'), + c_expected=unhex('19c592505907c24c5fa2ebfa932d2cbb48f3e4340a28f7eba5d068fcacabedf77784e2b24d7961775f0bf1a997ae8ba9fc4311be63716779c2b788f812cbb78c74e7517e22e910eff5f38d44469c50de1675ae198fd6a289ae7e6c30a9d4351b3d1f4c36eff9c68da91c40b82dc9b2799a33a26b60a4e70d7101862779469f3a9daec8e3e8f8c6a16bf092fba5866186b8d208fdeb274ac1f829659dc2be4ac4f306cb5584bad1936a92c9b76819234281bb395841c25756086ea564ca3e227e3d9f1052c0766d2eb79a47c150721e0dea7c0069d551b264801b7727ecaf82eecb99a876fda090bf6c3fc6b109f1701485f03ce66274b8435b0a014cfb3e79cced67057b5ae2ad7f5279eb714942e4c1ccff7e85c0db43e5d41289207363b444bb51bb8ab0371e70cbd55f0f3dad403e105176e3e8a225d84ac8bee38c821ee0f547431145dcb3139286abb11794a43a3c1b5229e4bcfe959c78adaee2d5f2497b5d24bc21fa03a9a58c2455373ec89583e7e588d7fe67991ee93783ed4a6f9eeae04e64e2e1e0e699f6dc9c5d39ef9278c985e7fdf2a764ffd1a0b95792ad681e930d76df4efe5d65dbbd0f1438481ed833ad4946ad1c69ad21dd7c86185774426f3fcf53b52ad4b40d228ce124072f592c7daa057f17d790a5bd5b93834d58c08c88dc8f0ef488156425b744654eaca9d64858a4d6ceb478795194bfadb18dc0ea054f9771215ad3cb1fd031d7be4598621926478d375a1845aa91d7c733f8f0e188c83896edf83b8646c99e29c0da2290e71c3d2e970720c97b5b7f950486033c6a2571ddf2bccdabb2dfa5fce4c3a1884606041d181c728794ae0e806ecb49af16756a4ce73c87bd4234e60f05535fa5929fd5a34473266401f63bbd6b90e003472ac0ce88f1b666597279d056a632c8d6b790fd411767848a69e37a8a839bc766a02ca2f695ec63f056a4e2a114cacf9fd90d730c970db387f6de73395f701a1d953b2a89dd7edad439fc205a54a481e889b098d5255670f026b4a2bf02d2bdde87c766b25fc5e0fd453757e756d18c8cd912f9a77f8e6bf0205374b462'), + k_expected=unhex('0bf323338d6f0a21d5514b673cd10b714ce6e36f35bcd1bf544196368ee51a13')) + # Encaps test from test group 2, test case 26 + encaps_test('mlkem768', + ek=unhex('89d2cb65f94dcbfc890efc7d0e5a7a38344d1641a3d0b024d50797a5f23c3a18b3101a1269069f43a842bacc098a8821271c673db1beb33034e4d7774d16635c7c2c3c2763453538bc1632e1851591a51642974e5928abb8e55fe55612f9b141aff015545394b2092e590970ec29a7b7e7aa1fb4493bf7cb731906c2a5cb49e6614859064e19b8fa26af51c44b5e7535bfdac072b646d3ea490d277f0d97ced47395fed91e8f2bce0e3ca122c2025f74067ab928a822b35653a74f06757629afb1a1caf237100ea935e793c8f58a71b3d6ae2c8658b10150d4a38f572a0d49d28ae89451d338326fdb3b4350036c1081117740edb86b12081c5c1223dbb5660d5b3cb3787d481849304c68be875466f14ee5495c2bd795ae412d09002d65b8719b90cba3603ac4958ea03cc138c86f7851593125334701b677f82f4952a4c93b5b4c134bb42a857fd15c650864a6aa94eb691c0b691be4684c1f5b7490467fc01b1d1fda4dda35c4ecc231bc73a6fef42c99d34eb82a4d014987b3e386910c62679a118f3c5bd9f467e4162042424357db92ef484a4a1798c1257e870a30cb20aaa0335d83314fe0aa7e63a862648041a72a6321523220b1ace9bb701b21ac1253cb812c15575a9085eabeade73a4ae76e6a7b158a20586d78a5ac620a5c9abcc9c043350a73656b0abe822da5e0ba76045fad75401d7a3b703791b7e99261710f86b72421d240a347638377205a152c794130a4e047742b888303bddc309116764de7424cebea6db65348ac537e01a9cc56ea667d5aa87ac9aaa4317d262c10143050b8d07a728ca633c13e468abcead372c77b8ecf3b986b98c1e55860b2b4216766ad874c35ed7205068739230220b5a2317d102c598356f168acbe80608de4c9a710b8dd07078cd7c671058af1b0b8304a314f7b29be78a933c7b9294424954a1bf8bc745de86198659e0e1225a910726074969c39a97c19240601a46e013dcdcb677a8cbd2c95a40629c256f24a328951df57502ab30772cc7e5b850027c8551781ce4985bdacf6b865c104e8a4bc65c41694d456b7169e45ab3d7acabeafe23ad6a7b94d1979a2f4c1cae7cd77d681d290b5d8e451bfdcccf5310b9d12a88ec29b10255d5e17a192670aa9731c5ca67ec784c502781be8527d6fc003c6701b3632284b40307a527c7620377feb0b73f722c9e3cd4dec64876b93ab5b7cfc4a657f852b659282864384f442b22e8a21109387b8b47585fc680d0ba45c7a8b1d7274bda57845d100d0f42a3b74628773351fd7ac305b2497639be90b3f4f71a6aa3561eecc6a691bb5cb3914d8634ca1e1af543c049a8c6e868c51f0423bd2d5ae09b79e57c27f3fe3ae2b26a441babfc6718ce8c05b4fe793b910b8fbcbbe7f1013242b40e0514d0bdc5c88bac594c794ce5122fbf34896819147b928381587963b0b90034aa07a10be176e01c80ad6a4b71b10af4241400a2a4cbbc05961a15ec1474ed51a3cc6d35800679a462809caa3ab4f7094cd6610b4a700cba939e7eac93e38c99755908727619ed76a34e53c4fa25bfc97008206697dd145e5b9188e5b014e941681e15fe3e132b8a3903474148ba28b987111c9bcb3989bbbc671c581b44a492845f288e62196e471fed3c39c1bbddb0837d0d4706b0922c4'), + m=unhex('2ce74ad291133518fe60c7df5d251b9d82add48462ff505c6e547e949e6b6bf7'), + c_expected=unhex('56b42d593aab8e8773bd92d76eabddf3b1546f8326f57a7b773764b6c0dd30470f68dff82e0dca92509274ecfe83a954735fde6e14676daaa3680c30d524f4efa79ed6a1f9ed7e1c00560e8683538c3105ab931be0d2b249b38cb9b13af5ceaf7887a59dba16688a7f28de0b14d19f391eb41832a56479416ccf94e997390ed7878eeaff49328a70e0ab5fce6c63c09b35f4e45994de615b88bb722f70e87d2bbd72ae71e1ee9008e459d8e743039a8ddeb874fce5301a2f8c0ee8c2fee7a4ee68b5ed6a6d9ab74f98bb3ba0fe89e82bd5a525c5e8790f818ccc605877d46c8bdb5c337b025bb840ff471896e43bfa99d73dbe31805c27a43e57f0618b3ae522a4644e0d4e4c1c548489431be558f3bfc50e16617e110dd7af9a6fd83e3fbb68c304d15f6cb700d61d7aa915a6751ea3ba80223e654132a20999a43bf408592730b9a9499636c09fa729f9cb1f9d3442f47357a2b9cf15d3103b9bf396c23088f118ede346b5c03891cfa5d517cef8471322e7e31087c4b036abad784bff72a9b11fa198facbcb91f067feaf76fcfe5327c1070b3da6988400756760d2d1f060298f1683d51e3616e98c51c9c03aa42f2e633651a47ad3cc2ab4a852ae0c4b04b4e1c3dd944445a2b12b4f42a6435105c04122fc3587afe409a00b308d63c5dd8163654504eedbb7b5329577c35fbeb3f463872cac28142b3c12a740ec6ea7ce9ad78c6fc8fe1b4df5fc55c1667f31f2312da07799dc870a478608549fedafe021f1cf2984180364e90ad98d845652aa3cdd7a8eb09f5e51423fab42a7b7bb4d514864be8d71297e9c3b17a993f0ae62e8ef52637bd1b885bd9b6ab727854d703d8dc478f96cb81fce4c60383ac01fcf0f971d4c8f352b7a82e218652f2c106ca92ae686bacfcef5d327347a97a9b375d67341552bc2c538778e0f9801823ccdfcd1eaaded55b18c9757e3f212b2889d3857db51f981d16185fd0f900853a75005e3020a8b95b7d8f2f2631c70d78a957c7a62e1b3719070acd1fd480c25b83847da027b6ebbc2eec2df22c87f9b46d5d7baf156b53cee929572b92c4784c4e829f3446a1ffe47f99decd0436029ddebd3ed8e87e5e73d123dbe8a4ddacf2abde87f33ae2b621c0ec5d5cad1259deec2aeff6088f04f27a20338b5762543e5100899a4cbfb7b3ca456b3a19b83a4c432230c23e1c7f107c4cb112152f1c0f30da0bb33f4f11f47eea43872bafa84ae22256d708e0604dade4b2a4dde8cccf11930e13553934ae3ece52f3d7ccc00287377879fe6b8ece7ef79423507c9da339559c20de1c51955999bae47401dc3cdfaa1b256d09c7db9fc8698bfcefa7302d56fbcde1fbaaa1c653454e6fd3d84e4f79a931c681cbb6cb462b10dae112bdfb7f65c7fdf6e5fc594ec3a474a94bd97e6ec81f71c230bf70ca0f13ce3dffbd9ff9804efd8f37a4d3629b43a8f55544ebc5ac0abd9a33d79699068346a0f1a3a96e115a5d80be165b562d082984d5aacc3a2301981a6418f8ba7d7b0d7ca5875c6'), + k_expected=unhex('2696d28e9c61c2a01ce9b1608dcb9d292785a0cd58efb7fe13b1de95f0db55b3')) + # Encaps test from test group 3, test case 51 + encaps_test('mlkem1024', + ek=unhex('307a4cea4148219b958ea0b7886659235a4d1980b192610847d86ef32739f94c3b446c4d81d89b8b422a9d079c88b11acaf321b014294e18b296e52f3f744cf9634a4fb01db0d99ef20a633a552e76a0585c6109f018768b763af3678b4780089c1342b96907a29a1c11521c744c2797d0bf2b9ccdca614672b45076773f458a31ef869be1eb2efeb50d0e37495dc5ca55e07528934f6293c4168027d0e53d07facc6630cb08197e53fb193a171135dc8ad9979402a71b6926bcdcdc47b93401910a5fcc1a813b682b09ba7a72d2486d6c799516465c14729b26949b0b7cbc7c640f267fed80b162c51fd8e09227c101d505a8fae8a2d7054e28a78ba8750decf9057c83979f7abb084945648006c5b28804f34e73b238111a65a1f500b1cc606a848f2859070beba7573179f36149cf5801bf89a1c38cc278415528d03bdb943f96280c8cc52042d9b91faa9d6ea7bcbb7ab1897a3266966f78393426c76d8a49578b98b159ebb46ee0a883a270d8057cd0231c86906a91dbbade6b2469581e2bca2fea8389f7c74bcd70961ea5b934fbcf9a6590bf86b8db548854d9a3fb30110433bd7a1b659ca8568085639237b3bdc37b7fa716d482a25b54106b3a8f54d3aa99b5123da96066904592f3a54ee23a7981ab608a2f4413cc658946c6d7780ea765644b3cc06c70034ab4eb351912e7715b56755d09021571bf340ab92598a24e811893195b96a1629f8041f58658431561fc0ab15292b913ec473f04479bc145cd4c563a286235646cd305a9be1014e2c7b130c33eb77cc4a0d9786bd6bc2a954bf3005778f8917ce13789bbb962807858b67731572b6d3c9b4b5206fac9a7c8961698d88324a915186899b29923f08442a3d386bd416bcc9a100164c930ec35eafb6ab35851b6c8ce6377366a175f3d75298c518d44898933f53dee617145093379c4659f68583b2b28122666bec57838991ff16c368dd22c36e780c91a3582e25e19794c6bf2ab42458a8dd7705de2c2aa20c054e84b3ef35032798626c248263253a71a11943571340a978cd0a602e47dee540a8814ba06f31414797cdf6049582361bbaba387a83d89913fe4c0c112b95621a4bda8123a14d1a842fb57b83a4fbaf33a8e552238a596aae7a150d75da648bc44644977ba1f87a4c68a8c4bd245b7d00721f7d64e822b085b901312ec37a8169802160cce1160f010be8cbcace8e7b005d7839234a707868309d03784b4273b1c8a160133ed298184704625f29cfa086d13263ee5899123c596ba788e5c54a8e9ba829b8a9d904bc4bc0bbea76bc53ff811214598472c9c202b73eff035dc09703af7bf1babaac73193cb46117a7c9492a43fc95789a924c5912787b2e2090ebbcfd3796221f06debf9cf70e056b8b9161d6347f47335f3e1776da4bb87c15cc826146ff0249a413b45aa93a805196ea453114b524e310aedaa46e3b99642368782566d049a726d6cca910993aed621d0149ea588a9abd909dbb69aa22829d9b83ada2209a6c2659f2169d668b9314842c6e22a74958b4c25bbdcd293d99cb609d866749a485dfb56024883cf5465dba0363206587f45597f89002fb8607232138e03b2a894525f265370054b48863614472b95d0a2303442e378b0dd1c75acbab971a9a8d1281c79613acec6933c377b3c578c2a61a1ec181b101297a37cc5197b2942f6a0e4704c0ec63540481b9f159dc255b59bb55df496ae54217b7689bd51dba0383a3d72d852ffca76df05b66eeccbd47bc53040817628c71e361d6af889084916b408a466c96e7086c4a60a10fcf7537bb94afbcc7d437590919c28650c4f2368259226a9bfda3a3a0ba1b5087d9d76442fd786c6f81c68c0360d7194d7072c4533aea86c2d1f8c0a27696066f6cfd11003f797270b32389713cffa093d991b63844c385e72277f166f5a3934d6bb89a4788de28321defc7457ab484bd30986dc1dab3008cd7b22f69702fabb9a1045407da4791c3590ff599d81d688cfa7cc12a68c50f51a1009411b44850f9015dc84a93b17c7a207552c661ea9838e31b95ead546248e56be7a5130505268771199880a141771a9e47acfed590cb3aa7cb7c5f74911d8912c29d6233f4d53bc64139e2f55be75507dd77868e384aec581f3f411db1a742972d3ebfd3315c84a5ad63a0e75c8bca3e3041e05d9067aff3b1244f763e7983'), + m=unhex('59c5154c04ae43aaff32700f081700389d54bec4c37c088b1c53f66212b12c72'), + c_expected=unhex('e2d5fd4c13cea0b52d874fea9012f3a51743a1093710bbf23950f9147a472ee5533928a2f46d592f35da8b4f758c893b0d7b98948be447b17cb2ae58af8a489ddd9232b99b1c0d2de77caa472bc3bbd4a7c60dbfdca92ebf3a1ce1c22dad13e887004e2924fd22656f5e508791de06d85e1a1426808ed9a89f6e2fd3c245d4758b22b02cade33b60fc889a33fc4447edebbfd4530de86596a33789d5dba6e6ec9f89879af4be4909a69017c9bb7a5e31815ea5f132eec4984faa7ccf594dd00d4d8487e45621af8f6e330551439c93ec078a7a3cc1594af91f8417375fd6088ceb5e85c67099091bac11498a0d711455f5e0d95cd7bbe5cdd8fecb319e6853c23c9be2c763df578666c40a40a87486e46ba8716146192904510a6dc59da8025825283d684db91410b4f12c6d8fbd0add75d3098918cb04ac7bc4db0d6bcdf1194dd86292e05b7b8630625b589cc509d215bbd06a2e7c66f424cdf8c40ac6c1e5ae6c964b7d9e92f95fc5c8852281628b81b9afabc7f03be3f62e8047bb88d01c68687b8dd4fe63820062b6788a53729053826ed3b7c7ef8241e19c85117b3c5341881d4f299e50374c8eefd5560bd18319a7963a3d02f0fbe84bc484b5a4018b97d274191c95f702bab9b0d105faf9fdcff97e437236567599faf73b075d406104d403cdf81224da590bec2897e30109e1f2e5ae4610c809a73f638c84210b3447a7c8b6dddb5ae200bf20e2fe4d4ba6c6b12767fb8760f66c5118e7a9935b41c9a471a1d3237688c1e618cc3be936aa3f5e44e086820b810e063211fc21c4044b3ac4d00df1bcc7b24dc07ba48b23b0fc12a3ed3d0a5cf7671415ab9cf21286fe63fb41418570555d4739b88104a8593f293025a4e3ee7c67e4b48e40f6ba8c09860c3fbbe55d45b45fc9ab629b17c276c9c9e2af3a043beafc18fd4f25ee7f83bddcd2d93914b7ed4f7c9af127f3f15c277be16551fef3ae03d7b9143f0c9c019ab97eea076366131f518363711b34e96d3f8a513f3e20b1d452c4b7ae3b975ea94d880dac6693399750d02220403f0d3e3fc1172a4de9dc280eaf0fee2883a6660bf5a3d246ff41d21b36ea521cf7aa689f800d0f86f4fa1057d8a13f9da8fffd0dc1fad3c04bb1cccb7c834db051a7ac2e4c60301996c93071ea416b421759935659cf62ca5f13ae07c3b195c148159d8beb03d440b00f5305765f20c0c46eee59c6d16206402db1c715e888bde59c781f35a7cc7c1c5ecb2155ae3e959c0964cc1ef8d7c69d1458a9a42f95f4c6b5b996345712aa290fbbf7dfd4a6e86463022a3f4725f6511bf7ea5e95c707cd3573609aadeaf540152c495f37fe6ec8bb9fa2aa61d15735934f4737928fde90ba995722465d4a64505a5201f07aa58cfd8ae226e02070b2dbf512b975319a7e8753b4fdae0eb4922869cc8e25c4a5560c2a0685de3ac392a8925ba882004894742e43ccfc277439ec8050a9aeb42932e01c840dfcedcc34d3991289a62c17d1284c839514b93351dbb2dda81f924565d70e7079d5b8126caab7a4a1c731655a53bcc09f5d63ec9086dea650055985edfa8297d9c95410c5d1894d17d5930549adbc2b8733c99fe62e17c4de34a5d89b12d18e42a422d2ce779c2c28eb2d98003d5cd323fcbecf02b5066e0e734810f09ed89013c00f011bd220f2e5d6a362df90599198a093b03c8d8efbfe0b617592faf1e64220c4440b53ffb47164f369c95290ba9f3108d686c57db645c53c012e57af25bd6693e2cc6b57651af1591fe5d8916640ec017c253df0606bb6b3035fae748f3d4034223b1b5efbf5283e778c1094291cf7b19be0f317350e6f8518fde0efb1381fb6e16c241f7f17a5210693a274159e7fac868cd0dc4359c3d9eefea0d9e31e43fa651392c65a543a59b3eee3a639dc9417d056a5ff0f160beee2eac29a7d88c0982cf70b5a46379f21e506aac61a9bb1b8c2b9dab0e44a823b61d0aa11d94f76a4a8e21f9d4280683208f4ea911116f6fd6a97426934ec3426b8c8f703da85e9dcf99336136003728b8ecdd04a389f6a817a78bfa61ba46020bf3c34829508f9d06d1553cd987aac380d86f168843ba3904de5f7058a41b4cd388bc9ce3aba7ee7139b7fc9e5b8cfaaa38990bd4a5db32e2613e7ec4f5f8b1292a38c6f4ff5a40490d76b126652fcf86e245235d636c65cd102b01e22781a72918c'), + k_expected=unhex('7264bde5c6cec14849693e2c3c86e48f80958a4f6186fc69333a4148e6e497f3')) + # Decaps test from test group 1, test case 1 (accept) + decaps_test('mlkem512', + dk=unhex('a5e26e1b2360203944acfc2d7c376780e55b5a5ca38674919437c794f54b8217bb0629c84c692ef7827eed864d0c508990ca4553f16f4720cb75368c1b8ca9dbc175f51bbebaa456f36611a2364775d248c0f4c40b342608f7370a983cf75c915570248e367375b665d9357ce4a8553e659be4a60ca68b58724689c23b74d34c9e78e168e7cb0df84641e41b6e6807be6cf4cf8f338525d57090b08aab5721216395c49147f6e817b117b129987317a7a5ff15a279f86af93c6a4995954000c3d4d8b0a07499a95a5c98d0b8303702dfd801b67c37268904c96abc462750384baea767a5ad30c5d452682b3ac864d1671db38f1cf2ce6e6c901d39c144da3d93b863f95717c3c585ab876d3ef2b10afa0b8142164c3c27fb179a923a3f924b15cebb22ec762907324f1cd4c47573ca1f103ca88844f3b86687280b3b5bb569b1c118b63565055834f39f320cb88c05c199e29684d7802cf45d8da342cc444d91a84d6d9461c873b66f9785488723a167412019077c9a7fcf4c7bd028be3007b3483026a442a095124c9607c950443fd69993615697e9ac1cb9d380437b85eb300ce4d9b5a5bc2132660da3527031a1057a565f2c76775565b0088637707410f2e955355425efe496113149cf52c901bccc48864c8aa4262367213602b63aa1a8bed77826c0c476152ab3464a20c9cd73f17a1d019466f2ae37859e6e5a8bb8862a480c1b12d6797b79663ed2333f188f34e6cf6ec87e43979f88787ce35877ddf0b689547bf5ba9eebb2659d76354ebc39ee83975310aca4f8867ff290793cc08bf29e60a97c28a71ea3084fe27845ab3664e80592412043b03056fdd5744bd74c9584094c2b75c689aca8e4b3d3f91994e4722b9b331399310975275a0065935b6cdf5a6a8216188452394238bc82736488a84a0c96c580a81c69032ad5e96f4c3061df5ab246c258cba0b68a32916bfc6686730b3ff0944a070f535a113fc349cddb0b67b40debfb5215167090f9891365bb3d87639fda05843a079a430fd5892f57ac4510450dec00b7905a3a14442231919f9ed4a76b2b159a6ccc3685b3dd1924935aa8e617af18b5a065ac45727767ee897cf4f9442b2ace30c0237b307d3e76bf8eeb78addc4aacd16463d8602fd5487b63c88bb66027f37d0d614d6f9c24603c42947664ac4398c6c52383469b4f9777e5ec7206210f3e5a796bf45c53268e25f39ac261af3bfa2ee755beb8b67ab3ac8df6c629c1176e9e3b965e9369f9b3b92ad7c20955641d99526fe7b9fe8c850820275cd964849250090733ce124ecf316624374bd18b7c358c06e9c136ee1259a9245abc55b964d689f5a08292d28265658ebb40cbfe488a2228275590ab9f32a34109709c1c291d4a23337274c7a5a5991c7a87b81c974ab18ce77859e4995e7c14f0371748b7712fb52c5966cd63063c4f3b81b47c45dde83fb3a2724029b10b3230214c04fa0577fc29ac9086ae18c53b3ed44e507412fca04b4f538a51588ec1f1029d152d9ae7735f76a077aa9484380aed9189e5912487fcc5b7c7012d9223dd967eecdac3008a8931b648243537f548c171698c5b381d846a72e5c92d4226c5a8909884f1c4a3404c1720a5279414d7f27b2b982652b6740219c56d217780d7a5e5ba59836349f726881dea18ef75c0772a8b922766953718cacc14ccbacb5fc412a2d0be521817645ab2bf6a4785e92bc94caf477a967876796c0a5190315ac0885671a4c749564c3b2c7aed9064eba299ef214ba2f40493667c8bd032aec5621711b41a3852c5c2bab4a349ce4b7f085a812bbbc820b81befe63a05b8bcdfe9c2a70a8b1aca9bf9816481907ff4432461111287303f0bd817c05726bfa18a2e24c7724921028032f622bd960a317d83b356b57f4a8004499cbc73c97d1eb7745972631c0561c1a3ab6ef91bd363280a10545da693e6d58aed6845e7cc5f0d08ca7905052c77366d1972ccfcc1a27610cb543665aa798e20940128b9567a7edb7a900407c70d359438435e13961608d552a94c5cda7859220509b483c5c52a210e9c812bc0c2328ca00e789a56b2606b90292e3543dacaa2431841d61a22ca90c1ccf0b5b4e0a6f640536d1a26ab5b8d2151327928ce02904cf1d15e32788a95f62d3c270b6fa1508f97b9155a2726d80a1afa3c5387a276a4d031a08abf4f2e74f1a0bb8a0fd3cb0ac923a76d541ca65fdec9c788a407326c7db508119f617f43b6e8a6f48a398702e051c20c31de77a1ba6777829f5539c886e3e14ded294d56ae5e88ac06ab09'), + c=unhex('19c592505907c24c5fa2ebfa932d2cbb48f3e4340a28f7eba5d068fcacabedf77784e2b24d7961775f0bf1a997ae8ba9fc4311be63716779c2b788f812cbb78c74e7517e22e910eff5f38d44469c50de1675ae198fd6a289ae7e6c30a9d4351b3d1f4c36eff9c68da91c40b82dc9b2799a33a26b60a4e70d7101862779469f3a9daec8e3e8f8c6a16bf092fba5866186b8d208fdeb274ac1f829659dc2be4ac4f306cb5584bad1936a92c9b76819234281bb395841c25756086ea564ca3e227e3d9f1052c0766d2eb79a47c150721e0dea7c0069d551b264801b7727ecaf82eecb99a876fda090bf6c3fc6b109f1701485f03ce66274b8435b0a014cfb3e79cced67057b5ae2ad7f5279eb714942e4c1ccff7e85c0db43e5d41289207363b444bb51bb8ab0371e70cbd55f0f3dad403e105176e3e8a225d84ac8bee38c821ee0f547431145dcb3139286abb11794a43a3c1b5229e4bcfe959c78adaee2d5f2497b5d24bc21fa03a9a58c2455373ec89583e7e588d7fe67991ee93783ed4a6f9eeae04e64e2e1e0e699f6dc9c5d39ef9278c985e7fdf2a764ffd1a0b95792ad681e930d76df4efe5d65dbbd0f1438481ed833ad4946ad1c69ad21dd7c86185774426f3fcf53b52ad4b40d228ce124072f592c7daa057f17d790a5bd5b93834d58c08c88dc8f0ef488156425b744654eaca9d64858a4d6ceb478795194bfadb18dc0ea054f9771215ad3cb1fd031d7be4598621926478d375a1845aa91d7c733f8f0e188c83896edf83b8646c99e29c0da2290e71c3d2e970720c97b5b7f950486033c6a2571ddf2bccdabb2dfa5fce4c3a1884606041d181c728794ae0e806ecb49af16756a4ce73c87bd4234e60f05535fa5929fd5a34473266401f63bbd6b90e003472ac0ce88f1b666597279d056a632c8d6b790fd411767848a69e37a8a839bc766a02ca2f695ec63f056a4e2a114cacf9fd90d730c970db387f6de73395f701a1d953b2a89dd7edad439fc205a54a481e889b098d5255670f026b4a2bf02d2bdde87c766b25fc5e0fd453757e756d18c8cd912f9a77f8e6bf0205374b462'), + k_expected=unhex('0bf323338d6f0a21d5514b673cd10b714ce6e36f35bcd1bf544196368ee51a13')) + # Decaps test from test group 2, test case 26 (accept) + decaps_test('mlkem768', + dk=unhex('b09125afb3cfb5295581373ab6885284d9706318280d223edc987fd14410dbe82e6ac89adfab70e67ca4b1c641ad037fd8c47870f159ec79cdcd52605b9890499bb6dbd8347f342c61436b642c0ddf4617db06198b8285dce4c09d9775a2f41c8cd18af8e75f57d4127df94d901ac83bacbd584cc50c43750f49b357f59350875c9b475480a8aaa168592ddb158614a639813566d205368c6c39f0413ca3230df60d44008282b682ac66b76c3c95f00b2a555035529c86ef3905b4a3968fea7802b6c5eecb08e8f0c42d7ab7cd21a62fb136412a1840b52c99970ccf51892f73497c3775be2189f7fc25e7c74d81fc217683292aa4866ddb04469855323a0810f0893de5c7f94a9c0b5337db83c44891b2e694695b76575032bf51761682958bd4f97be9a355b4a85bb6858b7e5a5ef653ab781056af9187d811c3a8936e5706503db57062410bcc9421f1ab867a657856c411c4e025ecb3c387729ae8e112f330b988e22f47c35c280750d21b107687af7b329ef3cb5289f06fb7d44548391e97ba6dd499b5907c54958413d92aa99d5646cf47a8f48cb70a07ad056b4eefe6c8c46645f7028a32410558638c48e83ac1570160c3833bf64052f5b7df4364d3e0b24e790aa7c98cee0441e6731d9de22d156c61e1c740397672ef54724f01b9d49923aa321f86b98823f21360138392b90c69434635275f9bfbb9b8a99e8e1b7f4ec25f75dbce33c13f750170bd6722efe496e7463e16aaa5867b869a96ad41b22bd2556c924596fd778d79a102f6e46d8eb18fefac8db19993e5414ac816705286892492c8c9e852d6145dff0c10e4a6703a459e7e732a6dfa2766a622b0622bfedb8f41c125f61b2ec264853b9ccc165979f6a263beb148905aac7618a70e829e23f28696f92ef6fa07c102cdbdb1288ba5cff3a81abba15974535fe3106a80068f14e98964572350a7112b1601c196710c096ccf164fbce1aabac9c5b9535070e61ab8068d611ca765fabb6412607dab30c4fc6ad073731fdc4c48b88e267c47b439ad2560c30561815ceb1f52c896489944bbbab52b1b1d1680a1057964dafa600c93a39a447ddbb0adf911afe3e823d8acc7cc04659f625f2c1837bb175282542cd22601f621581ab5a6c0384e087ccd32a5380b522fdd3a4202b5b41c85caff2903b2dc2645703d9bc711fbb404c0c0376187ac588aaf5718522d2273a9408dabcbc9701698d2da172aa6267a4c9693a24011c2265a2b6dc8e96304a98ddc5319a3140c399a08412c20f48537870bb84c32a094457895511ff7ec421de01a64b78534653f78327441b90cd115939dfaafa95b40d0a63d62d12eb5c9096018cc83871e44e6cd0be26d16b7b5a209b8e6471d2954adf9fabd0153707c9caa2bcc38ded841c791a0eb597eeee2c518d926edb28ab53caa5b7746466931b0ac9150688bf37049c1f82bcf648332434cd0a92fd2c958353a26cb65cb499057109b2d688cc43c4b385da7c50868af1b8075e57088f5db12dfa493eacb6dc4ec6e205baa2a89858ec2823c00553714cde47a96e36c7c198b3ec57ccf74d92cddb86aa0a8b8b5ca9d52bb60aba79f4f72b0125532ceb7a9077480d2bb60df51a989d2cb65f94dcbfc890efc7d0e5a7a38344d1641a3d0b024d50797a5f23c3a18b3101a1269069f43a842bacc098a8821271c673db1beb33034e4d7774d16635c7c2c3c2763453538bc1632e1851591a51642974e5928abb8e55fe55612f9b141aff015545394b2092e590970ec29a7b7e7aa1fb4493bf7cb731906c2a5cb49e6614859064e19b8fa26af51c44b5e7535bfdac072b646d3ea490d277f0d97ced47395fed91e8f2bce0e3ca122c2025f74067ab928a822b35653a74f06757629afb1a1caf237100ea935e793c8f58a71b3d6ae2c8658b10150d4a38f572a0d49d28ae89451d338326fdb3b4350036c1081117740edb86b12081c5c1223dbb5660d5b3cb3787d481849304c68be875466f14ee5495c2bd795ae412d09002d65b8719b90cba3603ac4958ea03cc138c86f7851593125334701b677f82f4952a4c93b5b4c134bb42a857fd15c650864a6aa94eb691c0b691be4684c1f5b7490467fc01b1d1fda4dda35c4ecc231bc73a6fef42c99d34eb82a4d014987b3e386910c62679a118f3c5bd9f467e4162042424357db92ef484a4a1798c1257e870a30cb20aaa0335d83314fe0aa7e63a862648041a72a6321523220b1ace9bb701b21ac1253cb812c15575a9085eabeade73a4ae76e6a7b158a20586d78a5ac620a5c9abcc9c043350a73656b0abe822da5e0ba76045fad75401d7a3b703791b7e99261710f86b72421d240a347638377205a152c794130a4e047742b888303bddc309116764de7424cebea6db65348ac537e01a9cc56ea667d5aa87ac9aaa4317d262c10143050b8d07a728ca633c13e468abcead372c77b8ecf3b986b98c1e55860b2b4216766ad874c35ed7205068739230220b5a2317d102c598356f168acbe80608de4c9a710b8dd07078cd7c671058af1b0b8304a314f7b29be78a933c7b9294424954a1bf8bc745de86198659e0e1225a910726074969c39a97c19240601a46e013dcdcb677a8cbd2c95a40629c256f24a328951df57502ab30772cc7e5b850027c8551781ce4985bdacf6b865c104e8a4bc65c41694d456b7169e45ab3d7acabeafe23ad6a7b94d1979a2f4c1cae7cd77d681d290b5d8e451bfdcccf5310b9d12a88ec29b10255d5e17a192670aa9731c5ca67ec784c502781be8527d6fc003c6701b3632284b40307a527c7620377feb0b73f722c9e3cd4dec64876b93ab5b7cfc4a657f852b659282864384f442b22e8a21109387b8b47585fc680d0ba45c7a8b1d7274bda57845d100d0f42a3b74628773351fd7ac305b2497639be90b3f4f71a6aa3561eecc6a691bb5cb3914d8634ca1e1af543c049a8c6e868c51f0423bd2d5ae09b79e57c27f3fe3ae2b26a441babfc6718ce8c05b4fe793b910b8fbcbbe7f1013242b40e0514d0bdc5c88bac594c794ce5122fbf34896819147b928381587963b0b90034aa07a10be176e01c80ad6a4b71b10af4241400a2a4cbbc05961a15ec1474ed51a3cc6d35800679a462809caa3ab4f7094cd6610b4a700cba939e7eac93e38c99755908727619ed76a34e53c4fa25bfc97008206697dd145e5b9188e5b014e941681e15fe3e132b8a3903474148ba28b987111c9bcb3989bbbc671c581b44a492845f288e62196e471fed3c39c1bbddb0837d0d4706b0922c472e31df613da9a1dd33b5d2d8939684b89f7649e1c59b959ffbe972786c477f66177dbf3b059173fd06afcd90e80e862174fc57f97607bbff5b73d6360fb5c37'), + c=unhex('56b42d593aab8e8773bd92d76eabddf3b1546f8326f57a7b773764b6c0dd30470f68dff82e0dca92509274ecfe83a954735fde6e14676daaa3680c30d524f4efa79ed6a1f9ed7e1c00560e8683538c3105ab931be0d2b249b38cb9b13af5ceaf7887a59dba16688a7f28de0b14d19f391eb41832a56479416ccf94e997390ed7878eeaff49328a70e0ab5fce6c63c09b35f4e45994de615b88bb722f70e87d2bbd72ae71e1ee9008e459d8e743039a8ddeb874fce5301a2f8c0ee8c2fee7a4ee68b5ed6a6d9ab74f98bb3ba0fe89e82bd5a525c5e8790f818ccc605877d46c8bdb5c337b025bb840ff471896e43bfa99d73dbe31805c27a43e57f0618b3ae522a4644e0d4e4c1c548489431be558f3bfc50e16617e110dd7af9a6fd83e3fbb68c304d15f6cb700d61d7aa915a6751ea3ba80223e654132a20999a43bf408592730b9a9499636c09fa729f9cb1f9d3442f47357a2b9cf15d3103b9bf396c23088f118ede346b5c03891cfa5d517cef8471322e7e31087c4b036abad784bff72a9b11fa198facbcb91f067feaf76fcfe5327c1070b3da6988400756760d2d1f060298f1683d51e3616e98c51c9c03aa42f2e633651a47ad3cc2ab4a852ae0c4b04b4e1c3dd944445a2b12b4f42a6435105c04122fc3587afe409a00b308d63c5dd8163654504eedbb7b5329577c35fbeb3f463872cac28142b3c12a740ec6ea7ce9ad78c6fc8fe1b4df5fc55c1667f31f2312da07799dc870a478608549fedafe021f1cf2984180364e90ad98d845652aa3cdd7a8eb09f5e51423fab42a7b7bb4d514864be8d71297e9c3b17a993f0ae62e8ef52637bd1b885bd9b6ab727854d703d8dc478f96cb81fce4c60383ac01fcf0f971d4c8f352b7a82e218652f2c106ca92ae686bacfcef5d327347a97a9b375d67341552bc2c538778e0f9801823ccdfcd1eaaded55b18c9757e3f212b2889d3857db51f981d16185fd0f900853a75005e3020a8b95b7d8f2f2631c70d78a957c7a62e1b3719070acd1fd480c25b83847da027b6ebbc2eec2df22c87f9b46d5d7baf156b53cee929572b92c4784c4e829f3446a1ffe47f99decd0436029ddebd3ed8e87e5e73d123dbe8a4ddacf2abde87f33ae2b621c0ec5d5cad1259deec2aeff6088f04f27a20338b5762543e5100899a4cbfb7b3ca456b3a19b83a4c432230c23e1c7f107c4cb112152f1c0f30da0bb33f4f11f47eea43872bafa84ae22256d708e0604dade4b2a4dde8cccf11930e13553934ae3ece52f3d7ccc00287377879fe6b8ece7ef79423507c9da339559c20de1c51955999bae47401dc3cdfaa1b256d09c7db9fc8698bfcefa7302d56fbcde1fbaaa1c653454e6fd3d84e4f79a931c681cbb6cb462b10dae112bdfb7f65c7fdf6e5fc594ec3a474a94bd97e6ec81f71c230bf70ca0f13ce3dffbd9ff9804efd8f37a4d3629b43a8f55544ebc5ac0abd9a33d79699068346a0f1a3a96e115a5d80be165b562d082984d5aacc3a2301981a6418f8ba7d7b0d7ca5875c6'), + k_expected=unhex('2696d28e9c61c2a01ce9b1608dcb9d292785a0cd58efb7fe13b1de95f0db55b3')) + # Decaps test from test group 3, test case 51 (accept) + decaps_test('mlkem1024', + dk=unhex('673751cbb596541131c66398662cb4b0eb80796a88b28144a5bbc854f80d4b35be0ab241e4795f8fbba814f50fa80498cbe8bf68a0a583a4c5981b41df0667db614a628c3060697438e62c8d36026ee29c96b673bf1a194ee49481351f4d1748dd01cd023142f01057142b741cba8302e432f88c63d0b4b5767ac3a5a59afa3a321e65b1d1511807a06e16a04b2f1070e465586d4a9b68e2b42d57a356fa7bb3d04e51b193ff4c757cfa0f15924ea6e49afb83b2919c985869ada544338f44ae96a874c425af87bc73f3cb0fd2627b1539b1f19a77e36b7fc817851d39bd8a069a6c2202c17469d421a588e65daf450030b6674ec1c734aa25414b119e61b26efc90df81059d2b9599414f93692bf45a4b1c5cc09edb37b1b1433026aea6b0200722b819c7bc061c53a4304992fca2aee2324a324ab91c3e5d562096b8a141756940f15a2800c274ea4f65817e639c5d2a278c6a294f9db331f84ccb0a10309f530a06eb962573c86005c15bfc7531a143026396721297e25cb655a294964b2fe531905f2802376b8ace35ae3e2814bab7062bc1a840657dbfcb5f41bb55475697849a31e2222e995518ca7640ad4b9cee9820984138be0510ffd6ac225393a5f0cb030528cd2a0610e78a5cf1b073039a6d143068c53dbd15a1d4446da7b310ee795d1fb31b2f97008f83bdf348a593a3bdcbb571907b36d0978162c253e6f50106c463149834abfb0707d8ab4a4babc323598a085b309764b7c32c9db0c9f2d52ef2f00bace7846868c33b82afa430a4c2f67b698a60526a161cd62115dca767c203e3e2cc787031a73b5b7dba1eee5ab04b77bb569b952d9a15d198779804197d23c18e5b055f5c8087d742f64418d6505e70418abfc6b1bf7bb3de286599f4676cf87946d65144998afae1c689449e3f349fd0809afb856dde4a94a2c0258d56432f40c3da812d3fd3b72259a61d2882e0f50b355121e564c6bd33366f32bf4a5996b9998961354925a2bacdf48056118453ac3792a7879b71579adb65f5d83b1ed6c8c49836de379daa027e62b96f683c1688935cb3fccd64329267273e60c6cd59ba1b7fc911e2662527eccb7a474e5ef00ca9f789a3838e889242e7fb2b08f3790613c4eed3c912ec4eb029b971096b384727697b4ddc3b698c9a6da6971fa4c574ecd18eb1c84c0c5790153aa6b9db61d8bac0a680a37ed623582a7e8c0885ebb35af341477764368e0647b14553672316d0b90317c5b53aa747e61b4750db9e63cc3712900005ca24226b523e0a179582c85968c107857bb41521b7342b13dcac462a53be38446f2142519667b48b1c68fcafa4d3c7e3e5aff163c41f2c1b4dbac5456c30776078e7c3a713819f6b9aca55d77d60637183a723035730f94285c42ac3587637f66ac30f2c4039e60420967576e27b96c8c004d9585f33939ac44f0d195b35d472fc219076f12d0984ac844728d5d2266bb5cd8b325dda497b4f397bfe722c9d7684201a921f502271985cb3f31c04884c090b063631253dc454537031f2c82c10a1722de6c556464dc9d64389da37e469480c921065c79a30c83c867c952b30548a6b5bdfeb6ea6247480f163b427b17cf94889220fe934564dab90f5b6a11648870b654495a6691ae21fea86bdc8c49093fa07e926af3aba0e7cec21f613b49986c6c8a139eda70b7ed8211a3215e8c43ef8c151ae61740ef83b48276033614b58e9ceb992233cd21dff70c7a6f7171707a2add37acbf136a4eb4a79517fd0c8aff0b5126435c3100331f208a546c9a4044a8f0503c8ade9506a018b4ca7c6e8d70120017d38b13b52786a85a540d81b8e71c376b796a7215abf065086d3c80ee94b8f09e2a3ba13b82583b825388e87ba010af507173563789a1dcd088907c52bd7fc1c6930605f060f37978211c10fb5717e3fa291d20b5d43fb74cd4711394b0027e41c52b523797470532cbe123c92950720e5e255256577d4e156ebd4c698d813405c61430b978694acde78031e74ba1d8517dae2346f008411231fcce7bff75bc361e691e776049004097b36490d876288701b2d3a1743ab8753d47ac6200e2da7458d3a059681233872794e6720186b20108b1d1033971ce19ed67a2a28e499a360a4ad86ae4194034f202f8fa3626fe75f307a4cea4148219b958ea0b7886659235a4d1980b192610847d86ef32739f94c3b446c4d81d89b8b422a9d079c88b11acaf321b014294e18b296e52f3f744cf9634a4fb01db0d99ef20a633a552e76a0585c6109f018768b763af3678b4780089c1342b96907a29a1c11521c744c2797d0bf2b9ccdca614672b45076773f458a31ef869be1eb2efeb50d0e37495dc5ca55e07528934f6293c4168027d0e53d07facc6630cb08197e53fb193a171135dc8ad9979402a71b6926bcdcdc47b93401910a5fcc1a813b682b09ba7a72d2486d6c799516465c14729b26949b0b7cbc7c640f267fed80b162c51fd8e09227c101d505a8fae8a2d7054e28a78ba8750decf9057c83979f7abb084945648006c5b28804f34e73b238111a65a1f500b1cc606a848f2859070beba7573179f36149cf5801bf89a1c38cc278415528d03bdb943f96280c8cc52042d9b91faa9d6ea7bcbb7ab1897a3266966f78393426c76d8a49578b98b159ebb46ee0a883a270d8057cd0231c86906a91dbbade6b2469581e2bca2fea8389f7c74bcd70961ea5b934fbcf9a6590bf86b8db548854d9a3fb30110433bd7a1b659ca8568085639237b3bdc37b7fa716d482a25b54106b3a8f54d3aa99b5123da96066904592f3a54ee23a7981ab608a2f4413cc658946c6d7780ea765644b3cc06c70034ab4eb351912e7715b56755d09021571bf340ab92598a24e811893195b96a1629f8041f58658431561fc0ab15292b913ec473f04479bc145cd4c563a286235646cd305a9be1014e2c7b130c33eb77cc4a0d9786bd6bc2a954bf3005778f8917ce13789bbb962807858b67731572b6d3c9b4b5206fac9a7c8961698d88324a915186899b29923f08442a3d386bd416bcc9a100164c930ec35eafb6ab35851b6c8ce6377366a175f3d75298c518d44898933f53dee617145093379c4659f68583b2b28122666bec57838991ff16c368dd22c36e780c91a3582e25e19794c6bf2ab42458a8dd7705de2c2aa20c054e84b3ef35032798626c248263253a71a11943571340a978cd0a602e47dee540a8814ba06f31414797cdf6049582361bbaba387a83d89913fe4c0c112b95621a4bda8123a14d1a842fb57b83a4fbaf33a8e552238a596aae7a150d75da648bc44644977ba1f87a4c68a8c4bd245b7d00721f7d64e822b085b901312ec37a8169802160cce1160f010be8cbcace8e7b005d7839234a707868309d03784b4273b1c8a160133ed298184704625f29cfa086d13263ee5899123c596ba788e5c54a8e9ba829b8a9d904bc4bc0bbea76bc53ff811214598472c9c202b73eff035dc09703af7bf1babaac73193cb46117a7c9492a43fc95789a924c5912787b2e2090ebbcfd3796221f06debf9cf70e056b8b9161d6347f47335f3e1776da4bb87c15cc826146ff0249a413b45aa93a805196ea453114b524e310aedaa46e3b99642368782566d049a726d6cca910993aed621d0149ea588a9abd909dbb69aa22829d9b83ada2209a6c2659f2169d668b9314842c6e22a74958b4c25bbdcd293d99cb609d866749a485dfb56024883cf5465dba0363206587f45597f89002fb8607232138e03b2a894525f265370054b48863614472b95d0a2303442e378b0dd1c75acbab971a9a8d1281c79613acec6933c377b3c578c2a61a1ec181b101297a37cc5197b2942f6a0e4704c0ec63540481b9f159dc255b59bb55df496ae54217b7689bd51dba0383a3d72d852ffca76df05b66eeccbd47bc53040817628c71e361d6af889084916b408a466c96e7086c4a60a10fcf7537bb94afbcc7d437590919c28650c4f2368259226a9bfda3a3a0ba1b5087d9d76442fd786c6f81c68c0360d7194d7072c4533aea86c2d1f8c0a27696066f6cfd11003f797270b32389713cffa093d991b63844c385e72277f166f5a3934d6bb89a4788de28321defc7457ab484bd30986dc1dab3008cd7b22f69702fabb9a1045407da4791c3590ff599d81d688cfa7cc12a68c50f51a1009411b44850f9015dc84a93b17c7a207552c661ea9838e31b95ead546248e56be7a5130505268771199880a141771a9e47acfed590cb3aa7cb7c5f74911d8912c29d6233f4d53bc64139e2f55be75507dd77868e384aec581f3f411db1a742972d3ebfd3315c84a5ad63a0e75c8bca3e3041e05d9067aff3b1244f763e7983d48ba34134bab88d635d8cf8ff5d686058fa68b6c2feeaa5fa4de65757086c0125e937bcc0d02faa8988ae7169df07f6a771e6e7fe3ab65e965c63c3e40ed909'), + c=unhex('e2d5fd4c13cea0b52d874fea9012f3a51743a1093710bbf23950f9147a472ee5533928a2f46d592f35da8b4f758c893b0d7b98948be447b17cb2ae58af8a489ddd9232b99b1c0d2de77caa472bc3bbd4a7c60dbfdca92ebf3a1ce1c22dad13e887004e2924fd22656f5e508791de06d85e1a1426808ed9a89f6e2fd3c245d4758b22b02cade33b60fc889a33fc4447edebbfd4530de86596a33789d5dba6e6ec9f89879af4be4909a69017c9bb7a5e31815ea5f132eec4984faa7ccf594dd00d4d8487e45621af8f6e330551439c93ec078a7a3cc1594af91f8417375fd6088ceb5e85c67099091bac11498a0d711455f5e0d95cd7bbe5cdd8fecb319e6853c23c9be2c763df578666c40a40a87486e46ba8716146192904510a6dc59da8025825283d684db91410b4f12c6d8fbd0add75d3098918cb04ac7bc4db0d6bcdf1194dd86292e05b7b8630625b589cc509d215bbd06a2e7c66f424cdf8c40ac6c1e5ae6c964b7d9e92f95fc5c8852281628b81b9afabc7f03be3f62e8047bb88d01c68687b8dd4fe63820062b6788a53729053826ed3b7c7ef8241e19c85117b3c5341881d4f299e50374c8eefd5560bd18319a7963a3d02f0fbe84bc484b5a4018b97d274191c95f702bab9b0d105faf9fdcff97e437236567599faf73b075d406104d403cdf81224da590bec2897e30109e1f2e5ae4610c809a73f638c84210b3447a7c8b6dddb5ae200bf20e2fe4d4ba6c6b12767fb8760f66c5118e7a9935b41c9a471a1d3237688c1e618cc3be936aa3f5e44e086820b810e063211fc21c4044b3ac4d00df1bcc7b24dc07ba48b23b0fc12a3ed3d0a5cf7671415ab9cf21286fe63fb41418570555d4739b88104a8593f293025a4e3ee7c67e4b48e40f6ba8c09860c3fbbe55d45b45fc9ab629b17c276c9c9e2af3a043beafc18fd4f25ee7f83bddcd2d93914b7ed4f7c9af127f3f15c277be16551fef3ae03d7b9143f0c9c019ab97eea076366131f518363711b34e96d3f8a513f3e20b1d452c4b7ae3b975ea94d880dac6693399750d02220403f0d3e3fc1172a4de9dc280eaf0fee2883a6660bf5a3d246ff41d21b36ea521cf7aa689f800d0f86f4fa1057d8a13f9da8fffd0dc1fad3c04bb1cccb7c834db051a7ac2e4c60301996c93071ea416b421759935659cf62ca5f13ae07c3b195c148159d8beb03d440b00f5305765f20c0c46eee59c6d16206402db1c715e888bde59c781f35a7cc7c1c5ecb2155ae3e959c0964cc1ef8d7c69d1458a9a42f95f4c6b5b996345712aa290fbbf7dfd4a6e86463022a3f4725f6511bf7ea5e95c707cd3573609aadeaf540152c495f37fe6ec8bb9fa2aa61d15735934f4737928fde90ba995722465d4a64505a5201f07aa58cfd8ae226e02070b2dbf512b975319a7e8753b4fdae0eb4922869cc8e25c4a5560c2a0685de3ac392a8925ba882004894742e43ccfc277439ec8050a9aeb42932e01c840dfcedcc34d3991289a62c17d1284c839514b93351dbb2dda81f924565d70e7079d5b8126caab7a4a1c731655a53bcc09f5d63ec9086dea650055985edfa8297d9c95410c5d1894d17d5930549adbc2b8733c99fe62e17c4de34a5d89b12d18e42a422d2ce779c2c28eb2d98003d5cd323fcbecf02b5066e0e734810f09ed89013c00f011bd220f2e5d6a362df90599198a093b03c8d8efbfe0b617592faf1e64220c4440b53ffb47164f369c95290ba9f3108d686c57db645c53c012e57af25bd6693e2cc6b57651af1591fe5d8916640ec017c253df0606bb6b3035fae748f3d4034223b1b5efbf5283e778c1094291cf7b19be0f317350e6f8518fde0efb1381fb6e16c241f7f17a5210693a274159e7fac868cd0dc4359c3d9eefea0d9e31e43fa651392c65a543a59b3eee3a639dc9417d056a5ff0f160beee2eac29a7d88c0982cf70b5a46379f21e506aac61a9bb1b8c2b9dab0e44a823b61d0aa11d94f76a4a8e21f9d4280683208f4ea911116f6fd6a97426934ec3426b8c8f703da85e9dcf99336136003728b8ecdd04a389f6a817a78bfa61ba46020bf3c34829508f9d06d1553cd987aac380d86f168843ba3904de5f7058a41b4cd388bc9ce3aba7ee7139b7fc9e5b8cfaaa38990bd4a5db32e2613e7ec4f5f8b1292a38c6f4ff5a40490d76b126652fcf86e245235d636c65cd102b01e22781a72918c'), + k_expected=unhex('7264bde5c6cec14849693e2c3c86e48f80958a4f6186fc69333a4148e6e497f3')) + # Decaps test from test group 4, test case 77 (reject) + decaps_test('mlkem512', + dk=unhex('69f9cbfd1237ba161cf6e6c18f488fc6e39ab4a5c9e6c22ea4e3ad8f267a9c442010d32e61f83e6bfa5c58706145376dbb849528f68007c822b33a95b84904dcd2708d0340c8b808bcd3aad0e48b85849583a1b4e5945dd9514a7f6461e057b7ecf61957e97cf62815f9c32294b326e1a1c4e360b9498ba80f8ca91532b171d0aefc4849fa53bc617932e208a677c6044a6600b8d8b83f26a747b18cfb78beafc551ad52b7ca6cb88f3b5d9ce2af6c67956c478cef491f59e0191b3bbe929b94b666c176138b00f49724341ee2e164b94c053c185a51f93e00f36861613a7fd72febd23a8b96a260234239c9628f995dc13807b43a69468167cb1a8f9dd07ee3b33238f63096ebc49d5051c4b65963d74a4766c226f0b94f1862c2124c8c749748c0bc4dc14cb34906b81c5524fb8100798542dc6cc2aa0a708575eabcc11f96a9e61c017a96a7ce93c42091737113ae783c0ae8755e594111edfabfd86c3212c612a7b62afd3c7a5c78b2f07344b789c2b2dbb5f4448be97bba4233c0039c0fe84300f9b03ac99497e6d46b6e95308ff84790f612cf186ec16811e80c179316a63b25703f60b842b61907e62894e736647b3c09da6fec5932782b36e0635085a3949e694d7e17cba3d9064330438c071b5836a770c55f6213cc1425845de5a334d75d3e5058c7809fda4bcd78191da9797325e6236c2650fc604ee43a83ceb34980084403a33259857907799a9d2a713a633b5c904727f61e42520991d655705cb6bc1b74af60713ef8712f14086869be8eb297d228b325a0609fd615eab7081540a61a82abf43b7df98a595be11f416b41e1eb75bb57977c25c64e97437d88ca5fda6159d668f6bab8157555b5d54c0f47cbcd16843b1a0a0f0210ee310313967f3d516499018fdf3114772470a1889cc06cb6b6690ac31abcfaf4bc707684545b000b580ccbfcbce9fa70aaea0bbd9110992a7c6c06cb368527fd229090757e6fe75705fa592a7608f050c6f88703cc28cb000c1d7e77b897b72c62bcc7aea21a57729483d2211832bed612430c983103c69e8c072c0ea7898f2283bec48c5ac81984d4a5a83619735a842bd172c0d1b39f43588af170458ba9ee7492eaaa94ea53a4d38498ecbb98a5f407e7c97b4e166e397192c216033014b878e938075c6c1f10a0065abc3163722f1a2effec8d6e3a0c4f7174fc16b79fb5186a75168f81a56aa48a20a04bddf182c6e179c3f69061555ef7396dd0b7499601a6eb3a96a9a22d04f1168db56355b07600a20370637b645976bbd97b6d6288a0d3036360472e3ac71d566db8fbb1b1d76cb755cd0d68bdbfc048eba2525eea9dd5b144fb3b60fbc34239320cbc069b35ab16b8756536fb33e8a6af1dd42c79f48ad120ae4b159d3d8c319060cce569c3f6035365585d34413795a6a18ec5136ab13c90e3af14c0b8a464c86b9073222b56b3f7328aea798155325911250ef016d72802e3878aa50540cc983956971d6efa352c02554dc760a5a91358ea56370884fd5b3f85b70e83e4697deb1705169e9c60a74528cf15281cb1b1c457d467b5f93a60373d10e0cf6a837aa3c9596a72bec29b2d7e58653d533061d381d51759752217eb46cac7807c4ad38b611644acf0a3f26b6b084ab47a83bf0d696f8a4768fc35bca6bc7903b2a237c27749f5510c863869e6ae56bb2afe4771c9221874f50f5b14baad5993b49238fd0a0c9f79b7b4584e41301f7a885c9f91819bea00d512581730539fb37e59e86a6d19ca25f0a811c9b428ba8614aa4f94807bc031cbcc183f3bf07fe2c1a6eba80d5a706ee0dab27e231458025d84a7a9b0230501116c290a6bb50626d97b939850942828390b0a2001b7853ad1ae9b011b2db36caeea73a2328e3c56485b491c299115a017c907ab54317260a593a0d7ba6d06615d6e2ca84b860eff3ccb597211bfe36bdef8069afa36c5a73392722650e4957dca597acba5605b63c163cfa94b64ddd62301a4332083361972589db0599a694dd4547a5ee9196577c22ed427ac89bb8ba3753eb76c41f2c1129c8a77d6805fa719b1b6ca11b740a78a3d41b5330526ab87d58d5925315a1485edc647c1604eb38138de637ad2c6ca5be44e1008b2c0867b229ccc36619e2758c4c2029eaeb26e7a803fca305a59cd585e117d698ece011cc3fce54d2e114545a21ac5be6771ab8f13122fad295e745a503b142f91aef7bde99998845fda043555c9c1ee535be125e5dce5d266667e723e67b6ba891c16cba174098a3f351778b0888c9590a9090cd404'), + c=unhex('5c26d456c6c7b0e8df0b125e5d5428fe393655127a5e05bdd1bcac14c47493783097b6185058fa700555dd8af10f0f979a39a603826ffeb0b44e9487539f3f1a07c673e96640ddf754c8b98cd83473568b49d095f682c1acf0e160ab93eb41a16a57d53b419620d351c837315080d530845cf8d63cfccdb6e9dfbe220a2c14221aa392e6337fa364df0d2e0398f15ac3dc822b5dd7217081107a45c8cb8eaca51e034117962aee7ec0ee212fa67a5d4b07d355a0981e4285116ecf5ca9fab6e3105e4de4aec5e32938a1eb91e65ce7b39c3b9829aa1e72b8092c3622e519ee092fac8106d6597ceb941c763288723cb55044a36d4181052a78b424b0de1b0260f624a8d3b317095371ee9beea9272250d598ac63c2138d23f99087777a902eba2163171a07546b72fce7f86ee3b1dc1b8eac85440b8d241742c3771f91bf981909e4f3e2505c594761259ed3aada6aa09181b99037a395d66e6ee4bbef97de6ba36c53a1808cba50938038c151603105bd6a4199ea44bf4b08961672598cb708f896e03cd9b8f8ad89decfbe6be0ef0006b7bd2f4aa6eb21c0218ede601d46924cf391ae3a44e43d96ebe84a630937c3409ef0710970c27e3add4e64dc64e83942abea9ccf498ef1fe72b254043d2775a37e0b5ddd3f596ea131e0734afa9d0223f4cd9d1ab7304ca979ad37f717bedc3a9526f8fc94433fe4614f82e709456f39bee7bacc84e5a70114af1c2ac8b9b3faa81c8f35f5a5d24189e1a457f58166473f5f1df0170aab5e4ac8fc719f945ccbe6f2fed24b23321d95c4c850b278b8c4ea02e3098d5a599aa3d842cf889b7f284ac5e6e66386d63f2c860b997966b4df2c32288a50045012b7362727b856af4f8258509b563758752ffbb1040f3c2ad8b0ded64fc15c95c1a16de0dae6625a9effce190fc7f3261d844c114913c6b1152a258a37761b81879b59c37a1dfac07c3e934510b45da44c2581a79dafbf00fabb207306269d9b74b93f4367b3ba22ccc51b362de16e49d9fdbf8cff84f6ce6892ca2245d34ceb9c8759e702832b66a572de9f3016a38f7328700f96b2e947'), + k_expected=unhex('a4a24e182fea12ff128ab2d4afe6569817513ffc547db70636752c9c66c002b8')) + # Decaps test from test group 5, test case 86 (reject) + decaps_test('mlkem768', + dk=unhex('1e4ac87b1a692a529fdbbab93374c57d110b10f2b1ddebac0d196b7ba631b8e9293028a8f379888c422dc8d32bbf226010c2c1ec73189080456b0564b258b0f23131bc79c8e8c11cef3938b243c5ce9c0edd37c8f9d29877dbbb615b9b5ac3c948487e467196a9143efbc7cedb64b45d4acda2666cbc2804f2c8662e128f6a9969ec15bc0b9351f6f96346aa7abc743a14fa030e37a2e7597bddfc5a22f9cedaf8614832527210b26f024c7f6c0dcf551e97a4858764c321d1834ad51d75bb246d277237b7bd41dc4362d063f4298292272d01011780b79856b296c4e946658b79603197c9b2a99ec66acb06ce2f69b5a5a61e9bd06ad443ceb0c74ed65345a903b614e81368aac2b3d2a79ca8ccaa1c3b88fb82a36632860b3f7950833fd0212ec96ede4ab6f5a0bda3ec6060a658f9457f6cc87c6b620c1a1451987486e496612a101d0e9c20577c571edb5282608bf4e1ac926c0db1c82a504a799d89885ca6252bd5b1c183af701392a407c05b848c2a3016c40613f02a449b3c7926da067a533116506840097510460bbfd36073dcb0bfa009b36a9123eaa68f835f74a01b00d2097835964df521ce9210789c30b7f06e5844b444c53322396e4799baf6a88af7315860d0192d48c2c0da6b5ba64325543acdf5900e8bc477ab05820072d463affed097e062bd78c99d12b385131a241b708865b4190af69ea0a64db71448a60829369c7555198e438c9abc310bc70101913bb12faa5beef975841617c847cd6b336f877987753822020b92c4cc97055c9b1e0b128bf11f505005b6ab0e627795a20609efa991e598b80f37b1c6a1c3a1e9aee7028f77570ab2139128a00108c50eb305cdb8f9a603a6b078413f6f9b14c6d82b5199ce59d887902a281a027b717495fe12672a127bbf9b256c43720d7c160b281c12757da135b1933352be4ab67e40248afc318e2370c3b8208e695bdf337459b9acbfe5b487f76e9b4b4001d6cf90ca8c699a174d42972dc733f33389fdf59a1daba81d834955027334185ad02c76cf294846ca9294ba0ed66741ddec791cab34196ac5657c5a78321b56c33306b5102397a5c09c3508f76b48282459f81d0c72a43f737bc2f12f45422628b67db51ac1424276a6c08c3f7615665bbb8e928148a270f991bcf365a90f87c30687b68809c91f231813b866bea82e30374d80aa0c02973437498a53b14bf6b6ca1ed76ab8a20d54a083f4a26b7c038d81967640c20bf4431e71dacce8577b21240e494c31f2d877daf4924fd39d82d6167fbcc1f9c5a259f843e30987ccc4bce7493a2404b5e44387f707425781b743fb555685584e2557cc038b1a9b3f4043121f5472eb2b96e5941fec011ceea50791636c6abc26c1377ee3b5146fc7c85cb335b1e795eec2033ee44b9aa90685245ef7b4436c000e66bc8bcbf1cdb803ac1421b1fdb266d5291c8310373a8a3ce9562ab197953871ab99f382cc5aa9c0f273d1dca55d2712853871e1a83cb3b85450f76d3f3c42bab5505f7212fdb6b8b7f6029972a8f3751e4c94c1108b02d6ac79f8d938f05a1b2c229b14b42b31b01a364017e59578c6b033833774cb9b570f9086b722903b375446b495d8a29bf80751877a80fb724a0210c3e1692f397c2f1ddc2e6ba17af81b92acfabef5f7573cb493d184027b718238c89a3549b8905b28a83362867c082d3019d3ca70700731ceb73e8472c1a3a093361c5fea6a7d40955d07a41b64e50081a361b604cc518447c8e25765ab7d68b243275207af8ca6564a4cb1e94199dba1878c59bec809ab48b2f211badc6a1998d9c7227c1303f469d46a9c7e5303f98aba67569ae8227c16ba1fb3244466a25e7f823671810cc26206feb29c7e2a1a91959eeb03a98252a4f7412674eb9a4b277e1f2595fca64033b41b40330812e9735b7c607501cd8183a22afc3392553744f33c4d202526945c6d78a60e201a16987a6fa59d94464b56506556784824a07058f57320e76c825b9347f2936f4a0e5cdaa18cf8833945ae312a36b5f5a3810aac82381fdae4cb9c6831d8eb8abab850416443d739086b1c326fc2a3975704e396a59680c3b5f360f5480d2b62169cd94ca71b37bc5878ba2985e068ba050b2ce50726d4b4451b77aaa8676eae094982210192197b1e92a27f59868b78867887b9a70c32af84630aa908814379e6519150ba16439b5e2b0603d06aa6674557f5b0983e5cb6a97596069b01bb3128c416680657204fd07640392e16b19f337a99a304844e1aa474e9c799062971f672268960f5a82f950070bbe9c2a71950a3785bdf0b8440255ed63928d257845168b1eccc4191325aa76645719b28ebd89302dc6723c786df5217b243099ca78238e57e64692f206b177abc259660395cd7860fb35a16f6b2fe6548c85ab66330c517fa74cdf3cb49d26b1181901af775a1e180813b6a24c456829b5c38104ece43c76a437a6a33b6fc6c5e65c8a89466c1425485b29b9e1854368afca353e143d0a90a6c6c9e7fdb62a606856b5614f12b64b796020c3534c3605cfdc73b86714f411850228a28b8f4b49e663416c84f7e381f6af1071343bf9d39b45439240cc03897295fea080b14bb2d8119a880e164495c61bebc7139c11857c85e1750338d6343913706a507c9566464cd2837cf914d1a3c35e89b235c6ab7ed078bed234757c02ef6993d4a273cb8150528da4d76708177e9425546c83e147039766603b30da6268f4598a53194240a2832a3d67533b5056f9aaac61b4b17b9a2693aa0d58891e6cc56cdd772410900c405af20b903797c64876915c37b8487a1449ce924cd345c29a36e08238f7a157cc7e516ab5ba73c8063f726bb5a0a0319e57127438c7fc601c99ccaae4c1a83726fdcb5045ed1a82a985ea995396d77272c66ce493289f6110910f37c2741ce47026a6f8261999c6482572b1693912ef12eebea7acf9234fb409f2a6090e6b0bfd895469d0b2a921bb723f87a33ea5465ab90f514b67698c0768b6ca498b022c512fa0875f054aa2265867e31c0e522651e024a07d60dd9f633166921f4126bc2b6aa01cc15a09b85bff8218c5aae95bc1ffb26ae5a137670f04910ca9d7241b6660c394c5455917746a26682fb71a432ea9530e839bdeb07433004f45a0ddaa0b24e3a566a540815f281e3fc259ac6cbc0acb8d62268b603bc676ab415c474bb94873e4487ae31a4e3845c79901550890ee8784eef904fee62ba8c5f952c68413052e0a7e3388bb8ff0ad602ae3ea14d9df6dd5e4cc6a381a41da5c137ecc49df587e178eaf47702ec623780691a3233f69f12bd9c9b9637c51378ad71a831055277254cc63c5ad4cb76b4ab82e5fca135e8d26a6b3a89fa5b6f'), + c=unhex('74a26c7d27146a22c7eab420134e973799cec1da2df61ae0fa7905a3a47485a063076bfa22d6e4fe5059de0a32e38f11abd63f990e91bd0e3a5bc6e710dfe5dc0f6d4a18147ebc2e2d9b179374d83692c53efbd45f28a2a928c2494f903576c410eb1773895ebeadb119960eebda9c3c710795a6d9b781fc58b30d08107f4e20944a382afb079f31d21724f2c26e6a53412f0a908be7586f2b3d6d7c1dea0270e98aa209244bd88ed68aae01432342ba5f49e015cb476b5b78d15ea77a354cc9e9fd07137d8760be42fd4746c62c02028e7b405ddc95df3d021921cfeddb3d961b957eca302a263dab2dc117beb3e79efacfcf936dfc09fc0d19c358d724fa381ea06ca067c384e944302c3907ab15a1da4b41352692add59b061541f07eff25ec42f46e1a0e370cad06ff3fd997d4d2c5648af762231b382d0593401936cba21551a2ae30d8e8effcf43916b83138bb5e610364429879fa9cdd5b7d3cf2feabaa1dc8d50ce69402e21103e795df7074d1fcf65f8a4e18986d5417780602c63be5a044863384bd3d8ffb685eac567ed8349dcf2ceb702b7375b145729998049d13e2cd466cf2231b9d3a20018ee908f8514a6c6a89df7232f91fcd84b81ebc8bc539e9a37a4324755564be1bf4fa1fb4571e0abbc9b52f9d090c33be599de6c8532c7cb7ec8b4e2d3c07505280e99923865903ffd18bc13b9c8164aa1eae84e38d3f57fdb8801785f105a6a8574bd2fe9bf305848e525330bc2d24f0257e47a4950f433a9233e8cdeba81dbae7d8c1a06d01f70de6ef663207d84952827bab3d451cbea0990007fbdb4240fe899a706f7c1563e05c70be9d575189ef83e0cf76195f6652491cce04f1ce2092170a92e0dd7301246a4c44fc0b4ee6aaa63fc7027840abd2ec25f654589738cd38b9e10b975cfb6c1d2eb4da97736998f84fdddd810d72da3c5ab13507420ddbfaa4f7750c1fae9c7dfb30f40a12aea689fc78da900020e3abb32a364d5c6b3c7544a1b5734a41e95c8314b448cd0b738d829af772a8f81c51adba2d85f326c8f5d6961cf12d44a9bedea00d1df5b48f429b1ce0c15ea5f5bc10b017247ba2c6be922b0563b8e9698677cb6c45ccf2081bf84219d2904c11ff92199f8aefad62d8608e200802c5a07202cc820e9e520e31bf36a83002eca4018b0b3a398801562aa86c77ab0d50a8fbc3768b0a643b97e7f9072168de29b8175999c9aa48d301a3f0303172e9c7d4f16329d5ca9d42397c3982e10c9da42de88bd6c2ab91c1e71e778e58bb8f801f207a88a9b47f9c687afbba34eda6d2899e4fa0008aa2b539711753dc7c07f614e814f683d6c037562ae1fbbe6d7d5fa54b7a6d9451e11b01aaccc3bf2ed64742dd100e0eab2df6cccf937b6d5981eca0e01f3245cf26a72ad1adf066c8f5430d72f509963a657d85e554c14e26e8bec5d5f3ab998c9b29f16b04747d80749b30e51fd2a7f690c22f9986aaf6358d6fab8ded54971b32641de2b258590eeaa6bf1f32324a7c4c983f49466d86'), + k_expected=unhex('3d23b10df232a180786f61261e85278251746580bebca6acbad60aef6952be69')) + # Decaps test from test group 6, test case 97 (reject) + decaps_test('mlkem1024', + dk=unhex('8445c336f3518b298163dcbb6357597983ca2e873dcb49610cf52f14dbcb947c1f3ee9266967276b0c576cf7c30ee6b93dea5118676cbee1b1d4794206fb369aba41167b4393855c84eba8f32373c05bae7631c802744aadb6c2de41250c494315230b52826c34587cb21b183b49b2a5ac04921ac6bfac1b24a4b37a93a4b168cce7591be6111f476260f2762959f5c1640118c2423772e2ad03dc7168a38c6dd39f5f7254264280c8bc10b914168070472fa880acb8601a8a0837f25fe194687cd68b7de2340f036dad891d38d1b0ce9c2633355cf57b50b896036fca260d2669f85bac79714fdafb41ef80b8c30264c31386ae60b05faa542a26b41eb85f67068f088034ff67aa2e815aab8bca6bf71f70ecc3cbcbc45ef701fcd542bd21c7b09568f369c669f396473844fba14957f51974d852b978014603a210c019036287008994f21255b25099ad82aa132438963b2c0a47cdf5f32ba46b76c7a6559f18bfd555b762e487b6ac992fe20e283ca0b3f6164496955995c3b28a57bbc29826f06fb38b253470af631bc46c3a8f9ce824321985dd01c05f69b824f916633b40654c75aaeb9385576ffde2990a6b0a3be829d6d84e34f1780589c79204c63c798f55d23187e461d48c21e5c047e535b19f458bba1345b9e41e0cb4a9c2d8c40b490a3babc553b3026b1672d28cbc8b498a3a99579a832feae74610f0b6250cc333e9493eb1621ed34aa4ab175f2ca231152509acb6ac86b20f6b39108439e5ec12d465a0fef35003e14277a21812146b2544716d6ab82d1b0726c27a98d589ebdacc4c54ba77b2498f217e14e34e66025a2a143a992520a61c0672cc9cced7c9450c683e90a3e4651db623a6db39ac26125b7fc1986d7b0493b8b72de7707dc20bbdd43713156af7d9430ef45399663c2202739168692dd657545b056d9c92385a7f414b34b90c7960d57b35ba7dde7b81fca0119d741b12780926018fe4c8030bf038e18b4fa33743d0d3c846417e9d5915c246315938b1e233614501d026959551258b233230d428b181b132f1d0b026067ba816999bc0cd6b547e548b63c9eaa091bac493dc598dbc2b0e146a2591c2a8c009dd5170aae027c541a1b5e66e45c65612984c46770493ec896ef25aa9305e9f06692cd0b2f06962e205bebe113a34ebb1a4830a9b3749641bb935007b23b24bfe576956254d7a35aa496ac446c67a7fec85a60057e8580617bcb3fad15c76440fed54cc789394fea24452cc6b0585b7eb0a88bba9500d9800e6241afeb523b55a96a535151d1049573206e59c7feb070966823634f77d5f1291755a243119621af8084ab7ac1e22a0568c6201417cbe3655d8a08dd5b513884c98d5a493fd49382ea41860f133ccd601e885966426a2b1f23d42d82e24582d99725192c21777467b1457b1dd429a0c41a5c3d704cea06278c59941b438c62727097809b4530dbe837ea396b6d31077fad3733053989a8442aac4255cb163b8ca2f27501ea967305695abd659aa02c83ee60bb574203e9937ae1c621c8ecb5cc1d21d556960b5b9161ea96fffebac72e1b8a6154fc4d88b56c04741f090cbb156a737c9e6a22ba8ac704bc304f8e17e5ea845fde59fbf788cce0b97c8761f89a242f3052583c6844a632031c964a6c4a85a128a28619ba1bb3d1bea4b49841fc847614a066841f52ed0eb8ae0b8b096e92b8195405815b231266f36b18c1a53333dab95d2a9a374b5478a4a41fb8759957c9ab22cae545ab544ba8dd05b83f3a613a2437adb073a9635cb4bbc965fb454cf27b298a40cd0da3b8f9ca99d8cb4286c5eb476416796070ba535aaa58cdb451cd6db5cbb0ca20f0c71de97c30da97ec7906d06b4b939396028c46ba0e7a865bc8308a3810f1212006339f7bc169b1666fdf475911bbc8aaab41755c9a8aabfa23c0e37f84fe46999e030494b9298ef9934e8a649c0a5cce2b22f31809afed23955d87881d99fc1d352896cac9055bea0d016ccba7805a3a50e221630379bd01135221cad5d9517c8cc42637b9fc0718e9a9bb4945c72d8d11d3d659d83a3c419509af5b470dd89b7f3accf5f35cfc322115fd66a5cd2875651326f9b3168913be5b9c87ae0b025ec7a2f4a072750946ac61170a7826d9704c5a23a1c0a2325146c3bc1858826c6b39279c2da7438a370ed8a0aa5169e3bec29ed88478732758d454143e227f8595883297842e6af133b17e4811b0f5713ac73b7e347423eb92822d2306fa14500a7207a0672672046544acc4ea9c16ed7421a069e0d737a98628519c6a29a424a868b46d9a0cc7c6c9ddd8b8bcbf422c8f48a73143d5abb66bc55499418430802bac544463cc7319d17998f29411365766d04c847f3129d9077b7d8339bfb96a6739c3f6b74a8f05f9138ab2fe37acb57634d1820b50176f5a0b6bc2940f1d5938f1936b5f95828b92eb72973c1590aeb7a552ceca10b00c303b7c75d402071a79e2c810af7c745e3336712492a42043f2903a37c6434cee20b1d159b057699ff9c1d3bd68029839a08f43e6c1c819913532f911dd370c7021488e11cb504cb9c70570fff35b4b4601191dc1ad9e6adc5fa9618798d7cc860c87a939e4ccf8533632268cf1a51aff0cb811c5545cb1656e65269477430699ccdea3800630b78cd5810334ccf02e013f3b80244e70acdb060bbe7a553b063456b2ea807473413165ce57dd563473cfbc90618ade1f0b888aa48e722bb2751858fe19687442a48e7ca0d2a29cd51bfd8f78c17b9660bfb54a470b2ae9a955c6ab8d6e5cc92ac8ed3c185daa8bc29f0578ebb812b97c9e5a848a6384de4e75a31470b53066a8d027ba44b21749c0492465f9072b28376c4e290b30c1863f9e5b79996083422bd8c272c10ecc6eb9a0a8225b31aa0a66e35b9c0b9a79582ba20a3c04cd29914f083a0158288ba4d6eb62d87264b912bca39732fbde536a377ad02b8c835d4a2f4e7b1ce115d0c860beaa7955a49ad689586a89a2b9f9b10d1595d2fc065ad018a7d56c614471f8e946fe8ab49e8226591119fcadb4f9a861631378736b6688b782d58e97e4572753a9664b6b8536812b25911aa76a242375433192738eee762f6b84315bb3436231e0a9b277ed28ae0050728346457e13405062db2804b8da60bb5c793d4cc0e101cba2d9182fd7124ff52bf4ca28292ac26d678088953971dba0b6fec2c9659353291c70c5b9245a0ca253304afd3c95102bea66875c6201680b4bda38687b648c28eb37478e3bc00ca8a3cc27204642b42b68fcbe7b21a366d0668a5029a7deef94cdd6a95d7ea8931673bf7112d4042107b1b8b9700c974f9c4e83a8facd89bfe0ca3cc4c2fce80a03d3576c222a792b72b1f070ab7f6b6f2b5ca2af5054afa70a896990159b45d1003e2a05648675e596016f1b71dd0f7bda7e2097fc73b3a143d12c726020ac34958ad7062b92b9abf3ca6be5ae29f57135e625a367971837e6363d1532094e022a23467cf932e1f89b5b0803c1ec99b585a78b5865096746f32258214ecb38065c97f455e155acc2dd005a9c76bed59cda73837d303504e6c976a606a2be7bbec5948b91a349e8936688cc0279754b743abc58666b19b6c3260051f19206bb962bb6633eb0048e32baacc5b020d02c86ca9770ad469db54a106ac73a35b8057422b3db202c5a5b4e3d535f0fc99326c4b8b7b16f1cb5af96803fa8c195fc0bceddaaf012a51728b76489082373c91e92c87acca795160782e3b0dd643544bb96abc2708d49b759cf057aa223bafd96a330baf39810fe8671b4343c297da1e1969c996216ab5106da668941b160d4477017136cbca5b5a8d44c4a8b1cf3ef79785e5aa25c3a1ad6c24fd140f79207de5a499f8a1534ffa804aa7b3889cbe25c0414704aa57897f17862364eca56258007248813912b836497f0359c2f7238a05d305a0ea152e72b44417a868134e91b3ca7931232fd4c25f8c2a492a339cdc0a138967211451f2562678fa14080a34436c42b07865ac036a81e97a7787a938025caf813450368bed0c94b1857604526405d27a1c1abc81b5b6ec13c71930a97d9232cf7021ef87a4d155328e62b583a83b4af21f9f5750f8575150424f63b899d71cad267c09e4467146e16e9b6c653f008c311375e2e006d4076a546b82f5314222f7c654317e79ec6035b73faf491757e61c828326d53044541c4d4537abd3ea1e67998c3382974ca78ae1b1960e4a9226b0219ab070f0d7aa66d76f9316adb80c54d6499771b471e8168d47bcaa08324ab6ba92c3a70275f24fa4dc10e251633fb98d162bb5537202c6a553ce7841c4d40b873b85ca03a0a1e1cfade6ba5180ab1323ccba9a3e9c53d37575ab1fd9e7316c6feecb0a14df6f2da56c2f56f55a89635cfcfda47927af1f0a47b2d4e4e61634b1b51d37a3a307a972420de1b7a481b83e583b6af16f63cb00c6'), + c=unhex('4f90106ff7c3dc4e47417f31ab56b1c5e426c1ecd5878aad2b705e75062da5fa6f4d18b704c941c6c6d941fd21191a69210bc39e24950d9f851b6de8ce30023dc7536439104d42245f3e04e6aa6763f8ac97adbd04cc69547bce0bf290ffb5d12946301174af1b0868c14d4293fa9dcc5b23f809b02cc78defe7f27935b9b681e531fc21ccb2af8ef6144d8498e63e0ee48af8d4cef7ac1f669ac740b06f79ddb58e794f2fc2ca832e05a0374c18a4f2cc78343eea064abc5f468f4dd11e0b6e8fa1d18a221d8241450c05eb9edf90d9d7f666ac82e7fd44af9328e0bc6004d5b114e80e9b980d18e081d771dfcb2acfd40142a2eb33234f75733eab7d8ee8a5a6f796681a4a8af85cce86971b821d4ad8371049e94e280b77b15d111a42aeadfc08d4f804bd78885443e81a393df7c8754c460915846e09a0596587460038f55d06ec21434a1c2df44d0c16706e8d2b83f0e7833976ef05bf1d9f0ddc9a37597e401b817c2bec8e02eb9df7591e239f25f8648e7f2f4f673093bd9cb703da32b353f58514c6ab55748b194e52f153d52f5f33fe95c5f9f65ea97ba721e8ddf333b64d233a867a12701e00c5d8a9b5ae344f3d847c27c079dcc9c3b40ec4604a9f041e7987e8b930c658b9a132de4e422c0e27553a2a0eab8c859eb0e5677e83272725c5c1652e61b9bbf5c9c59bc2357a4d1db9c607f34dc1ba074b84dfc69e4097a7ad2ba9a58000027296ad39fc1ce218a5eec7adfa8aa3b9100b0b603cfc83c152589e12e6bd9ee10c49131a701d315dfec38e018328916f9ffaa7305cfb66781707d2d1020eb782f9f003db4e46b87d693f62e8bde170141ff71f26ddf5310c00c9163655f5217dd2c8b0466ac89db55bd7fb3b0964bc9009e9686185117dcb50d6d0297753cf7f1217e819ee60e3f0faec4a5af0c2ea83ccde15cf045c6961de8ff6235c9d93ba4c89b7a82a7471fcfb0b8ead54d56e8a1de21b3933ac5b4a0689eef3598926e17bbb16aec61ec30a2ccc0e0323ec282887c108c3a4e83e3666493d8653d0e92443808c79d770bff48a49e65ae089fec790bba4c66354ef67a334c1ea5c6c5707b6928ebd1bdb6a940fa242c6ebd7f3e71272421c9082841a6cad2894bb8ac85f105d8bbc9e6f0a3df0d7c46f6e2f4cab904ed157afa85d4a852220a9636e1e8821643a9e4028d87a430432f09354b3973182385cf5abfc8f84982bee0bcbf5d18637399163a09eb45711e07c4458498c76979107cf91b3fc590ea4ad715d656d5e56dc32146580101c952e02ed7017960d54caaccc70607196980adbdaea420a52c0559ed23c9514f8ca7ab7f3baafd2fab58960a64128d5a50e9ad8db7d23a90ce64c1bc349d118d3603358377f84ff5a64457fa1cf41b27094bca72360bd429415b9ef9accb7a5d7b9e5f5fdca8fcfa4592e91d7e5120df7e3c6675af2211bb94d856a5d2285fbbb36984a1345590930b13232565d54812a9345324c232653190323cc67c840e478d09e6ddbcf999f7aa3b556f80332e67aca41ec0661088d7696bb64e9a98a0749faa9854d9b48754023bacaf3c8081a46157c6453bdc89341d3092f3b5337874ce5de559a56a2ffb7f401f6e28eecaf4fde5b60dea73d6b2182ef68e07a8297f3c959e17139b5dedc72c7a0e103aff866e89d1f62a1f6b97b61bc059bde5a2a06087ef783a441f23dd191c692d03c097ff9ee831f7715c6e508bf475e79a8353e84b06a9356045c8fd09fba35879069b9a3f478fbd051143c13d753bc45f3040e85985efd6b149efa9455a18e2894e6ea0be58f451ff1156f93cc7117b5d091e9dd50d41bfccd44f2c4eb7812aefd13c8b68d7f0103bb6ca38d233b6aadd01845b7e44d13c1cb1577d6c4354b063991344787f8c0be667a7440b98917ad64cc2ef2bc82efc3398b3b1b238540756ce9fc5edd26cc20e761d592a1a0530aa8befcfe8dadbac99a417ca0827f4983ff5be656669f2b5f985ff6b16c44bbea131d1fcc70fc53bf31ef225d1f5d41863b51b57ea65c6164f7531ae492efa64161b7daba3ef4586f3459be8a962367dc276597b98e91ff594efe8849bad4cf91b9e5f244cf03ca9615be128e96958533544a56e735994b92e4ef0d5fab54b78ec66641c7463f225d261c144f00a0270741d7a511994833635a8a9b670cbfbef239bf83327e247943b205da68db94e3f3'), + k_expected=unhex('7545cc458e0a274a83b13554224f0bd01d57cc4775ad12468d3fee5b08c93a6a')) + if __name__ == "__main__": # Run the tests, suppressing automatic sys.exit and collecting the # unittest.TestProgram instance returned by unittest.main instead. diff --git a/test/sclog/CMakeLists.txt b/test/sclog/CMakeLists.txt index 8a8ac570..9f9f75f9 100644 --- a/test/sclog/CMakeLists.txt +++ b/test/sclog/CMakeLists.txt @@ -2,7 +2,7 @@ # goes with the PuTTY test binary 'testsc'. For build instructions see # the comment at the top of testsc.c. -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.7...3.28) project(sclog LANGUAGES C) diff --git a/test/test_lineedit.c b/test/test_lineedit.c index fe0be162..3ad037fb 100644 --- a/test/test_lineedit.c +++ b/test/test_lineedit.c @@ -527,6 +527,65 @@ static void test_edit(Mock *mk, bool echo) EXPECT(mk, specials, 1, SS_EOF, 0); reset(mk); + /* ^M with the special flag is the Return key, and sends the line */ + ldisc_send(mk->ldisc, "abc", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0D", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D\x0A")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x0D\x0A")); + reset(mk); + + /* In non-LE_CRLF_NEWLINE mode, either of ^M or ^J without the + * special flag also sends the line */ + conf_set_int(mk->conf, CONF_protocol, PROT_SSH); + ldisc_configure(mk->ldisc, mk->conf); + ldisc_send(mk->ldisc, "abc", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0D", 1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x0D\x0A")); + reset(mk); + ldisc_send(mk->ldisc, "abc", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0A", 1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x0D\x0A")); + reset(mk); + + /* In LE_CRLF_NEWLINE mode, non-special ^J is just literal */ + conf_set_int(mk->conf, CONF_protocol, PROT_RAW); + ldisc_configure(mk->ldisc, mk->conf); + ldisc_send(mk->ldisc, "abc", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0A", 1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc^J")); + /* So when we press Return it's sent */ + ldisc_send(mk->ldisc, "\x0D", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0A\x0D\x0A")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc^J\x0D\x0A")); + reset(mk); + + /* In LE_CRLF_NEWLINE mode, non-special ^M is literal, but if + * followed with ^J, they combine into a Return */ + conf_set_int(mk->conf, CONF_protocol, PROT_RAW); + ldisc_configure(mk->ldisc, mk->conf); + ldisc_send(mk->ldisc, "abc", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0D", 1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc^M")); + /* So when we press Return it's sent */ + ldisc_send(mk->ldisc, "\x0A", 1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D\x0A")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc^M\x08 \x08\x08 \x08\x0D\x0A")); + reset(mk); + /* ^R redraws the current line, after printing "^R" at the end of * the previous attempt to make it clear that that's what * happened */ diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index 4e93c786..586be9fa 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -160,6 +160,12 @@ BEGIN_ENUM_TYPE(argon2flavour) ENUM_VALUE("Argon2id", Argon2id) END_ENUM_TYPE(argon2flavour) +BEGIN_ENUM_TYPE(mlkem_params) + ENUM_VALUE("mlkem512", &mlkem_params_512) + ENUM_VALUE("mlkem768", &mlkem_params_768) + ENUM_VALUE("mlkem1024", &mlkem_params_1024) +END_ENUM_TYPE(mlkem_params) + BEGIN_ENUM_TYPE(fptype) ENUM_VALUE("md5", SSH_FPTYPE_MD5) ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 05121458..d1ca3f5c 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -264,6 +264,10 @@ FUNC(void, ssh_hash_update, ARG(val_hash, h), ARG(val_string_ptrlen, data)) FUNC(opt_val_hash, blake2b_new_general, ARG(uint, hashlen)) +FUNC(val_shakexof, shake128_xof_from_input, ARG(val_string_ptrlen, input)) +FUNC(val_shakexof, shake256_xof_from_input, ARG(val_string_ptrlen, input)) +FUNC_WRAPPED(val_string, shake_xof_read, ARG(val_shakexof, xof), ARG(uint, size)) + /* * The ssh2_mac abstraction. Note the optional ssh_cipher parameter * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so @@ -401,6 +405,36 @@ FUNC_WRAPPED(int16_list, ntru_encrypt, ARG(int16_list, plaintext), FUNC_WRAPPED(int16_list, ntru_decrypt, ARG(int16_list, ciphertext), ARG(val_ntrukeypair, keypair)) +/* + * ML-KEM and its subroutines. + */ +FUNC(void, mlkem_keygen, + ARG(out_val_string_binarysink, ek), ARG(out_val_string_binarysink, dk), + ARG(mlkem_params, params)) +FUNC_WRAPPED(void, mlkem_keygen_internal, + ARG(out_val_string_binarysink, ek), + ARG(out_val_string_binarysink, dk), + ARG(mlkem_params, params), + ARG(val_string_ptrlen, d), ARG(val_string_ptrlen, z)) +FUNC_WRAPPED(void, mlkem_keygen_rho_sigma, + ARG(out_val_string_binarysink, ek), + ARG(out_val_string_binarysink, dk), + ARG(mlkem_params, params), ARG(val_string_ptrlen, rho), + ARG(val_string_ptrlen, sigma), ARG(val_string_ptrlen, z)) +FUNC(boolean, mlkem_encaps, + ARG(out_val_string_binarysink, ciphertext), + ARG(out_val_string_binarysink, k), + ARG(mlkem_params, params), + ARG(val_string_ptrlen, ek)) +FUNC_WRAPPED(boolean, mlkem_encaps_internal, + ARG(out_val_string_binarysink, ciphertext), + ARG(out_val_string_binarysink, k), + ARG(mlkem_params, params), + ARG(val_string_ptrlen, ek), ARG(val_string_ptrlen, m)) +FUNC(boolean, mlkem_decaps, ARG(out_val_string_binarysink, k), + ARG(mlkem_params, params), ARG(val_string_ptrlen, dk), + ARG(val_string_ptrlen, ciphertext)) + /* * RSA key exchange, and also the BinarySource get function * get_ssh1_rsa_priv_agent, which is a convenient way to make an diff --git a/test/testcrypt.c b/test/testcrypt.c index 3b495bba..fa3df196 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -36,6 +36,7 @@ #include "mpint.h" #include "crypto/ecc.h" #include "crypto/ntru.h" +#include "crypto/mlkem.h" #include "proxy/cproxy.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) @@ -99,6 +100,7 @@ uint64_t prng_reseed_time_ms(void) X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ X(ntrukeypair, NTRUKeyPair *, ntru_keypair_free(v)) \ X(ntruencodeschedule, NTRUEncodeSchedule *, ntru_encode_schedule_free(v)) \ + X(shakexof, ShakeXOF *, shake_xof_free(v)) \ /* end of list */ typedef struct Value Value; @@ -230,6 +232,7 @@ typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; typedef HttpDigestHash TD_httpdigesthash; +typedef const mlkem_params *TD_mlkem_params; #define BEGIN_ENUM_TYPE(name) \ static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \ @@ -443,12 +446,19 @@ static unsigned *get_out_uint(BinarySource *in) return uval; } -static BinarySink *get_out_val_string_binarysink(BinarySource *in) +static strbuf **get_out_val_string(BinarySource *in) { Value *val = value_new(VT_string); - val->vu_string = strbuf_new(); + val->vu_string = NULL; add_finaliser(finaliser_return_value, val); - return BinarySink_UPCAST(val->vu_string); + return &val->vu_string; +} + +static BinarySink *get_out_val_string_binarysink(BinarySource *in) +{ + strbuf *sb = strbuf_new(); + *get_out_val_string(in) = sb; + return BinarySink_UPCAST(sb); } static void return_val_string_asciz_const(strbuf *out, const char *s); @@ -743,6 +753,14 @@ strbuf *ssh_hash_final_wrapper(ssh_hash *h) return sb; } +strbuf *shake_xof_read_wrapper(ShakeXOF *sx, TD_uint size) +{ + strbuf *sb = strbuf_new(); + void *p = strbuf_append(sb, size); + shake_xof_read(sx, p, size); + return sb; +} + void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) { if (iv.len != ssh_cipher_alg(c)->blksize) @@ -1022,6 +1040,33 @@ int16_list *ntru_decrypt_wrapper(int16_list *ciphertext, NTRUKeyPair *keypair) return out; } +void mlkem_keygen_internal_wrapper( + BinarySink *ek, BinarySink *dk, const mlkem_params *params, + ptrlen d, ptrlen z) +{ + assert(d.len == 32 && "Invalid d length"); + assert(z.len == 32 && "Invalid z length"); + mlkem_keygen_internal(ek, dk, params, d.ptr, z.ptr); +} + +void mlkem_keygen_rho_sigma_wrapper( + BinarySink *ek, BinarySink *dk, const mlkem_params *params, + ptrlen rho, ptrlen sigma, ptrlen z) +{ + assert(rho.len == 32 && "Invalid rho length"); + assert(sigma.len == 32 && "Invalid sigma length"); + assert(z.len == 32 && "Invalid z length"); + mlkem_keygen_rho_sigma(ek, dk, params, rho.ptr, sigma.ptr, z.ptr); +} + +bool mlkem_encaps_internal_wrapper(BinarySink *ciphertext, BinarySink *kout, + const mlkem_params *params, ptrlen ek, + ptrlen m) +{ + assert(m.len == 32 && "Invalid m length"); + return mlkem_encaps_internal(ciphertext, kout, params, ek, m.ptr); +} + strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) { /* Fold the boolean return value in C into the string return value @@ -1639,6 +1684,8 @@ int main(int argc, char **argv) const char *infile = NULL, *outfile = NULL; bool doing_opts = true; + enable_dit(); /* in case this is used as a crypto helper (Hyrum's Law) */ + while (--argc > 0) { char *p = *++argv; diff --git a/test/testcrypt.py b/test/testcrypt.py index 217dbf35..a71bd394 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -182,7 +182,7 @@ def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): if typename in { "hashalg", "macalg", "keyalg", "cipheralg", "dh_group", "ecdh_alg", "rsaorder", "primegenpolicy", - "argon2flavour", "fptype", "httpdigesthash"}: + "argon2flavour", "fptype", "httpdigesthash", "mlkem_params"}: arg = coerce_to_bytes(arg) if isinstance(arg, bytes) and b" " not in arg: dictkey = (typename, arg) diff --git a/test/testsc.c b/test/testsc.c index 00421a67..d38dd639 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -82,6 +82,7 @@ #include "mpint.h" #include "crypto/ecc.h" #include "crypto/ntru.h" +#include "crypto/mlkem.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { @@ -431,6 +432,9 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(argon2) \ X(primegen_probabilistic) \ X(ntru) \ + X(mlkem512) \ + X(mlkem768) \ + X(mlkem1024) \ X(rfc6979_setup) \ X(rfc6979_attempt) \ /* end of list */ @@ -1745,6 +1749,60 @@ static void test_ntru(void) strbuf_free(buffer); } +static void test_mlkem(const mlkem_params *params) +{ + char rho[32], sigma[32], z[32], m[32], ek[1568], dk[3168], c[1568]; + char k[32], k2[32]; + + /* rho is a random but public value, so side channels are allowed + * to reveal it (and undoubtedly will). So we don't vary it + * between runs. */ + random_read(rho, 32); + + for (size_t i = 0; i < looplimit(32); i++) { + random_advance_counter(); + random_read(sigma, 32); + random_read(z, 32); + random_read(m, 32); + + log_start(); + + /* Every other iteration, tamper with the ciphertext so that + * implicit rejection occurs, because we need to test that + * that too is done in constant time. */ + unsigned tampering = i & 1; + + buffer_sink ek_sink[1]; buffer_sink_init(ek_sink, ek, sizeof(ek)); + buffer_sink dk_sink[1]; buffer_sink_init(dk_sink, dk, sizeof(dk)); + buffer_sink c_sink[1]; buffer_sink_init(c_sink, c, sizeof(c)); + buffer_sink k_sink[1]; buffer_sink_init(k_sink, k, sizeof(k)); + mlkem_keygen_rho_sigma( + BinarySink_UPCAST(ek_sink), BinarySink_UPCAST(dk_sink), + params, rho, sigma, z); + ptrlen ek_pl = make_ptrlen(ek, ek_sink->out - ek); + ptrlen dk_pl = make_ptrlen(dk, dk_sink->out - dk); + mlkem_encaps_internal( + BinarySink_UPCAST(c_sink), BinarySink_UPCAST(k_sink), + params, ek_pl, m); + dk[0] ^= tampering; + ptrlen c_pl = make_ptrlen(c, c_sink->out - c); + buffer_sink_init(k_sink, k2, sizeof(k2)); + bool success = mlkem_decaps( + BinarySink_UPCAST(k_sink), params, dk_pl, c_pl); + + log_end(); + + assert(success); + unsigned eq_expected = tampering ^ 1; + unsigned eq = smemeq(k, k2, 32); + assert(eq == eq_expected); + } +} + +static void test_mlkem512(void) { test_mlkem(&mlkem_params_512); } +static void test_mlkem768(void) { test_mlkem(&mlkem_params_768); } +static void test_mlkem1024(void) { test_mlkem(&mlkem_params_1024); } + static void test_rfc6979_setup(void) { mp_int *q = mp_new(512); @@ -1821,6 +1879,11 @@ int main(int argc, char **argv) bool keep_outfiles = false; bool test_names_given = false; + /* One day, perhaps, if I ever get this test to work on Arm, we + * might actually _check_ DIT is enabled, and check we're sticking + * to the precise list of DIT-affected instructions */ + enable_dit(); + memset(tests_to_run, 1, sizeof(tests_to_run)); random_hash = ssh_hash_new(&ssh_sha256); diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 4d8ef964..87ee6574 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -147,6 +147,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/no-console.c + ${CMAKE_SOURCE_DIR}/stubs/no-dit.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(pterm pterm) @@ -163,6 +164,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/no-console.c + ${CMAKE_SOURCE_DIR}/stubs/no-dit.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(ptermapp pterm) @@ -204,6 +206,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c + ${CMAKE_SOURCE_DIR}/stubs/no-dit.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) diff --git a/unix/askpass.c b/unix/askpass.c index a1143a8f..bdd62519 100644 --- a/unix/askpass.c +++ b/unix/askpass.c @@ -612,6 +612,8 @@ int main(int argc, char **argv) int exitcode; char *ret; + enable_dit(); /* maybe overkill, but we _are_ handling a secret */ + gtk_init(&argc, &argv); if (argc != 2) { diff --git a/unix/dialog.c b/unix/dialog.c index 835ad978..0838ff03 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -1739,6 +1739,9 @@ static void filefont_clicked(GtkButton *button, gpointer data) struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); if (uc->ctrl->type == CTRL_FILESELECT) { + /* + * FIXME: do something about uc->ctrl->fileselect.filter + */ #ifdef USE_GTK_FILE_CHOOSER_DIALOG GtkWidget *filechoose = gtk_file_chooser_dialog_new( uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), @@ -3345,9 +3348,18 @@ static void dlgparam_destroy(GtkWidget *widget, gpointer data) sfree(dp->selparams[i]); } sfree(dp->selparams); + dp->selparams = NULL; } #endif - sfree(dp); + /* + * Instead of freeing dp right now, defer it until we return to + * the GTK main loop. Then if any other last-minute GTK events + * happen while the rest of the widgets are being cleaned up, our + * handlers will still be able to try to look things up in dp. + * (They won't find anything - we've just emptied it - but at + * least they won't crash while trying.) + */ + queue_toplevel_callback(sfree, dp); } static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp, diff --git a/unix/gtkcompat.h b/unix/gtkcompat.h index 2e8e8b88..02768323 100644 --- a/unix/gtkcompat.h +++ b/unix/gtkcompat.h @@ -187,6 +187,7 @@ #endif /* 2.24 */ #if !GTK_CHECK_VERSION(3,0,0) +#define GDK_IS_X11_DISPLAY(display) (1) #define GDK_IS_X11_WINDOW(window) (1) #endif diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c index 3aaf6af7..cb9eeb1f 100644 --- a/unix/main-gtk-application.c +++ b/unix/main-gtk-application.c @@ -78,6 +78,7 @@ I suppose I'll have to look into OS X code signing. #define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" +#include "ssh.h" #include "gtkmisc.h" #include "gtkcompat.h" @@ -294,6 +295,8 @@ int main(int argc, char **argv) { int status; + enable_dit(); + /* Call the function in ux{putty,pterm}.c to do app-type * specific setup */ setup(false); /* false means we are not a one-session process */ diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index a9a897ca..f1e57012 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -31,6 +31,7 @@ #define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" +#include "ssh.h" #include "terminal.h" #include "gtkcompat.h" #include "unifont.h" @@ -594,6 +595,7 @@ int main(int argc, char **argv) bool need_config_box; setlocale(LC_CTYPE, ""); + enable_dit(); /* Call the function in ux{putty,pterm}.c to do app-type * specific setup */ diff --git a/unix/network.c b/unix/network.c index 2ebd76af..00848ab8 100644 --- a/unix/network.c +++ b/unix/network.c @@ -279,7 +279,7 @@ SockAddr *sk_nonamelookup(const char *host) #ifndef NO_IPV6 addr->ais = NULL; #else - ret->addresses = NULL; + addr->addresses = NULL; #endif addr->refcount = 1; return addr; @@ -907,7 +907,7 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, u.sin.sin_addr.s_addr = inet_addr(srcaddr); if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) { /* Override localhost_only with specified listen addr. */ - ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr); + s->localhost_only = ipv4_is_loopback(u.sin.sin_addr); } addr = &u; addrlen = sizeof(u.sin); @@ -1641,8 +1641,8 @@ SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) #ifndef NO_IPV6 addr->ais = NULL; #else - ret->addresses = NULL; - ret->naddresses = 0; + addr->addresses = NULL; + addr->naddresses = 0; #endif addr->refcount = 1; return addr; @@ -1666,8 +1666,8 @@ SockAddr *unix_sock_addr(const char *path) #ifndef NO_IPV6 addr->ais = NULL; #else - ret->addresses = NULL; - ret->naddresses = 0; + addr->addresses = NULL; + addr->naddresses = 0; #endif addr->refcount = 1; return addr; diff --git a/unix/pageant.c b/unix/pageant.c index bd6d6d7f..9ac9a640 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -1313,6 +1313,8 @@ int main(int argc, char **argv) const char *symlink_path = NULL; FILE *logfp = NULL; + enable_dit(); + progname = argv[0]; /* diff --git a/unix/platform.h b/unix/platform.h index 44c7986c..fb3629ee 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -84,10 +84,6 @@ typedef void *HelpCtx; #define NULL_HELPCTX ((HelpCtx)NULL) #define HELPCTX(x) NULL -typedef const char *FILESELECT_FILTER_TYPE; -#define FILTER_KEY_FILES NULL /* FIXME */ -#define FILTER_DYNLIB_FILES NULL /* FIXME */ - /* * Under X, selection data must not be NUL-terminated. */ @@ -398,13 +394,23 @@ void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); void fd_socket_set_psb_prefix(Socket *s, const char *prefix); /* - * Default font setting, which can vary depending on NOT_X_WINDOWS. + * Default font settings. We have a default font for each of + * client-side and server-side, so that we can use one of each as a + * fallback, and we also have a single overall default which goes into + * Conf to populate the initial state of Default Settings. + * + * In the past, this default varied with NOT_X_WINDOWS. But these days + * non-X11 environments like Wayland with only client-side fonts are + * common, and even an X11-capable _build_ of PuTTY is quite likely to + * find out at run time that X11 and its bitmap fonts aren't + * available. Also, a fixed-size bitmap font doesn't play nicely with + * high-DPI displays. And the GTK1 build of PuTTY, which can _only_ + * handle server-side fonts, is legacy. So the default font is + * unconditionally the client-side one. */ -#ifdef NOT_X_WINDOWS -#define DEFAULT_GTK_FONT "client:Monospace 12" -#else -#define DEFAULT_GTK_FONT "server:fixed" -#endif +#define DEFAULT_GTK_CLIENT_FONT "client:Monospace 12" +#define DEFAULT_GTK_SERVER_FONT "server:fixed" +#define DEFAULT_GTK_FONT DEFAULT_GTK_CLIENT_FONT /* * pty.c. diff --git a/unix/plink.c b/unix/plink.c index b4a9749f..d5db2c2a 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -683,6 +683,8 @@ int main(int argc, char **argv) struct winsize size; const struct BackendVtable *backvt; + enable_dit(); + /* * Initialise port and protocol to sensible defaults. (These * will be overridden by more or less anything.) diff --git a/unix/psusan.c b/unix/psusan.c index 4e8693af..268dad25 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -348,6 +348,8 @@ int main(int argc, char **argv) Conf *conf = make_ssh_server_conf(); + enable_dit(); + memset(&ssc, 0, sizeof(ssc)); ssc.application_name = "PSUSAN"; diff --git a/unix/pty.c b/unix/pty.c index c03328f7..2e3ca749 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -1138,8 +1138,6 @@ Backend *pty_backend_create( char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); putenv(x_display_env_var); /* As above, we don't free this. */ - } else { - unsetenv("DISPLAY"); } } #endif diff --git a/unix/putty.c b/unix/putty.c index a96217e3..a1dd78c5 100644 --- a/unix/putty.c +++ b/unix/putty.c @@ -18,6 +18,10 @@ #include "gtkcompat.h" +#ifndef NOT_X_WINDOWS +#include +#endif + /* * Stubs to avoid pty.c needing to be linked in. */ @@ -62,8 +66,11 @@ const bool dup_check_launchable = true; char *platform_get_x_display(void) { const char *display; +#ifndef NOT_X_WINDOWS /* Try to take account of --display and what have you. */ - if (!(display = gdk_get_display())) + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()) || + !(display = gdk_get_display())) +#endif /* fall back to traditional method */ display = getenv("DISPLAY"); return dupstr(display); @@ -80,6 +87,7 @@ const unsigned cmdline_tooltype = void setup(bool single) { sk_init(); + enable_dit(); settings_set_default_protocol(be_default_protocol); /* Find the appropriate default port. */ { diff --git a/unix/sftp.c b/unix/sftp.c index 9fb0bf6e..f2323ddd 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -576,6 +576,7 @@ const bool buildinfo_gtk_relevant = false; */ int main(int argc, char *argv[]) { + enable_dit(); uxsel_init(); CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); return psftp_main(arglist); diff --git a/unix/uppity.c b/unix/uppity.c index 30501af7..5832ed98 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -650,6 +650,8 @@ int main(int argc, char **argv) struct cmdline_instance *ci = &instances[ninstances++]; init_cmdline_instance(ci); + enable_dit(); + if (argc <= 1) { /* * We're going to terminate with an error message below, diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c index c3dc286b..190ef71e 100644 --- a/unix/utils/arm_arch_queries.c +++ b/unix/utils/arm_arch_queries.c @@ -92,6 +92,21 @@ bool platform_sha512_neon_available(void) #endif } +bool platform_dit_available(void) +{ +#if defined HWCAP_DIT + return getauxval(AT_HWCAP) & HWCAP_DIT; +#elif defined HWCAP2_DIT + return getauxval(AT_HWCAP2) & HWCAP2_DIT; +#elif defined __APPLE__ + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_DIT"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; +#else + return false; +#endif +} + #else /* defined __arm__ || defined __aarch64__ */ /* diff --git a/unix/window.c b/unix/window.c index bb772d32..7302dc2c 100644 --- a/unix/window.c +++ b/unix/window.c @@ -411,8 +411,8 @@ StripCtrlChars *gtk_seat_stripctrl_new( static void gtk_seat_notify_remote_exit(Seat *seat); static void gtk_seat_update_specials_menu(Seat *seat); static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status); -static const char *gtk_seat_get_x_display(Seat *seat); #ifndef NOT_X_WINDOWS +static const char *gtk_seat_get_x_display(Seat *seat); static bool gtk_seat_get_windowid(Seat *seat, long *id); #endif static void gtk_seat_set_trust_status(Seat *seat, bool trusted); @@ -439,10 +439,11 @@ static const SeatVtable gtk_seat_vt = { .prompt_descriptions = gtk_seat_prompt_descriptions, .is_utf8 = gtk_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, - .get_x_display = gtk_seat_get_x_display, #ifdef NOT_X_WINDOWS + .get_x_display = nullseat_get_x_display, .get_windowid = nullseat_get_windowid, #else + .get_x_display = gtk_seat_get_x_display, .get_windowid = gtk_seat_get_windowid, #endif .get_window_pixel_size = gtk_seat_get_window_pixel_size, @@ -1927,6 +1928,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->state & GDK_CONTROL_MASK) break; + consumed_meta_key = false; end = 1 + format_small_keypad_key( output+1, inst->term, sk_key, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK, @@ -2546,13 +2548,13 @@ static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) static void compute_whole_window_size(GtkFrontend *inst, int wchars, int hchars, int *wpix, int *hpix); -#endif static void gtkwin_deny_term_resize(void *vctx) { GtkFrontend *inst = (GtkFrontend *)vctx; drawing_area_setup_simple(inst); } +#endif static void gtkwin_timer(void *vctx, unsigned long now) { @@ -4347,12 +4349,14 @@ void modalfatalbox(const char *p, ...) exit(1); } +#ifndef NOT_X_WINDOWS static const char *gtk_seat_get_x_display(Seat *seat) { - return gdk_get_display(); + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) + return gdk_get_display(); + return NULL; } -#ifndef NOT_X_WINDOWS static bool gtk_seat_get_windowid(Seat *seat, long *id) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); @@ -4364,22 +4368,22 @@ static bool gtk_seat_get_windowid(Seat *seat, long *id) } #endif -char *setup_fonts_ucs(GtkFrontend *inst) +char *setup_fonts_ucs(GtkFrontend *inst, Conf *conf) { - bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold); - int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); + bool shadowbold = conf_get_bool(conf, CONF_shadowbold); + int shadowboldoffset = conf_get_int(conf, CONF_shadowboldoffset); FontSpec *fs; unifont *fonts[4]; int i; - fs = conf_get_fontspec(inst->conf, CONF_font); + fs = conf_get_fontspec(conf, CONF_font); fonts[0] = multifont_create(inst->area, fs->name, false, false, shadowboldoffset, shadowbold); if (!fonts[0]) { return dupprintf("unable to load font \"%s\"", fs->name); } - fs = conf_get_fontspec(inst->conf, CONF_boldfont); + fs = conf_get_fontspec(conf, CONF_boldfont); if (shadowbold || !fs->name[0]) { fonts[1] = NULL; } else { @@ -4392,7 +4396,7 @@ char *setup_fonts_ucs(GtkFrontend *inst) } } - fs = conf_get_fontspec(inst->conf, CONF_widefont); + fs = conf_get_fontspec(conf, CONF_widefont); if (fs->name[0]) { fonts[2] = multifont_create(inst->area, fs->name, true, false, shadowboldoffset, shadowbold); @@ -4406,7 +4410,7 @@ char *setup_fonts_ucs(GtkFrontend *inst) fonts[2] = NULL; } - fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); + fs = conf_get_fontspec(conf, CONF_wideboldfont); if (shadowbold || !fs->name[0]) { fonts[3] = NULL; } else { @@ -4857,7 +4861,7 @@ static void after_change_settings_dialog(void *vctx, int retval) conf_get_bool(newconf, CONF_shadowbold) || conf_get_int(oldconf, CONF_shadowboldoffset) != conf_get_int(newconf, CONF_shadowboldoffset)) { - char *errmsg = setup_fonts_ucs(inst); + char *errmsg = setup_fonts_ucs(inst, inst->conf); if (errmsg) { char *msgboxtext = dupprintf("Could not change fonts in terminal window: %s\n", @@ -4946,7 +4950,7 @@ static void change_font_size(GtkFrontend *inst, int increment) } } - errmsg = setup_fonts_ucs(inst); + errmsg = setup_fonts_ucs(inst, inst->conf); if (errmsg) goto cleanup; @@ -5346,15 +5350,48 @@ void new_session_window(Conf *conf, const char *geometry_string) inst->area = gtk_drawing_area_new(); gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); + /* + * Try to create the fonts for use in the window. If this fails, + * we'll try again with some fallback settings, and only abort + * completely if we can't find any fonts at all. + */ { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - window_setup_error(errmsg); - sfree(errmsg); - gtk_widget_destroy(inst->area); - sfree(inst); - return; + char *errmsg_main = setup_fonts_ucs(inst, inst->conf); + if (!errmsg_main) + goto fonts_ok; + + static const char *const fallbacks[] = { + DEFAULT_GTK_CLIENT_FONT, + DEFAULT_GTK_SERVER_FONT, + }; + for (size_t i = 0; i < lenof(fallbacks); i++) { + Conf *fallback_conf = conf_new(); + do_defaults(NULL, fallback_conf); + + FontSpec *fs = fontspec_new(fallbacks[i]); + conf_set_fontspec(fallback_conf, CONF_font, fs); + fontspec_free(fs); + + char *errmsg_fallback = setup_fonts_ucs(inst, fallback_conf); + conf_free(fallback_conf); + + if (!errmsg_fallback) { + fprintf(stderr, "%s; falling back to default font '%s'\n", + errmsg_main, fallbacks[i]); + sfree(errmsg_main); + goto fonts_ok; + } + + sfree(errmsg_fallback); } + + window_setup_error(errmsg_main); + sfree(errmsg_main); + gtk_widget_destroy(inst->area); + sfree(inst); + return; + + fonts_ok:; } #if GTK_CHECK_VERSION(2,0,0) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 7accda5f..2581559e 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -29,6 +29,7 @@ add_sources_from_current_dir(utils dupprintf.c dupstr.c dupwcs.c + dupwcscat.c dup_mb_to_wc.c dup_wc_to_mb.c encode_utf8.c diff --git a/utils/buildinfo.c b/utils/buildinfo.c index 3f4505ca..bb546733 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -44,6 +44,10 @@ char *buildinfo(const char *newline) * cases, two different compiler versions have the same _MSC_VER * value, and have to be distinguished by _MSC_FULL_VER. */ +#elif _MSC_VER == 1944 + put_fmt(buf, " 2022 (17.14)"); +#elif _MSC_VER == 1943 + put_fmt(buf, " 2022 (17.13)"); #elif _MSC_VER == 1942 put_fmt(buf, " 2022 (17.12)"); #elif _MSC_VER == 1941 diff --git a/utils/dupwcscat.c b/utils/dupwcscat.c new file mode 100644 index 00000000..4b9ed5a9 --- /dev/null +++ b/utils/dupwcscat.c @@ -0,0 +1,48 @@ +/* + * Implementation function behind dupwcscat() in misc.h. + * + * This function is called with an arbitrary number of 'const wchar_t + * *' parameters, of which the last one is a null pointer. The wrapper + * macro puts on the null pointer itself, so normally callers don't + * have to. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" + +wchar_t *dupwcscat_fn(const wchar_t *s1, ...) +{ + int len; + wchar_t *p, *q, *sn; + va_list ap; + + len = wcslen(s1); + va_start(ap, s1); + while (1) { + sn = va_arg(ap, wchar_t *); + if (!sn) + break; + len += wcslen(sn); + } + va_end(ap); + + p = snewn(len + 1, wchar_t); + wcscpy(p, s1); + q = p + wcslen(p); + + va_start(ap, s1); + while (1) { + sn = va_arg(ap, wchar_t *); + if (!sn) + break; + wcscpy(q, sn); + q += wcslen(q); + } + va_end(ap); + + return p; +} + diff --git a/utils/memory.c b/utils/memory.c index 0ba791ad..590be002 100644 --- a/utils/memory.c +++ b/utils/memory.c @@ -35,7 +35,10 @@ void *safemalloc(size_t factor1, size_t factor2, size_t addend) #ifdef MINEFIELD p = minefield_c_malloc(size); #elif defined ALLOCATION_ALIGNMENT - p = aligned_alloc(ALLOCATION_ALIGNMENT, size); + /* aligned_alloc requires the allocation size to be rounded up */ + p = aligned_alloc( + ALLOCATION_ALIGNMENT, + (size + ALLOCATION_ALIGNMENT - 1) & ~(ALLOCATION_ALIGNMENT-1)); #else p = malloc(size); #endif diff --git a/utils/tree234.c b/utils/tree234.c index 463f218c..004cfb8d 100644 --- a/utils/tree234.c +++ b/utils/tree234.c @@ -1398,7 +1398,8 @@ void findtest(void) lo = 0; hi = arraylen - 1; - while (lo <= hi) { + assert(lo <= hi); + do { mid = (lo + hi) / 2; c = strcmp(p, array[mid]); if (c < 0) @@ -1407,7 +1408,7 @@ void findtest(void) lo = mid + 1; else break; - } + } while (lo <= hi); if (c == 0) { if (rel == REL234_LT) @@ -1428,10 +1429,11 @@ void findtest(void) ret = NULL; } + index = -1; realret = findrelpos234(tree, p, NULL, rel, &index); if (realret != ret) { error("find(\"%s\",%s) gave %s should be %s", - p, relnames[j], realret, ret); + p, relnames[j], realret ? realret : "NULL", ret); } if (realret && index != mid) { error("find(\"%s\",%s) gave %d should be %d", diff --git a/version.h b/version.h index 0bbf6e40..ad94b41c 100644 --- a/version.h +++ b/version.h @@ -8,6 +8,6 @@ * default stuff used for local development runs of 'make'. */ -#define TEXTVER "发布版 0.82-cn2" -#define SSHVER "-Release-0-82-CN2" -#define BINARY_VERSION 0,82,0,2 +#define TEXTVER "发布版 0.83-cn1" +#define SSHVER "-Release-0-83-CN1" +#define BINARY_VERSION 0,83,0,1 diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 7e16b0f4..7d4fcadb 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -42,6 +42,9 @@ add_sources_from_current_dir(utils if(NOT HAVE_STRTOUMAX) add_sources_from_current_dir(utils utils/strtoumax.c) endif() +if(NOT HAVE_WMEMCHR) + add_sources_from_current_dir(utils utils/wmemchr.c) +endif() add_sources_from_current_dir(eventloop cliloop.c handle-wait.c) add_sources_from_current_dir(console @@ -83,9 +86,7 @@ add_dependencies(pageant generated_licence_h) target_link_libraries(pageant guimisc eventloop agent network crypto utils ${platform_libraries}) -set_target_properties(pageant PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") +set_target_properties(pageant PROPERTIES WIN32_EXECUTABLE ON) installed_program(pageant) add_sources_from_current_dir(plink no-jump-list.c nohelp.c plink.rc) @@ -111,9 +112,7 @@ target_link_libraries(putty guiterminal guimisc eventloop sshclient otherbackends settings network crypto utils ${platform_libraries}) -set_target_properties(putty PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") +set_target_properties(putty PROPERTIES WIN32_EXECUTABLE ON) installed_program(putty) add_executable(puttytel @@ -124,6 +123,7 @@ add_executable(puttytel ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c + ${CMAKE_SOURCE_DIR}/stubs/no-dit.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) @@ -132,9 +132,7 @@ add_dependencies(puttytel generated_licence_h) target_link_libraries(puttytel guiterminal guimisc eventloop otherbackends settings network utils ${platform_libraries}) -set_target_properties(puttytel PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") +set_target_properties(puttytel PROPERTIES WIN32_EXECUTABLE ON) installed_program(puttytel) add_executable(puttygen @@ -152,9 +150,7 @@ add_dependencies(puttygen generated_licence_h) target_link_libraries(puttygen keygen guimisc crypto utils ${platform_libraries}) -set_target_properties(puttygen PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") +set_target_properties(puttygen PROPERTIES WIN32_EXECUTABLE ON) installed_program(puttygen) if(HAVE_CONPTY) @@ -167,6 +163,7 @@ if(HAVE_CONPTY) ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c + ${CMAKE_SOURCE_DIR}/stubs/no-dit.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) be_list(pterm pterm) @@ -174,9 +171,7 @@ if(HAVE_CONPTY) target_link_libraries(pterm guiterminal guimisc eventloop settings network utils ${platform_libraries}) - set_target_properties(pterm PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") + set_target_properties(pterm PROPERTIES WIN32_EXECUTABLE ON) installed_program(pterm) else() message("ConPTY not available; cannot build Windows pterm") diff --git a/windows/config.c b/windows/config.c index 50854793..697f19b2 100644 --- a/windows/config.c +++ b/windows/config.c @@ -159,7 +159,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, } } ctrl_filesel(s, "响铃时播放的声音文件:", NO_SHORTCUT, - FILTER_WAVE_FILES, false, "选择声音文件", + FILTER_SOUND_FILES, false, "选择声音文件", HELPCTX(bell_style), conf_filesel_handler, I(CONF_bell_wavefile)); @@ -377,7 +377,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, if (!midsession && backend_vt_from_proto(PROT_SSH)) { s = ctrl_getset(b, "连接/SSH/X11", "x11", "X11 转发"); ctrl_filesel(s, "用于本地显示的 X 认证文件(T)", 't', - NULL, false, "选择 X 认证文件", + FILTER_ALL_FILES, false, "选择 X 认证文件", HELPCTX(ssh_tunnels_xauthority), conf_filesel_handler, I(CONF_xauthfile)); } diff --git a/windows/controls.c b/windows/controls.c index cd23273d..e3a041b8 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -2017,46 +2017,34 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED))) { - OPENFILENAMEW of; - wchar_t filename[FILENAME_MAX]; - - wchar_t *title_to_free = NULL; - - memset(&of, 0, sizeof(of)); - of.hwndOwner = dp->hwnd; - if (ctrl->fileselect.filter) - of.lpstrFilter = ctrl->fileselect.filter; - else - of.lpstrFilter = L"所有文件 (*.*)\0*\0\0\0"; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; + Filename *fn_prev = NULL; if (!ctrl->fileselect.just_button) { - GetDlgItemTextW(dp->hwnd, c->base_id+1, - filename, lenof(filename)); - filename[lenof(filename)-1] = L'\0'; - } else { - *filename = L'\0'; + wchar_t *text = GetDlgItemTextW_alloc(dp->hwnd, c->base_id+1); + if (*text) + fn_prev = filename_from_wstr(text); + sfree(text); } - of.nMaxFile = lenof(filename); - of.lpstrFileTitle = NULL; - of.lpstrTitle = title_to_free = dup_mb_to_wc( - DEFAULT_CODEPAGE, ctrl->fileselect.title); - of.Flags = 0; - if (request_file_w(NULL, &of, false, - ctrl->fileselect.for_writing)) { + + Filename *fn = request_file( + dp->hwnd, ctrl->fileselect.title, fn_prev, + ctrl->fileselect.for_writing, NULL, false, + ctrl->fileselect.filter); + if (fn_prev) + filename_free(fn_prev); + + if (fn) { if (!ctrl->fileselect.just_button) { - SetDlgItemTextW(dp->hwnd, c->base_id + 1, filename); + SetDlgItemTextW(dp->hwnd, c->base_id + 1, + filename_to_wstr(fn)); ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else { assert(!c->data); - c->data = filename; + c->data = fn; ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); c->data = NULL; } + filename_free(fn); } - - sfree(title_to_free); } break; case CTRL_FONTSELECT: @@ -2462,7 +2450,7 @@ Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) sfree(tmp); return ret; } else { - return filename_from_str(c->data); + return filename_copy(c->data); } } diff --git a/windows/pageant.c b/windows/pageant.c index 9594a040..e8d78841 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -52,7 +52,7 @@ static char *putty_path; static bool restrict_putty_acl = false; /* CWD for "add key" file requester. */ -static filereq *keypath = NULL; +static filereq_saved_dir *keypath = NULL; /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of * wParam are used by Windows, and should be masked off, so we shouldn't @@ -541,49 +541,21 @@ static void win_add_keyfile(Filename *filename, bool encrypted) */ static void prompt_add_keyfile(bool encrypted) { - OPENFILENAME of; - char *filelist = snewn(8192, char); - - if (!keypath) keypath = filereq_new(); - memset(&of, 0, sizeof(of)); - of.hwndOwner = traywindow; - of.lpstrFilter = FILTER_KEY_FILES_C; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filelist; - *filelist = '\0'; - of.nMaxFile = 8192; - of.lpstrFileTitle = NULL; - of.lpstrTitle = "选择私钥文件"; - of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; - if (request_file(keypath, &of, true, false)) { - if (strlen(filelist) > of.nFileOffset) { - /* Only one filename returned? */ - Filename *fn = filename_from_str(filelist); - win_add_keyfile(fn, encrypted); - filename_free(fn); - } else { - /* we are returned a bunch of strings, end to - * end. first string is the directory, the - * rest the filenames. terminated with an - * empty string. - */ - char *dir = filelist; - char *filewalker = filelist + strlen(dir) + 1; - while (*filewalker != '\0') { - char *filename = dupcat(dir, "\\", filewalker); - Filename *fn = filename_from_str(filename); - win_add_keyfile(fn, encrypted); - filename_free(fn); - sfree(filename); - filewalker += strlen(filewalker) + 1; - } - } + if (!keypath) + keypath = filereq_saved_dir_new(); + + struct request_multi_file_return *rmf = request_multi_file( + traywindow, "选择私钥文件", NULL, false, + keypath, true, FILTER_KEY_FILES); + + if (rmf) { + for (size_t i = 0; i < rmf->nfilenames; i++) + win_add_keyfile(rmf->filenames[i], encrypted); + request_multi_file_free(rmf); keylist_update(); pageant_forget_passphrases(); } - sfree(filelist); } /* @@ -1565,6 +1537,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) size_t nclkeys = 0, clkeysize = 0; dll_hijacking_protection(); + enable_dit(); hinst = inst; @@ -1974,7 +1947,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) DestroyMenu(systray_menu); } - if (keypath) filereq_free(keypath); + if (keypath) + filereq_saved_dir_free(keypath); if (openssh_config_file) { /* diff --git a/windows/platform.h b/windows/platform.h index 7e7d8fbd..762ed84a 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -66,6 +66,7 @@ struct Filename { char *cpath, *utf8path; }; Filename *filename_from_wstr(const wchar_t *str); +const wchar_t *filename_to_wstr(const Filename *fn); FILE *f_open(const Filename *filename, const char *mode, bool isprivate); #ifndef SUPERSEDE_FONTSPEC_FOR_TESTING @@ -295,27 +296,6 @@ void write_aclip(HWND hwnd, int clipboard, char *, int); */ #define sk_getxdmdata(socket, lenp) (NULL) -/* - * File-selector filter strings used in the config box. On Windows, - * these strings are of exactly the type needed to go in - * `lpstrFilter' in an OPENFILENAME structure. - */ -typedef const wchar_t *FILESELECT_FILTER_TYPE; -#define FILTER_KEY_FILES (L"PuTTY 私钥文件 (*.ppk)\0*.ppk\0" \ - L"所有文件 (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES (L"音频文件 (*.wav)\0*.WAV\0" \ - L"所有文件 (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES (L"动态链接库文件 (*.dll)\0*.dll\0" \ - L"所有文件 (*.*)\0*\0\0\0") - -/* char-based versions of the above, for outlying uses of file selectors. */ -#define FILTER_KEY_FILES_C ("PuTTY 私钥文件 (*.ppk)\0*.ppk\0" \ - "所有文件 (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES_C ("音频文件 (*.wav)\0*.WAV\0" \ - "所有文件 (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES_C ("动态链接库文件 (*.dll)\0*.dll\0" \ - "所有文件 (*.*)\0*\0\0\0") - /* * Exports from network.c. */ @@ -416,12 +396,21 @@ void init_common_controls(void); /* also does some DLL-loading */ /* * Exports from utils. */ -typedef struct filereq_tag filereq; /* cwd for file requester */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save); -filereq *filereq_new(void); -void filereq_free(filereq *state); +typedef struct filereq_saved_dir filereq_saved_dir; +filereq_saved_dir *filereq_saved_dir_new(void); +void filereq_saved_dir_free(filereq_saved_dir *state); +Filename *request_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter); +struct request_multi_file_return { + Filename **filenames; + size_t nfilenames; +}; +struct request_multi_file_return *request_multi_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter); +void request_multi_file_free(struct request_multi_file_return *); + void pgp_fingerprints_msgbox(HWND owner); int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, bool utf8, DWORD helpctxid); diff --git a/windows/plink.c b/windows/plink.c index b2e90cae..52cf915c 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -187,7 +187,6 @@ static void usage(void) printf(" 记录文件已存在时覆盖文件还是在文件末尾添加内容\n"); printf(" -shareexists\n"); printf(" 测试是否存在上游连接共享\n"); - exit(1); } static void version(void) @@ -298,6 +297,7 @@ int main(int argc, char **argv) SetConsoleOutputCP(CP_UTF8); dll_hijacking_protection(); + enable_dit(); /* * Initialise port and protocol to sensible defaults. (These diff --git a/windows/puttygen.c b/windows/puttygen.c index 93b6db42..979ecdb2 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -451,34 +451,6 @@ static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, return 0; } -/* - * Prompt for a key file. Assumes the filename buffer is of size - * FILENAME_MAX. - */ -static bool prompt_keyfile(HWND hwnd, char *dlgtitle, - char *filename, bool save, bool ppk) -{ - OPENFILENAME of; - memset(&of, 0, sizeof(of)); - of.hwndOwner = hwnd; - if (ppk) { - of.lpstrFilter = "PuTTY 私钥文件 (*.ppk)\0*.ppk\0" - "所有文件 (*.*)\0*\0\0\0"; - of.lpstrDefExt = ".ppk"; - } else { - of.lpstrFilter = "所有文件 (*.*)\0*\0\0\0"; - } - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; - *filename = '\0'; - of.nMaxFile = FILENAME_MAX; - of.lpstrFileTitle = NULL; - of.lpstrTitle = dlgtitle; - of.Flags = 0; - return request_file(NULL, &of, false, save); -} - /* * Dialog-box function for the Licence box. */ @@ -2013,7 +1985,6 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists) { - char filename[FILENAME_MAX]; char *passphrase, *passphrase2; int type, realtype; @@ -2064,26 +2035,28 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, break; } } - if (prompt_keyfile(hwnd, "保存私钥为:", - filename, true, (type == realtype))) { + Filename *fn = request_file( + hwnd, "保存私钥为:", NULL, true, NULL, false, + (type==realtype ? FILTER_KEY_FILES : FILTER_ALL_FILES)); + if (fn) { int ret; - FILE *fp = fopen(filename, "r"); + FILE *fp = f_open(fn, "r", false); if (fp) { char *buffer; fclose(fp); buffer = dupprintf("覆盖已存在的文件\n%s?", - filename); + filename_to_str(fn)); ret = MessageBox(hwnd, buffer, "PuTTYgen 警告", MB_YESNO | MB_ICONWARNING); sfree(buffer); if (ret != IDYES) { burnstr(passphrase); + filename_free(fn); break; } } if (state->ssh2) { - Filename *fn = filename_from_str(filename); if (type != realtype) ret = export_ssh2(fn, type, &state->ssh2key, *passphrase ? passphrase : NULL); @@ -2091,21 +2064,19 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, ret = ppk_save_f(fn, &state->ssh2key, *passphrase ? passphrase : NULL, &save_params); - filename_free(fn); } else { - Filename *fn = filename_from_str(filename); if (type != realtype) ret = export_ssh1(fn, type, &state->key, *passphrase ? passphrase : NULL); else ret = rsa1_save_f(fn, &state->key, *passphrase ? passphrase : NULL); - filename_free(fn); } if (ret <= 0) { MessageBox(hwnd, "无法保存密钥文件", "PuTTYgen 错误", MB_OK | MB_ICONERROR); } + filename_free(fn); } burnstr(passphrase); } @@ -2116,23 +2087,26 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "保存公钥为:", - filename, true, false)) { + Filename *fn = request_file( + hwnd, "保存公钥为:", NULL, true, NULL, false, + FILTER_ALL_FILES); + if (fn) { int ret; - FILE *fp = fopen(filename, "r"); + FILE *fp = f_open(fn, "r", false); if (fp) { char *buffer; fclose(fp); buffer = dupprintf("覆盖已存在的文件\n%s?", - filename); + filename_to_str(fn)); ret = MessageBox(hwnd, buffer, "PuTTYgen 警告", MB_YESNO | MB_ICONWARNING); sfree(buffer); - if (ret != IDYES) + if (ret != IDYES) { + filename_free(fn); break; + } } - fp = fopen(filename, "w"); + fp = f_open(fn, "w", false); if (!fp) { MessageBox(hwnd, "无法打开密钥文件", "PuTTYgen 错误", MB_OK | MB_ICONERROR); @@ -2153,6 +2127,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR); } } + filename_free(fn); } } break; @@ -2163,10 +2138,11 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (!state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "载入私钥:", filename, false, - LOWORD(wParam) == IDC_LOAD)) { - Filename *fn = filename_from_str(filename); + Filename *fn = request_file( + hwnd, "载入私钥:", NULL, false, NULL, false, + (LOWORD(wParam) == IDC_LOAD ? + FILTER_KEY_FILES : FILTER_ALL_FILES)); + if (fn) { load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); filename_free(fn); } @@ -2178,10 +2154,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists && !state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "载入证书:", filename, false, - false)) { - Filename *fn = filename_from_str(filename); + Filename *fn = request_file( + hwnd, "载入证书:", NULL, false, NULL, false, + FILTER_ALL_FILES); + if (fn) { add_certificate(hwnd, state, fn); filename_free(fn); } @@ -2390,6 +2366,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) struct InitialParams params[1]; dll_hijacking_protection(); + enable_dit(); init_common_controls(); hinst = inst; diff --git a/windows/sftp.c b/windows/sftp.c index b95b52b5..796a170a 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -652,6 +652,7 @@ int main(int argc, char *argv[]) SetConsoleOutputCP(CP_UTF8); dll_hijacking_protection(); + enable_dit(); CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); ret = psftp_main(arglist); diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c index b0193276..87728e10 100644 --- a/windows/utils/arm_arch_queries.c +++ b/windows/utils/arm_arch_queries.c @@ -43,3 +43,11 @@ bool platform_sha512_neon_available(void) * SHA-512 architecture extension. */ return false; } + +bool platform_dit_available(void) +{ + /* As of 2024-12-17, as far as I can tell from docs.microsoft.com, + * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the + * DIT bit in PSTATE. */ + return false; +} diff --git a/windows/utils/filename.c b/windows/utils/filename.c index 8f4c53af..fd494869 100644 --- a/windows/utils/filename.c +++ b/windows/utils/filename.c @@ -47,6 +47,11 @@ const char *filename_to_str(const Filename *fn) return fn->cpath; /* FIXME */ } +const wchar_t *filename_to_wstr(const Filename *fn) +{ + return fn->wpath; +} + bool filename_equal(const Filename *f1, const Filename *f2) { /* wpath is primary: two filenames refer to the same file if they diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c index 8db32901..8de62a3b 100644 --- a/windows/utils/getdlgitemtext_alloc.c +++ b/windows/utils/getdlgitemtext_alloc.c @@ -4,6 +4,8 @@ * string is dynamically allocated; caller must free. */ +#include + #include "putty.h" char *GetDlgItemText_alloc(HWND hwnd, int id) @@ -27,7 +29,7 @@ wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id) do { sgrowarray_nm(ret, size, size); GetDlgItemTextW(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); + } while (!wmemchr(ret, L'\0', size-1)); return ret; } diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c index 57363cad..e50293ca 100644 --- a/windows/utils/request_file.c +++ b/windows/utils/request_file.c @@ -1,114 +1,376 @@ -/* - * GetOpenFileName/GetSaveFileName tend to muck around with the process' - * working directory on at least some versions of Windows. - * Here's a wrapper that gives more control over this, and hides a little - * bit of other grottiness. - */ - #include "putty.h" -struct filereq_tag { - TCHAR cwd[MAX_PATH]; - WCHAR wcwd[MAX_PATH]; +typedef enum SavedDir { SD_NONE, SD_WCHAR, SD_CHAR } SavedDir; + +struct filereq_saved_dir { + SavedDir which; + union { + WCHAR wcwd[MAX_PATH]; + TCHAR cwd[MAX_PATH]; + }; }; +filereq_saved_dir *filereq_saved_dir_new(void) +{ + filereq_saved_dir *state = snew(filereq_saved_dir); + state->which = SD_NONE; + return state; +} + +void filereq_saved_dir_free(filereq_saved_dir *state) +{ + sfree(state); +} + +static void save_dir(filereq_saved_dir *state) +{ + DWORD dirlen; + + dirlen = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); + if (dirlen > 0 && dirlen < lenof(state->wcwd)) { + state->which = SD_WCHAR; + return; + } + + dirlen = GetCurrentDirectoryA(lenof(state->cwd), state->cwd); + if (dirlen > 0 && dirlen < lenof(state->cwd)) { + state->which = SD_CHAR; + return; + } + + state->which = SD_NONE; +} + +static void restore_dir(filereq_saved_dir *state) +{ + switch (state->which) { + case SD_WCHAR: + SetCurrentDirectoryW(state->wcwd); + break; + case SD_CHAR: + SetCurrentDirectoryA(state->cwd); + break; + case SD_NONE: + break; + } +} + /* - * `of' is expected to be initialised with most interesting fields, but - * this function does some administrivia. (assume `of' was memset to 0) - * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName - * `state' is optional. + * Internal function that brings up an ANSI-coded file dialog, + * returning a raw char * buffer containing the output. + * + * Inputs: + * - hwnd: the parent window for the dialog, or NULL if none + * - title: the window title + * - initial: a filename to populate the new dialog with, or NULL + * - dir: a location in which to persist the logical cwd used by + * successive file dialogs + * - save: true if the file dialog is for write rather than loading a file + * - filter: the default type of file being asked about, which will inform + * the choice of which files to display in the dialog, and also a default + * file extension for saving files + * - multi_filename_offset: NULL if you want to return exactly one file. + * Otherwise points to a size_t which gets nFileOffset from the result + * structure. This is passed to the request_multi_file_populate_* helpers + * below. + * - filename: buffer to put the output in + * - filename_size: size of the buffer. */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) +static bool do_filereq_a( + HWND hwnd, const char *title, Filename *initial, filereq_saved_dir *dir, + bool save, FilereqFilter filter, size_t *multi_filename_offset, + char *filename, size_t filename_size) { - TCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectory(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; - } + OPENFILENAMEA of; - /* Open the file requester, maybe setting lpstrInitialDir */ - { + memset(&of, 0, sizeof(of)); #ifdef OPENFILENAME_SIZE_VERSION_400 - of->lStructSize = OPENFILENAME_SIZE_VERSION_400; + of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else - of->lStructSize = sizeof(*of); + of.lStructSize = sizeof(of); #endif - of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileName(of) : GetOpenFileName(of); + + if (dir && dir->which == SD_CHAR) + of.lpstrInitialDir = dir->cwd; + + switch (filter) { + default: /* FILTER_ALL_FILES */ + of.lpstrFilter = "所有文件 (*.*)\0*\0\0\0"; + break; + case FILTER_KEY_FILES: + of.lpstrFilter = "PuTTY 私钥文件 (*.ppk)\0*.ppk\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".ppk"; + break; + case FILTER_DYNLIB_FILES: + of.lpstrFilter = "动态链接库文件 (*.dll)\0*.dll\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".dll"; + break; + case FILTER_SOUND_FILES: + of.lpstrFilter = "波形文件 (*.wav)\0*.WAV\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".wav"; + break; } + of.nFilterIndex = 1; - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); - if (r == 0 || r >= lenof(state->cwd)) - /* Didn't work, oh well. */ - state->cwd[0] = '\0'; + of.hwndOwner = hwnd; + + if (initial) { + strncpy(filename, filename_to_str(initial), filename_size - 1); + filename[filename_size - 1] = '\0'; + } else { + *filename = '\0'; } + of.lpstrFile = filename; + of.nMaxFile = filename_size; + + of.lpstrTitle = title; + + if (multi_filename_offset) + of.Flags |= OFN_ALLOWMULTISELECT | OFN_EXPLORER; + + bool toret = save ? GetSaveFileNameA(&of) : GetOpenFileNameA(&of); + + if (dir) + save_dir(dir); - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectory(cwd); + if (multi_filename_offset) + *multi_filename_offset = of.nFileOffset; - return ret; + return toret; } /* * Here's the same one again, the wide-string version */ -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save) +static bool do_filereq_w( + HWND hwnd, const char *title, Filename *initial, filereq_saved_dir *dir, + bool save, FilereqFilter filter, size_t *multi_filename_offset, + wchar_t *filename, size_t filename_size) { - WCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectoryW(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; + OPENFILENAMEW of; + void *tofree1 = NULL, *tofree2 = NULL; + + memset(&of, 0, sizeof(of)); + of.lStructSize = sizeof(of); + + if (dir && dir->which == SD_WCHAR) + of.lpstrInitialDir = dir->wcwd; + else if (dir && dir->which == SD_CHAR) { + wchar_t *winitdir = dup_mb_to_wc(CP_ACP, dir->cwd); + tofree1 = winitdir; + of.lpstrInitialDir = winitdir; } - /* Open the file requester, maybe setting lpstrInitialDir */ - { - of->lStructSize = sizeof(*of); - of->lpstrInitialDir = (state && state->wcwd[0]) ? state->wcwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileNameW(of) : GetOpenFileNameW(of); + switch (filter) { + default: /* FILTER_ALL_FILES */ + of.lpstrFilter = L"所有文件 (*.*)\0*\0\0\0"; + break; + case FILTER_KEY_FILES: + of.lpstrFilter = L"PuTTY 私钥文件 (*.ppk)\0*.ppk\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".ppk"; + break; + case FILTER_DYNLIB_FILES: + of.lpstrFilter = L"动态链接库文件 (*.dll)\0*.dll\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".dll"; + break; + case FILTER_SOUND_FILES: + of.lpstrFilter = L"波形文件 (*.wav)\0*.WAV\0" + "所有文件 (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".wav"; + break; } + of.nFilterIndex = 1; - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); - if (r == 0 || r >= lenof(state->wcwd)) - /* Didn't work, oh well. */ - state->wcwd[0] = L'\0'; + of.hwndOwner = hwnd; + + if (initial) { + wcsncpy(filename, filename_to_wstr(initial), filename_size - 1); + filename[filename_size - 1] = L'\0'; + } else { + *filename = L'\0'; } + of.lpstrFile = filename; + of.nMaxFile = filename_size; - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectoryW(cwd); + if (title) { + wchar_t *wtitle = dup_mb_to_wc(CP_ACP, title); + tofree2 = wtitle; + of.lpstrTitle = wtitle; + } - return ret; + if (multi_filename_offset) + of.Flags |= OFN_ALLOWMULTISELECT | OFN_EXPLORER; + + bool toret = save ? GetSaveFileNameW(&of) : GetOpenFileNameW(&of); + + if (dir) + save_dir(dir); + + sfree(tofree1); + sfree(tofree2); + + if (multi_filename_offset) + *multi_filename_offset = of.nFileOffset; + + return toret; } -filereq *filereq_new(void) +Filename *request_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter) { - filereq *state = snew(filereq); - state->cwd[0] = '\0'; - state->wcwd[0] = L'\0'; - return state; + filereq_saved_dir saved_cwd[1]; + Filename *filename = NULL; + + if (preserve_cwd) + save_dir(saved_cwd); + + init_winver(); + if (osPlatformId != VER_PLATFORM_WIN32_NT) { + char namebuf[MAX_PATH]; + if (do_filereq_a( + hwnd, title, initial, dir, save, filter, + NULL, namebuf, lenof(namebuf))) + filename = filename_from_str(namebuf); + } else { + wchar_t namebuf[MAX_PATH]; + if (do_filereq_w( + hwnd, title, initial, dir, save, filter, + NULL, namebuf, lenof(namebuf))) + filename = filename_from_wstr(namebuf); + } + + if (preserve_cwd) + restore_dir(saved_cwd); + + return filename; } -void filereq_free(filereq *state) +static struct request_multi_file_return *request_multi_file_populate_a( + const char *buf, size_t first_filename_offset) { - sfree(state); + struct request_multi_file_return *rmf = + snew(struct request_multi_file_return); + + /* + * We expect one of two situations (guaranteed by the return from + * the OFN_MULTISELECT file dialog API function): + * + * 1. There is a single NUL-terminated filename string in buf, + * potentially including a path, and first_filename_offset points + * to the leaf name part of it. + * + * 2. There are multiple NUL-terminated strings in buf, with the + * first being a path, and the remaining ones being leaf names to + * concatenate to that path. An empty string / extra NUL + * terminates the whole list. first_filename_offset points to the + * start of the first leaf name. + * + * Hence, we can tell these apart by finding out whether a NUL + * appears in the buffer before first_filename_offset. If no, + * we're in case 1; if yes, case 2. + */ + if (strlen(buf) > first_filename_offset) { + /* Case 1: a single filename. */ + rmf->nfilenames = 1; + rmf->filenames = snewn(1, Filename *); + rmf->filenames[0] = filename_from_str(buf); + } else { + /* Case 2: multiple filenames preceded by a path. */ + size_t filenamesize = 16; + rmf->nfilenames = 0; + rmf->filenames = snewn(filenamesize, Filename *); + + const char *dir = buf; + const char *sep = + (*dir && dir[strlen(dir)-1] == '\\') ? "" : "\\"; + const char *p = buf + strlen(dir) + 1; + for (; *p; p += strlen(p) + 1) { + char *filename = dupcat(dir, sep, p); + sgrowarray(rmf->filenames, filenamesize, rmf->nfilenames); + rmf->filenames[rmf->nfilenames++] = filename_from_str(filename); + sfree(filename); + } + } + return rmf; +} + +/* + * Here's the same one again, the wide-string version + */ +static struct request_multi_file_return *request_multi_file_populate_w( + const wchar_t *buf, size_t first_filename_offset) +{ + struct request_multi_file_return *rmf = + snew(struct request_multi_file_return); + if (wcslen(buf) > first_filename_offset) { + rmf->nfilenames = 1; + rmf->filenames = snewn(1, Filename *); + rmf->filenames[0] = filename_from_wstr(buf); + } else { + size_t filenamesize = 16; + rmf->nfilenames = 0; + rmf->filenames = snewn(filenamesize, Filename *); + + const wchar_t *dir = buf; + const wchar_t *sep = + (*dir && dir[wcslen(dir)-1] == L'\\') ? L"" : L"\\"; + const wchar_t *p = buf + wcslen(dir) + 1; + for (; *p; p += wcslen(p) + 1) { + wchar_t *filename = dupwcscat(dir, sep, p); + sgrowarray(rmf->filenames, filenamesize, rmf->nfilenames); + rmf->filenames[rmf->nfilenames++] = filename_from_wstr(filename); + sfree(filename); + } + } + return rmf; +} + +#define MULTI_FACTOR 32 + +struct request_multi_file_return *request_multi_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter) +{ + filereq_saved_dir saved_cwd[1]; + struct request_multi_file_return *rmf = NULL; + size_t first_filename_offset; + + if (preserve_cwd) + save_dir(saved_cwd); + + init_winver(); + if (osPlatformId != VER_PLATFORM_WIN32_NT) { + char namebuf[MAX_PATH * MULTI_FACTOR]; + if (do_filereq_a( + hwnd, title, initial, dir, save, filter, + &first_filename_offset, namebuf, lenof(namebuf))) + rmf = request_multi_file_populate_a( + namebuf, first_filename_offset); + } else { + wchar_t namebuf[MAX_PATH * MULTI_FACTOR]; + if (do_filereq_w( + hwnd, title, initial, dir, save, filter, + &first_filename_offset, namebuf, lenof(namebuf))) + rmf = request_multi_file_populate_w( + namebuf, first_filename_offset); + } + + if (preserve_cwd) + restore_dir(saved_cwd); + + return rmf; +} + +void request_multi_file_free(struct request_multi_file_return *rmf) +{ + for (size_t i = 0; i < rmf->nfilenames; i++) + filename_free(rmf->filenames[i]); + sfree(rmf->filenames); + sfree(rmf); } diff --git a/windows/utils/wmemchr.c b/windows/utils/wmemchr.c new file mode 100644 index 00000000..7ccdfe3c --- /dev/null +++ b/windows/utils/wmemchr.c @@ -0,0 +1,15 @@ +/* + * Work around lack of wmemchr in older MSVC libraries. + */ + +#include + +#include "defs.h" + +wchar_t *wmemchr(const wchar_t *s, wchar_t c, size_t n) +{ + for (; n != 0; s++, n--) + if (*s == c) + return (wchar_t *)s; + return NULL; +} diff --git a/windows/window.c b/windows/window.c index 6bf960a6..8c052ceb 100644 --- a/windows/window.c +++ b/windows/window.c @@ -448,6 +448,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int guess_width, guess_height; dll_hijacking_protection(); + enable_dit(); hinst = inst; hprev = prev; @@ -1504,11 +1505,6 @@ static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height) wgs->font_width = get_font_width(wgs, hdc, &tm); } -#ifdef RDB_DEBUG_PATCH - debug("Primary font H=%d, AW=%d, MW=%d\n", - tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth); -#endif - { CHARSETINFO info; DWORD cset = tm.tmCharSet; @@ -1795,10 +1791,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) int win_width, win_height, resize_action, window_border; RECT cr, wr; -#ifdef RDB_DEBUG_PATCH - debug("reset_window()\n"); -#endif - /* Current window sizes ... */ GetWindowRect(wgs->term_hwnd, &wr); GetClientRect(wgs->term_hwnd, &cr); @@ -1814,9 +1806,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) /* Are we being forced to reload the fonts ? */ if (reinit>1) { -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -- Forced deinit\n"); -#endif deinit_fonts(wgs); init_fonts(wgs, 0, 0); } @@ -1828,9 +1817,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) /* Is the window out of position ? */ if (!reinit) { recompute_window_offset(wgs); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Reposition terminal\n"); -#endif } if (IsZoomed(wgs->term_hwnd)) { @@ -1857,10 +1843,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) wgs->offset_height = (win_height - wgs->font_height*wgs->term->rows) / 2; InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Z font resize to (%d, %d)\n", - wgs->font_width, wgs->font_height); -#endif } } else { if (wgs->font_width * wgs->term->cols != win_width || @@ -1877,9 +1859,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) wgs->offset_height = (win_height - window_border - wgs->font_height*wgs->term->rows) / 2; InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Zoomed term_size\n"); -#endif } } return; @@ -1921,10 +1900,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) * so we resize to the default font size. */ if (reinit>0) { -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Forced re-init\n"); -#endif - wgs->offset_width = wgs->offset_height = window_border; wgs->extra_width = wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; @@ -2001,10 +1976,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) if ( width > wgs->term->cols ) width = wgs->term->cols; term_size(wgs->term, height, width, conf_get_int(wgs->conf, CONF_savelines)); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> term resize to (%d,%d)\n", - height, width); -#endif } } @@ -2014,11 +1985,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) SWP_NOMOVE | SWP_NOZORDER); InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> window resize to (%d,%d)\n", - wgs->font_width*term->cols + wgs->extra_width, - wgs->font_height*term->rows + wgs->extra_height); -#endif } return; } @@ -2040,10 +2006,6 @@ static void reset_window(WinGuiSeat *wgs, int reinit) wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> font resize to (%d,%d)\n", - wgs->font_width, wgs->font_height); -#endif } } @@ -2224,12 +2186,12 @@ static void wm_size_resize_term(WinGuiSeat *wgs, LPARAM lParam) * numbers of resize events. */ wgs->need_backend_resize = true; - conf_set_int(wgs->conf, CONF_height, h); - conf_set_int(wgs->conf, CONF_width, w); } else { term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines)); } + conf_set_int(wgs->conf, CONF_height, h); + conf_set_int(wgs->conf, CONF_width, w); } static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, @@ -2938,9 +2900,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term_update(wgs->term); break; case WM_ENTERSIZEMOVE: -#ifdef RDB_DEBUG_PATCH - debug("WM_ENTERSIZEMOVE\n"); -#endif EnableSizeTip(true); wgs->resizing = true; wgs->need_backend_resize = false; @@ -2948,9 +2907,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_EXITSIZEMOVE: EnableSizeTip(false); wgs->resizing = false; -#ifdef RDB_DEBUG_PATCH - debug("WM_EXITSIZEMOVE\n"); -#endif if (wgs->need_backend_resize) { term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), conf_get_int(wgs->conf, CONF_width), @@ -3063,15 +3019,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; case WM_SIZE: resize_action = conf_get_int(wgs->conf, CONF_resize_action); -#ifdef RDB_DEBUG_PATCH - debug("WM_SIZE %s (%d,%d)\n", - (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED": - (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED": - (wParam == SIZE_RESTORED && resizing) ? "to": - (wParam == SIZE_RESTORED) ? "SIZE_RESTORED": - "...", - LOWORD(lParam), HIWORD(lParam)); -#endif term_notify_minimised(wgs->term, wParam == SIZE_MINIMIZED); { /*