Skip to content

Commit

Permalink
Implement data v3 features for AEAD tag at the end and 64 bit packet …
Browse files Browse the repository at this point in the history
…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 <[email protected]>
  • Loading branch information
schwabe authored and Jenkins-dev committed Aug 19, 2024
1 parent a384f16 commit ca91f3e
Show file tree
Hide file tree
Showing 15 changed files with 704 additions and 67 deletions.
5 changes: 5 additions & 0 deletions openvpn/client/cliproto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
141 changes: 93 additions & 48 deletions openvpn/crypto/crypto_aead.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -62,94 +62,121 @@ 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;
};

struct Decrypt
{
typename CRYPTO_API::CipherContextAEAD impl;
Nonce nonce;
PacketIDReceive pid_recv;
PacketIDAEADReceive pid_recv{};
BufferAllocated work;
};

Expand All @@ -176,40 +203,50 @@ 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 = e.work.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
}
else
{
auth_tag_end = e.work.write_alloc(CRYPTO_API::CipherContextAEAD::AUTH_TAG_LEN);
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);
}

buf.swap(e.work);

// prepend additional data
nonce.prepend_ad(buf);
nonce.prepend_ad(buf, e.pid_send);
}
return e.pid_send.wrap_warning();
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -284,17 +329,17 @@ 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,
const char *recv_name,
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
Expand Down
40 changes: 40 additions & 0 deletions openvpn/crypto/cryptodc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit ca91f3e

Please sign in to comment.