Merge commit '86cc97e55fe346502462284d2e636a2b3708163e' as 'Sources/OpenVPN3'

This commit is contained in:
Sergey Abramchuk
2020-02-24 14:43:11 +03:00
655 changed files with 146468 additions and 0 deletions
@@ -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
+133
View File
@@ -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 */