358 lines
11 KiB
C++
358 lines
11 KiB
C++
|
/*
|
||
|
* Copyright (c) 2012 Juli Mallett. All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
|
* SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <openssl/bn.h>
|
||
|
#include <openssl/dh.h>
|
||
|
|
||
|
#include <common/buffer.h>
|
||
|
|
||
|
#include <crypto/crypto_hash.h>
|
||
|
|
||
|
#include <event/event_callback.h>
|
||
|
|
||
|
#include <ssh/ssh_algorithm_negotiation.h>
|
||
|
#include <ssh/ssh_key_exchange.h>
|
||
|
#include <ssh/ssh_protocol.h>
|
||
|
#include <ssh/ssh_server_host_key.h>
|
||
|
#include <ssh/ssh_session.h>
|
||
|
#include <ssh/ssh_filter.h>
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// File: ssh_key_exchange.cc //
|
||
|
// Description: SSH key check performed on filter session start //
|
||
|
// Project: WANProxy XTech //
|
||
|
// Adapted by: Andreu Vidal Bramfeld-Software //
|
||
|
// Last modified: 2015-04-01 //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#define DH_GROUP_MIN 1024
|
||
|
#define DH_GROUP_MAX 8192
|
||
|
|
||
|
#define USE_TEST_GROUP
|
||
|
|
||
|
namespace {
|
||
|
#ifdef USE_TEST_GROUP
|
||
|
static uint8_t test_prime_and_generator[] = {
|
||
|
0x00, 0x00, 0x00, 0x81, 0x00, 0xe3, 0x1d, 0xfe, 0x85, 0x59, 0x9b, 0xcb, 0x5c, 0x2b, 0xbe, 0xcf,
|
||
|
0x20, 0x1f, 0x5f, 0x49, 0xf1, 0xea, 0x31, 0x07, 0x7d, 0xa9, 0x26, 0xcb, 0x31, 0x03, 0x9d, 0x82,
|
||
|
0x33, 0x2f, 0xed, 0x67, 0xa3, 0xa9, 0xb1, 0xc9, 0xe6, 0x34, 0x6c, 0xd7, 0xb5, 0x1a, 0x0a, 0x94,
|
||
|
0x11, 0xa7, 0xd9, 0x26, 0xff, 0x0e, 0x8d, 0x72, 0xc1, 0x7b, 0x53, 0x9a, 0x13, 0x78, 0x7e, 0x16,
|
||
|
0x38, 0x74, 0x7c, 0xb2, 0xdc, 0x60, 0x2c, 0x8c, 0xe8, 0x31, 0xf8, 0xd9, 0x7b, 0xac, 0xa6, 0x71,
|
||
|
0xee, 0x61, 0x0c, 0x1a, 0xa4, 0x2f, 0x47, 0x2f, 0xe2, 0x22, 0xbd, 0x01, 0xe5, 0x25, 0xb6, 0x95,
|
||
|
0xda, 0x3f, 0xf7, 0x03, 0xf4, 0x0e, 0xd6, 0x8c, 0xbb, 0x69, 0x1d, 0xcb, 0xd1, 0xe2, 0x60, 0xdb,
|
||
|
0xf5, 0x0b, 0x85, 0x98, 0xe6, 0x17, 0xbe, 0x29, 0x4e, 0xa7, 0x90, 0x11, 0xac, 0xbc, 0xa5, 0x3e,
|
||
|
0x05, 0xfe, 0xe9, 0x56, 0x93, 0x00, 0x00, 0x00, 0x01, 0x02
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
static const uint8_t
|
||
|
DiffieHellmanGroupExchangeRequest = 34,
|
||
|
DiffieHellmanGroupExchangeGroup = 31,
|
||
|
DiffieHellmanGroupExchangeInitialize = 32,
|
||
|
DiffieHellmanGroupExchangeReply = 33;
|
||
|
|
||
|
/*
|
||
|
* XXX
|
||
|
* Like a non-trivial amount of other code, this has been
|
||
|
* written a bit fast-and-loose. The usage of the dh_ and
|
||
|
* k_ in particularly are a bit dodgy and need to be freed
|
||
|
* in the destructor.
|
||
|
*
|
||
|
* Need to add assertions and frees.
|
||
|
*/
|
||
|
template<CryptoHash::Algorithm hash_algorithm>
|
||
|
class DiffieHellmanGroupExchange : public SSH::KeyExchange {
|
||
|
LogHandle log_;
|
||
|
SSH::Session *session_;
|
||
|
DH *dh_;
|
||
|
Buffer key_exchange_;
|
||
|
BIGNUM *k_;
|
||
|
public:
|
||
|
DiffieHellmanGroupExchange(SSH::Session *session, const std::string& key_exchange_name)
|
||
|
: SSH::KeyExchange(key_exchange_name),
|
||
|
log_("/ssh/key_exchange/" + key_exchange_name),
|
||
|
session_(session),
|
||
|
dh_(NULL),
|
||
|
key_exchange_(),
|
||
|
k_()
|
||
|
{ }
|
||
|
|
||
|
~DiffieHellmanGroupExchange()
|
||
|
{ }
|
||
|
|
||
|
KeyExchange *clone(void) const
|
||
|
{
|
||
|
return (new DiffieHellmanGroupExchange(session_, name_));
|
||
|
}
|
||
|
|
||
|
bool hash(Buffer *out, const Buffer *in) const
|
||
|
{
|
||
|
return (CryptoHash::hash(hash_algorithm, out, in));
|
||
|
}
|
||
|
|
||
|
bool input(Filter* sender, Buffer *in)
|
||
|
{
|
||
|
SSH::ServerHostKey *key;
|
||
|
uint32_t max, min, n;
|
||
|
BIGNUM *e, *f;
|
||
|
Buffer server_public_key;
|
||
|
Buffer signature;
|
||
|
Buffer packet;
|
||
|
Buffer group;
|
||
|
Buffer exchange_hash;
|
||
|
Buffer data;
|
||
|
Buffer initialize;
|
||
|
|
||
|
switch (in->peek()) {
|
||
|
case DiffieHellmanGroupExchangeRequest:
|
||
|
if (session_->role_ != SSH::ServerRole) {
|
||
|
ERROR(log_) << "Received group exchange request as client.";
|
||
|
return (false);
|
||
|
}
|
||
|
in->skip(1);
|
||
|
key_exchange_ = *in;
|
||
|
if (!SSH::UInt32::decode(&min, in))
|
||
|
return (false);
|
||
|
if (!SSH::UInt32::decode(&n, in))
|
||
|
return (false);
|
||
|
if (!SSH::UInt32::decode(&max, in))
|
||
|
return (false);
|
||
|
if (min < DH_GROUP_MIN)
|
||
|
min = DH_GROUP_MIN;
|
||
|
if (max > DH_GROUP_MAX)
|
||
|
max = DH_GROUP_MAX;
|
||
|
if (min > max)
|
||
|
return (false);
|
||
|
if (n < min)
|
||
|
n = min;
|
||
|
else if (n > max)
|
||
|
n = max;
|
||
|
|
||
|
#ifdef USE_TEST_GROUP
|
||
|
group.append(test_prime_and_generator, sizeof test_prime_and_generator);
|
||
|
dh_ = DH_new();
|
||
|
SSH::MPInt::decode(&dh_->p, &group);
|
||
|
SSH::MPInt::decode(&dh_->g, &group);
|
||
|
ASSERT(log_, group.empty());
|
||
|
#else
|
||
|
DEBUG(log_) << "Doing DH_generate_parameters for " << n << " bits.";
|
||
|
ASSERT(log_, dh_ == NULL);
|
||
|
dh_ = DH_generate_parameters(n, 2, NULL, NULL);
|
||
|
if (dh_ == NULL) {
|
||
|
ERROR(log_) << "DH_generate_parameters failed.";
|
||
|
return (false);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
SSH::MPInt::encode(&group, dh_->p);
|
||
|
SSH::MPInt::encode(&group, dh_->g);
|
||
|
key_exchange_.append(group);
|
||
|
|
||
|
packet.append(DiffieHellmanGroupExchangeGroup);
|
||
|
packet.append(group);
|
||
|
sender->produce(packet);
|
||
|
return (true);
|
||
|
case DiffieHellmanGroupExchangeGroup:
|
||
|
if (session_->role_ != SSH::ClientRole) {
|
||
|
ERROR(log_) << "Received DH group as server.";
|
||
|
return (false);
|
||
|
}
|
||
|
in->skip(1);
|
||
|
key_exchange_.append(in);
|
||
|
|
||
|
dh_ = DH_new();
|
||
|
if (dh_ == NULL) {
|
||
|
ERROR(log_) << "DH_new failed.";
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
if (!SSH::MPInt::decode(&dh_->p, in))
|
||
|
return (false);
|
||
|
if (!SSH::MPInt::decode(&dh_->g, in))
|
||
|
return (false);
|
||
|
|
||
|
if (!DH_generate_key(dh_)) {
|
||
|
ERROR(log_) << "DH_generate_key failed.";
|
||
|
return (false);
|
||
|
}
|
||
|
e = dh_->pub_key;
|
||
|
|
||
|
SSH::MPInt::encode(&initialize, e);
|
||
|
key_exchange_.append(initialize);
|
||
|
|
||
|
packet.append(DiffieHellmanGroupExchangeInitialize);
|
||
|
packet.append(initialize);
|
||
|
sender->produce(packet);
|
||
|
return (true);
|
||
|
case DiffieHellmanGroupExchangeInitialize:
|
||
|
if (session_->role_ != SSH::ServerRole) {
|
||
|
ERROR(log_) << "Received group exchange initialization as client.";
|
||
|
return (false);
|
||
|
}
|
||
|
in->skip(1);
|
||
|
key_exchange_.append(in);
|
||
|
if (!SSH::MPInt::decode(&e, in))
|
||
|
return (false);
|
||
|
|
||
|
if (!DH_generate_key(dh_))
|
||
|
return (false);
|
||
|
f = dh_->pub_key;
|
||
|
|
||
|
SSH::MPInt::encode(&key_exchange_, f);
|
||
|
if (!exchange_finish(e)) {
|
||
|
ERROR(log_) << "Server key exchange finish failed.";
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
key = session_->chosen_algorithms_.server_host_key_;
|
||
|
if (!key->sign(&signature, &session_->exchange_hash_))
|
||
|
return (false);
|
||
|
key->encode_public_key(&server_public_key);
|
||
|
|
||
|
packet.append(DiffieHellmanGroupExchangeReply);
|
||
|
SSH::String::encode(&packet, server_public_key);
|
||
|
SSH::MPInt::encode(&packet, f);
|
||
|
SSH::String::encode(&packet, &signature);
|
||
|
sender->produce(packet);
|
||
|
|
||
|
sender->flush(SSH::ALGORITHM_NEGOTIATED);
|
||
|
/*
|
||
|
* XXX
|
||
|
* Should send NEWKEYS.
|
||
|
*/
|
||
|
return (true);
|
||
|
case DiffieHellmanGroupExchangeReply:
|
||
|
if (session_->role_ != SSH::ClientRole) {
|
||
|
ERROR(log_) << "Received group exchange reply as client.";
|
||
|
return (false);
|
||
|
}
|
||
|
in->skip(1);
|
||
|
if (!SSH::String::decode(&server_public_key, in))
|
||
|
return (false);
|
||
|
if (!SSH::MPInt::decode(&f, in))
|
||
|
return (false);
|
||
|
if (!SSH::String::decode(&signature, in))
|
||
|
return (false);
|
||
|
|
||
|
key = session_->chosen_algorithms_.server_host_key_;
|
||
|
if (!key->decode_public_key(&server_public_key)) {
|
||
|
ERROR(log_) << "Could not decode server public key:" << std::endl << server_public_key.hexdump();
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
SSH::MPInt::encode(&key_exchange_, f);
|
||
|
if (!exchange_finish(f)) {
|
||
|
ERROR(log_) << "Client key exchange finish failed.";
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
if (!key->verify(&signature, &session_->exchange_hash_)) {
|
||
|
ERROR(log_) << "Failed to verify exchange hash.";
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
sender->flush(SSH::ALGORITHM_NEGOTIATED);
|
||
|
/*
|
||
|
* XXX
|
||
|
* Should send NEWKEYS, but we're not ready for that yet.
|
||
|
* For now we just assume the peer will do it. How lazy,
|
||
|
* no?
|
||
|
*/
|
||
|
return (true);
|
||
|
default:
|
||
|
ERROR(log_) << "Not yet implemented.";
|
||
|
return (false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool init(Buffer *out)
|
||
|
{
|
||
|
ASSERT(log_, out->empty());
|
||
|
ASSERT(log_, session_->role_ == SSH::ClientRole);
|
||
|
|
||
|
Buffer request;
|
||
|
SSH::UInt32::encode(&request, DH_GROUP_MIN);
|
||
|
SSH::UInt32::encode(&request, DH_GROUP_MIN);
|
||
|
SSH::UInt32::encode(&request, DH_GROUP_MAX);
|
||
|
|
||
|
key_exchange_ = request;
|
||
|
|
||
|
out->append(DiffieHellmanGroupExchangeRequest);
|
||
|
out->append(request);
|
||
|
|
||
|
return (true);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
bool exchange_finish(BIGNUM *remote_pubkey)
|
||
|
{
|
||
|
SSH::ServerHostKey *key;
|
||
|
Buffer server_public_key;
|
||
|
Buffer exchange_hash;
|
||
|
Buffer data;
|
||
|
|
||
|
ASSERT(log_, dh_ != NULL);
|
||
|
|
||
|
uint8_t secret[DH_size(dh_)];
|
||
|
int secretlen = DH_compute_key(secret, remote_pubkey, dh_);
|
||
|
if (secretlen == -1)
|
||
|
return (false);
|
||
|
k_ = BN_bin2bn(secret, secretlen, NULL);
|
||
|
if (k_ == NULL)
|
||
|
return (false);
|
||
|
|
||
|
key = session_->chosen_algorithms_.server_host_key_;
|
||
|
key->encode_public_key(&server_public_key);
|
||
|
|
||
|
SSH::String::encode(&data, session_->client_version_);
|
||
|
SSH::String::encode(&data, session_->server_version_);
|
||
|
SSH::String::encode(&data, session_->client_kexinit_);
|
||
|
SSH::String::encode(&data, session_->server_kexinit_);
|
||
|
SSH::String::encode(&data, server_public_key);
|
||
|
data.append(key_exchange_);
|
||
|
SSH::MPInt::encode(&data, k_);
|
||
|
|
||
|
if (!CryptoHash::hash(hash_algorithm, &exchange_hash, &data))
|
||
|
return (false);
|
||
|
|
||
|
session_->exchange_hash_ = exchange_hash;
|
||
|
SSH::MPInt::encode(&session_->shared_secret_, k_);
|
||
|
if (session_->session_id_.empty())
|
||
|
session_->session_id_ = exchange_hash;
|
||
|
|
||
|
return (true);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
void
|
||
|
SSH::KeyExchange::add_algorithms(SSH::Session *session)
|
||
|
{
|
||
|
session->algorithm_negotiation_->add_algorithm(new DiffieHellmanGroupExchange<CryptoHash::SHA256>(session, "diffie-hellman-group-exchange-sha256"));
|
||
|
session->algorithm_negotiation_->add_algorithm(new DiffieHellmanGroupExchange<CryptoHash::SHA1>(session, "diffie-hellman-group-exchange-sha1"));
|
||
|
}
|