diff options
-rw-r--r-- | node/Node.cpp | 65 | ||||
-rw-r--r-- | node/Node.hpp | 43 | ||||
-rw-r--r-- | node/NodeConfig.cpp | 129 | ||||
-rw-r--r-- | node/NodeConfig.hpp | 43 |
4 files changed, 217 insertions, 63 deletions
diff --git a/node/Node.cpp b/node/Node.cpp index 5dbc5bb6..57bb8abc 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -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; diff --git a/node/Node.hpp b/node/Node.hpp index f4e2d423..bddced58 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -41,6 +41,45 @@ 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 */ enum ReasonForTermination @@ -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 }; diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp index 0daa9ebe..fca53942 100644 --- a/node/NodeConfig.cpp +++ b/node/NodeConfig.cpp @@ -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; - try { - // Minimum length - if (len < 28) - 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 - 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 - Salsa20 s20(nc->_keys,256,data); // first 64 bits of data are IV - s20.decrypt(((const unsigned char *)data) + 28,buf,len - 28); - - // Null-terminate string for execute() - buf[len - 28] = (char)0; - - // Execute command - std::vector<std::string> r(nc->execute(buf)); - - // 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; + 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)); } + } - // Generate result packet IV - Utils::getSecureRandom(buf,8); + return packets; +} - // 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); +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]; - // Copy arbitrary tag from original packet - memcpy(buf + 24,((const unsigned char *)data) + 24,4); + try { + if (len < 20) + return false; + + Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet(data,len); + + 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; + + 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); + + conversationId = packet.at<uint32_t>(16); + + 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; + } - // 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 diff --git a/node/NodeConfig.hpp b/node/NodeConfig.hpp index 66c99448..d284062d 100644 --- a/node/NodeConfig.hpp +++ b/node/NodeConfig.hpp @@ -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; |