Browse Source

Flesh out membership certificate with signature, better serialize/deserialize, and rename parameter to qualifier to make better conceptual sense.

Adam Ierymenko 11 năm trước cách đây
mục cha
commit
ea4e1136dd
6 tập tin đã thay đổi với 312 bổ sung67 xóa
  1. 9 0
      node/Address.hpp
  2. 13 0
      node/Identity.hpp
  3. 149 35
      node/Network.cpp
  4. 100 32
      node/Network.hpp
  5. 31 0
      node/Utils.cpp
  6. 10 0
      node/Utils.hpp

+ 9 - 0
node/Address.hpp

@@ -231,6 +231,15 @@ public:
 	 */
 	inline operator bool() const throw() { return (_a != 0); }
 
+	/**
+	 * Set to null/zero
+	 */
+	inline void zero()
+		throw()
+	{
+		_a = 0;
+	}
+
 	/**
 	 * Check if this address is reserved
 	 * 

+ 13 - 0
node/Identity.hpp

@@ -180,6 +180,19 @@ public:
 		return C25519::verify(_publicKey,data,len,signature);
 	}
 
+	/**
+	 * Verify a message signature against this identity
+	 *
+	 * @param data Data to check
+	 * @param len Length of data
+	 * @param signature Signature
+	 * @return True if signature validates and data integrity checks
+	 */
+	inline bool verify(const void *data,unsigned int len,const C25519::Signature &signature) const
+	{
+		return C25519::verify(_publicKey,data,len,signature);
+	}
+
 	/**
 	 * Shortcut method to perform key agreement with another identity
 	 *

+ 149 - 35
node/Network.cpp

@@ -30,6 +30,9 @@
 #include <stdlib.h>
 #include <math.h>
 
+#include <algorithm>
+#include <utility>
+
 #include "Constants.hpp"
 #include "RuntimeEnvironment.hpp"
 #include "NodeConfig.hpp"
@@ -40,73 +43,135 @@
 
 namespace ZeroTier {
 
-void Network::CertificateOfMembership::addParameter(uint64_t id,uint64_t value,uint64_t maxDelta)
+void Network::CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta)
 {
-	_params.push_back(_Parameter(id,value,maxDelta));
-	std::sort(_params.begin(),_params.end(),_SortByIdComparison());
+	_signedBy.zero();
+
+	for(std::vector<_Qualifier>::iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+		if (q->id == id) {
+			q->value = value;
+			q->maxDelta = maxDelta;
+			return;
+		}
+	}
+
+	_qualifiers.push_back(_Qualifier(id,value,maxDelta));
+	std::sort(_qualifiers.begin(),_qualifiers.end());
 }
 
 std::string Network::CertificateOfMembership::toString() const
 {
-	uint64_t tmp[3000];
-	unsigned long n = 0;
-	for(std::vector<_Parameter>::const_iterator p(_params.begin());p!=_params.end();++p) {
-		tmp[n++] = Utils::hton(p->id);
-		tmp[n++] = Utils::hton(p->value);
-		tmp[n++] = Utils::hton(p->maxDelta);
-		if (n >= 3000)
-			break; // sanity check -- certificates will never even approach this size
+	std::string s;
+
+	uint64_t *buf = new uint64_t[_qualifiers.size() * 3];
+	try {
+		unsigned int ptr = 0;
+		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+			buf[ptr++] = Utils::hton(q->id);
+			buf[ptr++] = Utils::hton(q->value);
+			buf[ptr++] = Utils::hton(q->maxDelta);
+		}
+		s.append(Utils::hex(buf,ptr * sizeof(uint64_t)));
+		delete [] buf;
+	} catch ( ... ) {
+		delete [] buf;
+		throw;
+	}
+
+	s.push_back(':');
+
+	s.append(_signedBy.toString());
+
+	if (_signedBy) {
+		s.push_back(':');
+		s.append(Utils::hex(_signature.data,_signature.size()));
 	}
-	return Utils::hex(tmp,sizeof(uint64_t) * n);
+
+	return s;
 }
 
 void Network::CertificateOfMembership::fromString(const char *s)
 {
-	std::string tmp(Utils::unhex(s));
-	_params.clear();
-	const char *ptr = tmp.data();
-	unsigned long remaining = tmp.length();
-	while (remaining >= 24) {
-		_Parameter p;
-		p.id = Utils::ntoh(*((const uint64_t *)(ptr)));
-		p.value = Utils::ntoh(*((const uint64_t *)(ptr + 8)));
-		p.maxDelta = Utils::ntoh(*((const uint64_t *)(ptr + 16)));
-		_params.push_back(p);
-		ptr += 24;
-		remaining -= 24;
+	_qualifiers.clear();
+	_signedBy.zero();
+
+	unsigned int colonAt = 0;
+	while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt;
+
+	if (colonAt) {
+		unsigned int buflen = colonAt / 2;
+		char *buf = new char[buflen];
+		unsigned int bufactual = Utils::unhex(s,colonAt,buf,buflen);
+		char *bufptr = buf;
+		try {
+			while (bufactual >= 24) {
+				_qualifiers.push_back(_Qualifier());
+				_qualifiers.back().id = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8;
+				_qualifiers.back().value = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8;
+				_qualifiers.back().maxDelta = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8;
+				bufactual -= 24;
+			}
+		} catch ( ... ) {}
+		delete [] buf;
 	}
+
+	if (s[colonAt]) {
+		s += colonAt + 1;
+		colonAt = 0;
+		while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt;
+
+		if (colonAt) {
+			char addrbuf[ZT_ADDRESS_LENGTH];
+			if (Utils::unhex(s,colonAt,addrbuf,sizeof(addrbuf)) == ZT_ADDRESS_LENGTH)
+				_signedBy.setTo(addrbuf,ZT_ADDRESS_LENGTH);
+
+			if ((_signedBy)&&(s[colonAt])) {
+				s += colonAt + 1;
+				colonAt = 0;
+				while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt;
+
+				if (colonAt) {
+					if (Utils::unhex(s,colonAt,_signature.data,_signature.size()) != _signature.size())
+						_signedBy.zero();
+				} else _signedBy.zero();
+			} else _signedBy.zero();
+		}
+	}
+
+	std::sort(_qualifiers.begin(),_qualifiers.end());
+	std::unique(_qualifiers.begin(),_qualifiers.end());
 }
 
-bool Network::CertificateOfMembership::compare(const CertificateOfMembership &other) const
+bool Network::CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const
 	throw()
 {
 	unsigned long myidx = 0;
 	unsigned long otheridx = 0;
 
-	while (myidx < _params.size()) {
+	while (myidx < _qualifiers.size()) {
 		// Fail if we're at the end of other, since this means the field is
 		// missing.
-		if (otheridx >= other._params.size())
+		if (otheridx >= other._qualifiers.size())
 			return false;
 
 		// Seek to corresponding tuple in other, ignoring tuples that
 		// we may not have. If we run off the end of other, the tuple is
 		// missing. This works because tuples are sorted by ID.
-		while (other._params[otheridx].id != _params[myidx].id) {
+		while (other._qualifiers[otheridx].id != _qualifiers[myidx].id) {
 			++otheridx;
-			if (otheridx >= other._params.size())
+			if (otheridx >= other._qualifiers.size())
 				return false;
 		}
 
 		// Compare to determine if the absolute value of the difference
 		// between these two parameters is within our maxDelta.
-		uint64_t a = _params[myidx].value;
-		uint64_t b = other._params[myidx].value;
+		uint64_t a = _qualifiers[myidx].value;
+		uint64_t b = other._qualifiers[myidx].value;
 		if (a >= b) {
-			if ((a - b) > _params[myidx].maxDelta)
+			if ((a - b) > _qualifiers[myidx].maxDelta)
 				return false;
 		} else {
-			if ((b - a) > _params[myidx].maxDelta)
+			if ((b - a) > _qualifiers[myidx].maxDelta)
 				return false;
 		}
 
@@ -116,6 +181,55 @@ bool Network::CertificateOfMembership::compare(const CertificateOfMembership &ot
 	return true;
 }
 
+bool Network::CertificateOfMembership::sign(const Identity &with)
+{
+	uint64_t *buf = new uint64_t[_qualifiers.size() * 3];
+	unsigned int ptr = 0;
+	for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+		buf[ptr++] = Utils::hton(q->id);
+		buf[ptr++] = Utils::hton(q->value);
+		buf[ptr++] = Utils::hton(q->maxDelta);
+	}
+
+	try {
+		_signature = with.sign(buf,ptr * sizeof(uint64_t));
+		_signedBy = with.address();
+		delete [] buf;
+		return true;
+	} catch ( ... ) {
+		_signedBy.zero();
+		delete [] buf;
+		return false;
+	}
+}
+
+bool Network::CertificateOfMembership::verify(const Identity &id) const
+{
+	if (!_signedBy)
+		return false;
+	if (id.address() != _signedBy)
+		return false;
+
+	uint64_t *buf = new uint64_t[_qualifiers.size() * 3];
+	unsigned int ptr = 0;
+	for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+		buf[ptr++] = Utils::hton(q->id);
+		buf[ptr++] = Utils::hton(q->value);
+		buf[ptr++] = Utils::hton(q->maxDelta);
+	}
+
+	bool valid = false;
+	try {
+		valid = id.verify(buf,ptr * sizeof(uint64_t),_signature);
+		delete [] buf;
+	} catch ( ... ) {
+		delete [] buf;
+	}
+	return valid;
+}
+
+// ---------------------------------------------------------------------------
+
 const Network::MulticastRates::Rate Network::MulticastRates::GLOBAL_DEFAULT_RATE(65535,65535,64);
 
 const char *Network::statusString(const Status s)
@@ -253,7 +367,7 @@ bool Network::isAllowed(const Address &peer) const
 		std::map<Address,CertificateOfMembership>::const_iterator pc(_membershipCertificates.find(peer));
 		if (pc == _membershipCertificates.end())
 			return false;
-		return _myCertificate.compare(pc->second);
+		return _myCertificate.agreesWith(pc->second);
 	} catch (std::exception &exc) {
 		TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what());
 	} catch ( ... ) {
@@ -282,7 +396,7 @@ void Network::clean()
 		}
 
 		for(std::map<Address,CertificateOfMembership>::iterator i=(_membershipCertificates.begin());i!=_membershipCertificates.end();) {
-			if (_myCertificate.compare(i->second)) {
+			if (_myCertificate.agreesWith(i->second)) {
 				if ((!writeError)&&(mcdb)) {
 					char tmp[ZT_ADDRESS_LENGTH];
 					i->first.copyTo(tmp,ZT_ADDRESS_LENGTH);

+ 100 - 32
node/Network.hpp

@@ -51,6 +51,7 @@
 #include "Identity.hpp"
 #include "InetAddress.hpp"
 #include "BandwidthAccount.hpp"
+#include "C25519.hpp"
 
 namespace ZeroTier {
 
@@ -86,37 +87,71 @@ public:
 	/**
 	 * Certificate of network membership
 	 *
-	 * The COM consists of a series of three-element 64-bit tuples. These values
-	 * are an id, a value, and a maximum delta. The ID is arbitrary and should be
-	 * assigned using a scheme that makes every ID globally unique for a given
-	 * type of parameter. ID 0 is reserved for the always-present timestamp
-	 * parameter. The value is parameter-specific. The maximum delta is the
-	 * maximum difference that is permitted between two values for determining
-	 * whether a certificate permits two peers to speak to one another. A value
-	 * of zero indicates that the values must equal.
+	 * The COM contains a sorted set of three-element tuples called qualifiers.
+	 * These contain an id, a value, and a maximum delta.
 	 *
-	 * Certificates of membership must be signed by the netconf master for the
-	 * network in question. This permits members to verify these certs against
-	 * the netconf master's public key before testing them.
+	 * The ID is arbitrary and should be assigned using a scheme that makes
+	 * every ID globally unique. ID 0 is reserved for the always-present
+	 * validity timestamp and range, and ID 1 is reserved for the always-present
+	 * network ID. IDs less than 65536 are reserved for future global
+	 * assignment.
+	 *
+	 * The value's meaning is ID-specific and isn't important here. What's
+	 * important is the value and the third member of the tuple: the maximum
+	 * delta. The maximum delta is the maximum difference permitted between
+	 * values for a given ID between certificates for the two certificates to
+	 * themselves agree.
+	 *
+	 * Network membership is checked by checking whether a peer's certificate
+	 * agrees with your own. The timestamp provides the fundamental criterion--
+	 * each member of a private network must constantly obtain new certificates
+	 * often enough to stay within the max delta for this qualifier. But other
+	 * criteria could be added in the future for very special behaviors, things
+	 * like latitude and longitude for instance.
 	 */
 	class CertificateOfMembership
 	{
 	public:
-		CertificateOfMembership() throw() {}
+		/**
+		 * Certificate type codes, used in serialization
+		 *
+		 * Only one so far, and only one hopefully there shall be for quite some
+		 * time.
+		 */
+		enum Type
+		{
+			COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519
+		};
+
+		/**
+		 * Reserved COM IDs
+		 *
+		 * IDs below 65536 should be considered reserved for future global
+		 * assignment here.
+		 */
+		enum ReservedIds
+		{
+			COM_RESERVED_ID_TIMESTAMP = 0, // timestamp, max delta defines cert life
+			COM_RESERVED_ID_NETWORK_ID = 1 // network ID, max delta always 0
+		};
+
+		CertificateOfMembership() {}
 		CertificateOfMembership(const char *s) { fromString(s); }
 		CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }
 
 		/**
-		 * Add a paramter to this certificate
+		 * Add or update a qualifier in this certificate
+		 *
+		 * Any signature is invalidated and signedBy is set to null.
 		 *
-		 * @param id Parameter ID
-		 * @param value Parameter value
-		 * @param maxDelta Parameter maximum difference with others
+		 * @param id Qualifier ID
+		 * @param value Qualifier value
+		 * @param maxDelta Qualifier maximum allowed difference (absolute value of difference)
 		 */
-		void addParameter(uint64_t id,uint64_t value,uint64_t maxDelta);
+		void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta);
 
 		/**
-		 * @return Hex-serialized representation of this certificate (minus signature)
+		 * @return String-serialized representation of this certificate
 		 */
 		std::string toString() const;
 
@@ -138,36 +173,69 @@ public:
 		 * paramters in this cert are present in the other and if they agree to
 		 * within this cert's max delta value for each given parameter.
 		 *
+		 * Tuples present in other but not in this cert are ignored, but any
+		 * tuples present in this cert but not in other result in 'false'.
+		 *
 		 * @param other Cert to compare with
 		 * @return True if certs agree and 'other' may be communicated with
 		 */
-		bool compare(const CertificateOfMembership &other) const
+		bool agreesWith(const CertificateOfMembership &other) const
 			throw();
 
+		/**
+		 * Sign this certificate
+		 *
+		 * @param with Identity to sign with, must include private key
+		 * @return True if signature was successful
+		 */
+		bool sign(const Identity &with);
+
+		/**
+		 * Verify certificate against an identity
+		 *
+		 * @param id Identity to verify against
+		 * @return True if certificate is signed by this identity and verification was successful
+		 */
+		bool verify(const Identity &id) const;
+
+		/**
+		 * @return True if signed
+		 */
+		inline bool isSigned() const
+			throw()
+		{
+			return (_signedBy);
+		}
+
+		/**
+		 * @return Address that signed this certificate or null address if none
+		 */
+		inline const Address &signedBy() const
+			throw()
+		{
+			return _signedBy;
+		}
+
 	private:
-		struct _Parameter
+		struct _Qualifier
 		{
-			_Parameter() throw() {}
-			_Parameter(uint64_t i,uint64_t v,uint64_t m) throw() :
+			_Qualifier() throw() {}
+			_Qualifier(uint64_t i,uint64_t v,uint64_t m) throw() :
 				id(i),
 				value(v),
 				maxDelta(m) {}
+
 			uint64_t id;
 			uint64_t value;
 			uint64_t maxDelta;
-		};
 
-		// Used with std::sort to ensure that _params are sorted
-		struct _SortByIdComparison
-		{
-			inline bool operator()(const _Parameter &a,const _Parameter &b) const
-				throw()
-			{
-				return (a.id < b.id);
-			}
+			inline bool operator==(const _Qualifier &q) const throw() { return (id == q.id); } // for unique
+			inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort
 		};
 
-		std::vector<_Parameter> _params;
+		std::vector<_Qualifier> _qualifiers; // sorted by id and unique
+		Address _signedBy;
+		C25519::Signature _signature;
 	};
 
 	/**

+ 31 - 0
node/Utils.cpp

@@ -283,6 +283,37 @@ unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len)
 	return l;
 }
 
+unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len)
+	throw()
+{
+	int n = 1;
+	unsigned char c,b = 0;
+	unsigned int l = 0;
+	const char *const end = hex + hexlen;
+
+	while (hex != end) {
+		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;
+}
+
 void Utils::getSecureRandom(void *buf,unsigned int bytes)
 {
 	static Mutex randomLock;

+ 10 - 0
node/Utils.hpp

@@ -148,6 +148,16 @@ public:
 	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); }
 
+	/**
+	 * @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)
+		throw();
+
 	/**
 	 * @param buf Buffer to fill
 	 * @param bytes Number of random bytes to generate