/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 | } |