ソースを参照

Break out certificate of membership into its own class.

Adam Ierymenko 12 年 前
コミット
b4ae1adfbf
5 ファイル変更414 行追加335 行削除
  1. 220 0
      node/CertificateOfMembership.cpp
  2. 192 0
      node/CertificateOfMembership.hpp
  3. 0 188
      node/Network.cpp
  4. 1 147
      node/Network.hpp
  5. 1 0
      objects.mk

+ 220 - 0
node/CertificateOfMembership.cpp

@@ -0,0 +1,220 @@
+/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2012-2013  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 <algorithm>
+
+#include "CertificateOfMembership.hpp"
+
+namespace ZeroTier {
+
+void CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta)
+{
+	_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 CertificateOfMembership::toString() const
+{
+	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 s;
+}
+
+void CertificateOfMembership::fromString(const char *s)
+{
+	_qualifiers.clear();
+	_signedBy.zero();
+	memset(_signature.data,0,_signature.size());
+
+	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 CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const
+	throw()
+{
+	unsigned long myidx = 0;
+	unsigned long otheridx = 0;
+
+	while (myidx < _qualifiers.size()) {
+		// Fail if we're at the end of other, since this means the field is
+		// missing.
+		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._qualifiers[otheridx].id != _qualifiers[myidx].id) {
+			++otheridx;
+			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 = _qualifiers[myidx].value;
+		uint64_t b = other._qualifiers[myidx].value;
+		if (a >= b) {
+			if ((a - b) > _qualifiers[myidx].maxDelta)
+				return false;
+		} else {
+			if ((b - a) > _qualifiers[myidx].maxDelta)
+				return false;
+		}
+
+		++myidx;
+	}
+
+	return true;
+}
+
+bool 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 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;
+}
+
+} // namespace ZeroTier

+ 192 - 0
node/CertificateOfMembership.hpp

@@ -0,0 +1,192 @@
+/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2012-2013  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/
+ */
+
+#ifndef _ZT_CERTIFICATEOFMEMBERSHIP_HPP
+#define _ZT_CERTIFICATEOFMEMBERSHIP_HPP
+
+#include <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "Constants.hpp"
+#include "Address.hpp"
+#include "C25519.hpp"
+#include "Identity.hpp"
+
+namespace ZeroTier {
+
+/**
+ * Certificate of network membership
+ *
+ * The COM contains a sorted set of three-element tuples called qualifiers.
+ * These contain 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. 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:
+	/**
+	 * 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() { memset(_signature.data,0,_signature.size()); }
+	CertificateOfMembership(const char *s) { fromString(s); }
+	CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }
+
+	/**
+	 * Add or update a qualifier in this certificate
+	 *
+	 * Any signature is invalidated and signedBy is set to null.
+	 *
+	 * @param id Qualifier ID
+	 * @param value Qualifier value
+	 * @param maxDelta Qualifier maximum allowed difference (absolute value of difference)
+	 */
+	void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta);
+
+	/**
+	 * @return String-serialized representation of this certificate
+	 */
+	std::string toString() const;
+
+	/**
+	 * Set this certificate equal to the hex-serialized string
+	 *
+	 * Invalid strings will result in invalid or undefined certificate
+	 * contents. These will subsequently fail validation and comparison.
+	 *
+	 * @param s String to deserialize
+	 */
+	void fromString(const char *s);
+	inline void fromString(const std::string &s) { fromString(s.c_str()); }
+
+	/**
+	 * Compare two certificates for parameter agreement
+	 *
+	 * This compares this certificate with the other and returns true if all
+	 * 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 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 _Qualifier
+	{
+		_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;
+
+		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<_Qualifier> _qualifiers; // sorted by id and unique
+	Address _signedBy;
+	C25519::Signature _signature;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 0 - 188
node/Network.cpp

@@ -42,194 +42,6 @@
 
 namespace ZeroTier {
 
-void Network::CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta)
-{
-	_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
-{
-	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 s;
-}
-
-void Network::CertificateOfMembership::fromString(const char *s)
-{
-	_qualifiers.clear();
-	_signedBy.zero();
-	memset(_signature.data,0,_signature.size());
-
-	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::agreesWith(const CertificateOfMembership &other) const
-	throw()
-{
-	unsigned long myidx = 0;
-	unsigned long otheridx = 0;
-
-	while (myidx < _qualifiers.size()) {
-		// Fail if we're at the end of other, since this means the field is
-		// missing.
-		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._qualifiers[otheridx].id != _qualifiers[myidx].id) {
-			++otheridx;
-			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 = _qualifiers[myidx].value;
-		uint64_t b = other._qualifiers[myidx].value;
-		if (a >= b) {
-			if ((a - b) > _qualifiers[myidx].maxDelta)
-				return false;
-		} else {
-			if ((b - a) > _qualifiers[myidx].maxDelta)
-				return false;
-		}
-
-		++myidx;
-	}
-
-	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)

+ 1 - 147
node/Network.hpp

@@ -51,7 +51,7 @@
 #include "Identity.hpp"
 #include "InetAddress.hpp"
 #include "BandwidthAccount.hpp"
-#include "C25519.hpp"
+#include "CertificateOfMembership.hpp"
 
 namespace ZeroTier {
 
@@ -84,152 +84,6 @@ class Network : NonCopyable
 	friend class NodeConfig;
 
 public:
-	/**
-	 * Certificate of network membership
-	 *
-	 * The COM contains a sorted set of three-element tuples called qualifiers.
-	 * These contain 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. 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:
-		/**
-		 * 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() { memset(_signature.data,0,_signature.size()); }
-		CertificateOfMembership(const char *s) { fromString(s); }
-		CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }
-
-		/**
-		 * Add or update a qualifier in this certificate
-		 *
-		 * Any signature is invalidated and signedBy is set to null.
-		 *
-		 * @param id Qualifier ID
-		 * @param value Qualifier value
-		 * @param maxDelta Qualifier maximum allowed difference (absolute value of difference)
-		 */
-		void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta);
-
-		/**
-		 * @return String-serialized representation of this certificate
-		 */
-		std::string toString() const;
-
-		/**
-		 * Set this certificate equal to the hex-serialized string
-		 *
-		 * Invalid strings will result in invalid or undefined certificate
-		 * contents. These will subsequently fail validation and comparison.
-		 *
-		 * @param s String to deserialize
-		 */
-		void fromString(const char *s);
-		inline void fromString(const std::string &s) { fromString(s.c_str()); }
-
-		/**
-		 * Compare two certificates for parameter agreement
-		 *
-		 * This compares this certificate with the other and returns true if all
-		 * 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 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 _Qualifier
-		{
-			_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;
-
-			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<_Qualifier> _qualifiers; // sorted by id and unique
-		Address _signedBy;
-		C25519::Signature _signature;
-	};
-
 	/**
 	 * Preload and rates of accrual for multicast group bandwidth limits
 	 *

+ 1 - 0
objects.mk

@@ -3,6 +3,7 @@ OBJS=\
 	ext/lz4/lz4hc.o \
 	ext/lz4/lz4.o \
 	node/C25519.o \
+	node/CertificateOfMembership.o \
 	node/Defaults.o \
 	node/Demarc.o \
 	node/EthernetTap.o \