diff options
-rw-r--r-- | node/Address.hpp | 9 | ||||
-rw-r--r-- | node/Identity.hpp | 13 | ||||
-rw-r--r-- | node/Network.cpp | 184 | ||||
-rw-r--r-- | node/Network.hpp | 132 | ||||
-rw-r--r-- | node/Utils.cpp | 31 | ||||
-rw-r--r-- | node/Utils.hpp | 10 |
6 files changed, 312 insertions, 67 deletions
diff --git a/node/Address.hpp b/node/Address.hpp index 034bc144..91e971d4 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -232,6 +232,15 @@ public: inline operator bool() const throw() { return (_a != 0); } /** + * Set to null/zero + */ + inline void zero() + throw() + { + _a = 0; + } + + /** * Check if this address is reserved * * The all-zero null address and any address beginning with 0xff are diff --git a/node/Identity.hpp b/node/Identity.hpp index eb8b19a4..b2a57941 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -181,6 +181,19 @@ public: } /** + * Verify a message signature against this identity + * + * @param data Data to check + * @param len Length of data + * @param signature Signature + * @return True if signature validates and data integrity checks + */ + inline bool verify(const void *data,unsigned int len,const C25519::Signature &signature) const + { + return C25519::verify(_publicKey,data,len,signature); + } + + /** * Shortcut method to perform key agreement with another identity * * This identity must have a private key. (Check hasPrivate()) diff --git a/node/Network.cpp b/node/Network.cpp index 82fa6398..cfc1204a 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -30,6 +30,9 @@ #include <stdlib.h> #include <math.h> +#include <algorithm> +#include <utility> + #include "Constants.hpp" #include "RuntimeEnvironment.hpp" #include "NodeConfig.hpp" @@ -40,73 +43,135 @@ namespace ZeroTier { -void Network::CertificateOfMembership::addParameter(uint64_t id,uint64_t value,uint64_t maxDelta) +void Network::CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta) { - _params.push_back(_Parameter(id,value,maxDelta)); - std::sort(_params.begin(),_params.end(),_SortByIdComparison()); + _signedBy.zero(); + + for(std::vector<_Qualifier>::iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + if (q->id == id) { + q->value = value; + q->maxDelta = maxDelta; + return; + } + } + + _qualifiers.push_back(_Qualifier(id,value,maxDelta)); + std::sort(_qualifiers.begin(),_qualifiers.end()); } std::string Network::CertificateOfMembership::toString() const { - uint64_t tmp[3000]; - unsigned long n = 0; - for(std::vector<_Parameter>::const_iterator p(_params.begin());p!=_params.end();++p) { - tmp[n++] = Utils::hton(p->id); - tmp[n++] = Utils::hton(p->value); - tmp[n++] = Utils::hton(p->maxDelta); - if (n >= 3000) - break; // sanity check -- certificates will never even approach this size + std::string s; + + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + try { + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + s.append(Utils::hex(buf,ptr * sizeof(uint64_t))); + delete [] buf; + } catch ( ... ) { + delete [] buf; + throw; + } + + s.push_back(':'); + + s.append(_signedBy.toString()); + + if (_signedBy) { + s.push_back(':'); + s.append(Utils::hex(_signature.data,_signature.size())); } - return Utils::hex(tmp,sizeof(uint64_t) * n); + + return s; } void Network::CertificateOfMembership::fromString(const char *s) { - std::string tmp(Utils::unhex(s)); - _params.clear(); - const char *ptr = tmp.data(); - unsigned long remaining = tmp.length(); - while (remaining >= 24) { - _Parameter p; - p.id = Utils::ntoh(*((const uint64_t *)(ptr))); - p.value = Utils::ntoh(*((const uint64_t *)(ptr + 8))); - p.maxDelta = Utils::ntoh(*((const uint64_t *)(ptr + 16))); - _params.push_back(p); - ptr += 24; - remaining -= 24; + _qualifiers.clear(); + _signedBy.zero(); + + unsigned int colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + unsigned int buflen = colonAt / 2; + char *buf = new char[buflen]; + unsigned int bufactual = Utils::unhex(s,colonAt,buf,buflen); + char *bufptr = buf; + try { + while (bufactual >= 24) { + _qualifiers.push_back(_Qualifier()); + _qualifiers.back().id = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers.back().value = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers.back().maxDelta = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + bufactual -= 24; + } + } catch ( ... ) {} + delete [] buf; } + + if (s[colonAt]) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + char addrbuf[ZT_ADDRESS_LENGTH]; + if (Utils::unhex(s,colonAt,addrbuf,sizeof(addrbuf)) == ZT_ADDRESS_LENGTH) + _signedBy.setTo(addrbuf,ZT_ADDRESS_LENGTH); + + if ((_signedBy)&&(s[colonAt])) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + if (Utils::unhex(s,colonAt,_signature.data,_signature.size()) != _signature.size()) + _signedBy.zero(); + } else _signedBy.zero(); + } else _signedBy.zero(); + } + } + + std::sort(_qualifiers.begin(),_qualifiers.end()); + std::unique(_qualifiers.begin(),_qualifiers.end()); } -bool Network::CertificateOfMembership::compare(const CertificateOfMembership &other) const +bool Network::CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const throw() { unsigned long myidx = 0; unsigned long otheridx = 0; - while (myidx < _params.size()) { + while (myidx < _qualifiers.size()) { // Fail if we're at the end of other, since this means the field is // missing. - if (otheridx >= other._params.size()) + if (otheridx >= other._qualifiers.size()) return false; // Seek to corresponding tuple in other, ignoring tuples that // we may not have. If we run off the end of other, the tuple is // missing. This works because tuples are sorted by ID. - while (other._params[otheridx].id != _params[myidx].id) { + while (other._qualifiers[otheridx].id != _qualifiers[myidx].id) { ++otheridx; - if (otheridx >= other._params.size()) + if (otheridx >= other._qualifiers.size()) return false; } // Compare to determine if the absolute value of the difference // between these two parameters is within our maxDelta. - uint64_t a = _params[myidx].value; - uint64_t b = other._params[myidx].value; + uint64_t a = _qualifiers[myidx].value; + uint64_t b = other._qualifiers[myidx].value; if (a >= b) { - if ((a - b) > _params[myidx].maxDelta) + if ((a - b) > _qualifiers[myidx].maxDelta) return false; } else { - if ((b - a) > _params[myidx].maxDelta) + if ((b - a) > _qualifiers[myidx].maxDelta) return false; } @@ -116,6 +181,55 @@ bool Network::CertificateOfMembership::compare(const CertificateOfMembership &ot return true; } +bool Network::CertificateOfMembership::sign(const Identity &with) +{ + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + + try { + _signature = with.sign(buf,ptr * sizeof(uint64_t)); + _signedBy = with.address(); + delete [] buf; + return true; + } catch ( ... ) { + _signedBy.zero(); + delete [] buf; + return false; + } +} + +bool Network::CertificateOfMembership::verify(const Identity &id) const +{ + if (!_signedBy) + return false; + if (id.address() != _signedBy) + return false; + + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + + bool valid = false; + try { + valid = id.verify(buf,ptr * sizeof(uint64_t),_signature); + delete [] buf; + } catch ( ... ) { + delete [] buf; + } + return valid; +} + +// --------------------------------------------------------------------------- + const Network::MulticastRates::Rate Network::MulticastRates::GLOBAL_DEFAULT_RATE(65535,65535,64); const char *Network::statusString(const Status s) @@ -253,7 +367,7 @@ bool Network::isAllowed(const Address &peer) const std::map<Address,CertificateOfMembership>::const_iterator pc(_membershipCertificates.find(peer)); if (pc == _membershipCertificates.end()) return false; - return _myCertificate.compare(pc->second); + return _myCertificate.agreesWith(pc->second); } catch (std::exception &exc) { TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what()); } catch ( ... ) { @@ -282,7 +396,7 @@ void Network::clean() } for(std::map<Address,CertificateOfMembership>::iterator i=(_membershipCertificates.begin());i!=_membershipCertificates.end();) { - if (_myCertificate.compare(i->second)) { + if (_myCertificate.agreesWith(i->second)) { if ((!writeError)&&(mcdb)) { char tmp[ZT_ADDRESS_LENGTH]; i->first.copyTo(tmp,ZT_ADDRESS_LENGTH); diff --git a/node/Network.hpp b/node/Network.hpp index cccae17e..e8d8a51e 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -51,6 +51,7 @@ #include "Identity.hpp" #include "InetAddress.hpp" #include "BandwidthAccount.hpp" +#include "C25519.hpp" namespace ZeroTier { @@ -86,37 +87,71 @@ public: /** * Certificate of network membership * - * The COM consists of a series of three-element 64-bit tuples. These values - * are an id, a value, and a maximum delta. The ID is arbitrary and should be - * assigned using a scheme that makes every ID globally unique for a given - * type of parameter. ID 0 is reserved for the always-present timestamp - * parameter. The value is parameter-specific. The maximum delta is the - * maximum difference that is permitted between two values for determining - * whether a certificate permits two peers to speak to one another. A value - * of zero indicates that the values must equal. + * The COM contains a sorted set of three-element tuples called qualifiers. + * These contain an id, a value, and a maximum delta. * - * Certificates of membership must be signed by the netconf master for the - * network in question. This permits members to verify these certs against - * the netconf master's public key before testing them. + * The ID is arbitrary and should be assigned using a scheme that makes + * every ID globally unique. ID 0 is reserved for the always-present + * validity timestamp and range, and ID 1 is reserved for the always-present + * network ID. IDs less than 65536 are reserved for future global + * assignment. + * + * The value's meaning is ID-specific and isn't important here. What's + * important is the value and the third member of the tuple: the maximum + * delta. The maximum delta is the maximum difference permitted between + * values for a given ID between certificates for the two certificates to + * themselves agree. + * + * Network membership is checked by checking whether a peer's certificate + * agrees with your own. The timestamp provides the fundamental criterion-- + * each member of a private network must constantly obtain new certificates + * often enough to stay within the max delta for this qualifier. But other + * criteria could be added in the future for very special behaviors, things + * like latitude and longitude for instance. */ class CertificateOfMembership { public: - CertificateOfMembership() throw() {} + /** + * Certificate type codes, used in serialization + * + * Only one so far, and only one hopefully there shall be for quite some + * time. + */ + enum Type + { + COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519 + }; + + /** + * Reserved COM IDs + * + * IDs below 65536 should be considered reserved for future global + * assignment here. + */ + enum ReservedIds + { + COM_RESERVED_ID_TIMESTAMP = 0, // timestamp, max delta defines cert life + COM_RESERVED_ID_NETWORK_ID = 1 // network ID, max delta always 0 + }; + + CertificateOfMembership() {} CertificateOfMembership(const char *s) { fromString(s); } CertificateOfMembership(const std::string &s) { fromString(s.c_str()); } /** - * Add a paramter to this certificate + * Add or update a qualifier in this certificate + * + * Any signature is invalidated and signedBy is set to null. * - * @param id Parameter ID - * @param value Parameter value - * @param maxDelta Parameter maximum difference with others + * @param id Qualifier ID + * @param value Qualifier value + * @param maxDelta Qualifier maximum allowed difference (absolute value of difference) */ - void addParameter(uint64_t id,uint64_t value,uint64_t maxDelta); + void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta); /** - * @return Hex-serialized representation of this certificate (minus signature) + * @return String-serialized representation of this certificate */ std::string toString() const; @@ -138,36 +173,69 @@ public: * paramters in this cert are present in the other and if they agree to * within this cert's max delta value for each given parameter. * + * Tuples present in other but not in this cert are ignored, but any + * tuples present in this cert but not in other result in 'false'. + * * @param other Cert to compare with * @return True if certs agree and 'other' may be communicated with */ - bool compare(const CertificateOfMembership &other) const + bool agreesWith(const CertificateOfMembership &other) const throw(); + /** + * Sign this certificate + * + * @param with Identity to sign with, must include private key + * @return True if signature was successful + */ + bool sign(const Identity &with); + + /** + * Verify certificate against an identity + * + * @param id Identity to verify against + * @return True if certificate is signed by this identity and verification was successful + */ + bool verify(const Identity &id) const; + + /** + * @return True if signed + */ + inline bool isSigned() const + throw() + { + return (_signedBy); + } + + /** + * @return Address that signed this certificate or null address if none + */ + inline const Address &signedBy() const + throw() + { + return _signedBy; + } + private: - struct _Parameter + struct _Qualifier { - _Parameter() throw() {} - _Parameter(uint64_t i,uint64_t v,uint64_t m) throw() : + _Qualifier() throw() {} + _Qualifier(uint64_t i,uint64_t v,uint64_t m) throw() : id(i), value(v), maxDelta(m) {} + uint64_t id; uint64_t value; uint64_t maxDelta; - }; - // Used with std::sort to ensure that _params are sorted - struct _SortByIdComparison - { - inline bool operator()(const _Parameter &a,const _Parameter &b) const - throw() - { - return (a.id < b.id); - } + inline bool operator==(const _Qualifier &q) const throw() { return (id == q.id); } // for unique + inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort }; - std::vector<_Parameter> _params; + std::vector<_Qualifier> _qualifiers; // sorted by id and unique + Address _signedBy; + C25519::Signature _signature; }; /** diff --git a/node/Utils.cpp b/node/Utils.cpp index 31174ecc..cf5519cb 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -283,6 +283,37 @@ unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len) return l; } +unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len) + throw() +{ + int n = 1; + unsigned char c,b = 0; + unsigned int l = 0; + const char *const end = hex + hexlen; + + while (hex != end) { + c = (unsigned char)*(hex++); + if ((c >= 48)&&(c <= 57)) { // 0..9 + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - 48)); + } else b = (c - 48) << 4; + } else if ((c >= 65)&&(c <= 70)) { // A..F + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (65 - 10))); + } else b = (c - (65 - 10)) << 4; + } else if ((c >= 97)&&(c <= 102)) { // a..f + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (97 - 10))); + } else b = (c - (97 - 10)) << 4; + } + } + + return l; +} + void Utils::getSecureRandom(void *buf,unsigned int bytes) { static Mutex randomLock; diff --git a/node/Utils.hpp b/node/Utils.hpp index 09e81ee6..8e9ab8d3 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -149,6 +149,16 @@ public: static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),buf,len); } /** + * @param hex Hexadecimal ASCII + * @param hexlen Length of hex ASCII + * @param buf Buffer to fill + * @param len Length of buffer + * @return Number of bytes actually written to buffer + */ + static unsigned int unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len) + throw(); + + /** * @param buf Buffer to fill * @param bytes Number of random bytes to generate */ |