Kaynağa Gözat

Certificate of ownership -- used to secure against IP address spoofing, especially for IPv4 and regular IPv6.

Adam Ierymenko 8 yıl önce
ebeveyn
işleme
10185e92fa

+ 9 - 0
controller/EmbeddedNetworkController.cpp

@@ -1706,6 +1706,15 @@ void EmbeddedNetworkController::_request(
 		}
 	}
 
+	// Issue a certificate of ownership for all static IPs
+	if (nc.staticIpCount) {
+		nc.certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1);
+		for(unsigned int i=0;i<nc.staticIpCount;++i)
+			nc.certificatesOfOwnership[0].addThing(nc.staticIps[i]);
+		nc.certificatesOfOwnership[0].sign(_signingId);
+		nc.certificateOfOwnershipCount = 1;
+	}
+
 	CertificateOfMembership com(now,credentialtmd,nwid,identity.address());
 	if (com.sign(_signingId)) {
 		nc.com = com;

+ 5 - 0
include/ZeroTierOne.h

@@ -136,6 +136,11 @@ extern "C" {
  */
 #define ZT_MAX_CAPABILITY_RULES 64
 
+/**
+ * Maximum number of certificates of ownership to assign to a single network member
+ */
+#define ZT_MAX_CERTIFICATES_OF_OWNERSHIP 4
+
 /**
  * Global maximum length for capability chain of custody (including initial issue)
  */

+ 0 - 1
node/Capability.hpp

@@ -396,7 +396,6 @@ public:
 
 		unsigned int p = startAt;
 
-		// These are the same between Tag and Capability
 		_nwid = b.template at<uint64_t>(p); p += 8;
 		_ts = b.template at<uint64_t>(p); p += 8;
 		_id = b.template at<uint32_t>(p); p += 4;

+ 46 - 0
node/CertificateOfOwnership.cpp

@@ -0,0 +1,46 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * 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/>.
+ */
+
+#include "CertificateOfOwnership.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Identity.hpp"
+#include "Topology.hpp"
+#include "Switch.hpp"
+#include "Network.hpp"
+
+namespace ZeroTier {
+
+int CertificateOfOwnership::verify(const RuntimeEnvironment *RR) const
+{
+	if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId)))
+		return -1;
+	const Identity id(RR->topology->getIdentity(_signedBy));
+	if (!id) {
+		RR->sw->requestWhois(_signedBy);
+		return 1;
+	}
+	try {
+		Buffer<(sizeof(CertificateOfOwnership) + 64)> tmp;
+		this->serialize(tmp,true);
+		return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1);
+	} catch ( ... ) {
+		return -1;
+	}
+}
+
+} // namespace ZeroTier

+ 251 - 0
node/CertificateOfOwnership.hpp

@@ -0,0 +1,251 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * 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/>.
+ */
+
+#ifndef ZT_CERTIFICATEOFOWNERSHIP_HPP
+#define ZT_CERTIFICATEOFOWNERSHIP_HPP
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "Constants.hpp"
+#include "C25519.hpp"
+#include "Address.hpp"
+#include "Identity.hpp"
+#include "Buffer.hpp"
+#include "InetAddress.hpp"
+#include "MAC.hpp"
+
+// Max things per CertificateOfOwnership
+#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS 16
+
+// Maximum size of a thing's value field in bytes
+#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE 16
+
+namespace ZeroTier {
+
+class RuntimeEnvironment;
+
+/**
+ * Certificate indicating ownership of a network identifier
+ */
+class CertificateOfOwnership
+{
+public:
+	enum Thing
+	{
+		THING_NULL = 0,
+		THING_MAC_ADDRESS = 1,
+		THING_IPV4_ADDRESS = 2,
+		THING_IPV6_ADDRESS = 3
+	};
+
+	CertificateOfOwnership() :
+		_networkId(0),
+		_ts(0),
+		_id(0),
+		_thingCount(0)
+	{
+	}
+
+	CertificateOfOwnership(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id) :
+		_networkId(nwid),
+		_ts(ts),
+		_flags(0),
+		_id(id),
+		_thingCount(0),
+		_issuedTo(issuedTo)
+	{
+	}
+
+	inline uint64_t networkId() const { return _networkId; }
+	inline uint64_t timestamp() const { return _ts; }
+	inline uint32_t id() const { return _id; }
+	inline unsigned int thingCount() const { return (unsigned int)_thingCount; }
+
+	inline Thing thingType(const unsigned int i) const { return (Thing)_thingTypes[i]; }
+	inline const uint8_t *thingValue(const unsigned int i) const { return _thingValues[i]; }
+
+	inline const Address &issuedTo() const { return _issuedTo; }
+
+	inline bool owns(const Thing &t,const void *v,unsigned int l)
+	{
+		for(unsigned int i=0,j=_thingCount;i<j;++i) {
+			if (_thingTypes[i] == (uint8_t)t) {
+				unsigned int k = 0;
+				while (k < l) {
+					if (reinterpret_cast<const uint8_t *>(v)[k] != _thingValues[i][k])
+						break;
+					++k;
+				}
+				if (k == l)
+					return true;
+			}
+		}
+		return false;
+	}
+
+	inline bool owns(const InetAddress &ip)
+	{
+		if (ip.ss_family == AF_INET)
+			return this->owns(THING_IPV4_ADDRESS,&(reinterpret_cast<const struct sockaddr_in *>(&ip)->sin_addr.s_addr),4);
+		if (ip.ss_family == AF_INET6)
+			return this->owns(THING_IPV6_ADDRESS,reinterpret_cast<const struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
+		return false;
+	}
+
+	inline bool owns(const MAC &mac)
+	{
+		uint8_t tmp[6];
+		mac.copyTo(tmp,6);
+		return this->owns(THING_MAC_ADDRESS,tmp,6);
+	}
+
+	inline void addThing(const InetAddress &ip)
+	{
+		if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return;
+		if (ip.ss_family == AF_INET) {
+			_thingTypes[_thingCount] = THING_IPV4_ADDRESS;
+			memcpy(_thingValues[_thingCount],&(reinterpret_cast<const struct sockaddr_in *>(&ip)->sin_addr.s_addr),4);
+			++_thingCount;
+		} else if (ip.ss_family == AF_INET6) {
+			_thingTypes[_thingCount] = THING_IPV6_ADDRESS;
+			memcpy(_thingValues[_thingCount],reinterpret_cast<const struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
+			++_thingCount;
+		}
+	}
+
+	inline void addThing(const MAC &mac)
+	{
+		if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return;
+		_thingTypes[_thingCount] = THING_MAC_ADDRESS;
+		mac.copyTo(_thingValues[_thingCount],6);
+		++_thingCount;
+	}
+
+	/**
+	 * @param signer Signing identity, must have private key
+	 * @return True if signature was successful
+	 */
+	inline bool sign(const Identity &signer)
+	{
+		if (signer.hasPrivate()) {
+			Buffer<sizeof(CertificateOfOwnership) + 64> tmp;
+			_signedBy = signer.address();
+			this->serialize(tmp,true);
+			_signature = signer.sign(tmp.data(),tmp.size());
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param RR Runtime environment to allow identity lookup for signedBy
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature
+	 */
+	int verify(const RuntimeEnvironment *RR) const;
+
+	template<unsigned int C>
+	inline void serialize(Buffer<C> &b,const bool forSign = false) const
+	{
+		if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+
+		b.append(_networkId);
+		b.append(_ts);
+		b.append(_flags);
+		b.append(_id);
+		b.append((uint16_t)_thingCount);
+		for(unsigned int i=0,j=_thingCount;i<j;++i) {
+			b.append((uint8_t)_thingTypes[i]);
+			b.append(_thingValues[i],ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
+		}
+
+		_issuedTo.appendTo(b);
+		_signedBy.appendTo(b);
+		if (!forSign) {
+			b.append((uint8_t)1); // 1 == Ed25519
+			b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); // length of signature
+			b.append(_signature.data,ZT_C25519_SIGNATURE_LEN);
+		}
+
+		b.append((uint16_t)0); // length of additional fields, currently 0
+
+		if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+	}
+
+	template<unsigned int C>
+	inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
+
+		memset(this,0,sizeof(CertificateOfOwnership));
+
+		_networkId = b.template at<uint64_t>(p); p += 8;
+		_ts = b.template at<uint64_t>(p); p += 8;
+		_flags = b.template at<uint64_t>(p); p += 8;
+		_id = b.template at<uint32_t>(p); p += 4;
+		_thingCount = b.template at<uint16_t>(p); p += 2;
+		for(unsigned int i=0,j=_thingCount;i<j;++i) {
+			if (i < ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
+				_thingTypes[i] = (uint8_t)b[p++];
+				memcpy(_thingValues[i],b.field(p,ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE),ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
+				p += ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE;
+			}
+		}
+
+		_issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
+		_signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
+		if (b[p++] == 1) {
+			if (b.template at<uint16_t>(p) != ZT_C25519_SIGNATURE_LEN)
+				throw std::runtime_error("invalid signature length");
+			p += 2;
+			memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN;
+		} else {
+			p += 2 + b.template at<uint16_t>(p);
+		}
+
+		p += 2 + b.template at<uint16_t>(p);
+		if (p > b.size())
+			throw std::runtime_error("extended field overflow");
+
+		return (p - startAt);
+	}
+
+	// Provides natural sort order by ID
+	inline bool operator<(const CertificateOfOwnership &coo) const { return (_id < coo._id); }
+
+	inline bool operator==(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) == 0); }
+	inline bool operator!=(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) != 0); }
+
+private:
+	uint64_t _networkId;
+	uint64_t _ts;
+	uint64_t _flags;
+	uint32_t _id;
+	uint16_t _thingCount;
+	uint8_t _thingTypes[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS];
+	uint8_t _thingValues[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS][ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE];
+	Address _issuedTo;
+	Address _signedBy;
+	C25519::Signature _signature;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 19 - 0
node/IncomingPacket.cpp

@@ -832,6 +832,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 		Capability cap;
 		Tag tag;
 		Revocation revocation;
+		CertificateOfOwnership coo;
 		bool trustEstablished = false;
 
 		unsigned int p = ZT_PACKET_IDX_PAYLOAD;
@@ -909,6 +910,24 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 					}
 				}
 			}
+
+			const unsigned int numCoos = at<uint16_t>(p); p += 2;
+			for(unsigned int i=0;i<numCoos;++i) {
+				p += coo.deserialize(*this,p);
+				const SharedPtr<Network> network(RR->node->network(coo.networkId()));
+				if (network) {
+					switch(network->addCredential(coo)) {
+						case Membership::ADD_REJECTED:
+							break;
+						case Membership::ADD_ACCEPTED_NEW:
+						case Membership::ADD_ACCEPTED_REDUNDANT:
+							trustEstablished = true;
+							break;
+						case Membership::ADD_DEFERRED_FOR_WHOIS:
+							return false;
+					}
+				}
+			}
 		}
 
 		peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished);

+ 128 - 36
node/Membership.cpp

@@ -37,6 +37,7 @@ Membership::Membership() :
 {
 	for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) _remoteTags[i] = &(_tagMem[i]);
 	for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) _remoteCaps[i] = &(_capMem[i]);
+	for(unsigned int i=0;i<ZT_MAX_CERTIFICATES_OF_OWNERSHIP;++i) _remoteCoos[i] = &(_cooMem[i]);
 }
 
 void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force)
@@ -62,8 +63,19 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now
 		}
 	}
 
+	const CertificateOfOwnership *sendCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
+	unsigned int sendCooCount = 0;
+	for(unsigned int c=0;c<nconf.certificateOfOwnershipCount;++c) {
+		if ( (_localCoos[c].id != nconf.certificatesOfOwnership[c].id()) || ((now - _localCoos[c].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
+			_localCoos[c].lastPushed = now;
+			_localCoos[c].id = nconf.certificatesOfOwnership[c].id();
+			sendCoos[sendCooCount++] = &(nconf.certificatesOfOwnership[c]);
+		}
+	}
+
 	unsigned int tagPtr = 0;
-	while ((tagPtr < sendTagCount)||(sendCom)||(sendCap)) {
+	unsigned int cooPtr = 0;
+	while ((tagPtr < sendTagCount)||(cooPtr < sendCooCount)||(sendCom)||(sendCap)) {
 		Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
 
 		if (sendCom) {
@@ -82,7 +94,7 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now
 		const unsigned int tagCountAt = outp.size();
 		outp.addSize(2);
 		unsigned int thisPacketTagCount = 0;
-		while ((tagPtr < sendTagCount)&&((outp.size() + sizeof(Tag) + 32) < ZT_PROTO_MAX_PACKET_LENGTH)) {
+		while ((tagPtr < sendTagCount)&&((outp.size() + sizeof(Tag) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
 			sendTags[tagPtr++]->serialize(outp);
 			++thisPacketTagCount;
 		}
@@ -91,6 +103,15 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now
 		// No revocations, these propagate differently
 		outp.append((uint16_t)0);
 
+		const unsigned int cooCountAt = outp.size();
+		outp.addSize(2);
+		unsigned int thisPacketCooCount = 0;
+		while ((cooPtr < sendCooCount)&&((outp.size() + sizeof(CertificateOfOwnership) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
+			sendCoos[cooPtr++]->serialize(outp);
+			++thisPacketCooCount;
+		}
+		outp.setAt(cooCountAt,(uint16_t)thisPacketCooCount);
+
 		outp.compress();
 		RR->sw->send(outp,true);
 	}
@@ -98,14 +119,14 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now
 
 const Capability *Membership::getCapability(const NetworkConfig &nconf,const uint32_t id) const
 {
-	const _RemoteCapability *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialSorter<_RemoteCapability>());
-	return ( ((c != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*c)->id == (uint64_t)id)) ? ((((*c)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*c)->cap,**c))) ? &((*c)->cap) : (const Capability *)0) : (const Capability *)0);
+	const _RemoteCredential<Capability> *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialComp<Capability>());
+	return ( ((c != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*c)->id == (uint64_t)id)) ? ((((*c)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*c)->credential,**c))) ? &((*c)->credential) : (const Capability *)0) : (const Capability *)0);
 }
 
 const Tag *Membership::getTag(const NetworkConfig &nconf,const uint32_t id) const
 {
-	const _RemoteTag *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialSorter<_RemoteTag>());
-	return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*t)->tag,**t))) ? &((*t)->tag) : (const Tag *)0) : (const Tag *)0);
+	const _RemoteCredential<Tag> *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialComp<Tag>());
+	return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*t)->credential,**t))) ? &((*t)->credential) : (const Tag *)0) : (const Tag *)0);
 }
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com)
@@ -141,14 +162,14 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Tag &tag)
 {
-	_RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialSorter<_RemoteTag>());
-	_RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteTag *)0;
+	_RemoteCredential<Tag> *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialComp<Tag>());
+	_RemoteCredential<Tag> *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteCredential<Tag> *)0;
 	if (have) {
-		if ( (!_isCredentialTimestampValid(nconf,tag,*have)) || (have->tag.timestamp() > tag.timestamp()) ) {
+		if ( (!_isCredentialTimestampValid(nconf,tag,*have)) || (have->credential.timestamp() > tag.timestamp()) ) {
 			TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId());
 			return ADD_REJECTED;
 		}
-		if (have->tag == tag) {
+		if (have->credential == tag) {
 			TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
 			return ADD_ACCEPTED_REDUNDANT;
 		}
@@ -162,7 +183,7 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 			TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
 			if (!have) have = _newTag(tag.id());
 			have->lastReceived = RR->node->now();
-			have->tag = tag;
+			have->credential = tag;
 			return ADD_ACCEPTED_NEW;
 		case 1:
 			return ADD_DEFERRED_FOR_WHOIS;
@@ -171,14 +192,14 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap)
 {
-	_RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialSorter<_RemoteCapability>());
-	_RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCapability *)0;
+	_RemoteCredential<Capability> *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialComp<Capability>());
+	_RemoteCredential<Capability> *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCredential<Capability> *)0;
 	if (have) {
-		if ( (!_isCredentialTimestampValid(nconf,cap,*have)) || (have->cap.timestamp() > cap.timestamp()) ) {
+		if ( (!_isCredentialTimestampValid(nconf,cap,*have)) || (have->credential.timestamp() > cap.timestamp()) ) {
 			TRACE("addCredential(Capability) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId());
 			return ADD_REJECTED;
 		}
-		if (have->cap == cap) {
+		if (have->credential == cap) {
 			TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId());
 			return ADD_ACCEPTED_REDUNDANT;
 		}
@@ -192,7 +213,7 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 			TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId());
 			if (!have) have = _newCapability(cap.id());
 			have->lastReceived = RR->node->now();
-			have->cap = cap;
+			have->credential = cap;
 			return ADD_ACCEPTED_NEW;
 		case 1:
 			return ADD_DEFERRED_FOR_WHOIS;
@@ -209,13 +230,15 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 			switch(rev.type()) {
 				default:
 				//case Revocation::CREDENTIAL_TYPE_ALL:
-					return ( (_revokeCom(rev)||_revokeCap(rev,now)||_revokeTag(rev,now)) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT );
+					return ( (_revokeCom(rev)||_revokeCap(rev,now)||_revokeTag(rev,now)||_revokeCoo(rev,now)) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT );
 				case Revocation::CREDENTIAL_TYPE_COM:
 					return (_revokeCom(rev) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
 				case Revocation::CREDENTIAL_TYPE_CAPABILITY:
 					return (_revokeCap(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
 				case Revocation::CREDENTIAL_TYPE_TAG:
 					return (_revokeTag(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+				case Revocation::CREDENTIAL_TYPE_COO:
+					return (_revokeCoo(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
 			}
 		}
 		case 1:
@@ -223,9 +246,40 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 	}
 }
 
-Membership::_RemoteTag *Membership::_newTag(const uint64_t id)
+
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfOwnership &coo)
+{
+	_RemoteCredential<CertificateOfOwnership> *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)coo.id(),_RemoteCredentialComp<CertificateOfOwnership>());
+	_RemoteCredential<CertificateOfOwnership> *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)coo.id())) ? *htmp : (_RemoteCredential<CertificateOfOwnership> *)0;
+	if (have) {
+		if ( (!_isCredentialTimestampValid(nconf,coo,*have)) || (have->credential.timestamp() > coo.timestamp()) ) {
+			TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId());
+			return ADD_REJECTED;
+		}
+		if (have->credential == coo) {
+			TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId());
+			return ADD_ACCEPTED_REDUNDANT;
+		}
+	}
+
+	switch(coo.verify(RR)) {
+		default:
+			TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx REJECTED (invalid)",cap.issuedTo().toString().c_str(),cap.networkId());
+			return ADD_REJECTED;
+		case 0:
+			TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId());
+			if (!have) have = _newCoo(coo.id());
+			have->lastReceived = RR->node->now();
+			have->credential = coo;
+			return ADD_ACCEPTED_NEW;
+		case 1:
+			return ADD_DEFERRED_FOR_WHOIS;
+	}
+}
+
+Membership::_RemoteCredential<Tag> *Membership::_newTag(const uint64_t id)
 {
-	_RemoteTag *t = NULL;
+	_RemoteCredential<Tag> *t = NULL;
 	uint64_t minlr = 0xffffffffffffffffULL;
 	for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
 		if (_remoteTags[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
@@ -236,21 +290,21 @@ Membership::_RemoteTag *Membership::_newTag(const uint64_t id)
 			minlr = _remoteTags[i]->lastReceived;
 		}
 	}
-	
-    if (t) {
-        t->id = id;
-        t->lastReceived = 0;
-        t->revocationThreshold = 0;
-        t->tag = Tag();
-    }
-
-	std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
+
+	if (t) {
+		t->id = id;
+		t->lastReceived = 0;
+		t->revocationThreshold = 0;
+		t->credential = Tag();
+	}
+
+	std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]));
 	return t;
 }
 
-Membership::_RemoteCapability *Membership::_newCapability(const uint64_t id)
+Membership::_RemoteCredential<Capability> *Membership::_newCapability(const uint64_t id)
 {
-	_RemoteCapability *c = NULL;
+	_RemoteCredential<Capability> *c = NULL;
 	uint64_t minlr = 0xffffffffffffffffULL;
 	for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) {
 		if (_remoteCaps[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
@@ -266,10 +320,35 @@ Membership::_RemoteCapability *Membership::_newCapability(const uint64_t id)
 		c->id = id;
 		c->lastReceived = 0;
 		c->revocationThreshold = 0;
-		c->cap = Capability();
+		c->credential = Capability();
+	}
+
+	std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]));
+	return c;
+}
+
+Membership::_RemoteCredential<CertificateOfOwnership> *Membership::_newCoo(const uint64_t id)
+{
+	_RemoteCredential<CertificateOfOwnership> *c = NULL;
+	uint64_t minlr = 0xffffffffffffffffULL;
+	for(unsigned int i=0;i<ZT_MAX_CERTIFICATES_OF_OWNERSHIP;++i) {
+		if (_remoteCoos[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
+			c = _remoteCoos[i];
+			break;
+		} else if (_remoteCoos[i]->lastReceived <= minlr) {
+			c = _remoteCoos[i];
+			minlr = _remoteCoos[i]->lastReceived;
+		}
+	}
+
+	if (c) {
+		c->id = id;
+		c->lastReceived = 0;
+		c->revocationThreshold = 0;
+		c->credential = CertificateOfOwnership();
 	}
 
-	std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
+	std::sort(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]));
 	return c;
 }
 
@@ -284,8 +363,8 @@ bool Membership::_revokeCom(const Revocation &rev)
 
 bool Membership::_revokeCap(const Revocation &rev,const uint64_t now)
 {
-	_RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteCapability>());
-	_RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCapability *)0;
+	_RemoteCredential<Capability> *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialComp<Capability>());
+	_RemoteCredential<Capability> *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential<Capability> *)0;
 	if (!have) have = _newCapability(rev.credentialId());
 	if (rev.threshold() > have->revocationThreshold) {
 		have->lastReceived = now;
@@ -297,8 +376,8 @@ bool Membership::_revokeCap(const Revocation &rev,const uint64_t now)
 
 bool Membership::_revokeTag(const Revocation &rev,const uint64_t now)
 {
-	_RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteTag>());
-	_RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteTag *)0;
+	_RemoteCredential<Tag> *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialComp<Tag>());
+	_RemoteCredential<Tag> *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential<Tag> *)0;
 	if (!have) have = _newTag(rev.credentialId());
 	if (rev.threshold() > have->revocationThreshold) {
 		have->lastReceived = now;
@@ -308,4 +387,17 @@ bool Membership::_revokeTag(const Revocation &rev,const uint64_t now)
 	return false;
 }
 
+bool Membership::_revokeCoo(const Revocation &rev,const uint64_t now)
+{
+	_RemoteCredential<CertificateOfOwnership> *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)rev.credentialId(),_RemoteCredentialComp<CertificateOfOwnership>());
+	_RemoteCredential<CertificateOfOwnership> *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential<CertificateOfOwnership> *)0;
+	if (!have) have = _newCoo(rev.credentialId());
+	if (rev.threshold() > have->revocationThreshold) {
+		have->lastReceived = now;
+		have->revocationThreshold = rev.threshold();
+		return true;
+	}
+	return false;
+}
+
 } // namespace ZeroTier

+ 36 - 45
node/Membership.hpp

@@ -39,49 +39,30 @@ class Network;
 /**
  * A container for certificates of membership and other network credentials
  *
- * This is kind of analogous to a join table between Peer and Network. It is
- * held by the Network object for each participating Peer.
+ * This is essentially a relational join between Peer and Network.
  *
  * This class is not thread safe. It must be locked externally.
  */
 class Membership
 {
 private:
-	// Tags and related state
-	struct _RemoteTag
-	{
-		_RemoteTag() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
-		// Tag ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
-		uint64_t id;
-		// Last time we received THEIR tag (with this ID)
-		uint64_t lastReceived;
-		// Revocation blacklist threshold or 0 if none
-		uint64_t revocationThreshold;
-		// THEIR tag
-		Tag tag;
-	};
-
-	// Credentials and related state
-	struct _RemoteCapability
+	template<typename T>
+	struct _RemoteCredential
 	{
-		_RemoteCapability() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
-		// Capability ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
+		_RemoteCredential() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
 		uint64_t id;
-		// Last time we received THEIR capability (with this ID)
-		uint64_t lastReceived;
-		// Revocation blacklist threshold or 0 if none
-		uint64_t revocationThreshold;
-		// THEIR capability
-		Capability cap;
+		uint64_t lastReceived; // last time we got this credential
+		uint64_t revocationThreshold; // credentials before this time are invalid
+		T credential;
+		inline bool operator<(const _RemoteCredential &c) const { return (id < c.id); }
 	};
 
-	// Comparison operator for remote credential entries
 	template<typename T>
-	struct _RemoteCredentialSorter
+	struct _RemoteCredentialComp
 	{
-		inline bool operator()(const T *a,const T *b) const { return (a->id < b->id); }
-		inline bool operator()(const uint64_t a,const T *b) const { return (a < b->id); }
-		inline bool operator()(const T *a,const uint64_t b) const { return (a->id < b); }
+		inline bool operator()(const _RemoteCredential<T> *a,const _RemoteCredential<T> *b) const { return (a->id < b->id); }
+		inline bool operator()(const uint64_t a,const _RemoteCredential<T> *b) const { return (a < b->id); }
+		inline bool operator()(const _RemoteCredential<T> *a,const uint64_t b) const { return (a->id < b); }
 		inline bool operator()(const uint64_t a,const uint64_t b) const { return (a < b); }
 	};
 
@@ -89,8 +70,8 @@ private:
 	struct _LocalCredentialPushState
 	{
 		_LocalCredentialPushState() : lastPushed(0),id(0) {}
-		uint64_t lastPushed;
-		uint32_t id;
+		uint64_t lastPushed; // last time we sent our own copy of this credential
+		uint64_t id;
 	};
 
 public:
@@ -117,7 +98,7 @@ public:
 		{
 			for(;;) {
 				if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
-					const Capability *tmp = &((*_i)->cap);
+					const Capability *tmp = &((*_i)->credential);
 					if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
 						++_i;
 						return tmp;
@@ -131,7 +112,7 @@ public:
 	private:
 		const Membership *_m;
 		const NetworkConfig *_c;
-		const _RemoteCapability *const *_i;
+		const _RemoteCredential<Capability> *const *_i;
 	};
 	friend class CapabilityIterator;
 
@@ -150,7 +131,7 @@ public:
 		{
 			for(;;) {
 				if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
-					const Tag *tmp = &((*_i)->tag);
+					const Tag *tmp = &((*_i)->credential);
 					if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
 						++_i;
 						return tmp;
@@ -164,7 +145,7 @@ public:
 	private:
 		const Membership *_m;
 		const NetworkConfig *_c;
-		const _RemoteTag *const *_i;
+		const _RemoteCredential<Tag> *const *_i;
 	};
 	friend class TagIterator;
 
@@ -249,12 +230,19 @@ public:
 	 */
 	AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Revocation &rev);
 
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfOwnership &coo);
+
 private:
-	_RemoteTag *_newTag(const uint64_t id);
-	_RemoteCapability *_newCapability(const uint64_t id);
+	_RemoteCredential<Tag> *_newTag(const uint64_t id);
+	_RemoteCredential<Capability> *_newCapability(const uint64_t id);
+	_RemoteCredential<CertificateOfOwnership> *_newCoo(const uint64_t id);
 	bool _revokeCom(const Revocation &rev);
 	bool _revokeCap(const Revocation &rev,const uint64_t now);
 	bool _revokeTag(const Revocation &rev,const uint64_t now);
+	bool _revokeCoo(const Revocation &rev,const uint64_t now);
 
 	template<typename C,typename CS>
 	inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred,const CS &state) const
@@ -275,17 +263,20 @@ private:
 	// Remote member's latest network COM
 	CertificateOfMembership _com;
 
-	// Sorted (in ascending order of ID) arrays of pointers to remote tags and capabilities
-	_RemoteTag *_remoteTags[ZT_MAX_NETWORK_TAGS];
-	_RemoteCapability *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES];
+	// Sorted (in ascending order of ID) arrays of pointers to remote credentials
+	_RemoteCredential<Tag> *_remoteTags[ZT_MAX_NETWORK_TAGS];
+	_RemoteCredential<Capability> *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES];
+	_RemoteCredential<CertificateOfOwnership> *_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
 
-	// This is the RAM allocated for remote tags and capabilities from which the sorted arrays are populated
-	_RemoteTag _tagMem[ZT_MAX_NETWORK_TAGS];
-	_RemoteCapability _capMem[ZT_MAX_NETWORK_CAPABILITIES];
+	// This is the RAM allocated for remote credential cache objects
+	_RemoteCredential<Tag> _tagMem[ZT_MAX_NETWORK_TAGS];
+	_RemoteCredential<Capability> _capMem[ZT_MAX_NETWORK_CAPABILITIES];
+	_RemoteCredential<CertificateOfOwnership> _cooMem[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
 
 	// Local credential push state tracking
 	_LocalCredentialPushState _localTags[ZT_MAX_NETWORK_TAGS];
 	_LocalCredentialPushState _localCaps[ZT_MAX_NETWORK_CAPABILITIES];
+	_LocalCredentialPushState _localCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
 };
 
 } // namespace ZeroTier

+ 11 - 0
node/Network.hpp

@@ -301,6 +301,17 @@ public:
 	 */
 	Membership::AddCredentialResult addCredential(const Address &sentFrom,const Revocation &rev);
 
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	inline Membership::AddCredentialResult addCredential(const CertificateOfOwnership &coo)
+	{
+		if (coo.networkId() != _id)
+			return Membership::ADD_REJECTED;
+		Mutex::Lock _l(_lock);
+		return _membership(coo.issuedTo()).addCredential(RR,_config,coo);
+	}
+
 	/**
 	 * Force push credentials (COM, etc.) to a peer now
 	 *

+ 22 - 3
node/NetworkConfig.cpp

@@ -21,7 +21,6 @@
 #include <algorithm>
 
 #include "NetworkConfig.hpp"
-#include "Utils.hpp"
 
 namespace ZeroTier {
 
@@ -137,6 +136,13 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
 			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) return false;
 		}
 
+		tmp->clear();
+		for(unsigned int i=0;i<this->certificateOfOwnershipCount;++i)
+			this->certificatesOfOwnership[i].serialize(*tmp);
+		if (tmp->size()) {
+			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) return false;
+		}
+
 		tmp->clear();
 		for(unsigned int i=0;i<this->specialistCount;++i)
 			tmp->append((uint64_t)this->specialists[i]);
@@ -297,10 +303,23 @@ bool NetworkConfig::fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACI
 				std::sort(&(this->tags[0]),&(this->tags[this->tagCount]));
 			}
 
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) {
+				unsigned int p = 0;
+				while (p < tmp->size()) {
+					if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP)
+						p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp,p);
+					else {
+						CertificateOfOwnership foo;
+						p += foo.deserialize(*tmp,p);
+					}
+				}
+			}
+
 			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) {
 				unsigned int p = 0;
-				while (((p + 8) <= tmp->size())&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) {
-					this->specialists[this->specialistCount++] = tmp->at<uint64_t>(p);
+				while ((p + 8) <= tmp->size()) {
+					if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS)
+						this->specialists[this->specialistCount++] = tmp->at<uint64_t>(p);
 					p += 8;
 				}
 			}

+ 15 - 6
node/NetworkConfig.hpp

@@ -35,10 +35,12 @@
 #include "MulticastGroup.hpp"
 #include "Address.hpp"
 #include "CertificateOfMembership.hpp"
+#include "CertificateOfOwnership.hpp"
 #include "Capability.hpp"
 #include "Tag.hpp"
 #include "Dictionary.hpp"
 #include "Identity.hpp"
+#include "Utils.hpp"
 
 /**
  * Default maximum time delta for COMs, tags, and capabilities
@@ -99,7 +101,7 @@
 namespace ZeroTier {
 
 // Dictionary capacity needed for max size network config
-#define ZT_NETWORKCONFIG_DICT_CAPACITY (4096 + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS))
+#define ZT_NETWORKCONFIG_DICT_CAPACITY (1024 + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS) + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP))
 
 // Dictionary capacity needed for max size network meta-data
 #define ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY 1024
@@ -173,6 +175,8 @@ namespace ZeroTier {
 #define ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES "CAP"
 // tags (binary blobs)
 #define ZT_NETWORKCONFIG_DICT_KEY_TAGS "TAG"
+// tags (binary blobs)
+#define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP "COO"
 // curve25519 signature
 #define ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE "C25519"
 
@@ -473,11 +477,6 @@ public:
 	 */
 	unsigned int staticIpCount;
 
-	/**
-	 * Number of pinned devices (devices with physical address hints)
-	 */
-	unsigned int pinnedCount;
-
 	/**
 	 * Number of rule table entries
 	 */
@@ -493,6 +492,11 @@ public:
 	 */
 	unsigned int tagCount;
 
+	/**
+	 * Number of certificates of ownership
+	 */
+	unsigned int certificateOfOwnershipCount;
+
 	/**
 	 * Specialist devices
 	 *
@@ -526,6 +530,11 @@ public:
 	 */
 	Tag tags[ZT_MAX_NETWORK_TAGS];
 
+	/**
+	 * Certificates of ownership for this network member
+	 */
+	CertificateOfOwnership certificatesOfOwnership[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
+
 	/**
 	 * Network type (currently just public or private)
 	 */

+ 2 - 0
node/Packet.hpp

@@ -730,6 +730,8 @@ public:
 		 *   <[...] one or more serialized Tags>
 		 *   <[2] 16-bit number of revocations>
 		 *   <[...] one or more serialized Revocations>
+		 *   <[2] 16-bit number of certificates of ownership>
+		 *   <[...] one or more serialized CertificateOfOwnership>
 		 *
 		 * This can be sent by anyone at any time to push network credentials.
 		 * These will of course only be accepted if they are properly signed.

+ 3 - 2
node/Revocation.hpp

@@ -50,9 +50,10 @@ public:
 	enum CredentialType
 	{
 		CREDENTIAL_TYPE_ALL = 0,
-		CREDENTIAL_TYPE_COM = 1,
+		CREDENTIAL_TYPE_COM = 1, // CertificateOfMembership
 		CREDENTIAL_TYPE_CAPABILITY = 2,
-		CREDENTIAL_TYPE_TAG = 3
+		CREDENTIAL_TYPE_TAG = 3,
+		CREDENTIAL_TYPE_COO = 4 // CertificateOfOwnership
 	};
 
 	Revocation()

+ 2 - 1
node/Tag.hpp

@@ -139,7 +139,8 @@ public:
 	{
 		unsigned int p = startAt;
 
-		// These are the same between Tag and Capability
+		memset(this,0,sizeof(Tag));
+
 		_networkId = b.template at<uint64_t>(p); p += 8;
 		_ts = b.template at<uint64_t>(p); p += 8;
 		_id = b.template at<uint32_t>(p); p += 4;

+ 1 - 0
objects.mk

@@ -4,6 +4,7 @@ OBJS=\
 	node/C25519.o \
 	node/Capability.o \
 	node/CertificateOfMembership.o \
+	node/CertificateOfOwnership.o \
 	node/Cluster.o \
 	node/Identity.o \
 	node/IncomingPacket.o \