|
Packit |
6c0a39 |
/*
|
|
Packit |
6c0a39 |
* dh-gex.c - diffie-hellman group exchange
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* This file is part of the SSH Library
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* Copyright (c) 2016 by Aris Adamantiadis <aris@0xbadc0de.be>
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* The SSH Library is free software; you can redistribute it and/or modify
|
|
Packit |
6c0a39 |
* it under the terms of the GNU Lesser General Public License as published by
|
|
Packit |
6c0a39 |
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
|
Packit |
6c0a39 |
* option) any later version.
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* The SSH Library is distributed in the hope that it will be useful, but
|
|
Packit |
6c0a39 |
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
Packit |
6c0a39 |
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
Packit |
6c0a39 |
* License for more details.
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* You should have received a copy of the GNU Lesser General Public License
|
|
Packit |
6c0a39 |
* along with the SSH Library; see the file COPYING. If not, write to
|
|
Packit |
6c0a39 |
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
Packit |
6c0a39 |
* MA 02111-1307, USA.
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#include "config.h"
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#include <errno.h>
|
|
Packit |
6c0a39 |
#include <stdbool.h>
|
|
Packit |
6c0a39 |
#include <string.h>
|
|
Packit |
6c0a39 |
#include <stdio.h>
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#include "libssh/priv.h"
|
|
Packit |
6c0a39 |
#include "libssh/dh-gex.h"
|
|
Packit |
6c0a39 |
#include "libssh/libssh.h"
|
|
Packit |
6c0a39 |
#include "libssh/ssh2.h"
|
|
Packit |
6c0a39 |
#include "libssh/callbacks.h"
|
|
Packit |
6c0a39 |
#include "libssh/dh.h"
|
|
Packit |
6c0a39 |
#include "libssh/buffer.h"
|
|
Packit |
6c0a39 |
#include "libssh/session.h"
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* Minimum, recommanded and maximum size of DH group */
|
|
Packit |
6c0a39 |
#define DH_PMIN 2048
|
|
Packit |
6c0a39 |
#define DH_PREQ 2048
|
|
Packit |
6c0a39 |
#define DH_PMAX 8192
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group);
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static ssh_packet_callback dhgex_client_callbacks[] = {
|
|
Packit |
6c0a39 |
ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */
|
|
Packit |
6c0a39 |
NULL, /* SSH_MSG_KEX_DH_GEX_INIT */
|
|
Packit |
6c0a39 |
ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */
|
|
Packit |
6c0a39 |
};
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = {
|
|
Packit |
6c0a39 |
.start = SSH2_MSG_KEX_DH_GEX_GROUP,
|
|
Packit |
6c0a39 |
.n_callbacks = 3,
|
|
Packit |
6c0a39 |
.callbacks = dhgex_client_callbacks,
|
|
Packit |
6c0a39 |
.user = NULL
|
|
Packit |
6c0a39 |
};
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief initiates a diffie-hellman-group-exchange kex
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
int ssh_client_dhgex_init(ssh_session session)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_dh_init_common(session->next_crypto);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK){
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmin = DH_PMIN;
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pn = DH_PREQ;
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmax = DH_PMAX;
|
|
Packit |
6c0a39 |
/* Minimum group size, preferred group size, maximum group size */
|
|
Packit |
6c0a39 |
rc = ssh_buffer_pack(session->out_buffer,
|
|
Packit |
6c0a39 |
"bddd",
|
|
Packit |
6c0a39 |
SSH2_MSG_KEX_DH_GEX_REQUEST,
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmin,
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pn,
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmax);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* register the packet callbacks */
|
|
Packit |
6c0a39 |
ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks);
|
|
Packit |
6c0a39 |
session->dh_handshake_state = DH_STATE_REQUEST_SENT;
|
|
Packit |
6c0a39 |
rc = ssh_packet_send(session);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
return rc;
|
|
Packit |
6c0a39 |
error:
|
|
Packit |
6c0a39 |
ssh_dh_cleanup(session->next_crypto);
|
|
Packit |
6c0a39 |
return SSH_ERROR;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief handle a DH_GEX_GROUP packet, client side. This packet contains
|
|
Packit |
6c0a39 |
* the group parameters.
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
int blen;
|
|
Packit |
6c0a39 |
bignum pmin1 = NULL, one = NULL;
|
|
Packit |
6c0a39 |
bignum_CTX ctx = bignum_ctx_new();
|
|
Packit |
6c0a39 |
bignum modulus, generator;
|
|
Packit |
6c0a39 |
const_bignum pubkey;
|
|
Packit |
6c0a39 |
(void) type;
|
|
Packit |
6c0a39 |
(void) user;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_GROUP received");
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (bignum_ctx_invalid(ctx)) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"Received DH_GEX_GROUP in invalid state");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
one = bignum_new();
|
|
Packit |
6c0a39 |
pmin1 = bignum_new();
|
|
Packit |
6c0a39 |
if (one == NULL || pmin1 == NULL) {
|
|
Packit |
6c0a39 |
ssh_set_error_oom(session);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = ssh_buffer_unpack(packet,
|
|
Packit |
6c0a39 |
"BB",
|
|
Packit |
6c0a39 |
&modulus,
|
|
Packit |
6c0a39 |
&generator);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
/* basic checks */
|
|
Packit |
6c0a39 |
if (ssh_fips_mode() &&
|
|
Packit |
6c0a39 |
!ssh_dh_is_known_group(modulus, generator)) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"The received DH group is not FIPS approved");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = bignum_set_word(one, 1);
|
|
Packit |
6c0a39 |
if (rc != 1) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
blen = bignum_num_bits(modulus);
|
|
Packit |
6c0a39 |
if (blen < DH_PMIN || blen > DH_PMAX) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"Invalid dh group parameter p: %d not in [%d:%d]",
|
|
Packit |
6c0a39 |
blen,
|
|
Packit |
6c0a39 |
DH_PMIN,
|
|
Packit |
6c0a39 |
DH_PMAX);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
if (bignum_cmp(modulus, one) <= 0) {
|
|
Packit |
6c0a39 |
/* p must be positive and preferably bigger than one */
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p");
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
if (!bignum_is_bit_set(modulus, 0)) {
|
|
Packit |
6c0a39 |
/* p must be a prime and therefore not divisible by 2 */
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
bignum_sub(pmin1, modulus, one);
|
|
Packit |
6c0a39 |
if (bignum_cmp(generator, one) <= 0 ||
|
|
Packit |
6c0a39 |
bignum_cmp(generator, pmin1) > 0) {
|
|
Packit |
6c0a39 |
/* generator must be at least 2 and smaller than p-1*/
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
bignum_ctx_free(ctx);
|
|
Packit |
6c0a39 |
ctx = NULL;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* all checks passed, set parameters */
|
|
Packit |
6c0a39 |
rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx,
|
|
Packit |
6c0a39 |
modulus, generator);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
bignum_safe_free(modulus);
|
|
Packit |
6c0a39 |
bignum_safe_free(generator);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* compute and send DH public parameter */
|
|
Packit |
6c0a39 |
rc = ssh_dh_keypair_gen_keys(session->next_crypto->dh_ctx,
|
|
Packit |
6c0a39 |
DH_CLIENT_KEYPAIR);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx,
|
|
Packit |
6c0a39 |
DH_CLIENT_KEYPAIR, NULL, &pubkey);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_buffer_pack(session->out_buffer,
|
|
Packit |
6c0a39 |
"bB",
|
|
Packit |
6c0a39 |
SSH2_MSG_KEX_DH_GEX_INIT,
|
|
Packit |
6c0a39 |
pubkey);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
session->dh_handshake_state = DH_STATE_INIT_SENT;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_packet_send(session);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
bignum_safe_free(one);
|
|
Packit |
6c0a39 |
bignum_safe_free(pmin1);
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
error:
|
|
Packit |
6c0a39 |
bignum_safe_free(one);
|
|
Packit |
6c0a39 |
bignum_safe_free(pmin1);
|
|
Packit |
6c0a39 |
if(!bignum_ctx_invalid(ctx)) {
|
|
Packit |
6c0a39 |
bignum_ctx_free(ctx);
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
ssh_dh_cleanup(session->next_crypto);
|
|
Packit |
6c0a39 |
session->session_state = SSH_SESSION_STATE_ERROR;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
struct ssh_crypto_struct *crypto=session->next_crypto;
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
ssh_string pubkey_blob = NULL;
|
|
Packit |
6c0a39 |
bignum server_pubkey = NULL;
|
|
Packit |
6c0a39 |
(void)type;
|
|
Packit |
6c0a39 |
(void)user;
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_REPLY received");
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks);
|
|
Packit |
6c0a39 |
rc = ssh_buffer_unpack(packet,
|
|
Packit |
6c0a39 |
"SBS",
|
|
Packit |
6c0a39 |
&pubkey_blob, &server_pubkey,
|
|
Packit |
6c0a39 |
&crypto->dh_server_signature);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR,
|
|
Packit |
6c0a39 |
NULL, server_pubkey);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
bignum_safe_free(server_pubkey);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob);
|
|
Packit |
6c0a39 |
ssh_string_free(pubkey_blob);
|
|
Packit |
6c0a39 |
if (rc != 0) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx,
|
|
Packit |
6c0a39 |
DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR,
|
|
Packit |
6c0a39 |
&session->next_crypto->shared_secret);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
ssh_set_error(session, SSH_FATAL, "Could not generate shared secret");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* Send the MSG_NEWKEYS */
|
|
Packit |
6c0a39 |
if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_packet_send(session);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent");
|
|
Packit |
6c0a39 |
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
error:
|
|
Packit |
6c0a39 |
ssh_dh_cleanup(session->next_crypto);
|
|
Packit |
6c0a39 |
session->session_state = SSH_SESSION_STATE_ERROR;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#ifdef WITH_SERVER
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#define MODULI_FILE "/etc/ssh/moduli"
|
|
Packit |
6c0a39 |
/* 2 "Safe" prime; (p-1)/2 is also prime. */
|
|
Packit |
6c0a39 |
#define SAFE_PRIME 2
|
|
Packit |
6c0a39 |
/* 0x04 Probabilistic Miller-Rabin primality tests. */
|
|
Packit |
6c0a39 |
#define PRIM_TEST_REQUIRED 0x04
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/**
|
|
Packit |
6c0a39 |
* @internal
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* @brief Determines if the proposed modulus size is more appropriate than the
|
|
Packit |
6c0a39 |
* current one.
|
|
Packit |
6c0a39 |
*
|
|
Packit |
6c0a39 |
* @returns 1 if it's more appropriate. Returns 0 if same or less appropriate
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
static bool dhgroup_better_size(uint32_t pmin,
|
|
Packit |
6c0a39 |
uint32_t pn,
|
|
Packit |
6c0a39 |
uint32_t pmax,
|
|
Packit |
6c0a39 |
size_t current_size,
|
|
Packit |
6c0a39 |
size_t proposed_size)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
if (current_size == proposed_size) {
|
|
Packit |
6c0a39 |
return false;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (current_size == pn) {
|
|
Packit |
6c0a39 |
/* can't do better */
|
|
Packit |
6c0a39 |
return false;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) {
|
|
Packit |
6c0a39 |
return true;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (proposed_size < pmin || proposed_size > pmax) {
|
|
Packit |
6c0a39 |
/* out of bounds */
|
|
Packit |
6c0a39 |
return false;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (current_size == 0) {
|
|
Packit |
6c0a39 |
/* not in the allowed window */
|
|
Packit |
6c0a39 |
return false;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (proposed_size >= pn && proposed_size < current_size) {
|
|
Packit |
6c0a39 |
return true;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (proposed_size <= pn && proposed_size > current_size) {
|
|
Packit |
6c0a39 |
return true;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (proposed_size >= pn && current_size < pn) {
|
|
Packit |
6c0a39 |
return true;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* We're in the allowed window but a better match already exists. */
|
|
Packit |
6c0a39 |
return false;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief returns 1 with 1/n probability
|
|
Packit |
6c0a39 |
* @returns 1 on with P(1/n), 0 with P(n-1/n).
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
static bool invn_chance(int n)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
uint32_t nounce;
|
|
Packit |
6c0a39 |
ssh_get_random(&nounce, sizeof(nounce), 0);
|
|
Packit |
6c0a39 |
return (nounce % n) == 0;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief retrieves a DH group from an open moduli file.
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
static int ssh_retrieve_dhgroup_file(FILE *moduli,
|
|
Packit |
6c0a39 |
uint32_t pmin,
|
|
Packit |
6c0a39 |
uint32_t pn,
|
|
Packit |
6c0a39 |
uint32_t pmax,
|
|
Packit |
6c0a39 |
size_t *best_size,
|
|
Packit |
6c0a39 |
char **best_generator,
|
|
Packit |
6c0a39 |
char **best_modulus)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
char timestamp[32] = {0};
|
|
Packit |
6c0a39 |
char generator[32] = {0};
|
|
Packit |
6c0a39 |
char modulus[4096] = {0};
|
|
Packit |
6c0a39 |
size_t type, tests, tries, size, proposed_size;
|
|
Packit |
6c0a39 |
int firstbyte;
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
size_t line = 0;
|
|
Packit |
6c0a39 |
size_t best_nlines = 0;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
for(;;) {
|
|
Packit |
6c0a39 |
line++;
|
|
Packit |
6c0a39 |
firstbyte = getc(moduli);
|
|
Packit |
6c0a39 |
if (firstbyte == '#'){
|
|
Packit |
6c0a39 |
do {
|
|
Packit |
6c0a39 |
firstbyte = getc(moduli);
|
|
Packit |
6c0a39 |
} while(firstbyte != '\n' && firstbyte != EOF);
|
|
Packit |
6c0a39 |
continue;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
if (firstbyte == EOF) {
|
|
Packit |
6c0a39 |
break;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
ungetc(firstbyte, moduli);
|
|
Packit |
6c0a39 |
rc = fscanf(moduli,
|
|
Packit |
6c0a39 |
"%31s %zu %zu %zu %zu %31s %4095s\n",
|
|
Packit |
6c0a39 |
timestamp,
|
|
Packit |
6c0a39 |
&type,
|
|
Packit |
6c0a39 |
&tests,
|
|
Packit |
6c0a39 |
&tries,
|
|
Packit |
6c0a39 |
&size,
|
|
Packit |
6c0a39 |
generator,
|
|
Packit |
6c0a39 |
modulus);
|
|
Packit |
6c0a39 |
if (rc != 7){
|
|
Packit |
6c0a39 |
if (rc == EOF) {
|
|
Packit |
6c0a39 |
break;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_INFO, "Invalid moduli entry line %zu", line);
|
|
Packit |
6c0a39 |
do {
|
|
Packit |
6c0a39 |
firstbyte = getc(moduli);
|
|
Packit |
6c0a39 |
} while(firstbyte != '\n' && firstbyte != EOF);
|
|
Packit |
6c0a39 |
continue;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* we only want safe primes that were tested */
|
|
Packit |
6c0a39 |
if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) {
|
|
Packit |
6c0a39 |
continue;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
proposed_size = size + 1;
|
|
Packit |
6c0a39 |
if (proposed_size != *best_size &&
|
|
Packit |
6c0a39 |
dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) {
|
|
Packit |
6c0a39 |
best_nlines = 0;
|
|
Packit |
6c0a39 |
*best_size = proposed_size;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
if (proposed_size == *best_size) {
|
|
Packit |
6c0a39 |
best_nlines++;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* Use reservoir sampling algorithm */
|
|
Packit |
6c0a39 |
if (proposed_size == *best_size && invn_chance(best_nlines)) {
|
|
Packit |
6c0a39 |
SAFE_FREE(*best_generator);
|
|
Packit |
6c0a39 |
SAFE_FREE(*best_modulus);
|
|
Packit |
6c0a39 |
*best_generator = strdup(generator);
|
|
Packit |
6c0a39 |
if (*best_generator == NULL) {
|
|
Packit |
6c0a39 |
return SSH_ERROR;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
*best_modulus = strdup(modulus);
|
|
Packit |
6c0a39 |
if (*best_modulus == NULL) {
|
|
Packit |
6c0a39 |
SAFE_FREE(*best_generator);
|
|
Packit |
6c0a39 |
return SSH_ERROR;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
if (*best_size != 0) {
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_INFO,
|
|
Packit |
6c0a39 |
"Selected %zu bits modulus out of %zu candidates in %zu lines",
|
|
Packit |
6c0a39 |
*best_size,
|
|
Packit |
6c0a39 |
best_nlines - 1,
|
|
Packit |
6c0a39 |
line);
|
|
Packit |
6c0a39 |
} else {
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_WARNING,
|
|
Packit |
6c0a39 |
"No moduli found for [%u:%u:%u]",
|
|
Packit |
6c0a39 |
pmin,
|
|
Packit |
6c0a39 |
pn,
|
|
Packit |
6c0a39 |
pmax);
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_OK;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief retrieves a DH group from the moduli file based on bits len parameters
|
|
Packit |
6c0a39 |
* @param[in] pmin minimum group size in bits
|
|
Packit |
6c0a39 |
* @param[in] pn preferred group size
|
|
Packit |
6c0a39 |
* @param[in] pmax maximum group size
|
|
Packit |
6c0a39 |
* @param[out] size size of the chosen modulus
|
|
Packit |
6c0a39 |
* @param[out] p modulus
|
|
Packit |
6c0a39 |
* @param[out] g generator
|
|
Packit |
6c0a39 |
* @return SSH_OK on success, SSH_ERROR otherwise.
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
static int ssh_retrieve_dhgroup(uint32_t pmin,
|
|
Packit |
6c0a39 |
uint32_t pn,
|
|
Packit |
6c0a39 |
uint32_t pmax,
|
|
Packit |
6c0a39 |
size_t *size,
|
|
Packit |
6c0a39 |
bignum *p,
|
|
Packit |
6c0a39 |
bignum *g)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
FILE *moduli = NULL;
|
|
Packit |
6c0a39 |
char *generator = NULL;
|
|
Packit |
6c0a39 |
char *modulus = NULL;
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* In FIPS mode, we can not negotiate arbitrary primes,
|
|
Packit |
6c0a39 |
* but just the approved ones */
|
|
Packit |
6c0a39 |
if (ssh_fips_mode()) {
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_TRACE, "In FIPS mode, using built-in primes");
|
|
Packit |
6c0a39 |
return ssh_fallback_group(pmax, p, g);
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
moduli = fopen(MODULI_FILE, "r");
|
|
Packit |
6c0a39 |
if (moduli == NULL) {
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_WARNING,
|
|
Packit |
6c0a39 |
"Unable to open moduli file: %s",
|
|
Packit |
6c0a39 |
strerror(errno));
|
|
Packit |
6c0a39 |
return ssh_fallback_group(pmax, p, g);
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
*size = 0;
|
|
Packit |
6c0a39 |
*p = NULL;
|
|
Packit |
6c0a39 |
*g = NULL;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_retrieve_dhgroup_file(moduli,
|
|
Packit |
6c0a39 |
pmin,
|
|
Packit |
6c0a39 |
pn,
|
|
Packit |
6c0a39 |
pmax,
|
|
Packit |
6c0a39 |
size,
|
|
Packit |
6c0a39 |
&generator,
|
|
Packit |
6c0a39 |
&modulus);
|
|
Packit |
6c0a39 |
fclose(moduli);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR || *size == 0) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = bignum_hex2bn(generator, g);
|
|
Packit |
6c0a39 |
if (rc == 0) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = bignum_hex2bn(modulus, p);
|
|
Packit |
6c0a39 |
if (rc == 0) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
SAFE_FREE(generator);
|
|
Packit |
6c0a39 |
SAFE_FREE(modulus);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_OK;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
error:
|
|
Packit |
6c0a39 |
bignum_safe_free(*g);
|
|
Packit |
6c0a39 |
bignum_safe_free(*p);
|
|
Packit |
6c0a39 |
SAFE_FREE(generator);
|
|
Packit |
6c0a39 |
SAFE_FREE(modulus);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
return SSH_ERROR;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request);
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static ssh_packet_callback dhgex_server_callbacks[]= {
|
|
Packit |
6c0a39 |
NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */
|
|
Packit |
6c0a39 |
NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */
|
|
Packit |
6c0a39 |
ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */
|
|
Packit |
6c0a39 |
NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */
|
|
Packit |
6c0a39 |
ssh_packet_server_dhgex_request /* SSH_MSG_GEX_DH_GEX_REQUEST */
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
};
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static struct ssh_packet_callbacks_struct ssh_dhgex_server_callbacks = {
|
|
Packit |
6c0a39 |
.start = SSH2_MSG_KEX_DH_GEX_REQUEST_OLD,
|
|
Packit |
6c0a39 |
.n_callbacks = 5,
|
|
Packit |
6c0a39 |
.callbacks = dhgex_server_callbacks,
|
|
Packit |
6c0a39 |
.user = NULL
|
|
Packit |
6c0a39 |
};
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief sets up the diffie-hellman-groupx kex callbacks
|
|
Packit |
6c0a39 |
*/
|
|
Packit |
6c0a39 |
void ssh_server_dhgex_init(ssh_session session){
|
|
Packit |
6c0a39 |
/* register the packet callbacks */
|
|
Packit |
6c0a39 |
ssh_packet_set_callbacks(session, &ssh_dhgex_server_callbacks);
|
|
Packit |
6c0a39 |
ssh_dh_init_common(session->next_crypto);
|
|
Packit |
6c0a39 |
session->dh_handshake_state = DH_STATE_INIT;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request)
|
|
Packit |
6c0a39 |
{
|
|
Packit |
6c0a39 |
bignum modulus = NULL, generator = NULL;
|
|
Packit |
6c0a39 |
uint32_t pmin, pn, pmax;
|
|
Packit |
6c0a39 |
size_t size = 0;
|
|
Packit |
6c0a39 |
int rc;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
(void) type;
|
|
Packit |
6c0a39 |
(void) user;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (session->dh_handshake_state != DH_STATE_INIT) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"Received DH_GEX_REQUEST in invalid state");
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* Minimum group size, preferred group size, maximum group size */
|
|
Packit |
6c0a39 |
rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pn, &pmax);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK){
|
|
Packit |
6c0a39 |
ssh_set_error_invalid(session);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_INFO, "dh-gex: DHGEX_REQUEST[%u:%u:%u]", pmin, pn, pmax);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"Invalid dh-gex arguments [%u:%u:%u]",
|
|
Packit |
6c0a39 |
pmin,
|
|
Packit |
6c0a39 |
pn,
|
|
Packit |
6c0a39 |
pmax);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmin = pmin;
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pn = pn;
|
|
Packit |
6c0a39 |
session->next_crypto->dh_pmax = pmax;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/* ensure safe parameters */
|
|
Packit |
6c0a39 |
if (pmin < DH_PMIN) {
|
|
Packit |
6c0a39 |
pmin = DH_PMIN;
|
|
Packit |
6c0a39 |
if (pn < pmin) {
|
|
Packit |
6c0a39 |
pn = pmin;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = ssh_retrieve_dhgroup(pmin,
|
|
Packit |
6c0a39 |
pn,
|
|
Packit |
6c0a39 |
pmax,
|
|
Packit |
6c0a39 |
&size,
|
|
Packit |
6c0a39 |
&modulus,
|
|
Packit |
6c0a39 |
&generator);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
ssh_set_error(session,
|
|
Packit |
6c0a39 |
SSH_FATAL,
|
|
Packit |
6c0a39 |
"Couldn't find DH group for [%u:%u:%u]",
|
|
Packit |
6c0a39 |
pmin,
|
|
Packit |
6c0a39 |
pn,
|
|
Packit |
6c0a39 |
pmax);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx,
|
|
Packit |
6c0a39 |
modulus, generator);
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
bignum_safe_free(generator);
|
|
Packit |
6c0a39 |
bignum_safe_free(modulus);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
rc = ssh_buffer_pack(session->out_buffer,
|
|
Packit |
6c0a39 |
"bBB",
|
|
Packit |
6c0a39 |
SSH2_MSG_KEX_DH_GEX_GROUP,
|
|
Packit |
6c0a39 |
modulus,
|
|
Packit |
6c0a39 |
generator);
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#ifdef HAVE_LIBCRYPTO
|
|
Packit |
6c0a39 |
bignum_safe_free(generator);
|
|
Packit |
6c0a39 |
bignum_safe_free(modulus);
|
|
Packit |
6c0a39 |
#endif
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
if (rc != SSH_OK) {
|
|
Packit |
6c0a39 |
ssh_set_error_invalid(session);
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
session->dh_handshake_state = DH_STATE_GROUP_SENT;
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
rc = ssh_packet_send(session);
|
|
Packit |
6c0a39 |
if (rc == SSH_ERROR) {
|
|
Packit |
6c0a39 |
goto error;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
error:
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
/** @internal
|
|
Packit |
6c0a39 |
* @brief parse an incoming SSH_MSG_KEX_DH_GEX_INIT packet and complete
|
|
Packit |
6c0a39 |
* Diffie-Hellman key exchange
|
|
Packit |
6c0a39 |
**/
|
|
Packit |
6c0a39 |
static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init){
|
|
Packit |
6c0a39 |
(void) type;
|
|
Packit |
6c0a39 |
(void) user;
|
|
Packit |
6c0a39 |
SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEX_DHGEX_INIT");
|
|
Packit |
6c0a39 |
ssh_packet_remove_callbacks(session, &ssh_dhgex_server_callbacks);
|
|
Packit |
6c0a39 |
ssh_server_dh_process_init(session, packet);
|
|
Packit |
6c0a39 |
return SSH_PACKET_USED;
|
|
Packit |
6c0a39 |
}
|
|
Packit |
6c0a39 |
|
|
Packit |
6c0a39 |
#endif /* WITH_SERVER */
|