/src/libssh/src/curve25519.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * curve25519.c - Curve25519 ECDH functions for key exchange |
3 | | * curve25519-sha256@libssh.org and curve25519-sha256 |
4 | | * |
5 | | * This file is part of the SSH Library |
6 | | * |
7 | | * Copyright (c) 2013 by Aris Adamantiadis <aris@badcode.be> |
8 | | * |
9 | | * The SSH Library is free software; you can redistribute it and/or modify |
10 | | * it under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation, version 2.1 of the License. |
12 | | * |
13 | | * The SSH Library is distributed in the hope that it will be useful, but |
14 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
15 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
16 | | * License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public License |
19 | | * along with the SSH Library; see the file COPYING. If not, write to |
20 | | * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
21 | | * MA 02111-1307, USA. |
22 | | */ |
23 | | |
24 | | #include "config.h" |
25 | | |
26 | | #include "libssh/curve25519.h" |
27 | | #ifdef HAVE_CURVE25519 |
28 | | |
29 | | #include "libssh/bignum.h" |
30 | | #include "libssh/buffer.h" |
31 | | #include "libssh/crypto.h" |
32 | | #include "libssh/dh.h" |
33 | | #include "libssh/pki.h" |
34 | | #include "libssh/priv.h" |
35 | | #include "libssh/session.h" |
36 | | #include "libssh/ssh2.h" |
37 | | |
38 | | static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); |
39 | | |
40 | | static ssh_packet_callback dh_client_callbacks[] = { |
41 | | ssh_packet_client_curve25519_reply, |
42 | | }; |
43 | | |
44 | | static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { |
45 | | .start = SSH2_MSG_KEX_ECDH_REPLY, |
46 | | .n_callbacks = 1, |
47 | | .callbacks = dh_client_callbacks, |
48 | | .user = NULL, |
49 | | }; |
50 | | |
51 | | int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k) |
52 | 649 | { |
53 | 649 | int rc; |
54 | | |
55 | | #ifdef DEBUG_CRYPTO |
56 | | ssh_log_hexdump("Session server cookie", |
57 | | session->next_crypto->server_kex.cookie, |
58 | | 16); |
59 | | ssh_log_hexdump("Session client cookie", |
60 | | session->next_crypto->client_kex.cookie, |
61 | | 16); |
62 | | #endif |
63 | | |
64 | 649 | rc = curve25519_do_create_k(session, k); |
65 | 649 | return rc; |
66 | 649 | } |
67 | | |
68 | | /** @internal |
69 | | * @brief Starts curve25519-sha256@libssh.org / curve25519-sha256 key exchange |
70 | | */ |
71 | | int ssh_client_curve25519_init(ssh_session session) |
72 | 1.48k | { |
73 | 1.48k | int rc; |
74 | | |
75 | 1.48k | rc = ssh_curve25519_init(session); |
76 | 1.48k | if (rc != SSH_OK) { |
77 | 0 | return rc; |
78 | 0 | } |
79 | | |
80 | 1.48k | rc = ssh_buffer_pack(session->out_buffer, |
81 | 1.48k | "bdP", |
82 | 1.48k | SSH2_MSG_KEX_ECDH_INIT, |
83 | 1.48k | CURVE25519_PUBKEY_SIZE, |
84 | 1.48k | (size_t)CURVE25519_PUBKEY_SIZE, |
85 | 1.48k | session->next_crypto->curve25519_client_pubkey); |
86 | 1.48k | if (rc != SSH_OK) { |
87 | 0 | ssh_set_error_oom(session); |
88 | 0 | return SSH_ERROR; |
89 | 0 | } |
90 | | |
91 | | /* register the packet callbacks */ |
92 | 1.48k | ssh_packet_set_callbacks(session, &ssh_curve25519_client_callbacks); |
93 | 1.48k | session->dh_handshake_state = DH_STATE_INIT_SENT; |
94 | 1.48k | rc = ssh_packet_send(session); |
95 | | |
96 | 1.48k | return rc; |
97 | 1.48k | } |
98 | | |
99 | | void ssh_client_curve25519_remove_callbacks(ssh_session session) |
100 | 1.44k | { |
101 | 1.44k | ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); |
102 | 1.44k | } |
103 | | |
104 | | static int ssh_curve25519_build_k(ssh_session session) |
105 | 649 | { |
106 | 649 | ssh_curve25519_pubkey k; |
107 | 649 | int rc; |
108 | | |
109 | 649 | rc = ssh_curve25519_create_k(session, k); |
110 | 649 | if (rc != SSH_OK) { |
111 | 1 | return rc; |
112 | 1 | } |
113 | | |
114 | 648 | bignum_bin2bn(k, |
115 | 648 | CURVE25519_PUBKEY_SIZE, |
116 | 648 | &session->next_crypto->shared_secret); |
117 | 648 | if (session->next_crypto->shared_secret == NULL) { |
118 | 0 | return SSH_ERROR; |
119 | 0 | } |
120 | | |
121 | | #ifdef DEBUG_CRYPTO |
122 | | ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); |
123 | | #endif |
124 | | |
125 | 648 | return SSH_OK; |
126 | 648 | } |
127 | | |
128 | | /** @internal |
129 | | * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back |
130 | | * a SSH_MSG_NEWKEYS |
131 | | */ |
132 | | static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply) |
133 | 1.44k | { |
134 | 1.44k | ssh_string q_s_string = NULL; |
135 | 1.44k | ssh_string pubkey_blob = NULL; |
136 | 1.44k | ssh_string signature = NULL; |
137 | 1.44k | int rc; |
138 | 1.44k | (void)type; |
139 | 1.44k | (void)user; |
140 | | |
141 | 1.44k | ssh_client_curve25519_remove_callbacks(session); |
142 | | |
143 | 1.44k | pubkey_blob = ssh_buffer_get_ssh_string(packet); |
144 | 1.44k | if (pubkey_blob == NULL) { |
145 | 1 | ssh_set_error(session, SSH_FATAL, "No public key in packet"); |
146 | 1 | goto error; |
147 | 1 | } |
148 | | |
149 | 1.44k | rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); |
150 | 1.44k | SSH_STRING_FREE(pubkey_blob); |
151 | 1.44k | if (rc != 0) { |
152 | 779 | ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); |
153 | 779 | goto error; |
154 | 779 | } |
155 | | |
156 | 666 | q_s_string = ssh_buffer_get_ssh_string(packet); |
157 | 666 | if (q_s_string == NULL) { |
158 | 6 | ssh_set_error(session, SSH_FATAL, "No Q_S ECC point in packet"); |
159 | 6 | goto error; |
160 | 6 | } |
161 | 660 | if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE) { |
162 | 10 | ssh_set_error(session, |
163 | 10 | SSH_FATAL, |
164 | 10 | "Incorrect size for server Curve25519 public key: %zu", |
165 | 10 | ssh_string_len(q_s_string)); |
166 | 10 | SSH_STRING_FREE(q_s_string); |
167 | 10 | goto error; |
168 | 10 | } |
169 | 650 | memcpy(session->next_crypto->curve25519_server_pubkey, |
170 | 650 | ssh_string_data(q_s_string), |
171 | 650 | CURVE25519_PUBKEY_SIZE); |
172 | 650 | SSH_STRING_FREE(q_s_string); |
173 | | |
174 | 650 | signature = ssh_buffer_get_ssh_string(packet); |
175 | 650 | if (signature == NULL) { |
176 | 1 | ssh_set_error(session, SSH_FATAL, "No signature in packet"); |
177 | 1 | goto error; |
178 | 1 | } |
179 | 649 | session->next_crypto->dh_server_signature = signature; |
180 | 649 | signature = NULL; /* ownership changed */ |
181 | | /* TODO: verify signature now instead of waiting for NEWKEYS */ |
182 | 649 | if (ssh_curve25519_build_k(session) < 0) { |
183 | 1 | ssh_set_error(session, SSH_FATAL, "Cannot build k number"); |
184 | 1 | goto error; |
185 | 1 | } |
186 | | |
187 | | /* Send the MSG_NEWKEYS */ |
188 | 648 | rc = ssh_packet_send_newkeys(session); |
189 | 648 | if (rc == SSH_ERROR) { |
190 | 0 | goto error; |
191 | 0 | } |
192 | 648 | session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; |
193 | | |
194 | 648 | return SSH_PACKET_USED; |
195 | | |
196 | 798 | error: |
197 | 798 | session->session_state = SSH_SESSION_STATE_ERROR; |
198 | 798 | return SSH_PACKET_USED; |
199 | 648 | } |
200 | | |
201 | | #ifdef WITH_SERVER |
202 | | |
203 | | static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init); |
204 | | |
205 | | static ssh_packet_callback dh_server_callbacks[] = { |
206 | | ssh_packet_server_curve25519_init, |
207 | | }; |
208 | | |
209 | | static struct ssh_packet_callbacks_struct ssh_curve25519_server_callbacks = { |
210 | | .start = SSH2_MSG_KEX_ECDH_INIT, |
211 | | .n_callbacks = 1, |
212 | | .callbacks = dh_server_callbacks, |
213 | | .user = NULL, |
214 | | }; |
215 | | |
216 | | /** @internal |
217 | | * @brief sets up the curve25519-sha256@libssh.org kex callbacks |
218 | | */ |
219 | | void ssh_server_curve25519_init(ssh_session session) |
220 | 0 | { |
221 | | /* register the packet callbacks */ |
222 | 0 | ssh_packet_set_callbacks(session, &ssh_curve25519_server_callbacks); |
223 | 0 | } |
224 | | |
225 | | /** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a |
226 | | * SSH_MSG_KEXDH_REPLY |
227 | | */ |
228 | | static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init) |
229 | 0 | { |
230 | | /* ECDH keys */ |
231 | 0 | ssh_string q_c_string = NULL; |
232 | 0 | ssh_string q_s_string = NULL; |
233 | 0 | ssh_string server_pubkey_blob = NULL; |
234 | | |
235 | | /* SSH host keys (rsa, ed25519 and ecdsa) */ |
236 | 0 | ssh_key privkey = NULL; |
237 | 0 | enum ssh_digest_e digest = SSH_DIGEST_AUTO; |
238 | 0 | ssh_string sig_blob = NULL; |
239 | 0 | int rc; |
240 | 0 | (void)type; |
241 | 0 | (void)user; |
242 | |
|
243 | 0 | ssh_packet_remove_callbacks(session, &ssh_curve25519_server_callbacks); |
244 | | |
245 | | /* Extract the client pubkey from the init packet */ |
246 | 0 | q_c_string = ssh_buffer_get_ssh_string(packet); |
247 | 0 | if (q_c_string == NULL) { |
248 | 0 | ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); |
249 | 0 | goto error; |
250 | 0 | } |
251 | 0 | if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE) { |
252 | 0 | ssh_set_error(session, |
253 | 0 | SSH_FATAL, |
254 | 0 | "Incorrect size for server Curve25519 public key: %zu", |
255 | 0 | ssh_string_len(q_c_string)); |
256 | 0 | goto error; |
257 | 0 | } |
258 | | |
259 | 0 | memcpy(session->next_crypto->curve25519_client_pubkey, |
260 | 0 | ssh_string_data(q_c_string), |
261 | 0 | CURVE25519_PUBKEY_SIZE); |
262 | 0 | SSH_STRING_FREE(q_c_string); |
263 | | |
264 | | /* Build server's key pair */ |
265 | 0 | rc = ssh_curve25519_init(session); |
266 | 0 | if (rc != SSH_OK) { |
267 | 0 | ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); |
268 | 0 | goto error; |
269 | 0 | } |
270 | | |
271 | 0 | rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); |
272 | 0 | if (rc < 0) { |
273 | 0 | ssh_set_error_oom(session); |
274 | 0 | goto error; |
275 | 0 | } |
276 | | |
277 | | /* build k and session_id */ |
278 | 0 | rc = ssh_curve25519_build_k(session); |
279 | 0 | if (rc < 0) { |
280 | 0 | ssh_set_error(session, SSH_FATAL, "Cannot build k number"); |
281 | 0 | goto error; |
282 | 0 | } |
283 | | |
284 | | /* privkey is not allocated */ |
285 | 0 | rc = ssh_get_key_params(session, &privkey, &digest); |
286 | 0 | if (rc == SSH_ERROR) { |
287 | 0 | goto error; |
288 | 0 | } |
289 | | |
290 | 0 | rc = ssh_make_sessionid(session); |
291 | 0 | if (rc != SSH_OK) { |
292 | 0 | ssh_set_error(session, SSH_FATAL, "Could not create a session id"); |
293 | 0 | goto error; |
294 | 0 | } |
295 | | |
296 | 0 | rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); |
297 | 0 | if (rc != 0) { |
298 | 0 | ssh_set_error(session, SSH_FATAL, "Could not export server public key"); |
299 | 0 | goto error; |
300 | 0 | } |
301 | | |
302 | | /* add host's public key */ |
303 | 0 | rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); |
304 | 0 | SSH_STRING_FREE(server_pubkey_blob); |
305 | 0 | if (rc < 0) { |
306 | 0 | ssh_set_error_oom(session); |
307 | 0 | goto error; |
308 | 0 | } |
309 | | |
310 | | /* add ecdh public key */ |
311 | 0 | q_s_string = ssh_string_new(CURVE25519_PUBKEY_SIZE); |
312 | 0 | if (q_s_string == NULL) { |
313 | 0 | ssh_set_error_oom(session); |
314 | 0 | goto error; |
315 | 0 | } |
316 | | |
317 | 0 | rc = ssh_string_fill(q_s_string, |
318 | 0 | session->next_crypto->curve25519_server_pubkey, |
319 | 0 | CURVE25519_PUBKEY_SIZE); |
320 | 0 | if (rc < 0) { |
321 | 0 | ssh_set_error(session, SSH_FATAL, "Could not copy public key"); |
322 | 0 | goto error; |
323 | 0 | } |
324 | | |
325 | 0 | rc = ssh_buffer_add_ssh_string(session->out_buffer, q_s_string); |
326 | 0 | SSH_STRING_FREE(q_s_string); |
327 | 0 | if (rc < 0) { |
328 | 0 | ssh_set_error_oom(session); |
329 | 0 | goto error; |
330 | 0 | } |
331 | | /* add signature blob */ |
332 | 0 | sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); |
333 | 0 | if (sig_blob == NULL) { |
334 | 0 | ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); |
335 | 0 | goto error; |
336 | 0 | } |
337 | | |
338 | 0 | rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); |
339 | 0 | SSH_STRING_FREE(sig_blob); |
340 | 0 | if (rc < 0) { |
341 | 0 | ssh_set_error_oom(session); |
342 | 0 | goto error; |
343 | 0 | } |
344 | | |
345 | 0 | SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); |
346 | 0 | rc = ssh_packet_send(session); |
347 | 0 | if (rc == SSH_ERROR) { |
348 | 0 | return SSH_ERROR; |
349 | 0 | } |
350 | | |
351 | 0 | session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; |
352 | | |
353 | | /* Send the MSG_NEWKEYS */ |
354 | 0 | rc = ssh_packet_send_newkeys(session); |
355 | 0 | if (rc == SSH_ERROR) { |
356 | 0 | goto error; |
357 | 0 | } |
358 | | |
359 | 0 | return SSH_PACKET_USED; |
360 | 0 | error: |
361 | 0 | SSH_STRING_FREE(q_c_string); |
362 | 0 | SSH_STRING_FREE(q_s_string); |
363 | 0 | ssh_buffer_reinit(session->out_buffer); |
364 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
365 | 0 | return SSH_PACKET_USED; |
366 | 0 | } |
367 | | |
368 | | #endif /* WITH_SERVER */ |
369 | | |
370 | | #endif /* HAVE_CURVE25519 */ |