Files

3845 lines
109 KiB
C++

// 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-2017 OpenVPN 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/>.
// ProtoContext, the fundamental OpenVPN protocol implementation.
// It can be used by OpenVPN clients, servers, or unit tests.
#ifndef OPENVPN_SSL_PROTO_H
#define OPENVPN_SSL_PROTO_H
#include <cstring>
#include <string>
#include <sstream>
#include <algorithm> // for std::min
#include <cstdint> // for std::uint32_t, etc.
#include <memory>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/version.hpp>
#include <openvpn/common/platform_name.hpp>
#include <openvpn/common/rc.hpp>
#include <openvpn/common/hexstr.hpp>
#include <openvpn/common/options.hpp>
#include <openvpn/common/mode.hpp>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/common/number.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/to_string.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/buffer/safestr.hpp>
#include <openvpn/buffer/bufcomposed.hpp>
#include <openvpn/ip/ip4.hpp>
#include <openvpn/ip/ip6.hpp>
#include <openvpn/ip/udp.hpp>
#include <openvpn/ip/tcp.hpp>
#include <openvpn/time/time.hpp>
#include <openvpn/time/durhelper.hpp>
#include <openvpn/frame/frame.hpp>
#include <openvpn/random/randapi.hpp>
#include <openvpn/crypto/cryptoalgs.hpp>
#include <openvpn/crypto/cryptodc.hpp>
#include <openvpn/crypto/cipher.hpp>
#include <openvpn/crypto/ovpnhmac.hpp>
#include <openvpn/crypto/tls_crypt.hpp>
#include <openvpn/crypto/tls_crypt_v2.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/ssl/mssparms.hpp>
#include <openvpn/transport/mssfix.hpp>
#include <openvpn/transport/protocol.hpp>
#include <openvpn/tun/layer.hpp>
#include <openvpn/tun/tunmtu.hpp>
#include <openvpn/compress/compress.hpp>
#include <openvpn/ssl/proto_context_options.hpp>
#include <openvpn/ssl/peerinfo.hpp>
#include <openvpn/ssl/ssllog.hpp>
#if OPENVPN_DEBUG_PROTO >= 1
#define OPENVPN_LOG_PROTO(x) OPENVPN_LOG(x)
#define OPENVPN_LOG_STRING_PROTO(x) OPENVPN_LOG_STRING(x)
#else
#define OPENVPN_LOG_PROTO(x)
#define OPENVPN_LOG_STRING_PROTO(x)
#endif
#if OPENVPN_DEBUG_PROTO >= 2
#define OPENVPN_LOG_PROTO_VERBOSE(x) OPENVPN_LOG(x)
#else
#define OPENVPN_LOG_PROTO_VERBOSE(x)
#endif
/*
ProtoContext -- OpenVPN protocol implementation
Protocol negotiation states:
Client:
1. send client reset to server
2. wait for server reset from server AND ack from 1 (C_WAIT_RESET, C_WAIT_RESET_ACK)
3. start SSL handshake
4. send auth message to server
5. wait for server auth message AND ack from 4 (C_WAIT_AUTH, C_WAIT_AUTH_ACK)
6. go active (ACTIVE)
Server:
1. wait for client reset (S_WAIT_RESET)
2. send server reset to client
3. wait for ACK from 2 (S_WAIT_RESET_ACK)
4. start SSL handshake
5. wait for auth message from client (S_WAIT_AUTH)
6. send auth message to client
7. wait for ACK from 6 (S_WAIT_AUTH_ACK)
8. go active (ACTIVE)
*/
namespace openvpn {
// utility namespace for ProtoContext
namespace proto_context_private {
namespace {
const unsigned char auth_prefix[] = { 0, 0, 0, 0, 2 }; // CONST GLOBAL
const unsigned char keepalive_message[] = { // CONST GLOBAL
0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
};
enum {
KEEPALIVE_FIRST_BYTE = 0x2a // first byte of keepalive message
};
inline bool is_keepalive(const Buffer& buf)
{
return buf.size() >= sizeof(keepalive_message)
&& buf[0] == KEEPALIVE_FIRST_BYTE
&& !std::memcmp(keepalive_message, buf.c_data(), sizeof(keepalive_message));
}
const unsigned char explicit_exit_notify_message[] = { // CONST GLOBAL
0x28, 0x7f, 0x34, 0x6b, 0xd4, 0xef, 0x7a, 0x81,
0x2d, 0x56, 0xb8, 0xd3, 0xaf, 0xc5, 0x45, 0x9c,
6 // OCC_EXIT
};
enum {
EXPLICIT_EXIT_NOTIFY_FIRST_BYTE = 0x28 // first byte of exit message
};
}
}
class ProtoContext
{
protected:
static constexpr size_t APP_MSG_MAX = 65536;
enum {
// packet opcode (high 5 bits) and key-id (low 3 bits) are combined in one byte
KEY_ID_MASK = 0x07,
OPCODE_SHIFT = 3,
// packet opcodes -- the V1 is intended to allow protocol changes in the future
//CONTROL_HARD_RESET_CLIENT_V1 = 1, // (obsolete) initial key from client, forget previous state
//CONTROL_HARD_RESET_SERVER_V1 = 2, // (obsolete) initial key from server, forget previous state
CONTROL_SOFT_RESET_V1 = 3, // new key, graceful transition from old to new key
CONTROL_V1 = 4, // control channel packet (usually TLS ciphertext)
ACK_V1 = 5, // acknowledgement for packets received
DATA_V1 = 6, // data channel packet with 1-byte header
DATA_V2 = 9, // data channel packet with 4-byte header
// indicates key_method >= 2
CONTROL_HARD_RESET_CLIENT_V2 = 7, // initial key from client, forget previous state
CONTROL_HARD_RESET_CLIENT_V3 = 10, // initial key from client, forget previous state
CONTROL_HARD_RESET_SERVER_V2 = 8, // initial key from server, forget previous state
// define the range of legal opcodes
FIRST_OPCODE = 3,
LAST_OPCODE = 9,
INVALID_OPCODE = 0,
// DATA_V2 constants
OP_SIZE_V2 = 4, // size of initial packet opcode
OP_PEER_ID_UNDEF = 0x00FFFFFF, // indicates that Peer ID is undefined
// states
// C_x : client states
// S_x : server states
// ACK states -- must be first before other states
STATE_UNDEF=-1,
C_WAIT_RESET_ACK=0,
C_WAIT_AUTH_ACK=1,
S_WAIT_RESET_ACK=2,
S_WAIT_AUTH_ACK=3,
LAST_ACK_STATE=3, // all ACK states must be <= this value
// key negotiation states (client)
C_INITIAL=4,
C_WAIT_RESET=5, // must be C_INITIAL+1
C_WAIT_AUTH=6,
// key negotiation states (server)
S_INITIAL=7,
S_WAIT_RESET=8, // must be S_INITIAL+1
S_WAIT_AUTH=9,
// key negotiation states (client and server)
ACTIVE=10,
};
static unsigned int opcode_extract(const unsigned int op)
{
return op >> OPCODE_SHIFT;
}
static unsigned int key_id_extract(const unsigned int op)
{
return op & KEY_ID_MASK;
}
static size_t op_head_size(const unsigned int op)
{
return opcode_extract(op) == DATA_V2 ? OP_SIZE_V2 : 1;
}
static unsigned int op_compose(const unsigned int opcode, const unsigned int key_id)
{
return (opcode << OPCODE_SHIFT) | key_id;
}
static unsigned int op32_compose(const unsigned int opcode,
const unsigned int key_id,
const int op_peer_id)
{
return (op_compose(opcode, key_id) << 24) | (op_peer_id & 0x00FFFFFF);
}
public:
OPENVPN_EXCEPTION(proto_error);
OPENVPN_EXCEPTION(process_server_push_error);
OPENVPN_EXCEPTION_INHERIT(option_error, proto_option_error);
// configuration data passed to ProtoContext constructor
class Config : public RCCopyable<thread_unsafe_refcount>
{
public:
typedef RCPtr<Config> Ptr;
// master SSL context factory
SSLFactoryAPI::Ptr ssl_factory;
// data channel
CryptoDCSettings dc;
// TLSPRF factory
TLSPRFFactory::Ptr tlsprf_factory;
// master Frame object
Frame::Ptr frame;
// (non-smart) pointer to current time
TimePtr now;
// Random number generator.
// Use-cases demand highest cryptographic strength
// such as key generation.
RandomAPI::Ptr rng;
// Pseudo-random number generator.
// Use-cases demand cryptographic strength
// combined with high performance. Used for
// IV and ProtoSessionID generation.
RandomAPI::Ptr prng;
// If relay mode is enabled, connect to a special OpenVPN
// server that acts as a relay/proxy to a second server.
bool relay_mode = false;
// defer data channel initialization until after client options pull
bool dc_deferred = false;
// transmit username/password creds to server (client-only)
bool xmit_creds = true;
// Transport protocol, i.e. UDPv4, etc.
Protocol protocol; // set with set_protocol()
// OSI layer
Layer layer;
// compressor
CompressContext comp_ctx;
// tls_auth/crypt parms
OpenVPNStaticKey tls_key; // leave this undefined to disable tls_auth/crypt
bool tls_crypt_v2 = false; // needed to distinguish between tls-crypt and tls-crypt-v2 server mode
BufferAllocated wkc; // leave this undefined to disable tls-crypt-v2 on client
OvpnHMACFactory::Ptr tls_auth_factory;
OvpnHMACContext::Ptr tls_auth_context;
int key_direction = -1; // 0, 1, or -1 for bidirectional
TLSCryptFactory::Ptr tls_crypt_factory;
TLSCryptContext::Ptr tls_crypt_context;
TLSCryptMetadataFactory::Ptr tls_crypt_metadata_factory;
// reliability layer parms
reliable::id_t reliable_window = 0;
size_t max_ack_list = 0;
// packet_id parms for both data and control channels
int pid_mode = 0; // PacketIDReceive::UDP_MODE or PacketIDReceive::TCP_MODE
// timeout parameters, relative to construction of KeyContext object
Time::Duration handshake_window; // SSL/TLS negotiation must complete by this time
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;
Time::Duration keepalive_timeout;
// extra peer info key/value pairs generated by client app
PeerInfo::Set::Ptr extra_peer_info;
// op header
bool enable_op32 = false;
int remote_peer_id = -1; // -1 to disable
int local_peer_id = -1; // -1 to disable
// MTU
unsigned int tun_mtu = 1500;
MSSParms mss_parms;
unsigned int mss_inter = 0;
// Debugging
int debug_level = 1;
// Compatibility
bool force_aes_cbc_ciphersuites = false;
// For compatibility with openvpn2 we send initial options on rekeying,
// instead of possible modifications caused by NCP
std::string initial_options;
void load(const OptionList& opt, const ProtoContextOptions& pco,
const int default_key_direction, const bool server)
{
// first set defaults
reliable_window = 4;
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);
protocol = Protocol();
pid_mode = PacketIDReceive::UDP_MODE;
key_direction = default_key_direction;
// layer
{
const Option* dev = opt.get_ptr("dev-type");
if (!dev)
dev = opt.get_ptr("dev");
if (!dev)
throw proto_option_error("missing dev-type or dev option");
const std::string& dev_type = dev->get(1, 64);
if (string::starts_with(dev_type, "tun"))
layer = Layer(Layer::OSI_LAYER_3);
else if (string::starts_with(dev_type, "tap"))
throw proto_option_error("TAP mode is not supported");
else
throw proto_option_error("bad dev-type");
}
// cipher/digest/tls-auth/tls-crypt
{
CryptoAlgs::Type cipher = CryptoAlgs::NONE;
CryptoAlgs::Type digest = CryptoAlgs::NONE;
// data channel cipher
{
const Option *o = opt.get_ptr("cipher");
if (o)
{
const std::string& cipher_name = o->get(1, 128);
if (cipher_name != "none")
cipher = CryptoAlgs::lookup(cipher_name);
}
else
cipher = CryptoAlgs::lookup("BF-CBC");
}
// data channel HMAC
{
const Option *o = opt.get_ptr("auth");
if (o)
{
const std::string& auth_name = o->get(1, 128);
if (auth_name != "none")
digest = CryptoAlgs::lookup(auth_name);
}
else
digest = CryptoAlgs::lookup("SHA1");
}
dc.set_cipher(cipher);
dc.set_digest(digest);
// tls-auth
{
const Option *o = opt.get_ptr(relay_prefix("tls-auth"));
if (o)
{
if (tls_crypt_context)
throw proto_option_error("tls-auth and tls-crypt are mutually exclusive");
tls_key.parse(o->get(1, 0));
const Option *tad = opt.get_ptr(relay_prefix("tls-auth-digest"));
if (tad)
digest = CryptoAlgs::lookup(tad->get(1, 128));
if (digest != CryptoAlgs::NONE)
set_tls_auth_digest(digest);
}
}
// tls-crypt
{
const Option *o = opt.get_ptr(relay_prefix("tls-crypt"));
if (o)
{
if (tls_auth_context)
throw proto_option_error("tls-auth and tls-crypt are mutually exclusive");
if (tls_crypt_context)
throw proto_option_error("tls-crypt and tls-crypt-v2 are mutually exclusive");
tls_key.parse(o->get(1, 0));
digest = CryptoAlgs::lookup("SHA256");
cipher = CryptoAlgs::lookup("AES-256-CTR");
if ((digest == CryptoAlgs::NONE) || (cipher == CryptoAlgs::NONE))
throw proto_option_error("missing support for tls-crypt algorithms");
set_tls_crypt_algs(digest, cipher);
}
}
// tls-crypt-v2
{
const Option *o = opt.get_ptr(relay_prefix("tls-crypt-v2"));
if (o)
{
if (tls_auth_context)
throw proto_option_error("tls-auth and tls-crypt-v2 are mutually exclusive");
if (tls_crypt_context)
throw proto_option_error("tls-crypt and tls-crypt-v2 are mutually exclusive");
digest = CryptoAlgs::lookup("SHA256");
cipher = CryptoAlgs::lookup("AES-256-CTR");
if ((digest == CryptoAlgs::NONE) || (cipher == CryptoAlgs::NONE))
throw proto_option_error("missing support for tls-crypt-v2 algorithms");
// initialize tls_crypt_context
set_tls_crypt_algs(digest, cipher);
std::string keyfile = o->get(1, 0);
if (opt.exists("client"))
{
// in client mode expect the key to be a PEM encoded tls-crypt-v2 client key (key + WKc)
TLSCryptV2ClientKey tls_crypt_v2_key(tls_crypt_context);
tls_crypt_v2_key.parse(keyfile);
tls_crypt_v2_key.extract_key(tls_key);
tls_crypt_v2_key.extract_wkc(wkc);
}
else
{
// in server mode this is a PEM encoded tls-crypt-v2 server key
TLSCryptV2ServerKey tls_crypt_v2_key;
tls_crypt_v2_key.parse(keyfile);
tls_crypt_v2_key.extract_key(tls_key);
}
tls_crypt_v2 = true;
}
}
}
// key-direction
{
if (key_direction >= -1 && key_direction <= 1)
{
const Option *o = opt.get_ptr(relay_prefix("key-direction"));
if (o)
{
const std::string& dir = o->get(1, 16);
if (dir == "0")
key_direction = 0;
else if (dir == "1")
key_direction = 1;
else if (dir == "bidirectional" || dir == "bi")
key_direction = -1;
else
throw proto_option_error("bad key-direction parameter");
}
}
else
throw proto_option_error("bad key-direction default");
}
// compression
{
const Option *o = opt.get_ptr("compress");
if (o)
{
if (o->size() >= 2)
{
const std::string meth_name = o->get(1, 128);
CompressContext::Type meth = CompressContext::parse_method(meth_name);
if (meth == CompressContext::NONE)
OPENVPN_THROW(proto_option_error, "Unknown compressor: '" << meth_name << '\'');
comp_ctx = CompressContext(pco.is_comp() ? meth : CompressContext::stub(meth), pco.is_comp_asym());
}
else
comp_ctx = CompressContext(pco.is_comp() ? CompressContext::ANY : CompressContext::COMP_STUB, pco.is_comp_asym());
}
else
{
o = opt.get_ptr("comp-lzo");
if (o)
{
if (o->size() == 2 && o->ref(1) == "no")
{
// On the client, by using ANY instead of ANY_LZO, we are telling the server
// that it's okay to use any of our supported compression methods.
comp_ctx = CompressContext(pco.is_comp() ? CompressContext::ANY : CompressContext::LZO_STUB, pco.is_comp_asym());
}
else
{
comp_ctx = CompressContext(pco.is_comp() ? CompressContext::LZO : CompressContext::LZO_STUB, pco.is_comp_asym());
}
}
}
}
// tun-mtu
tun_mtu = parse_tun_mtu(opt, tun_mtu);
// mssfix
mss_parms.parse(opt, true);
// 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)
{
// data channel
{
// cipher
std::string new_cipher;
try {
const Option *o = opt.get_ptr("cipher");
if (o)
{
new_cipher = o->get(1, 128);
if (new_cipher != "none")
dc.set_cipher(CryptoAlgs::lookup(new_cipher));
}
}
catch (const std::exception& e)
{
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed cipher '" << new_cipher << "': " << e.what());
}
// digest
std::string new_digest;
try {
const Option *o = opt.get_ptr("auth");
if (o)
{
new_digest = o->get(1, 128);
if (new_digest != "none")
dc.set_digest(CryptoAlgs::lookup(new_digest));
}
}
catch (const std::exception& e)
{
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed digest '" << new_digest << "': " << e.what());
}
}
// compression
std::string new_comp;
try {
const Option *o;
o = opt.get_ptr("compress");
if (o)
{
new_comp = o->get(1, 128);
CompressContext::Type meth = CompressContext::parse_method(new_comp);
if (meth != CompressContext::NONE)
{
// if compression is not availabe, CompressContext ctor throws an exception
if (pco.is_comp())
comp_ctx = CompressContext(meth, pco.is_comp_asym());
else
{
// server pushes compression but client has compression disabled
// degrade to asymmetric compression (downlink only)
comp_ctx = CompressContext(meth, true);
if (!comp_ctx.is_any_stub(meth))
{
OPENVPN_LOG("Server has pushed compressor "
<< comp_ctx.str()
<< ", but client has disabled compression, switching to asymmetric");
}
}
}
}
else
{
o = opt.get_ptr("comp-lzo");
if (o)
{
if (o->size() == 2 && o->ref(1) == "no")
{
comp_ctx = CompressContext(CompressContext::LZO_STUB, false);
}
else
{
comp_ctx = CompressContext(pco.is_comp() ? CompressContext::LZO : CompressContext::LZO_STUB, pco.is_comp_asym());
}
}
}
}
catch (const std::exception& e)
{
OPENVPN_THROW(process_server_push_error, "Problem accepting server-pushed compressor '" << new_comp << "': " << e.what());
}
// peer ID
try {
const Option *o = opt.get_ptr("peer-id");
if (o)
{
bool status = parse_number_validate<int>(o->get(1, 16),
16,
-1,
0xFFFFFE,
&remote_peer_id);
if (!status)
throw Exception("parse/range issue");
enable_op32 = true;
}
}
catch (const std::exception& e)
{
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());
}
std::string show_options() const
{
std::ostringstream os;
os << "PROTOCOL OPTIONS:" << std::endl;
os << " cipher: " << CryptoAlgs::name(dc.cipher()) << std::endl;
os << " digest: " << CryptoAlgs::name(dc.digest()) << std::endl;
os << " compress: " << comp_ctx.str() << std::endl;
os << " peer ID: " << remote_peer_id << std::endl;
return os.str();
}
void set_pid_mode(const bool tcp_linear)
{
if (protocol.is_udp() || !tcp_linear)
pid_mode = PacketIDReceive::UDP_MODE;
else if (protocol.is_tcp())
pid_mode = PacketIDReceive::TCP_MODE;
else
throw proto_option_error("transport protocol undefined");
}
void set_protocol(const Protocol& p)
{
// adjust options for new transport protocol
protocol = p;
set_pid_mode(false);
}
void set_tls_auth_digest(const CryptoAlgs::Type digest)
{
tls_auth_context = tls_auth_factory->new_obj(digest);
}
void set_tls_crypt_algs(const CryptoAlgs::Type digest,
const CryptoAlgs::Type cipher)
{
tls_crypt_context = tls_crypt_factory->new_obj(digest, cipher);
}
void set_xmit_creds(const bool xmit_creds_arg)
{
xmit_creds = xmit_creds_arg;
}
bool tls_auth_enabled() const
{
return tls_key.defined() && tls_auth_context;
}
bool tls_crypt_enabled() const
{
return tls_key.defined() && tls_crypt_context;
}
bool tls_crypt_v2_enabled() const
{
return tls_crypt_enabled() && tls_crypt_v2;
}
// generate a string summarizing options that will be
// transmitted to peer for options consistency check
std::string options_string()
{
if (!initial_options.empty())
return initial_options;
std::ostringstream out;
const bool server = ssl_factory->mode().is_server();
const unsigned int l2extra = (layer() == Layer::OSI_LAYER_2 ? 32 : 0);
out << "V4";
out << ",dev-type " << layer.dev_type();
out << ",link-mtu " << tun_mtu + link_mtu_adjust() + l2extra;
out << ",tun-mtu " << tun_mtu + l2extra;
out << ",proto " << protocol.str_client(true);
{
const char *compstr = comp_ctx.options_string();
if (compstr)
out << ',' << compstr;
}
if (tls_auth_context && (key_direction >= 0))
out << ",keydir " << key_direction;
out << ",cipher " << CryptoAlgs::name(dc.cipher(), "[null-cipher]");
out << ",auth " << CryptoAlgs::name(dc.digest(), "[null-digest]");
out << ",keysize " << (CryptoAlgs::key_length(dc.cipher()) * 8);
if (tls_auth_context)
out << ",tls-auth";
// sending tls-crypt does not make sense. If we got to this point it
// means that tls-crypt was already there and it worked fine.
// tls-auth has to be kept for backward compatibility as it is there
// since a bit.
out << ",key-method 2";
if (server)
out << ",tls-server";
else
out << ",tls-client";
initial_options = out.str();
return initial_options;
}
// generate a string summarizing information about the client
// including capabilities
std::string peer_info_string() const
{
std::ostringstream out;
const char *compstr = nullptr;
out << "IV_VER=" << OPENVPN_VERSION << '\n';
out << "IV_PLAT=" << platform_name() << '\n';
if (!force_aes_cbc_ciphersuites)
{
out << "IV_NCP=2\n"; // negotiable crypto parameters V2
out << "IV_TCPNL=1\n"; // supports TCP non-linear packet ID
out << "IV_PROTO=2\n"; // supports op32 and P_DATA_V2
compstr = comp_ctx.peer_info_string();
}
else
compstr = comp_ctx.peer_info_string_v1();
if (compstr)
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)
if (relay_mode)
out << "IV_RELAY=1\n";
const std::string ret = out.str();
OPENVPN_LOG_PROTO("Peer Info:" << std::endl << ret);
return ret;
}
// Used to generate link_mtu option sent to peer.
// Not const because dc.context() caches the DC context.
unsigned int link_mtu_adjust()
{
const size_t adj = protocol.extra_transport_bytes() + // extra 2 bytes for TCP-streamed packet length
(enable_op32 ? 4 : 1) + // leading op
comp_ctx.extra_payload_bytes() + // compression header
PacketID::size(PacketID::SHORT_FORM) + // sequence number
dc.context().encap_overhead(); // data channel crypto layer overhead
return (unsigned int)adj;
}
private:
enum LoadCommonType {
LOAD_COMMON_SERVER,
LOAD_COMMON_CLIENT,
LOAD_COMMON_CLIENT_PUSHED,
};
// load parameters that can be present in both config file or pushed options
void load_common(const OptionList& opt, const ProtoContextOptions& pco,
const LoadCommonType type)
{
// duration parms
load_duration_parm(renegotiate, "reneg-sec", opt, 10, false, false);
expire = renegotiate;
load_duration_parm(expire, "tran-window", opt, 10, false, false);
expire += renegotiate;
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
// keepalive, ping, ping-restart
{
const Option *o = opt.get_ptr("keepalive");
if (o)
{
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, false);
load_duration_parm(keepalive_timeout, "ping-restart", opt, 1, false, false);
}
}
}
std::string relay_prefix(const char *optname) const
{
std::string ret;
if (relay_mode)
ret = "relay-";
ret += optname;
return ret;
}
};
// Used to describe an incoming network packet
class PacketType
{
friend class ProtoContext;
enum {
DEFINED=1<<0, // packet is valid (otherwise invalid)
CONTROL=1<<1, // packet for control channel (otherwise for data channel)
SECONDARY=1<<2, // packet is associated with secondary KeyContext (otherwise primary)
SOFT_RESET=1<<3, // packet is a CONTROL_SOFT_RESET_V1 msg indicating a request for SSL/TLS renegotiate
};
public:
bool is_defined() const { return flags & DEFINED; }
bool is_control() const { return (flags & (CONTROL|DEFINED)) == (CONTROL|DEFINED); }
bool is_data() const { return (flags & (CONTROL|DEFINED)) == DEFINED; }
bool is_soft_reset() const { return (flags & (CONTROL|DEFINED|SECONDARY|SOFT_RESET))
== (CONTROL|DEFINED|SECONDARY|SOFT_RESET); }
int peer_id() const { return peer_id_; }
private:
PacketType(const Buffer& buf, class ProtoContext& proto)
: flags(0), opcode(INVALID_OPCODE), peer_id_(-1)
{
if (likely(buf.size()))
{
// get packet header byte
const unsigned int op = buf[0];
// examine opcode
{
const unsigned int opc = opcode_extract(op);
switch (opc)
{
case CONTROL_SOFT_RESET_V1:
case CONTROL_V1:
case ACK_V1:
{
flags |= CONTROL;
opcode = opc;
break;
}
case DATA_V2:
{
if (unlikely(buf.size() < 4))
return;
const int opi = ntohl(*(const std::uint32_t *)buf.c_data()) & 0x00FFFFFF;
if (opi != OP_PEER_ID_UNDEF)
peer_id_ = opi;
opcode = opc;
break;
}
case DATA_V1:
{
opcode = opc;
break;
}
case CONTROL_HARD_RESET_CLIENT_V2:
case CONTROL_HARD_RESET_CLIENT_V3:
{
if (!proto.is_server())
return;
flags |= CONTROL;
opcode = opc;
break;
}
case CONTROL_HARD_RESET_SERVER_V2:
{
if (proto.is_server())
return;
flags |= CONTROL;
opcode = opc;
break;
}
default:
return;
}
}
// examine key ID
{
const unsigned int kid = key_id_extract(op);
if (proto.primary && kid == proto.primary->key_id())
flags |= DEFINED;
else if (proto.secondary && kid == proto.secondary->key_id())
flags |= (DEFINED | SECONDARY);
else if (opcode == CONTROL_SOFT_RESET_V1 && kid == proto.upcoming_key_id)
flags |= (DEFINED | SECONDARY | SOFT_RESET);
}
}
}
unsigned int flags;
unsigned int opcode;
int peer_id_;
};
static const char *opcode_name(const unsigned int opcode)
{
switch (opcode)
{
case CONTROL_SOFT_RESET_V1:
return "CONTROL_SOFT_RESET_V1";
case CONTROL_V1:
return "CONTROL_V1";
case ACK_V1:
return "ACK_V1";
case DATA_V1:
return "DATA_V1";
case DATA_V2:
return "DATA_V2";
case CONTROL_HARD_RESET_CLIENT_V2:
return "CONTROL_HARD_RESET_CLIENT_V2";
case CONTROL_HARD_RESET_CLIENT_V3:
return "CONTROL_HARD_RESET_CLIENT_V3";
case CONTROL_HARD_RESET_SERVER_V2:
return "CONTROL_HARD_RESET_SERVER_V2";
}
return nullptr;
}
std::string dump_packet(const Buffer& buf)
{
std::ostringstream out;
try {
Buffer b(buf);
const size_t orig_size = b.size();
const unsigned int op = b.pop_front();
const unsigned int opcode = opcode_extract(op);
const char *op_name = opcode_name(opcode);
if (op_name)
out << op_name << '/' << key_id_extract(op);
else
return "BAD_PACKET";
if (opcode == DATA_V1 || opcode == DATA_V2)
{
if (opcode == DATA_V2)
{
const unsigned int p1 = b.pop_front();
const unsigned int p2 = b.pop_front();
const unsigned int p3 = b.pop_front();
const unsigned int peer_id = (p1<<16) + (p2<<8) + p3;
if (peer_id != 0xFFFFFF)
out << " PEER_ID=" << peer_id;
}
out << " SIZE=" << b.size() << '/' << orig_size;
}
else
{
{
ProtoSessionID src_psid(b);
out << " SRC_PSID=" << src_psid.str();
}
if (tls_wrap_mode == TLS_CRYPT)
{
PacketID pid;
pid.read(b, PacketID::LONG_FORM);
out << " PID=" << pid.str();
const unsigned char *hmac = b.read_alloc(hmac_size);
out << " HMAC=" << render_hex(hmac, hmac_size);
// nothing else to print as the content is encrypted beyond this point
out << " TLS-CRYPT ENCRYPTED";
}
else
{
if (tls_wrap_mode == TLS_AUTH)
{
const unsigned char *hmac = b.read_alloc(hmac_size);
out << " HMAC=" << render_hex(hmac, hmac_size);
PacketID pid;
pid.read(b, PacketID::LONG_FORM);
out << " PID=" << pid.str();
}
ReliableAck ack(0);
ack.read(b);
const bool dest_psid_defined = !ack.empty();
out << " ACK=[";
while (!ack.empty())
{
out << " " << ack.front();
ack.pop_front();
}
out << " ]";
if (dest_psid_defined)
{
ProtoSessionID dest_psid(b);
out << " DEST_PSID=" << dest_psid.str();
}
if (opcode != ACK_V1)
out << " MSG_ID=" << ReliableAck::read_id(b);
}
if (opcode != ACK_V1)
out << " SIZE=" << b.size() << '/' << orig_size;
}
#ifdef OPENVPN_DEBUG_PROTO_DUMP
out << '\n' << string::trim_crlf_copy(dump_hex(buf));
#endif
}
catch (const std::exception& e)
{
out << " EXCEPTION: " << e.what();
}
return out.str();
}
protected:
// used for reading/writing authentication strings (username, password, etc.)
static void write_string_length(const size_t size, Buffer& buf)
{
if (size > 0xFFFF)
throw proto_error("auth_string_overflow");
const std::uint16_t net_size = htons(size);
buf.write((const unsigned char *)&net_size, sizeof(net_size));
}
static size_t read_string_length(Buffer& buf)
{
if (buf.size())
{
std::uint16_t net_size;
buf.read((unsigned char *)&net_size, sizeof(net_size));
return ntohs(net_size);
}
else
return 0;
}
template <typename S>
static void write_auth_string(const S& str, Buffer& buf)
{
const size_t len = str.length();
if (len)
{
write_string_length(len+1, buf);
buf.write((const unsigned char *)str.c_str(), len);
buf.null_terminate();
}
else
write_string_length(0, buf);
}
template <typename S>
static S read_auth_string(Buffer& buf)
{
const size_t len = read_string_length(buf);
if (len)
{
const char *data = (const char *) buf.read_alloc(len);
if (len > 1)
return S(data, len-1);
}
return S();
}
template <typename S>
static void write_control_string(const S& str, Buffer& buf)
{
const size_t len = str.length();
buf.write((const unsigned char *)str.c_str(), len);
buf.null_terminate();
}
template <typename S>
static S read_control_string(const Buffer& buf)
{
size_t size = buf.size();
if (size)
{
if (buf[size-1] == 0)
--size;
if (size)
return S((const char *)buf.c_data(), size);
}
return S();
}
template <typename S>
void write_control_string(const S& str)
{
const size_t len = str.length();
BufferPtr bp = new BufferAllocated(len+1, 0);
write_control_string(str, *bp);
control_send(std::move(bp));
}
static unsigned char *skip_string(Buffer& buf)
{
const size_t len = read_string_length(buf);
return buf.read_alloc(len);
}
static void write_empty_string(Buffer& buf)
{
write_string_length(0, buf);
}
// Packet structure for managing network packets, passed as a template
// parameter to ProtoStackBase
class Packet
{
friend class ProtoContext;
public:
Packet()
{
reset_non_buf();
}
Packet(BufferPtr&& buf_arg, const unsigned int opcode_arg = CONTROL_V1)
: opcode(opcode_arg), buf(std::move(buf_arg))
{
}
void reset()
{
reset_non_buf();
buf.reset();
}
void frame_prepare(const Frame& frame, const unsigned int context)
{
if (!buf)
buf.reset(new BufferAllocated());
frame.prepare(context, *buf);
}
bool is_raw() const { return opcode != CONTROL_V1; }
operator bool() const { return bool(buf); }
const BufferPtr& buffer_ptr() { return buf; }
const Buffer& buffer() const { return *buf; }
private:
void reset_non_buf()
{
opcode = INVALID_OPCODE;
}
unsigned int opcode;
BufferPtr buf;
};
// KeyContext encapsulates a single SSL/TLS session.
// ProtoStackBase uses CRTP-based static polymorphism for method callbacks.
class KeyContext : ProtoStackBase<Packet, KeyContext>, public RC<thread_unsafe_refcount>
{
typedef ProtoStackBase<Packet, KeyContext> Base;
friend Base;
typedef Base::ReliableSend ReliableSend;
typedef Base::ReliableRecv ReliableRecv;
// ProtoStackBase protected vars
using Base::now;
using Base::rel_recv;
using Base::rel_send;
using Base::xmit_acks;
// ProtoStackBase member functions
using Base::start_handshake;
using Base::raw_send;
using Base::send_pending_acks;
// Helper for handling deferred data channel setup,
// for example if cipher/digest are pushed.
struct DataChannelKey
{
DataChannelKey() : rekey_defined(false) {}
OpenVPNStaticKey key;
bool rekey_defined;
CryptoDCInstance::RekeyType rekey_type;
};
public:
typedef RCPtr<KeyContext> Ptr;
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_unwrap_wkc_error);
// 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,
// 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->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),
key_limit_renegotiation_fired(false),
tlsprf(p.config->tlsprf_factory->new_obj(p.is_server()))
{
// reliable protocol?
set_protocol(proto.config->protocol);
// get key_id from parent
key_id_ = proto.next_key_id();
// set initial state
set_state((proto.is_server() ? S_INITIAL : C_INITIAL) + (initiator ? 0 : 1));
// cache stuff that we need to access in hot path
cache_op32();
// remember when we were constructed
construct_time = *now;
// set must-negotiate-by time
set_event(KEV_NONE, KEV_NEGOTIATE, construct_time + proto.config->handshake_window);
}
void set_protocol(const Protocol& p)
{
is_reliable = p.is_reliable(); // cache is_reliable state locally
}
uint32_t get_tls_warnings() const
{
return Base::get_tls_warnings();
}
// need to call only on the initiator side of the connection
void start()
{
if (state == C_INITIAL || state == S_INITIAL)
{
send_reset();
set_state(state+1);
dirty = true;
}
}
// control channel flush
void flush()
{
if (dirty)
{
post_ack_action();
Base::flush();
send_pending_acks();
dirty = false;
}
}
void invalidate(const Error::Type reason)
{
Base::invalidate(reason);
}
// retransmit packets as part of reliability layer
void retransmit()
{
// note that we don't set dirty here
Base::retransmit();
}
// when should we next call retransmit method
Time next_retransmit() const
{
const Time t = Base::next_retransmit();
if (t <= next_event_time)
return t;
else
return next_event_time;
}
void app_send_validate(BufferPtr&& bp)
{
if (bp->size() > APP_MSG_MAX)
throw proto_error("app_send: sent control message is too large");
Base::app_send(std::move(bp));
}
// send app-level cleartext data to peer via SSL
void app_send(BufferPtr&& bp)
{
if (state >= ACTIVE)
{
app_send_validate(std::move(bp));
dirty = true;
}
else
app_pre_write_queue.push_back(bp);
}
// pass received ciphertext packets on network to SSL/reliability layers
bool net_recv(Packet&& pkt)
{
const bool ret = Base::net_recv(std::move(pkt));
dirty = true;
return ret;
}
// data channel encrypt
void encrypt(BufferAllocated& buf)
{
if (state >= ACTIVE
&& (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
&& !invalidated())
{
// compress and encrypt packet and prepend op header
const bool pid_wrap = do_encrypt(buf, true);
// 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 replay 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
}
// data channel decrypt
void decrypt(BufferAllocated& buf)
{
try {
if (state >= ACTIVE
&& (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
&& !invalidated())
{
// Knock off leading op from buffer, but pass the 32-bit version to
// decrypt so it can be used as Additional Data for packet authentication.
const size_t head_size = op_head_size(buf[0]);
const unsigned char *op32 = (head_size == OP_SIZE_V2) ? buf.c_data() : nullptr;
buf.advance(head_size);
// decrypt packet
const Error::Type err = crypto->decrypt(buf, now->seconds_since_epoch(), op32);
if (err)
{
proto.stats->error(err);
if (proto.is_tcp() && (err == Error::DECRYPT_ERROR || err == Error::HMAC_ERROR))
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);
// set MSS for segments server can receive
if (proto.config->mss_inter > 0)
MSSFix::mssfix(buf, proto.config->mss_inter);
}
else
buf.reset_size(); // no crypto context available
}
catch (BufferException&)
{
proto.stats->error(Error::BUFFER_ERROR);
buf.reset_size();
if (proto.is_tcp())
invalidate(Error::BUFFER_ERROR);
}
}
// usually called by parent ProtoContext object when this KeyContext
// has been retired.
void prepare_expire(const EventType current_ev = KeyContext::KEV_NONE)
{
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?
bool event_pending()
{
if (current_event == KEV_NONE && *now >= next_event_time)
process_next_event();
return current_event != KEV_NONE;
}
// get KEV_x event
EventType get_event() const { return current_event; }
// clear KEV_x event
void reset_event() { current_event = KEV_NONE; }
// was session invalidated by an exception?
bool invalidated() const { return Base::invalidated(); }
// Reason for invalidation
Error::Type invalidation_reason() const { return Base::invalidation_reason(); }
// our Key ID in the OpenVPN protocol
unsigned int key_id() const { return key_id_; }
// indicates that data channel is keyed and ready to encrypt/decrypt packets
bool data_channel_ready() const { return state >= ACTIVE; }
bool is_dirty() const { return dirty; }
// notification from parent of rekey operation
void rekey(const CryptoDCInstance::RekeyType type)
{
if (crypto)
crypto->rekey(type);
else if (data_channel_key)
{
// save for deferred processing
data_channel_key->rekey_type = type;
data_channel_key->rekey_defined = true;
}
}
// time that our state transitioned to ACTIVE
Time reached_active() const { return reached_active_time_; }
// transmit a keepalive message to peer
void send_keepalive()
{
send_data_channel_message(proto_context_private::keepalive_message,
sizeof(proto_context_private::keepalive_message));
}
// send explicit-exit-notify message to peer
void send_explicit_exit_notify()
{
#ifndef OPENVPN_DISABLE_EXPLICIT_EXIT // explicit exit should always be enabled in production
if (crypto_flags & CryptoDCInstance::EXPLICIT_EXIT_NOTIFY_DEFINED)
crypto->explicit_exit_notify();
else
send_data_channel_message(proto_context_private::explicit_exit_notify_message,
sizeof(proto_context_private::explicit_exit_notify_message));
#endif
}
// general purpose method for sending constant string messages
// to peer via data channel
void send_data_channel_message(const unsigned char *data, const size_t size)
{
if (state >= ACTIVE
&& (crypto_flags & CryptoDCInstance::CRYPTO_DEFINED)
&& !invalidated())
{
// allocate packet
Packet pkt;
pkt.frame_prepare(*proto.config->frame, Frame::WRITE_DC_MSG);
// write keepalive message
pkt.buf->write(data, size);
// process packet for transmission
do_encrypt(*pkt.buf, false); // set compress hint to "no"
// send it
proto.net_send(key_id_, pkt);
}
}
// validate the integrity of a packet
static bool validate(const Buffer& net_buf, ProtoContext& proto, TimePtr now)
{
try {
Buffer recv(net_buf);
switch (proto.tls_wrap_mode)
{
case TLS_AUTH:
return validate_tls_auth(recv, proto, now);
case TLS_CRYPT_V2:
if (opcode_extract(recv[0]) == CONTROL_HARD_RESET_CLIENT_V3)
{
// skip validation of HARD_RESET_V3 because the tls-crypt
// engine has not been initialized yet
OPENVPN_LOG_PROTO_VERBOSE("SKIPPING VALIDATION OF HARD_RESET_V3");
return true;
}
/* no break */
case TLS_CRYPT:
return validate_tls_crypt(recv, proto, now);
case TLS_PLAIN:
return validate_tls_plain(recv, proto, now);
}
}
catch (BufferException& e)
{
OPENVPN_LOG_PROTO_VERBOSE("validate() exception: " << e.what());
}
return false;
}
// Initialize the components of the OpenVPN data channel protocol
void init_data_channel()
{
// set up crypto for data channel
if (data_channel_key)
{
bool enable_compress = true;
Config& c = *proto.config;
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();
if (crypto_flags & CryptoDCInstance::CIPHER_DEFINED)
crypto->init_cipher(key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::ENCRYPT | key_dir),
key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::DECRYPT | key_dir));
if (crypto_flags & CryptoDCInstance::HMAC_DEFINED)
crypto->init_hmac(key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::ENCRYPT | key_dir),
key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));
crypto->init_pid(PacketID::SHORT_FORM,
c.pid_mode,
PacketID::SHORT_FORM,
"DATA", int(key_id_),
proto.stats);
crypto->init_remote_peer_id(c.remote_peer_id);
enable_compress = crypto->consider_compression(proto.config->comp_ctx);
if (data_channel_key->rekey_defined)
crypto->rekey(data_channel_key->rekey_type);
data_channel_key.reset();
// set up compression for data channel
if (enable_compress)
compress = proto.config->comp_ctx.new_compressor(proto.config->frame, proto.stats);
else
compress.reset();
// cache op32 for hot path in do_encrypt
cache_op32();
int crypto_encap = (enable_op32 ? OP_SIZE_V2 : 1) +
c.comp_ctx.extra_payload_bytes() +
PacketID::size(PacketID::SHORT_FORM) +
c.dc.context().encap_overhead();
int transport_encap = 0;
if (c.mss_parms.mtu)
{
if (proto.is_tcp())
transport_encap += sizeof(struct TCPHeader);
else
transport_encap += sizeof(struct UDPHeader);
if (c.protocol.is_ipv6())
transport_encap += sizeof(struct IPv6Header);
else
transport_encap += sizeof(struct IPv4Header);
transport_encap += c.protocol.extra_transport_bytes();
}
if (c.mss_parms.mssfix != 0)
{
OPENVPN_LOG_PROTO("MTU mssfix=" << c.mss_parms.mssfix <<
" crypto_encap=" << crypto_encap <<
" transport_encap=" << transport_encap);
c.mss_inter = c.mss_parms.mssfix - (crypto_encap + transport_encap);
}
}
}
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:
static bool validate_tls_auth(Buffer &recv, ProtoContext& proto, TimePtr now)
{
const unsigned char *orig_data = recv.data();
const size_t orig_size = recv.size();
// advance buffer past initial op byte
recv.advance(1);
// get source PSID
ProtoSessionID src_psid(recv);
// verify HMAC
{
recv.advance(proto.hmac_size);
if (!proto.ta_hmac_recv->ovpn_hmac_cmp(orig_data, orig_size,
1 + ProtoSessionID::SIZE,
proto.hmac_size,
PacketID::size(PacketID::LONG_FORM)))
return false;
}
// verify source PSID
if (!proto.psid_peer.match(src_psid))
return false;
// read tls_auth packet ID
const PacketID pid = proto.ta_pid_recv.read_next(recv);
// get current time_t
const PacketID::time_t t = now->seconds_since_epoch();
// verify tls_auth packet ID
const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);
// make sure that our own PSID is contained in packet received from peer
if (ReliableAck::ack_skip(recv))
{
ProtoSessionID dest_psid(recv);
if (!proto.psid_self.match(dest_psid))
return false;
}
return pid_ok;
}
static bool validate_tls_crypt(Buffer& recv, ProtoContext& proto, TimePtr now)
{
const unsigned char *orig_data = recv.data();
const size_t orig_size = recv.size();
// advance buffer past initial op byte
recv.advance(1);
// get source PSID
ProtoSessionID src_psid(recv);
// read tls_auth packet ID
const PacketID pid = proto.ta_pid_recv.read_next(recv);
recv.advance(proto.hmac_size);
const size_t head_size = 1 + ProtoSessionID::SIZE + PacketID::size(PacketID::LONG_FORM);
const size_t data_offset = head_size + proto.hmac_size;
if (orig_size < data_offset)
return false;
// we need a buffer to perform the payload decryption and being this a static
// function we can't use the instance member like in decapsulate_tls_crypt()
BufferAllocated work;
proto.config->frame->prepare(Frame::DECRYPT_WORK, work);
// decrypt payload from 'recv' into 'work'
const size_t decrypt_bytes = proto.tls_crypt_recv->decrypt(orig_data + head_size,
work.data(), work.max_size(),
recv.c_data(), recv.size());
if (!decrypt_bytes)
return false;
work.inc_size(decrypt_bytes);
// verify HMAC
if (!proto.tls_crypt_recv->hmac_cmp(orig_data,
TLSCryptContext::hmac_offset,
work.c_data(), work.size()))
return false;
// verify source PSID
if (proto.psid_peer.defined())
{
if (!proto.psid_peer.match(src_psid))
return false;
}
else
{
proto.psid_peer = src_psid;
}
// get current time_t
const PacketID::time_t t = now->seconds_since_epoch();
// verify tls_auth packet ID
const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);
// make sure that our own PSID is contained in packet received from peer
if (ReliableAck::ack_skip(work))
{
ProtoSessionID dest_psid(work);
if (!proto.psid_self.match(dest_psid))
return false;
}
return pid_ok;
}
static bool validate_tls_plain(Buffer& recv, ProtoContext& proto, TimePtr now)
{
// advance buffer past initial op byte
recv.advance(1);
// verify source PSID
ProtoSessionID src_psid(recv);
if (!proto.psid_peer.match(src_psid))
return false;
// make sure that our own PSID is contained in packet received from peer
if (ReliableAck::ack_skip(recv))
{
ProtoSessionID dest_psid(recv);
if (!proto.psid_self.match(dest_psid))
return false;
}
return true;
}
bool do_encrypt(BufferAllocated& buf, const bool compress_hint)
{
bool pid_wrap;
// set MSS for segments client can receive
if (proto.config->mss_inter > 0)
MSSFix::mssfix(buf, proto.config->mss_inter);
// compress packet
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));
static_assert(sizeof(op32) == OP_SIZE_V2, "OP_SIZE_V2 inconsistency");
// encrypt packet
pid_wrap = crypto->encrypt(buf, now->seconds_since_epoch(), (const unsigned char *)&op32);
// prepend op
buf.prepend((const unsigned char *)&op32, sizeof(op32));
}
else
{
// encrypt packet
pid_wrap = crypto->encrypt(buf, now->seconds_since_epoch(), nullptr);
// prepend op
buf.push_front(op_compose(DATA_V1, key_id_));
}
return pid_wrap;
}
// cache op32 and remote_peer_id
void cache_op32()
{
enable_op32 = proto.config->enable_op32;
remote_peer_id = proto.config->remote_peer_id;
}
void set_state(const int 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(proto.debug_prefix() << " KeyContext[" << key_id_ << "] " << event_type_string(current));
current_event = current;
}
void set_event(const EventType current, const EventType next, const Time& 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;
}
void invalidate_callback() // called by ProtoStackBase when session is invalidated
{
reached_active_time_ = Time();
next_event = KEV_NONE;
next_event_time = Time::infinite();
}
// 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 (!key_limit_renegotiation_fired && state >= ACTIVE && !invalidated())
{
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);
}
}
// 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)
{
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, reached_active() + proto.config->become_primary);
}
void process_next_event()
{
if (*now >= next_event_time)
{
switch (next_event)
{
case KEV_BECOME_PRIMARY:
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:
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:
kev_error(KEV_EXPIRE, Error::N_KEV_EXPIRE);
break;
default:
break;
}
}
}
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 bool tls_crypt_v2) const
{
if (key_id_)
{
return CONTROL_SOFT_RESET_V1;
}
else
{
if (proto.is_server() == sender)
return CONTROL_HARD_RESET_SERVER_V2;
if (!tls_crypt_v2)
return CONTROL_HARD_RESET_CLIENT_V2;
else
return CONTROL_HARD_RESET_CLIENT_V3;
}
}
void send_reset()
{
Packet pkt;
pkt.opcode = initial_op(true, proto.tls_wrap_mode == TLS_CRYPT_V2);
pkt.frame_prepare(*proto.config->frame, Frame::WRITE_SSL_INIT);
raw_send(std::move(pkt));
}
void raw_recv(Packet&& raw_pkt) // called by ProtoStackBase
{
if (raw_pkt.buf->empty() &&
raw_pkt.opcode == initial_op(false, proto.tls_wrap_mode == TLS_CRYPT_V2))
{
switch (state)
{
case C_WAIT_RESET:
//send_reset(); // fixme -- possibly not needed
set_state(C_WAIT_RESET_ACK);
break;
case S_WAIT_RESET:
send_reset();
set_state(S_WAIT_RESET_ACK);
break;
}
}
}
void app_recv(BufferPtr&& to_app_buf) // called by ProtoStackBase
{
app_recv_buf.put(std::move(to_app_buf));
if (app_recv_buf.size() > APP_MSG_MAX)
throw proto_error("app_recv: received control message is too large");
BufferComposed::Complete bcc = app_recv_buf.complete();
switch (state)
{
case C_WAIT_AUTH:
if (recv_auth_complete(bcc))
{
recv_auth(bcc.get());
set_state(C_WAIT_AUTH_ACK);
}
break;
case S_WAIT_AUTH:
if (recv_auth_complete(bcc))
{
recv_auth(bcc.get());
send_auth();
set_state(S_WAIT_AUTH_ACK);
}
break;
case S_WAIT_AUTH_ACK: // rare case where client receives auth, goes ACTIVE, but the ACK response is dropped
case ACTIVE:
if (bcc.advance_to_null()) // does composed buffer contain terminating null char?
proto.app_recv(key_id_, bcc.get());
break;
}
}
void net_send(const Packet& net_pkt, const Base::NetSendType nstype) // called by ProtoStackBase
{
if (!is_reliable || nstype != Base::NET_SEND_RETRANSMIT) // retransmit packets on UDP only, not TCP
proto.net_send(key_id_, net_pkt);
}
void post_ack_action()
{
if (state <= LAST_ACK_STATE && !rel_send.n_unacked())
{
switch (state)
{
case C_WAIT_RESET_ACK:
start_handshake();
send_auth();
set_state(C_WAIT_AUTH);
break;
case S_WAIT_RESET_ACK:
start_handshake();
set_state(S_WAIT_AUTH);
break;
case C_WAIT_AUTH_ACK:
active();
set_state(ACTIVE);
break;
case S_WAIT_AUTH_ACK:
active();
set_state(ACTIVE);
break;
}
}
}
void send_auth()
{
BufferPtr buf = new BufferAllocated();
proto.config->frame->prepare(Frame::WRITE_SSL_CLEARTEXT, *buf);
buf->write(proto_context_private::auth_prefix, sizeof(proto_context_private::auth_prefix));
tlsprf->self_randomize(*proto.config->rng);
tlsprf->self_write(*buf);
const std::string options = proto.config->options_string();
write_auth_string(options, *buf);
if (!proto.is_server())
{
OPENVPN_LOG_PROTO("Tunnel Options:" << options);
buf->or_flags(BufferAllocated::DESTRUCT_ZERO);
if (proto.config->xmit_creds)
proto.client_auth(*buf);
else
{
write_empty_string(*buf); // username
write_empty_string(*buf); // password
}
const std::string peer_info = proto.config->peer_info_string();
write_auth_string(peer_info, *buf);
}
app_send_validate(std::move(buf));
dirty = true;
}
void recv_auth(BufferPtr buf)
{
const unsigned char *buf_pre = buf->read_alloc(sizeof(proto_context_private::auth_prefix));
if (std::memcmp(buf_pre, proto_context_private::auth_prefix, sizeof(proto_context_private::auth_prefix)))
throw proto_error("bad_auth_prefix");
tlsprf->peer_read(*buf);
const std::string options = read_auth_string<std::string>(*buf);
if (proto.is_server())
{
const std::string username = read_auth_string<std::string>(*buf);
const SafeString password = read_auth_string<SafeString>(*buf);
const std::string peer_info = read_auth_string<std::string>(*buf);
proto.server_auth(username, password, peer_info, Base::auth_cert());
}
}
// return true if complete recv_auth message is contained in buffer
bool recv_auth_complete(BufferComplete& bc) const
{
if (!bc.advance(sizeof(proto_context_private::auth_prefix)))
return false;
if (!tlsprf->peer_read_complete(bc))
return false;
if (!bc.advance_string()) // options
return false;
if (proto.is_server())
{
if (!bc.advance_string()) // username
return false;
if (!bc.advance_string()) // password
return false;
if (!bc.advance_string()) // peer_info
return false;
}
return true;
}
void active()
{
if (proto.config->debug_level >= 1)
OPENVPN_LOG_SSL("SSL Handshake: " << Base::ssl_handshake_details());
generate_session_keys();
while (!app_pre_write_queue.empty())
{
app_send_validate(std::move(app_pre_write_queue.front()));
app_pre_write_queue.pop_front();
dirty = true;
}
reached_active_time_ = *now;
proto.slowest_handshake_.max(reached_active_time_ - construct_time);
active_event();
}
// use the TLS PRF construction to exchange session keys for building
// the data channel crypto context
void generate_session_keys()
{
std::unique_ptr<DataChannelKey> dck(new DataChannelKey());
tlsprf->generate_key_expansion(dck->key, proto.psid_self, proto.psid_peer);
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)
init_data_channel();
}
void prepend_dest_psid_and_acks(Buffer& buf)
{
// if sending ACKs, prepend dest PSID
if (!xmit_acks.empty())
{
if (proto.psid_peer.defined())
proto.psid_peer.prepend(buf);
else
{
proto.stats->error(Error::CC_ERROR);
throw proto_error("peer_psid_undef");
}
}
// prepend ACKs for messages received from peer
xmit_acks.prepend(buf);
}
bool verify_src_psid(const ProtoSessionID& src_psid)
{
if (proto.psid_peer.defined())
{
if (!proto.psid_peer.match(src_psid))
{
proto.stats->error(Error::CC_ERROR);
if (proto.is_tcp())
invalidate(Error::CC_ERROR);
return false;
}
}
else
{
proto.psid_peer = src_psid;
}
return true;
}
bool verify_dest_psid(Buffer& buf)
{
ProtoSessionID dest_psid(buf);
if (!proto.psid_self.match(dest_psid))
{
proto.stats->error(Error::CC_ERROR);
if (proto.is_tcp())
invalidate(Error::CC_ERROR);
return false;
}
return true;
}
void gen_head_tls_auth(const unsigned int opcode, Buffer& buf)
{
// write tls-auth packet ID
proto.ta_pid_send.write_next(buf, true, now->seconds_since_epoch());
// make space for tls-auth HMAC
buf.prepend_alloc(proto.hmac_size);
// write source PSID
proto.psid_self.prepend(buf);
// write opcode
buf.push_front(op_compose(opcode, key_id_));
// write hmac
proto.ta_hmac_send->ovpn_hmac_gen(buf.data(), buf.size(),
1 + ProtoSessionID::SIZE,
proto.hmac_size,
PacketID::size(PacketID::LONG_FORM));
}
void gen_head_tls_crypt(const unsigned int opcode, BufferAllocated& buf)
{
// in 'work' we store all the fields that are not supposed to be encrypted
proto.config->frame->prepare(Frame::ENCRYPT_WORK, work);
// make space for HMAC
work.prepend_alloc(proto.hmac_size);
// write tls-crypt packet ID
proto.ta_pid_send.write_next(work, true, now->seconds_since_epoch());
// write source PSID
proto.psid_self.prepend(work);
// write opcode
work.push_front(op_compose(opcode, key_id_));
// compute HMAC using header fields (from 'work') and plaintext
// payload (from 'buf')
proto.tls_crypt_send->hmac_gen(work.data(), TLSCryptContext::hmac_offset,
buf.c_data(), buf.size());
const size_t data_offset = TLSCryptContext::hmac_offset + proto.hmac_size;
// encrypt the content of 'buf' (packet payload) into 'work'
const size_t decrypt_bytes = proto.tls_crypt_send->encrypt(work.c_data() + TLSCryptContext::hmac_offset,
work.data() + data_offset,
work.max_size() - data_offset,
buf.c_data(), buf.size());
if (!decrypt_bytes)
{
buf.reset_size();
return;
}
work.inc_size(decrypt_bytes);
// append WKc to wrapped packet for tls-crypt-v2
if ((opcode == CONTROL_HARD_RESET_CLIENT_V3)
&& (proto.tls_wrap_mode == TLS_CRYPT_V2))
proto.tls_crypt_append_wkc(work);
// 'work' now contains the complete packet ready to go. swap it with 'buf'
buf.swap(work);
}
void gen_head_tls_plain(const unsigned int opcode, Buffer& buf)
{
// write source PSID
proto.psid_self.prepend(buf);
// write opcode
buf.push_front(op_compose(opcode, key_id_));
}
void gen_head(const unsigned int opcode, BufferAllocated& buf)
{
switch (proto.tls_wrap_mode)
{
case TLS_AUTH:
gen_head_tls_auth(opcode, buf);
break;
case TLS_CRYPT:
case TLS_CRYPT_V2:
gen_head_tls_crypt(opcode, buf);
break;
case TLS_PLAIN:
gen_head_tls_plain(opcode, buf);
break;
}
}
void encapsulate(id_t id, Packet& pkt) // called by ProtoStackBase
{
BufferAllocated& buf = *pkt.buf;
// prepend message sequence number
ReliableAck::prepend_id(buf, id);
// prepend dest PSID and ACKs to reply to peer
prepend_dest_psid_and_acks(buf);
// generate message head
gen_head(pkt.opcode, buf);
}
void generate_ack(Packet& pkt) // called by ProtoStackBase
{
BufferAllocated& buf = *pkt.buf;
// prepend dest PSID and ACKs to reply to peer
prepend_dest_psid_and_acks(buf);
gen_head(ACK_V1, buf);
}
bool decapsulate_post_process(Packet& pkt, ProtoSessionID& src_psid, const PacketID pid)
{
Buffer& recv = *pkt.buf;
// update our last-packet-received time
proto.update_last_received();
// verify source PSID
if (!verify_src_psid(src_psid))
return false;
// get current time_t
const PacketID::time_t t = now->seconds_since_epoch();
// verify tls_auth/crypt packet ID
const bool pid_ok = proto.ta_pid_recv.test_add(pid, t, false);
// process ACKs sent by peer (if packet ID check failed,
// read the ACK IDs, but don't modify the rel_send object).
if (ReliableAck::ack(rel_send, recv, pid_ok))
{
// make sure that our own PSID is contained in packet received from peer
if (!verify_dest_psid (recv))
return false;
}
// for CONTROL packets only, not ACK
if (pkt.opcode != ACK_V1)
{
// get message sequence number
const id_t id = ReliableAck::read_id (recv);
if (pid_ok)
{
// try to push message into reliable receive object
const unsigned int rflags = rel_recv.receive (pkt, id);
// should we ACK packet back to sender?
if (rflags & ReliableRecv::ACK_TO_SENDER)
xmit_acks.push_back (id); // ACK packet to sender
// was packet accepted by reliable receive object?
if (rflags & ReliableRecv::IN_WINDOW)
{
proto.ta_pid_recv.test_add (pid, t, true); // remember tls_auth packet ID so that it can't be replayed
return true;
}
}
else // treat as replay
{
proto.stats->error (Error::REPLAY_ERROR);
if (pid.is_valid ())
xmit_acks.push_back (id); // even replayed packets must be ACKed or protocol could deadlock
}
}
else
{
if (pid_ok)
proto.ta_pid_recv.test_add (pid, t, true); // remember tls_auth packet ID of ACK packet to prevent replay
else
proto.stats->error (Error::REPLAY_ERROR);
}
return false;
}
bool decapsulate_tls_auth(Packet &pkt)
{
Buffer& recv = *pkt.buf;
const unsigned char *orig_data = recv.data ();
const size_t orig_size = recv.size ();
// advance buffer past initial op byte
recv.advance (1);
// get source PSID
ProtoSessionID src_psid (recv);
// verify HMAC
{
recv.advance (proto.hmac_size);
if (!proto.ta_hmac_recv->ovpn_hmac_cmp(orig_data, orig_size,
1 + ProtoSessionID::SIZE,
proto.hmac_size,
PacketID::size (PacketID::LONG_FORM)))
{
proto.stats->error(Error::HMAC_ERROR);
if (proto.is_tcp())
invalidate(Error::HMAC_ERROR);
return false;
}
}
// read tls_auth packet ID
const PacketID pid = proto.ta_pid_recv.read_next(recv);
return decapsulate_post_process(pkt, src_psid, pid);
}
bool decapsulate_tls_crypt(Packet &pkt)
{
BufferAllocated& recv = *pkt.buf;
const unsigned char *orig_data = recv.data();
const size_t orig_size = recv.size();
// advance buffer past initial op byte
recv.advance(1);
// get source PSID
ProtoSessionID src_psid(recv);
// get tls-crypt packet ID
const PacketID pid = proto.ta_pid_recv.read_next(recv);
// skip the hmac
recv.advance(proto.hmac_size);
const size_t data_offset = TLSCryptContext::hmac_offset + proto.hmac_size;
if (orig_size < data_offset)
return false;
// decrypt payload
proto.config->frame->prepare(Frame::DECRYPT_WORK, work);
const size_t decrypt_bytes = proto.tls_crypt_recv->decrypt(orig_data + TLSCryptContext::hmac_offset,
work.data(), work.max_size(),
recv.c_data(), recv.size());
if (!decrypt_bytes)
{
proto.stats->error(Error::DECRYPT_ERROR);
if (proto.is_tcp())
invalidate(Error::DECRYPT_ERROR);
return false;
}
work.inc_size(decrypt_bytes);
// verify HMAC
if (!proto.tls_crypt_recv->hmac_cmp(orig_data, TLSCryptContext::hmac_offset,
work.c_data(), work.size()))
{
proto.stats->error(Error::HMAC_ERROR);
if (proto.is_tcp())
invalidate(Error::HMAC_ERROR);
return false;
}
// move the decrypted payload to 'recv', so that the processing of the
// packet can continue
recv.swap(work);
return decapsulate_post_process(pkt, src_psid, pid);
}
bool decapsulate_tls_plain(Packet &pkt)
{
Buffer& recv = *pkt.buf;
// update our last-packet-received time
proto.update_last_received();
// advance buffer past initial op byte
recv.advance(1);
// verify source PSID
ProtoSessionID src_psid(recv);
if (!verify_src_psid(src_psid))
return false;
// process ACKs sent by peer
if (ReliableAck::ack(rel_send, recv, true))
{
// make sure that our own PSID is in packet received from peer
if (!verify_dest_psid(recv))
return false;
}
// for CONTROL packets only, not ACK
if (pkt.opcode != ACK_V1)
{
// get message sequence number
const id_t id = ReliableAck::read_id(recv);
// try to push message into reliable receive object
const unsigned int rflags = rel_recv.receive(pkt, id);
// should we ACK packet back to sender?
if (rflags & ReliableRecv::ACK_TO_SENDER)
xmit_acks.push_back(id); // ACK packet to sender
// was packet accepted by reliable receive object?
if (rflags & ReliableRecv::IN_WINDOW)
return true;
}
return false;
}
bool unwrap_tls_crypt_wkc(Buffer &recv)
{
// the ``WKc`` is located at the end of the packet, after the tls-crypt
// payload.
// Format is as follows (as documented by Steffan Krager):
//
// ``len = len(WKc)`` (16 bit, network byte order)
// ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
// ``IV = 128 most significant bits of T``
// ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
const unsigned char *orig_data = recv.data();
const size_t orig_size = recv.size();
const size_t hmac_size = proto.config->tls_crypt_context->digest_size();
const size_t tls_frame_size = 1 + ProtoSessionID::SIZE +
PacketID::size(PacketID::LONG_FORM) +
hmac_size +
// the following is the tls-crypt payload
sizeof(char) + // length of ACK array
sizeof(id_t); // reliable ID
// check that at least the authentication tag ``T`` is present
if (orig_size < (tls_frame_size + hmac_size))
return false;
// the ``WKc`` is just appended after the standard tls-crypt frame
const unsigned char *wkc_raw = orig_data + tls_frame_size;
const size_t wkc_raw_size = orig_size - tls_frame_size - sizeof(uint16_t);
// retrieve the ``WKc`` len from the bottom of the packet and convert it to Host Order
uint16_t wkc_len = ntohs(*(uint16_t *)(wkc_raw + wkc_raw_size));
// length sanity check (the size of the ``len`` field is included in the value)
if ((wkc_len - sizeof(uint16_t)) != wkc_raw_size)
return false;
BufferAllocated plaintext(wkc_len, BufferAllocated::CONSTRUCT_ZERO);
// plaintext will be used to compute the Auth Tag, therefore start by prepnding
// the WKc length in network order
wkc_len = htons(wkc_len);
plaintext.write(&wkc_len, sizeof(wkc_len));
const size_t decrypt_bytes = proto.tls_crypt_server->decrypt(wkc_raw,
plaintext.data() + 2,
plaintext.max_size() - 2,
wkc_raw + hmac_size,
wkc_raw_size - hmac_size);
plaintext.inc_size(decrypt_bytes);
// decrypted data must at least contain a full 2048bits client key
// (metadata is optional)
if (plaintext.size() < OpenVPNStaticKey::KEY_SIZE)
{
proto.stats->error(Error::DECRYPT_ERROR);
if (proto.is_tcp())
invalidate(Error::DECRYPT_ERROR);
return false;
}
if (!proto.tls_crypt_server->hmac_cmp(wkc_raw, 0,
plaintext.c_data(),
plaintext.size()))
{
proto.stats->error(Error::HMAC_ERROR);
if (proto.is_tcp())
invalidate(Error::HMAC_ERROR);
return false;
}
// we can now remove the WKc length from the plaintext, as it is not
// really part of the key material
plaintext.advance(sizeof(wkc_len));
// WKc has been authenticated: it contains the client key followed
// by the optional metadata. Let's initialize the tls-crypt context
// with the client key
OpenVPNStaticKey client_key;
plaintext.read(client_key.raw_alloc(), OpenVPNStaticKey::KEY_SIZE);
proto.reset_tls_crypt(*proto.config, client_key);
// verify metadata
int metadata_type = -1;
if (!plaintext.empty())
metadata_type = plaintext.pop_front();
if (!proto.tls_crypt_metadata->verify(metadata_type, plaintext))
{
proto.stats->error(Error::TLS_CRYPT_META_FAIL);
return false;
}
// virtually remove the WKc from the packet
recv.set_size(tls_frame_size);
return true;
}
bool decapsulate(Packet& pkt) // called by ProtoStackBase
{
try {
switch (proto.tls_wrap_mode)
{
case TLS_AUTH:
return decapsulate_tls_auth(pkt);
case TLS_CRYPT_V2:
if (pkt.opcode == CONTROL_HARD_RESET_CLIENT_V3)
{
// unwrap WKc and extract Kc (client key) from packet.
// This way we can initialize the tls-crypt per-client contexts
// (this happens on the server side only)
if (!unwrap_tls_crypt_wkc(*pkt.buf))
{
return false;
}
}
// now that the tls-crypt contexts have been initialized it is
// possible to proceed with the standard tls-crypt decapsulation
/* no break */
case TLS_CRYPT:
return decapsulate_tls_crypt(pkt);
case TLS_PLAIN:
return decapsulate_tls_plain(pkt);
}
}
catch (BufferException&)
{
proto.stats->error(Error::BUFFER_ERROR);
if (proto.is_tcp())
invalidate(Error::BUFFER_ERROR);
}
return false;
}
// for debugging
static const char *state_string(const int s)
{
switch (s)
{
case C_WAIT_RESET_ACK:
return "C_WAIT_RESET_ACK";
case C_WAIT_AUTH_ACK:
return "C_WAIT_AUTH_ACK";
case S_WAIT_RESET_ACK:
return "S_WAIT_RESET_ACK";
case S_WAIT_AUTH_ACK:
return "S_WAIT_AUTH_ACK";
case C_INITIAL:
return "C_INITIAL";
case C_WAIT_RESET:
return "C_WAIT_RESET";
case C_WAIT_AUTH:
return "C_WAIT_AUTH";
case S_INITIAL:
return "S_INITIAL";
case S_WAIT_RESET:
return "S_WAIT_RESET";
case S_WAIT_AUTH:
return "S_WAIT_AUTH";
case ACTIVE:
return "ACTIVE";
default:
return "STATE_UNDEF";
}
}
// for debugging
int seconds_until(const Time& next_time)
{
Time::Duration d = next_time - *now;
if (d.is_infinite())
return -1;
else
return d.to_seconds();
}
// BEGIN KeyContext data members
ProtoContext& proto; // parent
int state;
unsigned int key_id_;
unsigned int crypto_flags;
int remote_peer_id; // -1 to disable
bool enable_op32;
bool dirty;
bool key_limit_renegotiation_fired;
bool is_reliable;
Compress::Ptr compress;
CryptoDCInstance::Ptr crypto;
TLSPRFInstance::Ptr tlsprf;
Time construct_time;
Time reached_active_time_;
Time next_event_time;
EventType current_event;
EventType next_event;
std::deque<BufferPtr> app_pre_write_queue;
std::unique_ptr<DataChannelKey> data_channel_key;
BufferComposed app_recv_buf;
std::unique_ptr<DataLimit> data_limit;
BufferAllocated work;
// static member used by validate_tls_crypt()
static BufferAllocated static_work;
};
public:
class TLSWrapPreValidate : public RC<thread_unsafe_refcount>
{
public:
typedef RCPtr<TLSWrapPreValidate> Ptr;
virtual bool validate(const BufferAllocated& net_buf) = 0;
};
// Validate the integrity of a packet, only considering tls-auth HMAC.
class TLSAuthPreValidate : public TLSWrapPreValidate
{
public:
OPENVPN_SIMPLE_EXCEPTION(tls_auth_pre_validate);
TLSAuthPreValidate(const Config& c, const bool server)
{
if (!c.tls_auth_enabled())
throw tls_auth_pre_validate();
// save hard reset op we expect to receive from peer
reset_op = server ? CONTROL_HARD_RESET_CLIENT_V2 : CONTROL_HARD_RESET_SERVER_V2;
// init OvpnHMACInstance
ta_hmac_recv = c.tls_auth_context->new_obj();
// init tls_auth hmac
if (c.key_direction >= 0)
{
// key-direction is 0 or 1
const unsigned int key_dir = c.key_direction ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));
}
else
{
// key-direction bidirectional mode
ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
}
}
bool validate(const BufferAllocated& net_buf)
{
try
{
if (!net_buf.size())
return false;
const unsigned int op = net_buf[0];
if (opcode_extract(op) != reset_op || key_id_extract(op) != 0)
return false;
return ta_hmac_recv->ovpn_hmac_cmp(net_buf.c_data(), net_buf.size(),
1 + ProtoSessionID::SIZE,
ta_hmac_recv->output_size(),
PacketID::size(PacketID::LONG_FORM));
}
catch (BufferException&)
{
}
return false;
}
private:
OvpnHMACInstance::Ptr ta_hmac_recv;
unsigned int reset_op;
};
class TLSCryptPreValidate : public TLSWrapPreValidate
{
public:
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_pre_validate);
TLSCryptPreValidate(const Config& c, const bool server)
{
if (!c.tls_crypt_enabled())
throw tls_crypt_pre_validate();
// save hard reset op we expect to receive from peer
reset_op = server ? CONTROL_HARD_RESET_CLIENT_V2 : CONTROL_HARD_RESET_SERVER_V2;
tls_crypt_recv = c.tls_crypt_context->new_obj_recv();
// static direction assignment - not user configurable
const unsigned int key_dir = server ? OpenVPNStaticKey::NORMAL : OpenVPNStaticKey::INVERSE;
tls_crypt_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir),
c.tls_key.slice(OpenVPNStaticKey::CIPHER | OpenVPNStaticKey::DECRYPT | key_dir));
// needed to create the decrypt buffer during validation
frame = c.frame;
}
bool validate(const BufferAllocated& net_buf)
{
try
{
if (!net_buf.size())
return false;
const unsigned int op = net_buf[0];
if (opcode_extract(op) != reset_op || key_id_extract(op) != 0)
return false;
const size_t data_offset = TLSCryptContext::hmac_offset + tls_crypt_recv->output_hmac_size();
if (net_buf.size() < data_offset)
return false;
frame->prepare(Frame::DECRYPT_WORK, work);
// decrypt payload from 'net_buf' into 'work'
const size_t decrypt_bytes = tls_crypt_recv->decrypt(net_buf.c_data() + TLSCryptContext::hmac_offset,
work.data(), work.max_size(),
net_buf.c_data() + data_offset,
net_buf.size() - data_offset);
if (!decrypt_bytes)
return false;
work.inc_size(decrypt_bytes);
// verify HMAC
return tls_crypt_recv->hmac_cmp(net_buf.c_data(),
TLSCryptContext::hmac_offset,
work.data(), work.size());
}
catch (BufferException&)
{
}
return false;
}
protected:
unsigned int reset_op;
private:
TLSCryptInstance::Ptr tls_crypt_recv;
Frame::Ptr frame;
BufferAllocated work;
};
class TLSCryptV2PreValidate : public TLSCryptPreValidate
{
public:
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_pre_validate);
TLSCryptV2PreValidate(const Config& c, const bool server)
: TLSCryptPreValidate(c, server)
{
if (!c.tls_crypt_v2_enabled())
throw tls_crypt_v2_pre_validate();
// in case of server peer, we expect the new v3 packet type
if (server)
reset_op = CONTROL_HARD_RESET_CLIENT_V3;
}
};
OPENVPN_SIMPLE_EXCEPTION(select_key_context_error);
ProtoContext(const Config::Ptr& config_arg, // configuration
const SessionStats::Ptr& stats_arg) // error stats
: config(config_arg),
stats(stats_arg),
mode_(config_arg->ssl_factory->mode()),
n_key_ids(0),
now_(config_arg->now)
{
const Config& c = *config;
// tls-auth setup
if (c.tls_crypt_v2_enabled())
{
tls_wrap_mode = TLS_CRYPT_V2;
// get HMAC size from Digest object
hmac_size = c.tls_crypt_context->digest_size();
}
else if (c.tls_crypt_enabled())
{
tls_wrap_mode = TLS_CRYPT;
// get HMAC size from Digest object
hmac_size = c.tls_crypt_context->digest_size();
}
else if (c.tls_auth_enabled())
{
tls_wrap_mode = TLS_AUTH;
// get HMAC size from Digest object
hmac_size = c.tls_auth_context->size();
}
else
{
tls_wrap_mode = TLS_PLAIN;
hmac_size = 0;
}
}
uint32_t get_tls_warnings() const
{
if (primary)
return primary->get_tls_warnings();
OPENVPN_LOG("TLS: primary key context uninitialized. Can't retrieve TLS warnings");
return 0;
}
void reset_tls_crypt(const Config& c, const OpenVPNStaticKey& key)
{
tls_crypt_send = c.tls_crypt_context->new_obj_send();
tls_crypt_recv = c.tls_crypt_context->new_obj_recv();
// static direction assignment - not user configurable
unsigned int key_dir = is_server() ?
OpenVPNStaticKey::NORMAL :
OpenVPNStaticKey::INVERSE;
tls_crypt_send->init(key.slice(OpenVPNStaticKey::HMAC |
OpenVPNStaticKey::ENCRYPT | key_dir),
key.slice(OpenVPNStaticKey::CIPHER |
OpenVPNStaticKey::ENCRYPT | key_dir));
tls_crypt_recv->init(key.slice(OpenVPNStaticKey::HMAC |
OpenVPNStaticKey::DECRYPT | key_dir),
key.slice(OpenVPNStaticKey::CIPHER |
OpenVPNStaticKey::DECRYPT | key_dir));
}
void reset_tls_crypt_server(const Config& c)
{
//tls-crypt session key is derived later from WKc received from the client
tls_crypt_send.reset();
tls_crypt_recv.reset();
//server context is used only to process incoming WKc's
tls_crypt_server = c.tls_crypt_context->new_obj_recv();
//the server key is composed by one key set only, therefore direction and
//mode should not be specified when slicing
tls_crypt_server->init(c.tls_key.slice(OpenVPNStaticKey::HMAC),
c.tls_key.slice(OpenVPNStaticKey::CIPHER));
tls_crypt_metadata = c.tls_crypt_metadata_factory->new_obj();
}
void reset()
{
const Config& c = *config;
// defer data channel initialization until after client options pull?
dc_deferred = c.dc_deferred;
// clear key contexts
reset_all();
// start with key ID 0
upcoming_key_id = 0;
unsigned int key_dir;
// tls-auth initialization
switch (tls_wrap_mode)
{
case TLS_CRYPT:
reset_tls_crypt(c, c.tls_key);
// init tls_crypt packet ID
ta_pid_send.init(PacketID::LONG_FORM);
ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
break;
case TLS_CRYPT_V2:
if (is_server())
// setup key to be used to unwrap WKc upon client connection.
// tls-crypt session key setup is postponed to reception of WKc
// from client
reset_tls_crypt_server(c);
else
reset_tls_crypt(c, c.tls_key);
// init tls_crypt packet ID
ta_pid_send.init(PacketID::LONG_FORM);
ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
break;
case TLS_AUTH:
// init OvpnHMACInstance
ta_hmac_send = c.tls_auth_context->new_obj();
ta_hmac_recv = c.tls_auth_context->new_obj();
// init tls_auth hmac
if (c.key_direction >= 0)
{
// key-direction is 0 or 1
key_dir = c.key_direction ? OpenVPNStaticKey::INVERSE : OpenVPNStaticKey::NORMAL;
ta_hmac_send->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::ENCRYPT | key_dir));
ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC | OpenVPNStaticKey::DECRYPT | key_dir));
}
else
{
// key-direction bidirectional mode
ta_hmac_send->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
ta_hmac_recv->init(c.tls_key.slice(OpenVPNStaticKey::HMAC));
}
// init tls_auth packet ID
ta_pid_send.init(PacketID::LONG_FORM);
ta_pid_recv.init(c.pid_mode, PacketID::LONG_FORM, "SSL-CC", 0, stats);
break;
case TLS_PLAIN:
break;
}
// initialize proto session ID
psid_self.randomize(*c.prng);
psid_peer.reset();
// initialize key contexts
primary.reset(new KeyContext(*this, is_client()));
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " New KeyContext PRIMARY id=" << primary->key_id());
// initialize keepalive timers
keepalive_expire = Time::infinite(); // initially disabled
update_last_sent(); // set timer for initial keepalive send
}
void set_protocol(const Protocol& p)
{
config->set_protocol(p);
if (primary)
primary->set_protocol(p);
if (secondary)
secondary->set_protocol(p);
}
// Free up space when parent object has been halted but
// object destruction is not immediately scheduled.
void pre_destroy()
{
reset_all();
}
// Is primary key defined
bool primary_defined()
{
return bool(primary);
}
virtual ~ProtoContext() {}
// return the PacketType of an incoming network packet
PacketType packet_type(const Buffer& buf)
{
return PacketType(buf, *this);
}
// start protocol negotiation
void start()
{
if (!primary)
throw proto_error("start: no primary key");
primary->start();
update_last_received(); // set an upper bound on when we expect a response
}
// trigger a protocol renegotiation
void renegotiate()
{
// initialize secondary key context
new_secondary_key(true);
secondary->start();
}
// Should be called at the end of sequence of send/recv
// operations on underlying protocol object.
// If control_channel is true, do a full flush.
// If control_channel is false, optimize flush for data
// channel only.
void flush(const bool control_channel)
{
if (control_channel || process_events())
{
do {
if (primary)
primary->flush();
if (secondary)
secondary->flush();
} while (process_events());
}
}
// Perform various time-based housekeeping tasks such as retransmiting
// unacknowleged packets as part of the reliability layer and testing
// for keepalive timouts.
// Should be called at the time returned by next_housekeeping.
void housekeeping()
{
// handle control channel retransmissions on primary
if (primary)
primary->retransmit();
// handle control channel retransmissions on secondary
if (secondary)
secondary->retransmit();
// handle possible events
flush(false);
// handle keepalive/expiration
keepalive_housekeeping();
}
// When should we next call housekeeping?
// Will return a time value for immediate execution
// if session has been invalidated.
Time next_housekeeping() const
{
if (!invalidated())
{
Time ret = Time::infinite();
if (primary)
ret.min(primary->next_retransmit());
if (secondary)
ret.min(secondary->next_retransmit());
ret.min(keepalive_xmit);
ret.min(keepalive_expire);
return ret;
}
else
return Time();
}
// send app-level cleartext to remote peer
void control_send(BufferPtr&& app_bp)
{
select_control_send_context().app_send(std::move(app_bp));
}
void control_send(BufferAllocated&& app_buf)
{
control_send(app_buf.move_to_ptr());
}
// validate a control channel network packet
bool control_net_validate(const PacketType& type, const Buffer& net_buf)
{
return type.is_defined() && KeyContext::validate(net_buf, *this, now_);
}
// pass received control channel network packets (ciphertext) into protocol object
bool control_net_recv(const PacketType& type, BufferAllocated&& net_buf)
{
Packet pkt(net_buf.move_to_ptr(), type.opcode);
if (type.is_soft_reset() && !renegotiate_request(pkt))
return false;
return select_key_context(type, true).net_recv(std::move(pkt));
}
bool control_net_recv(const PacketType& type, BufferPtr&& net_bp)
{
Packet pkt(std::move(net_bp), type.opcode);
if (type.is_soft_reset() && !renegotiate_request(pkt))
return false;
return select_key_context(type, true).net_recv(std::move(pkt));
}
// 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());
if (!primary)
throw proto_error("data_encrypt: no primary key");
primary->encrypt(in_out);
}
// decrypt a data channel packet (automatically select primary
// or secondary KeyContext based on packet content)
bool data_decrypt(const PacketType& type, BufferAllocated& in_out)
{
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
if (in_out.size())
{
update_last_received();
ret = true;
}
// discard keepalive packets
if (proto_context_private::is_keepalive(in_out))
{
in_out.reset_size();
}
return ret;
}
// enter disconnected state
void disconnect(const Error::Type reason)
{
if (primary)
primary->invalidate(reason);
if (secondary)
secondary->invalidate(reason);
}
// normally used by UDP clients to tell the server that
// they are disconnecting
void send_explicit_exit_notify()
{
if (is_client() && is_udp() && primary)
primary->send_explicit_exit_notify();
}
// should be called after a successful network packet transmit
void update_last_sent()
{
keepalive_xmit = *now_ + config->keepalive_ping;
}
// can we call data_encrypt or data_decrypt yet?
bool data_channel_ready() const { return primary && primary->data_channel_ready(); }
// total number of SSL/TLS negotiations during lifetime of ProtoContext object
unsigned int negotiations() const { return n_key_ids; }
// worst-case handshake time
const Time::Duration& slowest_handshake() { return slowest_handshake_; }
// was primary context invalidated by an exception?
bool invalidated() const { return primary && primary->invalidated(); }
// reason for invalidation if invalidated() above returns true
Error::Type invalidation_reason() const { return primary->invalidation_reason(); }
// Do late initialization of data channel, for example
// on client after server push, or on server after client
// capabilities are known.
void init_data_channel()
{
dc_deferred = false;
// initialize data channel (crypto & compression)
if (primary)
primary->init_data_channel();
if (secondary)
secondary->init_data_channel();
}
// Call on client with server-pushed options
void process_push(const OptionList& opt, const ProtoContextOptions& pco)
{
// modify config with pushed options
config->process_push(opt, pco);
// in case keepalive parms were modified by push
keepalive_parms_modified();
}
// Return the current transport alignment adjustment
size_t align_adjust_hint() const
{
return config->enable_op32 ? 0 : 1;
}
// Return true if keepalive parameter(s) are enabled
bool is_keepalive_enabled() const
{
return config->keepalive_ping.enabled()
|| config->keepalive_timeout.enabled();
}
// Disable keepalive for rest of session,
// but return the previous keepalive parameters.
void disable_keepalive(unsigned int& keepalive_ping,
unsigned int& keepalive_timeout)
{
keepalive_ping = config->keepalive_ping.enabled() ? config->keepalive_ping.to_seconds() : 0;
keepalive_timeout = config->keepalive_timeout.enabled() ? config->keepalive_timeout.to_seconds() : 0;
config->keepalive_ping = Time::Duration::infinite();
config->keepalive_timeout = Time::Duration::infinite();
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 (primary && 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()
{
return config->dc;
}
// reset the data channel factory
void reset_dc_factory()
{
config->dc.reset();
}
// set the local peer ID (or -1 to disable)
void set_local_peer_id(const int local_peer_id)
{
config->local_peer_id = local_peer_id;
}
// current time
const Time& now() const { return *now_; }
void update_now() { now_->update(); }
// frame
const Frame& frame() const { return *config->frame; }
const Frame::Ptr& frameptr() const { return config->frame; }
// client or server?
const Mode& mode() const { return mode_; }
bool is_server() const { return mode_.is_server(); }
bool is_client() const { return mode_.is_client(); }
// tcp/udp mode
bool is_tcp() { return config->protocol.is_tcp(); }
bool is_udp() { return config->protocol.is_udp(); }
// configuration
const Config& conf() const { return *config; }
Config& conf() { return *config; }
Config::Ptr conf_ptr() const { return config; }
// stats
SessionStats& stat() const { return *stats; }
private:
// TLS wrapping mode for the control channel
enum TLSWrapMode {
TLS_PLAIN,
TLS_AUTH,
TLS_CRYPT,
TLS_CRYPT_V2
};
void reset_all()
{
if (primary)
primary->rekey(CryptoDCInstance::DEACTIVATE_ALL);
primary.reset();
secondary.reset();
}
virtual void control_net_send(const Buffer& net_buf) = 0;
// app may take ownership of app_bp via std::move
virtual void control_recv(BufferPtr&& app_bp) = 0;
// Called on client to request username/password credentials.
// Should be overriden by derived class if credentials are required.
// username and password should be written into buf with write_auth_string().
virtual void client_auth(Buffer& buf)
{
write_empty_string(buf); // username
write_empty_string(buf); // password
}
// Called on server with credentials and peer info provided by client.
// Should be overriden by derived class if credentials are required.
virtual void server_auth(const std::string& username,
const SafeString& password,
const std::string& peer_info,
const AuthCert::Ptr& auth_cert)
{
}
// Called when initial KeyContext transitions to ACTIVE state
virtual void active()
{
}
void update_last_received()
{
keepalive_expire = *now_ + config->keepalive_timeout;
}
void net_send(const unsigned int key_id, const Packet& net_pkt)
{
control_net_send(net_pkt.buffer());
}
void app_recv(const unsigned int key_id, BufferPtr&& to_app_buf)
{
control_recv(std::move(to_app_buf));
}
// we're getting a request from peer to renegotiate.
bool renegotiate_request(Packet& pkt)
{
if (KeyContext::validate(pkt.buffer(), *this, now_))
{
new_secondary_key(false);
return true;
}
else
return false;
}
// select a KeyContext (primary or secondary) for received network packets
KeyContext& select_key_context(const PacketType& type, const bool control)
{
const unsigned int flags = type.flags & (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL);
if (!control)
{
if (flags == (PacketType::DEFINED) && primary)
return *primary;
else if (flags == (PacketType::DEFINED|PacketType::SECONDARY) && secondary)
return *secondary;
}
else
{
if (flags == (PacketType::DEFINED|PacketType::CONTROL) && primary)
return *primary;
else if (flags == (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL) && secondary)
return *secondary;
}
throw select_key_context_error();
}
// Select a KeyContext (primary or secondary) for control channel sends.
// Even after new key context goes active, we still wait for
// 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()
{
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " CONTROL SEND");
if (!primary)
throw proto_error("select_control_send_context: no primary key");
return *primary;
}
// Possibly send a keepalive message, and check for expiration
// of session due to lack of received packets from peer.
void keepalive_housekeeping()
{
const Time now = *now_;
// check for keepalive timeouts
if (now >= keepalive_xmit && primary)
{
primary->send_keepalive();
update_last_sent();
}
if (now >= keepalive_expire)
{
// no contact with peer, disconnect
stats->error(Error::KEEPALIVE_TIMEOUT);
disconnect(Error::KEEPALIVE_TIMEOUT);
}
}
// Process KEV_x events
// Return true if any events were processed.
bool process_events()
{
bool did_work = false;
// primary
if (primary && primary->event_pending())
{
process_primary_event();
did_work = true;
}
// secondary
if (secondary && secondary->event_pending())
{
process_secondary_event();
did_work = true;
}
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.
void promote_secondary_to_primary()
{
primary.swap(secondary);
if (primary)
primary->rekey(CryptoDCInstance::PRIMARY_SECONDARY_SWAP);
if (secondary)
secondary->prepare_expire();
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " PRIMARY_SECONDARY_SWAP");
}
void process_primary_event()
{
const KeyContext::EventType ev = primary->get_event();
if (ev != KeyContext::KEV_NONE)
{
primary->reset_event();
switch (ev)
{
case KeyContext::KEV_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:
if (secondary && !secondary->invalidated())
promote_secondary_to_primary();
else
{
stats->error(Error::PRIMARY_EXPIRE);
disconnect(Error::PRIMARY_EXPIRE); // primary context expired and no secondary context available
}
break;
case KeyContext::KEV_NEGOTIATE:
stats->error(Error::HANDSHAKE_TIMEOUT);
disconnect(Error::HANDSHAKE_TIMEOUT); // primary negotiation failed
break;
default:
break;
}
}
primary->set_next_event_if_unspecified();
}
void process_secondary_event()
{
const KeyContext::EventType ev = secondary->get_event();
if (ev != KeyContext::KEV_NONE)
{
secondary->reset_event();
switch (ev)
{
case KeyContext::KEV_ACTIVE:
secondary->rekey(CryptoDCInstance::NEW_SECONDARY);
if (primary)
primary->prepare_expire();
break;
case KeyContext::KEV_BECOME_PRIMARY:
if (!secondary->invalidated())
promote_secondary_to_primary();
break;
case KeyContext::KEV_EXPIRE:
secondary->rekey(CryptoDCInstance::DEACTIVATE_SECONDARY);
secondary.reset();
break;
case KeyContext::KEV_RENEGOTIATE_QUEUE:
if (primary)
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();
}
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;
}
// 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()
{
++n_key_ids;
unsigned int ret = upcoming_key_id;
if ((upcoming_key_id = (upcoming_key_id + 1) & KEY_ID_MASK) == 0)
upcoming_key_id = 1;
return ret;
}
// call whenever keepalive parms are modified,
// to reset timers
void keepalive_parms_modified()
{
update_last_received();
// For keepalive_xmit timer, don't reschedule current cycle
// unless it would fire earlier. Subsequent cycles will
// time according to new keepalive_ping value.
const Time kx = *now_ + config->keepalive_ping;
if (kx < keepalive_xmit)
keepalive_xmit = kx;
}
void tls_crypt_append_wkc(BufferAllocated& dst)
{
if (!config->wkc.defined())
throw proto_error("Client Key Wrapper undefined");
dst.append(config->wkc);
}
// BEGIN ProtoContext data members
Config::Ptr config;
SessionStats::Ptr stats;
size_t hmac_size;
TLSWrapMode tls_wrap_mode;
Mode mode_; // client or server
unsigned int upcoming_key_id;
unsigned int n_key_ids;
TimePtr now_; // pointer to current time (a clone of config->now)
Time keepalive_xmit; // time in future when we will transmit a keepalive (subject to continuous change)
Time keepalive_expire; // time in future when we must have received a packet from peer or we will timeout session
Time::Duration slowest_handshake_; // longest time to reach a successful handshake
OvpnHMACInstance::Ptr ta_hmac_send;
OvpnHMACInstance::Ptr ta_hmac_recv;
TLSCryptInstance::Ptr tls_crypt_send;
TLSCryptInstance::Ptr tls_crypt_recv;
TLSCryptInstance::Ptr tls_crypt_server;
TLSCryptMetadata::Ptr tls_crypt_metadata;
PacketIDSend ta_pid_send;
PacketIDReceive ta_pid_recv;
ProtoSessionID psid_self;
ProtoSessionID psid_peer;
KeyContext::Ptr primary;
KeyContext::Ptr secondary;
bool dc_deferred;
// END ProtoContext data members
};
} // namespace openvpn
#endif //OPENVPN_SSL_PROTO_H