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"