diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index ca548fd4e..78a9b7c7f 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/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(p); p += 8; _ts = b.template at(p); p += 8; _id = b.template at(p); p += 4; diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp new file mode 100644 index 000000000..8305c489f --- /dev/null +++ b/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 . + */ + +#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 diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp new file mode 100644 index 000000000..69b26aec3 --- /dev/null +++ b/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 . + */ + +#ifndef ZT_CERTIFICATEOFOWNERSHIP_HPP +#define ZT_CERTIFICATEOFOWNERSHIP_HPP + +#include +#include +#include +#include + +#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(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(&ip)->sin_addr.s_addr),4); + if (ip.ss_family == AF_INET6) + return this->owns(THING_IPV6_ADDRESS,reinterpret_cast(&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(&ip)->sin_addr.s_addr),4); + ++_thingCount; + } else if (ip.ss_family == AF_INET6) { + _thingTypes[_thingCount] = THING_IPV6_ADDRESS; + memcpy(_thingValues[_thingCount],reinterpret_cast(&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 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 + inline void serialize(Buffer &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 + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + memset(this,0,sizeof(CertificateOfOwnership)); + + _networkId = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _flags = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + _thingCount = b.template at(p); p += 2; + for(unsigned int i=0,j=_thingCount;i(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(p); + } + + p += 2 + b.template at(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 diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index b077f7e21..b5b2bcb39 100644 --- a/node/IncomingPacket.cpp +++ b/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(p); p += 2; + for(unsigned int i=0;i 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); diff --git a/node/Membership.cpp b/node/Membership.cpp index 8c6dab649..1eacb93d1 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -37,6 +37,7 @@ Membership::Membership() : { for(unsigned int i=0;i= 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 *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialComp()); + 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 *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialComp()); + 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 *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteCredential *)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 *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCredential *)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) { - _RemoteTag *t = NULL; + _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)coo.id(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)coo.id())) ? *htmp : (_RemoteCredential *)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 *Membership::_newTag(const uint64_t id) +{ + _RemoteCredential *t = NULL; uint64_t minlr = 0xffffffffffffffffULL; for(unsigned int i=0;iid == 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 *Membership::_newCapability(const uint64_t id) { - _RemoteCapability *c = NULL; + _RemoteCredential *c = NULL; uint64_t minlr = 0xffffffffffffffffULL; for(unsigned int i=0;iid == 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]),_RemoteCredentialSorter<_RemoteCapability>()); + std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES])); + return c; +} + +Membership::_RemoteCredential *Membership::_newCoo(const uint64_t id) +{ + _RemoteCredential *c = NULL; + uint64_t minlr = 0xffffffffffffffffULL; + for(unsigned int i=0;iid == 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(&(_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 *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)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 *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)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 *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)rev.credentialId(),_RemoteCredentialComp()); + _RemoteCredential *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCredential *)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 diff --git a/node/Membership.hpp b/node/Membership.hpp index 9814dce8e..4e9d77692 100644 --- a/node/Membership.hpp +++ b/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 - { - _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) - 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; - }; - - // Comparison operator for remote credential entries template - struct _RemoteCredentialSorter + struct _RemoteCredential { - 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); } + _RemoteCredential() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {} + uint64_t id; + 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); } + }; + + template + struct _RemoteCredentialComp + { + inline bool operator()(const _RemoteCredential *a,const _RemoteCredential *b) const { return (a->id < b->id); } + inline bool operator()(const uint64_t a,const _RemoteCredential *b) const { return (a < b->id); } + inline bool operator()(const _RemoteCredential *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 *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 *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 *_newTag(const uint64_t id); + _RemoteCredential *_newCapability(const uint64_t id); + _RemoteCredential *_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 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 *_remoteTags[ZT_MAX_NETWORK_TAGS]; + _RemoteCredential *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]; + _RemoteCredential *_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 _tagMem[ZT_MAX_NETWORK_TAGS]; + _RemoteCredential _capMem[ZT_MAX_NETWORK_CAPABILITIES]; + _RemoteCredential _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 diff --git a/node/Network.hpp b/node/Network.hpp index 85ee6e9aa..56c7fc60c 100644 --- a/node/Network.hpp +++ b/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 * diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 2f356b15d..fe7393e80 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -21,7 +21,6 @@ #include #include "NetworkConfig.hpp" -#include "Utils.hpp" namespace ZeroTier { @@ -137,6 +136,13 @@ bool NetworkConfig::toDictionary(Dictionary &d,b if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) return false; } + tmp->clear(); + for(unsigned int i=0;icertificateOfOwnershipCount;++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;ispecialistCount;++i) tmp->append((uint64_t)this->specialists[i]); @@ -297,10 +303,23 @@ bool NetworkConfig::fromDictionary(const Dictionarytags[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(p); + while ((p + 8) <= tmp->size()) { + if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) + this->specialists[this->specialistCount++] = tmp->at(p); p += 8; } } diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 390873952..85c240901 100644 --- a/node/NetworkConfig.hpp +++ b/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) */ diff --git a/node/Packet.hpp b/node/Packet.hpp index b736b84af..6482356a4 100644 --- a/node/Packet.hpp +++ b/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. diff --git a/node/Revocation.hpp b/node/Revocation.hpp index bc290e758..3903f440c 100644 --- a/node/Revocation.hpp +++ b/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() diff --git a/node/Tag.hpp b/node/Tag.hpp index 653482004..146e8da99 100644 --- a/node/Tag.hpp +++ b/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(p); p += 8; _ts = b.template at(p); p += 8; _id = b.template at(p); p += 4; diff --git a/objects.mk b/objects.mk index 31498b72d..427024ebd 100644 --- a/objects.mk +++ b/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 \