Coverage Report

Created: 2025-11-24 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/sntrup761.c
Line
Count
Source
1
/*
2
 * sntrup761.c - SNTRUP761x25519 ECDH functions for key exchange
3
 * sntrup761x25519-sha512@openssh.com - based on curve25519.c.
4
 *
5
 * This file is part of the SSH Library
6
 *
7
 * Copyright (c) 2013      by Aris Adamantiadis <aris@badcode.be>
8
 * Copyright (c) 2023 Simon Josefsson <simon@josefsson.org>
9
 * Copyright (c) 2025 Jakub Jelen <jjelen@redhat.com>
10
 *
11
 * The SSH Library is free software; you can redistribute it and/or modify
12
 * it under the terms of the GNU Lesser General Public License as published by
13
 * the Free Software Foundation, version 2.1 of the License.
14
 *
15
 * The SSH Library is distributed in the hope that it will be useful, but
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
18
 * License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public License
21
 * along with the SSH Library; see the file COPYING.  If not, write to
22
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
23
 * MA 02111-1307, USA.
24
 */
25
26
#include "config.h"
27
28
#include "libssh/sntrup761.h"
29
#ifdef HAVE_SNTRUP761
30
31
#include "libssh/bignum.h"
32
#include "libssh/buffer.h"
33
#include "libssh/crypto.h"
34
#include "libssh/dh.h"
35
#include "libssh/pki.h"
36
#include "libssh/priv.h"
37
#include "libssh/session.h"
38
#include "libssh/ssh2.h"
39
40
void crypto_hash_sha512(unsigned char *out,
41
                        const unsigned char *in,
42
                        unsigned long long inlen)
43
0
{
44
0
    sha512(in, inlen, out);
45
0
}
46
47
#ifndef HAVE_LIBGCRYPT
48
static void crypto_random(void *ctx, size_t length, uint8_t *dst)
49
0
{
50
0
    int *err = ctx;
51
0
    *err = ssh_get_random(dst, length, 1);
52
0
}
53
#endif /* HAVE_LIBGCRYPT */
54
55
static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply);
56
57
static ssh_packet_callback dh_client_callbacks[] = {
58
    ssh_packet_client_sntrup761x25519_reply,
59
};
60
61
static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_client_callbacks =
62
    {
63
        .start = SSH2_MSG_KEX_ECDH_REPLY,
64
        .n_callbacks = 1,
65
        .callbacks = dh_client_callbacks,
66
        .user = NULL,
67
};
68
69
static int ssh_sntrup761x25519_init(ssh_session session)
70
0
{
71
0
    int rc;
72
73
0
    rc = ssh_curve25519_init(session);
74
0
    if (rc != SSH_OK) {
75
0
        return rc;
76
0
    }
77
78
0
    if (!session->server) {
79
#ifdef HAVE_LIBGCRYPT
80
        gcry_error_t err;
81
82
        err = gcry_kem_keypair(GCRY_KEM_SNTRUP761,
83
                               session->next_crypto->sntrup761_client_pubkey,
84
                               SNTRUP761_PUBLICKEY_SIZE,
85
                               session->next_crypto->sntrup761_privkey,
86
                               SNTRUP761_SECRETKEY_SIZE);
87
        if (err) {
88
            SSH_LOG(SSH_LOG_TRACE,
89
                    "Failed to generate sntrup761 key: %s",
90
                    gpg_strerror(err));
91
            return SSH_ERROR;
92
        }
93
#else
94
0
        sntrup761_keypair(session->next_crypto->sntrup761_client_pubkey,
95
0
                          session->next_crypto->sntrup761_privkey,
96
0
                          &rc,
97
0
                          crypto_random);
98
0
        if (rc != 1) {
99
0
            SSH_LOG(SSH_LOG_TRACE,
100
0
                    "Failed to generate sntrup761 key: PRNG failure");
101
0
            return SSH_ERROR;
102
0
        }
103
0
#endif /* HAVE_LIBGCRYPT */
104
0
    }
105
106
0
    return SSH_OK;
107
0
}
108
109
/** @internal
110
 * @brief Starts sntrup761x25519-sha512@openssh.com key exchange
111
 */
112
int ssh_client_sntrup761x25519_init(ssh_session session)
113
0
{
114
0
    int rc;
115
116
0
    rc = ssh_sntrup761x25519_init(session);
117
0
    if (rc != SSH_OK) {
118
0
        return rc;
119
0
    }
120
121
0
    rc = ssh_buffer_pack(session->out_buffer,
122
0
                         "bdPP",
123
0
                         SSH2_MSG_KEX_ECDH_INIT,
124
0
                         CURVE25519_PUBKEY_SIZE + SNTRUP761_PUBLICKEY_SIZE,
125
0
                         (size_t)SNTRUP761_PUBLICKEY_SIZE,
126
0
                         session->next_crypto->sntrup761_client_pubkey,
127
0
                         (size_t)CURVE25519_PUBKEY_SIZE,
128
0
                         session->next_crypto->curve25519_client_pubkey);
129
0
    if (rc != SSH_OK) {
130
0
        ssh_set_error_oom(session);
131
0
        return SSH_ERROR;
132
0
    }
133
134
    /* register the packet callbacks */
135
0
    ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_client_callbacks);
136
0
    session->dh_handshake_state = DH_STATE_INIT_SENT;
137
0
    rc = ssh_packet_send(session);
138
139
0
    return rc;
140
0
}
141
142
void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session)
143
0
{
144
0
    ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_client_callbacks);
145
0
}
146
147
static int ssh_sntrup761x25519_build_k(ssh_session session)
148
0
{
149
0
    unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE];
150
0
    unsigned char *k = ssk + SNTRUP761_SIZE;
151
0
    unsigned char hss[SHA512_DIGEST_LEN];
152
0
    int rc;
153
154
0
    rc = ssh_curve25519_create_k(session, k);
155
0
    if (rc != SSH_OK) {
156
0
        return SSH_ERROR;
157
0
    }
158
159
#ifdef DEBUG_CRYPTO
160
    ssh_log_hexdump("Curve25519 shared secret", k, CURVE25519_PUBKEY_SIZE);
161
#endif
162
163
#ifdef HAVE_LIBGCRYPT
164
    if (session->server) {
165
        gcry_error_t err;
166
        err = gcry_kem_encap(GCRY_KEM_SNTRUP761,
167
                             session->next_crypto->sntrup761_client_pubkey,
168
                             SNTRUP761_PUBLICKEY_SIZE,
169
                             session->next_crypto->sntrup761_ciphertext,
170
                             SNTRUP761_CIPHERTEXT_SIZE,
171
                             ssk,
172
                             SNTRUP761_SIZE,
173
                             NULL,
174
                             0);
175
        if (err) {
176
            SSH_LOG(SSH_LOG_TRACE,
177
                    "Failed to encapsulate sntrup761 shared secret: %s",
178
                    gpg_strerror(err));
179
            return SSH_ERROR;
180
        }
181
    } else {
182
        gcry_error_t err;
183
        err = gcry_kem_decap(GCRY_KEM_SNTRUP761,
184
                             session->next_crypto->sntrup761_privkey,
185
                             SNTRUP761_SECRETKEY_SIZE,
186
                             session->next_crypto->sntrup761_ciphertext,
187
                             SNTRUP761_CIPHERTEXT_SIZE,
188
                             ssk,
189
                             SNTRUP761_SIZE,
190
                             NULL,
191
                             0);
192
        if (err) {
193
            SSH_LOG(SSH_LOG_TRACE,
194
                    "Failed to decapsulate sntrup761 shared secret: %s",
195
                    gpg_strerror(err));
196
            return SSH_ERROR;
197
        }
198
    }
199
#else
200
0
    if (session->server) {
201
0
        sntrup761_enc(session->next_crypto->sntrup761_ciphertext,
202
0
                      ssk,
203
0
                      session->next_crypto->sntrup761_client_pubkey,
204
0
                      &rc,
205
0
                      crypto_random);
206
0
        if (rc != 1) {
207
0
            return SSH_ERROR;
208
0
        }
209
0
    } else {
210
0
        sntrup761_dec(ssk,
211
0
                      session->next_crypto->sntrup761_ciphertext,
212
0
                      session->next_crypto->sntrup761_privkey);
213
0
    }
214
0
#endif /* HAVE_LIBGCRYPT */
215
216
#ifdef DEBUG_CRYPTO
217
    ssh_log_hexdump("server cipher text",
218
                    session->next_crypto->sntrup761_ciphertext,
219
                    SNTRUP761_CIPHERTEXT_SIZE);
220
    ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE);
221
#endif
222
223
0
    sha512(ssk, sizeof ssk, hss);
224
225
0
    bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret);
226
0
    if (session->next_crypto->shared_secret == NULL) {
227
0
        return SSH_ERROR;
228
0
    }
229
230
#ifdef DEBUG_CRYPTO
231
    ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret);
232
#endif
233
234
0
    return 0;
235
0
}
236
237
/** @internal
238
 * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back
239
 * a SSH_MSG_NEWKEYS
240
 */
241
static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply)
242
0
{
243
0
    ssh_string q_s_string = NULL;
244
0
    ssh_string pubkey_blob = NULL;
245
0
    ssh_string signature = NULL;
246
0
    int rc;
247
0
    (void)type;
248
0
    (void)user;
249
250
0
    ssh_client_sntrup761x25519_remove_callbacks(session);
251
252
0
    pubkey_blob = ssh_buffer_get_ssh_string(packet);
253
0
    if (pubkey_blob == NULL) {
254
0
        ssh_set_error(session, SSH_FATAL, "No public key in packet");
255
0
        goto error;
256
0
    }
257
258
0
    rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob);
259
0
    SSH_STRING_FREE(pubkey_blob);
260
0
    if (rc != 0) {
261
0
        ssh_set_error(session, SSH_FATAL, "Failed to import next public key");
262
0
        goto error;
263
0
    }
264
265
0
    q_s_string = ssh_buffer_get_ssh_string(packet);
266
0
    if (q_s_string == NULL) {
267
0
        ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_S in packet");
268
0
        goto error;
269
0
    }
270
0
    if (ssh_string_len(q_s_string) != (SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)) {
271
0
        ssh_set_error(session,
272
0
                      SSH_FATAL,
273
0
                      "Incorrect size for server sntrup761x25519 ciphertext+key: %d",
274
0
                      (int)ssh_string_len(q_s_string));
275
0
        SSH_STRING_FREE(q_s_string);
276
0
        goto error;
277
0
    }
278
0
    memcpy(session->next_crypto->sntrup761_ciphertext,
279
0
           ssh_string_data(q_s_string),
280
0
           SNTRUP761_CIPHERTEXT_SIZE);
281
0
    memcpy(session->next_crypto->curve25519_server_pubkey,
282
0
           (char *)ssh_string_data(q_s_string) + SNTRUP761_CIPHERTEXT_SIZE,
283
0
           CURVE25519_PUBKEY_SIZE);
284
0
    SSH_STRING_FREE(q_s_string);
285
286
0
    signature = ssh_buffer_get_ssh_string(packet);
287
0
    if (signature == NULL) {
288
0
        ssh_set_error(session, SSH_FATAL, "No signature in packet");
289
0
        goto error;
290
0
    }
291
0
    session->next_crypto->dh_server_signature = signature;
292
0
    signature = NULL; /* ownership changed */
293
    /* TODO: verify signature now instead of waiting for NEWKEYS */
294
0
    if (ssh_sntrup761x25519_build_k(session) < 0) {
295
0
        ssh_set_error(session, SSH_FATAL, "Cannot build k number");
296
0
        goto error;
297
0
    }
298
299
    /* Send the MSG_NEWKEYS */
300
0
    if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) {
301
0
        goto error;
302
0
    }
303
304
0
    rc = ssh_packet_send(session);
305
0
    if (rc == SSH_ERROR) {
306
0
        goto error;
307
0
    }
308
309
0
    SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent");
310
0
    session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
311
312
0
    return SSH_PACKET_USED;
313
314
0
error:
315
0
    session->session_state = SSH_SESSION_STATE_ERROR;
316
0
    return SSH_PACKET_USED;
317
0
}
318
319
#ifdef WITH_SERVER
320
321
static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init);
322
323
static ssh_packet_callback dh_server_callbacks[] = {
324
    ssh_packet_server_sntrup761x25519_init,
325
};
326
327
static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_server_callbacks =
328
    {
329
        .start = SSH2_MSG_KEX_ECDH_INIT,
330
        .n_callbacks = 1,
331
        .callbacks = dh_server_callbacks,
332
        .user = NULL,
333
};
334
335
/** @internal
336
 * @brief sets up the sntrup761x25519-sha512@openssh.com kex callbacks
337
 */
338
void ssh_server_sntrup761x25519_init(ssh_session session)
339
0
{
340
    /* register the packet callbacks */
341
0
    ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_server_callbacks);
342
0
}
343
344
/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a
345
 * SSH_MSG_KEXDH_REPLY
346
 */
347
static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init)
348
0
{
349
    /* ECDH/SNTRUP761 keys */
350
0
    ssh_string q_c_string = NULL;
351
0
    ssh_string q_s_string = NULL;
352
0
    ssh_string server_pubkey_blob = NULL;
353
354
    /* SSH host keys (rsa, ed25519 and ecdsa) */
355
0
    ssh_key privkey = NULL;
356
0
    enum ssh_digest_e digest = SSH_DIGEST_AUTO;
357
0
    ssh_string sig_blob = NULL;
358
0
    int rc;
359
0
    (void)type;
360
0
    (void)user;
361
362
0
    ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_server_callbacks);
363
364
    /* Extract the client pubkey from the init packet */
365
0
    q_c_string = ssh_buffer_get_ssh_string(packet);
366
0
    if (q_c_string == NULL) {
367
0
        ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_C in packet");
368
0
        goto error;
369
0
    }
370
0
    if (ssh_string_len(q_c_string) != (SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)) {
371
0
        ssh_set_error(session,
372
0
                      SSH_FATAL,
373
0
                      "Incorrect size for server sntrup761x25519 public key: %zu",
374
0
                      ssh_string_len(q_c_string));
375
0
        goto error;
376
0
    }
377
378
0
    memcpy(session->next_crypto->sntrup761_client_pubkey,
379
0
           ssh_string_data(q_c_string),
380
0
           SNTRUP761_PUBLICKEY_SIZE);
381
0
    memcpy(session->next_crypto->curve25519_client_pubkey,
382
0
           ((char *)ssh_string_data(q_c_string)) + SNTRUP761_PUBLICKEY_SIZE,
383
0
           CURVE25519_PUBKEY_SIZE);
384
0
    SSH_STRING_FREE(q_c_string);
385
386
#ifdef DEBUG_CRYPTO
387
    ssh_log_hexdump("client public key sntrup761",
388
                    session->next_crypto->sntrup761_client_pubkey,
389
                    SNTRUP761_PUBLICKEY_SIZE);
390
    ssh_log_hexdump("client public key c25519",
391
                    session->next_crypto->curve25519_client_pubkey,
392
                    CURVE25519_PUBKEY_SIZE);
393
#endif
394
395
    /* Build server's key pair */
396
0
    rc = ssh_sntrup761x25519_init(session);
397
0
    if (rc != SSH_OK) {
398
0
        ssh_set_error(session, SSH_FATAL, "Failed to generate sntrup761 keys");
399
0
        goto error;
400
0
    }
401
402
0
    rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY);
403
0
    if (rc < 0) {
404
0
        ssh_set_error_oom(session);
405
0
        goto error;
406
0
    }
407
408
    /* build k and session_id */
409
0
    rc = ssh_sntrup761x25519_build_k(session);
410
0
    if (rc < 0) {
411
0
        ssh_set_error(session, SSH_FATAL, "Cannot build k number");
412
0
        goto error;
413
0
    }
414
415
    /* privkey is not allocated */
416
0
    rc = ssh_get_key_params(session, &privkey, &digest);
417
0
    if (rc == SSH_ERROR) {
418
0
        goto error;
419
0
    }
420
421
0
    rc = ssh_make_sessionid(session);
422
0
    if (rc != SSH_OK) {
423
0
        ssh_set_error(session, SSH_FATAL, "Could not create a session id");
424
0
        goto error;
425
0
    }
426
427
0
    rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob);
428
0
    if (rc != 0) {
429
0
        ssh_set_error(session, SSH_FATAL, "Could not export server public key");
430
0
        goto error;
431
0
    }
432
433
    /* add host's public key */
434
0
    rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob);
435
0
    SSH_STRING_FREE(server_pubkey_blob);
436
0
    if (rc < 0) {
437
0
        ssh_set_error_oom(session);
438
0
        goto error;
439
0
    }
440
441
    /* add ecdh public key */
442
0
    rc = ssh_buffer_add_u32(session->out_buffer,
443
0
                            ntohl(SNTRUP761_CIPHERTEXT_SIZE
444
0
                                  + CURVE25519_PUBKEY_SIZE));
445
0
    if (rc < 0) {
446
0
        ssh_set_error_oom(session);
447
0
        goto error;
448
0
    }
449
450
0
    rc = ssh_buffer_add_data(session->out_buffer,
451
0
                             session->next_crypto->sntrup761_ciphertext,
452
0
                             SNTRUP761_CIPHERTEXT_SIZE);
453
0
    if (rc < 0) {
454
0
        ssh_set_error_oom(session);
455
0
        goto error;
456
0
    }
457
458
0
    rc = ssh_buffer_add_data(session->out_buffer,
459
0
                             session->next_crypto->curve25519_server_pubkey,
460
0
                             CURVE25519_PUBKEY_SIZE);
461
0
    if (rc < 0) {
462
0
        ssh_set_error_oom(session);
463
0
        goto error;
464
0
    }
465
466
#ifdef DEBUG_CRYPTO
467
    ssh_log_hexdump("server public key c25519",
468
                    session->next_crypto->curve25519_server_pubkey,
469
                    CURVE25519_PUBKEY_SIZE);
470
#endif
471
472
    /* add signature blob */
473
0
    sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest);
474
0
    if (sig_blob == NULL) {
475
0
        ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
476
0
        goto error;
477
0
    }
478
479
0
    rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob);
480
0
    SSH_STRING_FREE(sig_blob);
481
0
    if (rc < 0) {
482
0
        ssh_set_error_oom(session);
483
0
        goto error;
484
0
    }
485
486
#ifdef DEBUG_CRYPTO
487
    ssh_log_hexdump("ECDH_REPLY:",
488
                    ssh_buffer_get(session->out_buffer),
489
                    ssh_buffer_get_len(session->out_buffer));
490
#endif
491
492
0
    SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent");
493
0
    rc = ssh_packet_send(session);
494
0
    if (rc == SSH_ERROR) {
495
0
        return SSH_ERROR;
496
0
    }
497
498
    /* Send the MSG_NEWKEYS */
499
0
    rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS);
500
0
    if (rc < 0) {
501
0
        goto error;
502
0
    }
503
504
0
    session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
505
0
    rc = ssh_packet_send(session);
506
0
    if (rc == SSH_ERROR) {
507
0
        goto error;
508
0
    }
509
0
    SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent");
510
511
0
    return SSH_PACKET_USED;
512
0
error:
513
0
    SSH_STRING_FREE(q_c_string);
514
0
    SSH_STRING_FREE(q_s_string);
515
0
    ssh_buffer_reinit(session->out_buffer);
516
0
    session->session_state = SSH_SESSION_STATE_ERROR;
517
0
    return SSH_PACKET_USED;
518
0
}
519
520
#endif /* WITH_SERVER */
521
522
#endif /* HAVE_SNTRUP761 */