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

Implement data v3 features for AEAD tag at the end and 64 bit packet counter

Split the implementation of the packet counter for normal packet ID
that includes the "weird" long format for long 64 bit packet ids used
in tls-auth and tls-crypt and a simplified implementation for AEAD that
only does 32 bit and 64 bit flat counters.

Signed-off-by: Arne Schwabe <arne@openvpn.net>
This commit is contained in:
Arne Schwabe 2024-01-29 09:34:06 +01:00 committed by Jenkins-dev
parent a384f16b32
commit ca91f3e91c
15 changed files with 704 additions and 67 deletions

View File

@ -1359,6 +1359,11 @@ class Session : ProtoContextCallbackInterface,
notify_callback->client_proto_renegotiated();
}
bool supports_proto_v3() override
{
return tun_factory->supports_proto_v3();
}
void housekeeping_callback(const openvpn_io::error_code &e)
{
try

View File

@ -32,7 +32,7 @@
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/frame/frame.hpp>
#include <openvpn/crypto/static_key.hpp>
#include <openvpn/crypto/packet_id.hpp>
#include <openvpn/crypto/packet_id_aead.hpp>
#include <openvpn/log/sessionstats.hpp>
#include <openvpn/crypto/cryptodc.hpp>
@ -62,86 +62,113 @@ class Crypto : public CryptoDCInstance
std::memset(data, 0, sizeof(data));
}
// setup
void set_tail(const StaticKey &sk)
/**
* Sets the IV tail for AEAD operations
*
* The IV for AEAD ciphers (both AES-GCM and Chacha20-Poly1305) consists of 96 bits/12 bytes
* (It then gets concatenated with internal 32 bits for block counter to form a 128 bit counter for the
* encryption).
*
* Since we only use 4 bytes (32 bit packet ID) or 8 bytes (64 bit packet ID) on the wire, we
* fill out the rest of the IV with pseudorandom bytes that come from of the negotiated key for the
* HMAC key (this key is not used by AEAD ciphers, so we reuse it for this purpose in AEAD mode).
*/
void set_tail(const StaticKey &sk, bool use64bitcounter)
{
if (sk.size() < 8)
size_t implicit_iv_len = use64bitcounter ? 4 : 8;
if (sk.size() < implicit_iv_len)
throw aead_error("insufficient key material for nonce tail");
std::memcpy(data + 8, sk.data(), 8);
/* 4 bytes opcode + 4-8 bytes on wire IV */
size_t implicit_iv_offset = data_offset_pkt_id + (12 - implicit_iv_len);
std::memcpy(data + implicit_iv_offset, sk.data(), implicit_iv_len);
}
// for encrypt
Nonce(const Nonce &ref, PacketIDSend &pid_send, const PacketID::time_t now, const unsigned char *op32)
Nonce(const Nonce &ref, PacketIDAEADSend &pid_send, const unsigned char *op32)
{
/** Copy op code and tail of packet ID */
std::memcpy(data, ref.data, sizeof(data));
Buffer buf(data + 4, 4, false);
pid_send.write_next(buf, false, now);
Buffer buf(data + data_offset_pkt_id, PacketIDAEAD::long_id_size, false);
pid_send.write_next(buf);
if (op32)
{
ad_op32 = true;
std::memcpy(data, op32, 4);
std::memcpy(data, op32, op32_size);
}
else
ad_op32 = false;
}
// for encrypt
void prepend_ad(Buffer &buf) const
void prepend_ad(Buffer &buf, const PacketIDAEADSend &pid_send) const
{
buf.prepend(data + 4, 4);
buf.prepend(data + data_offset_pkt_id, pid_send.length());
}
// for decrypt
Nonce(const Nonce &ref, Buffer &buf, const unsigned char *op32)
Nonce(const Nonce &ref, const PacketIDAEADReceive &recv_pid, Buffer &buf, const unsigned char *op32)
{
/* Copy opcode and tail of packet ID */
std::memcpy(data, ref.data, sizeof(data));
buf.read(data + 4, 4);
/* copy dynamic packet of IV into */
buf.read(data + data_offset_pkt_id, recv_pid.length());
if (op32)
{
ad_op32 = true;
std::memcpy(data, op32, 4);
std::memcpy(data, op32, op32_size);
}
else
ad_op32 = false;
}
// for decrypt
bool verify_packet_id(PacketIDReceive &pid_recv, const PacketID::time_t now)
bool verify_packet_id(PacketIDAEADReceive &pid_recv, const PacketID::time_t now)
{
Buffer buf(data + 4, 4, true);
const PacketID pid = pid_recv.read_next(buf);
return pid_recv.test_add(pid, now, true); // verify packet ID
Buffer buf(data + data_offset_pkt_id, PacketIDAEAD::long_id_size, true);
const PacketIDAEAD pid = pid_recv.read_next(buf);
return pid_recv.test_add(pid, now); // verify packet ID
}
const unsigned char *iv() const
{
return data + 4;
return data + data_offset_pkt_id;
}
const unsigned char *ad() const
{
return ad_op32 ? data : data + 4;
return ad_op32 ? data : data + data_offset_pkt_id;
}
size_t ad_len() const
size_t ad_len(const PacketIDAEADSend &pid_send) const
{
return ad_op32 ? 8 : 4;
return (ad_op32 ? op32_size : 0) + pid_send.length();
}
size_t ad_len(const PacketIDAEADReceive &pid_recv) const
{
return (ad_op32 ? op32_size : 0) + pid_recv.length();
}
private:
bool ad_op32; // true if AD includes op32 opcode
bool ad_op32; // true if AD (authenticated data) includes op32 opcode
// Sample data:
// [ OP32 (optional) ] [ pkt ID ] [ nonce tail ]
// [ 48 00 00 01 ] [ 00 00 00 05 ] [ 7f 45 64 db 33 5b 6c 29 ]
unsigned char data[16];
static constexpr std::size_t data_offset_pkt_id = 4;
static constexpr std::size_t op32_size = 4;
};
struct Encrypt
{
typename CRYPTO_API::CipherContextAEAD impl;
Nonce nonce;
PacketIDSend pid_send;
PacketIDAEADSend pid_send{false};
BufferAllocated work;
};
@ -149,7 +176,7 @@ class Crypto : public CryptoDCInstance
{
typename CRYPTO_API::CipherContextAEAD impl;
Nonce nonce;
PacketIDReceive pid_recv;
PacketIDAEADReceive pid_recv{};
BufferAllocated work;
};
@ -176,32 +203,42 @@ class Crypto : public CryptoDCInstance
if (buf.size())
{
// build nonce/IV/AD
Nonce nonce(e.nonce, e.pid_send, now, op32);
Nonce nonce(e.nonce, e.pid_send, op32);
// encrypt to work buf
frame->prepare(Frame::ENCRYPT_WORK, e.work);
if (e.work.max_size() < buf.size())
throw aead_error("encrypt work buffer too small");
// alloc auth tag in buffer
unsigned char *auth_tag = e.work.prepend_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
unsigned char *auth_tag_end;
// prepare output buffer
unsigned char *work_data = e.work.write_alloc(buf.size());
if (e.impl.requires_authtag_at_end())
unsigned char *auth_tag;
unsigned char *auth_tag_tmp = nullptr;
// alloc auth tag in buffer where it needs to be
// Create a temporary auth tag at the end if the implementation and mode require it
if (dc_settings.aeadTagAtTheEnd())
{
auth_tag_end = e.work.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
auth_tag = e.work.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
else
{
auth_tag = e.work.prepend_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
if (e.impl.requires_authtag_at_end())
{
auth_tag_tmp = e.work.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
}
// encrypt
e.impl.encrypt(buf.data(), work_data, buf.size(), nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len());
e.impl.encrypt(buf.data(), work_data, buf.size(), nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len(e.pid_send));
if (e.impl.requires_authtag_at_end())
if (auth_tag_tmp)
{
/* move the auth tag to the front */
std::memcpy(auth_tag, auth_tag_end, CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
std::memcpy(auth_tag, auth_tag_tmp, CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
/* Ignore the auth tag at the end */
e.work.inc_size(-CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
@ -209,7 +246,7 @@ class Crypto : public CryptoDCInstance
buf.swap(e.work);
// prepend additional data
nonce.prepend_ad(buf);
nonce.prepend_ad(buf, e.pid_send);
}
return e.pid_send.wrap_warning();
}
@ -220,29 +257,37 @@ class Crypto : public CryptoDCInstance
if (buf.size())
{
// get nonce/IV/AD
Nonce nonce(d.nonce, buf, op32);
Nonce nonce(d.nonce, d.pid_recv, buf, op32);
// get auth tag
unsigned char *auth_tag = buf.read_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
// get auth tag if it is at the front. If the auth tag is at the end
// the decrypt function will just treat it as part of the input
unsigned char *auth_tag = nullptr;
// initialize work buffer
if (!dc_settings.aeadTagAtTheEnd())
{
auth_tag = buf.read_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
// initialize work buffer.
frame->prepare(Frame::DECRYPT_WORK, d.work);
if (d.work.max_size() < buf.size())
throw aead_error("decrypt work buffer too small");
if (e.impl.requires_authtag_at_end())
if (auth_tag && e.impl.requires_authtag_at_end())
{
unsigned char *auth_tag_end = buf.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
std::memcpy(auth_tag_end, auth_tag, CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
auth_tag = nullptr;
}
// decrypt from buf -> work
if (!d.impl.decrypt(buf.c_data(), d.work.data(), buf.size(), nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len()))
if (!d.impl.decrypt(buf.c_data(), d.work.data(), buf.size(), nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len(d.pid_recv)))
{
buf.reset_size();
return Error::DECRYPT_ERROR;
}
if (e.impl.requires_authtag_at_end())
if (dc_settings.aeadTagAtTheEnd() || e.impl.requires_authtag_at_end())
{
d.work.set_size(buf.size() - CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
@ -284,8 +329,8 @@ class Crypto : public CryptoDCInstance
void init_hmac(StaticKey &&encrypt_key,
StaticKey &&decrypt_key) override
{
e.nonce.set_tail(encrypt_key);
d.nonce.set_tail(decrypt_key);
e.nonce.set_tail(encrypt_key, dc_settings.use64bitPktCounter());
d.nonce.set_tail(decrypt_key, dc_settings.use64bitPktCounter());
}
void init_pid(const int recv_mode,
@ -293,8 +338,8 @@ class Crypto : public CryptoDCInstance
const int recv_unit,
const SessionStats::Ptr &recv_stats_arg) override
{
e.pid_send.init(PacketID::SHORT_FORM);
d.pid_recv.init(recv_mode, PacketID::SHORT_FORM, recv_name, recv_unit, recv_stats_arg);
e.pid_send = PacketIDAEADSend{dc_settings.use64bitPktCounter()};
d.pid_recv.init(recv_name, recv_unit, dc_settings.use64bitPktCounter(), recv_stats_arg);
}
// Indicate whether or not cipher/digest is defined

View File

@ -122,6 +122,16 @@ class CryptoDCSettingsData
digest_ = digest;
}
void set_aead_tag_end(bool at_the_end)
{
aead_tag_at_the_end = at_the_end;
}
void set_64_bit_packet_id(bool use_64bit_packet_id)
{
pktcounter_64bit = use_64bit_packet_id;
}
CryptoAlgs::Type cipher() const
{
return cipher_;
@ -139,6 +149,16 @@ class CryptoDCSettingsData
return (CryptoAlgs::use_cipher_digest(cipher_) ? digest_ : CryptoAlgs::NONE);
}
bool use64bitPktCounter() const
{
return pktcounter_64bit;
}
bool aeadTagAtTheEnd() const
{
return aead_tag_at_the_end;
}
void set_key_derivation(CryptoAlgs::KeyDerivation method)
{
key_derivation_ = method;
@ -154,6 +174,8 @@ class CryptoDCSettingsData
CryptoAlgs::Type cipher_ = CryptoAlgs::NONE;
CryptoAlgs::Type digest_ = CryptoAlgs::NONE;
CryptoAlgs::KeyDerivation key_derivation_ = CryptoAlgs::KeyDerivation::OPENVPN_PRF;
bool pktcounter_64bit = false;
bool aead_tag_at_the_end = false;
};
// Factory for CryptoDCInstance objects
@ -221,6 +243,24 @@ class CryptoDCSettings : public CryptoDCSettingsData
}
}
void set_aead_tag_end(bool at_the_end)
{
if (at_the_end != aeadTagAtTheEnd())
{
CryptoDCSettingsData::set_aead_tag_end(at_the_end);
dirty = true;
}
}
void set_64_bit_packet_id(bool use_64bit_packet_id)
{
if (use_64bit_packet_id != use64bitPktCounter())
{
CryptoDCSettingsData::set_64_bit_packet_id(use_64bit_packet_id);
dirty = true;
}
}
CryptoDCContext &context()
{
if (!context_ || dirty)

View File

@ -0,0 +1,413 @@
// 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- 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/>.
// Manage OpenVPN protocol Packet IDs for packet replay detection
#pragma once
#include <algorithm>
#include <string>
#include <cstring>
#include <sstream>
#include <cstdint> // for std::uint32_t
#include <openvpn/io/io.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/circ_list.hpp>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/common/endian64.hpp>
#include <openvpn/error/error.hpp>
#include <openvpn/time/time.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/log/sessionstats.hpp>
namespace openvpn {
/**
* Communicate packet-id over the wire for AEAD
* A short packet-id is just a 32 bit sequence number. A long packet-id is a
* 64 bit sequence number. This sequence number is reused for AEAD IV.
*
* This data structure is always sent over the net in network byte order,
*
* This class is different from PacketID in the way that it always uses
* a "flat" packet id that is either 32 or 64 bit while PacketID has a long
* packet id that is 32bit + 32bit but follow different rules and includes
* a timestamp. Merging PacketIDAEAD and PacketID would result in a much
* more convoluted and hard to understand class than keeping them seperate
*
*/
struct PacketIDAEAD
{
typedef std::uint64_t aead_id_t;
aead_id_t id = 0; // legal values are 1 through 2^64-1
bool wide = false;
/**
* Returns the size of the packet id. This is either 4 or 8 depending on the mode in use
* @return 4 or 8
*/
[[nodiscard]] constexpr std::size_t size() const
{
return size(wide);
}
static constexpr size_t size(bool wide)
{
if (wide)
return long_id_size;
else
return short_id_size;
}
explicit PacketIDAEAD(bool wide_arg)
: wide(wide_arg)
{
}
explicit PacketIDAEAD(bool wide_arg, aead_id_t id_arg)
: id(id_arg), wide(wide_arg)
{
}
constexpr static std::size_t short_id_size = sizeof(std::uint32_t);
constexpr static std::size_t long_id_size = sizeof(std::uint64_t);
bool is_valid() const
{
return id != 0;
}
void reset()
{
id = aead_id_t(0);
}
/**
* Reads the packet id from the specified buffer.
* @param buf the buffer to read the packet id from
*/
void read(ConstBuffer &buf)
{
if (wide)
{
std::uint64_t net_id;
buf.read(reinterpret_cast<unsigned char *>(&net_id), sizeof(net_id));
id = Endian::rev64(net_id);
}
else
{
std::uint32_t net_id;
buf.read(reinterpret_cast<unsigned char *>(&net_id), sizeof(net_id));
id = ntohl(net_id);
}
}
/** Writes the packet id to a buffer */
void write(Buffer &buf) const
{
if (wide)
{
const std::uint64_t net_id = Endian::rev64(id);
buf.write(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
}
else
{
const std::uint32_t net_id = htonl(static_cast<std::uint32_t>(id));
buf.write(reinterpret_cast<const unsigned char *>(&net_id), sizeof(net_id));
}
}
std::string str() const
{
std::ostringstream os;
os << std::hex << "[0x" << id << "]";
return os.str();
}
};
class PacketIDAEADSend
{
public:
OPENVPN_SIMPLE_EXCEPTION(packet_id_wrap);
PacketIDAEADSend(bool wide_arg)
: pid_(wide_arg)
{
}
/**
* Increment the packet ID and return the next packet id to use.
* @throws packet_id_wrap if the packet id space is exhausted
* @return packet id to use next.
*/
PacketIDAEAD next()
{
++pid_.id;
PacketIDAEAD ret{pid_.wide, pid_.id};
if (!pid_.wide && unlikely(pid_.id == std::numeric_limits<std::uint32_t>::max())) // wraparound
{
throw packet_id_wrap();
}
else if (unlikely(pid_.id == std::numeric_limits<decltype(pid_.id)>::max()))
{
throw packet_id_wrap();
}
return ret;
}
/**
* increases the packet id and writes it to a buffer
* @param buf buffer to write to
*/
void write_next(Buffer &buf)
{
const PacketIDAEAD pid = next();
pid.write(buf);
}
/**
* When a VPN runs in TLS mode (the only mode that OpenVPN supports,
* there is no --secret mode anymore), it needs to be warned about wrapping to
* start thinking about triggering a new SSL/TLS handshake.
* This method can be called to see if that level has been reached.
*/
bool wrap_warning() const
{
if (pid_.wide)
return false;
const PacketIDAEAD::aead_id_t wrap_at = 0xFF000000;
return pid_.id >= wrap_at;
}
std::string str() const
{
std::string ret;
ret = pid_.str();
if (pid_.wide)
ret += 'L';
return ret;
}
/**
* Returns the size of the packet id. This is either 4 or 8 depending on the mode in use
* @return 4 or 8
*/
[[nodiscard]] constexpr std::size_t length() const
{
return pid_.size();
}
private:
PacketIDAEAD pid_;
};
/*
* This is the data structure we keep on the receiving side,
* to check that no packet-id is accepted more than once.
*
* Replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER.
* PKTID_RECV_EXPIRE is backtrack expire in seconds.
*/
template <unsigned int REPLAY_WINDOW_ORDER,
unsigned int PKTID_RECV_EXPIRE>
class PacketIDAEADReceiveType
{
public:
static constexpr unsigned int REPLAY_WINDOW_BYTES = 1u << REPLAY_WINDOW_ORDER;
static constexpr unsigned int REPLAY_WINDOW_SIZE = REPLAY_WINDOW_BYTES * 8;
void init(const char *name_arg,
const int unit_arg,
bool wide_arg,
const SessionStats::Ptr &stats_arg)
{
wide = wide_arg;
base = 0;
extent = 0;
expire = 0;
id_high = 0;
id_floor = 0;
unit = unit_arg;
name = name_arg;
stats = stats_arg;
std::memset(history, 0, sizeof(history));
}
/**
* Checks if a packet ID is allowed and modifies the history of seen packets ids and
* adds any errors to the internal stats.
*
* It returns the verdict of the packet id if it is fine or not
*
* @param pin packet ID to check
* @param now Current time to check that reordered packets are in the allowed time
* @return true if the packet id is okay and has been accepted
*/
[[nodiscard]] bool test_add(const PacketIDAEAD &pin,
const Time::base_type now)
{
const Error::Type err = do_test_add(pin, now);
if (unlikely(err != Error::SUCCESS))
{
stats->error(err);
return false;
}
else
return true;
}
/**
* Checks if a packet ID is allowed and modifies the history of seen packets ids.
*
* It returns the verdict of the packet id if it is fine or not
*
* @param pin packet ID to check
* @param now Current time to check that reordered packets are in the allowed time
* @return Error::SUCCESS if successful, otherwise PKTID_EXPIRE, PKTID_BACKTRACK or PKTID_REPLAY
*/
[[nodiscard]] Error::Type do_test_add(const PacketIDAEAD &pin,
const Time::base_type now)
{
// expire backtracks at or below id_floor after PKTID_RECV_EXPIRE time
if (unlikely(now >= expire))
id_floor = id_high;
expire = now + PKTID_RECV_EXPIRE;
// ID must not be zero
if (unlikely(!pin.is_valid()))
return Error::PKTID_INVALID;
if (likely(pin.id == id_high + 1))
{
// well-formed ID sequence (incremented by 1)
base = replay_index(-1);
history[base / 8] |= static_cast<uint8_t>(1 << (base % 8));
if (extent < REPLAY_WINDOW_SIZE)
++extent;
id_high = pin.id;
}
else if (pin.id > id_high)
{
// ID jumped forward by more than one
const auto delta = pin.id - id_high;
if (delta < REPLAY_WINDOW_SIZE)
{
base = replay_index(-delta);
history[base / 8] |= static_cast<uint8_t>(1u << (base % 8));
extent += static_cast<std::size_t>(delta);
if (extent > REPLAY_WINDOW_SIZE)
extent = REPLAY_WINDOW_SIZE;
for (unsigned i = 1; i < delta; ++i)
{
const auto newbase = replay_index(i);
history[newbase / 8] &= static_cast<uint8_t>(~(1u << (newbase % 8)));
}
}
else
{
base = 0;
extent = REPLAY_WINDOW_SIZE;
std::memset(history, 0, sizeof(history));
history[0] = 1;
}
id_high = pin.id;
}
else
{
// ID backtrack
const auto delta = id_high - pin.id;
if (delta < extent)
{
if (pin.id > id_floor)
{
const auto ri = replay_index(delta);
std::uint8_t *p = &history[ri / 8];
const std::uint8_t mask = static_cast<uint8_t>(1u << (ri % 8));
if (*p & mask)
return Error::PKTID_REPLAY;
*p |= mask;
}
else
return Error::PKTID_EXPIRE;
}
else
return Error::PKTID_BACKTRACK;
}
return Error::SUCCESS;
}
PacketIDAEAD read_next(Buffer &buf) const
{
PacketIDAEAD pid{wide};
pid.read(buf);
return pid;
}
[[nodiscard]] std::string str() const
{
std::ostringstream os;
os << "[e=" << extent << " f=" << id_floor << id_high << ']';
return os.str();
}
[[nodiscard]] std::size_t constexpr length() const
{
return PacketIDAEAD::size(wide);
}
private:
[[nodiscard]] constexpr std::size_t replay_index(PacketIDAEAD::aead_id_t i) const
{
return (base + i) & (REPLAY_WINDOW_SIZE - 1);
}
std::size_t base; // bit position of deque base in history
std::size_t extent; // extent (in bits) of deque in history
Time::base_type expire; // expiration of history
PacketIDAEAD::aead_id_t id_high; // highest sequence number received
PacketIDAEAD::aead_id_t id_floor; // we will only accept backtrack IDs > id_floor
//!< 32 or 64 bit packet counter
bool wide;
int unit; // unit number of this object (for debugging)
std::string name; // name of this object (for debugging)
SessionStats::Ptr stats;
//! "sliding window" bitmask of recent packet IDs received */
std::uint8_t history[REPLAY_WINDOW_BYTES];
};
// Our standard packet ID window with order=8 (window size=2048).
// and recv expire=30 seconds.
typedef PacketIDAEADReceiveType<8, 30> PacketIDAEADReceive;
} // namespace openvpn

View File

@ -142,6 +142,13 @@ class ClientConfig : public DCO,
return ctrl;
}
bool supports_proto_v3() override
{
/* Currently, there is no version of ovpn-dco for Linux or Windows that supports
* the new features, so we always return false here */
return false;
}
protected:
ClientConfig() = default;
};

View File

@ -307,6 +307,12 @@ class ServerProto
{
}
bool supports_proto_v3() override
{
/* TODO: currently all server implementations do not implement this feature in their data channel */
return false;
}
bool defined_() const
{
return !halt && TransportLink::send;

View File

@ -203,6 +203,11 @@ class ProtoContextCallbackInterface
buf.write(&empty, 2);
}
/** the protocol context needs to know if the parent and its tun/transport layer are able to
* support 64bit and AEAD tag at the end in order to properly handshake this protocol feature
*/
virtual bool supports_proto_v3() = 0;
//! Called when KeyContext transitions to ACTIVE state
virtual void active(bool primary) = 0;
};
@ -279,6 +284,7 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
IV_PROTO_CC_EXIT_NOTIFY = (1 << 7),
IV_PROTO_AUTH_FAIL_TEMP = (1 << 8),
IV_PROTO_DYN_TLS_CRYPT = (1 << 9),
IV_PROTO_DATA_V3 = (1 << 10),
IV_PROTO_DNS_OPTION_V2 = (1 << 11),
IV_PROTO_PUSH_UPDATE = (1 << 12)
};
@ -831,6 +837,14 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
// Overrides "key-derivation" method set above
dc.set_key_derivation(CryptoAlgs::KeyDerivation::TLS_EKM);
}
else if (flag == "aead-tag-end")
{
dc.set_aead_tag_end(true);
}
else if (flag == "pkt-id-64-bit")
{
dc.set_64_bit_packet_id(true);
}
else
{
OPENVPN_THROW(process_server_push_error, "unknown flag '" << flag << "'");
@ -905,6 +919,12 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
os << ", peer-id " << remote_peer_id;
if (dc.aeadTagAtTheEnd())
os << ", aead-tag-end";
if (dc.use64bitPktCounter())
os << ", pkt-id-64-bit";
os << std::endl;
}
@ -1085,13 +1105,12 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
// generate a string summarizing information about the client
// including capabilities
std::string peer_info_string() const
std::string peer_info_string(bool proto_v3_support) const
{
std::ostringstream out;
const char *compstr = nullptr;
// supports op32 and P_DATA_V2 and expects a push reply
unsigned int iv_proto = IV_PROTO_DATA_V2
| IV_PROTO_REQUEST_PUSH
| IV_PROTO_AUTH_PENDING_KW
@ -1100,6 +1119,9 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
| IV_PROTO_AUTH_FAIL_TEMP
| IV_PROTO_PUSH_UPDATE;
if (proto_v3_support)
iv_proto |= IV_PROTO_DATA_V3;
if (CryptoAlgs::lookup("SHA256") != CryptoAlgs::NONE && CryptoAlgs::lookup("AES-256-CTR") != CryptoAlgs::NONE)
iv_proto |= IV_PROTO_DYN_TLS_CRYPT;
@ -2860,7 +2882,7 @@ class ProtoContext : public logging::LoggingMixin<OPENVPN_DEBUG_PROTO,
write_empty_string(*buf); // username
write_empty_string(*buf); // password
}
const std::string peer_info = proto.config->peer_info_string();
const std::string peer_info = proto.config->peer_info_string(proto.proto_callback->supports_proto_v3());
write_auth_string(peer_info, *buf);
}
app_send_validate(std::move(buf));

View File

@ -109,6 +109,11 @@ class ClientConfig : public TunClientFactory
tun_persist.reset();
}
bool supports_proto_v3() override
{
return true;
}
private:
ClientConfig()
: n_parallel(8), retain_sd(false), tun_prefix(false), builder(nullptr)

View File

@ -101,6 +101,16 @@ struct TunClientFactory : public virtual RC<thread_unsafe_refcount>
return false;
}
/**
* Return whether this tun implementation will support data v3 features
* (AEAD tag at the end and 64 bit packet counters).
*
* This is more a property of the data encryption layer than of the tun device
* but since all of our DCO encryptions are setup with the tun setup, we also
* make it the responsibility of the tun client to signal v3 data layer support.
*/
virtual bool supports_proto_v3() = 0;
// Called on TunClient close, after TunClient::stop has been called.
// disconnected ->
// true: this is the final disconnect, or

View File

@ -45,10 +45,13 @@ class ClientConfig : public TunClientFactory
TunClientParent &parent,
TransportClient *transcli) override;
private:
ClientConfig()
bool supports_proto_v3() override
{
return true;
}
private:
ClientConfig() = default;
};
class Client : public TunClient

View File

@ -121,6 +121,12 @@ class ClientConfig : public TunClientFactory
return new TunLinuxSetup::Setup<TUN_LINUX>();
}
bool supports_proto_v3() override
{
/* The normal tun implementation that uses the internal data channel */
return true;
}
private:
ClientConfig()
{

View File

@ -116,6 +116,11 @@ class ClientConfig : public TunClientFactory
return new ClientConfig;
}
bool supports_proto_v3() override
{
return true;
}
TunClient::Ptr new_tun_client_obj(openvpn_io::io_context &io_context,
TunClientParent &parent,
TransportClient *transcli) override;

View File

@ -87,6 +87,11 @@ class ClientConfig : public TunClientFactory
TunClientParent &parent,
TransportClient *transcli) override;
bool supports_proto_v3() override
{
return tun_type != TunWin::OvpnDco;
}
void finalize(const bool disconnected) override
{
if (disconnected)

View File

@ -96,7 +96,7 @@ static openvpn::Frame::Context frame_ctx()
}
TEST(crypto, dcaead)
void test_datachannel_crypto(bool tag_at_the_end, bool longpktcounter = false)
{
auto frameptr = openvpn::Frame::Ptr{new openvpn::Frame{frame_ctx()}};
@ -104,6 +104,8 @@ TEST(crypto, dcaead)
openvpn::CryptoDCSettingsData dc;
dc.set_cipher(openvpn::CryptoAlgs::AES_256_GCM);
dc.set_aead_tag_end(tag_at_the_end);
dc.set_64_bit_packet_id(longpktcounter);
openvpn::AEAD::Crypto<openvpn::SSLLib::CryptoAPI> cryptodc{nullptr, dc, frameptr, statsptr};
@ -155,20 +157,57 @@ TEST(crypto, dcaead)
bool const wrapwarn = cryptodc.encrypt(work, now, op32);
ASSERT_FALSE(wrapwarn);
/* 16 for tag, 4 for IV */
EXPECT_EQ(work.size(), std::strlen(plaintext) + 4 + 16);
size_t pkt_counter_len = longpktcounter ? 8 : 4;
size_t tag_len = 16;
/* 16 for tag, 4 or 8 for packet counter */
EXPECT_EQ(work.size(), std::strlen(plaintext) + pkt_counter_len + tag_len);
const uint8_t exp_tag_short[16]{0x1f, 0xdd, 0x90, 0x8f, 0x0e, 0x9d, 0xc2, 0x5e, 0x79, 0xd8, 0x32, 0x02, 0x0d, 0x58, 0xe7, 0x3f};
const uint8_t exp_tag_long[16]{0x52, 0xee, 0xef, 0xdb, 0x34, 0xb7, 0xbd, 0x79, 0xfe, 0xbf, 0x69, 0xd0, 0x4e, 0x92, 0xfe, 0x4b};
const uint8_t *expected_tag;
if (longpktcounter)
expected_tag = exp_tag_long;
else
expected_tag = exp_tag_short;
const uint8_t expected_tag[16]{0x1f, 0xdd, 0x90, 0x8f, 0x0e, 0x9d, 0xc2, 0x5e, 0x79, 0xd8, 0x32, 0x02, 0x0d, 0x58, 0xe7, 0x3f};
// Packet id/IV should 1
uint8_t packetid1[]{0, 0, 0, 1};
EXPECT_TRUE(std::memcmp(work.data(), packetid1, 4) == 0);
if (longpktcounter)
{
uint8_t packetid1[]{0, 0, 0, 0, 0, 0, 0, 1};
EXPECT_EQ(std::memcmp(work.data(), packetid1, 8), 0);
}
else
{
uint8_t packetid1[]{0, 0, 0, 1};
EXPECT_EQ(std::memcmp(work.data(), packetid1, 4), 0);
}
// Tag is in the front after packet id
EXPECT_TRUE(std::memcmp(work.data() + 4, expected_tag, 16) == 0);
if (tag_at_the_end)
{
EXPECT_EQ(std::memcmp(work.data() + 56 + pkt_counter_len, expected_tag, 16), 0);
}
else
{
EXPECT_EQ(std::memcmp(work.data() + pkt_counter_len, expected_tag, 16), 0);
}
// Check a few random bytes of the encrypted output
const uint8_t bytesat30[6]{0xa8, 0x2e, 0x6b, 0x2e, 0x6b, 0x17};
EXPECT_TRUE(std::memcmp(work.data() + 30, bytesat30, 6) == 0);
// Check a few random bytes of the encrypted output. Different IVs lead to different output here.
ptrdiff_t tagoffset = tag_at_the_end ? 0 : 16;
if (longpktcounter)
{
const uint8_t bytesat14[6]{0xc7, 0x40, 0x47, 0x81, 0xac, 0x8c};
EXPECT_EQ(std::memcmp(work.data() + tagoffset + 14, bytesat14, 6), 0);
}
else
{
const uint8_t bytesat14[6]{0xa8, 0x2e, 0x6b, 0x17, 0x06, 0xd9};
EXPECT_EQ(std::memcmp(work.data() + tagoffset + 14, bytesat14, 6), 0);
}
/* Check now if decrypting also works */
auto ret = cryptodc.decrypt(work, now, op32);
@ -176,5 +215,27 @@ TEST(crypto, dcaead)
EXPECT_EQ(ret, openvpn::Error::SUCCESS);
EXPECT_EQ(work.size(), std::strlen(plaintext));
EXPECT_TRUE(std::memcmp(work.data(), plaintext, std::strlen(plaintext)) == 0);
EXPECT_EQ(std::memcmp(work.data(), plaintext, std::strlen(plaintext)), 0);
}
TEST(crypto, dcaead_tag_at_the_front)
{
test_datachannel_crypto(false);
}
TEST(crypto, dcaead_tag_at_the_end)
{
test_datachannel_crypto(true);
}
TEST(crypto, dcaead_tag_at_the_front_long_pktcntr)
{
test_datachannel_crypto(false, true);
}
TEST(crypto, dcaead_tag_at_the_end_long_pktcntr)
{
test_datachannel_crypto(true, true);
}

View File

@ -351,6 +351,10 @@ class TestProto : public ProtoContextCallbackInterface
{
}
bool supports_proto_v3() override
{
return true;
}
public:
OPENVPN_EXCEPTION(session_invalidated);
@ -1258,7 +1262,7 @@ TEST(proto, iv_ciphers_aead)
auto protoConf = openvpn::ProtoContext::ProtoConfig();
auto infostring = protoConf.peer_info_string();
auto infostring = protoConf.peer_info_string(false);
auto ivciphers = infostring.substr(infostring.find("IV_CIPHERS="));
ivciphers = ivciphers.substr(0, ivciphers.find("\n"));
@ -1277,7 +1281,7 @@ TEST(proto, iv_ciphers_non_preferred)
auto protoConf = openvpn::ProtoContext::ProtoConfig();
auto infostring = protoConf.peer_info_string();
auto infostring = protoConf.peer_info_string(true);
auto ivciphers = infostring.substr(infostring.find("IV_CIPHERS="));
ivciphers = ivciphers.substr(0, ivciphers.find("\n"));
@ -1316,7 +1320,7 @@ TEST(proto, iv_ciphers_legacy)
auto protoConf = openvpn::ProtoContext::ProtoConfig();
auto infostring = protoConf.peer_info_string();
auto infostring = protoConf.peer_info_string(false);
auto ivciphers = infostring.substr(infostring.find("IV_CIPHERS="));
ivciphers = ivciphers.substr(0, ivciphers.find("\n"));