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

This commit is contained in:
Adam Ierymenko 2017-02-23 11:47:36 -08:00
parent 33b94e8478
commit 10185e92fa
15 changed files with 553 additions and 97 deletions

View File

@ -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;

View File

@ -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)
*/

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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)
{
_RemoteTag *t = NULL;
_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)
{
_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]),_RemoteCredentialSorter<_RemoteCapability>());
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(&(_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

View File

@ -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<typename T>
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<typename T>
struct _RemoteCredentialComp
{
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

View File

@ -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
*

View File

@ -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;
}
}

View File

@ -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)
*/

View File

@ -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.

View File

@ -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()

View File

@ -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;

View File

@ -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 \