Browse Source

Add signatures to Dictionary, and fix unhex() API in Utils to be a little safer.

Adam Ierymenko 11 years ago
parent
commit
e3c5ada3a7
5 changed files with 228 additions and 136 deletions
  1. 154 0
      node/Dictionary.cpp
  2. 52 85
      node/Dictionary.hpp
  3. 14 32
      node/Utils.cpp
  4. 7 19
      node/Utils.hpp
  5. 1 0
      objects.mk

+ 154 - 0
node/Dictionary.cpp

@@ -0,0 +1,154 @@
+/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2011-2014  ZeroTier Networks LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#include "Dictionary.hpp"
+#include "C25519.hpp"
+#include "Identity.hpp"
+#include "Utils.hpp"
+
+namespace ZeroTier {
+
+void Dictionary::fromString(const char *s)
+{
+	clear();
+	bool escapeState = false;
+	std::string keyBuf;
+	std::string *element = &keyBuf;
+	while (*s) {
+		if (escapeState) {
+			escapeState = false;
+			switch(*s) {
+				case '0':
+					element->push_back((char)0);
+					break;
+				case 'r':
+					element->push_back('\r');
+					break;
+				case 'n':
+					element->push_back('\n');
+					break;
+				default:
+					element->push_back(*s);
+					break;
+			}
+		} else {
+			if (*s == '\\') {
+				escapeState = true;
+			} else if (*s == '=') {
+				if (element == &keyBuf)
+					element = &((*this)[keyBuf]);
+			} else if ((*s == '\r')||(*s == '\n')) {
+				if ((element == &keyBuf)&&(keyBuf.length() > 0))
+					(*this)[keyBuf];
+				keyBuf = "";
+				element = &keyBuf;
+			} else element->push_back(*s);
+		}
+		++s;
+	}
+	if ((element == &keyBuf)&&(keyBuf.length() > 0))
+		(*this)[keyBuf];
+}
+
+bool Dictionary::sign(const Identity &id)
+{
+	try {
+		std::string buf;
+		_mkSigBuf(buf);
+		char nows[32];
+		Utils::snprintf(nows,sizeof(nows),"%llx",(unsigned long long)Utils::now());
+		C25519::Signature sig(id.sign(buf.data(),buf.length()));
+		(*this)[ZT_DICTIONARY_SIGNATURE] = Utils::hex(sig.data,sig.size());
+		(*this)[ZT_DICTIONARY_SIGNATURE_IDENTITY] = id.toString(false);
+		(*this)[ZT_DICTIONARY_SIGNATURE_TIMESTAMP] = nows;
+		return true;
+	} catch ( ... ) {
+		return false;
+	}
+}
+
+bool Dictionary::verify(const Identity &id) const
+{
+	try {
+		std::string buf;
+		_mkSigBuf(buf);
+		const_iterator sig(find(ZT_DICTIONARY_SIGNATURE));
+		if (sig == end())
+			return false;
+		std::string sigbin(Utils::unhex(sig->second));
+		return id.verify(buf.data(),buf.length(),sigbin.data(),sigbin.length());
+	} catch ( ... ) {
+		return false;
+	}
+}
+
+void Dictionary::_mkSigBuf(std::string &buf) const
+{
+	unsigned long pairs = 0;
+	for(const_iterator i(begin());i!=end();++i) {
+		if ((i->first.length() < 2)||( (i->first[0] != '~')&&(i->first[1] != '!') )) {
+			buf.append(i->first);
+			buf.push_back('=');
+			buf.append(i->second);
+			buf.push_back('\0');
+			++pairs;
+		}
+	}
+	buf.push_back((char)0xff);
+	buf.push_back((char)((pairs >> 24) & 0xff)); // pad with number of key/value pairs at end
+	buf.push_back((char)((pairs >> 16) & 0xff));
+	buf.push_back((char)((pairs >> 8) & 0xff));
+	buf.push_back((char)(pairs & 0xff));
+}
+
+void Dictionary::_appendEsc(const char *data,unsigned int len,std::string &to)
+{
+	for(unsigned int i=0;i<len;++i) {
+		switch(data[i]) {
+			case 0:
+				to.append("\\0");
+				break;
+			case '\r':
+				to.append("\\r");
+				break;
+			case '\n':
+				to.append("\\n");
+				break;
+			case '\\':
+				to.append("\\\\");
+				break;
+			case '=':
+				to.append("\\=");
+				break;
+			default:
+				to.push_back(data[i]);
+				break;
+		}
+	}
+}
+
+} // namespace ZeroTier

+ 52 - 85
node/Dictionary.hpp

@@ -34,8 +34,15 @@
  
  
 #include "Constants.hpp"
 #include "Constants.hpp"
 
 
+// Three fields are added/updated by sign()
+#define ZT_DICTIONARY_SIGNATURE "~!ed25519"
+#define ZT_DICTIONARY_SIGNATURE_IDENTITY "~!sigid"
+#define ZT_DICTIONARY_SIGNATURE_TIMESTAMP "~!sigts"
+
 namespace ZeroTier {
 namespace ZeroTier {
 
 
+class Identity;
+
 /**
 /**
  * Simple key/value dictionary with string serialization
  * Simple key/value dictionary with string serialization
  *
  *
@@ -43,29 +50,29 @@ namespace ZeroTier {
  * It does not support comments or other syntactic complexities. It is
  * It does not support comments or other syntactic complexities. It is
  * human-readable if the keys and values in the dictionary are also
  * human-readable if the keys and values in the dictionary are also
  * human-readable. Otherwise it might contain unprintable characters.
  * human-readable. Otherwise it might contain unprintable characters.
+ *
+ * Keys beginning with "~!" are reserved for signatures and are ignored
+ * during the signature process.
+ *
+ * Note: the signature code depends on std::map<> being sorted, but no
+ * other code does. So if the underlying data structure is ever swapped
+ * out for an unsorted one, the signature code will have to be updated
+ * to sort before composing the string to sign.
  */
  */
 class Dictionary : public std::map<std::string,std::string>
 class Dictionary : public std::map<std::string,std::string>
 {
 {
 public:
 public:
-	Dictionary()
-	{
-	}
+	Dictionary() {}
 
 
 	/**
 	/**
 	 * @param s String-serialized dictionary
 	 * @param s String-serialized dictionary
 	 */
 	 */
-	Dictionary(const char *s)
-	{
-		fromString(s);
-	}
+	Dictionary(const char *s) { fromString(s); }
 
 
 	/**
 	/**
 	 * @param s String-serialized dictionary
 	 * @param s String-serialized dictionary
 	 */
 	 */
-	Dictionary(const std::string &s)
-	{
-		fromString(s.c_str());
-	}
+	Dictionary(const std::string &s) { fromString(s.c_str()); }
 
 
 	/**
 	/**
 	 * Get a key, throwing an exception if it is not present
 	 * Get a key, throwing an exception if it is not present
@@ -102,10 +109,7 @@ public:
 	 * @param key Key to check
 	 * @param key Key to check
 	 * @return True if dictionary contains key
 	 * @return True if dictionary contains key
 	 */
 	 */
-	inline bool contains(const std::string &key) const
-	{
-		return (find(key) != end());
-	}
+	inline bool contains(const std::string &key) const { return (find(key) != end()); }
 
 
 	/**
 	/**
 	 * @return String-serialized dictionary
 	 * @return String-serialized dictionary
@@ -113,14 +117,12 @@ public:
 	inline std::string toString() const
 	inline std::string toString() const
 	{
 	{
 		std::string s;
 		std::string s;
-
 		for(const_iterator kv(begin());kv!=end();++kv) {
 		for(const_iterator kv(begin());kv!=end();++kv) {
 			_appendEsc(kv->first.data(),(unsigned int)kv->first.length(),s);
 			_appendEsc(kv->first.data(),(unsigned int)kv->first.length(),s);
 			s.push_back('=');
 			s.push_back('=');
 			_appendEsc(kv->second.data(),(unsigned int)kv->second.length(),s);
 			_appendEsc(kv->second.data(),(unsigned int)kv->second.length(),s);
 			s.append(ZT_EOL_S);
 			s.append(ZT_EOL_S);
 		}
 		}
-
 		return s;
 		return s;
 	}
 	}
 
 
@@ -129,78 +131,43 @@ public:
 	 *
 	 *
 	 * @param s String-serialized dictionary
 	 * @param s String-serialized dictionary
 	 */
 	 */
-	inline void fromString(const char *s)
-	{
-		clear();
-		bool escapeState = false;
-		std::string keyBuf;
-		std::string *element = &keyBuf;
-		while (*s) {
-			if (escapeState) {
-				escapeState = false;
-				switch(*s) {
-					case '0':
-						element->push_back((char)0);
-						break;
-					case 'r':
-						element->push_back('\r');
-						break;
-					case 'n':
-						element->push_back('\n');
-						break;
-					default:
-						element->push_back(*s);
-						break;
-				}
-			} else {
-				if (*s == '\\') {
-					escapeState = true;
-				} else if (*s == '=') {
-					if (element == &keyBuf)
-						element = &((*this)[keyBuf]);
-				} else if ((*s == '\r')||(*s == '\n')) {
-					if ((element == &keyBuf)&&(keyBuf.length() > 0))
-						(*this)[keyBuf];
-					keyBuf = "";
-					element = &keyBuf;
-				} else element->push_back(*s);
-			}
-			++s;
-		}
-		if ((element == &keyBuf)&&(keyBuf.length() > 0))
-			(*this)[keyBuf];
-	}
-	inline void fromString(const std::string &s)
+	void fromString(const char *s);
+	inline void fromString(const std::string &s) { fromString(s.c_str()); }
+
+	/**
+	 * @return True if this dictionary is cryptographically signed
+	 */
+	inline bool hasSignature() const { return (find(ZT_DICTIONARY_SIGNATURE) != end()); }
+
+	/**
+	 * Remove any signature from this dictionary
+	 */
+	inline void removeSignature()
 	{
 	{
-		fromString(s.c_str());
+		erase(ZT_DICTIONARY_SIGNATURE);
+		erase(ZT_DICTIONARY_SIGNATURE_IDENTITY);
+		erase(ZT_DICTIONARY_SIGNATURE_TIMESTAMP);
 	}
 	}
 
 
+	/**
+	 * Add or update signature fields with a signature of all other keys and values
+	 *
+	 * @param with Identity to sign with (must have secret key)
+	 * @return True on success
+	 */
+	bool sign(const Identity &id);
+
+	/**
+	 * Verify signature against an identity
+	 *
+	 * @param id Identity to verify against
+	 * @return True if signature verification OK
+	 */
+	bool verify(const Identity &id) const;
+
 private:
 private:
-	static inline void _appendEsc(const char *data,unsigned int len,std::string &to)
-	{
-		for(unsigned int i=0;i<len;++i) {
-			switch(data[i]) {
-				case 0:
-					to.append("\\0");
-					break;
-				case '\r':
-					to.append("\\r");
-					break;
-				case '\n':
-					to.append("\\n");
-					break;
-				case '\\':
-					to.append("\\\\");
-					break;
-				case '=':
-					to.append("\\=");
-					break;
-				default:
-					to.push_back(data[i]);
-					break;
-			}
-		}
-	}
+	void _mkSigBuf(std::string &buf) const;
+	static void _appendEsc(const char *data,unsigned int len,std::string &to);
 };
 };
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 14 - 32
node/Utils.cpp

@@ -97,12 +97,16 @@ std::string Utils::hex(const void *data,unsigned int len)
 	return r;
 	return r;
 }
 }
 
 
-std::string Utils::unhex(const char *hex)
+std::string Utils::unhex(const char *hex,unsigned int maxlen)
 {
 {
 	int n = 1;
 	int n = 1;
 	unsigned char c,b = 0;
 	unsigned char c,b = 0;
+	const char *eof = hex + maxlen;
 	std::string r;
 	std::string r;
 
 
+	if (!maxlen)
+		return r;
+
 	while ((c = (unsigned char)*(hex++))) {
 	while ((c = (unsigned char)*(hex++))) {
 		if ((c >= 48)&&(c <= 57)) { // 0..9
 		if ((c >= 48)&&(c <= 57)) { // 0..9
 			if ((n ^= 1))
 			if ((n ^= 1))
@@ -117,48 +121,24 @@ std::string Utils::unhex(const char *hex)
 				r.push_back((char)(b | (c - (97 - 10))));
 				r.push_back((char)(b | (c - (97 - 10))));
 			else b = (c - (97 - 10)) << 4;
 			else b = (c - (97 - 10)) << 4;
 		}
 		}
+		if (hex == eof)
+			break;
 	}
 	}
 
 
 	return r;
 	return r;
 }
 }
 
 
-unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len)
+unsigned int Utils::unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len)
 {
 {
 	int n = 1;
 	int n = 1;
 	unsigned char c,b = 0;
 	unsigned char c,b = 0;
 	unsigned int l = 0;
 	unsigned int l = 0;
+	const char *eof = hex + maxlen;
 
 
-	while ((c = (unsigned char)*(hex++))) {
-		if ((c >= 48)&&(c <= 57)) { // 0..9
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - 48));
-			} else b = (c - 48) << 4;
-		} else if ((c >= 65)&&(c <= 70)) { // A..F
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - (65 - 10)));
-			} else b = (c - (65 - 10)) << 4;
-		} else if ((c >= 97)&&(c <= 102)) { // a..f
-			if ((n ^= 1)) {
-				if (l >= len) break;
-				((unsigned char *)buf)[l++] = (b | (c - (97 - 10)));
-			} else b = (c - (97 - 10)) << 4;
-		}
-	}
-
-	return l;
-}
-
-unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len)
-{
-	int n = 1;
-	unsigned char c,b = 0;
-	unsigned int l = 0;
-	const char *const end = hex + hexlen;
+	if (!maxlen)
+		return 0;
 
 
-	while (hex != end) {
-		c = (unsigned char)*(hex++);
+	while ((c = (unsigned char)*(hex++))) {
 		if ((c >= 48)&&(c <= 57)) { // 0..9
 		if ((c >= 48)&&(c <= 57)) { // 0..9
 			if ((n ^= 1)) {
 			if ((n ^= 1)) {
 				if (l >= len) break;
 				if (l >= len) break;
@@ -175,6 +155,8 @@ unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned
 				((unsigned char *)buf)[l++] = (b | (c - (97 - 10)));
 				((unsigned char *)buf)[l++] = (b | (c - (97 - 10)));
 			} else b = (c - (97 - 10)) << 4;
 			} else b = (c - (97 - 10)) << 4;
 		}
 		}
+		if (hex == eof)
+			break;
 	}
 	}
 
 
 	return l;
 	return l;

+ 7 - 19
node/Utils.hpp

@@ -145,11 +145,12 @@ public:
 	 * This ignores all non-hex characters, just stepping over them and
 	 * This ignores all non-hex characters, just stepping over them and
 	 * continuing. Upper and lower case are supported for letters a-f.
 	 * continuing. Upper and lower case are supported for letters a-f.
 	 *
 	 *
-	 * @param hex Hexadecimal ASCII code (non-hex chars are ignored)
+	 * @param hex Hexadecimal ASCII code (non-hex chars are ignored, stops at zero or maxlen)
+	 * @param maxlen Maximum length of hex string buffer
 	 * @return Binary data
 	 * @return Binary data
 	 */
 	 */
-	static std::string unhex(const char *hex);
-	static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str()); }
+	static std::string unhex(const char *hex,unsigned int maxlen);
+	static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str(),(unsigned int)hex.length()); }
 
 
 	/**
 	/**
 	 * Convert hexadecimal to binary data
 	 * Convert hexadecimal to binary data
@@ -158,26 +159,13 @@ public:
 	 * continuing. Upper and lower case are supported for letters a-f.
 	 * continuing. Upper and lower case are supported for letters a-f.
 	 *
 	 *
 	 * @param hex Hexadecimal ASCII
 	 * @param hex Hexadecimal ASCII
+	 * @param maxlen Maximum length of hex string buffer
 	 * @param buf Buffer to fill
 	 * @param buf Buffer to fill
 	 * @param len Length of buffer
 	 * @param len Length of buffer
 	 * @return Number of characters actually written
 	 * @return Number of characters actually written
 	 */
 	 */
-	static unsigned int unhex(const char *hex,void *buf,unsigned int len);
-	static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),buf,len); }
-
-	/**
-	 * Convert hexadecimal to binary data
-	 *
-	 * This ignores all non-hex characters, just stepping over them and
-	 * continuing. Upper and lower case are supported for letters a-f.
-	 *
-	 * @param hex Hexadecimal ASCII
-	 * @param hexlen Length of hex ASCII
-	 * @param buf Buffer to fill
-	 * @param len Length of buffer
-	 * @return Number of bytes actually written to buffer
-	 */
-	static unsigned int unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len);
+	static unsigned int unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len);
+	static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),hex.length(),buf,len); }
 
 
 	/**
 	/**
 	 * Generate secure random bytes
 	 * Generate secure random bytes

+ 1 - 0
objects.mk

@@ -3,6 +3,7 @@ OBJS=\
 	node/C25519.o \
 	node/C25519.o \
 	node/CertificateOfMembership.o \
 	node/CertificateOfMembership.o \
 	node/Defaults.o \
 	node/Defaults.o \
+	node/Dictionary.o \
 	node/HttpClient.o \
 	node/HttpClient.o \
 	node/Identity.o \
 	node/Identity.o \
 	node/InetAddress.o \
 	node/InetAddress.o \