diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index dc67f231..47c3967e 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -19,7 +19,7 @@ the interesting functions as you go.
The libssh library provides:
- - Key Exchange Methods: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
+ - Key Exchange Methods: sntrup761x25519-sha512@openssh.com, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
- Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256
- Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc
- Compression Schemes: zlib, zlib@openssh.com, none
diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h
index 95e6a062..67200ac6 100644
--- a/include/libssh/crypto.h
+++ b/include/libssh/crypto.h
@@ -45,10 +45,11 @@
#ifdef HAVE_OPENSSL_ECDH_H
#include
#endif
+#include "libssh/curve25519.h"
#include "libssh/dh.h"
#include "libssh/ecdh.h"
#include "libssh/kex.h"
-#include "libssh/curve25519.h"
+#include "libssh/sntrup761.h"
#define DIGEST_MAX_LEN 64
@@ -82,6 +83,8 @@ enum ssh_key_exchange_e {
SSH_KEX_DH_GROUP18_SHA512,
/* diffie-hellman-group14-sha256 */
SSH_KEX_DH_GROUP14_SHA256,
+ /* sntrup761x25519-sha512@openssh.com */
+ SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM,
};
enum ssh_cipher_e {
@@ -132,6 +135,11 @@ struct ssh_crypto_struct {
#endif
ssh_curve25519_pubkey curve25519_client_pubkey;
ssh_curve25519_pubkey curve25519_server_pubkey;
+#endif
+#ifdef HAVE_SNTRUP761
+ ssh_sntrup761_privkey sntrup761_privkey;
+ ssh_sntrup761_pubkey sntrup761_client_pubkey;
+ ssh_sntrup761_ciphertext sntrup761_ciphertext;
#endif
ssh_string dh_server_signature; /* information used by dh_handshake. */
size_t session_id_len;
diff --git a/include/libssh/sntrup761.h b/include/libssh/sntrup761.h
new file mode 100644
index 00000000..96e9ce0c
--- /dev/null
+++ b/include/libssh/sntrup761.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2013 by Aris Adamantiadis
+ * Copyright (c) 2023 Simon Josefsson
+ * Copyright (c) 2025 Jakub Jelen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation,
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SNTRUP761_H_
+#define SNTRUP761_H_
+
+#include "config.h"
+#include "curve25519.h"
+#include "libssh.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef HAVE_CURVE25519
+#define HAVE_SNTRUP761 1
+#endif
+
+extern void crypto_hash_sha512(unsigned char *out,
+ const unsigned char *in,
+ unsigned long long inlen);
+
+/*
+ * Derived from public domain source, written by (in alphabetical order):
+ * - Daniel J. Bernstein
+ * - Chitchanok Chuengsatiansup
+ * - Tanja Lange
+ * - Christine van Vredendaal
+ */
+
+#include
+#include
+
+#define SNTRUP761_SECRETKEY_SIZE 1763
+#define SNTRUP761_PUBLICKEY_SIZE 1158
+#define SNTRUP761_CIPHERTEXT_SIZE 1039
+#define SNTRUP761_SIZE 32
+
+typedef void sntrup761_random_func(void *ctx, size_t length, uint8_t *dst);
+
+void sntrup761_keypair(uint8_t *pk,
+ uint8_t *sk,
+ void *random_ctx,
+ sntrup761_random_func *random);
+void sntrup761_enc(uint8_t *c,
+ uint8_t *k,
+ const uint8_t *pk,
+ void *random_ctx,
+ sntrup761_random_func *random);
+void sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk);
+
+typedef unsigned char ssh_sntrup761_pubkey[SNTRUP761_PUBLICKEY_SIZE];
+typedef unsigned char ssh_sntrup761_privkey[SNTRUP761_SECRETKEY_SIZE];
+typedef unsigned char ssh_sntrup761_ciphertext[SNTRUP761_CIPHERTEXT_SIZE];
+
+int ssh_client_sntrup761x25519_init(ssh_session session);
+void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session);
+
+#ifdef WITH_SERVER
+void ssh_server_sntrup761x25519_init(ssh_session session);
+#endif /* WITH_SERVER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SNTRUP761_H_ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e0243bb0..1e30ca7e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -87,6 +87,7 @@ set(libssh_SRCS
connector.c
crypto_common.c
curve25519.c
+ sntrup761.c
dh.c
ecdh.c
error.c
@@ -119,6 +120,7 @@ set(libssh_SRCS
wrapper.c
external/bcrypt_pbkdf.c
external/blowfish.c
+ external/sntrup761.c
config_parser.c
token.c
pki_ed25519_common.c
diff --git a/src/client.c b/src/client.c
index eaef7e32..e0e1b706 100644
--- a/src/client.c
+++ b/src/client.c
@@ -289,6 +289,11 @@ int dh_handshake(ssh_session session)
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
rc = ssh_client_curve25519_init(session);
break;
+#endif
+#ifdef HAVE_SNTRUP761
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
+ rc = ssh_client_sntrup761x25519_init(session);
+ break;
#endif
default:
rc = SSH_ERROR;
diff --git a/src/external/sntrup761.c b/src/external/sntrup761.c
new file mode 100644
index 00000000..b9bf7239
--- /dev/null
+++ b/src/external/sntrup761.c
@@ -0,0 +1,1060 @@
+/*
+ * Derived from public domain source, written by (in alphabetical order):
+ * - Daniel J. Bernstein
+ * - Chitchanok Chuengsatiansup
+ * - Tanja Lange
+ * - Christine van Vredendaal
+ */
+
+#include
+#include
+
+#define SNTRUP761_SECRETKEY_SIZE 1763
+#define SNTRUP761_PUBLICKEY_SIZE 1158
+#define SNTRUP761_CIPHERTEXT_SIZE 1039
+#define SNTRUP761_SIZE 32
+
+typedef void sntrup761_random_func (void *ctx, size_t length, uint8_t *dst);
+
+void
+sntrup761_keypair (uint8_t *pk, uint8_t *sk,
+ void *random_ctx, sntrup761_random_func *random);
+
+void
+sntrup761_enc (uint8_t *c, uint8_t *k, const uint8_t *pk,
+ void *random_ctx, sntrup761_random_func *random);
+
+void
+sntrup761_dec (uint8_t *k, const uint8_t *c, const uint8_t *sk);
+
+extern void crypto_hash_sha512 (unsigned char *out,
+ const unsigned char *in,
+ unsigned long long inlen);
+
+#define MAX_LEN 761
+
+/* from supercop-20201130/crypto_sort/int32/portable4/int32_minmax.inc */
+#define int32_MINMAX(a,b) \
+do { \
+ int64_t ab = (int64_t)b ^ (int64_t)a; \
+ int64_t c = (int64_t)b - (int64_t)a; \
+ c ^= ab & (c ^ b); \
+ c >>= 31; \
+ c &= ab; \
+ a ^= c; \
+ b ^= c; \
+} while(0)
+
+/* from supercop-20201130/crypto_sort/int32/portable4/sort.c */
+static void
+crypto_sort_int32 (void *array, long long n)
+{
+ long long top, p, q, r, i, j;
+ int32_t *x = array;
+
+ if (n < 2)
+ return;
+ top = 1;
+ while (top < n - top)
+ top += top;
+
+ for (p = top; p >= 1; p >>= 1)
+ {
+ i = 0;
+ while (i + 2 * p <= n)
+ {
+ for (j = i; j < i + p; ++j)
+ int32_MINMAX (x[j], x[j + p]);
+ i += 2 * p;
+ }
+ for (j = i; j < n - p; ++j)
+ int32_MINMAX (x[j], x[j + p]);
+
+ i = 0;
+ j = 0;
+ for (q = top; q > p; q >>= 1)
+ {
+ if (j != i)
+ for (;;)
+ {
+ int32_t a;
+ if (j == n - q)
+ goto done;
+ a = x[j + p];
+ for (r = q; r > p; r >>= 1)
+ int32_MINMAX (a, x[j + r]);
+ x[j + p] = a;
+ ++j;
+ if (j == i + p)
+ {
+ i += 2 * p;
+ break;
+ }
+ }
+ while (i + p <= n - q)
+ {
+ for (j = i; j < i + p; ++j)
+ {
+ int32_t a = x[j + p];
+ for (r = q; r > p; r >>= 1)
+ int32_MINMAX (a, x[j + r]);
+ x[j + p] = a;
+ }
+ i += 2 * p;
+ }
+ /* now i + p > n - q */
+ j = i;
+ while (j < n - q)
+ {
+ int32_t a = x[j + p];
+ for (r = q; r > p; r >>= 1)
+ int32_MINMAX (a, x[j + r]);
+ x[j + p] = a;
+ ++j;
+ }
+
+ done:;
+ }
+ }
+}
+
+/* from supercop-20201130/crypto_sort/uint32/useint32/sort.c */
+
+/* can save time by vectorizing xor loops */
+/* can save time by integrating xor loops with int32_sort */
+
+static void
+crypto_sort_uint32 (void *array, long long n)
+{
+ uint32_t *x = array;
+ long long j;
+ for (j = 0; j < n; ++j)
+ x[j] ^= 0x80000000;
+ crypto_sort_int32 (array, n);
+ for (j = 0; j < n; ++j)
+ x[j] ^= 0x80000000;
+}
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/uint32.c */
+
+/*
+CPU division instruction typically takes time depending on x.
+This software is designed to take time independent of x.
+Time still varies depending on m; user must ensure that m is constant.
+Time also varies on CPUs where multiplication is variable-time.
+There could be more CPU issues.
+There could also be compiler issues.
+*/
+
+static void
+uint32_divmod_uint14 (uint32_t * q, uint16_t * r, uint32_t x, uint16_t m)
+{
+ uint32_t v = 0x80000000;
+ uint32_t qpart;
+ uint32_t mask;
+
+ v /= m;
+
+ /* caller guarantees m > 0 */
+ /* caller guarantees m < 16384 */
+ /* vm <= 2^31 <= vm+m-1 */
+ /* xvm <= 2^31 x <= xvm+x(m-1) */
+
+ *q = 0;
+
+ qpart = (x * (uint64_t) v) >> 31;
+ /* 2^31 qpart <= xv <= 2^31 qpart + 2^31-1 */
+ /* 2^31 qpart m <= xvm <= 2^31 qpart m + (2^31-1)m */
+ /* 2^31 qpart m <= 2^31 x <= 2^31 qpart m + (2^31-1)m + x(m-1) */
+ /* 0 <= 2^31 newx <= (2^31-1)m + x(m-1) */
+ /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */
+ /* 0 <= newx <= (1-1/2^31)(2^14-1) + (2^32-1)((2^14-1)-1)/2^31 */
+
+ x -= qpart * m;
+ *q += qpart;
+ /* x <= 49146 */
+
+ qpart = (x * (uint64_t) v) >> 31;
+ /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */
+ /* 0 <= newx <= m + 49146(2^14-1)/2^31 */
+ /* 0 <= newx <= m + 0.4 */
+ /* 0 <= newx <= m */
+
+ x -= qpart * m;
+ *q += qpart;
+ /* x <= m */
+
+ x -= m;
+ *q += 1;
+ mask = -(x >> 31);
+ x += mask & (uint32_t) m;
+ *q += mask;
+ /* x < m */
+
+ *r = x;
+}
+
+
+static uint16_t
+uint32_mod_uint14 (uint32_t x, uint16_t m)
+{
+ uint32_t q;
+ uint16_t r;
+ uint32_divmod_uint14 (&q, &r, x, m);
+ return r;
+}
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/int32.c */
+
+static void
+int32_divmod_uint14 (int32_t * q, uint16_t * r, int32_t x, uint16_t m)
+{
+ uint32_t uq, uq2;
+ uint16_t ur, ur2;
+ uint32_t mask;
+
+ uint32_divmod_uint14 (&uq, &ur, 0x80000000 + (uint32_t) x, m);
+ uint32_divmod_uint14 (&uq2, &ur2, 0x80000000, m);
+ ur -= ur2;
+ uq -= uq2;
+ mask = -(uint32_t) (ur >> 15);
+ ur += mask & m;
+ uq += mask;
+ *r = ur;
+ *q = uq;
+}
+
+
+static uint16_t
+int32_mod_uint14 (int32_t x, uint16_t m)
+{
+ int32_t q;
+ uint16_t r;
+ int32_divmod_uint14 (&q, &r, x, m);
+ return r;
+}
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/paramsmenu.h */
+#define p 761
+#define q 4591
+#define Rounded_bytes 1007
+#define Rq_bytes 1158
+#define w 286
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.h */
+
+/* Decode(R,s,M,len) */
+/* assumes 0 < M[i] < 16384 */
+/* produces 0 <= R[i] < M[i] */
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.c */
+
+static void
+Decode (uint16_t * out, const unsigned char *S, const uint16_t * M,
+ long long len)
+{
+ if (len == 1)
+ {
+ if (M[0] == 1)
+ *out = 0;
+ else if (M[0] <= 256)
+ *out = uint32_mod_uint14 (S[0], M[0]);
+ else
+ *out = uint32_mod_uint14 (S[0] + (((uint16_t) S[1]) << 8), M[0]);
+ }
+ if (len > 1)
+ {
+ uint16_t R2[(MAX_LEN + 1) / 2];
+ uint16_t M2[(MAX_LEN + 1) / 2];
+ uint16_t bottomr[MAX_LEN / 2];
+ uint32_t bottomt[MAX_LEN / 2];
+ long long i;
+ for (i = 0; i < len - 1; i += 2)
+ {
+ uint32_t m = M[i] * (uint32_t) M[i + 1];
+ if (m > 256 * 16383)
+ {
+ bottomt[i / 2] = 256 * 256;
+ bottomr[i / 2] = S[0] + 256 * S[1];
+ S += 2;
+ M2[i / 2] = (((m + 255) >> 8) + 255) >> 8;
+ }
+ else if (m >= 16384)
+ {
+ bottomt[i / 2] = 256;
+ bottomr[i / 2] = S[0];
+ S += 1;
+ M2[i / 2] = (m + 255) >> 8;
+ }
+ else
+ {
+ bottomt[i / 2] = 1;
+ bottomr[i / 2] = 0;
+ M2[i / 2] = m;
+ }
+ }
+ if (i < len)
+ M2[i / 2] = M[i];
+ Decode (R2, S, M2, (len + 1) / 2);
+ for (i = 0; i < len - 1; i += 2)
+ {
+ uint32_t r = bottomr[i / 2];
+ uint32_t r1;
+ uint16_t r0;
+ r += bottomt[i / 2] * R2[i / 2];
+ uint32_divmod_uint14 (&r1, &r0, r, M[i]);
+ r1 = uint32_mod_uint14 (r1, M[i + 1]); /* only needed for invalid inputs */
+ *out++ = r0;
+ *out++ = r1;
+ }
+ if (i < len)
+ *out++ = R2[i / 2];
+ }
+}
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.h */
+
+/* Encode(s,R,M,len) */
+/* assumes 0 <= R[i] < M[i] < 16384 */
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.c */
+
+/* 0 <= R[i] < M[i] < 16384 */
+static void
+Encode (unsigned char *out, const uint16_t * R, const uint16_t * M,
+ long long len)
+{
+ if (len == 1)
+ {
+ uint16_t r = R[0];
+ uint16_t m = M[0];
+ while (m > 1)
+ {
+ *out++ = r;
+ r >>= 8;
+ m = (m + 255) >> 8;
+ }
+ }
+ if (len > 1)
+ {
+ uint16_t R2[(MAX_LEN + 1) / 2];
+ uint16_t M2[(MAX_LEN + 1) / 2];
+ long long i;
+ for (i = 0; i < len - 1; i += 2)
+ {
+ uint32_t m0 = M[i];
+ uint32_t r = R[i] + R[i + 1] * m0;
+ uint32_t m = M[i + 1] * m0;
+ while (m >= 16384)
+ {
+ *out++ = r;
+ r >>= 8;
+ m = (m + 255) >> 8;
+ }
+ R2[i / 2] = r;
+ M2[i / 2] = m;
+ }
+ if (i < len)
+ {
+ R2[i / 2] = R[i];
+ M2[i / 2] = M[i];
+ }
+ Encode (out, R2, M2, (len + 1) / 2);
+ }
+}
+
+/* from supercop-20201130/crypto_kem/sntrup761/ref/kem.c */
+
+/* ----- masks */
+
+/* return -1 if x!=0; else return 0 */
+static int
+int16_t_nonzero_mask (int16_t x)
+{
+ uint16_t u = x; /* 0, else 1...65535 */
+ uint32_t v = u; /* 0, else 1...65535 */
+ v = -v; /* 0, else 2^32-65535...2^32-1 */
+ v >>= 31; /* 0, else 1 */
+ return -v; /* 0, else -1 */
+}
+
+/* return -1 if x<0; otherwise return 0 */
+static int
+int16_t_negative_mask (int16_t x)
+{
+ uint16_t u = x;
+ u >>= 15;
+ return -(int) u;
+ /* alternative with gcc -fwrapv: */
+ /* x>>15 compiles to CPU's arithmetic right shift */
+}
+
+/* ----- arithmetic mod 3 */
+
+typedef int8_t small;
+
+/* F3 is always represented as -1,0,1 */
+/* so ZZ_fromF3 is a no-op */
+
+/* x must not be close to top int16_t */
+static small
+F3_freeze (int16_t x)
+{
+ return int32_mod_uint14 (x + 1, 3) - 1;
+}
+
+/* ----- arithmetic mod q */
+
+#define q12 ((q-1)/2)
+typedef int16_t Fq;
+/* always represented as -q12...q12 */
+/* so ZZ_fromFq is a no-op */
+
+/* x must not be close to top int32 */
+static Fq
+Fq_freeze (int32_t x)
+{
+ return int32_mod_uint14 (x + q12, q) - q12;
+}
+
+static Fq
+Fq_recip (Fq a1)
+{
+ int i = 1;
+ Fq ai = a1;
+
+ while (i < q - 2)
+ {
+ ai = Fq_freeze (a1 * (int32_t) ai);
+ i += 1;
+ }
+ return ai;
+}
+
+/* ----- small polynomials */
+
+/* 0 if Weightw_is(r), else -1 */
+static int
+Weightw_mask (small * r)
+{
+ int weight = 0;
+ int i;
+
+ for (i = 0; i < p; ++i)
+ weight += r[i] & 1;
+ return int16_t_nonzero_mask (weight - w);
+}
+
+/* R3_fromR(R_fromRq(r)) */
+static void
+R3_fromRq (small * out, const Fq * r)
+{
+ int i;
+ for (i = 0; i < p; ++i)
+ out[i] = F3_freeze (r[i]);
+}
+
+/* h = f*g in the ring R3 */
+static void
+R3_mult (small * h, const small * f, const small * g)
+{
+ small fg[p + p - 1];
+ small result;
+ int i, j;
+
+ for (i = 0; i < p; ++i)
+ {
+ result = 0;
+ for (j = 0; j <= i; ++j)
+ result = F3_freeze (result + f[j] * g[i - j]);
+ fg[i] = result;
+ }
+ for (i = p; i < p + p - 1; ++i)
+ {
+ result = 0;
+ for (j = i - p + 1; j < p; ++j)
+ result = F3_freeze (result + f[j] * g[i - j]);
+ fg[i] = result;
+ }
+
+ for (i = p + p - 2; i >= p; --i)
+ {
+ fg[i - p] = F3_freeze (fg[i - p] + fg[i]);
+ fg[i - p + 1] = F3_freeze (fg[i - p + 1] + fg[i]);
+ }
+
+ for (i = 0; i < p; ++i)
+ h[i] = fg[i];
+}
+
+/* returns 0 if recip succeeded; else -1 */
+static int
+R3_recip (small * out, const small * in)
+{
+ small f[p + 1], g[p + 1], v[p + 1], r[p + 1];
+ int i, loop, delta;
+ int sign, swap, t;
+
+ for (i = 0; i < p + 1; ++i)
+ v[i] = 0;
+ for (i = 0; i < p + 1; ++i)
+ r[i] = 0;
+ r[0] = 1;
+ for (i = 0; i < p; ++i)
+ f[i] = 0;
+ f[0] = 1;
+ f[p - 1] = f[p] = -1;
+ for (i = 0; i < p; ++i)
+ g[p - 1 - i] = in[i];
+ g[p] = 0;
+
+ delta = 1;
+
+ for (loop = 0; loop < 2 * p - 1; ++loop)
+ {
+ for (i = p; i > 0; --i)
+ v[i] = v[i - 1];
+ v[0] = 0;
+
+ sign = -g[0] * f[0];
+ swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]);
+ delta ^= swap & (delta ^ -delta);
+ delta += 1;
+
+ for (i = 0; i < p + 1; ++i)
+ {
+ t = swap & (f[i] ^ g[i]);
+ f[i] ^= t;
+ g[i] ^= t;
+ t = swap & (v[i] ^ r[i]);
+ v[i] ^= t;
+ r[i] ^= t;
+ }
+
+ for (i = 0; i < p + 1; ++i)
+ g[i] = F3_freeze (g[i] + sign * f[i]);
+ for (i = 0; i < p + 1; ++i)
+ r[i] = F3_freeze (r[i] + sign * v[i]);
+
+ for (i = 0; i < p; ++i)
+ g[i] = g[i + 1];
+ g[p] = 0;
+ }
+
+ sign = f[0];
+ for (i = 0; i < p; ++i)
+ out[i] = sign * v[p - 1 - i];
+
+ return int16_t_nonzero_mask (delta);
+}
+
+/* ----- polynomials mod q */
+
+/* h = f*g in the ring Rq */
+static void
+Rq_mult_small (Fq * h, const Fq * f, const small * g)
+{
+ Fq fg[p + p - 1];
+ Fq result;
+ int i, j;
+
+ for (i = 0; i < p; ++i)
+ {
+ result = 0;
+ for (j = 0; j <= i; ++j)
+ result = Fq_freeze (result + f[j] * (int32_t) g[i - j]);
+ fg[i] = result;
+ }
+ for (i = p; i < p + p - 1; ++i)
+ {
+ result = 0;
+ for (j = i - p + 1; j < p; ++j)
+ result = Fq_freeze (result + f[j] * (int32_t) g[i - j]);
+ fg[i] = result;
+ }
+
+ for (i = p + p - 2; i >= p; --i)
+ {
+ fg[i - p] = Fq_freeze (fg[i - p] + fg[i]);
+ fg[i - p + 1] = Fq_freeze (fg[i - p + 1] + fg[i]);
+ }
+
+ for (i = 0; i < p; ++i)
+ h[i] = fg[i];
+}
+
+/* h = 3f in Rq */
+static void
+Rq_mult3 (Fq * h, const Fq * f)
+{
+ int i;
+
+ for (i = 0; i < p; ++i)
+ h[i] = Fq_freeze (3 * f[i]);
+}
+
+/* out = 1/(3*in) in Rq */
+/* returns 0 if recip succeeded; else -1 */
+static int
+Rq_recip3 (Fq * out, const small * in)
+{
+ Fq f[p + 1], g[p + 1], v[p + 1], r[p + 1];
+ int i, loop, delta;
+ int swap, t;
+ int32_t f0, g0;
+ Fq scale;
+
+ for (i = 0; i < p + 1; ++i)
+ v[i] = 0;
+ for (i = 0; i < p + 1; ++i)
+ r[i] = 0;
+ r[0] = Fq_recip (3);
+ for (i = 0; i < p; ++i)
+ f[i] = 0;
+ f[0] = 1;
+ f[p - 1] = f[p] = -1;
+ for (i = 0; i < p; ++i)
+ g[p - 1 - i] = in[i];
+ g[p] = 0;
+
+ delta = 1;
+
+ for (loop = 0; loop < 2 * p - 1; ++loop)
+ {
+ for (i = p; i > 0; --i)
+ v[i] = v[i - 1];
+ v[0] = 0;
+
+ swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]);
+ delta ^= swap & (delta ^ -delta);
+ delta += 1;
+
+ for (i = 0; i < p + 1; ++i)
+ {
+ t = swap & (f[i] ^ g[i]);
+ f[i] ^= t;
+ g[i] ^= t;
+ t = swap & (v[i] ^ r[i]);
+ v[i] ^= t;
+ r[i] ^= t;
+ }
+
+ f0 = f[0];
+ g0 = g[0];
+ for (i = 0; i < p + 1; ++i)
+ g[i] = Fq_freeze (f0 * g[i] - g0 * f[i]);
+ for (i = 0; i < p + 1; ++i)
+ r[i] = Fq_freeze (f0 * r[i] - g0 * v[i]);
+
+ for (i = 0; i < p; ++i)
+ g[i] = g[i + 1];
+ g[p] = 0;
+ }
+
+ scale = Fq_recip (f[0]);
+ for (i = 0; i < p; ++i)
+ out[i] = Fq_freeze (scale * (int32_t) v[p - 1 - i]);
+
+ return int16_t_nonzero_mask (delta);
+}
+
+/* ----- rounded polynomials mod q */
+
+static void
+Round (Fq * out, const Fq * a)
+{
+ int i;
+ for (i = 0; i < p; ++i)
+ out[i] = a[i] - F3_freeze (a[i]);
+}
+
+/* ----- sorting to generate short polynomial */
+
+static void
+Short_fromlist (small * out, const uint32_t * in)
+{
+ uint32_t L[p];
+ int i;
+
+ for (i = 0; i < w; ++i)
+ L[i] = in[i] & (uint32_t) - 2;
+ for (i = w; i < p; ++i)
+ L[i] = (in[i] & (uint32_t) - 3) | 1;
+ crypto_sort_uint32 (L, p);
+ for (i = 0; i < p; ++i)
+ out[i] = (L[i] & 3) - 1;
+}
+
+/* ----- underlying hash function */
+
+#define Hash_bytes 32
+
+/* e.g., b = 0 means out = Hash0(in) */
+static void
+Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen)
+{
+#define MAX_X_LEN 1158
+ unsigned char x[MAX_X_LEN + 1];
+ unsigned char h[64];
+ int i;
+
+ x[0] = b;
+ for (i = 0; i < inlen; ++i)
+ x[i + 1] = in[i];
+ crypto_hash_sha512 (h, x, inlen + 1);
+ for (i = 0; i < 32; ++i)
+ out[i] = h[i];
+}
+
+/* ----- higher-level randomness */
+
+static uint32_t
+urandom32 (void *random_ctx, sntrup761_random_func * random)
+{
+ unsigned char c[4];
+ uint32_t out[4];
+
+ random (random_ctx, 4, c);
+ out[0] = (uint32_t) c[0];
+ out[1] = ((uint32_t) c[1]) << 8;
+ out[2] = ((uint32_t) c[2]) << 16;
+ out[3] = ((uint32_t) c[3]) << 24;
+ return out[0] + out[1] + out[2] + out[3];
+}
+
+static void
+Short_random (small * out, void *random_ctx, sntrup761_random_func * random)
+{
+ uint32_t L[p];
+ int i;
+
+ for (i = 0; i < p; ++i)
+ L[i] = urandom32 (random_ctx, random);
+ Short_fromlist (out, L);
+}
+
+static void
+Small_random (small * out, void *random_ctx, sntrup761_random_func * random)
+{
+ int i;
+
+ for (i = 0; i < p; ++i)
+ out[i] = (((urandom32 (random_ctx, random) & 0x3fffffff) * 3) >> 30) - 1;
+}
+
+/* ----- Streamlined NTRU Prime Core */
+
+/* h,(f,ginv) = KeyGen() */
+static void
+KeyGen (Fq * h, small * f, small * ginv, void *random_ctx,
+ sntrup761_random_func * random)
+{
+ small g[p];
+ Fq finv[p];
+
+ for (;;)
+ {
+ Small_random (g, random_ctx, random);
+ if (R3_recip (ginv, g) == 0)
+ break;
+ }
+ Short_random (f, random_ctx, random);
+ Rq_recip3 (finv, f); /* always works */
+ Rq_mult_small (h, finv, g);
+}
+
+/* c = Encrypt(r,h) */
+static void
+Encrypt (Fq * c, const small * r, const Fq * h)
+{
+ Fq hr[p];
+
+ Rq_mult_small (hr, h, r);
+ Round (c, hr);
+}
+
+/* r = Decrypt(c,(f,ginv)) */
+static void
+Decrypt (small * r, const Fq * c, const small * f, const small * ginv)
+{
+ Fq cf[p];
+ Fq cf3[p];
+ small e[p];
+ small ev[p];
+ int mask;
+ int i;
+
+ Rq_mult_small (cf, c, f);
+ Rq_mult3 (cf3, cf);
+ R3_fromRq (e, cf3);
+ R3_mult (ev, e, ginv);
+
+ mask = Weightw_mask (ev); /* 0 if weight w, else -1 */
+ for (i = 0; i < w; ++i)
+ r[i] = ((ev[i] ^ 1) & ~mask) ^ 1;
+ for (i = w; i < p; ++i)
+ r[i] = ev[i] & ~mask;
+}
+
+/* ----- encoding small polynomials (including short polynomials) */
+
+#define Small_bytes ((p+3)/4)
+
+/* these are the only functions that rely on p mod 4 = 1 */
+
+static void
+Small_encode (unsigned char *s, const small * f)
+{
+ small x;
+ int i;
+
+ for (i = 0; i < p / 4; ++i)
+ {
+ x = *f++ + 1;
+ x += (*f++ + 1) << 2;
+ x += (*f++ + 1) << 4;
+ x += (*f++ + 1) << 6;
+ *s++ = x;
+ }
+ x = *f++ + 1;
+ *s++ = x;
+}
+
+static void
+Small_decode (small * f, const unsigned char *s)
+{
+ unsigned char x;
+ int i;
+
+ for (i = 0; i < p / 4; ++i)
+ {
+ x = *s++;
+ *f++ = ((small) (x & 3)) - 1;
+ x >>= 2;
+ *f++ = ((small) (x & 3)) - 1;
+ x >>= 2;
+ *f++ = ((small) (x & 3)) - 1;
+ x >>= 2;
+ *f++ = ((small) (x & 3)) - 1;
+ }
+ x = *s++;
+ *f++ = ((small) (x & 3)) - 1;
+}
+
+/* ----- encoding general polynomials */
+
+static void
+Rq_encode (unsigned char *s, const Fq * r)
+{
+ uint16_t R[p], M[p];
+ int i;
+
+ for (i = 0; i < p; ++i)
+ R[i] = r[i] + q12;
+ for (i = 0; i < p; ++i)
+ M[i] = q;
+ Encode (s, R, M, p);
+}
+
+static void
+Rq_decode (Fq * r, const unsigned char *s)
+{
+ uint16_t R[p], M[p];
+ int i;
+
+ for (i = 0; i < p; ++i)
+ M[i] = q;
+ Decode (R, s, M, p);
+ for (i = 0; i < p; ++i)
+ r[i] = ((Fq) R[i]) - q12;
+}
+
+/* ----- encoding rounded polynomials */
+
+static void
+Rounded_encode (unsigned char *s, const Fq * r)
+{
+ uint16_t R[p], M[p];
+ int i;
+
+ for (i = 0; i < p; ++i)
+ R[i] = ((r[i] + q12) * 10923) >> 15;
+ for (i = 0; i < p; ++i)
+ M[i] = (q + 2) / 3;
+ Encode (s, R, M, p);
+}
+
+static void
+Rounded_decode (Fq * r, const unsigned char *s)
+{
+ uint16_t R[p], M[p];
+ int i;
+
+ for (i = 0; i < p; ++i)
+ M[i] = (q + 2) / 3;
+ Decode (R, s, M, p);
+ for (i = 0; i < p; ++i)
+ r[i] = R[i] * 3 - q12;
+}
+
+/* ----- Streamlined NTRU Prime Core plus encoding */
+
+typedef small Inputs[p]; /* passed by reference */
+#define Inputs_random Short_random
+#define Inputs_encode Small_encode
+#define Inputs_bytes Small_bytes
+
+#define Ciphertexts_bytes Rounded_bytes
+#define SecretKeys_bytes (2*Small_bytes)
+#define PublicKeys_bytes Rq_bytes
+
+/* pk,sk = ZKeyGen() */
+static void
+ZKeyGen (unsigned char *pk, unsigned char *sk, void *random_ctx,
+ sntrup761_random_func * random)
+{
+ Fq h[p];
+ small f[p], v[p];
+
+ KeyGen (h, f, v, random_ctx, random);
+ Rq_encode (pk, h);
+ Small_encode (sk, f);
+ sk += Small_bytes;
+ Small_encode (sk, v);
+}
+
+/* C = ZEncrypt(r,pk) */
+static void
+ZEncrypt (unsigned char *C, const Inputs r, const unsigned char *pk)
+{
+ Fq h[p];
+ Fq c[p];
+ Rq_decode (h, pk);
+ Encrypt (c, r, h);
+ Rounded_encode (C, c);
+}
+
+/* r = ZDecrypt(C,sk) */
+static void
+ZDecrypt (Inputs r, const unsigned char *C, const unsigned char *sk)
+{
+ small f[p], v[p];
+ Fq c[p];
+
+ Small_decode (f, sk);
+ sk += Small_bytes;
+ Small_decode (v, sk);
+ Rounded_decode (c, C);
+ Decrypt (r, c, f, v);
+}
+
+/* ----- confirmation hash */
+
+#define Confirm_bytes 32
+
+/* h = HashConfirm(r,pk,cache); cache is Hash4(pk) */
+static void
+HashConfirm (unsigned char *h, const unsigned char *r,
+ /* const unsigned char *pk, */ const unsigned char *cache)
+{
+ unsigned char x[Hash_bytes * 2];
+ int i;
+
+ Hash_prefix (x, 3, r, Inputs_bytes);
+ for (i = 0; i < Hash_bytes; ++i)
+ x[Hash_bytes + i] = cache[i];
+ Hash_prefix (h, 2, x, sizeof x);
+}
+
+/* ----- session-key hash */
+
+/* k = HashSession(b,y,z) */
+static void
+HashSession (unsigned char *k, int b, const unsigned char *y,
+ const unsigned char *z)
+{
+ unsigned char x[Hash_bytes + Ciphertexts_bytes + Confirm_bytes];
+ int i;
+
+ Hash_prefix (x, 3, y, Inputs_bytes);
+ for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i)
+ x[Hash_bytes + i] = z[i];
+ Hash_prefix (k, b, x, sizeof x);
+}
+
+/* ----- Streamlined NTRU Prime */
+
+/* pk,sk = KEM_KeyGen() */
+void
+sntrup761_keypair (unsigned char *pk, unsigned char *sk, void *random_ctx,
+ sntrup761_random_func * random)
+{
+ int i;
+
+ ZKeyGen (pk, sk, random_ctx, random);
+ sk += SecretKeys_bytes;
+ for (i = 0; i < PublicKeys_bytes; ++i)
+ *sk++ = pk[i];
+ random (random_ctx, Inputs_bytes, sk);
+ sk += Inputs_bytes;
+ Hash_prefix (sk, 4, pk, PublicKeys_bytes);
+}
+
+/* c,r_enc = Hide(r,pk,cache); cache is Hash4(pk) */
+static void
+Hide (unsigned char *c, unsigned char *r_enc, const Inputs r,
+ const unsigned char *pk, const unsigned char *cache)
+{
+ Inputs_encode (r_enc, r);
+ ZEncrypt (c, r, pk);
+ c += Ciphertexts_bytes;
+ HashConfirm (c, r_enc, cache);
+}
+
+/* c,k = Encap(pk) */
+void
+sntrup761_enc (unsigned char *c, unsigned char *k, const unsigned char *pk,
+ void *random_ctx, sntrup761_random_func * random)
+{
+ Inputs r;
+ unsigned char r_enc[Inputs_bytes];
+ unsigned char cache[Hash_bytes];
+
+ Hash_prefix (cache, 4, pk, PublicKeys_bytes);
+ Inputs_random (r, random_ctx, random);
+ Hide (c, r_enc, r, pk, cache);
+ HashSession (k, 1, r_enc, c);
+}
+
+/* 0 if matching ciphertext+confirm, else -1 */
+static int
+Ciphertexts_diff_mask (const unsigned char *c, const unsigned char *c2)
+{
+ uint16_t differentbits = 0;
+ int len = Ciphertexts_bytes + Confirm_bytes;
+
+ while (len-- > 0)
+ differentbits |= (*c++) ^ (*c2++);
+ return (1 & ((differentbits - 1) >> 8)) - 1;
+}
+
+/* k = Decap(c,sk) */
+void
+sntrup761_dec (unsigned char *k, const unsigned char *c, const unsigned char *sk)
+{
+ const unsigned char *pk = sk + SecretKeys_bytes;
+ const unsigned char *rho = pk + PublicKeys_bytes;
+ const unsigned char *cache = rho + Inputs_bytes;
+ Inputs r;
+ unsigned char r_enc[Inputs_bytes];
+ unsigned char cnew[Ciphertexts_bytes + Confirm_bytes];
+ int mask;
+ int i;
+
+ ZDecrypt (r, c, sk);
+ Hide (cnew, r_enc, r, pk, cache);
+ mask = Ciphertexts_diff_mask (c, cnew);
+ for (i = 0; i < Inputs_bytes; ++i)
+ r_enc[i] ^= mask & (r_enc[i] ^ rho[i]);
+ HashSession (k, 1 + mask, r_enc, c);
+}
diff --git a/src/kex.c b/src/kex.c
index 1f82eb43..f64fdea1 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -40,6 +40,7 @@
#include "libssh/ssh2.h"
#include "libssh/string.h"
#include "libssh/curve25519.h"
+#include "libssh/sntrup761.h"
#include "libssh/knownhosts.h"
#include "libssh/misc.h"
#include "libssh/pki.h"
@@ -95,6 +96,12 @@
#define CURVE25519 ""
#endif /* HAVE_CURVE25519 */
+#ifdef HAVE_SNTRUP761
+#define SNTRUP761X25519 "sntrup761x25519-sha512@openssh.com,"
+#else
+#define SNTRUP761X25519 ""
+#endif /* HAVE_SNTRUP761 */
+
#ifdef HAVE_ECC
#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,"
#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \
@@ -159,6 +166,7 @@
#define DEFAULT_KEY_EXCHANGE \
CURVE25519 \
+ SNTRUP761X25519 \
ECDH \
"diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \
GEX_SHA256 \
@@ -894,6 +902,8 @@ kex_select_kex_type(const char *kex)
return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG;
} else if (strcmp(kex, "curve25519-sha256") == 0) {
return SSH_KEX_CURVE25519_SHA256;
+ } else if (strcmp(kex, "sntrup761x25519-sha512@openssh.com") == 0) {
+ return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM;
}
/* should not happen. We should be getting only valid names at this stage */
return 0;
@@ -933,6 +943,11 @@ static void revert_kex_callbacks(ssh_session session)
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
ssh_client_curve25519_remove_callbacks(session);
break;
+#endif
+#ifdef HAVE_SNTRUP761
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
+ ssh_client_sntrup761x25519_remove_callbacks(session);
+ break;
#endif
}
}
@@ -1475,8 +1490,35 @@ int ssh_make_sessionid(ssh_session session)
}
break;
#endif /* HAVE_CURVE25519 */
+#ifdef HAVE_SNTRUP761
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
+ rc = ssh_buffer_pack(buf,
+ "dPPdPP",
+ SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE,
+ (size_t)SNTRUP761_PUBLICKEY_SIZE,
+ session->next_crypto->sntrup761_client_pubkey,
+ (size_t)CURVE25519_PUBKEY_SIZE,
+ session->next_crypto->curve25519_client_pubkey,
+ SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE,
+ (size_t)SNTRUP761_CIPHERTEXT_SIZE,
+ session->next_crypto->sntrup761_ciphertext,
+ (size_t)CURVE25519_PUBKEY_SIZE,
+ session->next_crypto->curve25519_server_pubkey);
+
+ if (rc != SSH_OK) {
+ goto error;
+ }
+ break;
+#endif /* HAVE_SNTRUP761 */
+ }
+ if (session->next_crypto->kex_type == SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM) {
+ rc = ssh_buffer_pack(buf,
+ "F",
+ session->next_crypto->shared_secret,
+ SHA512_DIGEST_LEN);
+ } else {
+ rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret);
}
- rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret);
if (rc != SSH_OK) {
goto error;
}
@@ -1532,6 +1574,7 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_DH_GROUP16_SHA512:
case SSH_KEX_DH_GROUP18_SHA512:
case SSH_KEX_ECDH_SHA2_NISTP521:
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
session->next_crypto->digest_len = SHA512_DIGEST_LENGTH;
session->next_crypto->digest_type = SSH_KDF_SHA512;
session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len);
@@ -1668,7 +1711,12 @@ int ssh_generate_session_keys(ssh_session session)
size_t intkey_srv_to_cli_len = 0;
int rc = -1;
- k_string = ssh_make_bignum_string(crypto->shared_secret);
+ if (session->next_crypto->kex_type == SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM) {
+ k_string = ssh_make_padded_bignum_string(crypto->shared_secret,
+ SHA512_DIGEST_LEN);
+ } else {
+ k_string = ssh_make_bignum_string(crypto->shared_secret);
+ }
if (k_string == NULL) {
ssh_set_error_oom(session);
goto error;
diff --git a/src/session.c b/src/session.c
index 9970f769..ddfbdfea 100644
--- a/src/session.c
+++ b/src/session.c
@@ -449,6 +449,8 @@ const char* ssh_get_kex_algo(ssh_session session) {
return "curve25519-sha256";
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
return "curve25519-sha256@libssh.org";
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
+ return "sntrup761x25519-sha512@openssh.com";
default:
break;
}
diff --git a/src/sntrup761.c b/src/sntrup761.c
new file mode 100644
index 00000000..ced7cd8a
--- /dev/null
+++ b/src/sntrup761.c
@@ -0,0 +1,466 @@
+/*
+ * sntrup761.c - SNTRUP761x25519 ECDH functions for key exchange
+ * sntrup761x25519-sha512@openssh.com - based on curve25519.c.
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2013 by Aris Adamantiadis
+ * Copyright (c) 2023 Simon Josefsson
+ * Copyright (c) 2025 Jakub Jelen
+ *
+ * The SSH Library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, version 2.1 of the License.
+ *
+ * The SSH Library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the SSH Library; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "libssh/sntrup761.h"
+#ifdef HAVE_SNTRUP761
+
+#include "libssh/bignum.h"
+#include "libssh/buffer.h"
+#include "libssh/crypto.h"
+#include "libssh/dh.h"
+#include "libssh/pki.h"
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/ssh2.h"
+
+void crypto_hash_sha512(unsigned char *out,
+ const unsigned char *in,
+ unsigned long long inlen)
+{
+ sha512(in, inlen, out);
+}
+
+static void crypto_random(void *ctx, size_t length, uint8_t *dst)
+{
+ int *err = ctx;
+ *err = ssh_get_random(dst, length, 1);
+}
+
+static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply);
+
+static ssh_packet_callback dh_client_callbacks[] = {
+ ssh_packet_client_sntrup761x25519_reply,
+};
+
+static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_client_callbacks =
+ {
+ .start = SSH2_MSG_KEX_ECDH_REPLY,
+ .n_callbacks = 1,
+ .callbacks = dh_client_callbacks,
+ .user = NULL,
+};
+
+static int ssh_sntrup761x25519_init(ssh_session session)
+{
+ int rc;
+
+ rc = ssh_curve25519_init(session);
+ if (rc != SSH_OK) {
+ return rc;
+ }
+
+ if (!session->server) {
+ sntrup761_keypair(session->next_crypto->sntrup761_client_pubkey,
+ session->next_crypto->sntrup761_privkey,
+ &rc,
+ crypto_random);
+ if (rc != 1) {
+ SSH_LOG(SSH_LOG_TRACE,
+ "Failed to generate sntrup761 key: PRNG failure");
+ return SSH_ERROR;
+ }
+ }
+
+ return SSH_OK;
+}
+
+/** @internal
+ * @brief Starts sntrup761x25519-sha512@openssh.com key exchange
+ */
+int ssh_client_sntrup761x25519_init(ssh_session session)
+{
+ int rc;
+
+ rc = ssh_sntrup761x25519_init(session);
+ if (rc != SSH_OK) {
+ return rc;
+ }
+
+ rc = ssh_buffer_pack(session->out_buffer,
+ "bdPP",
+ SSH2_MSG_KEX_ECDH_INIT,
+ CURVE25519_PUBKEY_SIZE + SNTRUP761_PUBLICKEY_SIZE,
+ (size_t)SNTRUP761_PUBLICKEY_SIZE,
+ session->next_crypto->sntrup761_client_pubkey,
+ (size_t)CURVE25519_PUBKEY_SIZE,
+ session->next_crypto->curve25519_client_pubkey);
+ if (rc != SSH_OK) {
+ ssh_set_error_oom(session);
+ return SSH_ERROR;
+ }
+
+ /* register the packet callbacks */
+ ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_client_callbacks);
+ session->dh_handshake_state = DH_STATE_INIT_SENT;
+ rc = ssh_packet_send(session);
+
+ return rc;
+}
+
+void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session)
+{
+ ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_client_callbacks);
+}
+
+static int ssh_sntrup761x25519_build_k(ssh_session session)
+{
+ unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE];
+ unsigned char *k = ssk + SNTRUP761_SIZE;
+ unsigned char hss[SHA512_DIGEST_LEN];
+ int rc;
+
+ rc = ssh_curve25519_create_k(session, k);
+ if (rc != SSH_OK) {
+ return SSH_ERROR;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_log_hexdump("Curve25519 shared secret", k, CURVE25519_PUBKEY_SIZE);
+#endif
+
+ if (session->server) {
+ sntrup761_enc(session->next_crypto->sntrup761_ciphertext,
+ ssk,
+ session->next_crypto->sntrup761_client_pubkey,
+ &rc,
+ crypto_random);
+ if (rc != 1) {
+ return SSH_ERROR;
+ }
+ } else {
+ sntrup761_dec(ssk,
+ session->next_crypto->sntrup761_ciphertext,
+ session->next_crypto->sntrup761_privkey);
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_log_hexdump("server cipher text",
+ session->next_crypto->sntrup761_ciphertext,
+ SNTRUP761_CIPHERTEXT_SIZE);
+ ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE);
+#endif
+
+ sha512(ssk, sizeof ssk, hss);
+
+ bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret);
+ if (session->next_crypto->shared_secret == NULL) {
+ return SSH_ERROR;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret);
+#endif
+
+ return 0;
+}
+
+/** @internal
+ * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back
+ * a SSH_MSG_NEWKEYS
+ */
+static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply)
+{
+ ssh_string q_s_string = NULL;
+ ssh_string pubkey_blob = NULL;
+ ssh_string signature = NULL;
+ int rc;
+ (void)type;
+ (void)user;
+
+ ssh_client_sntrup761x25519_remove_callbacks(session);
+
+ pubkey_blob = ssh_buffer_get_ssh_string(packet);
+ if (pubkey_blob == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No public key in packet");
+ goto error;
+ }
+
+ rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob);
+ SSH_STRING_FREE(pubkey_blob);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL, "Failed to import next public key");
+ goto error;
+ }
+
+ q_s_string = ssh_buffer_get_ssh_string(packet);
+ if (q_s_string == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_S in packet");
+ goto error;
+ }
+ if (ssh_string_len(q_s_string) != (SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)) {
+ ssh_set_error(session,
+ SSH_FATAL,
+ "Incorrect size for server sntrup761x25519 ciphertext+key: %d",
+ (int)ssh_string_len(q_s_string));
+ SSH_STRING_FREE(q_s_string);
+ goto error;
+ }
+ memcpy(session->next_crypto->sntrup761_ciphertext,
+ ssh_string_data(q_s_string),
+ SNTRUP761_CIPHERTEXT_SIZE);
+ memcpy(session->next_crypto->curve25519_server_pubkey,
+ (char *)ssh_string_data(q_s_string) + SNTRUP761_CIPHERTEXT_SIZE,
+ CURVE25519_PUBKEY_SIZE);
+ SSH_STRING_FREE(q_s_string);
+
+ signature = ssh_buffer_get_ssh_string(packet);
+ if (signature == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No signature in packet");
+ goto error;
+ }
+ session->next_crypto->dh_server_signature = signature;
+ signature = NULL; /* ownership changed */
+ /* TODO: verify signature now instead of waiting for NEWKEYS */
+ if (ssh_sntrup761x25519_build_k(session) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Cannot build k number");
+ goto error;
+ }
+
+ /* Send the MSG_NEWKEYS */
+ if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) {
+ goto error;
+ }
+
+ rc = ssh_packet_send(session);
+ if (rc == SSH_ERROR) {
+ goto error;
+ }
+
+ SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent");
+ session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
+
+ return SSH_PACKET_USED;
+
+error:
+ session->session_state = SSH_SESSION_STATE_ERROR;
+ return SSH_PACKET_USED;
+}
+
+#ifdef WITH_SERVER
+
+static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init);
+
+static ssh_packet_callback dh_server_callbacks[] = {
+ ssh_packet_server_sntrup761x25519_init,
+};
+
+static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_server_callbacks =
+ {
+ .start = SSH2_MSG_KEX_ECDH_INIT,
+ .n_callbacks = 1,
+ .callbacks = dh_server_callbacks,
+ .user = NULL,
+};
+
+/** @internal
+ * @brief sets up the sntrup761x25519-sha512@openssh.com kex callbacks
+ */
+void ssh_server_sntrup761x25519_init(ssh_session session)
+{
+ /* register the packet callbacks */
+ ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_server_callbacks);
+}
+
+/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a
+ * SSH_MSG_KEXDH_REPLY
+ */
+static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init)
+{
+ /* ECDH/SNTRUP761 keys */
+ ssh_string q_c_string = NULL;
+ ssh_string q_s_string = NULL;
+ ssh_string server_pubkey_blob = NULL;
+
+ /* SSH host keys (rsa, ed25519 and ecdsa) */
+ ssh_key privkey = NULL;
+ enum ssh_digest_e digest = SSH_DIGEST_AUTO;
+ ssh_string sig_blob = NULL;
+ int rc;
+ (void)type;
+ (void)user;
+
+ ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_server_callbacks);
+
+ /* Extract the client pubkey from the init packet */
+ q_c_string = ssh_buffer_get_ssh_string(packet);
+ if (q_c_string == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_C in packet");
+ goto error;
+ }
+ if (ssh_string_len(q_c_string) != (SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)) {
+ ssh_set_error(session,
+ SSH_FATAL,
+ "Incorrect size for server sntrup761x25519 public key: %zu",
+ ssh_string_len(q_c_string));
+ goto error;
+ }
+
+ memcpy(session->next_crypto->sntrup761_client_pubkey,
+ ssh_string_data(q_c_string),
+ SNTRUP761_PUBLICKEY_SIZE);
+ memcpy(session->next_crypto->curve25519_client_pubkey,
+ ((char *)ssh_string_data(q_c_string)) + SNTRUP761_PUBLICKEY_SIZE,
+ CURVE25519_PUBKEY_SIZE);
+ SSH_STRING_FREE(q_c_string);
+
+#ifdef DEBUG_CRYPTO
+ ssh_log_hexdump("client public key sntrup761",
+ session->next_crypto->sntrup761_client_pubkey,
+ SNTRUP761_PUBLICKEY_SIZE);
+ ssh_log_hexdump("client public key c25519",
+ session->next_crypto->curve25519_client_pubkey,
+ CURVE25519_PUBKEY_SIZE);
+#endif
+
+ /* Build server's key pair */
+ rc = ssh_sntrup761x25519_init(session);
+ if (rc != SSH_OK) {
+ ssh_set_error(session, SSH_FATAL, "Failed to generate sntrup761 keys");
+ goto error;
+ }
+
+ rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY);
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ /* build k and session_id */
+ rc = ssh_sntrup761x25519_build_k(session);
+ if (rc < 0) {
+ ssh_set_error(session, SSH_FATAL, "Cannot build k number");
+ goto error;
+ }
+
+ /* privkey is not allocated */
+ rc = ssh_get_key_params(session, &privkey, &digest);
+ if (rc == SSH_ERROR) {
+ goto error;
+ }
+
+ rc = ssh_make_sessionid(session);
+ if (rc != SSH_OK) {
+ ssh_set_error(session, SSH_FATAL, "Could not create a session id");
+ goto error;
+ }
+
+ rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL, "Could not export server public key");
+ goto error;
+ }
+
+ /* add host's public key */
+ rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob);
+ SSH_STRING_FREE(server_pubkey_blob);
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ /* add ecdh public key */
+ rc = ssh_buffer_add_u32(session->out_buffer,
+ ntohl(SNTRUP761_CIPHERTEXT_SIZE
+ + CURVE25519_PUBKEY_SIZE));
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ rc = ssh_buffer_add_data(session->out_buffer,
+ session->next_crypto->sntrup761_ciphertext,
+ SNTRUP761_CIPHERTEXT_SIZE);
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ rc = ssh_buffer_add_data(session->out_buffer,
+ session->next_crypto->curve25519_server_pubkey,
+ CURVE25519_PUBKEY_SIZE);
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_log_hexdump("server public key c25519",
+ session->next_crypto->curve25519_server_pubkey,
+ CURVE25519_PUBKEY_SIZE);
+#endif
+
+ /* add signature blob */
+ sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest);
+ if (sig_blob == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
+ goto error;
+ }
+
+ rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob);
+ SSH_STRING_FREE(sig_blob);
+ if (rc < 0) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_log_hexdump("ECDH_REPLY:",
+ ssh_buffer_get(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+#endif
+
+ SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent");
+ rc = ssh_packet_send(session);
+ if (rc == SSH_ERROR) {
+ return SSH_ERROR;
+ }
+
+ /* Send the MSG_NEWKEYS */
+ rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS);
+ if (rc < 0) {
+ goto error;
+ }
+
+ session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
+ rc = ssh_packet_send(session);
+ if (rc == SSH_ERROR) {
+ goto error;
+ }
+ SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent");
+
+ return SSH_PACKET_USED;
+error:
+ SSH_STRING_FREE(q_c_string);
+ SSH_STRING_FREE(q_s_string);
+ ssh_buffer_reinit(session->out_buffer);
+ session->session_state = SSH_SESSION_STATE_ERROR;
+ return SSH_PACKET_USED;
+}
+
+#endif /* WITH_SERVER */
+
+#endif /* HAVE_SNTRUP761 */
diff --git a/src/wrapper.c b/src/wrapper.c
index 55fa0ca4..7a181d92 100644
--- a/src/wrapper.c
+++ b/src/wrapper.c
@@ -48,8 +48,9 @@
#ifdef WITH_GEX
#include "libssh/dh-gex.h"
#endif /* WITH_GEX */
-#include "libssh/ecdh.h"
#include "libssh/curve25519.h"
+#include "libssh/ecdh.h"
+#include "libssh/sntrup761.h"
static struct ssh_hmac_struct ssh_hmac_tab[] = {
{ "hmac-sha1", SSH_HMAC_SHA1, false },
@@ -586,6 +587,11 @@ int crypt_set_algorithms_server(ssh_session session){
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
ssh_server_curve25519_init(session);
break;
+#endif
+#ifdef HAVE_SNTRUP761
+ case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
+ ssh_server_sntrup761x25519_init(session);
+ break;
#endif
default:
ssh_set_error(session,
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 89b95d08..f545e7b2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -135,6 +135,7 @@ if (SSH_EXECUTABLE)
diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256
diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521
+ sntrup761x25519-sha512@openssh.com
curve25519-sha256 curve25519-sha256@libssh.org
ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521
diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c
index d1190605..95dd569e 100644
--- a/tests/client/torture_algorithms.c
+++ b/tests/client/torture_algorithms.c
@@ -718,6 +718,23 @@ static void torture_algorithms_ecdh_curve25519_sha256_libssh_org(void **state) {
}
#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+static void
+torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com(void **state)
+{
+ struct torture_state *s = *state;
+
+ if (ssh_fips_mode()) {
+ skip();
+ }
+
+ test_algorithm(s->ssh.session,
+ "sntrup761x25519-sha512@openssh.com",
+ NULL /*cipher*/,
+ NULL /*hmac*/);
+}
+#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */
+
static void torture_algorithms_dh_group1(void **state) {
struct torture_state *s = *state;
@@ -985,6 +1002,11 @@ int torture_run_tests(void) {
session_setup,
session_teardown),
#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+ cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com,
+ session_setup,
+ session_teardown),
+#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */
#if defined(HAVE_ECC)
cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256,
session_setup,
diff --git a/tests/external_override/torture_override.c b/tests/external_override/torture_override.c
index 8024574b..5bfbcba8 100644
--- a/tests/external_override/torture_override.c
+++ b/tests/external_override/torture_override.c
@@ -255,6 +255,37 @@ static void torture_override_ecdh_curve25519_sha256_libssh_org(void **state)
}
#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+static void
+torture_override_ecdh_sntrup761x25519_sha512_openssh_com(void **state)
+{
+ struct torture_state *s = *state;
+ bool internal_curve25519_called;
+
+ if (ssh_fips_mode()) {
+ skip();
+ }
+
+ test_algorithm(s->ssh.session,
+ "sntrup761x25519-sha512@openssh.com",
+ NULL, /* cipher */
+ NULL /* hostkey */);
+
+ internal_curve25519_called = internal_curve25519_function_called();
+
+ /* TODO: when non-internal sntrup761 is supported, this is a good
+ place to add override checks of the sntrup761-related functions
+ too. Currently none of our external crypto libraries supports
+ sntrup761. */
+
+#if SHOULD_CALL_INTERNAL_CURVE25519
+ assert_true(internal_curve25519_called);
+#else
+ assert_false(internal_curve25519_called);
+#endif
+}
+#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */
+
#ifdef OPENSSH_SSH_ED25519
static void torture_override_ed25519(void **state)
{
@@ -299,6 +330,11 @@ int torture_run_tests(void)
session_setup,
session_teardown),
#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+ cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512_openssh_com,
+ session_setup,
+ session_teardown),
+#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */
#ifdef OPENSSH_SSH_ED25519
cmocka_unit_test_setup_teardown(torture_override_ed25519,
session_setup,
diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c
index 069ed8df..2f13646a 100644
--- a/tests/pkd/pkd_hello.c
+++ b/tests/pkd/pkd_hello.c
@@ -280,8 +280,19 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown)
#endif
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \
+ f(client, rsa_sntrup761x25519_sha512_openssh_com, kexcmd("sntrup761x25519-sha512@openssh.com"), setup_rsa, teardown) \
+ f(client, ecdsa_256_sntrup761x25519_sha512_openssh_com, kexcmd("sntrup761x25519-sha512@openssh.com"), setup_ecdsa_256, teardown) \
+ f(client, ecdsa_384_sntrup761x25519_sha512_openssh_com, kexcmd("sntrup761x25519-sha512@openssh.com"), setup_ecdsa_384, teardown) \
+ f(client, ecdsa_521_sntrup761x25519_sha512_openssh_com, kexcmd("sntrup761x25519-sha512@openssh.com"), setup_ecdsa_521, teardown)
+#else
+#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd)
+#endif
+
#define PKDTESTS_KEX_COMMON(f, client, kexcmd) \
PKDTESTS_KEX_FIPS(f, client, kexcmd) \
+ PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \
f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown) \
f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown) \
f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown) \
@@ -314,8 +325,16 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
PKDTESTS_KEX_COMMON(f, client, kexcmd)
#endif
+#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM
+#define PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd) \
+ f(client, ed25519_sntrup761x25519_sha512_openssh_com, kexcmd("sntrup761x25519-sha512@openssh.com"), setup_ed25519, teardown)
+#else
+#define PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd)
+#endif
+
#define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \
/* Kex algorithms. */ \
+ PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd) \
f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \
f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \
f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \
diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake
index 3b6bde0e..356d48cb 100644
--- a/tests/tests_config.h.cmake
+++ b/tests/tests_config.h.cmake
@@ -49,6 +49,7 @@
#cmakedefine OPENSSH_ECDH_SHA2_NISTP521 1
#cmakedefine OPENSSH_CURVE25519_SHA256 1
#cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1
+#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1
#cmakedefine OPENSSH_SSH_ED25519 1
#cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1
#cmakedefine OPENSSH_SSH_RSA 1
diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c
index db2b4693..7c08c705 100644
--- a/tests/unittests/torture_options.c
+++ b/tests/unittests/torture_options.c
@@ -216,6 +216,7 @@ static void torture_options_set_key_exchange(void **state)
/* Test known kexes */
rc = ssh_options_set(session,
SSH_OPTIONS_KEY_EXCHANGE,
+ "sntrup761x25519-sha512@openssh.com,"
"curve25519-sha256,curve25519-sha256@libssh.org,"
"ecdh-sha2-nistp256,diffie-hellman-group16-sha512,"
"diffie-hellman-group18-sha512,"
@@ -230,6 +231,7 @@ static void torture_options_set_key_exchange(void **state)
"diffie-hellman-group14-sha256");
} else {
assert_string_equal(session->opts.wanted_methods[SSH_KEX],
+ "sntrup761x25519-sha512@openssh.com,"
"curve25519-sha256,curve25519-sha256@libssh.org,"
"ecdh-sha2-nistp256,diffie-hellman-group16-sha512,"
"diffie-hellman-group18-sha512,"
@@ -278,6 +280,7 @@ static void torture_options_get_key_exchange(void **state)
} else {
assert_string_equal(value,
"curve25519-sha256,curve25519-sha256@libssh.org,"
+ "sntrup761x25519-sha512@openssh.com,"
"ecdh-sha2-nistp256,ecdh-sha2-nistp384,"
"ecdh-sha2-nistp521,diffie-hellman-group18-sha512,"
"diffie-hellman-group16-sha512,"
@@ -1312,7 +1315,7 @@ static void torture_options_copy(void **state)
"BindAddress 127.0.0.2\n"
"GlobalKnownHostsFile /etc/ssh/known_hosts2\n"
"UserKnownHostsFile ~/.ssh/known_hosts2\n"
- "KexAlgorithms curve25519-sha256,ecdh-sha2-nistp521\n"
+ "KexAlgorithms curve25519-sha256,sntrup761x25519-sha512@openssh.com,ecdh-sha2-nistp521\n"
"Ciphers aes256-ctr\n"
"MACs hmac-sha2-256\n"
"HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n"