Browse Source

Better encode/decode code for control bus.

Adam Ierymenko 12 years ago
parent
commit
a677597b44
4 changed files with 217 additions and 63 deletions
  1. 65 0
      node/Node.cpp
  2. 43 0
      node/Node.hpp
  3. 72 57
      node/NodeConfig.cpp
  4. 37 6
      node/NodeConfig.hpp

+ 65 - 0
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;

+ 43 - 0
node/Node.hpp

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

+ 72 - 57
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

+ 37 - 6
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;