mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-02-20 17:52:46 +00:00
Better encode/decode code for control bus.
This commit is contained in:
parent
1fce55fab1
commit
a677597b44
@ -57,6 +57,8 @@
|
||||
#include "Constants.hpp"
|
||||
#include "InetAddress.hpp"
|
||||
#include "Pack.hpp"
|
||||
#include "Salsa20.hpp"
|
||||
#include "HMAC.hpp"
|
||||
#include "RuntimeEnvironment.hpp"
|
||||
#include "NodeConfig.hpp"
|
||||
#include "Defaults.hpp"
|
||||
@ -71,6 +73,69 @@
|
||||
|
||||
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
|
||||
{
|
||||
RuntimeEnvironment renv;
|
||||
|
@ -40,6 +40,45 @@ namespace ZeroTier {
|
||||
class Node
|
||||
{
|
||||
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
|
||||
*/
|
||||
@ -108,6 +147,10 @@ public:
|
||||
static unsigned int versionRevision() throw();
|
||||
|
||||
private:
|
||||
// Nodes are not copyable
|
||||
Node(const Node&);
|
||||
const Node& operator=(const Node&);
|
||||
|
||||
void *const _impl; // private implementation
|
||||
};
|
||||
|
||||
|
@ -52,19 +52,12 @@ namespace ZeroTier {
|
||||
NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken)
|
||||
throw(std::runtime_error) :
|
||||
_r(renv),
|
||||
_authToken(authToken),
|
||||
_controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this)
|
||||
{
|
||||
SHA256_CTX sha;
|
||||
|
||||
SHA256_Init(&sha);
|
||||
SHA256_Update(&sha,_authToken.data(),_authToken.length());
|
||||
SHA256_Final(_keys,&sha); // first 32 bytes of keys[]: Salsa20 key
|
||||
|
||||
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
|
||||
SHA256_Update(&sha,authToken,strlen(authToken));
|
||||
SHA256_Final(_controlSocketKey,&sha);
|
||||
}
|
||||
|
||||
NodeConfig::~NodeConfig()
|
||||
@ -146,64 +139,86 @@ std::vector<std::string> NodeConfig::execute(const char *command)
|
||||
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 buf[131072];
|
||||
NodeConfig *nc = (NodeConfig *)arg;
|
||||
const RuntimeEnvironment *_r = nc->_r;
|
||||
char keytmp[32];
|
||||
std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets;
|
||||
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 {
|
||||
// Minimum length
|
||||
if (len < 28)
|
||||
return;
|
||||
if (len >= sizeof(buf)) // only up to len - 28 bytes are used on receive/decrypt
|
||||
return;
|
||||
if (len < 20)
|
||||
return false;
|
||||
|
||||
// Compare first 16 bytes of HMAC, which is after IV in packet
|
||||
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;
|
||||
Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet(data,len);
|
||||
|
||||
// Decrypt payload if we passed HMAC
|
||||
Salsa20 s20(nc->_keys,256,data); // first 64 bits of data are IV
|
||||
s20.decrypt(((const unsigned char *)data) + 28,buf,len - 28);
|
||||
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);
|
||||
if (memcmp(packet.field(0,8),hmac,8))
|
||||
return false;
|
||||
|
||||
// Null-terminate string for execute()
|
||||
buf[len - 28] = (char)0;
|
||||
Salsa20 s20(key,256,packet.field(8,8));
|
||||
s20.decrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16);
|
||||
|
||||
// Execute command
|
||||
std::vector<std::string> r(nc->execute(buf));
|
||||
conversationId = packet.at<uint32_t>(16);
|
||||
|
||||
// Result packet contains a series of null-terminated results
|
||||
unsigned int resultLen = 28;
|
||||
for(std::vector<std::string>::iterator i(r.begin());i!=r.end();++i) {
|
||||
if ((resultLen + i->length() + 1) >= sizeof(buf))
|
||||
return; // result too long
|
||||
memcpy(buf + resultLen,i->c_str(),i->length() + 1);
|
||||
resultLen += i->length() + 1;
|
||||
const char *pl = ((const char *)packet.data()) + 20;
|
||||
unsigned int pll = packet.size() - 20;
|
||||
payload.clear();
|
||||
for(unsigned int i=0;i<pll;) {
|
||||
unsigned int eos = i;
|
||||
while ((eos < pll)&&(pl[eos]))
|
||||
++eos;
|
||||
if (eos > i) {
|
||||
payload.push_back(std::string(pl + i,eos - i));
|
||||
i = eos + 1;
|
||||
} else break;
|
||||
}
|
||||
|
||||
// Generate result packet IV
|
||||
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);
|
||||
return true;
|
||||
} 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
|
||||
|
@ -40,17 +40,25 @@
|
||||
#include "Utils.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "UdpSocket.hpp"
|
||||
#include "Buffer.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class RuntimeEnvironment;
|
||||
|
||||
/**
|
||||
* Maximum size of a packet for node configuration
|
||||
*/
|
||||
#define ZT_NODECONFIG_MAX_PACKET_SIZE 4096
|
||||
|
||||
/**
|
||||
* Node configuration endpoint
|
||||
*
|
||||
* Packet format for local UDP configuration packets:
|
||||
* [8] random initialization vector
|
||||
* [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
|
||||
* [...] payload
|
||||
*
|
||||
@ -58,8 +66,6 @@ class RuntimeEnvironment;
|
||||
* responses, the payload consists of one or more response lines delimited
|
||||
* by NULL (0) characters. The tag field is replicated in the result
|
||||
* packet.
|
||||
*
|
||||
* TODO: further document use of keys, encryption...
|
||||
*/
|
||||
class NodeConfig
|
||||
{
|
||||
@ -132,14 +138,39 @@ public:
|
||||
*/
|
||||
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:
|
||||
static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len);
|
||||
|
||||
const RuntimeEnvironment *_r;
|
||||
|
||||
const std::string _authToken;
|
||||
unsigned char _keys[64]; // Salsa20 key, HMAC key
|
||||
|
||||
unsigned char _controlSocketKey[32];
|
||||
UdpSocket _controlSocket;
|
||||
std::map< uint64_t,SharedPtr<Network> > _networks;
|
||||
Mutex _networks_m;
|
||||
|
Loading…
x
Reference in New Issue
Block a user