Line | Count | Source |
1 | | /* |
2 | | * dh-gex.c - diffie-hellman group exchange |
3 | | * |
4 | | * This file is part of the SSH Library |
5 | | * |
6 | | * Copyright (c) 2016 by Aris Adamantiadis <aris@0xbadc0de.be> |
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; either version 2.1 of the License, or (at your |
11 | | * option) any later version. |
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 <errno.h> |
27 | | #include <stdbool.h> |
28 | | #include <string.h> |
29 | | #include <stdio.h> |
30 | | |
31 | | #include "libssh/priv.h" |
32 | | #include "libssh/dh-gex.h" |
33 | | #include "libssh/libssh.h" |
34 | | #include "libssh/ssh2.h" |
35 | | #include "libssh/callbacks.h" |
36 | | #include "libssh/dh.h" |
37 | | #include "libssh/buffer.h" |
38 | | #include "libssh/session.h" |
39 | | |
40 | | /* Minimum, recommended and maximum size of DH group */ |
41 | 394 | #define DH_PMIN 2048 |
42 | 0 | #define DH_PREQ 2048 |
43 | 2.30k | #define DH_PMAX 8192 |
44 | | |
45 | | static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group); |
46 | | static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply); |
47 | | |
48 | | static ssh_packet_callback dhgex_client_callbacks[] = { |
49 | | ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */ |
50 | | NULL, /* SSH_MSG_KEX_DH_GEX_INIT */ |
51 | | ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */ |
52 | | }; |
53 | | |
54 | | static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = { |
55 | | .start = SSH2_MSG_KEX_DH_GEX_GROUP, |
56 | | .n_callbacks = 3, |
57 | | .callbacks = dhgex_client_callbacks, |
58 | | .user = NULL |
59 | | }; |
60 | | |
61 | | /** @internal |
62 | | * @brief initiates a diffie-hellman-group-exchange kex |
63 | | */ |
64 | | int ssh_client_dhgex_init(ssh_session session) |
65 | 0 | { |
66 | 0 | int rc; |
67 | |
|
68 | 0 | rc = ssh_dh_init_common(session->next_crypto); |
69 | 0 | if (rc != SSH_OK){ |
70 | 0 | goto error; |
71 | 0 | } |
72 | | |
73 | 0 | session->next_crypto->dh_pmin = DH_PMIN; |
74 | 0 | session->next_crypto->dh_pn = DH_PREQ; |
75 | 0 | session->next_crypto->dh_pmax = DH_PMAX; |
76 | | /* Minimum group size, preferred group size, maximum group size */ |
77 | 0 | rc = ssh_buffer_pack(session->out_buffer, |
78 | 0 | "bddd", |
79 | 0 | SSH2_MSG_KEX_DH_GEX_REQUEST, |
80 | 0 | session->next_crypto->dh_pmin, |
81 | 0 | session->next_crypto->dh_pn, |
82 | 0 | session->next_crypto->dh_pmax); |
83 | 0 | if (rc != SSH_OK) { |
84 | 0 | goto error; |
85 | 0 | } |
86 | | |
87 | | /* register the packet callbacks */ |
88 | 0 | ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks); |
89 | 0 | session->dh_handshake_state = DH_STATE_REQUEST_SENT; |
90 | 0 | rc = ssh_packet_send(session); |
91 | 0 | if (rc == SSH_ERROR) { |
92 | 0 | goto error; |
93 | 0 | } |
94 | 0 | return rc; |
95 | 0 | error: |
96 | 0 | ssh_dh_cleanup(session->next_crypto); |
97 | 0 | return SSH_ERROR; |
98 | 0 | } |
99 | | |
100 | | /** @internal |
101 | | * @brief handle a DH_GEX_GROUP packet, client side. This packet contains |
102 | | * the group parameters. |
103 | | */ |
104 | | SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) |
105 | 0 | { |
106 | 0 | int rc; |
107 | 0 | int blen; |
108 | 0 | bignum pmin1 = NULL, one = NULL; |
109 | 0 | bignum modulus = NULL, generator = NULL; |
110 | 0 | #if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L |
111 | 0 | const_bignum pubkey; |
112 | | #else |
113 | | bignum pubkey = NULL; |
114 | | #endif /* OPENSSL_VERSION_NUMBER */ |
115 | 0 | (void) type; |
116 | 0 | (void) user; |
117 | |
|
118 | 0 | SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_GROUP received"); |
119 | |
|
120 | 0 | if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) { |
121 | 0 | ssh_set_error(session, |
122 | 0 | SSH_FATAL, |
123 | 0 | "Received DH_GEX_GROUP in invalid state"); |
124 | 0 | goto error; |
125 | 0 | } |
126 | 0 | one = bignum_new(); |
127 | 0 | pmin1 = bignum_new(); |
128 | 0 | if (one == NULL || pmin1 == NULL) { |
129 | 0 | ssh_set_error_oom(session); |
130 | 0 | goto error; |
131 | 0 | } |
132 | 0 | rc = ssh_buffer_unpack(packet, |
133 | 0 | "BB", |
134 | 0 | &modulus, |
135 | 0 | &generator); |
136 | 0 | if (rc != SSH_OK) { |
137 | 0 | ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet"); |
138 | 0 | goto error; |
139 | 0 | } |
140 | | /* basic checks */ |
141 | 0 | if (ssh_fips_mode() && |
142 | 0 | !ssh_dh_is_known_group(modulus, generator)) { |
143 | 0 | ssh_set_error(session, |
144 | 0 | SSH_FATAL, |
145 | 0 | "The received DH group is not FIPS approved"); |
146 | 0 | goto error; |
147 | 0 | } |
148 | 0 | rc = bignum_set_word(one, 1); |
149 | 0 | if (rc != 1) { |
150 | 0 | goto error; |
151 | 0 | } |
152 | 0 | blen = bignum_num_bits(modulus); |
153 | 0 | if (blen < DH_PMIN || blen > DH_PMAX) { |
154 | 0 | ssh_set_error(session, |
155 | 0 | SSH_FATAL, |
156 | 0 | "Invalid dh group parameter p: %d not in [%d:%d]", |
157 | 0 | blen, |
158 | 0 | DH_PMIN, |
159 | 0 | DH_PMAX); |
160 | 0 | goto error; |
161 | 0 | } |
162 | 0 | if (bignum_cmp(modulus, one) <= 0) { |
163 | | /* p must be positive and preferably bigger than one */ |
164 | 0 | ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); |
165 | 0 | goto error; |
166 | 0 | } |
167 | 0 | if (!bignum_is_bit_set(modulus, 0)) { |
168 | | /* p must be a prime and therefore not divisible by 2 */ |
169 | 0 | ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); |
170 | 0 | goto error; |
171 | 0 | } |
172 | 0 | bignum_sub(pmin1, modulus, one); |
173 | 0 | if (bignum_cmp(generator, one) <= 0 || |
174 | 0 | bignum_cmp(generator, pmin1) > 0) { |
175 | | /* generator must be at least 2 and smaller than p-1*/ |
176 | 0 | ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g"); |
177 | 0 | goto error; |
178 | 0 | } |
179 | | |
180 | | /* all checks passed, set parameters (the BNs are copied in openssl backend) */ |
181 | 0 | rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, |
182 | 0 | modulus, generator); |
183 | 0 | if (rc != SSH_OK) { |
184 | 0 | goto error; |
185 | 0 | } |
186 | 0 | #ifdef HAVE_LIBCRYPTO |
187 | 0 | bignum_safe_free(modulus); |
188 | 0 | bignum_safe_free(generator); |
189 | 0 | #endif |
190 | 0 | modulus = NULL; |
191 | 0 | generator = NULL; |
192 | | |
193 | | /* compute and send DH public parameter */ |
194 | 0 | rc = ssh_dh_keypair_gen_keys(session->next_crypto->dh_ctx, |
195 | 0 | DH_CLIENT_KEYPAIR); |
196 | 0 | if (rc == SSH_ERROR) { |
197 | 0 | goto error; |
198 | 0 | } |
199 | | |
200 | 0 | rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, |
201 | 0 | DH_CLIENT_KEYPAIR, NULL, &pubkey); |
202 | 0 | if (rc != SSH_OK) { |
203 | 0 | goto error; |
204 | 0 | } |
205 | | |
206 | 0 | rc = ssh_buffer_pack(session->out_buffer, |
207 | 0 | "bB", |
208 | 0 | SSH2_MSG_KEX_DH_GEX_INIT, |
209 | 0 | pubkey); |
210 | 0 | if (rc != SSH_OK) { |
211 | 0 | goto error; |
212 | 0 | } |
213 | | #if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L |
214 | | bignum_safe_free(pubkey); |
215 | | #endif /* OPENSSL_VERSION_NUMBER */ |
216 | | |
217 | 0 | session->dh_handshake_state = DH_STATE_INIT_SENT; |
218 | |
|
219 | 0 | rc = ssh_packet_send(session); |
220 | 0 | if (rc == SSH_ERROR) { |
221 | 0 | goto error; |
222 | 0 | } |
223 | | |
224 | 0 | bignum_safe_free(one); |
225 | 0 | bignum_safe_free(pmin1); |
226 | 0 | return SSH_PACKET_USED; |
227 | | |
228 | 0 | error: |
229 | 0 | bignum_safe_free(modulus); |
230 | 0 | bignum_safe_free(generator); |
231 | 0 | bignum_safe_free(one); |
232 | 0 | bignum_safe_free(pmin1); |
233 | | #if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L |
234 | | bignum_safe_free(pubkey); |
235 | | #endif /* OPENSSL_VERSION_NUMBER */ |
236 | 0 | ssh_dh_cleanup(session->next_crypto); |
237 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
238 | |
|
239 | 0 | return SSH_PACKET_USED; |
240 | 0 | } |
241 | | |
242 | | void ssh_client_dhgex_remove_callbacks(ssh_session session) |
243 | 0 | { |
244 | 0 | ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); |
245 | 0 | } |
246 | | |
247 | | static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) |
248 | 0 | { |
249 | 0 | struct ssh_crypto_struct *crypto=session->next_crypto; |
250 | 0 | int rc; |
251 | 0 | ssh_string pubkey_blob = NULL; |
252 | 0 | bignum server_pubkey = NULL; |
253 | 0 | (void)type; |
254 | 0 | (void)user; |
255 | 0 | SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); |
256 | |
|
257 | 0 | ssh_client_dhgex_remove_callbacks(session); |
258 | 0 | rc = ssh_buffer_unpack(packet, |
259 | 0 | "SBS", |
260 | 0 | &pubkey_blob, &server_pubkey, |
261 | 0 | &crypto->dh_server_signature); |
262 | 0 | if (rc == SSH_ERROR) { |
263 | 0 | ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet"); |
264 | 0 | goto error; |
265 | 0 | } |
266 | 0 | rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, |
267 | 0 | NULL, server_pubkey); |
268 | 0 | if (rc != SSH_OK) { |
269 | 0 | bignum_safe_free(server_pubkey); |
270 | 0 | goto error; |
271 | 0 | } |
272 | | /* The ownership was passed to the crypto structure */ |
273 | 0 | server_pubkey = NULL; |
274 | |
|
275 | 0 | rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); |
276 | 0 | SSH_STRING_FREE(pubkey_blob); |
277 | 0 | if (rc != 0) { |
278 | 0 | goto error; |
279 | 0 | } |
280 | | |
281 | 0 | rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, |
282 | 0 | DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, |
283 | 0 | &session->next_crypto->shared_secret); |
284 | 0 | ssh_dh_debug_crypto(session->next_crypto); |
285 | 0 | if (rc == SSH_ERROR) { |
286 | 0 | ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); |
287 | 0 | goto error; |
288 | 0 | } |
289 | | |
290 | | /* Send the MSG_NEWKEYS */ |
291 | 0 | rc = ssh_packet_send_newkeys(session); |
292 | 0 | if (rc == SSH_ERROR) { |
293 | 0 | goto error; |
294 | 0 | } |
295 | 0 | session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; |
296 | |
|
297 | 0 | return SSH_PACKET_USED; |
298 | 0 | error: |
299 | 0 | SSH_STRING_FREE(pubkey_blob); |
300 | 0 | ssh_dh_cleanup(session->next_crypto); |
301 | 0 | session->session_state = SSH_SESSION_STATE_ERROR; |
302 | |
|
303 | 0 | return SSH_PACKET_USED; |
304 | 0 | } |
305 | | |
306 | | #ifdef WITH_SERVER |
307 | | |
308 | 71 | #define MODULI_FILE "/etc/ssh/moduli" |
309 | | /* 2 "Safe" prime; (p-1)/2 is also prime. */ |
310 | 0 | #define SAFE_PRIME 2 |
311 | | /* 0x04 Probabilistic Miller-Rabin primality tests. */ |
312 | 0 | #define PRIM_TEST_REQUIRED 0x04 |
313 | | |
314 | | /** |
315 | | * @internal |
316 | | * |
317 | | * @brief Determines if the proposed modulus size is more appropriate than the |
318 | | * current one. |
319 | | * |
320 | | * @returns 1 if it's more appropriate. Returns 0 if same or less appropriate |
321 | | */ |
322 | | static bool dhgroup_better_size(uint32_t pmin, |
323 | | uint32_t pn, |
324 | | uint32_t pmax, |
325 | | size_t current_size, |
326 | | size_t proposed_size) |
327 | 0 | { |
328 | 0 | if (current_size == proposed_size) { |
329 | 0 | return false; |
330 | 0 | } |
331 | | |
332 | 0 | if (current_size == pn) { |
333 | | /* can't do better */ |
334 | 0 | return false; |
335 | 0 | } |
336 | | |
337 | 0 | if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) { |
338 | 0 | return true; |
339 | 0 | } |
340 | | |
341 | 0 | if (proposed_size < pmin || proposed_size > pmax) { |
342 | | /* out of bounds */ |
343 | 0 | return false; |
344 | 0 | } |
345 | | |
346 | 0 | if (current_size == 0) { |
347 | | /* not in the allowed window */ |
348 | 0 | return false; |
349 | 0 | } |
350 | | |
351 | 0 | if (proposed_size >= pn && proposed_size < current_size) { |
352 | 0 | return true; |
353 | 0 | } |
354 | | |
355 | 0 | if (proposed_size <= pn && proposed_size > current_size) { |
356 | 0 | return true; |
357 | 0 | } |
358 | | |
359 | 0 | if (proposed_size >= pn && current_size < pn) { |
360 | 0 | return true; |
361 | 0 | } |
362 | | |
363 | | /* We're in the allowed window but a better match already exists. */ |
364 | 0 | return false; |
365 | 0 | } |
366 | | |
367 | | /** @internal |
368 | | * @brief returns 1 with 1/n probability |
369 | | * @returns 1 on with P(1/n), 0 with P(n-1/n). |
370 | | */ |
371 | | static bool invn_chance(size_t n) |
372 | 0 | { |
373 | 0 | size_t nounce = 0; |
374 | 0 | int ok; |
375 | |
|
376 | 0 | ok = ssh_get_random(&nounce, sizeof(nounce), 0); |
377 | 0 | if (!ok) { |
378 | 0 | return false; |
379 | 0 | } |
380 | 0 | return (nounce % n) == 0; |
381 | 0 | } |
382 | | |
383 | | /** @internal |
384 | | * @brief retrieves a DH group from an open moduli file. |
385 | | */ |
386 | | static int ssh_retrieve_dhgroup_file(FILE *moduli, |
387 | | uint32_t pmin, |
388 | | uint32_t pn, |
389 | | uint32_t pmax, |
390 | | size_t *best_size, |
391 | | char **best_generator, |
392 | | char **best_modulus) |
393 | 0 | { |
394 | 0 | char timestamp[32] = {0}; |
395 | 0 | char generator[32] = {0}; |
396 | 0 | char modulus[4096] = {0}; |
397 | 0 | size_t type, tests, tries, size, proposed_size; |
398 | 0 | int firstbyte; |
399 | 0 | int rc; |
400 | 0 | size_t line = 0; |
401 | 0 | size_t best_nlines = 0; |
402 | |
|
403 | 0 | *best_size = 0; |
404 | 0 | for(;;) { |
405 | 0 | line++; |
406 | 0 | firstbyte = getc(moduli); |
407 | 0 | if (firstbyte == '#'){ |
408 | 0 | do { |
409 | 0 | firstbyte = getc(moduli); |
410 | 0 | } while(firstbyte != '\n' && firstbyte != EOF); |
411 | 0 | if (firstbyte == EOF) { |
412 | 0 | break; |
413 | 0 | } |
414 | 0 | continue; |
415 | 0 | } |
416 | 0 | if (firstbyte == EOF) { |
417 | 0 | break; |
418 | 0 | } |
419 | 0 | ungetc(firstbyte, moduli); |
420 | 0 | rc = fscanf(moduli, |
421 | 0 | "%31s %zu %zu %zu %zu %31s %4095s\n", |
422 | 0 | timestamp, |
423 | 0 | &type, |
424 | 0 | &tests, |
425 | 0 | &tries, |
426 | 0 | &size, |
427 | 0 | generator, |
428 | 0 | modulus); |
429 | 0 | if (rc != 7){ |
430 | 0 | if (rc == EOF) { |
431 | 0 | break; |
432 | 0 | } |
433 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Invalid moduli entry line %zu", line); |
434 | 0 | do { |
435 | 0 | firstbyte = getc(moduli); |
436 | 0 | } while(firstbyte != '\n' && firstbyte != EOF); |
437 | 0 | if (firstbyte == EOF) { |
438 | 0 | break; |
439 | 0 | } |
440 | 0 | continue; |
441 | 0 | } |
442 | | |
443 | | /* we only want safe primes that were tested */ |
444 | 0 | if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) { |
445 | 0 | continue; |
446 | 0 | } |
447 | | |
448 | 0 | proposed_size = size + 1; |
449 | 0 | if (proposed_size != *best_size && |
450 | 0 | dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { |
451 | 0 | best_nlines = 1; |
452 | 0 | *best_size = proposed_size; |
453 | 0 | } else if (proposed_size == *best_size) { |
454 | 0 | best_nlines++; |
455 | 0 | } |
456 | | |
457 | | /* Use reservoir sampling algorithm */ |
458 | 0 | if (proposed_size == *best_size && invn_chance(best_nlines)) { |
459 | 0 | SAFE_FREE(*best_generator); |
460 | 0 | SAFE_FREE(*best_modulus); |
461 | 0 | *best_generator = strdup(generator); |
462 | 0 | if (*best_generator == NULL) { |
463 | 0 | return SSH_ERROR; |
464 | 0 | } |
465 | 0 | *best_modulus = strdup(modulus); |
466 | 0 | if (*best_modulus == NULL) { |
467 | 0 | SAFE_FREE(*best_generator); |
468 | 0 | return SSH_ERROR; |
469 | 0 | } |
470 | 0 | } |
471 | 0 | } |
472 | 0 | if (*best_size != 0) { |
473 | 0 | SSH_LOG(SSH_LOG_DEBUG, |
474 | 0 | "Selected %zu bits modulus out of %zu candidates in %zu lines", |
475 | 0 | *best_size, |
476 | 0 | best_nlines - 1, |
477 | 0 | line); |
478 | 0 | } else { |
479 | 0 | SSH_LOG(SSH_LOG_DEBUG, |
480 | 0 | "No moduli found for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", |
481 | 0 | pmin, |
482 | 0 | pn, |
483 | 0 | pmax); |
484 | 0 | } |
485 | |
|
486 | 0 | return SSH_OK; |
487 | 0 | } |
488 | | |
489 | | /** @internal |
490 | | * @brief retrieves a DH group from the moduli file based on bits len parameters |
491 | | * @param[in] pmin minimum group size in bits |
492 | | * @param[in] pn preferred group size |
493 | | * @param[in] pmax maximum group size |
494 | | * @param[out] size size of the chosen modulus |
495 | | * @param[out] p modulus |
496 | | * @param[out] g generator |
497 | | * @return SSH_OK on success, SSH_ERROR otherwise. |
498 | | */ |
499 | | static int ssh_retrieve_dhgroup(char *moduli_file, |
500 | | uint32_t pmin, |
501 | | uint32_t pn, |
502 | | uint32_t pmax, |
503 | | size_t *size, |
504 | | bignum *p, |
505 | | bignum *g) |
506 | 71 | { |
507 | 71 | FILE *moduli = NULL; |
508 | 71 | char *generator = NULL; |
509 | 71 | char *modulus = NULL; |
510 | 71 | int rc; |
511 | | |
512 | | /* In FIPS mode, we can not negotiate arbitrary primes, |
513 | | * but just the approved ones */ |
514 | 71 | if (ssh_fips_mode()) { |
515 | 0 | SSH_LOG(SSH_LOG_TRACE, "In FIPS mode, using built-in primes"); |
516 | 0 | return ssh_fallback_group(pmax, p, g); |
517 | 0 | } |
518 | | |
519 | 71 | if (moduli_file != NULL) |
520 | 0 | moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); |
521 | 71 | else |
522 | 71 | moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); |
523 | | |
524 | 71 | if (moduli == NULL) { |
525 | 71 | SSH_LOG_STRERROR(SSH_LOG_DEBUG, |
526 | 71 | errno, |
527 | 71 | "Unable to open moduli file: %s"); |
528 | 71 | return ssh_fallback_group(pmax, p, g); |
529 | 71 | } |
530 | | |
531 | 0 | *size = 0; |
532 | 0 | *p = NULL; |
533 | 0 | *g = NULL; |
534 | |
|
535 | 0 | rc = ssh_retrieve_dhgroup_file(moduli, |
536 | 0 | pmin, |
537 | 0 | pn, |
538 | 0 | pmax, |
539 | 0 | size, |
540 | 0 | &generator, |
541 | 0 | &modulus); |
542 | 0 | fclose(moduli); |
543 | 0 | if (rc == SSH_ERROR || *size == 0) { |
544 | 0 | goto error; |
545 | 0 | } |
546 | 0 | rc = bignum_hex2bn(generator, g); |
547 | 0 | if (rc == 0) { |
548 | 0 | goto error; |
549 | 0 | } |
550 | 0 | rc = bignum_hex2bn(modulus, p); |
551 | 0 | if (rc == 0) { |
552 | 0 | goto error; |
553 | 0 | } |
554 | 0 | SAFE_FREE(generator); |
555 | 0 | SAFE_FREE(modulus); |
556 | |
|
557 | 0 | return SSH_OK; |
558 | | |
559 | 0 | error: |
560 | 0 | bignum_safe_free(*g); |
561 | 0 | bignum_safe_free(*p); |
562 | 0 | SAFE_FREE(generator); |
563 | 0 | SAFE_FREE(modulus); |
564 | |
|
565 | 0 | return SSH_ERROR; |
566 | 0 | } |
567 | | |
568 | | static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request); |
569 | | static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init); |
570 | | |
571 | | static ssh_packet_callback dhgex_server_callbacks[] = { |
572 | | NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ |
573 | | NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ |
574 | | ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */ |
575 | | NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */ |
576 | | ssh_packet_server_dhgex_request /* SSH_MSG_KEX_DH_GEX_REQUEST */ |
577 | | |
578 | | }; |
579 | | |
580 | | static struct ssh_packet_callbacks_struct ssh_dhgex_server_callbacks = { |
581 | | .start = SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, |
582 | | .n_callbacks = 5, |
583 | | .callbacks = dhgex_server_callbacks, |
584 | | .user = NULL |
585 | | }; |
586 | | |
587 | | /** @internal |
588 | | * @brief sets up the diffie-hellman-groupx kex callbacks |
589 | | */ |
590 | 180 | void ssh_server_dhgex_init(ssh_session session){ |
591 | | /* register the packet callbacks */ |
592 | 180 | ssh_packet_set_callbacks(session, &ssh_dhgex_server_callbacks); |
593 | 180 | ssh_dh_init_common(session->next_crypto); |
594 | 180 | session->dh_handshake_state = DH_STATE_INIT; |
595 | 180 | } |
596 | | |
597 | | static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) |
598 | 10.9k | { |
599 | 10.9k | bignum modulus = NULL, generator = NULL; |
600 | 10.9k | uint32_t pmin, pn, pmax; |
601 | 10.9k | size_t size = 0; |
602 | 10.9k | int rc; |
603 | | |
604 | 10.9k | (void) type; |
605 | 10.9k | (void) user; |
606 | | |
607 | 10.9k | if (session->dh_handshake_state != DH_STATE_INIT) { |
608 | 0 | ssh_set_error(session, |
609 | 0 | SSH_FATAL, |
610 | 0 | "Received DH_GEX_REQUEST in invalid state"); |
611 | 0 | goto error; |
612 | 0 | } |
613 | | |
614 | | /* Minimum group size, preferred group size, maximum group size */ |
615 | 10.9k | rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pn, &pmax); |
616 | 10.9k | if (rc != SSH_OK){ |
617 | 9.09k | ssh_set_error_invalid(session); |
618 | 9.09k | goto error; |
619 | 9.09k | } |
620 | 1.81k | SSH_LOG(SSH_LOG_DEBUG, "dh-gex: DHGEX_REQUEST[%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); |
621 | | |
622 | 1.81k | if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) { |
623 | 1.74k | ssh_set_error(session, |
624 | 1.74k | SSH_FATAL, |
625 | 1.74k | "Invalid dh-gex arguments [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", |
626 | 1.74k | pmin, |
627 | 1.74k | pn, |
628 | 1.74k | pmax); |
629 | 1.74k | goto error; |
630 | 1.74k | } |
631 | 71 | session->next_crypto->dh_pmin = pmin; |
632 | 71 | session->next_crypto->dh_pn = pn; |
633 | 71 | session->next_crypto->dh_pmax = pmax; |
634 | | |
635 | | /* ensure safe parameters */ |
636 | 71 | if (pmin < DH_PMIN) { |
637 | 58 | pmin = DH_PMIN; |
638 | 58 | if (pn < pmin) { |
639 | 28 | pn = pmin; |
640 | 28 | } |
641 | 58 | } |
642 | 71 | rc = ssh_retrieve_dhgroup(session->server_opts.moduli_file, |
643 | 71 | pmin, |
644 | 71 | pn, |
645 | 71 | pmax, |
646 | 71 | &size, |
647 | 71 | &modulus, |
648 | 71 | &generator); |
649 | 71 | if (rc == SSH_ERROR) { |
650 | 0 | ssh_set_error(session, |
651 | 0 | SSH_FATAL, |
652 | 0 | "Couldn't find DH group for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", |
653 | 0 | pmin, |
654 | 0 | pn, |
655 | 0 | pmax); |
656 | 0 | goto error; |
657 | 0 | } |
658 | 71 | rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, |
659 | 71 | modulus, generator); |
660 | 71 | if (rc != SSH_OK) { |
661 | 0 | bignum_safe_free(generator); |
662 | 0 | bignum_safe_free(modulus); |
663 | 0 | goto error; |
664 | 0 | } |
665 | 71 | rc = ssh_buffer_pack(session->out_buffer, |
666 | 71 | "bBB", |
667 | 71 | SSH2_MSG_KEX_DH_GEX_GROUP, |
668 | 71 | modulus, |
669 | 71 | generator); |
670 | | |
671 | 71 | #ifdef HAVE_LIBCRYPTO |
672 | 71 | bignum_safe_free(generator); |
673 | 71 | bignum_safe_free(modulus); |
674 | 71 | #endif |
675 | | |
676 | 71 | if (rc != SSH_OK) { |
677 | 0 | ssh_set_error_invalid(session); |
678 | 0 | goto error; |
679 | 0 | } |
680 | | |
681 | 71 | session->dh_handshake_state = DH_STATE_GROUP_SENT; |
682 | | |
683 | 71 | rc = ssh_packet_send(session); |
684 | 71 | if (rc == SSH_ERROR) { |
685 | 0 | goto error; |
686 | 0 | } |
687 | | |
688 | 10.9k | error: |
689 | 10.9k | return SSH_PACKET_USED; |
690 | 71 | } |
691 | | |
692 | | /** @internal |
693 | | * @brief parse an incoming SSH_MSG_KEX_DH_GEX_INIT packet and complete |
694 | | * Diffie-Hellman key exchange |
695 | | **/ |
696 | 5 | static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init){ |
697 | 5 | (void) type; |
698 | 5 | (void) user; |
699 | 5 | SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEX_DHGEX_INIT"); |
700 | 5 | ssh_packet_remove_callbacks(session, &ssh_dhgex_server_callbacks); |
701 | 5 | ssh_server_dh_process_init(session, packet); |
702 | 5 | return SSH_PACKET_USED; |
703 | 5 | } |
704 | | |
705 | | #endif /* WITH_SERVER */ |