From 4becc8eb827c3f6740c09a8206fc9931350cb857 Mon Sep 17 00:00:00 2001 From: Simon Josefsson Date: Wed, 2 Aug 2023 11:39:48 +0200 Subject: [PATCH] kex: Add sntrup761x25519-sha512@openssh.com. All of the initial work was done by Simon. Jakub cleaned up the formatting issues, resolved the padding of bignum to match specs and be interoperable with OpenSSH (and few more minor details). Closes: #194. Signed-off-by: Simon Josefsson Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider Reviewed-by: Sahana Prasad --- doc/mainpage.dox | 2 +- include/libssh/crypto.h | 10 +- include/libssh/sntrup761.h | 86 ++ src/CMakeLists.txt | 2 + src/client.c | 5 + src/external/sntrup761.c | 1060 ++++++++++++++++++++ src/kex.c | 52 +- src/session.c | 2 + src/sntrup761.c | 466 +++++++++ src/wrapper.c | 8 +- tests/CMakeLists.txt | 1 + tests/client/torture_algorithms.c | 22 + tests/external_override/torture_override.c | 36 + tests/pkd/pkd_hello.c | 19 + tests/tests_config.h.cmake | 1 + tests/unittests/torture_options.c | 5 +- 16 files changed, 1771 insertions(+), 6 deletions(-) create mode 100644 include/libssh/sntrup761.h create mode 100644 src/external/sntrup761.c create mode 100644 src/sntrup761.c 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"