mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-06 12:33:44 +02:00
Better encode/decode code for control bus.
This commit is contained in:
parent
1fce55fab1
commit
a677597b44
4 changed files with 214 additions and 60 deletions
|
@ -57,6 +57,8 @@
|
||||||
#include "Constants.hpp"
|
#include "Constants.hpp"
|
||||||
#include "InetAddress.hpp"
|
#include "InetAddress.hpp"
|
||||||
#include "Pack.hpp"
|
#include "Pack.hpp"
|
||||||
|
#include "Salsa20.hpp"
|
||||||
|
#include "HMAC.hpp"
|
||||||
#include "RuntimeEnvironment.hpp"
|
#include "RuntimeEnvironment.hpp"
|
||||||
#include "NodeConfig.hpp"
|
#include "NodeConfig.hpp"
|
||||||
#include "Defaults.hpp"
|
#include "Defaults.hpp"
|
||||||
|
@ -71,6 +73,69 @@
|
||||||
|
|
||||||
namespace ZeroTier {
|
namespace ZeroTier {
|
||||||
|
|
||||||
|
struct _LocalClientImpl
|
||||||
|
{
|
||||||
|
unsigned char key[32];
|
||||||
|
UdpSocket *sock;
|
||||||
|
void (*resultHandler)(void *,unsigned long,const char *);
|
||||||
|
void *arg;
|
||||||
|
Mutex inUseLock;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void _CBlocalClientHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len)
|
||||||
|
{
|
||||||
|
_LocalClientImpl *impl = (_LocalClientImpl *)arg;
|
||||||
|
Mutex::Lock _l(impl->inUseLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::LocalClient::LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg)
|
||||||
|
throw() :
|
||||||
|
_impl((void *)0)
|
||||||
|
{
|
||||||
|
_LocalClientImpl *impl = new _LocalClientImpl;
|
||||||
|
|
||||||
|
UdpSocket *sock = (UdpSocket *)0;
|
||||||
|
for(unsigned int i=0;i<5000;++i) {
|
||||||
|
try {
|
||||||
|
sock = new UdpSocket(true,32768 + (rand() % 20000),false,&_CBlocalClientHandler,impl);
|
||||||
|
break;
|
||||||
|
} catch ( ... ) {
|
||||||
|
sock = (UdpSocket *)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If socket fails to bind, there's a big problem like missing IPv4 stack
|
||||||
|
if (sock) {
|
||||||
|
SHA256_CTX sha;
|
||||||
|
SHA256_Init(&sha);
|
||||||
|
SHA256_Update(&sha,authToken,strlen(authToken));
|
||||||
|
SHA256_Final(impl->key,&sha);
|
||||||
|
|
||||||
|
impl->sock = sock;
|
||||||
|
impl->resultHandler = resultHandler;
|
||||||
|
impl->arg = arg;
|
||||||
|
_impl = impl;
|
||||||
|
} else delete impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::LocalClient::~LocalClient()
|
||||||
|
{
|
||||||
|
if (_impl) {
|
||||||
|
((_LocalClientImpl *)_impl)->inUseLock.lock();
|
||||||
|
delete ((_LocalClientImpl *)_impl)->sock;
|
||||||
|
((_LocalClientImpl *)_impl)->inUseLock.unlock();
|
||||||
|
delete ((_LocalClientImpl *)_impl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long Node::LocalClient::send(const char *command)
|
||||||
|
throw()
|
||||||
|
{
|
||||||
|
uint32_t convId = (uint32_t)rand();
|
||||||
|
|
||||||
|
return convId;
|
||||||
|
}
|
||||||
|
|
||||||
struct _NodeImpl
|
struct _NodeImpl
|
||||||
{
|
{
|
||||||
RuntimeEnvironment renv;
|
RuntimeEnvironment renv;
|
||||||
|
|
|
@ -40,6 +40,45 @@ namespace ZeroTier {
|
||||||
class Node
|
class Node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Client for controlling a local ZeroTier One node
|
||||||
|
*/
|
||||||
|
class LocalClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create a new node config client
|
||||||
|
*
|
||||||
|
* The result handler will be called from a different thread. Its
|
||||||
|
* arguments are the request ID generated by send() and each line
|
||||||
|
* of output. It may be called more than once per request result
|
||||||
|
* if the command generates more than one line of output.
|
||||||
|
*
|
||||||
|
* @param authToken Authentication token
|
||||||
|
* @param resultHandler Function to call when commands provide results
|
||||||
|
*/
|
||||||
|
LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg)
|
||||||
|
throw();
|
||||||
|
|
||||||
|
~LocalClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a command to the local node
|
||||||
|
*
|
||||||
|
* @param command
|
||||||
|
* @return Request ID that will be provided to result handler when/if results are sent back
|
||||||
|
*/
|
||||||
|
unsigned long send(const char *command)
|
||||||
|
throw();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// LocalClient is not copyable
|
||||||
|
LocalClient(const LocalClient&);
|
||||||
|
const LocalClient& operator=(const LocalClient&);
|
||||||
|
|
||||||
|
void *_impl;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returned by node main if/when it terminates
|
* Returned by node main if/when it terminates
|
||||||
*/
|
*/
|
||||||
|
@ -108,6 +147,10 @@ public:
|
||||||
static unsigned int versionRevision() throw();
|
static unsigned int versionRevision() throw();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Nodes are not copyable
|
||||||
|
Node(const Node&);
|
||||||
|
const Node& operator=(const Node&);
|
||||||
|
|
||||||
void *const _impl; // private implementation
|
void *const _impl; // private implementation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -52,19 +52,12 @@ namespace ZeroTier {
|
||||||
NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken)
|
NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken)
|
||||||
throw(std::runtime_error) :
|
throw(std::runtime_error) :
|
||||||
_r(renv),
|
_r(renv),
|
||||||
_authToken(authToken),
|
|
||||||
_controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this)
|
_controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this)
|
||||||
{
|
{
|
||||||
SHA256_CTX sha;
|
SHA256_CTX sha;
|
||||||
|
|
||||||
SHA256_Init(&sha);
|
SHA256_Init(&sha);
|
||||||
SHA256_Update(&sha,_authToken.data(),_authToken.length());
|
SHA256_Update(&sha,authToken,strlen(authToken));
|
||||||
SHA256_Final(_keys,&sha); // first 32 bytes of keys[]: Salsa20 key
|
SHA256_Final(_controlSocketKey,&sha);
|
||||||
|
|
||||||
SHA256_Init(&sha);
|
|
||||||
SHA256_Update(&sha,_keys,32);
|
|
||||||
SHA256_Update(&sha,_authToken.data(),_authToken.length());
|
|
||||||
SHA256_Final(_keys + 32,&sha); // second 32 bytes of keys[]: HMAC key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeConfig::~NodeConfig()
|
NodeConfig::~NodeConfig()
|
||||||
|
@ -146,64 +139,86 @@ std::vector<std::string> NodeConfig::execute(const char *command)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len)
|
std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > NodeConfig::encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &payload)
|
||||||
|
throw(std::out_of_range)
|
||||||
{
|
{
|
||||||
char hmacKey[32];
|
|
||||||
char hmac[32];
|
char hmac[32];
|
||||||
char buf[131072];
|
char keytmp[32];
|
||||||
NodeConfig *nc = (NodeConfig *)arg;
|
std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets;
|
||||||
const RuntimeEnvironment *_r = nc->_r;
|
Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet;
|
||||||
|
|
||||||
|
packet.setSize(16); // HMAC and IV
|
||||||
|
packet.append((uint32_t)(conversationId & 0xffffffff));
|
||||||
|
for(unsigned int i=0;i<payload.size();++i) {
|
||||||
|
packet.append(payload[i]); // will throw if too big
|
||||||
|
packet.append((unsigned char)0);
|
||||||
|
|
||||||
|
if (((i + 1) >= payload.size())||((packet.size() + payload[i + 1].length() + 1) >= packet.capacity())) {
|
||||||
|
Utils::getSecureRandom(packet.field(8,8),8);
|
||||||
|
|
||||||
|
memcpy(keytmp,key,32);
|
||||||
|
for(unsigned int i=0;i<32;++i)
|
||||||
|
keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20
|
||||||
|
HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac);
|
||||||
|
memcpy(packet.field(0,8),hmac,8);
|
||||||
|
|
||||||
|
Salsa20 s20(key,256,packet.field(8,8));
|
||||||
|
s20.encrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16);
|
||||||
|
|
||||||
|
packets.push_back(packet);
|
||||||
|
|
||||||
|
packet.setSize(16); // HMAC and IV
|
||||||
|
packet.append((uint32_t)(conversationId & 0xffffffff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packets;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeConfig::decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector<std::string> &payload)
|
||||||
|
{
|
||||||
|
char hmac[32];
|
||||||
|
char keytmp[32];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Minimum length
|
if (len < 20)
|
||||||
if (len < 28)
|
return false;
|
||||||
return;
|
|
||||||
if (len >= sizeof(buf)) // only up to len - 28 bytes are used on receive/decrypt
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Compare first 16 bytes of HMAC, which is after IV in packet
|
Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet(data,len);
|
||||||
memcpy(hmacKey,nc->_keys + 32,32);
|
|
||||||
*((uint64_t *)hmacKey) ^= *((const uint64_t *)data); // include IV in HMAC
|
|
||||||
HMAC::sha256(hmacKey,32,((const unsigned char *)data) + 28,len - 28,hmac);
|
|
||||||
if (memcmp(hmac,((const unsigned char *)data) + 8,16))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Decrypt payload if we passed HMAC
|
memcpy(keytmp,key,32);
|
||||||
Salsa20 s20(nc->_keys,256,data); // first 64 bits of data are IV
|
for(unsigned int i=0;i<32;++i)
|
||||||
s20.decrypt(((const unsigned char *)data) + 28,buf,len - 28);
|
keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20
|
||||||
|
HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac);
|
||||||
|
if (memcmp(packet.field(0,8),hmac,8))
|
||||||
|
return false;
|
||||||
|
|
||||||
// Null-terminate string for execute()
|
Salsa20 s20(key,256,packet.field(8,8));
|
||||||
buf[len - 28] = (char)0;
|
s20.decrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16);
|
||||||
|
|
||||||
// Execute command
|
conversationId = packet.at<uint32_t>(16);
|
||||||
std::vector<std::string> r(nc->execute(buf));
|
|
||||||
|
|
||||||
// Result packet contains a series of null-terminated results
|
const char *pl = ((const char *)packet.data()) + 20;
|
||||||
unsigned int resultLen = 28;
|
unsigned int pll = packet.size() - 20;
|
||||||
for(std::vector<std::string>::iterator i(r.begin());i!=r.end();++i) {
|
payload.clear();
|
||||||
if ((resultLen + i->length() + 1) >= sizeof(buf))
|
for(unsigned int i=0;i<pll;) {
|
||||||
return; // result too long
|
unsigned int eos = i;
|
||||||
memcpy(buf + resultLen,i->c_str(),i->length() + 1);
|
while ((eos < pll)&&(pl[eos]))
|
||||||
resultLen += i->length() + 1;
|
++eos;
|
||||||
|
if (eos > i) {
|
||||||
|
payload.push_back(std::string(pl + i,eos - i));
|
||||||
|
i = eos + 1;
|
||||||
|
} else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate result packet IV
|
return true;
|
||||||
Utils::getSecureRandom(buf,8);
|
|
||||||
|
|
||||||
// Generate result packet HMAC
|
|
||||||
memcpy(hmacKey,nc->_keys + 32,32);
|
|
||||||
*((uint64_t *)hmacKey) ^= *((const uint64_t *)buf); // include IV in HMAC
|
|
||||||
HMAC::sha256(hmacKey,32,((const unsigned char *)buf) + 28,resultLen - 28,hmac);
|
|
||||||
memcpy(buf + 8,hmac,16);
|
|
||||||
|
|
||||||
// Copy arbitrary tag from original packet
|
|
||||||
memcpy(buf + 24,((const unsigned char *)data) + 24,4);
|
|
||||||
|
|
||||||
// Send encrypted result back to requester
|
|
||||||
sock->send(remoteAddr,buf,resultLen,-1);
|
|
||||||
} catch ( ... ) {
|
} catch ( ... ) {
|
||||||
TRACE("unexpected exception parsing control packet or generating response");
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZeroTier
|
} // namespace ZeroTier
|
||||||
|
|
|
@ -40,17 +40,25 @@
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "Http.hpp"
|
#include "Http.hpp"
|
||||||
#include "UdpSocket.hpp"
|
#include "UdpSocket.hpp"
|
||||||
|
#include "Buffer.hpp"
|
||||||
|
|
||||||
namespace ZeroTier {
|
namespace ZeroTier {
|
||||||
|
|
||||||
class RuntimeEnvironment;
|
class RuntimeEnvironment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of a packet for node configuration
|
||||||
|
*/
|
||||||
|
#define ZT_NODECONFIG_MAX_PACKET_SIZE 4096
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node configuration endpoint
|
* Node configuration endpoint
|
||||||
*
|
*
|
||||||
* Packet format for local UDP configuration packets:
|
* Packet format for local UDP configuration packets:
|
||||||
* [8] random initialization vector
|
|
||||||
* [16] first 16 bytes of HMAC-SHA-256 of payload
|
* [16] first 16 bytes of HMAC-SHA-256 of payload
|
||||||
|
* [ -- begin HMAC'ed envelope -- ]
|
||||||
|
* [8] random initialization vector
|
||||||
|
* [ -- begin cryptographic envelope -- ]
|
||||||
* [4] arbitrary tag, echoed in response
|
* [4] arbitrary tag, echoed in response
|
||||||
* [...] payload
|
* [...] payload
|
||||||
*
|
*
|
||||||
|
@ -58,8 +66,6 @@ class RuntimeEnvironment;
|
||||||
* responses, the payload consists of one or more response lines delimited
|
* responses, the payload consists of one or more response lines delimited
|
||||||
* by NULL (0) characters. The tag field is replicated in the result
|
* by NULL (0) characters. The tag field is replicated in the result
|
||||||
* packet.
|
* packet.
|
||||||
*
|
|
||||||
* TODO: further document use of keys, encryption...
|
|
||||||
*/
|
*/
|
||||||
class NodeConfig
|
class NodeConfig
|
||||||
{
|
{
|
||||||
|
@ -132,14 +138,39 @@ public:
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> execute(const char *command);
|
std::vector<std::string> execute(const char *command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Armor payload for control bus
|
||||||
|
*
|
||||||
|
* Note that no single element of payload can be longer than the max packet
|
||||||
|
* size. If this occurs out_of_range is thrown.
|
||||||
|
*
|
||||||
|
* @param key 32 byte key
|
||||||
|
* @param conversationId 32-bit conversation ID (bits beyond 32 are ignored)
|
||||||
|
* @param payload One or more strings to encode in packet
|
||||||
|
* @return One or more transport armored packets (if payload too big)
|
||||||
|
* @throws std::out_of_range An element of payload is too big
|
||||||
|
*/
|
||||||
|
static std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &payload)
|
||||||
|
throw(std::out_of_range);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a packet from the control bus
|
||||||
|
*
|
||||||
|
* @param key 32 byte key
|
||||||
|
* @param data Packet data
|
||||||
|
* @param len Packet length
|
||||||
|
* @param conversationId Result parameter filled with conversation ID on success
|
||||||
|
* @param payload Result parameter filled with payload on success
|
||||||
|
* @return True on success, false on invalid packet or packet that failed authentication
|
||||||
|
*/
|
||||||
|
static bool decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector<std::string> &payload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len);
|
static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len);
|
||||||
|
|
||||||
const RuntimeEnvironment *_r;
|
const RuntimeEnvironment *_r;
|
||||||
|
|
||||||
const std::string _authToken;
|
unsigned char _controlSocketKey[32];
|
||||||
unsigned char _keys[64]; // Salsa20 key, HMAC key
|
|
||||||
|
|
||||||
UdpSocket _controlSocket;
|
UdpSocket _controlSocket;
|
||||||
std::map< uint64_t,SharedPtr<Network> > _networks;
|
std::map< uint64_t,SharedPtr<Network> > _networks;
|
||||||
Mutex _networks_m;
|
Mutex _networks_m;
|
||||||
|
|
Loading…
Add table
Reference in a new issue