0
0
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:
Lev Stipakov 2020-08-24 08:21:57 +00:00
parent 9a15079d25
commit 60e43763a4
6 changed files with 330 additions and 7 deletions

View File

@ -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
View 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

View File

@ -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;

View File

@ -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

View 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

View File

@ -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