mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-02-11 00:00:08 +08:00
390 lines
11 KiB
C++
390 lines
11 KiB
C++
// OpenVPN -- An application to securely tunnel IP networks
|
|
// over a single port, with support for SSL/TLS-based
|
|
// session authentication and key exchange,
|
|
// packet encryption, packet authentication, and
|
|
// packet compression.
|
|
//
|
|
// Copyright (C) 2012-2018 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/>.
|
|
|
|
// kovpn crypto wrappers
|
|
|
|
#ifndef OPENVPN_KOVPN_KOCRYPTO_H
|
|
#define OPENVPN_KOVPN_KOCRYPTO_H
|
|
|
|
#include <cstring> // for std::memset, std::memcpy
|
|
#include <utility> // for std::move
|
|
|
|
#include <openvpn/common/size.hpp>
|
|
#include <openvpn/common/exception.hpp>
|
|
#include <openvpn/common/rc.hpp>
|
|
#include <openvpn/frame/frame.hpp>
|
|
#include <openvpn/crypto/cryptodc.hpp>
|
|
#include <openvpn/crypto/bs64_data_limit.hpp>
|
|
#include <openvpn/kovpn/kovpn.hpp>
|
|
|
|
namespace openvpn {
|
|
namespace KoRekey {
|
|
|
|
OPENVPN_EXCEPTION(korekey_error);
|
|
|
|
struct Info {
|
|
Info() {}
|
|
|
|
Info(const CryptoDCContext::Ptr& dc_context_delegate_arg,
|
|
const unsigned int key_id_arg,
|
|
const Frame::Ptr& frame_arg)
|
|
: dc_context_delegate(dc_context_delegate_arg),
|
|
key_id(key_id_arg),
|
|
frame(frame_arg)
|
|
{
|
|
}
|
|
|
|
CryptoDCContext::Ptr dc_context_delegate;
|
|
CompressContext comp_ctx;
|
|
unsigned int key_id = 0;
|
|
int remote_peer_id = -1;
|
|
bool tcp_linear = false;
|
|
StaticKey encrypt_cipher;
|
|
StaticKey encrypt_hmac;
|
|
StaticKey decrypt_cipher;
|
|
StaticKey decrypt_hmac;
|
|
Frame::Ptr frame;
|
|
};
|
|
|
|
class Key
|
|
{
|
|
// noncopyable because of "opk.primary = &key" below
|
|
Key(const Key&) = delete;
|
|
Key& operator=(const Key&) = delete;
|
|
|
|
public:
|
|
static void validate(const CryptoAlgs::Type cipher,
|
|
const CryptoAlgs::Type digest)
|
|
{
|
|
const CryptoAlgs::Alg& calg = CryptoAlgs::get(cipher);
|
|
const CryptoAlgs::Alg& halg = CryptoAlgs::get(digest);
|
|
|
|
switch (cipher)
|
|
{
|
|
case CryptoAlgs::AES_128_GCM:
|
|
case CryptoAlgs::AES_192_GCM:
|
|
case CryptoAlgs::AES_256_GCM:
|
|
case CryptoAlgs::AES_128_CBC:
|
|
case CryptoAlgs::AES_192_CBC:
|
|
case CryptoAlgs::AES_256_CBC:
|
|
case CryptoAlgs::BF_CBC:
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "cipher alg " << calg.name() << " is not currently supported by kovpn");
|
|
}
|
|
|
|
if (calg.mode() == CryptoAlgs::CBC_HMAC)
|
|
{
|
|
switch (digest)
|
|
{
|
|
case CryptoAlgs::SHA1:
|
|
case CryptoAlgs::SHA256:
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "HMAC alg " << halg.name() << " is not currently supported by kovpn");
|
|
}
|
|
}
|
|
}
|
|
|
|
Key(const CryptoDCInstance::RekeyType rktype,
|
|
const Info& rkinfo, // must remain in scope for duration of Key lifetime
|
|
const int peer_id,
|
|
const bool verbose)
|
|
{
|
|
std::memset(&opk, 0, sizeof(opk));
|
|
|
|
// set peer ID
|
|
opk.peer_id = peer_id;
|
|
|
|
// set rekey op
|
|
bool new_key = false;
|
|
bool secondary_key = false; // only relevant for non-deactivate ops to secondary
|
|
switch (rktype)
|
|
{
|
|
case CryptoDCInstance::ACTIVATE_PRIMARY:
|
|
{
|
|
new_key = true;
|
|
opk.op = OVPN_KEYS_PRIMARY_ONLY;
|
|
break;
|
|
}
|
|
case CryptoDCInstance::ACTIVATE_PRIMARY_MOVE:
|
|
{
|
|
new_key = true;
|
|
opk.op = OVPN_KEYS_PRIMARY_ASSIGN_MOVE;
|
|
break;
|
|
}
|
|
case CryptoDCInstance::NEW_SECONDARY:
|
|
{
|
|
new_key = true;
|
|
secondary_key = true;
|
|
opk.op = OVPN_KEYS_SECONDARY_ONLY;
|
|
break;
|
|
}
|
|
case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
|
|
{
|
|
opk.op = OVPN_KEYS_PRIMARY_SECONDARY_SWAP;
|
|
break;
|
|
}
|
|
case CryptoDCInstance::DEACTIVATE_SECONDARY:
|
|
{
|
|
opk.op = OVPN_KEYS_SECONDARY_ONLY;
|
|
break;
|
|
}
|
|
case CryptoDCInstance::DEACTIVATE_ALL:
|
|
{
|
|
opk.op = OVPN_KEYS_BOTH;
|
|
break;
|
|
}
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "unrecognized rekey type=" << (int)rktype);
|
|
}
|
|
|
|
if (new_key)
|
|
{
|
|
const CryptoDCContext::Info ci = rkinfo.dc_context_delegate->crypto_info();
|
|
const CryptoAlgs::Alg& calg = CryptoAlgs::get(ci.cipher_alg);
|
|
|
|
// set crypto family
|
|
switch (calg.mode())
|
|
{
|
|
case CryptoAlgs::CBC_HMAC:
|
|
opk.crypto_family = OVPN_CRYPTO_FAMILY_CBC_HMAC;
|
|
break;
|
|
case CryptoAlgs::AEAD:
|
|
opk.crypto_family = OVPN_CRYPTO_FAMILY_AEAD;
|
|
break;
|
|
default:
|
|
opk.crypto_family = OVPN_CRYPTO_FAMILY_UNDEF;
|
|
break;
|
|
}
|
|
|
|
std::memset(&key, 0, sizeof(key));
|
|
key.key_id = rkinfo.key_id;
|
|
key.remote_peer_id = rkinfo.remote_peer_id;
|
|
|
|
switch (ci.cipher_alg)
|
|
{
|
|
case CryptoAlgs::AES_128_GCM:
|
|
key.cipher_alg = OVPN_ALG_AES_GCM;
|
|
key.encrypt.cipher_key_size = 128 / 8;
|
|
break;
|
|
case CryptoAlgs::AES_192_GCM:
|
|
key.cipher_alg = OVPN_ALG_AES_GCM;
|
|
key.encrypt.cipher_key_size = 192 / 8;
|
|
break;
|
|
case CryptoAlgs::AES_256_GCM:
|
|
key.cipher_alg = OVPN_ALG_AES_GCM;
|
|
key.encrypt.cipher_key_size = 256 / 8;
|
|
break;
|
|
case CryptoAlgs::AES_128_CBC:
|
|
key.cipher_alg = OVPN_ALG_AES_CBC;
|
|
key.encrypt.cipher_key_size = 128 / 8;
|
|
break;
|
|
case CryptoAlgs::AES_192_CBC:
|
|
key.cipher_alg = OVPN_ALG_AES_CBC;
|
|
key.encrypt.cipher_key_size = 192 / 8;
|
|
break;
|
|
case CryptoAlgs::AES_256_CBC:
|
|
key.cipher_alg = OVPN_ALG_AES_CBC;
|
|
key.encrypt.cipher_key_size = 256 / 8;
|
|
break;
|
|
case CryptoAlgs::BF_CBC:
|
|
key.cipher_alg = OVPN_ALG_BF_CBC;
|
|
key.encrypt.cipher_key_size = 128 / 8;
|
|
|
|
// special data limits for 64-bit block-size ciphers (CVE-2016-6329)
|
|
key.encrypt.data_limit = key.decrypt.data_limit = OPENVPN_BS64_DATA_LIMIT;
|
|
break;
|
|
default:
|
|
key.cipher_alg = OVPN_ALG_UNDEF;
|
|
break;
|
|
}
|
|
key.decrypt.cipher_key_size = key.encrypt.cipher_key_size;
|
|
|
|
// make sure that chosen cipher/family is supported
|
|
if (opk.crypto_family == OVPN_CRYPTO_FAMILY_UNDEF
|
|
|| key.cipher_alg == OVPN_ALG_UNDEF)
|
|
OPENVPN_THROW(korekey_error, "cipher alg " << calg.name() << " is not currently supported by kovpn");
|
|
|
|
// set cipher keys
|
|
key.encrypt.cipher_key = verify_key("cipher encrypt",
|
|
rkinfo.encrypt_cipher,
|
|
key.encrypt.cipher_key_size);
|
|
key.decrypt.cipher_key = verify_key("cipher decrypt",
|
|
rkinfo.decrypt_cipher,
|
|
key.decrypt.cipher_key_size);
|
|
|
|
switch (calg.mode())
|
|
{
|
|
case CryptoAlgs::CBC_HMAC:
|
|
{
|
|
// if CBC mode, process HMAC digest
|
|
const CryptoAlgs::Alg& halg = CryptoAlgs::get(ci.hmac_alg);
|
|
switch (ci.hmac_alg)
|
|
{
|
|
case CryptoAlgs::SHA1:
|
|
key.hmac_alg = OVPN_ALG_HMAC_SHA1;
|
|
break;
|
|
case CryptoAlgs::SHA256:
|
|
key.hmac_alg = OVPN_ALG_HMAC_SHA256;
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "HMAC alg " << halg.name() << " is not currently supported by kovpn");
|
|
}
|
|
key.encrypt.hmac_key_size = halg.size();
|
|
key.decrypt.hmac_key_size = key.encrypt.hmac_key_size;
|
|
|
|
// set hmac keys
|
|
key.encrypt.hmac_key = verify_key("hmac encrypt",
|
|
rkinfo.encrypt_hmac,
|
|
key.encrypt.hmac_key_size);
|
|
key.decrypt.hmac_key = verify_key("hmac decrypt",
|
|
rkinfo.decrypt_hmac,
|
|
key.decrypt.hmac_key_size);
|
|
|
|
// handle compression V1
|
|
switch (rkinfo.comp_ctx.type())
|
|
{
|
|
case CompressContext::LZO_STUB:
|
|
key.compress.alg = OVPN_COMP_NONE;
|
|
key.compress.swap = false;
|
|
break;
|
|
case CompressContext::COMP_STUB:
|
|
key.compress.alg = OVPN_COMP_NONE;
|
|
key.compress.swap = true;
|
|
break;
|
|
case CompressContext::LZ4:
|
|
key.compress.alg = OVPN_COMP_LZ4;
|
|
key.compress.swap = true;
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "Compression alg " << rkinfo.comp_ctx.str() << " is not supported by kovpn in CBC/HMAC mode");
|
|
}
|
|
break;
|
|
}
|
|
case CryptoAlgs::AEAD:
|
|
{
|
|
// if AEAD mode, copy nonce tail from the HMAC key material
|
|
set_nonce_tail("AEAD nonce tail encrypt",
|
|
key.encrypt.nonce_tail,
|
|
sizeof(key.encrypt.nonce_tail),
|
|
rkinfo.encrypt_hmac);
|
|
set_nonce_tail("AEAD nonce tail decrypt",
|
|
key.decrypt.nonce_tail,
|
|
sizeof(key.decrypt.nonce_tail),
|
|
rkinfo.decrypt_hmac);
|
|
|
|
// handle compression V2
|
|
switch (rkinfo.comp_ctx.type())
|
|
{
|
|
case CompressContext::COMP_STUBv2:
|
|
key.compress.alg = OVPN_COMP_NONE;
|
|
break;
|
|
case CompressContext::LZ4v2:
|
|
key.compress.alg = OVPN_COMP_LZ4;
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(korekey_error, "Compression alg " << rkinfo.comp_ctx.str() << " is not supported by kovpn in AEAD mode");
|
|
}
|
|
key.compress.swap = false;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// should have been caught above
|
|
throw korekey_error("internal error");
|
|
}
|
|
}
|
|
|
|
// handle compression
|
|
key.compress.asym = rkinfo.comp_ctx.asym();
|
|
key.compress.max_decompress_size = (*rkinfo.frame)[Frame::DECOMPRESS_WORK].payload();
|
|
|
|
// handle TCP linear
|
|
key.tcp_linear = rkinfo.tcp_linear;
|
|
|
|
if (verbose)
|
|
OPENVPN_LOG("KOREKEY"
|
|
<< " op=" << int(rktype) << '/' << opk.op
|
|
<< " rpid=" << key.remote_peer_id
|
|
<< " pri=" << key.key_id
|
|
<< " cipher=" << key.cipher_alg
|
|
<< "[e=" << render_hex(key.encrypt.cipher_key, 8)
|
|
<< " d=" << render_hex(key.decrypt.cipher_key, 8) << ']'
|
|
<< " hmac=" << key.hmac_alg
|
|
<< "[e=" << render_hex(key.encrypt.hmac_key, 8)
|
|
<< " d=" << render_hex(key.decrypt.hmac_key, 8) << ']'
|
|
<< " comp=" << key.compress.alg
|
|
<< " swap=" << key.compress.swap
|
|
<< " asym=" << key.compress.asym
|
|
<< " tcp_linear=" << key.tcp_linear
|
|
<< " dl=[e=" << key.encrypt.data_limit
|
|
<< " d=" << key.decrypt.data_limit << ']');
|
|
|
|
// set key
|
|
if (secondary_key)
|
|
opk.secondary = &key;
|
|
else
|
|
opk.primary = &key;
|
|
}
|
|
else if (verbose)
|
|
{
|
|
OPENVPN_LOG("KOREKEY" << " op=" << int(rktype) << '/' << opk.op);
|
|
}
|
|
}
|
|
|
|
const struct ovpn_peer_keys_reset *operator()() const
|
|
{
|
|
return &opk;
|
|
}
|
|
|
|
private:
|
|
const unsigned char *verify_key(const char *title, const StaticKey& sk, const size_t size_required)
|
|
{
|
|
if (sk.size() < size_required)
|
|
OPENVPN_THROW(korekey_error, title << ": insufficient key material, provided=" << sk.size() << " required=" << size_required);
|
|
return sk.data();
|
|
}
|
|
|
|
void set_nonce_tail(const char *title, unsigned char *dest, const size_t dest_size, const StaticKey& src)
|
|
{
|
|
const int NONCE_TAIL_SIZE = CryptoAlgs::AEAD_NONCE_TAIL_SIZE;
|
|
|
|
const unsigned char *k = verify_key(title, src, NONCE_TAIL_SIZE);
|
|
if (dest_size < NONCE_TAIL_SIZE)
|
|
OPENVPN_THROW(korekey_error, title << ": cannot set");
|
|
std::memcpy(dest, k, NONCE_TAIL_SIZE);
|
|
|
|
// if dest is larger than NONCE_TAIL_SIZE, zero remaining bytes
|
|
if (dest_size > NONCE_TAIL_SIZE)
|
|
std::memset(dest + NONCE_TAIL_SIZE, 0, dest_size - NONCE_TAIL_SIZE);
|
|
}
|
|
|
|
struct ovpn_peer_keys_reset opk;
|
|
struct ovpn_key_config key;
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|