mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-04-24 00:00:05 +08:00
Merge commit '86cc97e55fe346502462284d2e636a2b3708163e' as 'Sources/OpenVPN3'
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2012-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/>.
|
||||
|
||||
// Special data limits on Blowfish, Triple DES, and other 64-bit
|
||||
// block-size ciphers vulnerable to "Sweet32" birthday attack
|
||||
// (CVE-2016-6329). Limit such cipher keys to no more than 64 MB
|
||||
// of data encrypted/decrypted. Note that we trigger early at
|
||||
// 48 MB to compensate for possible delays in renegotiation and
|
||||
// rollover to the new key.
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_DATALIMIT_H
|
||||
#define OPENVPN_CRYPTO_DATALIMIT_H
|
||||
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
|
||||
#ifndef OPENVPN_BS64_DATA_LIMIT
|
||||
#define OPENVPN_BS64_DATA_LIMIT 48000000
|
||||
#endif
|
||||
|
||||
namespace openvpn {
|
||||
inline bool is_bs64_cipher(const CryptoAlgs::Type cipher)
|
||||
{
|
||||
return CryptoAlgs::get(cipher).block_size() == 8;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
// 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/>.
|
||||
|
||||
// General-purpose cipher classes that are independent of the underlying CRYPTO_API
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CIPHER_H
|
||||
#define OPENVPN_CRYPTO_CIPHER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
template <typename CRYPTO_API>
|
||||
class CipherContext
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(cipher_mode_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(cipher_uninitialized);
|
||||
OPENVPN_SIMPLE_EXCEPTION(cipher_init_insufficient_key_material);
|
||||
OPENVPN_SIMPLE_EXCEPTION(cipher_internal_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(cipher_output_buffer);
|
||||
|
||||
public:
|
||||
CipherContext() : mode_(CRYPTO_API::CipherContext::MODE_UNDEF) {}
|
||||
|
||||
CipherContext(const CryptoAlgs::Type cipher, const StaticKey& key, const int mode)
|
||||
: mode_(CRYPTO_API::CipherContext::MODE_UNDEF)
|
||||
{
|
||||
init(cipher, key, mode);
|
||||
}
|
||||
|
||||
bool defined() const { return ctx.is_initialized(); }
|
||||
|
||||
// size of iv buffer to pass to encrypt_decrypt
|
||||
size_t iv_length() const
|
||||
{
|
||||
return ctx.iv_length();
|
||||
}
|
||||
|
||||
// cipher mode (such as CIPH_CBC_MODE, etc.)
|
||||
int cipher_mode() const
|
||||
{
|
||||
return ctx.cipher_mode();
|
||||
}
|
||||
|
||||
// size of out buffer to pass to encrypt_decrypt
|
||||
size_t output_size(const size_t in_size) const
|
||||
{
|
||||
return in_size + ctx.block_size();
|
||||
}
|
||||
|
||||
void init(const CryptoAlgs::Type cipher, const StaticKey& key, const int mode)
|
||||
{
|
||||
const CryptoAlgs::Alg& alg = CryptoAlgs::get(cipher);
|
||||
|
||||
// check that provided key is large enough
|
||||
if (key.size() < alg.key_length())
|
||||
throw cipher_init_insufficient_key_material();
|
||||
|
||||
// IV consistency check
|
||||
if (alg.iv_length() > CRYPTO_API::CipherContext::MAX_IV_LENGTH)
|
||||
throw cipher_internal_error();
|
||||
|
||||
// initialize cipher context with cipher type, key, and encrypt/decrypt mode
|
||||
ctx.init(cipher, key.data(), mode);
|
||||
|
||||
// save mode in object
|
||||
mode_ = mode;
|
||||
}
|
||||
|
||||
size_t encrypt(const unsigned char *iv,
|
||||
unsigned char *out, const size_t out_size,
|
||||
const unsigned char *in, const size_t in_size)
|
||||
{
|
||||
if (mode_ != CRYPTO_API::CipherContext::ENCRYPT)
|
||||
throw cipher_mode_error();
|
||||
return encrypt_decrypt(iv, out, out_size, in, in_size);
|
||||
}
|
||||
|
||||
size_t decrypt(const unsigned char *iv,
|
||||
unsigned char *out, const size_t out_size,
|
||||
const unsigned char *in, const size_t in_size)
|
||||
{
|
||||
if (mode_ != CRYPTO_API::CipherContext::DECRYPT)
|
||||
throw cipher_mode_error();
|
||||
return encrypt_decrypt(iv, out, out_size, in, in_size);
|
||||
}
|
||||
|
||||
size_t encrypt_decrypt(const unsigned char *iv,
|
||||
unsigned char *out, const size_t out_size,
|
||||
const unsigned char *in, const size_t in_size)
|
||||
{
|
||||
if (out_size < output_size(in_size))
|
||||
throw cipher_output_buffer();
|
||||
ctx.reset(iv);
|
||||
size_t outlen = 0;
|
||||
if (!ctx.update(out, out_size, in, in_size, outlen))
|
||||
return 0;
|
||||
if (!ctx.final(out + outlen, out_size - outlen, outlen))
|
||||
return 0;
|
||||
return outlen;
|
||||
}
|
||||
|
||||
private:
|
||||
int mode_;
|
||||
typename CRYPTO_API::CipherContext ctx;
|
||||
};
|
||||
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_CIPHER_H
|
||||
@@ -0,0 +1,350 @@
|
||||
// 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/>.
|
||||
|
||||
// OpenVPN AEAD data channel interface
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CRYPTO_AEAD_H
|
||||
#define OPENVPN_CRYPTO_CRYPTO_AEAD_H
|
||||
|
||||
#include <cstring> // for std::memcpy, std::memset
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/frame/frame.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/packet_id.hpp>
|
||||
#include <openvpn/log/sessionstats.hpp>
|
||||
#include <openvpn/crypto/cryptodc.hpp>
|
||||
|
||||
// Sample AES-GCM head:
|
||||
// 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a...
|
||||
// [ OP32 ] [seq # ] [ auth tag ] [ payload ... ]
|
||||
// [4-byte
|
||||
// IV head]
|
||||
|
||||
namespace openvpn {
|
||||
namespace AEAD {
|
||||
|
||||
OPENVPN_EXCEPTION(aead_error);
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class Crypto : public CryptoDCInstance
|
||||
{
|
||||
class Nonce {
|
||||
public:
|
||||
Nonce()
|
||||
{
|
||||
static_assert(4 + CRYPTO_API::CipherContextGCM::IV_LEN == sizeof(data),
|
||||
"AEAD IV_LEN inconsistency");
|
||||
ad_op32 = false;
|
||||
std::memset(data, 0, sizeof(data));
|
||||
}
|
||||
|
||||
// setup
|
||||
void set_tail(const StaticKey& sk)
|
||||
{
|
||||
if (sk.size() < 8)
|
||||
throw aead_error("insufficient key material for nonce tail");
|
||||
std::memcpy(data + 8, sk.data(), 8);
|
||||
}
|
||||
|
||||
// for encrypt
|
||||
Nonce(const Nonce& ref, PacketIDSend& pid_send, const PacketID::time_t now,
|
||||
const unsigned char *op32)
|
||||
{
|
||||
std::memcpy(data, ref.data, sizeof(data));
|
||||
Buffer buf(data + 4, 4, false);
|
||||
pid_send.write_next(buf, false, now);
|
||||
if (op32)
|
||||
{
|
||||
ad_op32 = true;
|
||||
std::memcpy(data, op32, 4);
|
||||
}
|
||||
else
|
||||
ad_op32 = false;
|
||||
}
|
||||
|
||||
// for encrypt
|
||||
void prepend_ad(Buffer& buf) const
|
||||
{
|
||||
buf.prepend(data + 4, 4);
|
||||
}
|
||||
|
||||
// for decrypt
|
||||
Nonce(const Nonce& ref, Buffer& buf, const unsigned char *op32)
|
||||
{
|
||||
std::memcpy(data, ref.data, sizeof(data));
|
||||
buf.read(data + 4, 4);
|
||||
if (op32)
|
||||
{
|
||||
ad_op32 = true;
|
||||
std::memcpy(data, op32, 4);
|
||||
}
|
||||
else
|
||||
ad_op32 = false;
|
||||
}
|
||||
|
||||
// for decrypt
|
||||
bool verify_packet_id(PacketIDReceive& 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
|
||||
}
|
||||
|
||||
const unsigned char *iv() const
|
||||
{
|
||||
return data + 4;
|
||||
}
|
||||
|
||||
const unsigned char *ad() const
|
||||
{
|
||||
return ad_op32 ? data : data + 4;
|
||||
}
|
||||
|
||||
const size_t ad_len() const
|
||||
{
|
||||
return ad_op32 ? 8 : 4;
|
||||
}
|
||||
|
||||
private:
|
||||
bool ad_op32; // true if AD 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];
|
||||
};
|
||||
|
||||
struct Encrypt {
|
||||
typename CRYPTO_API::CipherContextGCM impl;
|
||||
Nonce nonce;
|
||||
PacketIDSend pid_send;
|
||||
BufferAllocated work;
|
||||
};
|
||||
|
||||
struct Decrypt {
|
||||
typename CRYPTO_API::CipherContextGCM impl;
|
||||
Nonce nonce;
|
||||
PacketIDReceive pid_recv;
|
||||
BufferAllocated work;
|
||||
};
|
||||
public:
|
||||
typedef CryptoDCInstance Base;
|
||||
|
||||
Crypto(const CryptoAlgs::Type cipher_arg,
|
||||
const Frame::Ptr& frame_arg,
|
||||
const SessionStats::Ptr& stats_arg)
|
||||
: cipher(cipher_arg),
|
||||
frame(frame_arg),
|
||||
stats(stats_arg)
|
||||
{
|
||||
}
|
||||
|
||||
// Encrypt/Decrypt
|
||||
|
||||
// returns true if packet ID is close to wrapping
|
||||
virtual bool encrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
|
||||
{
|
||||
// only process non-null packets
|
||||
if (buf.size())
|
||||
{
|
||||
// build nonce/IV/AD
|
||||
Nonce nonce(e.nonce, e.pid_send, now, op32);
|
||||
|
||||
if (CRYPTO_API::CipherContextGCM::SUPPORTS_IN_PLACE_ENCRYPT)
|
||||
{
|
||||
unsigned char *data = buf.data();
|
||||
const size_t size = buf.size();
|
||||
|
||||
// alloc auth tag in buffer
|
||||
unsigned char *auth_tag = buf.prepend_alloc(CRYPTO_API::CipherContextGCM::AUTH_TAG_LEN);
|
||||
|
||||
// encrypt in-place
|
||||
e.impl.encrypt(data, data, size, nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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::CipherContextGCM::AUTH_TAG_LEN);
|
||||
|
||||
// prepare output buffer
|
||||
unsigned char *work_data = e.work.write_alloc(buf.size());
|
||||
|
||||
// encrypt
|
||||
e.impl.encrypt(buf.data(), work_data, buf.size(), nonce.iv(), auth_tag, nonce.ad(), nonce.ad_len());
|
||||
buf.swap(e.work);
|
||||
}
|
||||
|
||||
// prepend additional data
|
||||
nonce.prepend_ad(buf);
|
||||
}
|
||||
return e.pid_send.wrap_warning();
|
||||
}
|
||||
|
||||
virtual Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
|
||||
{
|
||||
// only process non-null packets
|
||||
if (buf.size())
|
||||
{
|
||||
// get nonce/IV/AD
|
||||
Nonce nonce(d.nonce, buf, op32);
|
||||
|
||||
// get auth tag
|
||||
unsigned char *auth_tag = buf.read_alloc(CRYPTO_API::CipherContextGCM::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");
|
||||
|
||||
// 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()))
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::DECRYPT_ERROR;
|
||||
}
|
||||
d.work.set_size(buf.size());
|
||||
|
||||
// verify packet ID
|
||||
if (!nonce.verify_packet_id(d.pid_recv, now))
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::REPLAY_ERROR;
|
||||
}
|
||||
|
||||
// return cleartext result in buf
|
||||
buf.swap(d.work);
|
||||
}
|
||||
return Error::SUCCESS;
|
||||
}
|
||||
|
||||
// Initialization
|
||||
|
||||
virtual void init_cipher(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key)
|
||||
{
|
||||
e.impl.init(cipher, encrypt_key.data(), encrypt_key.size(), CRYPTO_API::CipherContextGCM::ENCRYPT);
|
||||
d.impl.init(cipher, decrypt_key.data(), decrypt_key.size(), CRYPTO_API::CipherContextGCM::DECRYPT);
|
||||
}
|
||||
|
||||
virtual void init_hmac(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key)
|
||||
{
|
||||
e.nonce.set_tail(encrypt_key);
|
||||
d.nonce.set_tail(decrypt_key);
|
||||
}
|
||||
|
||||
virtual void init_pid(const int send_form,
|
||||
const int recv_mode,
|
||||
const int recv_form,
|
||||
const char *recv_name,
|
||||
const int recv_unit,
|
||||
const SessionStats::Ptr& recv_stats_arg)
|
||||
{
|
||||
e.pid_send.init(send_form);
|
||||
d.pid_recv.init(recv_mode, recv_form, recv_name, recv_unit, recv_stats_arg);
|
||||
}
|
||||
|
||||
// Indicate whether or not cipher/digest is defined
|
||||
|
||||
virtual unsigned int defined() const
|
||||
{
|
||||
unsigned int ret = CRYPTO_DEFINED;
|
||||
|
||||
// AEAD mode doesn't use HMAC, but we still indicate HMAC_DEFINED
|
||||
// because we want to use the HMAC keying material for the AEAD nonce tail.
|
||||
if (CryptoAlgs::defined(cipher))
|
||||
ret |= (CIPHER_DEFINED|HMAC_DEFINED);
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual bool consider_compression(const CompressContext& comp_ctx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rekeying
|
||||
|
||||
virtual void rekey(const typename Base::RekeyType type)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type cipher;
|
||||
Frame::Ptr frame;
|
||||
SessionStats::Ptr stats;
|
||||
Encrypt e;
|
||||
Decrypt d;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoContext : public CryptoDCContext
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoContext> Ptr;
|
||||
|
||||
CryptoContext(const CryptoAlgs::Type cipher_arg,
|
||||
const Frame::Ptr& frame_arg,
|
||||
const SessionStats::Ptr& stats_arg)
|
||||
: cipher(CryptoAlgs::legal_dc_cipher(cipher_arg)),
|
||||
frame(frame_arg),
|
||||
stats(stats_arg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual CryptoDCInstance::Ptr new_obj(const unsigned int key_id)
|
||||
{
|
||||
return new Crypto<CRYPTO_API>(cipher, frame, stats);
|
||||
}
|
||||
|
||||
// cipher/HMAC/key info
|
||||
virtual Info crypto_info()
|
||||
{
|
||||
Info ret;
|
||||
ret.cipher_alg = cipher;
|
||||
ret.hmac_alg = CryptoAlgs::NONE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Info for ProtoContext::link_mtu_adjust
|
||||
|
||||
virtual size_t encap_overhead() const
|
||||
{
|
||||
return CRYPTO_API::CipherContextGCM::AUTH_TAG_LEN;
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type cipher;
|
||||
Frame::Ptr frame;
|
||||
SessionStats::Ptr stats;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
// 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/>.
|
||||
|
||||
// OpenVPN CBC/HMAC data channel
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CRYPTO_CHM_H
|
||||
#define OPENVPN_CRYPTO_CRYPTO_CHM_H
|
||||
|
||||
#include <openvpn/crypto/encrypt_chm.hpp>
|
||||
#include <openvpn/crypto/decrypt_chm.hpp>
|
||||
#include <openvpn/crypto/cryptodc.hpp>
|
||||
#include <openvpn/random/randapi.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoCHM : public CryptoDCInstance
|
||||
{
|
||||
public:
|
||||
typedef CryptoDCInstance Base;
|
||||
|
||||
CryptoCHM(const CryptoAlgs::Type cipher_arg,
|
||||
const CryptoAlgs::Type digest_arg,
|
||||
const Frame::Ptr& frame_arg,
|
||||
const SessionStats::Ptr& stats_arg,
|
||||
const RandomAPI::Ptr& prng_arg)
|
||||
: cipher(cipher_arg),
|
||||
digest(digest_arg),
|
||||
frame(frame_arg),
|
||||
stats(stats_arg),
|
||||
prng(prng_arg)
|
||||
{
|
||||
encrypt_.frame = frame;
|
||||
decrypt_.frame = frame;
|
||||
encrypt_.set_prng(prng);
|
||||
}
|
||||
|
||||
// Encrypt/Decrypt
|
||||
|
||||
/* returns true if packet ID is close to wrapping */
|
||||
virtual bool encrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
|
||||
{
|
||||
encrypt_.encrypt(buf, now);
|
||||
return encrypt_.pid_send.wrap_warning();
|
||||
}
|
||||
|
||||
virtual Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32)
|
||||
{
|
||||
return decrypt_.decrypt(buf, now);
|
||||
}
|
||||
|
||||
// Initialization
|
||||
|
||||
virtual void init_cipher(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key)
|
||||
{
|
||||
encrypt_.cipher.init(cipher, encrypt_key, CRYPTO_API::CipherContext::ENCRYPT);
|
||||
decrypt_.cipher.init(cipher, decrypt_key, CRYPTO_API::CipherContext::DECRYPT);
|
||||
}
|
||||
|
||||
virtual void init_hmac(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key)
|
||||
{
|
||||
encrypt_.hmac.init(digest, encrypt_key);
|
||||
decrypt_.hmac.init(digest, decrypt_key);
|
||||
}
|
||||
|
||||
virtual void init_pid(const int send_form,
|
||||
const int recv_mode,
|
||||
const int recv_form,
|
||||
const char *recv_name,
|
||||
const int recv_unit,
|
||||
const SessionStats::Ptr& recv_stats_arg)
|
||||
{
|
||||
encrypt_.pid_send.init(send_form);
|
||||
decrypt_.pid_recv.init(recv_mode, recv_form, recv_name, recv_unit, recv_stats_arg);
|
||||
}
|
||||
|
||||
virtual bool consider_compression(const CompressContext& comp_ctx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Indicate whether or not cipher/digest is defined
|
||||
|
||||
virtual unsigned int defined() const
|
||||
{
|
||||
unsigned int ret = CRYPTO_DEFINED;
|
||||
if (CryptoAlgs::defined(cipher))
|
||||
ret |= CIPHER_DEFINED;
|
||||
if (CryptoAlgs::defined(digest))
|
||||
ret |= HMAC_DEFINED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Rekeying
|
||||
|
||||
virtual void rekey(const typename Base::RekeyType type)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type cipher;
|
||||
CryptoAlgs::Type digest;
|
||||
Frame::Ptr frame;
|
||||
SessionStats::Ptr stats;
|
||||
RandomAPI::Ptr prng;
|
||||
|
||||
EncryptCHM<CRYPTO_API> encrypt_;
|
||||
DecryptCHM<CRYPTO_API> decrypt_;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoContextCHM : public CryptoDCContext
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoContextCHM> Ptr;
|
||||
|
||||
CryptoContextCHM(const CryptoAlgs::Type cipher_arg,
|
||||
const CryptoAlgs::Type digest_arg,
|
||||
const Frame::Ptr& frame_arg,
|
||||
const SessionStats::Ptr& stats_arg,
|
||||
const RandomAPI::Ptr& prng_arg)
|
||||
: cipher(CryptoAlgs::legal_dc_cipher(cipher_arg)),
|
||||
digest(CryptoAlgs::legal_dc_digest(digest_arg)),
|
||||
frame(frame_arg),
|
||||
stats(stats_arg),
|
||||
prng(prng_arg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual CryptoDCInstance::Ptr new_obj(const unsigned int key_id)
|
||||
{
|
||||
return new CryptoCHM<CRYPTO_API>(cipher, digest, frame, stats, prng);
|
||||
}
|
||||
|
||||
// cipher/HMAC/key info
|
||||
virtual Info crypto_info()
|
||||
{
|
||||
Info ret;
|
||||
ret.cipher_alg = cipher;
|
||||
ret.hmac_alg = digest;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Info for ProtoContext::link_mtu_adjust
|
||||
|
||||
virtual size_t encap_overhead() const
|
||||
{
|
||||
return CryptoAlgs::size(digest) + // HMAC
|
||||
CryptoAlgs::iv_length(cipher) + // Cipher IV
|
||||
CryptoAlgs::block_size(cipher); // worst-case PKCS#7 padding expansion
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type cipher;
|
||||
CryptoAlgs::Type digest;
|
||||
Frame::Ptr frame;
|
||||
SessionStats::Ptr stats;
|
||||
RandomAPI::Ptr prng;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,261 @@
|
||||
// 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/>.
|
||||
|
||||
// Crypto algorithms
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CRYPTOALGS_H
|
||||
#define OPENVPN_CRYPTO_CRYPTOALGS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/string.hpp>
|
||||
#include <openvpn/common/likely.hpp>
|
||||
#include <openvpn/common/arraysize.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace CryptoAlgs {
|
||||
|
||||
OPENVPN_EXCEPTION(crypto_alg);
|
||||
OPENVPN_SIMPLE_EXCEPTION(crypto_alg_index);
|
||||
|
||||
enum Type {
|
||||
NONE=0,
|
||||
|
||||
// CBC ciphers
|
||||
AES_128_CBC,
|
||||
AES_192_CBC,
|
||||
AES_256_CBC,
|
||||
DES_CBC,
|
||||
DES_EDE3_CBC,
|
||||
BF_CBC,
|
||||
|
||||
// CTR ciphers
|
||||
AES_256_CTR,
|
||||
|
||||
// AEAD ciphers
|
||||
AES_128_GCM,
|
||||
AES_192_GCM,
|
||||
AES_256_GCM,
|
||||
|
||||
// digests
|
||||
MD4,
|
||||
MD5,
|
||||
SHA1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
|
||||
SIZE,
|
||||
};
|
||||
|
||||
enum Mode {
|
||||
MODE_UNDEF=0,
|
||||
CBC_HMAC,
|
||||
AEAD,
|
||||
MODE_MASK=0x03,
|
||||
};
|
||||
|
||||
enum AlgFlags { // bits below must start after Mode bits
|
||||
F_CIPHER=(1<<2), // alg is a cipher
|
||||
F_DIGEST=(1<<3), // alg is a digest
|
||||
F_ALLOW_DC=(1<<4), // alg may be used in OpenVPN data channel
|
||||
F_NO_CIPHER_DIGEST=(1<<5), // cipher alg does not depend on any additional digest
|
||||
};
|
||||
|
||||
// size in bytes of AEAD "nonce tail" normally taken from
|
||||
// HMAC key material
|
||||
enum {
|
||||
AEAD_NONCE_TAIL_SIZE = 8
|
||||
};
|
||||
|
||||
class Alg
|
||||
{
|
||||
public:
|
||||
constexpr Alg(const char *name,
|
||||
const unsigned int flags,
|
||||
const unsigned int size,
|
||||
const unsigned int iv_length,
|
||||
const unsigned int block_size)
|
||||
|
||||
: name_(name),
|
||||
flags_(flags),
|
||||
size_(size),
|
||||
iv_length_(iv_length),
|
||||
block_size_(block_size)
|
||||
{
|
||||
}
|
||||
|
||||
const char *name() const { return name_; }
|
||||
unsigned int flags() const { return flags_; } // contains Mode and AlgFlags
|
||||
Mode mode() const { return Mode(flags_ & MODE_MASK); }
|
||||
size_t size() const { return size_; } // digest size
|
||||
size_t key_length() const { return size_; } // cipher key length
|
||||
size_t iv_length() const { return iv_length_; } // cipher only
|
||||
size_t block_size() const { return block_size_; } // cipher only
|
||||
|
||||
private:
|
||||
const char *name_;
|
||||
unsigned int flags_;
|
||||
unsigned int size_;
|
||||
unsigned int iv_length_;
|
||||
unsigned int block_size_;
|
||||
};
|
||||
|
||||
constexpr Alg algs[] = { // NOTE: MUST be indexed by CryptoAlgs::Type (CONST GLOBAL)
|
||||
{ "NONE", F_CIPHER|F_DIGEST|F_ALLOW_DC|CBC_HMAC, 0, 0, 0 },
|
||||
{ "AES-128-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 16, 16, 16 },
|
||||
{ "AES-192-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 24, 16, 16 },
|
||||
{ "AES-256-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 32, 16, 16 },
|
||||
{ "DES-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 8, 8, 8 },
|
||||
{ "DES-EDE3-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 24, 8, 8 },
|
||||
{ "BF-CBC", F_CIPHER|F_ALLOW_DC|CBC_HMAC, 16, 8, 8 },
|
||||
{ "AES-256-CTR", F_CIPHER, 32, 16, 16 },
|
||||
{ "AES-128-GCM", F_CIPHER|F_ALLOW_DC|AEAD|F_NO_CIPHER_DIGEST, 16, 12, 16 },
|
||||
{ "AES-192-GCM", F_CIPHER|F_ALLOW_DC|AEAD|F_NO_CIPHER_DIGEST, 24, 12, 16 },
|
||||
{ "AES-256-GCM", F_CIPHER|F_ALLOW_DC|AEAD|F_NO_CIPHER_DIGEST, 32, 12, 16 },
|
||||
{ "MD4", F_DIGEST, 16, 0, 0 },
|
||||
{ "MD5", F_DIGEST|F_ALLOW_DC, 16, 0, 0 },
|
||||
{ "SHA1", F_DIGEST|F_ALLOW_DC, 20, 0, 0 },
|
||||
{ "SHA224", F_DIGEST|F_ALLOW_DC, 28, 0, 0 },
|
||||
{ "SHA256", F_DIGEST|F_ALLOW_DC, 32, 0, 0 },
|
||||
{ "SHA384", F_DIGEST|F_ALLOW_DC, 48, 0, 0 },
|
||||
{ "SHA512", F_DIGEST|F_ALLOW_DC, 64, 0, 0 },
|
||||
};
|
||||
|
||||
inline bool defined(const Type type)
|
||||
{
|
||||
return type != NONE;
|
||||
}
|
||||
|
||||
inline const Alg* get_index_ptr(const size_t i)
|
||||
{
|
||||
static_assert(SIZE == array_size(algs), "algs array inconsistency");
|
||||
if (unlikely(i >= SIZE))
|
||||
throw crypto_alg_index();
|
||||
return &algs[i];
|
||||
}
|
||||
|
||||
inline const Alg& get_index(const size_t i)
|
||||
{
|
||||
return *get_index_ptr(i);
|
||||
}
|
||||
|
||||
inline const Alg* get_ptr(const Type type)
|
||||
{
|
||||
return get_index_ptr(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
inline const Alg& get(const Type type)
|
||||
{
|
||||
return get_index(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
inline Type lookup(const std::string& name)
|
||||
{
|
||||
for (size_t i = 0; i < SIZE; ++i)
|
||||
{
|
||||
const Alg& alg = algs[i];
|
||||
if (string::strcasecmp(name, alg.name()) == 0)
|
||||
return static_cast<Type>(i);
|
||||
}
|
||||
OPENVPN_THROW(crypto_alg, name << ": not found");
|
||||
}
|
||||
|
||||
inline const char *name(const Type type)
|
||||
{
|
||||
return get(type).name();
|
||||
}
|
||||
|
||||
inline const char *name(const Type type, const char *default_name)
|
||||
{
|
||||
if (type == NONE)
|
||||
return default_name;
|
||||
else
|
||||
return get(type).name();
|
||||
}
|
||||
|
||||
inline size_t size(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return alg.size();
|
||||
}
|
||||
|
||||
inline size_t key_length(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return alg.key_length();
|
||||
}
|
||||
|
||||
inline size_t iv_length(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return alg.iv_length();
|
||||
}
|
||||
|
||||
inline size_t block_size(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return alg.block_size();
|
||||
}
|
||||
|
||||
inline Mode mode(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return alg.mode();
|
||||
}
|
||||
|
||||
inline Type legal_dc_cipher(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
if ((alg.flags() & (F_CIPHER|F_ALLOW_DC)) != (F_CIPHER|F_ALLOW_DC))
|
||||
OPENVPN_THROW(crypto_alg, alg.name() << ": bad cipher for data channel use");
|
||||
return type;
|
||||
}
|
||||
|
||||
inline Type legal_dc_digest(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
if ((alg.flags() & (F_DIGEST|F_ALLOW_DC)) != (F_DIGEST|F_ALLOW_DC))
|
||||
OPENVPN_THROW(crypto_alg, alg.name() << ": bad digest for data channel use");
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific algorithm depends on an additional digest or not
|
||||
*
|
||||
* @param type CryptoAlgs::Type to check
|
||||
*
|
||||
* @return Returns true if the queried algorithm depends on a digest,
|
||||
* otherwise false. The check is done strictly against the
|
||||
* CryptoAlgs::AlgFlags F_NO_CIPHER_DIGEST flag.
|
||||
*/
|
||||
inline bool use_cipher_digest(const Type type)
|
||||
{
|
||||
const Alg& alg = get(type);
|
||||
return !(alg.flags() & F_NO_CIPHER_DIGEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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/>.
|
||||
|
||||
// Base class for OpenVPN data channel encryption/decryption
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CRYPTODC_H
|
||||
#define OPENVPN_CRYPTO_CRYPTODC_H
|
||||
|
||||
#include <utility> // for std::move
|
||||
#include <cstdint> // for std::uint32_t, etc.
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/error/error.hpp>
|
||||
#include <openvpn/common/rc.hpp>
|
||||
#include <openvpn/frame/frame.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/packet_id.hpp>
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
#include <openvpn/compress/compress.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
// Base class for encryption/decryption of data channel
|
||||
class CryptoDCInstance : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoDCInstance> Ptr;
|
||||
|
||||
// Encrypt/Decrypt
|
||||
|
||||
// returns true if packet ID is close to wrapping
|
||||
virtual bool encrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32) = 0;
|
||||
|
||||
virtual Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now, const unsigned char *op32) = 0;
|
||||
|
||||
// Initialization
|
||||
|
||||
// return value of defined()
|
||||
enum {
|
||||
CIPHER_DEFINED=(1<<0), // may call init_cipher method
|
||||
HMAC_DEFINED=(1<<1), // may call init_hmac method
|
||||
CRYPTO_DEFINED=(1<<2), // may call encrypt or decrypt methods
|
||||
EXPLICIT_EXIT_NOTIFY_DEFINED=(1<<3), // may call explicit_exit_notify method
|
||||
};
|
||||
virtual unsigned int defined() const = 0;
|
||||
|
||||
virtual void init_cipher(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key) = 0;
|
||||
|
||||
virtual void init_hmac(StaticKey&& encrypt_key,
|
||||
StaticKey&& decrypt_key) = 0;
|
||||
|
||||
virtual void init_pid(const int send_form,
|
||||
const int recv_mode,
|
||||
const int recv_form,
|
||||
const char *recv_name,
|
||||
const int recv_unit,
|
||||
const SessionStats::Ptr& recv_stats_arg) = 0;
|
||||
|
||||
virtual void init_remote_peer_id(const int remote_peer_id) {}
|
||||
|
||||
virtual bool consider_compression(const CompressContext& comp_ctx) = 0;
|
||||
|
||||
virtual void explicit_exit_notify() {}
|
||||
|
||||
// Rekeying
|
||||
|
||||
enum RekeyType {
|
||||
ACTIVATE_PRIMARY,
|
||||
ACTIVATE_PRIMARY_MOVE,
|
||||
NEW_SECONDARY,
|
||||
PRIMARY_SECONDARY_SWAP,
|
||||
DEACTIVATE_SECONDARY,
|
||||
DEACTIVATE_ALL,
|
||||
};
|
||||
|
||||
virtual void rekey(const RekeyType type) = 0;
|
||||
};
|
||||
|
||||
// Factory for CryptoDCInstance objects
|
||||
class CryptoDCContext : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoDCContext> Ptr;
|
||||
|
||||
virtual CryptoDCInstance::Ptr new_obj(const unsigned int key_id) = 0;
|
||||
|
||||
// cipher/HMAC/key info
|
||||
struct Info {
|
||||
Info() : cipher_alg(CryptoAlgs::NONE), hmac_alg(CryptoAlgs::NONE) {}
|
||||
CryptoAlgs::Type cipher_alg;
|
||||
CryptoAlgs::Type hmac_alg;
|
||||
};
|
||||
virtual Info crypto_info() = 0;
|
||||
|
||||
// Info for ProtoContext::link_mtu_adjust
|
||||
virtual size_t encap_overhead() const = 0;
|
||||
};
|
||||
|
||||
// Factory for CryptoDCContext objects
|
||||
class CryptoDCFactory : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoDCFactory> Ptr;
|
||||
|
||||
virtual CryptoDCContext::Ptr new_obj(const CryptoAlgs::Type cipher,
|
||||
const CryptoAlgs::Type digest) = 0;
|
||||
};
|
||||
|
||||
// Manage cipher/digest settings, DC factory, and DC context.
|
||||
class CryptoDCSettings
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(no_data_channel_factory);
|
||||
|
||||
CryptoDCSettings()
|
||||
: cipher_(CryptoAlgs::NONE),
|
||||
digest_(CryptoAlgs::NONE),
|
||||
dirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
void set_factory(const CryptoDCFactory::Ptr& factory)
|
||||
{
|
||||
factory_ = factory;
|
||||
context_.reset();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void set_cipher(const CryptoAlgs::Type cipher)
|
||||
{
|
||||
if (cipher != cipher_)
|
||||
{
|
||||
cipher_ = cipher;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_digest(const CryptoAlgs::Type digest)
|
||||
{
|
||||
if (digest != digest_)
|
||||
{
|
||||
digest_ = digest;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
CryptoDCContext& context()
|
||||
{
|
||||
if (!context_ || dirty)
|
||||
{
|
||||
if (!factory_)
|
||||
throw no_data_channel_factory();
|
||||
context_ = factory_->new_obj(cipher_, digest_);
|
||||
dirty = false;
|
||||
}
|
||||
return *context_;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
factory_.reset();
|
||||
context_.reset();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
CryptoAlgs::Type cipher() const { return cipher_; }
|
||||
|
||||
/**
|
||||
* Retrieve the digest configured for the data channel.
|
||||
* If the configured data channel cipher does not use any
|
||||
* additional digest, CryptoAlgs::NONE is returned.
|
||||
*
|
||||
* @return Returns the cipher digest in use
|
||||
*/
|
||||
CryptoAlgs::Type digest() const
|
||||
{
|
||||
return (CryptoAlgs::use_cipher_digest(cipher_) ? digest_ : CryptoAlgs::NONE);
|
||||
}
|
||||
|
||||
|
||||
CryptoDCFactory::Ptr factory() const { return factory_; }
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type cipher_;
|
||||
CryptoAlgs::Type digest_;
|
||||
CryptoDCFactory::Ptr factory_;
|
||||
CryptoDCContext::Ptr context_;
|
||||
bool dirty;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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/>.
|
||||
|
||||
// Select appropriate OpenVPN protocol data channel implementation
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_CRYPTODCSEL_H
|
||||
#define OPENVPN_CRYPTO_CRYPTODCSEL_H
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/crypto/cryptodc.hpp>
|
||||
#include <openvpn/crypto/crypto_chm.hpp>
|
||||
#include <openvpn/crypto/crypto_aead.hpp>
|
||||
#include <openvpn/random/randapi.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
OPENVPN_EXCEPTION(crypto_dc_select);
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoDCSelect : public CryptoDCFactory
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<CryptoDCSelect> Ptr;
|
||||
|
||||
CryptoDCSelect(const Frame::Ptr& frame_arg,
|
||||
const SessionStats::Ptr& stats_arg,
|
||||
const RandomAPI::Ptr& prng_arg)
|
||||
: frame(frame_arg),
|
||||
stats(stats_arg),
|
||||
prng(prng_arg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual CryptoDCContext::Ptr new_obj(const CryptoAlgs::Type cipher,
|
||||
const CryptoAlgs::Type digest)
|
||||
{
|
||||
const CryptoAlgs::Alg& alg = CryptoAlgs::get(cipher);
|
||||
if (alg.flags() & CryptoAlgs::CBC_HMAC)
|
||||
return new CryptoContextCHM<CRYPTO_API>(cipher, digest, frame, stats, prng);
|
||||
else if (alg.flags() & CryptoAlgs::AEAD)
|
||||
return new AEAD::CryptoContext<CRYPTO_API>(cipher, frame, stats);
|
||||
else
|
||||
OPENVPN_THROW(crypto_dc_select, alg.name() << ": only CBC/HMAC and AEAD cipher modes supported");
|
||||
}
|
||||
|
||||
private:
|
||||
Frame::Ptr frame;
|
||||
SessionStats::Ptr stats;
|
||||
RandomAPI::Ptr prng;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
// 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/>.
|
||||
|
||||
// General-purpose OpenVPN protocol decrypt method (CBC/HMAC) that is independent of the underlying CRYPTO_API
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_DECRYPT_CHM_H
|
||||
#define OPENVPN_CRYPTO_DECRYPT_CHM_H
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/memneq.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/frame/frame.hpp>
|
||||
#include <openvpn/crypto/cipher.hpp>
|
||||
#include <openvpn/crypto/ovpnhmac.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/packet_id.hpp>
|
||||
#include <openvpn/log/sessionstats.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class DecryptCHM {
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(chm_unsupported_cipher_mode);
|
||||
|
||||
Error::Type decrypt(BufferAllocated& buf, const PacketID::time_t now)
|
||||
{
|
||||
// skip null packets
|
||||
if (!buf.size())
|
||||
return Error::SUCCESS;
|
||||
|
||||
// verify the HMAC
|
||||
if (hmac.defined())
|
||||
{
|
||||
unsigned char local_hmac[CRYPTO_API::HMACContext::MAX_HMAC_SIZE];
|
||||
const size_t hmac_size = hmac.output_size();
|
||||
const unsigned char *packet_hmac = buf.read_alloc(hmac_size);
|
||||
hmac.hmac(local_hmac, hmac_size, buf.c_data(), buf.size());
|
||||
if (crypto::memneq(local_hmac, packet_hmac, hmac_size))
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::HMAC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt packet ID + payload
|
||||
if (cipher.defined())
|
||||
{
|
||||
unsigned char iv_buf[CRYPTO_API::CipherContext::MAX_IV_LENGTH];
|
||||
const size_t iv_length = cipher.iv_length();
|
||||
|
||||
// extract IV from head of packet
|
||||
buf.read(iv_buf, iv_length);
|
||||
|
||||
// initialize work buffer
|
||||
frame->prepare(Frame::DECRYPT_WORK, work);
|
||||
|
||||
// decrypt from buf -> work
|
||||
const size_t decrypt_bytes = cipher.decrypt(iv_buf, work.data(), work.max_size(), buf.c_data(), buf.size());
|
||||
if (!decrypt_bytes)
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::DECRYPT_ERROR;
|
||||
}
|
||||
work.set_size(decrypt_bytes);
|
||||
|
||||
// handle different cipher modes
|
||||
const int cipher_mode = cipher.cipher_mode();
|
||||
if (cipher_mode == CRYPTO_API::CipherContext::CIPH_CBC_MODE)
|
||||
{
|
||||
if (!verify_packet_id(work, now))
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::REPLAY_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw chm_unsupported_cipher_mode();
|
||||
}
|
||||
|
||||
// return cleartext result in buf
|
||||
buf.swap(work);
|
||||
}
|
||||
else // no encryption
|
||||
{
|
||||
if (!verify_packet_id(buf, now))
|
||||
{
|
||||
buf.reset_size();
|
||||
return Error::REPLAY_ERROR;
|
||||
}
|
||||
}
|
||||
return Error::SUCCESS;
|
||||
}
|
||||
|
||||
Frame::Ptr frame;
|
||||
CipherContext<CRYPTO_API> cipher;
|
||||
OvpnHMAC<CRYPTO_API> hmac;
|
||||
PacketIDReceive pid_recv;
|
||||
|
||||
private:
|
||||
bool verify_packet_id(BufferAllocated& buf, const PacketID::time_t now)
|
||||
{
|
||||
// ignore packet ID if pid_recv is not initialized
|
||||
if (pid_recv.initialized())
|
||||
{
|
||||
const PacketID pid = pid_recv.read_next(buf);
|
||||
if (!pid_recv.test_add(pid, now, true)) // verify packet ID
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BufferAllocated work;
|
||||
};
|
||||
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_DECRYPT_H
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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/>.
|
||||
|
||||
// Crypto digest/HMAC API
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_DIGESTAPI_H
|
||||
#define OPENVPN_CRYPTO_DIGESTAPI_H
|
||||
|
||||
#include <openvpn/common/rc.hpp>
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
// Digest/HMAC abstract base classes and factories
|
||||
|
||||
class DigestInstance : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<DigestInstance> Ptr;
|
||||
|
||||
virtual void update(const unsigned char *in, const size_t size) = 0;
|
||||
virtual size_t final(unsigned char *out) = 0;
|
||||
virtual size_t size() const = 0;
|
||||
};
|
||||
|
||||
class HMACInstance : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<HMACInstance> Ptr;
|
||||
|
||||
virtual void reset() = 0;
|
||||
virtual void update(const unsigned char *in, const size_t size) = 0;
|
||||
virtual size_t final(unsigned char *out) = 0;
|
||||
virtual size_t size() const = 0;
|
||||
};
|
||||
|
||||
class DigestContext : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<DigestContext> Ptr;
|
||||
|
||||
virtual std::string name() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
virtual DigestInstance::Ptr new_digest() = 0;
|
||||
|
||||
virtual HMACInstance::Ptr new_hmac(const unsigned char *key,
|
||||
const size_t key_size) = 0;
|
||||
};
|
||||
|
||||
class DigestFactory : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<DigestFactory> Ptr;
|
||||
|
||||
virtual DigestContext::Ptr new_context(const CryptoAlgs::Type digest_type) = 0;
|
||||
|
||||
virtual DigestInstance::Ptr new_digest(const CryptoAlgs::Type digest_type) = 0;
|
||||
|
||||
virtual HMACInstance::Ptr new_hmac(const CryptoAlgs::Type digest_type,
|
||||
const unsigned char *key,
|
||||
const size_t key_size) = 0;
|
||||
};
|
||||
|
||||
// Digest implementation using CRYPTO_API
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoDigestInstance : public DigestInstance
|
||||
{
|
||||
public:
|
||||
CryptoDigestInstance(const CryptoAlgs::Type digest)
|
||||
: impl(digest)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void update(const unsigned char *in, const size_t size)
|
||||
{
|
||||
impl.update(in, size);
|
||||
}
|
||||
|
||||
virtual size_t final(unsigned char *out)
|
||||
{
|
||||
return impl.final(out);
|
||||
}
|
||||
|
||||
virtual size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
private:
|
||||
typename CRYPTO_API::DigestContext impl;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoHMACInstance : public HMACInstance
|
||||
{
|
||||
public:
|
||||
CryptoHMACInstance(const CryptoAlgs::Type digest,
|
||||
const unsigned char *key,
|
||||
const size_t key_size)
|
||||
: impl(digest, key, key_size)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void reset()
|
||||
{
|
||||
impl.reset();
|
||||
}
|
||||
|
||||
virtual void update(const unsigned char *in, const size_t size)
|
||||
{
|
||||
impl.update(in, size);
|
||||
}
|
||||
|
||||
virtual size_t final(unsigned char *out)
|
||||
{
|
||||
return impl.final(out);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
private:
|
||||
typename CRYPTO_API::HMACContext impl;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoDigestContext : public DigestContext
|
||||
{
|
||||
public:
|
||||
CryptoDigestContext(const CryptoAlgs::Type digest_type)
|
||||
: digest(digest_type)
|
||||
{
|
||||
}
|
||||
|
||||
virtual std::string name() const
|
||||
{
|
||||
return CryptoAlgs::name(digest);
|
||||
}
|
||||
|
||||
virtual size_t size() const
|
||||
{
|
||||
return CryptoAlgs::size(digest);
|
||||
}
|
||||
|
||||
virtual DigestInstance::Ptr new_digest()
|
||||
{
|
||||
return new CryptoDigestInstance<CRYPTO_API>(digest);
|
||||
}
|
||||
|
||||
virtual HMACInstance::Ptr new_hmac(const unsigned char *key,
|
||||
const size_t key_size)
|
||||
{
|
||||
return new CryptoHMACInstance<CRYPTO_API>(digest,
|
||||
key,
|
||||
key_size);
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type digest;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoDigestFactory : public DigestFactory
|
||||
{
|
||||
public:
|
||||
virtual DigestContext::Ptr new_context(const CryptoAlgs::Type digest_type)
|
||||
{
|
||||
return new CryptoDigestContext<CRYPTO_API>(digest_type);
|
||||
}
|
||||
|
||||
virtual DigestInstance::Ptr new_digest(const CryptoAlgs::Type digest_type)
|
||||
{
|
||||
return new CryptoDigestInstance<CRYPTO_API>(digest_type);
|
||||
}
|
||||
|
||||
virtual HMACInstance::Ptr new_hmac(const CryptoAlgs::Type digest_type,
|
||||
const unsigned char *key,
|
||||
const size_t key_size)
|
||||
{
|
||||
return new CryptoHMACInstance<CRYPTO_API>(digest_type,
|
||||
key,
|
||||
key_size);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,136 @@
|
||||
// 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/>.
|
||||
|
||||
// General-purpose OpenVPN protocol encrypt method (CBC/HMAC) that is independent of the underlying CRYPTO_API
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_ENCRYPT_CHM_H
|
||||
#define OPENVPN_CRYPTO_ENCRYPT_CHM_H
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/random/randapi.hpp>
|
||||
#include <openvpn/frame/frame.hpp>
|
||||
#include <openvpn/crypto/cipher.hpp>
|
||||
#include <openvpn/crypto/ovpnhmac.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/packet_id.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
template <typename CRYPTO_API>
|
||||
class EncryptCHM {
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(chm_unsupported_cipher_mode);
|
||||
|
||||
void encrypt(BufferAllocated& buf, const PacketID::time_t now)
|
||||
{
|
||||
// skip null packets
|
||||
if (!buf.size())
|
||||
return;
|
||||
|
||||
if (cipher.defined())
|
||||
{
|
||||
// workspace for generating IV
|
||||
unsigned char iv_buf[CRYPTO_API::CipherContext::MAX_IV_LENGTH];
|
||||
const size_t iv_length = cipher.iv_length();
|
||||
|
||||
// IV and packet ID are generated differently depending on cipher mode
|
||||
const int cipher_mode = cipher.cipher_mode();
|
||||
if (cipher_mode == CRYPTO_API::CipherContext::CIPH_CBC_MODE)
|
||||
{
|
||||
// in CBC mode, use an explicit, random IV
|
||||
prng->rand_bytes(iv_buf, iv_length);
|
||||
|
||||
// generate fresh outgoing packet ID and prepend to cleartext buffer
|
||||
pid_send.write_next(buf, true, now);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw chm_unsupported_cipher_mode();
|
||||
}
|
||||
|
||||
// initialize work buffer
|
||||
frame->prepare(Frame::ENCRYPT_WORK, work);
|
||||
|
||||
// encrypt from buf -> work
|
||||
const size_t encrypt_bytes = cipher.encrypt(iv_buf, work.data(), work.max_size(), buf.c_data(), buf.size());
|
||||
if (!encrypt_bytes)
|
||||
{
|
||||
buf.reset_size();
|
||||
return;
|
||||
}
|
||||
work.set_size(encrypt_bytes);
|
||||
|
||||
// prepend the IV to the ciphertext
|
||||
work.prepend(iv_buf, iv_length);
|
||||
|
||||
// HMAC the ciphertext
|
||||
prepend_hmac(work);
|
||||
|
||||
// return ciphertext result in buf
|
||||
buf.swap(work);
|
||||
}
|
||||
else // no encryption
|
||||
{
|
||||
// generate fresh outgoing packet ID and prepend to cleartext buffer
|
||||
pid_send.write_next(buf, true, now);
|
||||
|
||||
// HMAC the cleartext
|
||||
prepend_hmac(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void set_prng(RandomAPI::Ptr prng_arg)
|
||||
{
|
||||
prng_arg->assert_crypto();
|
||||
prng = std::move(prng_arg);
|
||||
}
|
||||
|
||||
Frame::Ptr frame;
|
||||
CipherContext<CRYPTO_API> cipher;
|
||||
OvpnHMAC<CRYPTO_API> hmac;
|
||||
PacketIDSend pid_send;
|
||||
|
||||
private:
|
||||
// compute HMAC signature of data buffer,
|
||||
// then prepend the signature to the buffer.
|
||||
void prepend_hmac(BufferAllocated& buf)
|
||||
{
|
||||
if (hmac.defined())
|
||||
{
|
||||
const unsigned char *content = buf.data();
|
||||
const size_t content_size = buf.size();
|
||||
const size_t hmac_size = hmac.output_size();
|
||||
unsigned char *hmac_buf = buf.prepend_alloc(hmac_size);
|
||||
hmac.hmac(hmac_buf, hmac_size, content, content_size);
|
||||
}
|
||||
}
|
||||
|
||||
BufferAllocated work;
|
||||
RandomAPI::Ptr prng;
|
||||
};
|
||||
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_ENCRYPT_H
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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/>.
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_HASHSTR_H
|
||||
#define OPENVPN_CRYPTO_HASHSTR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/common/hexstr.hpp>
|
||||
#include <openvpn/common/base64.hpp>
|
||||
#include <openvpn/crypto/digestapi.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
class HashString
|
||||
{
|
||||
public:
|
||||
HashString(DigestFactory& digest_factory,
|
||||
const CryptoAlgs::Type digest_type)
|
||||
: ctx(digest_factory.new_digest(digest_type))
|
||||
{
|
||||
}
|
||||
|
||||
void update(const std::string& str)
|
||||
{
|
||||
ctx->update((unsigned char *)str.c_str(), str.length());
|
||||
}
|
||||
|
||||
void update(const char *str)
|
||||
{
|
||||
ctx->update((unsigned char *)str, std::strlen(str));
|
||||
}
|
||||
|
||||
void update(const char c)
|
||||
{
|
||||
ctx->update((unsigned char *)&c, 1);
|
||||
}
|
||||
|
||||
void update(const Buffer& buf)
|
||||
{
|
||||
ctx->update(buf.c_data(), buf.size());
|
||||
}
|
||||
|
||||
BufferPtr final()
|
||||
{
|
||||
BufferPtr ret(new BufferAllocated(ctx->size(), BufferAllocated::ARRAY));
|
||||
ctx->final(ret->data());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void final(Buffer& output)
|
||||
{
|
||||
const size_t size = ctx->size();
|
||||
if (size > output.max_size())
|
||||
OPENVPN_BUFFER_THROW(buffer_overflow);
|
||||
ctx->final(output.data());
|
||||
output.set_size(size);
|
||||
}
|
||||
|
||||
std::string final_hex()
|
||||
{
|
||||
BufferPtr bp = final();
|
||||
return render_hex_generic(*bp);
|
||||
}
|
||||
|
||||
std::string final_base64()
|
||||
{
|
||||
BufferPtr bp = final();
|
||||
return base64->encode(*bp);
|
||||
}
|
||||
|
||||
private:
|
||||
DigestInstance::Ptr ctx;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,240 @@
|
||||
// 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/>.
|
||||
|
||||
// OpenVPN HMAC classes
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_OVPNHMAC_H
|
||||
#define OPENVPN_CRYPTO_OVPNHMAC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/rc.hpp>
|
||||
#include <openvpn/common/memneq.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
// OpenVPN protocol HMAC usage for HMAC/CBC integrity checking and tls-auth
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class OvpnHMAC
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(ovpn_hmac_context_digest_size);
|
||||
OPENVPN_SIMPLE_EXCEPTION(ovpn_hmac_context_bad_sizing);
|
||||
|
||||
public:
|
||||
OvpnHMAC() {}
|
||||
|
||||
OvpnHMAC(const CryptoAlgs::Type digest, const StaticKey& key)
|
||||
{
|
||||
init(digest, key);
|
||||
}
|
||||
|
||||
bool defined() const { return ctx.is_initialized(); }
|
||||
|
||||
// size of out buffer to pass to hmac
|
||||
size_t output_size() const
|
||||
{
|
||||
return ctx.size();
|
||||
}
|
||||
|
||||
void init(const CryptoAlgs::Type digest, const StaticKey& key)
|
||||
{
|
||||
const CryptoAlgs::Alg& alg = CryptoAlgs::get(digest);
|
||||
|
||||
// check that key is large enough
|
||||
if (key.size() < alg.size())
|
||||
throw ovpn_hmac_context_digest_size();
|
||||
|
||||
// initialize HMAC context with digest type and key
|
||||
ctx.init(digest, key.data(), alg.size());
|
||||
}
|
||||
|
||||
void hmac(unsigned char *out, const size_t out_size,
|
||||
const unsigned char *in, const size_t in_size)
|
||||
{
|
||||
ctx.reset();
|
||||
ctx.update(in, in_size);
|
||||
ctx.final(out);
|
||||
}
|
||||
|
||||
// Special HMAC for OpenVPN control packets
|
||||
|
||||
void ovpn_hmac_gen(unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3)
|
||||
{
|
||||
if (ovpn_hmac_pre(data, data_size, l1, l2, l3))
|
||||
ctx.final(data + l1);
|
||||
else
|
||||
throw ovpn_hmac_context_bad_sizing();
|
||||
}
|
||||
|
||||
// verify the HMAC generated by ovpn_hmac_gen, return true if verified
|
||||
bool ovpn_hmac_cmp(const unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3)
|
||||
{
|
||||
unsigned char local_hmac[CRYPTO_API::HMACContext::MAX_HMAC_SIZE];
|
||||
if (ovpn_hmac_pre(data, data_size, l1, l2, l3))
|
||||
{
|
||||
ctx.final(local_hmac);
|
||||
return !crypto::memneq(data + l1, local_hmac, l2);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Convoluting OpenVPN control channel packets for HMAC:
|
||||
// <-- L1 --> <-L2> <L3>
|
||||
// [OP] [PSID] [HMAC] [PID] [...] -> canonical order
|
||||
//
|
||||
// [HMAC] [PID] [OP] [PSID] [...] -> HMAC order
|
||||
|
||||
bool ovpn_hmac_pre(const unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3)
|
||||
{
|
||||
const size_t lsum = l1 + l2 + l3;
|
||||
if (lsum > data_size || l2 != ctx.size())
|
||||
return false;
|
||||
ctx.reset();
|
||||
ctx.update(data + l1 + l2, l3);
|
||||
ctx.update(data, l1);
|
||||
ctx.update(data + lsum, data_size - lsum);
|
||||
return true;
|
||||
}
|
||||
|
||||
typename CRYPTO_API::HMACContext ctx;
|
||||
};
|
||||
|
||||
// OvpnHMAC wrapper API using dynamic polymorphism
|
||||
|
||||
class OvpnHMACInstance : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<OvpnHMACInstance> Ptr;
|
||||
|
||||
virtual void init(const StaticKey& key) = 0;
|
||||
|
||||
virtual size_t output_size() const = 0;
|
||||
|
||||
virtual void ovpn_hmac_gen(unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3) = 0;
|
||||
|
||||
virtual bool ovpn_hmac_cmp(const unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3) = 0;
|
||||
};
|
||||
|
||||
class OvpnHMACContext : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<OvpnHMACContext> Ptr;
|
||||
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
virtual OvpnHMACInstance::Ptr new_obj() = 0;
|
||||
};
|
||||
|
||||
class OvpnHMACFactory : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<OvpnHMACFactory> Ptr;
|
||||
|
||||
virtual OvpnHMACContext::Ptr new_obj(const CryptoAlgs::Type digest_type) = 0;
|
||||
};
|
||||
|
||||
// OvpnHMAC wrapper implementation using dynamic polymorphism
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoOvpnHMACInstance : public OvpnHMACInstance
|
||||
{
|
||||
public:
|
||||
CryptoOvpnHMACInstance(const CryptoAlgs::Type digest_arg)
|
||||
: digest(digest_arg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void init(const StaticKey& key)
|
||||
{
|
||||
ovpn_hmac.init(digest, key);
|
||||
}
|
||||
|
||||
virtual size_t output_size() const
|
||||
{
|
||||
return ovpn_hmac.output_size();
|
||||
}
|
||||
|
||||
virtual void ovpn_hmac_gen(unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3)
|
||||
{
|
||||
ovpn_hmac.ovpn_hmac_gen(data, data_size, l1, l2, l3);
|
||||
}
|
||||
|
||||
virtual bool ovpn_hmac_cmp(const unsigned char *data, const size_t data_size,
|
||||
const size_t l1, const size_t l2, const size_t l3)
|
||||
{
|
||||
return ovpn_hmac.ovpn_hmac_cmp(data, data_size, l1, l2, l3);
|
||||
}
|
||||
|
||||
private:
|
||||
typename CryptoAlgs::Type digest;
|
||||
OvpnHMAC<CRYPTO_API> ovpn_hmac;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoOvpnHMACContext : public OvpnHMACContext
|
||||
{
|
||||
public:
|
||||
CryptoOvpnHMACContext(const CryptoAlgs::Type digest_type)
|
||||
: digest(CryptoAlgs::legal_dc_digest(digest_type))
|
||||
{
|
||||
}
|
||||
|
||||
virtual size_t size() const
|
||||
{
|
||||
return CryptoAlgs::size(digest);
|
||||
}
|
||||
|
||||
virtual OvpnHMACInstance::Ptr new_obj()
|
||||
{
|
||||
return new CryptoOvpnHMACInstance<CRYPTO_API>(digest);
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type digest;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoOvpnHMACFactory : public OvpnHMACFactory
|
||||
{
|
||||
public:
|
||||
virtual OvpnHMACContext::Ptr new_obj(const CryptoAlgs::Type digest_type)
|
||||
{
|
||||
return new CryptoOvpnHMACContext<CRYPTO_API>(digest_type);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,447 @@
|
||||
// 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/>.
|
||||
|
||||
// Manage OpenVPN protocol Packet IDs for packet replay detection
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_PACKET_ID_H
|
||||
#define OPENVPN_CRYPTO_PACKET_ID_H
|
||||
|
||||
#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/time/time.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/log/sessionstats.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
/*
|
||||
* Communicate packet-id over the wire.
|
||||
* A short packet-id is just a 32 bit
|
||||
* sequence number. A long packet-id
|
||||
* includes a timestamp as well.
|
||||
*
|
||||
* Long packet-ids are used as IVs for
|
||||
* CFB/OFB ciphers.
|
||||
*
|
||||
* This data structure is always sent
|
||||
* over the net in network byte order,
|
||||
* by calling htonpid, ntohpid,
|
||||
* htontime, and ntohtime on the
|
||||
* data elements to change them
|
||||
* to and from standard sizes.
|
||||
*
|
||||
* In addition, time is converted to
|
||||
* a PacketID::net_time_t before sending,
|
||||
* since openvpn always
|
||||
* uses a 32-bit time_t but some
|
||||
* 64 bit platforms use a
|
||||
* 64 bit time_t.
|
||||
*/
|
||||
struct PacketID
|
||||
{
|
||||
typedef std::uint32_t id_t;
|
||||
typedef std::uint32_t net_time_t;
|
||||
typedef Time::base_type time_t;
|
||||
|
||||
enum {
|
||||
SHORT_FORM = 0, // short form of ID (4 bytes)
|
||||
LONG_FORM = 1, // long form of ID (8 bytes)
|
||||
|
||||
UNDEF = 0, // special undefined/null id_t value
|
||||
};
|
||||
|
||||
id_t id; // legal values are 1 through 2^32-1
|
||||
time_t time; // converted to PacketID::net_time_t before transmission
|
||||
|
||||
static size_t size(const int form)
|
||||
{
|
||||
if (form == PacketID::LONG_FORM)
|
||||
return longidsize;
|
||||
else
|
||||
return shortidsize;
|
||||
}
|
||||
|
||||
constexpr static size_t shortidsize = sizeof(id_t);
|
||||
constexpr static size_t longidsize = sizeof(id_t) + sizeof(net_time_t);
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return id != UNDEF;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
id = id_t(0);
|
||||
time = time_t(0);
|
||||
}
|
||||
|
||||
void read(Buffer& buf, const int form)
|
||||
{
|
||||
id_t net_id;
|
||||
net_time_t net_time;
|
||||
|
||||
buf.read ((unsigned char *)&net_id, sizeof (net_id));
|
||||
id = ntohl (net_id);
|
||||
|
||||
if (form == LONG_FORM)
|
||||
{
|
||||
buf.read ((unsigned char *)&net_time, sizeof (net_time));
|
||||
time = ntohl (net_time);
|
||||
}
|
||||
else
|
||||
time = time_t(0);
|
||||
}
|
||||
|
||||
void write(Buffer& buf, const int form, const bool prepend) const
|
||||
{
|
||||
const id_t net_id = htonl(id);
|
||||
const net_time_t net_time = htonl(time);
|
||||
|
||||
if (prepend)
|
||||
{
|
||||
if (form == LONG_FORM)
|
||||
buf.prepend ((unsigned char *)&net_time, sizeof (net_time));
|
||||
buf.prepend ((unsigned char *)&net_id, sizeof (net_id));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.write ((unsigned char *)&net_id, sizeof (net_id));
|
||||
if (form == LONG_FORM)
|
||||
buf.write ((unsigned char *)&net_time, sizeof (net_time));
|
||||
}
|
||||
}
|
||||
|
||||
std::string str() const
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "[" << time << "," << id << "]";
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct PacketIDConstruct : public PacketID
|
||||
{
|
||||
PacketIDConstruct(const PacketID::time_t v_time = PacketID::time_t(0), const PacketID::id_t v_id = PacketID::id_t(0))
|
||||
{
|
||||
id = v_id;
|
||||
time = v_time;
|
||||
}
|
||||
};
|
||||
|
||||
class PacketIDSend
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(packet_id_wrap);
|
||||
|
||||
PacketIDSend()
|
||||
{
|
||||
init(PacketID::SHORT_FORM);
|
||||
}
|
||||
|
||||
void init(const int form) // PacketID::LONG_FORM or PacketID::SHORT_FORM
|
||||
{
|
||||
pid_.id = PacketID::id_t(0);
|
||||
pid_.time = PacketID::time_t(0);
|
||||
form_ = form;
|
||||
}
|
||||
|
||||
PacketID next(const PacketID::time_t now)
|
||||
{
|
||||
PacketID ret;
|
||||
if (!pid_.time)
|
||||
pid_.time = now;
|
||||
ret.id = ++pid_.id;
|
||||
if (unlikely(!pid_.id)) // wraparound
|
||||
{
|
||||
if (form_ != PacketID::LONG_FORM)
|
||||
throw packet_id_wrap();
|
||||
pid_.time = now;
|
||||
ret.id = pid_.id = 1;
|
||||
}
|
||||
ret.time = pid_.time;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void write_next(Buffer& buf, const bool prepend, const PacketID::time_t now)
|
||||
{
|
||||
const PacketID pid = next(now);
|
||||
pid.write(buf, form_, prepend);
|
||||
}
|
||||
|
||||
/*
|
||||
* In TLS mode, when a packet ID gets to this level,
|
||||
* start thinking about triggering a new
|
||||
* SSL/TLS handshake.
|
||||
*/
|
||||
bool wrap_warning() const
|
||||
{
|
||||
const PacketID::id_t wrap_at = 0xFF000000;
|
||||
return pid_.id >= wrap_at;
|
||||
}
|
||||
|
||||
std::string str() const
|
||||
{
|
||||
std::string ret;
|
||||
ret = pid_.str();
|
||||
if (form_ == PacketID::LONG_FORM)
|
||||
ret += 'L';
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
PacketID pid_;
|
||||
int form_;
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the data structure we keep on the receiving side,
|
||||
* to check that no packet-id (i.e. sequence number + optional timestamp)
|
||||
* 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 PacketIDReceiveType
|
||||
{
|
||||
public:
|
||||
static constexpr unsigned int REPLAY_WINDOW_BYTES = 1 << REPLAY_WINDOW_ORDER;
|
||||
static constexpr unsigned int REPLAY_WINDOW_SIZE = REPLAY_WINDOW_BYTES * 8;
|
||||
|
||||
// mode
|
||||
enum {
|
||||
UDP_MODE = 0,
|
||||
TCP_MODE = 1
|
||||
};
|
||||
|
||||
OPENVPN_SIMPLE_EXCEPTION(packet_id_not_initialized);
|
||||
|
||||
PacketIDReceiveType()
|
||||
: initialized_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void init(const int mode_arg,
|
||||
const int form_arg,
|
||||
const char *name_arg,
|
||||
const int unit_arg,
|
||||
const SessionStats::Ptr& stats_arg)
|
||||
{
|
||||
initialized_ = true;
|
||||
base = 0;
|
||||
extent = 0;
|
||||
expire = 0;
|
||||
id_high = 0;
|
||||
time_high = 0;
|
||||
id_floor = 0;
|
||||
max_backtrack = 0;
|
||||
mode = mode_arg;
|
||||
form = form_arg;
|
||||
unit = unit_arg;
|
||||
name = name_arg;
|
||||
stats = stats_arg;
|
||||
std::memset(history, 0, sizeof(history));
|
||||
}
|
||||
|
||||
bool initialized() const
|
||||
{
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
bool test_add(const PacketID& pin,
|
||||
const PacketID::time_t now,
|
||||
const bool mod) // don't modify history unless mod is true
|
||||
{
|
||||
const Error::Type err = do_test_add(pin, now, mod);
|
||||
if (unlikely(err != Error::SUCCESS))
|
||||
{
|
||||
stats->error(err);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
Error::Type do_test_add(const PacketID& pin,
|
||||
const PacketID::time_t now,
|
||||
const bool mod) // don't modify history unless mod is true
|
||||
{
|
||||
// make sure we were initialized
|
||||
if (unlikely(!initialized_))
|
||||
throw packet_id_not_initialized();
|
||||
|
||||
// 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;
|
||||
|
||||
// time changed?
|
||||
if (unlikely(pin.time != time_high))
|
||||
{
|
||||
if (pin.time > time_high)
|
||||
{
|
||||
// time moved forward, accept
|
||||
if (!mod)
|
||||
return Error::SUCCESS;
|
||||
base = 0;
|
||||
extent = 0;
|
||||
id_high = 0;
|
||||
time_high = pin.time;
|
||||
id_floor = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// time moved backward, reject
|
||||
return Error::PKTID_TIME_BACKTRACK;
|
||||
}
|
||||
}
|
||||
|
||||
if (likely(pin.id == id_high + 1))
|
||||
{
|
||||
// well-formed ID sequence (incremented by 1)
|
||||
if (!mod)
|
||||
return Error::SUCCESS;
|
||||
base = REPLAY_INDEX(-1);
|
||||
history[base / 8] |= (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
|
||||
if (!mod)
|
||||
return Error::SUCCESS;
|
||||
const unsigned int delta = pin.id - id_high;
|
||||
if (delta < REPLAY_WINDOW_SIZE)
|
||||
{
|
||||
base = REPLAY_INDEX(-delta);
|
||||
history[base / 8] |= (1 << (base % 8));
|
||||
extent += delta;
|
||||
if (extent > REPLAY_WINDOW_SIZE)
|
||||
extent = REPLAY_WINDOW_SIZE;
|
||||
for (unsigned i = 1; i < delta; ++i)
|
||||
{
|
||||
const unsigned int newbase = REPLAY_INDEX(i);
|
||||
history[newbase / 8] &= ~(1 << (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 unsigned int delta = id_high - pin.id;
|
||||
if (delta > max_backtrack)
|
||||
max_backtrack = delta;
|
||||
if (delta < extent)
|
||||
{
|
||||
if (pin.id > id_floor)
|
||||
{
|
||||
const unsigned int ri = REPLAY_INDEX(delta);
|
||||
std::uint8_t *p = &history[ri / 8];
|
||||
const std::uint8_t mask = (1 << (ri % 8));
|
||||
if (*p & mask)
|
||||
return Error::PKTID_REPLAY;
|
||||
if (!mod)
|
||||
return Error::SUCCESS;
|
||||
*p |= mask;
|
||||
}
|
||||
else
|
||||
return Error::PKTID_EXPIRE;
|
||||
}
|
||||
else
|
||||
return Error::PKTID_BACKTRACK;
|
||||
}
|
||||
|
||||
return Error::SUCCESS;
|
||||
}
|
||||
|
||||
PacketID read_next(Buffer& buf) const
|
||||
{
|
||||
if (!initialized_)
|
||||
throw packet_id_not_initialized();
|
||||
PacketID pid;
|
||||
pid.read(buf, form);
|
||||
return pid;
|
||||
}
|
||||
|
||||
std::string str() const
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "[e=" << extent << " f=" << id_floor << " h=" << time_high << '/' << id_high << ']';
|
||||
return os.str();
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int REPLAY_INDEX(const int i) const
|
||||
{
|
||||
return (base + i) & (REPLAY_WINDOW_SIZE - 1);
|
||||
}
|
||||
|
||||
bool initialized_;
|
||||
|
||||
unsigned int base; // bit position of deque base in history
|
||||
unsigned int extent; // extent (in bits) of deque in history
|
||||
PacketID::time_t expire; // expiration of history
|
||||
PacketID::id_t id_high; // highest sequence number received
|
||||
PacketID::time_t time_high; // highest time stamp received
|
||||
PacketID::id_t id_floor; // we will only accept backtrack IDs > id_floor
|
||||
unsigned int max_backtrack;
|
||||
|
||||
int mode; // UDP_MODE or TCP_MODE
|
||||
int form; // PacketID::LONG_FORM or PacketID::SHORT_FORM
|
||||
int unit; // unit number of this object (for debugging)
|
||||
std::string name; // name of this object (for debugging)
|
||||
|
||||
SessionStats::Ptr stats;
|
||||
|
||||
std::uint8_t history[REPLAY_WINDOW_BYTES]; /* "sliding window" bitmask of recent packet IDs received */
|
||||
};
|
||||
|
||||
// Our standard packet ID window with order=8 (window size=2048).
|
||||
// and recv expire=30 seconds.
|
||||
typedef PacketIDReceiveType<8, 30> PacketIDReceive;
|
||||
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_PACKET_ID_H
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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/>.
|
||||
|
||||
// A general purpose container for OpenVPN protocol encrypt and decrypt objects.
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_SELFTEST_H
|
||||
#define OPENVPN_CRYPTO_SELFTEST_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
//#include <openvpn/openssl/util/selftest.hpp>
|
||||
#endif
|
||||
|
||||
#ifdef USE_APPLE_SSL
|
||||
//#include <openvpn/applecrypto/util/selftest.hpp>
|
||||
#endif
|
||||
|
||||
#ifdef USE_MBEDTLS
|
||||
#include <openvpn/mbedtls/util/selftest.hpp>
|
||||
#endif
|
||||
|
||||
#ifdef USE_MBEDTLS_APPLE_HYBRID
|
||||
//#include <openvpn/applecrypto/util/selftest.hpp>
|
||||
#include <openvpn/mbedtls/util/selftest.hpp>
|
||||
#endif
|
||||
|
||||
namespace openvpn {
|
||||
namespace SelfTest {
|
||||
inline std::string crypto_self_test()
|
||||
{
|
||||
std::string ret;
|
||||
# ifdef USE_OPENSSL
|
||||
//ret += crypto_self_test_openssl();
|
||||
# endif
|
||||
# ifdef USE_APPLE_SSL
|
||||
//ret += crypto_self_test_apple();
|
||||
# endif
|
||||
# if defined(USE_MBEDTLS) || defined(USE_MBEDTLS_APPLE_HYBRID)
|
||||
ret += crypto_self_test_mbedtls();
|
||||
# endif
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_CRYPTO_H
|
||||
@@ -0,0 +1,183 @@
|
||||
// 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/>.
|
||||
|
||||
// Classes for handling OpenVPN static keys (and tls-auth keys)
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_STATIC_KEY_H
|
||||
#define OPENVPN_CRYPTO_STATIC_KEY_H
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/hexstr.hpp>
|
||||
#include <openvpn/common/file.hpp>
|
||||
#include <openvpn/common/splitlines.hpp>
|
||||
#include <openvpn/common/base64.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/random/randapi.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
class StaticKey
|
||||
{
|
||||
friend class OpenVPNStaticKey;
|
||||
typedef BufferAllocated key_t;
|
||||
|
||||
public:
|
||||
StaticKey() {}
|
||||
StaticKey(const unsigned char *key_data, const size_t key_size)
|
||||
: key_data_(key_data, key_size, key_t::DESTRUCT_ZERO) {}
|
||||
|
||||
size_t size() const { return key_data_.size(); }
|
||||
const unsigned char *data() const { return key_data_.c_data(); }
|
||||
void erase() { key_data_.clear(); }
|
||||
|
||||
std::string render_hex() const { return openvpn::render_hex_generic(key_data_); }
|
||||
|
||||
void parse_from_base64(const std::string& b64, const size_t capacity)
|
||||
{
|
||||
key_data_.reset(capacity, key_t::DESTRUCT_ZERO);
|
||||
base64->decode(key_data_, b64);
|
||||
}
|
||||
|
||||
std::string render_to_base64() const
|
||||
{
|
||||
return base64->encode(key_data_);
|
||||
}
|
||||
|
||||
void init_from_rng(RandomAPI& rng, const size_t key_size)
|
||||
{
|
||||
rng.assert_crypto();
|
||||
key_data_.init(key_size, key_t::DESTRUCT_ZERO);
|
||||
rng.rand_bytes(key_data_.data(), key_size);
|
||||
key_data_.set_size(key_size);
|
||||
}
|
||||
|
||||
private:
|
||||
key_t key_data_;
|
||||
};
|
||||
|
||||
class OpenVPNStaticKey
|
||||
{
|
||||
typedef StaticKey::key_t key_t;
|
||||
|
||||
public:
|
||||
enum {
|
||||
KEY_SIZE = 256 // bytes
|
||||
};
|
||||
|
||||
// key specifier
|
||||
enum {
|
||||
// key for cipher and hmac
|
||||
CIPHER = 0,
|
||||
HMAC = (1<<0),
|
||||
|
||||
// do we want to encrypt or decrypt with this key
|
||||
ENCRYPT = 0,
|
||||
DECRYPT = (1<<1),
|
||||
|
||||
// key direction
|
||||
NORMAL = 0,
|
||||
INVERSE = (1<<2)
|
||||
};
|
||||
|
||||
OPENVPN_SIMPLE_EXCEPTION(static_key_parse_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(static_key_bad_size);
|
||||
|
||||
bool defined() const { return key_data_.defined(); }
|
||||
|
||||
StaticKey slice(unsigned int key_specifier) const
|
||||
{
|
||||
if (key_data_.size() != KEY_SIZE)
|
||||
throw static_key_bad_size();
|
||||
static const unsigned char key_table[] = { 0, 1, 2, 3, 2, 3, 0, 1 };
|
||||
const unsigned int idx = key_table[key_specifier & 7] * 64;
|
||||
return StaticKey(key_data_.c_data() + idx, KEY_SIZE / 4);
|
||||
}
|
||||
|
||||
void parse_from_file(const std::string& filename)
|
||||
{
|
||||
const std::string str = read_text(filename);
|
||||
parse(str);
|
||||
}
|
||||
|
||||
void parse(const std::string& key_text)
|
||||
{
|
||||
SplitLines in(key_text, 0);
|
||||
key_t data(KEY_SIZE, key_t::DESTRUCT_ZERO);
|
||||
bool in_body = false;
|
||||
while (in(true))
|
||||
{
|
||||
const std::string& line = in.line_ref();
|
||||
if (line == static_key_head())
|
||||
in_body = true;
|
||||
else if (line == static_key_foot())
|
||||
in_body = false;
|
||||
else if (in_body)
|
||||
parse_hex(data, line);
|
||||
}
|
||||
if (in_body || data.size() != KEY_SIZE)
|
||||
throw static_key_parse_error();
|
||||
key_data_ = data;
|
||||
}
|
||||
|
||||
std::string render() const
|
||||
{
|
||||
if (key_data_.size() != KEY_SIZE)
|
||||
throw static_key_bad_size();
|
||||
std::ostringstream out;
|
||||
out << static_key_head() << "\n";
|
||||
for (size_t i = 0; i < KEY_SIZE; i += 16)
|
||||
out << render_hex(key_data_.c_data() + i, 16) << "\n";
|
||||
out << static_key_foot() << "\n";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
unsigned char *raw_alloc()
|
||||
{
|
||||
key_data_.init(KEY_SIZE, key_t::DESTRUCT_ZERO|key_t::ARRAY);
|
||||
return key_data_.data();
|
||||
}
|
||||
|
||||
void erase()
|
||||
{
|
||||
key_data_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
static const char *static_key_head()
|
||||
{
|
||||
return "-----BEGIN OpenVPN Static key V1-----";
|
||||
}
|
||||
|
||||
static const char *static_key_foot()
|
||||
{
|
||||
return "-----END OpenVPN Static key V1-----";
|
||||
}
|
||||
|
||||
key_t key_data_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace openvpn
|
||||
|
||||
#endif // OPENVPN_CRYPTO_STATIC_KEY_H
|
||||
@@ -0,0 +1,323 @@
|
||||
// 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/>.
|
||||
|
||||
// OpenVPN TLS-Crypt classes
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_TLSCRYPT_H
|
||||
#define OPENVPN_CRYPTO_TLSCRYPT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/rc.hpp>
|
||||
#include <openvpn/common/memneq.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/cryptoalgs.hpp>
|
||||
#include <openvpn/crypto/packet_id.hpp>
|
||||
#include <openvpn/ssl/psid.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
|
||||
// OpenVPN protocol HMAC usage for HMAC/CTR integrity checking and tls-crypt
|
||||
|
||||
// Control packet format when tls-crypt is enabled:
|
||||
// [OP] [PSID] [PID] [HMAC] [...]
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class TLSCrypt
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(ovpn_tls_crypt_context_digest_size);
|
||||
OPENVPN_SIMPLE_EXCEPTION(ovpn_tls_crypt_context_bad_sizing);
|
||||
OPENVPN_SIMPLE_EXCEPTION(ovpn_tls_crypt_wrong_mode);
|
||||
|
||||
TLSCrypt() : mode(CRYPTO_API::CipherContext::MODE_UNDEF) {}
|
||||
|
||||
TLSCrypt(const CryptoAlgs::Type digest, const StaticKey& key_hmac,
|
||||
const CryptoAlgs::Type cipher, const StaticKey& key_crypt,
|
||||
const int mode)
|
||||
{
|
||||
init(digest, key_hmac, cipher, key_crypt, mode);
|
||||
}
|
||||
|
||||
bool defined() const { return ctx_hmac.is_initialized() && ctx_crypt.is_initialized(); }
|
||||
|
||||
// size of out buffer to pass to hmac
|
||||
size_t output_hmac_size() const
|
||||
{
|
||||
return ctx_hmac.size();
|
||||
}
|
||||
|
||||
void init(const CryptoAlgs::Type digest, const StaticKey& key_hmac,
|
||||
const CryptoAlgs::Type cipher, const StaticKey& key_crypt,
|
||||
const int mode_arg)
|
||||
{
|
||||
const CryptoAlgs::Alg& alg_hmac = CryptoAlgs::get(digest);
|
||||
|
||||
// check that key is large enough
|
||||
if (key_hmac.size() < alg_hmac.size())
|
||||
throw ovpn_tls_crypt_context_digest_size();
|
||||
|
||||
// initialize HMAC context with digest type and key
|
||||
ctx_hmac.init(digest, key_hmac.data(), alg_hmac.size());
|
||||
|
||||
// initialize Cipher context with cipher, key and mode
|
||||
ctx_crypt.init(cipher, key_crypt.data(), mode_arg);
|
||||
|
||||
mode = mode_arg;
|
||||
}
|
||||
|
||||
bool hmac_gen(unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len)
|
||||
{
|
||||
hmac_pre(header, header_len, payload, payload_len);
|
||||
ctx_hmac.final(header + header_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hmac_cmp(const unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len)
|
||||
{
|
||||
unsigned char local_hmac[CRYPTO_API::HMACContext::MAX_HMAC_SIZE];
|
||||
|
||||
hmac_pre(header, header_len, payload, payload_len);
|
||||
ctx_hmac.final(local_hmac);
|
||||
|
||||
return !crypto::memneq(header + header_len, local_hmac, output_hmac_size());
|
||||
}
|
||||
|
||||
size_t encrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen)
|
||||
{
|
||||
if (mode != CRYPTO_API::CipherContext::ENCRYPT)
|
||||
throw ovpn_tls_crypt_wrong_mode();
|
||||
|
||||
return encrypt_decrypt(iv, out, olen, in, ilen);
|
||||
}
|
||||
|
||||
size_t decrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen)
|
||||
{
|
||||
if (mode != CRYPTO_API::CipherContext::DECRYPT)
|
||||
throw ovpn_tls_crypt_wrong_mode();
|
||||
|
||||
return encrypt_decrypt(iv, out, olen, in, ilen);
|
||||
}
|
||||
|
||||
private:
|
||||
// assume length check on header has already been performed
|
||||
void hmac_pre(const unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len)
|
||||
{
|
||||
ctx_hmac.reset();
|
||||
ctx_hmac.update(header, header_len);
|
||||
ctx_hmac.update(payload, payload_len);
|
||||
}
|
||||
|
||||
size_t encrypt_decrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen)
|
||||
{
|
||||
ctx_crypt.reset(iv);
|
||||
|
||||
size_t outlen = 0;
|
||||
|
||||
if (!ctx_crypt.update(out, olen, in, ilen, outlen))
|
||||
return 0;
|
||||
|
||||
if (!ctx_crypt.final(out + outlen, olen - outlen, outlen))
|
||||
return 0;
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
typename CRYPTO_API::HMACContext ctx_hmac;
|
||||
typename CRYPTO_API::CipherContext ctx_crypt;
|
||||
int mode;
|
||||
};
|
||||
|
||||
// OvpnHMAC wrapper API using dynamic polymorphism
|
||||
|
||||
class TLSCryptInstance : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<TLSCryptInstance> Ptr;
|
||||
|
||||
virtual void init(const StaticKey& key_hmac, const StaticKey& key_crypt) = 0;
|
||||
|
||||
virtual size_t output_hmac_size() const = 0;
|
||||
|
||||
virtual bool hmac_gen(unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len) = 0;
|
||||
|
||||
virtual bool hmac_cmp(const unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len) = 0;
|
||||
|
||||
virtual size_t encrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen) = 0;
|
||||
|
||||
virtual size_t decrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen) = 0;
|
||||
};
|
||||
|
||||
class TLSCryptContext : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<TLSCryptContext> Ptr;
|
||||
|
||||
virtual size_t digest_size() const = 0;
|
||||
|
||||
virtual size_t cipher_key_size() const = 0;
|
||||
|
||||
virtual TLSCryptInstance::Ptr new_obj_send() = 0;
|
||||
|
||||
virtual TLSCryptInstance::Ptr new_obj_recv() = 0;
|
||||
|
||||
// This is the size of the header in a TLSCrypt-wrapped packets,
|
||||
// excluding the HMAC. Format:
|
||||
//
|
||||
// [OP] [PSID] [PID] [HMAC] [...]
|
||||
//
|
||||
|
||||
constexpr const static size_t hmac_offset = 1 + ProtoSessionID::SIZE + PacketID::longidsize;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class TLSCryptFactory : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<TLSCryptFactory> Ptr;
|
||||
|
||||
virtual TLSCryptContext::Ptr new_obj(const CryptoAlgs::Type digest_type,
|
||||
const CryptoAlgs::Type cipher_type) = 0;
|
||||
};
|
||||
|
||||
// TLSCrypt wrapper implementation using dynamic polymorphism
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoTLSCryptInstance : public TLSCryptInstance
|
||||
{
|
||||
public:
|
||||
CryptoTLSCryptInstance(const CryptoAlgs::Type digest_arg,
|
||||
const CryptoAlgs::Type cipher_arg,
|
||||
int mode_arg)
|
||||
: digest(digest_arg),
|
||||
cipher(cipher_arg),
|
||||
mode(mode_arg)
|
||||
{
|
||||
}
|
||||
|
||||
void init(const StaticKey& key_hmac, const StaticKey& key_crypt)
|
||||
{
|
||||
tls_crypt.init(digest, key_hmac, cipher, key_crypt, mode);
|
||||
}
|
||||
|
||||
size_t output_hmac_size() const
|
||||
{
|
||||
return tls_crypt.output_hmac_size();
|
||||
}
|
||||
|
||||
bool hmac_gen(unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len)
|
||||
{
|
||||
return tls_crypt.hmac_gen(header, header_len, payload, payload_len);
|
||||
}
|
||||
|
||||
// verify the HMAC generated by hmac_gen, return true if verified
|
||||
bool hmac_cmp(const unsigned char *header, const size_t header_len,
|
||||
const unsigned char *payload, const size_t payload_len)
|
||||
{
|
||||
return tls_crypt.hmac_cmp(header, header_len, payload, payload_len);
|
||||
}
|
||||
|
||||
size_t encrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen)
|
||||
{
|
||||
return tls_crypt.encrypt(iv, out, olen, in, ilen);
|
||||
}
|
||||
|
||||
size_t decrypt(const unsigned char *iv, unsigned char *out, const size_t olen,
|
||||
const unsigned char *in, const size_t ilen)
|
||||
{
|
||||
return tls_crypt.decrypt(iv, out, olen, in, ilen);
|
||||
}
|
||||
|
||||
private:
|
||||
typename CryptoAlgs::Type digest;
|
||||
typename CryptoAlgs::Type cipher;
|
||||
int mode;
|
||||
TLSCrypt<CRYPTO_API> tls_crypt;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoTLSCryptContext : public TLSCryptContext
|
||||
{
|
||||
public:
|
||||
CryptoTLSCryptContext(const CryptoAlgs::Type digest_type,
|
||||
const CryptoAlgs::Type cipher_type)
|
||||
: digest(digest_type),
|
||||
cipher(cipher_type)
|
||||
{
|
||||
}
|
||||
|
||||
virtual size_t digest_size() const
|
||||
{
|
||||
return CryptoAlgs::size(digest);
|
||||
}
|
||||
|
||||
virtual size_t cipher_key_size() const
|
||||
{
|
||||
return CryptoAlgs::key_length(cipher);
|
||||
}
|
||||
|
||||
virtual TLSCryptInstance::Ptr new_obj_send()
|
||||
{
|
||||
return new CryptoTLSCryptInstance<CRYPTO_API>(digest, cipher,
|
||||
CRYPTO_API::CipherContext::ENCRYPT);
|
||||
}
|
||||
|
||||
virtual TLSCryptInstance::Ptr new_obj_recv()
|
||||
{
|
||||
return new CryptoTLSCryptInstance<CRYPTO_API>(digest, cipher,
|
||||
CRYPTO_API::CipherContext::DECRYPT);
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoAlgs::Type digest;
|
||||
CryptoAlgs::Type cipher;
|
||||
};
|
||||
|
||||
template <typename CRYPTO_API>
|
||||
class CryptoTLSCryptFactory : public TLSCryptFactory
|
||||
{
|
||||
public:
|
||||
virtual TLSCryptContext::Ptr new_obj(const CryptoAlgs::Type digest_type,
|
||||
const CryptoAlgs::Type cipher_type)
|
||||
{
|
||||
return new CryptoTLSCryptContext<CRYPTO_API>(digest_type, cipher_type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,196 @@
|
||||
// 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) 2017-2018 OpenVPN Technologies, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Classes for handling OpenVPN tls-crypt-v2 internals
|
||||
|
||||
#ifndef OPENVPN_CRYPTO_TLS_CRYPT_V2_H
|
||||
#define OPENVPN_CRYPTO_TLS_CRYPT_V2_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/crypto/static_key.hpp>
|
||||
#include <openvpn/crypto/tls_crypt.hpp>
|
||||
#include <openvpn/ssl/sslchoose.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
constexpr static const char* tls_crypt_v2_server_key_name = "OpenVPN tls-crypt-v2 server key";
|
||||
constexpr static const char* tls_crypt_v2_client_key_name = "OpenVPN tls-crypt-v2 client key";
|
||||
|
||||
class TLSCryptV2ServerKey
|
||||
{
|
||||
public:
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_server_key_parse_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_server_key_encode_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_server_key_bad_size);
|
||||
|
||||
TLSCryptV2ServerKey()
|
||||
: key_size(128),
|
||||
key(key_size, BufferAllocated::DESTRUCT_ZERO)
|
||||
{}
|
||||
|
||||
bool defined() const
|
||||
{
|
||||
return key.defined();
|
||||
}
|
||||
|
||||
void parse(const std::string& key_text)
|
||||
{
|
||||
if (!SSLLib::PEMAPI::pem_decode(key, key_text.c_str(), key_text.length(),
|
||||
tls_crypt_v2_server_key_name))
|
||||
throw tls_crypt_v2_server_key_parse_error();
|
||||
|
||||
if (key.size() != key_size)
|
||||
throw tls_crypt_v2_server_key_bad_size();
|
||||
}
|
||||
|
||||
void extract_key(OpenVPNStaticKey& tls_key)
|
||||
{
|
||||
std::memcpy(tls_key.raw_alloc(), key.c_data(), key_size);
|
||||
}
|
||||
|
||||
std::string render() const
|
||||
{
|
||||
BufferAllocated data(32 + 2 * key.size(), 0);
|
||||
|
||||
if (!SSLLib::PEMAPI::pem_encode(data, key.c_data(), key.size(),
|
||||
tls_crypt_v2_server_key_name))
|
||||
throw tls_crypt_v2_server_key_encode_error();
|
||||
|
||||
return std::string((const char *)data.c_data());
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t key_size;
|
||||
BufferAllocated key;
|
||||
};
|
||||
|
||||
|
||||
class TLSCryptV2ClientKey
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
WKC_MAX_SIZE = 1024, // bytes
|
||||
};
|
||||
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_client_key_parse_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_client_key_encode_error);
|
||||
OPENVPN_SIMPLE_EXCEPTION(tls_crypt_v2_client_key_bad_size);
|
||||
|
||||
TLSCryptV2ClientKey() = delete;
|
||||
|
||||
TLSCryptV2ClientKey(TLSCryptContext::Ptr context)
|
||||
: key_size(OpenVPNStaticKey::KEY_SIZE),
|
||||
tag_size(context->digest_size())
|
||||
{}
|
||||
|
||||
bool defined() const
|
||||
{
|
||||
return key.defined() && wkc.defined();
|
||||
}
|
||||
|
||||
void parse(const std::string& key_text)
|
||||
{
|
||||
BufferAllocated data(key_size + WKC_MAX_SIZE, BufferAllocated::DESTRUCT_ZERO);
|
||||
|
||||
if (!SSLLib::PEMAPI::pem_decode(data, key_text.c_str(), key_text.length(),
|
||||
tls_crypt_v2_client_key_name))
|
||||
throw tls_crypt_v2_client_key_parse_error();
|
||||
|
||||
if (data.size() < (tag_size + key_size))
|
||||
throw tls_crypt_v2_client_key_bad_size();
|
||||
|
||||
key.init(data.data(), key_size, BufferAllocated::DESTRUCT_ZERO);
|
||||
wkc.init(data.data() + key_size, data.size() - key_size, BufferAllocated::DESTRUCT_ZERO);
|
||||
}
|
||||
|
||||
void extract_key(OpenVPNStaticKey& tls_key)
|
||||
{
|
||||
std::memcpy(tls_key.raw_alloc(), key.c_data(), key_size);
|
||||
}
|
||||
|
||||
std::string render() const
|
||||
{
|
||||
BufferAllocated data(32 + 2 * (key.size() + wkc.size()), 0);
|
||||
BufferAllocated in(key, BufferAllocated::GROW);
|
||||
in.append(wkc);
|
||||
|
||||
if (!SSLLib::PEMAPI::pem_encode(data, in.c_data(), in.size(),
|
||||
tls_crypt_v2_client_key_name))
|
||||
throw tls_crypt_v2_client_key_encode_error();
|
||||
|
||||
return std::string((const char *)data.c_data());
|
||||
}
|
||||
|
||||
void extract_wkc(BufferAllocated& wkc_out) const
|
||||
{
|
||||
wkc_out = wkc;
|
||||
}
|
||||
|
||||
private:
|
||||
BufferAllocated key;
|
||||
BufferAllocated wkc;
|
||||
|
||||
const size_t key_size;
|
||||
const size_t tag_size;
|
||||
};
|
||||
|
||||
// the user can extend the TLSCryptMetadata and the TLSCryptMetadataFactory
|
||||
// classes to implement its own metadata verification method.
|
||||
//
|
||||
// default method is to *ignore* the metadata contained in the WKc sent by the client
|
||||
class TLSCryptMetadata : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<TLSCryptMetadata> Ptr;
|
||||
|
||||
// override this method with your own verification mechanism.
|
||||
//
|
||||
// If type is -1 it means that metadata is empty.
|
||||
//
|
||||
virtual bool verify(int type, Buffer& metadata) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// abstract class to be extended when creating other factories
|
||||
class TLSCryptMetadataFactory : public RC<thread_unsafe_refcount>
|
||||
{
|
||||
public:
|
||||
typedef RCPtr<TLSCryptMetadataFactory> Ptr;
|
||||
|
||||
virtual TLSCryptMetadata::Ptr new_obj() = 0;
|
||||
};
|
||||
|
||||
// factory implementation for the basic verification method
|
||||
class CryptoTLSCryptMetadataFactory : public TLSCryptMetadataFactory
|
||||
{
|
||||
public:
|
||||
TLSCryptMetadata::Ptr new_obj()
|
||||
{
|
||||
return new TLSCryptMetadata();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* OPENVPN_CRYPTO_TLS_CRYPT_V2_H */
|
||||
Reference in New Issue
Block a user