mirror of
https://github.com/OpenVPN/openvpn3.git
synced 2024-09-20 04:02:15 +02:00
ovpn-dco: init data channel keys
Implement OvpnDcoRekey, which parses key info into format consumed by ovpn-dco. Use KoRekey abstractions to hook into protocol layer and get notified about rekeying events. Pass new key to kernel or swap keys when commanded by protocol layer. Implement ovpn-dco netlink commands: - OVPN_CMD_NEW_KEY - OVPN_CMD_DEL_KEY - OVPN_CMD_SWAP_KEYS Signed-off-by: Lev Stipakov <lev@openvpn.net>
This commit is contained in:
parent
9a15079d25
commit
60e43763a4
@ -39,9 +39,9 @@
|
||||
#ifdef ENABLE_KOVPN
|
||||
#include <openvpn/kovpn/kovpn.hpp>
|
||||
#include <openvpn/kovpn/kodev.hpp>
|
||||
#include <openvpn/kovpn/korekey.hpp>
|
||||
#include <openvpn/kovpn/kostats.hpp>
|
||||
#elif ENABLE_OVPNDCO
|
||||
#include <openvpn/dco/key.hpp>
|
||||
#include <openvpn/tun/linux/client/sitnl.hpp>
|
||||
#include <openvpn/common/uniqueptr.hpp>
|
||||
#include <openvpn/tun/linux/client/genl.hpp>
|
||||
@ -49,6 +49,8 @@
|
||||
#error either ENABLE_KOVPN or ENABLE_OVPNDCO must be defined
|
||||
#endif
|
||||
|
||||
#include <openvpn/kovpn/korekey.hpp>
|
||||
|
||||
#ifdef ENABLE_PG
|
||||
#include <openvpn/kovpn/kodevtun.hpp>
|
||||
#endif
|
||||
|
47
openvpn/dco/key.hpp
Normal file
47
openvpn/dco/key.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
// 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-2020 OpenVPN Inc.
|
||||
// Copyright (C) 2020-2020 Lev Stipakov <lev@openvpn.net>
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace openvpn {
|
||||
namespace KoRekey {
|
||||
|
||||
struct KeyDirection {
|
||||
const unsigned char *cipher_key;
|
||||
const unsigned char *hmac_key; // only CBC
|
||||
unsigned char nonce_tail[12]; // only GCM
|
||||
unsigned int cipher_key_size;
|
||||
unsigned int hmac_key_size; // only CBC
|
||||
};
|
||||
|
||||
struct KeyConfig {
|
||||
KeyDirection encrypt;
|
||||
KeyDirection decrypt;
|
||||
|
||||
int key_id;
|
||||
int remote_peer_id;
|
||||
unsigned int cipher_alg;
|
||||
unsigned int hmac_alg; // only CBC
|
||||
};
|
||||
|
||||
} // namespace KoRekey
|
||||
} // namespace openvpn
|
@ -22,7 +22,7 @@
|
||||
|
||||
// tun/transport client for ovpn-dco
|
||||
|
||||
class OvpnDcoClient : public Client {
|
||||
class OvpnDcoClient : public Client, public KoRekey::Receiver {
|
||||
friend class ClientConfig;
|
||||
friend class GeNL;
|
||||
|
||||
@ -60,6 +60,11 @@ public:
|
||||
// execute commands to bring up interface
|
||||
add_cmds->execute_log();
|
||||
|
||||
// Add a hook so ProtoContext will call back to
|
||||
// rekey() on rekey ops.
|
||||
dc_settings.set_factory(CryptoDCFactory::Ptr(new KoRekey::Factory(
|
||||
dc_settings.factory(), this, config->transport.frame)));
|
||||
|
||||
// signal that we are connected
|
||||
tun_parent->tun_connected();
|
||||
}
|
||||
@ -127,6 +132,47 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void rekey(const CryptoDCInstance::RekeyType rktype,
|
||||
const KoRekey::Info &rkinfo) override {
|
||||
if (halt)
|
||||
return;
|
||||
|
||||
rekey_impl(rktype, rkinfo);
|
||||
}
|
||||
|
||||
void rekey_impl(const CryptoDCInstance::RekeyType rktype,
|
||||
const KoRekey::Info &rkinfo) {
|
||||
KoRekey::OvpnDcoKey key(rktype, rkinfo);
|
||||
auto kc = key();
|
||||
|
||||
switch (rktype) {
|
||||
case CryptoDCInstance::ACTIVATE_PRIMARY:
|
||||
genl->new_key(OVPN_KEY_SLOT_PRIMARY, kc);
|
||||
break;
|
||||
|
||||
case CryptoDCInstance::NEW_SECONDARY:
|
||||
genl->new_key(OVPN_KEY_SLOT_SECONDARY, kc);
|
||||
break;
|
||||
|
||||
case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
|
||||
genl->swap_keys();
|
||||
break;
|
||||
|
||||
case CryptoDCInstance::DEACTIVATE_SECONDARY:
|
||||
genl->del_key(OVPN_KEY_SLOT_SECONDARY);
|
||||
break;
|
||||
|
||||
case CryptoDCInstance::DEACTIVATE_ALL:
|
||||
// TODO: deactivate all keys
|
||||
OPENVPN_LOG("ovpndcocli: deactivate all keys");
|
||||
break;
|
||||
|
||||
default:
|
||||
OPENVPN_LOG("ovpndcocli: unknown rekey type: " << rktype);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool tun_read_handler(BufferAllocated &buf) {
|
||||
if (halt)
|
||||
return false;
|
||||
|
@ -32,7 +32,6 @@
|
||||
#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 {
|
||||
@ -130,4 +129,10 @@ namespace openvpn {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_KOVPN
|
||||
#include <openvpn/kovpn/kovpnkocrypto.hpp>
|
||||
#elif ENABLE_OVPNDCO
|
||||
#include <openvpn/kovpn/ovpndcokocrypto.hpp>
|
||||
#else
|
||||
#error either ENABLE_KOVPN or ENABLE_OVPNDCO must be defined
|
||||
#endif
|
||||
|
134
openvpn/kovpn/ovpndcokocrypto.hpp
Normal file
134
openvpn/kovpn/ovpndcokocrypto.hpp
Normal file
@ -0,0 +1,134 @@
|
||||
// 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-2020 OpenVPN Inc.
|
||||
// Copyright (C) 2020-2020 Lev Stipakov <lev@openvpn.net>
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
// ovpn-dco crypto wrappers
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace openvpn {
|
||||
namespace KoRekey {
|
||||
|
||||
/**
|
||||
* @brief Parses key information into format consumed by ovpn-dco.
|
||||
*
|
||||
*/
|
||||
class OvpnDcoKey : public Key {
|
||||
public:
|
||||
OvpnDcoKey(const CryptoDCInstance::RekeyType rktype, const Info &rkinfo) {
|
||||
std::memset(&kc, 0, sizeof(kc));
|
||||
|
||||
kc.remote_peer_id = rkinfo.remote_peer_id;
|
||||
|
||||
const CryptoDCContext::Info ci = rkinfo.dc_context_delegate->crypto_info();
|
||||
const CryptoAlgs::Alg &calg = CryptoAlgs::get(ci.cipher_alg);
|
||||
switch (ci.cipher_alg) {
|
||||
case CryptoAlgs::AES_128_GCM:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_GCM;
|
||||
kc.encrypt.cipher_key_size = 128 / 8;
|
||||
break;
|
||||
case CryptoAlgs::AES_192_GCM:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_GCM;
|
||||
kc.encrypt.cipher_key_size = 192 / 8;
|
||||
break;
|
||||
case CryptoAlgs::AES_256_GCM:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_GCM;
|
||||
kc.encrypt.cipher_key_size = 256 / 8;
|
||||
break;
|
||||
case CryptoAlgs::AES_128_CBC:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_CBC;
|
||||
kc.encrypt.cipher_key_size = 128 / 8;
|
||||
break;
|
||||
case CryptoAlgs::AES_192_CBC:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_CBC;
|
||||
kc.encrypt.cipher_key_size = 192 / 8;
|
||||
break;
|
||||
case CryptoAlgs::AES_256_CBC:
|
||||
kc.cipher_alg = OVPN_CIPHER_ALG_AES_CBC;
|
||||
kc.encrypt.cipher_key_size = 256 / 8;
|
||||
break;
|
||||
default:
|
||||
OPENVPN_THROW(korekey_error,
|
||||
"cipher alg " << calg.name()
|
||||
<< " is not currently supported by ovpn-dco");
|
||||
break;
|
||||
}
|
||||
kc.decrypt.cipher_key_size = kc.encrypt.cipher_key_size;
|
||||
|
||||
kc.encrypt.cipher_key = verify_key("cipher encrypt", rkinfo.encrypt_cipher,
|
||||
kc.encrypt.cipher_key_size);
|
||||
kc.decrypt.cipher_key = verify_key("cipher decrypt", rkinfo.decrypt_cipher,
|
||||
kc.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::SHA256:
|
||||
kc.hmac_alg = OVPN_HMAC_ALG_SHA256;
|
||||
break;
|
||||
case CryptoAlgs::SHA512:
|
||||
kc.hmac_alg = OVPN_HMAC_ALG_SHA512;
|
||||
break;
|
||||
default:
|
||||
OPENVPN_THROW(korekey_error,
|
||||
"HMAC alg "
|
||||
<< halg.name()
|
||||
<< " is not currently supported by ovpn-dco");
|
||||
}
|
||||
kc.encrypt.hmac_key_size = halg.size();
|
||||
kc.decrypt.hmac_key_size = kc.encrypt.hmac_key_size;
|
||||
|
||||
// set hmac keys
|
||||
kc.encrypt.hmac_key = verify_key("hmac encrypt", rkinfo.encrypt_hmac,
|
||||
kc.encrypt.hmac_key_size);
|
||||
kc.decrypt.hmac_key = verify_key("hmac decrypt", rkinfo.decrypt_hmac,
|
||||
kc.decrypt.hmac_key_size);
|
||||
}
|
||||
break;
|
||||
|
||||
case CryptoAlgs::AEAD:
|
||||
set_nonce_tail("AEAD nonce tail encrypt", kc.encrypt.nonce_tail,
|
||||
sizeof(kc.encrypt.nonce_tail), rkinfo.encrypt_hmac);
|
||||
set_nonce_tail("AEAD nonce tail decrypt", kc.decrypt.nonce_tail,
|
||||
sizeof(kc.decrypt.nonce_tail), rkinfo.decrypt_hmac);
|
||||
|
||||
break;
|
||||
|
||||
default: {
|
||||
// should have been caught above
|
||||
throw korekey_error("internal error");
|
||||
}
|
||||
}
|
||||
|
||||
kc.key_id = rkinfo.key_id;
|
||||
}
|
||||
|
||||
const struct KeyConfig *operator()() const { return &kc; }
|
||||
|
||||
private:
|
||||
struct KeyConfig kc;
|
||||
};
|
||||
|
||||
} // namespace KoRekey
|
||||
} // namespace openvpn
|
@ -25,6 +25,7 @@
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
#include <openvpn/buffer/bufstr.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/dco/key.hpp>
|
||||
|
||||
#include <linux/ovpn_dco.h>
|
||||
#include <netlink/genl/ctrl.h>
|
||||
@ -47,11 +48,13 @@ typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
|
||||
*
|
||||
* Before using this class, caller should create ovpn-dco network device.
|
||||
*
|
||||
* @tparam ReadHandler class which implements <tt>tun_read_handler(BufferAllocated &buf)</tt> method. \n
|
||||
* @tparam ReadHandler class which implements
|
||||
* <tt>tun_read_handler(BufferAllocated &buf)</tt> method. \n
|
||||
*
|
||||
* buf has following layout:
|
||||
* \li first byte - command type ( \p OVPN_CMD_PACKET, \p OVPN_CMD_DEL_PEER or -1 for error)
|
||||
* \li following bytes - command-specific payload
|
||||
* \li first byte - command type ( \p OVPN_CMD_PACKET, \p OVPN_CMD_DEL_PEER or
|
||||
* -1 for error)
|
||||
* \li following bytes - command-specific payload
|
||||
*/
|
||||
template <typename ReadHandler> class GeNL : public RC<thread_unsafe_refcount> {
|
||||
OPENVPN_EXCEPTION(netlink_error);
|
||||
@ -200,6 +203,92 @@ public:
|
||||
OPENVPN_THROW(netlink_error, " send_data() nla_put_failure");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject new key into kernel module
|
||||
*
|
||||
* @param key_slot OVPN_KEY_SLOT_PRIMARY or OVPN_KEY_SLOT_SECONDARY
|
||||
* @param kc pointer to KeyConfig struct which contains key data
|
||||
* @throws netlink_error thrown if error occurs during sending netlink message
|
||||
*/
|
||||
void new_key(unsigned int key_slot, const KoRekey::KeyConfig *kc) {
|
||||
auto msg_ptr = create_msg(OVPN_CMD_NEW_KEY);
|
||||
auto* msg = msg_ptr.get();
|
||||
|
||||
const int NONCE_LEN = 12;
|
||||
|
||||
struct nlattr *key_dir;
|
||||
|
||||
NLA_PUT_U32(msg, OVPN_ATTR_REMOTE_PEER_ID, kc->remote_peer_id);
|
||||
NLA_PUT_U8(msg, OVPN_ATTR_KEY_SLOT, key_slot);
|
||||
NLA_PUT_U16(msg, OVPN_ATTR_KEY_ID, kc->key_id);
|
||||
NLA_PUT_U16(msg, OVPN_ATTR_CIPHER_ALG, kc->cipher_alg);
|
||||
if (kc->cipher_alg == OVPN_CIPHER_ALG_AES_CBC) {
|
||||
NLA_PUT_U16(msg, OVPN_ATTR_HMAC_ALG, kc->hmac_alg);
|
||||
}
|
||||
|
||||
key_dir = nla_nest_start(msg, OVPN_ATTR_ENCRYPT_KEY);
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, kc->encrypt.cipher_key_size,
|
||||
kc->encrypt.cipher_key);
|
||||
if (kc->cipher_alg == OVPN_CIPHER_ALG_AES_GCM) {
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, NONCE_LEN,
|
||||
kc->encrypt.nonce_tail);
|
||||
} else {
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_HMAC_KEY, kc->encrypt.hmac_key_size,
|
||||
kc->encrypt.hmac_key);
|
||||
}
|
||||
nla_nest_end(msg, key_dir);
|
||||
|
||||
key_dir = nla_nest_start(msg, OVPN_ATTR_DECRYPT_KEY);
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, kc->decrypt.cipher_key_size,
|
||||
kc->decrypt.cipher_key);
|
||||
if (kc->cipher_alg == OVPN_CIPHER_ALG_AES_GCM) {
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, NONCE_LEN,
|
||||
kc->decrypt.nonce_tail);
|
||||
} else {
|
||||
NLA_PUT(msg, OVPN_KEY_DIR_ATTR_HMAC_KEY, kc->decrypt.hmac_key_size,
|
||||
kc->decrypt.hmac_key);
|
||||
}
|
||||
nla_nest_end(msg, key_dir);
|
||||
|
||||
send_netlink_message(msg);
|
||||
return;
|
||||
|
||||
nla_put_failure:
|
||||
OPENVPN_THROW(netlink_error, " set_keys() nla_put_failure");
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap keys between primary and secondary slots. Called
|
||||
* by client as part of rekeying logic to promote and demote keys.
|
||||
*
|
||||
* @throws netlink_error thrown if error occurs during sending netlink message
|
||||
*/
|
||||
void swap_keys() {
|
||||
auto msg_ptr = create_msg(OVPN_CMD_SWAP_KEYS);
|
||||
auto* msg = msg_ptr.get();
|
||||
|
||||
send_netlink_message(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove key from key slot.
|
||||
*
|
||||
* @param key_slot OVPN_KEY_SLOT_PRIMARY or OVPN_KEY_SLOT_SECONDARY
|
||||
* @throws netlink_error thrown if error occurs during sending netlink message
|
||||
*/
|
||||
void del_key(unsigned int key_slot) {
|
||||
auto msg_ptr = create_msg(OVPN_CMD_DEL_KEY);
|
||||
auto* msg = msg_ptr.get();
|
||||
|
||||
NLA_PUT_U8(msg, OVPN_ATTR_KEY_SLOT, key_slot);
|
||||
|
||||
send_netlink_message(msg);
|
||||
return;
|
||||
|
||||
nla_put_failure:
|
||||
OPENVPN_THROW(netlink_error, " del_key() nla_put_failure");
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!halt) {
|
||||
halt = true;
|
||||
@ -208,7 +297,7 @@ public:
|
||||
stream->cancel();
|
||||
stream->close();
|
||||
} catch (...) {
|
||||
// ASIO might throw transport exceptions which I found is safe to ignore
|
||||
// ASIO might throw transport exceptions which I found is safe to ignore
|
||||
}
|
||||
|
||||
// contrary to what ASIO doc says, stream->close() doesn't
|
||||
|
Loading…
Reference in New Issue
Block a user