Skip to content

Commit

Permalink
ovpn3 core : Added automatic data limits for Blowfish,
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jamesyonan committed Sep 1, 2016
1 parent 26d0169 commit 662bf78
Show file tree
Hide file tree
Showing 13 changed files with 776 additions and 202 deletions.
4 changes: 3 additions & 1 deletion openvpn/client/cliproto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion openvpn/common/version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
45 changes: 45 additions & 0 deletions openvpn/crypto/bs64_data_limit.hpp
Original file line number Diff line number Diff line change
@@ -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
10 changes: 8 additions & 2 deletions openvpn/error/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
12 changes: 4 additions & 8 deletions openvpn/reliable/relsend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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:
Expand Down Expand Up @@ -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;
}

Expand Down
10 changes: 10 additions & 0 deletions openvpn/server/servproto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
188 changes: 188 additions & 0 deletions openvpn/ssl/datalimit.hpp
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 662bf78

Please sign in to comment.