0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-19 19:52:15 +02:00

ovpn3 core : Added automatic data limits for Blowfish,

Triple DES, and other 64-bit block-size ciphers vulnerable
to "Sweet32" birthday attack (CVE-2016-6329).  Limit such
cipher keys to no more than 64 MB of data
encrypted/decrypted.  While our overall goal is to limit
data-limited keys to 64 MB, we trigger a renegotiation
at 48 MB to compensate for possible delays in renegotiation
and rollover to the new key.

This client-side implementation extends data limit
protection to the entire session, even when the server
doesn't implement data limits.

This capability is advertised to servers via the a
peer info setting:

  IV_BS64DL=1

meaning "Block-Size 64-bit Data Limit".  The "1" indicates
the implementation version.

The implementation currently has some limitations:

* Keys are renegotiated at a maximum rate of once per
  5 seconds to reduce the likelihood of loss of
  synchronization between peers.

* The maximum renegotiation rate may be further extended
  if the peer delays rollover from the old to new key
  after renegotiation.

Added N_KEY_LIMIT_RENEG stats counter to count the number
of data-limit-triggered renegotiations.

Added new stats counter KEY_STATE_ERROR which roughly
corresponds to the OpenVPN 2.x error "TLS Error:
local/remote TLS keys are out of sync".

Prevously, the TLS ack/retransmit timeout was hardcoded to
2 seconds.  Now we lower the default to 1 second and make
it variable using the (pushable) "tls-timeout" directive.
Additionally, the tls-timeout directive can be specified
in milliseconds instead of seconds by using the
"tls-timeout-ms" form of the directive.

Made the "become primary" time duration configurable via
the (pushable) "become-primary" directive which accepts
a number-of-seconds parameter.  become-primary indicates
the time delay between renegotiation and rollover to the
new key for encryption/transmission.  become-primary
defaults to the handshake-window which in turn defaults
to 60 seconds.

Incremented core version to 3.0.20.
This commit is contained in:
James Yonan 2016-09-01 15:19:00 -06:00
parent 26d0169055
commit 662bf7833e
13 changed files with 776 additions and 202 deletions

View File

@ -296,6 +296,8 @@ namespace openvpn {
// do a full flush
Base::flush(true);
}
else
cli_stats->error(Error::KEY_STATE_ERROR);
// schedule housekeeping wakeup
set_housekeeping_timer();
@ -783,7 +785,7 @@ namespace openvpn {
void process_inactive(const OptionList& opt)
{
try {
const Option *o = load_duration_parm(inactive_duration, "inactive", opt, 1, false);
const Option *o = load_duration_parm(inactive_duration, "inactive", opt, 1, false, false);
if (o)
{
if (o->size() >= 3)

View File

@ -24,6 +24,6 @@
#ifndef OPENVPN_COMMON_VERSION_H
#define OPENVPN_COMMON_VERSION_H
#define OPENVPN_VERSION "3.0.19"
#define OPENVPN_VERSION "3.0.20"
#endif // OPENVPN_COMMON_VERSION_H

View File

@ -0,0 +1,45 @@
// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2015 OpenVPN Technologies, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
// Special data limits on Blowfish, Triple DES, and other 64-bit
// block-size ciphers vulnerable to "Sweet32" birthday attack
// (CVE-2016-6329). Limit such cipher keys to no more than 64 MB
// of data encrypted/decrypted. Note that we trigger early at
// 48 MB to compensate for possible delays in renegotiation and
// rollover to the new key.
#ifndef OPENVPN_CRYPTO_DATALIMIT_H
#define OPENVPN_CRYPTO_DATALIMIT_H
#include <openvpn/crypto/cryptoalgs.hpp>
#ifndef OPENVPN_BS64_DATA_LIMIT
#define OPENVPN_BS64_DATA_LIMIT 48000000
#endif
namespace openvpn {
inline bool is_bs64_cipher(const CryptoAlgs::Type cipher)
{
return CryptoAlgs::get(cipher).block_size() == 8;
}
}
#endif

View File

@ -77,12 +77,15 @@ namespace openvpn {
CLIENT_RESTART, // RESTART message from server received
N_PAUSE, // Number of transitions to Pause state
N_RECONNECT, // Number of reconnections
N_KEY_LIMIT_RENEG, // Number of renegotiations triggered by per-key limits such as data or packet limits
KEY_STATE_ERROR, // Received packet didn't match expected key state
PROXY_ERROR, // HTTP proxy error
PROXY_NEED_CREDS, // HTTP proxy needs credentials
// key event errors
KEV_NEGOTIATE_ERROR,
KEV_EXPIRE_ERROR,
KEV_PENDING_ERROR,
N_KEV_EXPIRE,
// Packet ID error detail
PKTID_INVALID,
@ -147,10 +150,13 @@ namespace openvpn {
"CLIENT_RESTART",
"N_PAUSE",
"N_RECONNECT",
"N_KEY_LIMIT_RENEG",
"KEY_STATE_ERROR",
"PROXY_ERROR",
"PROXY_NEED_CREDS",
"KEV_NEGOTIATE_ERROR",
"KEV_EXPIRE_ERROR",
"KEV_PENDING_ERROR",
"N_KEV_EXPIRE",
"PKTID_INVALID",
"PKTID_BACKTRACK",
"PKTID_EXPIRE",

View File

@ -38,10 +38,6 @@ namespace openvpn {
public:
typedef reliable::id_t id_t;
enum {
RETRANSMIT = 2 // retransmit in N seconds if ACK not received
};
class Message : public ReliableMessageBase<PACKET>
{
friend class ReliableSendTemplate;
@ -61,9 +57,9 @@ namespace openvpn {
return ret;
}
void reset_retransmit(const Time& now)
void reset_retransmit(const Time& now, const Time::Duration& tls_timeout)
{
retransmit_at_ = now + Time::Duration::seconds(RETRANSMIT);
retransmit_at_ = now + tls_timeout;
}
private:
@ -128,11 +124,11 @@ namespace openvpn {
// Return a fresh Message object that can be used to
// construct the next packet in the sequence. Don't call
// unless ready() returns true.
Message& send(const Time& now)
Message& send(const Time& now, const Time::Duration& tls_timeout)
{
Message& msg = window_.ref_by_id(next);
msg.id_ = next++;
msg.reset_retransmit(now);
msg.reset_retransmit(now, tls_timeout);
return msg;
}

View File

@ -499,6 +499,16 @@ namespace openvpn {
ManLink::send->float_notify(addr);
}
virtual void data_limit_notify(const int key_id,
const DataLimit::Mode cdl_mode,
const DataLimit::State cdl_status)
{
Base::update_now();
Base::data_limit_notify(key_id, cdl_mode, cdl_status);
Base::flush(true);
set_housekeeping_timer();
}
bool get_management()
{
if (!ManLink::send)

188
openvpn/ssl/datalimit.hpp Normal file
View File

@ -0,0 +1,188 @@
// OpenVPN
// Copyright (C) 2012-2015 OpenVPN Technologies, Inc.
// All rights reserved
#ifndef OPENVPN_SSL_DATALIMIT_H
#define OPENVPN_SSL_DATALIMIT_H
#include <openvpn/common/exception.hpp>
namespace openvpn {
// Helper for handling keys which can have an upper limit
// on maximum amount of data encrypted/decrypted, such
// as Blowfish.
class DataLimit
{
public:
typedef unsigned int size_type;
enum Mode {
Encrypt=0,
Decrypt=1,
};
enum State {
None=0,
Green=1,
Red=2,
};
struct Parameters
{
size_type encrypt_red_limit = 0;
size_type decrypt_red_limit = 0;
};
DataLimit(const Parameters& p)
: encrypt(p.encrypt_red_limit),
decrypt(p.decrypt_red_limit)
{
}
State update_state(const Mode mode, const State newstate)
{
return elgible(mode, component(mode).update_state(newstate));
}
State add(const Mode mode, const size_type n)
{
return elgible(mode, component(mode).add(n));
}
bool is_decrypt_green()
{
return decrypt.get_state() >= Green;
}
static const char *mode_str(const Mode m)
{
switch (m)
{
case Encrypt:
return "Encrypt";
case Decrypt:
return "Decrypt";
default:
return "Mode_???";
}
}
static const char *state_str(const State s)
{
switch (s)
{
case None:
return "None";
case Green:
return "Green";
case Red:
return "Red";
default:
return "State_???";
}
}
private:
// Don't return Encrypt-Red until Decrypt-Green
// has been received. This confirms that the peer
// is now transmitting on the key ID, making it
// eligible for renegotiation.
State elgible(const Mode mode, const State state)
{
// Bit positions for Encrypt/Decrypt and Green/Red
enum {
EG = 1<<0,
ER = 1<<1,
DG = 1<<2,
DR = 1<<3,
};
if (state > None)
{
const unsigned int mask = 1 << ((int(state) - 1) + (int(mode) << 1));
if (!(flags & mask))
{
flags |= mask;
if ((mask & (ER|DG)) && ((flags & (ER|DG)) == (ER|DG)))
return Red;
else if (mask & ER)
return None;
else
return state;
}
}
return None;
}
class Component
{
public:
Component(const size_type red_limit_arg)
: red_limit(red_limit_arg)
{
}
State add(const size_type n)
{
bytes += n;
return update_state(transition(state));
}
State update_state(const State newstate)
{
State ret = None;
if (newstate > state)
state = ret = newstate;
return ret;
}
State get_state() const
{
return state;
}
private:
State transition(State s) const
{
switch (s)
{
case None:
if (bytes)
return Green;
else
return None;
case Green:
if (red_limit && bytes >= red_limit)
return Red;
else
return None;
case Red:
default:
return None;
}
}
const size_type red_limit;
size_type bytes = 0;
State state = None;
};
Component& component(const Mode m)
{
switch (m)
{
case Encrypt:
return encrypt;
case Decrypt:
return decrypt;
default:
throw Exception("DataLimit::Component: unknown mode");
}
}
Component encrypt;
Component decrypt;
unsigned int flags = 0;
};
}
#endif

View File

@ -44,6 +44,7 @@
#include <openvpn/common/number.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/format.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/buffer/safestr.hpp>
#include <openvpn/buffer/bufcomposed.hpp>
@ -57,10 +58,12 @@
#include <openvpn/crypto/ovpnhmac.hpp>
#include <openvpn/crypto/packet_id.hpp>
#include <openvpn/crypto/static_key.hpp>
#include <openvpn/crypto/bs64_data_limit.hpp>
#include <openvpn/log/sessionstats.hpp>
#include <openvpn/ssl/protostack.hpp>
#include <openvpn/ssl/psid.hpp>
#include <openvpn/ssl/tlsprf.hpp>
#include <openvpn/ssl/datalimit.hpp>
#include <openvpn/transport/protocol.hpp>
#include <openvpn/tun/layer.hpp>
#include <openvpn/tun/tunmtu.hpp>
@ -300,6 +303,7 @@ namespace openvpn {
Time::Duration become_primary; // KeyContext (that is ACTIVE) becomes primary at this time
Time::Duration renegotiate; // start SSL/TLS renegotiation at this time
Time::Duration expire; // KeyContext expires at this time
Time::Duration tls_timeout; // Packet retransmit timeout on TLS control channel
// keepalive parameters
Time::Duration keepalive_ping;
@ -333,6 +337,7 @@ namespace openvpn {
max_ack_list = 4;
handshake_window = Time::Duration::seconds(60);
renegotiate = Time::Duration::seconds(3600);
tls_timeout = Time::Duration::seconds(1);
keepalive_ping = Time::Duration::seconds(8);
keepalive_timeout = Time::Duration::seconds(40);
comp_ctx = CompressContext(CompressContext::NONE, false);
@ -340,9 +345,6 @@ namespace openvpn {
pid_mode = PacketIDReceive::UDP_MODE;
key_direction = default_key_direction;
// load parameters that can be present in both config file or pushed options
load_common(opt, pco, server ? LOAD_COMMON_SERVER : LOAD_COMMON_CLIENT);
// layer
{
const Option* dev = opt.get_ptr("dev-type");
@ -467,20 +469,14 @@ namespace openvpn {
// tun-mtu
tun_mtu = parse_tun_mtu(opt, tun_mtu);
// load parameters that can be present in both config file or pushed options
load_common(opt, pco, server ? LOAD_COMMON_SERVER : LOAD_COMMON_CLIENT);
}
// load options string pushed by server
void process_push(const OptionList& opt, const ProtoContextOptions& pco)
{
try {
// load parameters that can be present in both config file or pushed options
load_common(opt, pco, LOAD_COMMON_CLIENT_PUSHED);
}
catch (const std::exception& e)
{
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed parameter: " << e.what());
}
// data channel
{
// cipher
@ -569,6 +565,15 @@ namespace openvpn {
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed peer-id: " << e.what());
}
try {
// load parameters that can be present in both config file or pushed options
load_common(opt, pco, LOAD_COMMON_CLIENT_PUSHED);
}
catch (const std::exception& e)
{
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed parameter: " << e.what());
}
// show negotiated options
OPENVPN_LOG_STRING_PROTO(show_options());
}
@ -687,6 +692,8 @@ namespace openvpn {
out << compstr;
if (extra_peer_info)
out << extra_peer_info->to_string();
if (is_bs64_cipher(dc.cipher()))
out << "IV_BS64DL=1\n"; // indicate support for data limits when using 64-bit block-size ciphers, version 1 (CVE-2016-6329)
const std::string ret = out.str();
OPENVPN_LOG_PROTO("Peer Info:" << std::endl << ret);
return ret;
@ -716,13 +723,22 @@ namespace openvpn {
const LoadCommonType type)
{
// duration parms
load_duration_parm(renegotiate, "reneg-sec", opt, 10, false);
load_duration_parm(renegotiate, "reneg-sec", opt, 10, false, false);
expire = renegotiate;
load_duration_parm(expire, "tran-window", opt, 10, false);
load_duration_parm(expire, "tran-window", opt, 10, false, false);
expire += renegotiate;
load_duration_parm(handshake_window, "hand-window", opt, 10, false);
become_primary = Time::Duration::seconds(std::min(handshake_window.to_seconds(),
load_duration_parm(handshake_window, "hand-window", opt, 10, false, false);
if (is_bs64_cipher(dc.cipher())) // special data limits for 64-bit block-size ciphers (CVE-2016-6329)
{
become_primary = Time::Duration::seconds(5);
tls_timeout = Time::Duration::milliseconds(1000);
}
else
become_primary = Time::Duration::seconds(std::min(handshake_window.to_seconds(),
renegotiate.to_seconds() / 2));
load_duration_parm(become_primary, "become-primary", opt, 0, false, false);
load_duration_parm(tls_timeout, "tls-timeout", opt, 100, false, true);
if (type == LOAD_COMMON_SERVER)
renegotiate += handshake_window; // avoid renegotiation collision with client
@ -731,13 +747,13 @@ namespace openvpn {
const Option *o = opt.get_ptr("keepalive");
if (o)
{
set_duration_parm(keepalive_ping, "keepalive ping", o->get(1, 16), 1, false);
set_duration_parm(keepalive_timeout, "keepalive timeout", o->get(2, 16), 1, type == LOAD_COMMON_SERVER);
set_duration_parm(keepalive_ping, "keepalive ping", o->get(1, 16), 1, false, false);
set_duration_parm(keepalive_timeout, "keepalive timeout", o->get(2, 16), 1, type == LOAD_COMMON_SERVER, false);
}
else
{
load_duration_parm(keepalive_ping, "ping", opt, 1, false);
load_duration_parm(keepalive_timeout, "ping-restart", opt, 1, false);
load_duration_parm(keepalive_ping, "ping", opt, 1, false, false);
load_duration_parm(keepalive_timeout, "ping-restart", opt, 1, false, false);
}
}
}
@ -834,8 +850,6 @@ namespace openvpn {
}
}
bool is_secondary() const { return flags & SECONDARY; }
unsigned int flags;
unsigned int opcode;
int peer_id_;
@ -1112,25 +1126,101 @@ namespace openvpn {
public:
typedef RCPtr<KeyContext> Ptr;
// timeline of events for KeyContext (occurring in order)
// KeyContext events occur on two basic key types:
// Primary Key -- the key we transmit/encrypt on.
// Secondary Key -- new keys and retiring keys.
//
// The very first key created (key_id == 0) is a
// primary key. Subsequently created keys are always,
// at least initially, secondary keys. Secondary keys
// promote to primary via the KEV_BECOME_PRIMARY event
// (actually KEV_BECOME_PRIMARY swaps the primary and
// secondary keys, so the old primary is demoted
// to secondary and marked for expiration).
//
// Secondary keys are created by:
// 1. locally-generated soft renegotiation requests, and
// 2. peer-requested soft renegotiation requests.
// In each case, any previous secondary key will be
// wiped (including a secondary key that exists due to
// demotion of a previous primary key that has been marked
// for expiration).
enum EventType {
KEV_NONE,
KEV_ACTIVE, // KeyContext has reached the ACTIVE state
KEV_NEGOTIATE, // SSL/TLS negotiation must complete by this time
KEV_BECOME_PRIMARY, // KeyContext becomes primary for data channel traffic
KEV_RENEGOTIATE, // start renegotiating a new KeyContext at this time
KEV_EXPIRE, // expiration of KeyContext
KEV_NEGOTIATE_FAILED, // SSL/TLS negotiation failed
// KeyContext has reached the ACTIVE state, occurs on both
// primary and secondary.
KEV_ACTIVE,
// SSL/TLS negotiation must complete by this time. If this
// event is hit on the first primary (i.e. first KeyContext
// with key_id == 0), it is fatal to the session and will
// trigger a disconnect/reconnect. If it's hit on the
// secondary, it will trigger a soft renegotiation.
KEV_NEGOTIATE,
// When a KeyContext (normally the secondary) is scheduled
// to transition to the primary state.
KEV_BECOME_PRIMARY,
// Waiting for condition on secondary (usually
// dataflow-based) to trigger KEV_BECOME_PRIMARY.
KEV_PRIMARY_PENDING,
// Start renegotiating a new KeyContext on secondary
// (ignored unless originating on primary).
KEV_RENEGOTIATE,
// Trigger a renegotiation originating from either
// primary or secondary.
KEV_RENEGOTIATE_FORCE,
// Queue delayed renegotiation request from secondary
// to take effect after KEV_BECOME_PRIMARY.
KEV_RENEGOTIATE_QUEUE,
// Expiration of KeyContext.
KEV_EXPIRE,
};
// for debugging
static const char *event_type_string(const EventType et)
{
switch (et)
{
case KEV_NONE:
return "KEV_NONE";
case KEV_ACTIVE:
return "KEV_ACTIVE";
case KEV_NEGOTIATE:
return "KEV_NEGOTIATE";
case KEV_BECOME_PRIMARY:
return "KEV_BECOME_PRIMARY";
case KEV_PRIMARY_PENDING:
return "KEV_PRIMARY_PENDING";
case KEV_RENEGOTIATE:
return "KEV_RENEGOTIATE";
case KEV_RENEGOTIATE_FORCE:
return "KEV_RENEGOTIATE_FORCE";
case KEV_RENEGOTIATE_QUEUE:
return "KEV_RENEGOTIATE_QUEUE";
case KEV_EXPIRE:
return "KEV_EXPIRE";
default:
return "KEV_?";
}
}
KeyContext(ProtoContext& p, const bool initiator)
: Base(*p.config->ssl_factory, p.config->now, p.config->frame, p.stats,
: Base(*p.config->ssl_factory,
p.config->now, p.config->tls_timeout,
p.config->frame, p.stats,
p.config->reliable_window, p.config->max_ack_list),
proto(p),
state(STATE_UNDEF),
crypto_flags(0),
dirty(0),
handled_pid_wrap(false),
key_limit_renegotiation_fired(false),
is_reliable(p.config->protocol.is_reliable()),
tlsprf(p.config->tlsprf_factory->new_obj(p.is_server()))
{
@ -1232,8 +1322,12 @@ namespace openvpn {
// compress and encrypt packet and prepend op header
const bool pid_wrap = do_encrypt(buf, true);
// check for rare situation where packet ID is near overflow
test_pid_wrap(pid_wrap);
// Trigger a new SSL/TLS negotiation if packet ID (a 32-bit unsigned int)
// is getting close to wrapping around. If it wraps back to 0 without
// a renegotiation, it would cause the relay protection logic to wrongly
// think that all further packets are replays.
if (pid_wrap)
schedule_key_limit_renegotiation();
}
else
buf.reset_size(); // no crypto context available
@ -1262,6 +1356,10 @@ namespace openvpn {
invalidate(err);
}
// trigger renegotiation if we hit decrypt data limit
if (data_limit)
data_limit_add(DataLimit::Decrypt, buf.size());
// decompress packet
if (compress)
compress->decompress(buf);
@ -1280,9 +1378,34 @@ namespace openvpn {
// usually called by parent ProtoContext object when this KeyContext
// has been retired.
void prepare_expire()
void prepare_expire(const EventType current_ev = KeyContext::KEV_NONE)
{
set_event(KEV_NONE, KEV_EXPIRE, construct_time + proto.config->expire);
set_event(current_ev,
KEV_EXPIRE,
key_limit_renegotiation_fired ? data_limit_expire() : construct_time + proto.config->expire);
}
// set a default next event, if unspecified
void set_next_event_if_unspecified()
{
if (next_event == KEV_NONE && !invalidated())
prepare_expire();
}
// set a key limit renegotiation event at time t
void key_limit_reneg(const EventType ev, const Time& t)
{
if (t.defined())
set_event(KEV_NONE, ev, t + Time::Duration::seconds(proto.is_server() ? 2 : 1));
}
// return time of upcoming KEV_BECOME_PRIMARY event
Time become_primary_time()
{
if (next_event == KEV_BECOME_PRIMARY)
return next_event_time;
else
return Time();
}
// is an KEV_x event pending?
@ -1297,7 +1420,7 @@ namespace openvpn {
EventType get_event() const { return current_event; }
// clear KEV_x event
void reset_event() { set_event(KEV_NONE); }
void reset_event() { current_event = KEV_NONE; }
// was session invalidated by an exception?
bool invalidated() const { return Base::invalidated(); }
@ -1455,6 +1578,16 @@ namespace openvpn {
const unsigned int key_dir = proto.is_server() ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
const OpenVPNStaticKey& key = data_channel_key->key;
// special data limits for 64-bit block-size ciphers (CVE-2016-6329)
if (is_bs64_cipher(c.dc.cipher()))
{
DataLimit::Parameters dp;
dp.encrypt_red_limit = OPENVPN_BS64_DATA_LIMIT;
dp.decrypt_red_limit = OPENVPN_BS64_DATA_LIMIT;
OPENVPN_LOG_PROTO("Per-Key Data Limit: " << dp.encrypt_red_limit << '/' << dp.decrypt_red_limit);
data_limit.reset(new DataLimit(dp));
}
// build crypto context for data channel encryption/decryption
crypto = c.dc.context().new_obj(key_id_);
crypto_flags = crypto->defined();
@ -1490,6 +1623,13 @@ namespace openvpn {
}
}
void data_limit_notify(const DataLimit::Mode cdl_mode,
const DataLimit::State cdl_status)
{
if (data_limit)
data_limit_event(cdl_mode, data_limit->update_state(cdl_mode, cdl_status));
}
private:
bool do_encrypt(BufferAllocated& buf, const bool compress_hint)
{
@ -1499,6 +1639,10 @@ namespace openvpn {
if (compress)
compress->compress(buf, compress_hint);
// trigger renegotiation if we hit encrypt data limit
if (data_limit)
data_limit_add(DataLimit::Encrypt, buf.size());
if (enable_op32)
{
const std::uint32_t op32 = htonl(op32_compose(DATA_V2, key_id_, remote_peer_id));
@ -1531,26 +1675,19 @@ namespace openvpn {
void set_state(const int newstate)
{
OPENVPN_LOG_PROTO_VERBOSE("KeyContext[" << key_id_ << "] " << (proto.is_server() ? "SERVER " : "CLIENT ") << state_string(state) << " -> " << state_string(newstate));
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << state_string(state) << " -> " << state_string(newstate));
state = newstate;
}
void set_event(const EventType current)
{
OPENVPN_LOG_PROTO_VERBOSE("KeyContext[" << key_id_ << "] " << event_type_string(current));
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << event_type_string(current));
current_event = current;
}
void set_event(const EventType next, const Time& next_time)
{
OPENVPN_LOG_PROTO_VERBOSE("KeyContext[" << key_id_ << "] " << event_type_string(next) << '(' << seconds_until(next_time) << ')');
next_event = next;
next_event_time = next_time;
}
void set_event(const EventType current, const EventType next, const Time& next_time)
{
OPENVPN_LOG_PROTO_VERBOSE("KeyContext[" << key_id_ << "] " << event_type_string(current) << " -> " << event_type_string(next) << '(' << seconds_until(next_time) << ')');
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << event_type_string(current) << " -> " << event_type_string(next) << '(' << seconds_until(next_time) << ')');
current_event = current;
next_event = next;
next_event_time = next_time;
@ -1559,31 +1696,76 @@ namespace openvpn {
void invalidate_callback() // called by ProtoStackBase when session is invalidated
{
reached_active_time_ = Time();
set_event(KEV_NONE, Time::infinite());
next_event = KEV_NONE;
next_event_time = Time::infinite();
}
// Trigger a new SSL/TLS negotiation if packet ID (a 32-bit unsigned int)
// is getting close to wrapping around. If it wraps back to 0 without
// a renegotiation, it would cause the relay protection logic to wrongly
// think that all further packets are replays.
void test_pid_wrap(const bool pid_wrap)
// Trigger a renegotiation based on data flow condition such
// as per-key data limit or packet ID approaching wraparound.
void schedule_key_limit_renegotiation()
{
if (pid_wrap && !handled_pid_wrap)
if (!key_limit_renegotiation_fired && state >= ACTIVE && !invalidated())
{
trigger_renegotiation();
handled_pid_wrap = true;
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " SCHEDULE KEY LIMIT RENEGOTIATION");
key_limit_renegotiation_fired = true;
proto.stats->error(Error::N_KEY_LIMIT_RENEG);
// If primary, renegotiate now (within a second or two).
// If secondary, queue the renegotiation request until
// key reaches primary.
if (next_event == KEV_BECOME_PRIMARY) // secondary key before transition to primary?
set_event(KEV_RENEGOTIATE_QUEUE); // reneg request crosses over to primary, doesn't wipe next_event (KEV_BECOME_PRIMARY)
else
key_limit_reneg(KEV_RENEGOTIATE, *now);
}
}
void trigger_renegotiation()
// Handle data-limited keys such as Blowfish and other 64-bit block-size ciphers.
void data_limit_add(const DataLimit::Mode mode, const size_t size)
{
if (state >= ACTIVE && !invalidated())
set_event(KEV_RENEGOTIATE, KEV_EXPIRE, construct_time + proto.config->expire);
const DataLimit::State state = data_limit->add(mode, size);
if (state > DataLimit::None)
data_limit_event(mode, state);
}
// Handle a DataLimit event.
void data_limit_event(const DataLimit::Mode mode, const DataLimit::State state)
{
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " DATA LIMIT " << DataLimit::mode_str(mode) << ' ' << DataLimit::state_str(state) << " key_id=" << key_id_);
// State values:
// DataLimit::Green -- first packet received and decrypted.
// DataLimit::Red -- data limit has been exceeded, so trigger a renegotiation.
if (state == DataLimit::Red)
schedule_key_limit_renegotiation();
// When we are in KEV_PRIMARY_PENDING state, we must receive at least
// one packet from the peer on this key before we transition to
// KEV_BECOME_PRIMARY so we can transmit on it.
if (next_event == KEV_PRIMARY_PENDING && data_limit->is_decrypt_green())
set_event(KEV_NONE, KEV_BECOME_PRIMARY, *now + Time::Duration::seconds(1));
}
// Should we enter KEV_PRIMARY_PENDING state? Do it if:
// 1. we are a client,
// 2. data limit is enabled,
// 3. this is a renegotiated key in secondary context, i.e. not the first key, and
// 4. no data received yet from peer on this key.
bool data_limit_defer() const
{
return !proto.is_server() && data_limit && key_id_ && !data_limit->is_decrypt_green();
}
// General expiration set when key hits data limit threshold.
Time data_limit_expire() const
{
return *now + (proto.config->handshake_window * 2);
}
void active_event()
{
set_event(KEV_ACTIVE, KEV_BECOME_PRIMARY, construct_time + proto.config->become_primary);
set_event(KEV_ACTIVE, KEV_BECOME_PRIMARY, reached_active() + proto.config->become_primary);
}
void process_next_event()
@ -1592,26 +1774,24 @@ namespace openvpn {
{
switch (next_event)
{
case KEV_NEGOTIATE:
if (state >= ACTIVE)
set_event(KEV_NEGOTIATE, KEV_BECOME_PRIMARY, construct_time + proto.config->become_primary);
else
{
proto.stats->error(Error::KEV_NEGOTIATE_ERROR);
invalidate(Error::KEV_NEGOTIATE_ERROR);
set_event(KEV_NEGOTIATE_FAILED);
}
break;
case KEV_BECOME_PRIMARY:
set_event(KEV_BECOME_PRIMARY, KEV_RENEGOTIATE, construct_time + proto.config->renegotiate);
if (data_limit_defer())
set_event(KEV_NONE, KEV_PRIMARY_PENDING, data_limit_expire());
else
set_event(KEV_BECOME_PRIMARY, KEV_RENEGOTIATE, construct_time + proto.config->renegotiate);
break;
case KEV_RENEGOTIATE:
set_event(KEV_RENEGOTIATE, KEV_EXPIRE, construct_time + proto.config->expire);
case KEV_RENEGOTIATE_FORCE:
prepare_expire(next_event);
break;
case KEV_NEGOTIATE:
kev_error(KEV_NEGOTIATE, Error::KEV_NEGOTIATE_ERROR);
break;
case KEV_PRIMARY_PENDING:
kev_error(KEV_PRIMARY_PENDING, Error::KEV_PENDING_ERROR);
break;
case KEV_EXPIRE:
proto.stats->error(Error::KEV_EXPIRE_ERROR);
invalidate(Error::KEV_EXPIRE_ERROR);
set_event(KEV_EXPIRE);
kev_error(KEV_EXPIRE, Error::N_KEV_EXPIRE);
break;
default:
break;
@ -1619,6 +1799,13 @@ namespace openvpn {
}
}
void kev_error(const EventType ev, const Error::Type reason)
{
proto.stats->error(reason);
invalidate(reason);
set_event(ev);
}
unsigned int initial_op(const bool sender) const
{
if (key_id_)
@ -1803,7 +1990,7 @@ namespace openvpn {
{
std::unique_ptr<DataChannelKey> dck(new DataChannelKey());
tlsprf->generate_key_expansion(dck->key, proto.psid_self, proto.psid_peer);
OPENVPN_LOG_PROTO_VERBOSE("KEY " << proto.mode().str() << ' ' << dck->key.render());
OPENVPN_LOG_PROTO_VERBOSE(proto.debug_prefix() << " KEY " << proto.mode().str() << ' ' << dck->key.render());
tlsprf->erase();
dck.swap(data_channel_key);
if (!proto.dc_deferred)
@ -2060,30 +2247,6 @@ namespace openvpn {
gen_head(ACK_V1, buf);
}
// for debugging
static const char *event_type_string(const EventType et)
{
switch (et)
{
case KEV_NONE:
return "KEV_NONE";
case KEV_ACTIVE:
return "KEV_ACTIVE";
case KEV_NEGOTIATE:
return "KEV_NEGOTIATE";
case KEV_BECOME_PRIMARY:
return "KEV_BECOME_PRIMARY";
case KEV_RENEGOTIATE:
return "KEV_RENEGOTIATE";
case KEV_EXPIRE:
return "KEV_EXPIRE";
case KEV_NEGOTIATE_FAILED:
return "KEV_NEGOTIATE_FAILED";
default:
return "KEV_?";
}
}
// for debugging
static const char *state_string(const int s)
{
@ -2135,7 +2298,7 @@ namespace openvpn {
int remote_peer_id; // -1 to disable
bool enable_op32;
bool dirty;
bool handled_pid_wrap;
bool key_limit_renegotiation_fired;
bool is_reliable;
Compress::Ptr compress;
CryptoDCInstance::Ptr crypto;
@ -2148,6 +2311,7 @@ namespace openvpn {
std::deque<BufferPtr> app_pre_write_queue;
std::unique_ptr<DataChannelKey> data_channel_key;
BufferComposed app_recv_buf;
std::unique_ptr<DataLimit> data_limit;
};
public:
@ -2244,9 +2408,6 @@ namespace openvpn {
// validate options
c.validate_complete();
// by default, fast_transition is turned off
fast_transition = false;
// defer data channel initialization until after client options pull?
dc_deferred = c.dc_deferred;
@ -2292,7 +2453,7 @@ namespace openvpn {
// initialize key contexts
primary.reset(new KeyContext(*this, is_client()));
OPENVPN_LOG_PROTO_VERBOSE("New KeyContext PRIMARY id=" << primary->key_id());
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " New KeyContext PRIMARY id=" << primary->key_id());
// initialize keepalive timers
keepalive_expire = Time::infinite(); // initially disabled
@ -2325,8 +2486,7 @@ namespace openvpn {
void renegotiate()
{
// initialize secondary key context
secondary.reset(new KeyContext(*this, true));
OPENVPN_LOG_PROTO_VERBOSE("New KeyContext SECONDARY id=" << secondary->key_id() << " local-triggered");
new_secondary_key(true);
secondary->start();
}
@ -2424,6 +2584,7 @@ namespace openvpn {
// encrypt a data channel packet using primary KeyContext
void data_encrypt(BufferAllocated& in_out)
{
//OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " DATA ENCRYPT size=" << in_out.size());
primary->encrypt(in_out);
}
@ -2433,6 +2594,8 @@ namespace openvpn {
{
bool ret = false;
//OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " DATA DECRYPT key_id=" << select_key_context(type, false).key_id() << " size=" << in_out.size());
select_key_context(type, false).decrypt(in_out);
// update time of most recent packet received
@ -2488,21 +2651,6 @@ namespace openvpn {
// reason for invalidation if invalidated() above returns true
Error::Type invalidation_reason() const { return primary->invalidation_reason(); }
// Enable original OpenVPN behavior of switching to new SSL/TLS key
// context for control channel sends immediately after renegotiation
// reaches ACTIVE state. By default, we will transition to new
// key context over the "become_primary" duration in Config.
// The default behavior is known to be more reliable, but the
// original behavior may be enabled by calling this method.
void enable_fast_transition() { fast_transition = true; }
// A placeholder for enabling strict OpenVPN 2.x protocol
// compatibility.
void enable_strict_openvpn_2x()
{
enable_fast_transition();
}
// Do late initialization of data channel, for example
// on client after server push, or on server after client
// capabilities are known.
@ -2551,6 +2699,17 @@ namespace openvpn {
keepalive_parms_modified();
}
// Notify our component KeyContext when per-key Data Limits have been reached
void data_limit_notify(const int key_id,
const DataLimit::Mode cdl_mode,
const DataLimit::State cdl_status)
{
if (key_id == primary->key_id())
primary->data_limit_notify(cdl_mode, cdl_status);
else if (secondary && key_id == secondary->key_id())
secondary->data_limit_notify(cdl_mode, cdl_status);
}
// access the data channel settings
CryptoDCSettings& dc_settings()
{
@ -2651,8 +2810,7 @@ namespace openvpn {
{
if (KeyContext::validate(pkt.buffer(), *this, now_))
{
secondary.reset(new KeyContext(*this, false));
OPENVPN_LOG_PROTO_VERBOSE("New KeyContext SECONDARY id=" << secondary->key_id() << " remote-triggered");
new_secondary_key(false);
return true;
}
else
@ -2681,26 +2839,15 @@ namespace openvpn {
}
// Select a KeyContext (primary or secondary) for control channel sends.
// NOTE: possible incompatibility with existing OpenVPN protocol.
// Even after new key context goes active, we still wait for
// KEV_BECOME_PRIMARY event before we use it for app-level control-channel
// transmissions. Simulations have found this method to be more reliable.
// To revert to old OpenVPN behavior, call enable_fast_transition() method.
// KEV_BECOME_PRIMARY event (controlled by the become_primary duration
// in Config) before we use it for app-level control-channel
// transmissions. Simulations have found this method to be more reliable
// than the immediate rollover practiced by OpenVPN 2.x.
KeyContext& select_control_send_context()
{
if (!fast_transition || !secondary)
{
return *primary;
}
else
{
const Time p = primary->reached_active();
const Time s = secondary->reached_active();
if (p.defined() && s.defined() && s > p)
return *secondary;
else
return *primary;
}
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " CONTROL SEND");
return *primary;
}
// Possibly send a keepalive message, and check for expiration
@ -2746,6 +2893,17 @@ namespace openvpn {
return did_work;
}
// Create a new secondary key.
// initiator --
// false : remote renegotiation request
// true : local renegotiation request
void new_secondary_key(const bool initiator)
{
// Create the secondary
secondary.reset(new KeyContext(*this, initiator));
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " New KeyContext SECONDARY id=" << secondary->key_id() << (initiator ? " local-triggered" : " remote-triggered"));
}
// Promote a newly renegotiated KeyContext to primary status.
// This is usually triggered by become_primary variable (Time::Duration)
// in Config.
@ -2754,7 +2912,7 @@ namespace openvpn {
primary.swap(secondary);
primary->rekey(CryptoDCInstance::PROMOTE_SECONDARY_TO_PRIMARY);
secondary->prepare_expire();
OPENVPN_LOG_PROTO_VERBOSE("*** PROMOTE_SECONDARY_TO_PRIMARY pri=" << primary->key_id() << " sec=" << secondary->key_id());
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " PROMOTE_SECONDARY_TO_PRIMARY");
}
void process_primary_event()
@ -2766,11 +2924,12 @@ namespace openvpn {
switch (ev)
{
case KeyContext::KEV_ACTIVE:
OPENVPN_LOG_PROTO_VERBOSE("*** SESSION_ACTIVE");
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " SESSION_ACTIVE");
primary->rekey(CryptoDCInstance::ACTIVATE_PRIMARY);
active();
break;
case KeyContext::KEV_RENEGOTIATE:
case KeyContext::KEV_RENEGOTIATE_FORCE:
renegotiate();
break;
case KeyContext::KEV_EXPIRE:
@ -2782,7 +2941,7 @@ namespace openvpn {
disconnect(Error::PRIMARY_EXPIRE); // primary context expired and no secondary context available
}
break;
case KeyContext::KEV_NEGOTIATE_FAILED:
case KeyContext::KEV_NEGOTIATE:
stats->error(Error::HANDSHAKE_TIMEOUT);
disconnect(Error::HANDSHAKE_TIMEOUT); // primary negotiation failed
break;
@ -2790,6 +2949,7 @@ namespace openvpn {
break;
}
}
primary->set_next_event_if_unspecified();
}
void process_secondary_event()
@ -2812,16 +2972,40 @@ namespace openvpn {
secondary->rekey(CryptoDCInstance::DEACTIVATE_SECONDARY);
secondary.reset();
break;
case KeyContext::KEV_NEGOTIATE_FAILED:
case KeyContext::KEV_RENEGOTIATE_QUEUE:
primary->key_limit_reneg(KeyContext::KEV_RENEGOTIATE_FORCE, secondary->become_primary_time());
break;
case KeyContext::KEV_NEGOTIATE:
stats->error(Error::HANDSHAKE_TIMEOUT);
case KeyContext::KEV_PRIMARY_PENDING:
case KeyContext::KEV_RENEGOTIATE_FORCE:
renegotiate();
break;
default:
break;
}
}
if (secondary)
secondary->set_next_event_if_unspecified();
}
#ifdef OPENVPN_INSTRUMENTATION
std::string debug_prefix()
{
std::string ret = openvpn::to_string(now_->raw());
ret += is_server() ? " SERVER[" : " CLIENT[";
if (primary)
ret += openvpn::to_string(primary->key_id());
if (secondary)
{
ret += '/';
ret += openvpn::to_string(secondary->key_id());
}
ret += ']';
return ret;
}
#endif
// key_id starts at 0, increments to KEY_ID_MASK, then recycles back to 1.
// Therefore, if key_id is 0, it is the first key.
unsigned int next_key_id()
@ -2876,8 +3060,6 @@ namespace openvpn {
KeyContext::Ptr secondary;
bool dc_deferred;
bool fast_transition;
// END ProtoContext data members
};

View File

@ -91,11 +91,13 @@ namespace openvpn {
ProtoStackBase(SSLFactoryAPI& ssl_factory, // SSL factory object that can be used to generate new SSL sessions
TimePtr now_arg, // pointer to current time
const Time::Duration& tls_timeout_arg, // packet retransmit timeout
const Frame::Ptr& frame, // contains info on how to allocate and align buffers
const SessionStats::Ptr& stats_arg, // error statistics
const id_t span, // basically the window size for our reliability layer
const size_t max_ack_list) // maximum number of ACK messages to bundle in one packet
: ssl_(ssl_factory.ssl()),
: tls_timeout(tls_timeout_arg),
ssl_(ssl_factory.ssl()),
frame_(frame),
up_stack_reentry_level(0),
invalidated_(false),
@ -190,7 +192,7 @@ namespace openvpn {
if (m.ready_retransmit(*now))
{
parent().net_send(m.packet, NET_SEND_RETRANSMIT);
m.reset_retransmit(*now);
m.reset_retransmit(*now, tls_timeout);
}
}
update_retransmit();
@ -326,7 +328,7 @@ namespace openvpn {
// encapsulate SSL ciphertext packets
while (ssl_->read_ciphertext_ready() && rel_send.ready())
{
typename ReliableSend::Message& m = rel_send.send(*now);
typename ReliableSend::Message& m = rel_send.send(*now, tls_timeout);
m.packet = PACKET(ssl_->read_ciphertext());
// encapsulate packet
@ -350,7 +352,7 @@ namespace openvpn {
{
while (!raw_write_queue.empty() && rel_send.ready())
{
typename ReliableSend::Message& m = rel_send.send(*now);
typename ReliableSend::Message& m = rel_send.send(*now, tls_timeout);
m.packet = raw_write_queue.front();
raw_write_queue.pop_front();
@ -453,6 +455,7 @@ namespace openvpn {
}
private:
const Time::Duration tls_timeout;
typename SSLAPI::Ptr ssl_;
Frame::Ptr frame_;
int up_stack_reentry_level;

View File

@ -28,45 +28,62 @@
namespace openvpn {
inline void set_duration_parm(Time::Duration& dur,
const char *name,
const std::string& name,
const std::string& valstr,
const unsigned int min_value,
const bool x2)
const bool x2, // multiply result by 2
const bool ms) // values are in milliseconds rather than seconds
{
const unsigned int maxdur = 60*60*24*7; // maximum duration -- 7 days
const unsigned int maxdur = ms ? 1000*60*60*24 : 60*60*24*7; // maximum duration -- milliseconds: 1 day, seconds: 7 days
unsigned int value = 0;
const bool status = parse_number<unsigned int>(valstr, value);
if (!status)
OPENVPN_THROW(option_error, name << ": error parsing number of seconds");
OPENVPN_THROW(option_error, name << ": error parsing number of " << (ms ? "milliseconds" : "seconds"));
if (x2)
value *= 2;
if (value == 0 || value > maxdur)
value = maxdur;
if (value < min_value)
value = min_value;
dur = Time::Duration::seconds(value);
dur = ms ? Time::Duration::milliseconds(value) : Time::Duration::seconds(value);
}
inline const Option* load_duration_parm(Time::Duration& dur,
const char *name,
const std::string& name,
const OptionList& opt,
const unsigned int min_value,
const bool x2)
const bool x2,
const bool allow_ms)
{
const Option *o = opt.get_ptr(name);
if (o)
set_duration_parm(dur, name, o->get(1, 16), min_value, x2);
return o;
// look for milliseconds given as <name>-ms
if (allow_ms)
{
const Option *o = opt.get_ptr(name + "-ms");
if (o)
{
set_duration_parm(dur, name, o->get(1, 16), min_value, x2, true);
return o;
}
}
// look for seconds given as <name>
{
const Option *o = opt.get_ptr(name);
if (o)
set_duration_parm(dur, name, o->get(1, 16), allow_ms ? 1 : min_value, x2, false);
return o;
}
}
inline Time::Duration load_duration_default(const char *name,
inline Time::Duration load_duration_default(const std::string& name,
const OptionList& opt,
const Time::Duration& default_duration,
const unsigned int min_value,
const bool x2)
const bool x2,
const bool allow_ms)
{
Time::Duration ret(default_duration);
load_duration_parm(ret, name, opt, min_value, x2);
load_duration_parm(ret, name, opt, min_value, x2, allow_ms);
return ret;
}

View File

@ -39,6 +39,7 @@
#include <openvpn/server/servhalt.hpp>
#include <openvpn/server/peerstats.hpp>
#include <openvpn/server/peeraddr.hpp>
#include <openvpn/ssl/datalimit.hpp>
namespace openvpn {
@ -118,6 +119,12 @@ namespace openvpn {
// client float notification
virtual void float_notify(const PeerAddr::Ptr& addr) = 0;
// Data limit notification -- trigger a renegotiation
// when cdl_status == DataLimit::Red.
virtual void data_limit_notify(const int key_id,
const DataLimit::Mode cdl_mode,
const DataLimit::State cdl_status) = 0;
// push a halt or restart message to client
virtual void push_halt_restart_msg(const HaltRestart::Type type,
const std::string& reason,

View File

@ -5,6 +5,7 @@ GCC_EXTRA="$GCC_EXTRA -DOPENVPN_SHOW_SESSION_TOKEN"
[ "$EXIT" = "1" ] && GCC_EXTRA="$GCC_EXTRA -DTUN_NULL_EXIT"
[ "$GREMLIN" = "1" ] && GCC_EXTRA="$GCC_EXTRA -DOPENVPN_GREMLIN"
[ "$DEX" = "1" ] && GCC_EXTRA="$GCC_EXTRA -DOPENVPN_DISABLE_EXPLICIT_EXIT"
[ "$BS64" = "1" ] && GCC_EXTRA="$GCC_EXTRA -DOPENVPN_BS64_DATA_LIMIT=2500000"
if [ "$AGENT" = "1" ]; then
GCC_EXTRA="$GCC_EXTRA -DOPENVPN_COMMAND_AGENT"
export JSON=1

View File

@ -39,6 +39,51 @@
#define OPENVPN_DEBUG
#define OPENVPN_ENABLE_ASSERT
#define USE_TLS_AUTH
#define OPENVPN_INSTRUMENTATION
// Data limits for Blowfish and other 64-bit block-size ciphers
#ifndef BF
#define BF 0
#endif
#define OPENVPN_BS64_DATA_LIMIT 50000
#if BF == 1
#define PROTO_CIPHER "BF-CBC"
#define TLS_VER_MIN TLSVersion::UNDEF
#define HANDSHAKE_WINDOW 60
#define BECOME_PRIMARY_CLIENT 5
#define BECOME_PRIMARY_SERVER 5
#define TLS_TIMEOUT_CLIENT 1000
#define TLS_TIMEOUT_SERVER 1000
#define FEEDBACK 0
#elif BF == 2
#define PROTO_CIPHER "BF-CBC"
#define TLS_VER_MIN TLSVersion::UNDEF
#define HANDSHAKE_WINDOW 10
#define BECOME_PRIMARY_CLIENT 10
#define BECOME_PRIMARY_SERVER 10
#define TLS_TIMEOUT_CLIENT 2000
#define TLS_TIMEOUT_SERVER 1000
#define FEEDBACK 0
#elif BF == 3
#define PROTO_CIPHER "BF-CBC"
#define TLS_VER_MIN TLSVersion::UNDEF
#define HANDSHAKE_WINDOW 60
#define BECOME_PRIMARY_CLIENT 60
#define BECOME_PRIMARY_SERVER 10
#define TLS_TIMEOUT_CLIENT 2000
#define TLS_TIMEOUT_SERVER 1000
#define FEEDBACK 0
#elif BF != 0
#error unknown BF value
#endif
// TLS timeout
#ifndef TLS_TIMEOUT_CLIENT
#define TLS_TIMEOUT_CLIENT 2000
#endif
#ifndef TLS_TIMEOUT_SERVER
#define TLS_TIMEOUT_SERVER 2000
#endif
// NoisyWire
#ifndef NOERR
@ -52,6 +97,13 @@
#define RENEG 900
#endif
// feedback
#ifndef FEEDBACK
#define FEEDBACK 1
#else
#define FEEDBACK 0
#endif
// number of threads to use for test
#ifndef N_THREADS
#define N_THREADS 1
@ -87,17 +139,17 @@
// setup cipher
#ifndef PROTO_CIPHER
#ifdef PROTOv2
#define PROTO_CIPHER AES-256-GCM
#define PROTO_CIPHER "AES-256-GCM"
#define TLS_VER_MIN TLSVersion::V1_2
#else
#define PROTO_CIPHER AES-128-CBC
#define PROTO_CIPHER "AES-128-CBC"
#define TLS_VER_MIN TLSVersion::UNDEF
#endif
#endif
// setup digest
#ifndef PROTO_DIGEST
#define PROTO_DIGEST SHA1
#define PROTO_DIGEST "SHA1"
#endif
// setup compressor
@ -272,7 +324,7 @@ public:
#if defined(VERBOSE) || defined(DROUGHT_LIMIT)
{
const unsigned int r = drought.raw();
#ifdef VERBOSE
#if defined(VERBOSE)
std::cout << "*** Drought " << name << " has reached " << r << std::endl;
#endif
#ifdef DROUGHT_LIMIT
@ -309,15 +361,14 @@ public:
typedef Base::PacketType PacketType;
OPENVPN_EXCEPTION(session_invalidated);
TestProto(const Base::Config::Ptr& config,
const SessionStats::Ptr& stats)
: Base(config, stats),
control_drought("control", config->now),
data_drought("data", config->now),
frame(config->frame),
app_bytes_(0),
net_bytes_(0),
data_bytes_(0)
frame(config->frame)
{
// zero progress value
std::memset(progress_, 0, 11);
@ -332,7 +383,6 @@ public:
void initial_app_send(const char *msg)
{
Base::start();
const size_t msglen = std::strlen(msg) + 1;
BufferAllocated app_buf((unsigned char *)msg, msglen, 0);
copy_progress(app_buf);
@ -340,6 +390,28 @@ public:
flush(true);
}
void app_send_templ_init(const char *msg)
{
Base::start();
const size_t msglen = std::strlen(msg) + 1;
templ.reset(new BufferAllocated((unsigned char *)msg, msglen, 0));
flush(true);
}
void app_send_templ()
{
#if !FEEDBACK
if (bool(iteration++ & 1) == is_server())
{
modmsg(templ);
BufferAllocated app_buf(*templ);
control_send(std::move(app_buf));
flush(true);
++n_control_send_;
}
#endif
}
bool do_housekeeping()
{
if (now() >= Base::next_housekeeping())
@ -390,6 +462,8 @@ public:
size_t net_bytes() const { return net_bytes_; }
size_t app_bytes() const { return app_bytes_; }
size_t data_bytes() const { return data_bytes_; }
size_t n_control_recv() const { return n_control_recv_; }
size_t n_control_send() const { return n_control_send_; }
const char *progress() const { return progress_; }
@ -399,6 +473,12 @@ public:
control_drought.event();
}
void check_invalidated()
{
if (Base::invalidated())
throw session_invalidated(Error::name(Base::invalidation_reason()));
}
std::deque<BufferPtr> net_out;
DroughtMeasure control_drought;
@ -425,9 +505,12 @@ private:
std::cout << now().raw() << " " << mode().str() << " " << show << std::endl;
}
#endif
#if FEEDBACK
modmsg(work);
control_send(std::move(work));
#endif
control_drought.event();
++n_control_recv_;
}
void copy_progress(Buffer& buf)
@ -464,9 +547,13 @@ private:
}
Frame::Ptr frame;
size_t app_bytes_;
size_t net_bytes_;
size_t data_bytes_;
size_t app_bytes_ = 0;
size_t net_bytes_ = 0;
size_t data_bytes_ = 0;
size_t n_control_send_ = 0;
size_t n_control_recv_ = 0;
BufferPtr templ;
size_t iteration = 0;
char progress_[11];
};
@ -522,8 +609,6 @@ private:
class NoisyWire
{
public:
OPENVPN_SIMPLE_EXCEPTION(session_invalidated);
NoisyWire(const std::string title_arg,
TimePtr now_arg,
RandomAPI& rand_arg,
@ -543,8 +628,8 @@ public:
void xfer(T1& a, T2& b)
{
// check for errors
if (a.invalidated() || b.invalidated())
throw session_invalidated();
a.check_invalidated();
b.check_invalidated();
// need to retransmit?
if (a.do_housekeeping())
@ -554,10 +639,13 @@ public:
#endif
}
// queue a control channel packet
a.app_send_templ();
// queue a data channel packet
if (a.data_channel_ready())
{
BufferPtr bp = a.data_encrypt_string("Waiting for godot...");
BufferPtr bp = a.data_encrypt_string("Waiting for godot A... Waiting for godot B... Waiting for godot C... Waiting for godot D... Waiting for godot E... Waiting for godot F... Waiting for godot G... Waiting for godot H... Waiting for godot I... Waiting for godot J...");
wire.push_back(bp);
}
@ -594,7 +682,7 @@ public:
#ifdef VERBOSE
if (bp->size())
{
const std::string show((char *)bp->data(), bp->size());
const std::string show((char *)bp->data(), std::min(bp->size(), size_t(40)));
std::cout << now->raw() << " " << title << " DATA CHANNEL DECRYPT: " << show << std::endl;
}
#endif
@ -606,6 +694,13 @@ public:
#endif
}
}
else
{
#ifdef VERBOSE
std::cout << now->raw() << " " << title << " KEY_STATE_ERROR" << std::endl;
#endif
b.stat().error(Error::KEY_STATE_ERROR);
}
}
b.flush(true);
}
@ -786,12 +881,12 @@ int test(const int thread_num)
cp->remote_peer_id = 100;
#endif
cp->comp_ctx = CompressContext(COMP_METH, false);
cp->dc.set_cipher(CryptoAlgs::lookup(STRINGIZE(PROTO_CIPHER)));
cp->dc.set_digest(CryptoAlgs::lookup(STRINGIZE(PROTO_DIGEST)));
cp->dc.set_cipher(CryptoAlgs::lookup(PROTO_CIPHER));
cp->dc.set_digest(CryptoAlgs::lookup(PROTO_DIGEST));
#ifdef USE_TLS_AUTH
cp->tls_auth_factory.reset(new CryptoOvpnHMACFactory<ClientCryptoAPI>());
cp->tls_auth_key.parse(tls_auth_key);
cp->set_tls_auth_digest(CryptoAlgs::lookup(STRINGIZE(PROTO_DIGEST)));
cp->set_tls_auth_digest(CryptoAlgs::lookup(PROTO_DIGEST));
cp->key_direction = 0;
#endif
cp->reliable_window = 4;
@ -804,7 +899,12 @@ int test(const int thread_num)
#else
cp->handshake_window = Time::Duration::seconds(18); // will cause a small number of handshake failures
#endif
cp->become_primary = Time::Duration::seconds(30);
#ifdef BECOME_PRIMARY_CLIENT
cp->become_primary = Time::Duration::seconds(BECOME_PRIMARY_CLIENT);
#else
cp->become_primary = cp->handshake_window;
#endif
cp->tls_timeout = Time::Duration::milliseconds(TLS_TIMEOUT_CLIENT);
#if defined(CLIENT_NO_RENEG)
cp->renegotiate = Time::Duration::infinite();
#else
@ -851,12 +951,12 @@ int test(const int thread_num)
sp->remote_peer_id = 101;
#endif
sp->comp_ctx = CompressContext(COMP_METH, false);
sp->dc.set_cipher(CryptoAlgs::lookup(STRINGIZE(PROTO_CIPHER)));
sp->dc.set_digest(CryptoAlgs::lookup(STRINGIZE(PROTO_DIGEST)));
sp->dc.set_cipher(CryptoAlgs::lookup(PROTO_CIPHER));
sp->dc.set_digest(CryptoAlgs::lookup(PROTO_DIGEST));
#ifdef USE_TLS_AUTH
sp->tls_auth_factory.reset(new CryptoOvpnHMACFactory<ServerCryptoAPI>());
sp->tls_auth_key.parse(tls_auth_key);
sp->set_tls_auth_digest(CryptoAlgs::lookup(STRINGIZE(PROTO_DIGEST)));
sp->set_tls_auth_digest(CryptoAlgs::lookup(PROTO_DIGEST));
sp->key_direction = 1;
#endif
sp->reliable_window = 4;
@ -869,7 +969,12 @@ int test(const int thread_num)
#else
sp->handshake_window = Time::Duration::seconds(17) + Time::Duration::binary_ms(512);
#endif
sp->become_primary = Time::Duration::seconds(30);
#ifdef BECOME_PRIMARY_SERVER
sp->become_primary = Time::Duration::seconds(BECOME_PRIMARY_SERVER);
#else
sp->become_primary = sp->handshake_window;
#endif
sp->tls_timeout = Time::Duration::milliseconds(TLS_TIMEOUT_SERVER);
#if defined(SERVER_NO_RENEG)
sp->renegotiate = Time::Duration::infinite();
#else
@ -907,9 +1012,14 @@ int test(const int thread_num)
int j = -1;
try {
#if FEEDBACK
// start feedback loop
cli_proto.initial_app_send(message);
serv_proto.start();
#else
cli_proto.app_send_templ_init(message);
serv_proto.app_send_templ_init(message);
#endif
// message loop
for (j = 0; j < ITER; ++j)
@ -937,6 +1047,9 @@ int test(const int thread_num)
<< " net_bytes=" << nb
<< " data_bytes=" << db
<< " prog=" << cli_proto.progress() << '/' << serv_proto.progress()
#if !FEEDBACK
<< " CTRL=" << cli_proto.n_control_recv() << '/' << cli_proto.n_control_send() << '/' << serv_proto.n_control_recv() << '/' << serv_proto.n_control_send()
#endif
<< " D=" << cli_proto.control_drought().raw() << '/' << cli_proto.data_drought().raw() << '/' << serv_proto.control_drought().raw() << '/' << serv_proto.data_drought().raw()
<< " N=" << cli_proto.negotiations() << '/' << serv_proto.negotiations()
<< " SH=" << cli_proto.slowest_handshake().raw() << '/' << serv_proto.slowest_handshake().raw()
@ -948,6 +1061,10 @@ int test(const int thread_num)
cli_stats->show_error_counts();
std::cerr << "-------- SERVER STATS --------" << std::endl;
serv_stats->show_error_counts();
#endif
#ifdef OPENVPN_MAX_DATALIMIT_BYTES
std::cerr << "------------------------------" << std::endl;
std::cerr << "MAX_DATALIMIT_BYTES=" << DataLimit::max_bytes() << std::endl;
#endif
}
catch (const std::exception& e)