Coverage Report

Created: 2026-09-03 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/mlkem_native.c
Line
Count
Source
1
/*
2
 * This file is part of the SSH Library
3
 *
4
 * Copyright (c) 2025 by Red Hat, Inc.
5
 *
6
 * Author: Jakub Jelen <jjelen@redhat.com>
7
 *
8
 * The SSH Library is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation, version 2.1 of the License.
11
 *
12
 * The SSH Library is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15
 * License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with the SSH Library; see the file COPYING.  If not, write to
19
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
20
 * MA 02111-1307, USA.
21
 */
22
23
#include "config.h"
24
25
#include "libssh/crypto.h"
26
#include "libssh/mlkem.h"
27
#include "libssh/mlkem_native.h"
28
#include "libssh/session.h"
29
30
#define crypto_kem_mlkem768_PUBLICKEYBYTES  1184
31
#define crypto_kem_mlkem768_SECRETKEYBYTES  2400
32
#define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088
33
34
const struct mlkem_type_info MLKEM768_INFO = {
35
    .pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES,
36
    .privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES,
37
    .ciphertext_size = crypto_kem_mlkem768_CIPHERTEXTBYTES,
38
};
39
40
int ssh_mlkem_init(ssh_session session)
41
416
{
42
416
    int ret = SSH_ERROR;
43
416
    struct ssh_crypto_struct *crypto = session->next_crypto;
44
416
    const struct mlkem_type_info *mlkem_info = NULL;
45
416
    unsigned char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN];
46
416
    struct libcrux_mlkem768_keypair keypair;
47
416
    int err;
48
49
416
    mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
50
416
    if (mlkem_info == NULL) {
51
0
        SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
52
0
        goto cleanup;
53
0
    }
54
55
416
    err = ssh_get_random(rnd, sizeof(rnd), 0);
56
416
    if (err != 1) {
57
0
        SSH_LOG(SSH_LOG_WARNING,
58
0
                "Failed to generate random data for ML-KEM keygen");
59
0
        goto cleanup;
60
0
    }
61
62
416
    keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd);
63
64
416
    if (ssh_string_len(crypto->mlkem_client_pubkey) < mlkem_info->pubkey_size) {
65
416
        SSH_STRING_FREE(crypto->mlkem_client_pubkey);
66
416
    }
67
416
    if (crypto->mlkem_client_pubkey == NULL) {
68
416
        crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size);
69
416
        if (crypto->mlkem_client_pubkey == NULL) {
70
0
            ssh_set_error_oom(session);
71
0
            goto cleanup;
72
0
        }
73
416
    }
74
416
    err = ssh_string_fill(crypto->mlkem_client_pubkey,
75
416
                          keypair.pk.value,
76
416
                          mlkem_info->pubkey_size);
77
416
    if (err) {
78
0
        SSH_LOG(SSH_LOG_WARNING,
79
0
                "Failed to fill the string with client pubkey");
80
0
        goto cleanup;
81
0
    }
82
83
416
    if (crypto->mlkem_privkey == NULL) {
84
416
        crypto->mlkem_privkey = malloc(mlkem_info->privkey_size);
85
416
        if (crypto->mlkem_privkey == NULL) {
86
0
            ssh_set_error_oom(session);
87
0
            goto cleanup;
88
0
        }
89
416
    }
90
416
    memcpy(crypto->mlkem_privkey, keypair.sk.value, mlkem_info->privkey_size);
91
416
    crypto->mlkem_privkey_len = mlkem_info->privkey_size;
92
93
416
    ret = SSH_OK;
94
95
416
cleanup:
96
416
    ssh_burn(&keypair, sizeof(keypair));
97
416
    ssh_burn(rnd, sizeof(rnd));
98
416
    return ret;
99
416
}
100
101
int ssh_mlkem_encapsulate(ssh_session session,
102
                          ssh_mlkem_shared_secret shared_secret)
103
419
{
104
419
    int ret = SSH_ERROR;
105
419
    const struct mlkem_type_info *mlkem_info = NULL;
106
419
    struct ssh_crypto_struct *crypto = session->next_crypto;
107
419
    const unsigned char *pubkey_data = NULL;
108
419
    ssh_string pubkey = crypto->mlkem_client_pubkey;
109
419
    struct libcrux_mlkem768_enc_result enc;
110
419
    struct libcrux_mlkem768_pk mlkem_pub = {0};
111
419
    unsigned char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN];
112
419
    int err;
113
114
419
    if (pubkey == NULL) {
115
0
        SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session");
116
0
        return SSH_ERROR;
117
0
    }
118
119
419
    mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
120
419
    if (mlkem_info == NULL) {
121
0
        SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
122
0
        return SSH_ERROR;
123
0
    }
124
125
419
    pubkey_data = ssh_string_data(pubkey);
126
419
    memcpy(mlkem_pub.value, pubkey_data, mlkem_info->pubkey_size);
127
419
    err = libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub);
128
419
    if (err == 0) {
129
131
        SSH_LOG(SSH_LOG_WARNING, "Invalid public key");
130
131
        return SSH_ERROR;
131
131
    }
132
133
288
    err = ssh_get_random(rnd, sizeof(rnd), 0);
134
288
    if (err != 1) {
135
0
        SSH_LOG(SSH_LOG_WARNING,
136
0
                "Failed to generate random data for ML-KEM keygen");
137
0
        goto cleanup;
138
0
    }
139
140
288
    enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd);
141
142
288
    if (ssh_string_len(crypto->mlkem_ciphertext) < mlkem_info->ciphertext_size) {
143
288
        SSH_STRING_FREE(crypto->mlkem_ciphertext);
144
288
    }
145
288
    if (crypto->mlkem_ciphertext == NULL) {
146
288
        crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size);
147
288
        if (crypto->mlkem_ciphertext == NULL) {
148
0
            ssh_set_error_oom(session);
149
0
            goto cleanup;
150
0
        }
151
288
    }
152
288
    err = ssh_string_fill(crypto->mlkem_ciphertext,
153
288
                          enc.fst.value,
154
288
                          sizeof(enc.fst.value));
155
288
    if (err != SSH_OK) {
156
0
        SSH_LOG(SSH_LOG_WARNING, "Failed to fill the string with ciphertext");
157
0
        goto cleanup;
158
0
    }
159
288
    memcpy(shared_secret, enc.snd, sizeof(enc.snd));
160
161
288
    ret = SSH_OK;
162
163
288
cleanup:
164
288
    ssh_burn(rnd, sizeof(rnd));
165
288
    ssh_burn(&enc, sizeof(enc));
166
288
    return ret;
167
288
}
168
169
int ssh_mlkem_decapsulate(const ssh_session session,
170
                          ssh_mlkem_shared_secret shared_secret)
171
281
{
172
281
    const struct mlkem_type_info *mlkem_info = NULL;
173
281
    struct ssh_crypto_struct *crypto = session->next_crypto;
174
281
    ssh_string ciphertext = NULL;
175
281
    unsigned char *ciphertext_data = NULL;
176
281
    struct libcrux_mlkem768_sk mlkem_priv = {0};
177
281
    struct libcrux_mlkem768_ciphertext mlkem_ciphertext = {0};
178
179
281
    mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
180
281
    if (mlkem_info == NULL) {
181
0
        SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
182
0
        return SSH_ERROR;
183
0
    }
184
185
281
    ciphertext = crypto->mlkem_ciphertext;
186
281
    if (ciphertext == NULL) {
187
0
        SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session");
188
0
        return SSH_ERROR;
189
0
    }
190
191
281
    ciphertext_data = ssh_string_data(ciphertext);
192
281
    memcpy(mlkem_ciphertext.value,
193
281
           ciphertext_data,
194
281
           sizeof(mlkem_ciphertext.value));
195
196
281
    memcpy(mlkem_priv.value, crypto->mlkem_privkey, crypto->mlkem_privkey_len);
197
198
281
    libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv,
199
281
                                                 &mlkem_ciphertext,
200
281
                                                 shared_secret);
201
281
    return SSH_OK;
202
281
}