mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-04-24 00:00:05 +08:00
5edb23a7ab
275cf80efb mac/tuncli: Don't take address of temporary error. 1406187bfc tun/win/tunutil: Don't auto& a temporary iterator. fe7f984c5d ip/ping6: Use _WIN32, not _MSC_VER (to fix MinGW). 03a906771e win: add OpenSSL as solution configuration 89cc11b300 win: enable building Windows client with OpenSSL febb24e7d9 openssl/compat.hpp: remove functions already defined in OpenSSL 0833eb1f76 linux/tunsetup: Fix missing asio/errinfo declaration d54b742910 linux: Improve cpu_time() using glibc/kernel methods a55fe2b554 tests: Added unit test for linux/cputime.hpp e33a00e6de [OVPN3-431] agent: Wintun support for agent 42592eb1b1 appveyor: initial commit 3e3f2078e6 win: rename env var in project file a2496a3616 Wintun: experimental support 58a7866b45 build script: added OPENSSL_DIST parameter to specify a custom OpenSSL build 288ea0277e OpenSSLContext: SSL_CTX_set_ecdh_auto() becomes a no-op in OpenSSL 1.1, so #ifdef out to avoid compiler warnings 3ef5059fa6 TLSSessionTicketBase: removed the ERROR symbol from a local enum in case it conflicts with a global preprocessor symbol 3364ed76b8 TLSSessionTicketBase: removed trailing comma from Status enum 025c7bad88 mbedtls/sslctx: Fix missing override in virtual methods 6cb3243681 mbedTLS: ssl() method accepting hostname should check if it is null ca31da7d28 bio_memq_stream.hpp: fixed multi-thread race (introduced with OpenSSL 1.1 support) using init_static() approach 2deb402223 OpenSSLContext::tls_ticket_key_callback: get self with SSL_get_ex_data instead of ssl->ctx->app_verify_arg eec139a100 MSF::find: renamed template type names to avoid conflict with preprocessor symbol (ITER) in test/ssl/proto.cpp 1024d37f33 str_neq: fixed bug where neq was not initialized c00b6f6302 Listen::List: refactored and extended expand_ports() 448c549a0b cpu_time(): added bool thread parameter to return CPU time of current thread (instead of process) 868801d7d9 Linux library: added cpu_time() method to return the CPU time of the current process 964d2cd428 SSL layer: added did_full_handshake() method and implemented for OpenSSL dd18d6c806 crypto::str_neq: use atomic_thread_fence(std::memory_order_acq_rel) instead of OPENVPN_COMPILER_FENCE 6a30af9528 OpenSSLSessionCache: use map instead of unordered_map 3ecbcbc81b OptionList: fixed compile errors that occur when get_num<T>() is used with a const type 72e9f858e4 SSL: added SSLConst::PEER_CERT_OPTIONAL flag and implemented for OpenSSL 33f15c8840 OpenSSL: use OPENSSL_VERSION_NUMBER instead of SSLEAY_VERSION_NUMBER cadb712ea9 ProfileMerge: added "static-key" to is_fileref_directive() 85befa316a TLS session tickets: work around an issue in OpenSSL session ticket keying callback f43c4c1440 TLSSessionTicketBase: misc fixes/enhancements c5f4d59d39 OpenSSLContext: added missing X509_free() to rebuild_authcert() 658fcc50eb OptionList: added get_num methods with min/max but no default 162eeaa485 SSL layer: added RFC 5077 TLS session resumption ticket support e0a821ddd6 OpenSSLContext: use C++11 member initializers 1ea5acce3c OpenSSLContext: minor changes to handshake_details() 74c0a4f995 string: added copy_fill() method 3e5921c06d AuthCert: added is_uninitialized() method 3d6b6b2319 library: added convenience method MSF::find() for maps/sets 18f5f4d1b5 SSLConfigAPI: remove set_enable_renegotiation() 18dcfd616c Added crypto::str_neq() function for securely comparing variable-length strings 4fc5725b9e RunContext: added get_servers() method ae22f155fd server: determine when server-side session ID should be preserved on soon-to-be-closed connections 5e34759d50 client: HALT/RESTART message was not properly purging the Session ID when required e1647eb407 Fix builds with GCC 4.8 compilers b55f78dd1d test_sitnl.cpp: account for old iptools output 236d39258b Allow overriding DEP_DIR by environment variable d56e049ea4 Refactor dependencies to be in a cmake script e9dc75ec90 sitnl: add unit tests faad8454be sitnl: pick the best gw by longest prefix and lowest metric dfcc4bc437 [OVPN3-354] cli.cpp: support for round-robin DNS and redirect gw 8a502f3b61 [OVPN3-354] tun linux: support for round-robin DNS and redirect gw c9315c7dc1 gwnetlink.hpp: specify destination when looking for gateway 89f091daf0 sitnl: implement interface filtering when looking for gateway 220de072a2 sitnl: support for multipart messages 5771dfc0ee transport: remove ip_hole_punch API d448b4a7db tun/builder/client.hpp: use "override" method specifier d85e92621d Make reproducible builds possible 7150f72e09 tun: remove code duplications in Linux tun implementations 8112f0cd7c [OVPN3-378] cli: support for TunBuilder API 6f0e9f6388 Fix Asio 0003 patch. 964662bacb Add /bigobj to build.py 74e40a8907 Upgrade ASIO to 0.13.0 a2713ce1f6 PureTLS: enable SNI by default when configuring client 19a44dbdda Merge branch 'qa' a5fdf43726 InitProcess: comment clarification that crypto_init declaration causes SSL library init when instantiated dec3bc140e OpenSSL: Revert a commit that breaks OpenSSL initialization 16a4e3d4a7 [OVPN3-405] asio: A quick fix for incorrect error message encoding aa785c30c1 Fix Base64::UCharWrap compiler warnings 51a1469e6b Merge various fixes 218cfa39cb Explicitly disable TAP support when parsing configurations 3a0e768ecd Explicitly disable any potential TAP support aba98471fc Fix base64 unit test with mbedtls and windows 9f84174f0b Add unit tests for Base64 017bc545ce Add base64 decode for void* data 452a353b2d Fix lzo build script to use it as dependency for the unit tests dfdd528dc1 Convert unit test to Googletest bd9ee482e6 Add copyright header to test_comp 059f20f2b2 Move compression unit test from common to core repository 5a024cde5c Added Snappy corpus for testing compression/decompression. ec4d400933 Add compatibility functions for OpenSSL 1.1.0 9768562a01 OpenSSL 1.1: Add argument to external sign to specify algorithm 1bbd2cc78c OpenSSL 1.1: Replace RSA_F_RSA_EAY_PRIVATE_ENCRYPT with Openssl variant c959a3cff0 OpenSSL 1.1: Replace remaining direct access to members 4307f024ca OpenSSL 1.1: And missing remaining compat implementations 3385c45151 OpenSSL 1.1: Use opaque pointer for HMAC_CTX f29453f4ca OpenSSL 1.1: Add compat includes for HMAC c107a1f6ab OpenSSL 1.1: Remove support for OpenSSL older than 1.0.0 024a10adc2 OpenSSL 1.1: Use EVP_MD_ctx as opaque pointer 35d82906c4 OpenSSL 1.1: Change EVP_CIPHER ctx field to pointer ebf4b7e87d OpenSSL 1.1: Use X509_digest to get certificate digest 7d3e5d02f2 OpenSSL 1.1: Use SSL_get_ex_data instead of direct access 8717f822ca OpenSSL 1.1: Replace ctx->current with X509_STORE_CTX_get_current_cert 67fbe1ab3f OpenSSL 1.1: Use X509_check_purpose to check certificate types 7b5a92d58e OpenSSL 1.1: Change OpenSSL TLS version logic to match mbed TLS c28b7d1893 OpenSSL 1.1: Adjust default OpenSSL cipher suites f108044a09 OpenSSL 1.1: Add defines for TLS 1.3 in tlsver.hpp ee1308b505 OpenSSL 1.1: Replace initialisation of RSA_meth with access method 905d681af1 OpenSSL 1.1: Use standard tls methods cf28e4600c OpenSSL 1.1: Change BIO wrappers around to use access methods 5e6571163d OpenSSL 1.1: Implement compat methods for new BIO methods in 1.0.2 8837539a73 Use std::nothrow as argument for new e6ec025932 Merge branch 'qa' 752a38c067 [OVPN3-397] size.hpp: wrap typedef in guards d4e50f8c54 Merge branch 'qa' d8d14e1991 [UCONNECT-1027] implement ResolveThread and ensure it is properly detachable 525a9a88a6 Merge branch qa 30ea53cb92 Replace custom memcpy implementation de7c672ee7 Workaround for compiler bug in memneq 84fcecd5e7 Fix missing override annotation in udp/tcp/httpcli 1a3a69a496 [UCONNECT-1027] use one AsioWork object for the whole pre-resolve opertation c4cbf93f9b Revert "[UCONNECT-1027] remotelist: create standalone object for resolve thread" 6ef089164e Allow unit tests to be also compiled with mbed TLS and on Windows 7c67bf7f50 Add unit tests for route emulation and establish common test suite 64a7b2f124 Add build file for core unit tests 0a0d080a49 Implement allowing local LAN access 2105b4b7c0 Fix Android route exclusion emulation git-subtree-dir: Sources/OpenVPNAdapter/Libraries/Vendors/openvpn git-subtree-split: 275cf80efb7a08adc920f7ca49075c776e596b08
3845 lines
110 KiB
C++
3845 lines
110 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;
|
|
|
|
// GUI version, passed to server as IV_GUI_VER
|
|
std::string gui_version;
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
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;
|
|
|
|
if (!gui_version.empty())
|
|
out << "IV_GUI_VER=" << gui_version << '\n';
|
|
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
|
|
const bool is_tcp() { return config->protocol.is_tcp(); }
|
|
const 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
|