From b4ae1adfbffecc090357b4e9e5c04ec3b2d3280d Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 7 Oct 2013 15:29:03 -0400 Subject: Break out certificate of membership into its own class. --- node/CertificateOfMembership.cpp | 220 +++++++++++++++++++++++++++++++++++++++ node/CertificateOfMembership.hpp | 192 ++++++++++++++++++++++++++++++++++ node/Network.cpp | 188 --------------------------------- node/Network.hpp | 148 +------------------------- objects.mk | 1 + 5 files changed, 414 insertions(+), 335 deletions(-) create mode 100644 node/CertificateOfMembership.cpp create mode 100644 node/CertificateOfMembership.hpp diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp new file mode 100644 index 00000000..0944851a --- /dev/null +++ b/node/CertificateOfMembership.cpp @@ -0,0 +1,220 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include + +#include "CertificateOfMembership.hpp" + +namespace ZeroTier { + +void CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta) +{ + _signedBy.zero(); + + for(std::vector<_Qualifier>::iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + if (q->id == id) { + q->value = value; + q->maxDelta = maxDelta; + return; + } + } + + _qualifiers.push_back(_Qualifier(id,value,maxDelta)); + std::sort(_qualifiers.begin(),_qualifiers.end()); +} + +std::string CertificateOfMembership::toString() const +{ + std::string s; + + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + try { + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + s.append(Utils::hex(buf,ptr * sizeof(uint64_t))); + delete [] buf; + } catch ( ... ) { + delete [] buf; + throw; + } + + s.push_back(':'); + + s.append(_signedBy.toString()); + + if (_signedBy) { + s.push_back(':'); + s.append(Utils::hex(_signature.data,_signature.size())); + } + + return s; +} + +void CertificateOfMembership::fromString(const char *s) +{ + _qualifiers.clear(); + _signedBy.zero(); + memset(_signature.data,0,_signature.size()); + + unsigned int colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + unsigned int buflen = colonAt / 2; + char *buf = new char[buflen]; + unsigned int bufactual = Utils::unhex(s,colonAt,buf,buflen); + char *bufptr = buf; + try { + while (bufactual >= 24) { + _qualifiers.push_back(_Qualifier()); + _qualifiers.back().id = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers.back().value = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers.back().maxDelta = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + bufactual -= 24; + } + } catch ( ... ) {} + delete [] buf; + } + + if (s[colonAt]) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + char addrbuf[ZT_ADDRESS_LENGTH]; + if (Utils::unhex(s,colonAt,addrbuf,sizeof(addrbuf)) == ZT_ADDRESS_LENGTH) + _signedBy.setTo(addrbuf,ZT_ADDRESS_LENGTH); + + if ((_signedBy)&&(s[colonAt])) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + if (Utils::unhex(s,colonAt,_signature.data,_signature.size()) != _signature.size()) + _signedBy.zero(); + } else _signedBy.zero(); + } else _signedBy.zero(); + } + } + + std::sort(_qualifiers.begin(),_qualifiers.end()); + std::unique(_qualifiers.begin(),_qualifiers.end()); +} + +bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const + throw() +{ + unsigned long myidx = 0; + unsigned long otheridx = 0; + + while (myidx < _qualifiers.size()) { + // Fail if we're at the end of other, since this means the field is + // missing. + if (otheridx >= other._qualifiers.size()) + return false; + + // Seek to corresponding tuple in other, ignoring tuples that + // we may not have. If we run off the end of other, the tuple is + // missing. This works because tuples are sorted by ID. + while (other._qualifiers[otheridx].id != _qualifiers[myidx].id) { + ++otheridx; + if (otheridx >= other._qualifiers.size()) + return false; + } + + // Compare to determine if the absolute value of the difference + // between these two parameters is within our maxDelta. + uint64_t a = _qualifiers[myidx].value; + uint64_t b = other._qualifiers[myidx].value; + if (a >= b) { + if ((a - b) > _qualifiers[myidx].maxDelta) + return false; + } else { + if ((b - a) > _qualifiers[myidx].maxDelta) + return false; + } + + ++myidx; + } + + return true; +} + +bool CertificateOfMembership::sign(const Identity &with) +{ + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + + try { + _signature = with.sign(buf,ptr * sizeof(uint64_t)); + _signedBy = with.address(); + delete [] buf; + return true; + } catch ( ... ) { + _signedBy.zero(); + delete [] buf; + return false; + } +} + +bool CertificateOfMembership::verify(const Identity &id) const +{ + if (!_signedBy) + return false; + if (id.address() != _signedBy) + return false; + + uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; + unsigned int ptr = 0; + for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { + buf[ptr++] = Utils::hton(q->id); + buf[ptr++] = Utils::hton(q->value); + buf[ptr++] = Utils::hton(q->maxDelta); + } + + bool valid = false; + try { + valid = id.verify(buf,ptr * sizeof(uint64_t),_signature); + delete [] buf; + } catch ( ... ) { + delete [] buf; + } + return valid; +} + +} // namespace ZeroTier diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp new file mode 100644 index 00000000..99d784b2 --- /dev/null +++ b/node/CertificateOfMembership.hpp @@ -0,0 +1,192 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_CERTIFICATEOFMEMBERSHIP_HPP +#define _ZT_CERTIFICATEOFMEMBERSHIP_HPP + +#include +#include + +#include +#include + +#include "Constants.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Identity.hpp" + +namespace ZeroTier { + +/** + * Certificate of network membership + * + * The COM contains a sorted set of three-element tuples called qualifiers. + * These contain an id, a value, and a maximum delta. + * + * The ID is arbitrary and should be assigned using a scheme that makes + * every ID globally unique. ID 0 is reserved for the always-present + * validity timestamp and range, and ID 1 is reserved for the always-present + * network ID. IDs less than 65536 are reserved for future global + * assignment. + * + * The value's meaning is ID-specific and isn't important here. What's + * important is the value and the third member of the tuple: the maximum + * delta. The maximum delta is the maximum difference permitted between + * values for a given ID between certificates for the two certificates to + * themselves agree. + * + * Network membership is checked by checking whether a peer's certificate + * agrees with your own. The timestamp provides the fundamental criterion-- + * each member of a private network must constantly obtain new certificates + * often enough to stay within the max delta for this qualifier. But other + * criteria could be added in the future for very special behaviors, things + * like latitude and longitude for instance. + */ +class CertificateOfMembership +{ +public: + /** + * Certificate type codes, used in serialization + * + * Only one so far, and only one hopefully there shall be for quite some + * time. + */ + enum Type + { + COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519 + }; + + /** + * Reserved COM IDs + * + * IDs below 65536 should be considered reserved for future global + * assignment here. + */ + enum ReservedIds + { + COM_RESERVED_ID_TIMESTAMP = 0, // timestamp, max delta defines cert life + COM_RESERVED_ID_NETWORK_ID = 1 // network ID, max delta always 0 + }; + + CertificateOfMembership() { memset(_signature.data,0,_signature.size()); } + CertificateOfMembership(const char *s) { fromString(s); } + CertificateOfMembership(const std::string &s) { fromString(s.c_str()); } + + /** + * Add or update a qualifier in this certificate + * + * Any signature is invalidated and signedBy is set to null. + * + * @param id Qualifier ID + * @param value Qualifier value + * @param maxDelta Qualifier maximum allowed difference (absolute value of difference) + */ + void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta); + + /** + * @return String-serialized representation of this certificate + */ + std::string toString() const; + + /** + * Set this certificate equal to the hex-serialized string + * + * Invalid strings will result in invalid or undefined certificate + * contents. These will subsequently fail validation and comparison. + * + * @param s String to deserialize + */ + void fromString(const char *s); + inline void fromString(const std::string &s) { fromString(s.c_str()); } + + /** + * Compare two certificates for parameter agreement + * + * This compares this certificate with the other and returns true if all + * paramters in this cert are present in the other and if they agree to + * within this cert's max delta value for each given parameter. + * + * Tuples present in other but not in this cert are ignored, but any + * tuples present in this cert but not in other result in 'false'. + * + * @param other Cert to compare with + * @return True if certs agree and 'other' may be communicated with + */ + bool agreesWith(const CertificateOfMembership &other) const + throw(); + + /** + * Sign this certificate + * + * @param with Identity to sign with, must include private key + * @return True if signature was successful + */ + bool sign(const Identity &with); + + /** + * Verify certificate against an identity + * + * @param id Identity to verify against + * @return True if certificate is signed by this identity and verification was successful + */ + bool verify(const Identity &id) const; + + /** + * @return True if signed + */ + inline bool isSigned() const throw() { return (_signedBy); } + + /** + * @return Address that signed this certificate or null address if none + */ + inline const Address &signedBy() const throw() { return _signedBy; } + +private: + struct _Qualifier + { + _Qualifier() throw() {} + _Qualifier(uint64_t i,uint64_t v,uint64_t m) throw() : + id(i), + value(v), + maxDelta(m) {} + + uint64_t id; + uint64_t value; + uint64_t maxDelta; + + inline bool operator==(const _Qualifier &q) const throw() { return (id == q.id); } // for unique + inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort + }; + + std::vector<_Qualifier> _qualifiers; // sorted by id and unique + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Network.cpp b/node/Network.cpp index d3b0b6c9..911ae998 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -42,194 +42,6 @@ namespace ZeroTier { -void Network::CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta) -{ - _signedBy.zero(); - - for(std::vector<_Qualifier>::iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - if (q->id == id) { - q->value = value; - q->maxDelta = maxDelta; - return; - } - } - - _qualifiers.push_back(_Qualifier(id,value,maxDelta)); - std::sort(_qualifiers.begin(),_qualifiers.end()); -} - -std::string Network::CertificateOfMembership::toString() const -{ - std::string s; - - uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; - try { - unsigned int ptr = 0; - for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - buf[ptr++] = Utils::hton(q->id); - buf[ptr++] = Utils::hton(q->value); - buf[ptr++] = Utils::hton(q->maxDelta); - } - s.append(Utils::hex(buf,ptr * sizeof(uint64_t))); - delete [] buf; - } catch ( ... ) { - delete [] buf; - throw; - } - - s.push_back(':'); - - s.append(_signedBy.toString()); - - if (_signedBy) { - s.push_back(':'); - s.append(Utils::hex(_signature.data,_signature.size())); - } - - return s; -} - -void Network::CertificateOfMembership::fromString(const char *s) -{ - _qualifiers.clear(); - _signedBy.zero(); - memset(_signature.data,0,_signature.size()); - - unsigned int colonAt = 0; - while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; - - if (colonAt) { - unsigned int buflen = colonAt / 2; - char *buf = new char[buflen]; - unsigned int bufactual = Utils::unhex(s,colonAt,buf,buflen); - char *bufptr = buf; - try { - while (bufactual >= 24) { - _qualifiers.push_back(_Qualifier()); - _qualifiers.back().id = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; - _qualifiers.back().value = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; - _qualifiers.back().maxDelta = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; - bufactual -= 24; - } - } catch ( ... ) {} - delete [] buf; - } - - if (s[colonAt]) { - s += colonAt + 1; - colonAt = 0; - while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; - - if (colonAt) { - char addrbuf[ZT_ADDRESS_LENGTH]; - if (Utils::unhex(s,colonAt,addrbuf,sizeof(addrbuf)) == ZT_ADDRESS_LENGTH) - _signedBy.setTo(addrbuf,ZT_ADDRESS_LENGTH); - - if ((_signedBy)&&(s[colonAt])) { - s += colonAt + 1; - colonAt = 0; - while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; - - if (colonAt) { - if (Utils::unhex(s,colonAt,_signature.data,_signature.size()) != _signature.size()) - _signedBy.zero(); - } else _signedBy.zero(); - } else _signedBy.zero(); - } - } - - std::sort(_qualifiers.begin(),_qualifiers.end()); - std::unique(_qualifiers.begin(),_qualifiers.end()); -} - -bool Network::CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const - throw() -{ - unsigned long myidx = 0; - unsigned long otheridx = 0; - - while (myidx < _qualifiers.size()) { - // Fail if we're at the end of other, since this means the field is - // missing. - if (otheridx >= other._qualifiers.size()) - return false; - - // Seek to corresponding tuple in other, ignoring tuples that - // we may not have. If we run off the end of other, the tuple is - // missing. This works because tuples are sorted by ID. - while (other._qualifiers[otheridx].id != _qualifiers[myidx].id) { - ++otheridx; - if (otheridx >= other._qualifiers.size()) - return false; - } - - // Compare to determine if the absolute value of the difference - // between these two parameters is within our maxDelta. - uint64_t a = _qualifiers[myidx].value; - uint64_t b = other._qualifiers[myidx].value; - if (a >= b) { - if ((a - b) > _qualifiers[myidx].maxDelta) - return false; - } else { - if ((b - a) > _qualifiers[myidx].maxDelta) - return false; - } - - ++myidx; - } - - return true; -} - -bool Network::CertificateOfMembership::sign(const Identity &with) -{ - uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; - unsigned int ptr = 0; - for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - buf[ptr++] = Utils::hton(q->id); - buf[ptr++] = Utils::hton(q->value); - buf[ptr++] = Utils::hton(q->maxDelta); - } - - try { - _signature = with.sign(buf,ptr * sizeof(uint64_t)); - _signedBy = with.address(); - delete [] buf; - return true; - } catch ( ... ) { - _signedBy.zero(); - delete [] buf; - return false; - } -} - -bool Network::CertificateOfMembership::verify(const Identity &id) const -{ - if (!_signedBy) - return false; - if (id.address() != _signedBy) - return false; - - uint64_t *buf = new uint64_t[_qualifiers.size() * 3]; - unsigned int ptr = 0; - for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - buf[ptr++] = Utils::hton(q->id); - buf[ptr++] = Utils::hton(q->value); - buf[ptr++] = Utils::hton(q->maxDelta); - } - - bool valid = false; - try { - valid = id.verify(buf,ptr * sizeof(uint64_t),_signature); - delete [] buf; - } catch ( ... ) { - delete [] buf; - } - return valid; -} - -// --------------------------------------------------------------------------- - const Network::MulticastRates::Rate Network::MulticastRates::GLOBAL_DEFAULT_RATE(65535,65535,64); const char *Network::statusString(const Status s) diff --git a/node/Network.hpp b/node/Network.hpp index 81af694b..9f43c957 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -51,7 +51,7 @@ #include "Identity.hpp" #include "InetAddress.hpp" #include "BandwidthAccount.hpp" -#include "C25519.hpp" +#include "CertificateOfMembership.hpp" namespace ZeroTier { @@ -84,152 +84,6 @@ class Network : NonCopyable friend class NodeConfig; public: - /** - * Certificate of network membership - * - * The COM contains a sorted set of three-element tuples called qualifiers. - * These contain an id, a value, and a maximum delta. - * - * The ID is arbitrary and should be assigned using a scheme that makes - * every ID globally unique. ID 0 is reserved for the always-present - * validity timestamp and range, and ID 1 is reserved for the always-present - * network ID. IDs less than 65536 are reserved for future global - * assignment. - * - * The value's meaning is ID-specific and isn't important here. What's - * important is the value and the third member of the tuple: the maximum - * delta. The maximum delta is the maximum difference permitted between - * values for a given ID between certificates for the two certificates to - * themselves agree. - * - * Network membership is checked by checking whether a peer's certificate - * agrees with your own. The timestamp provides the fundamental criterion-- - * each member of a private network must constantly obtain new certificates - * often enough to stay within the max delta for this qualifier. But other - * criteria could be added in the future for very special behaviors, things - * like latitude and longitude for instance. - */ - class CertificateOfMembership - { - public: - /** - * Certificate type codes, used in serialization - * - * Only one so far, and only one hopefully there shall be for quite some - * time. - */ - enum Type - { - COM_UINT64_ED25519 = 1 // tuples of unsigned 64's signed with Ed25519 - }; - - /** - * Reserved COM IDs - * - * IDs below 65536 should be considered reserved for future global - * assignment here. - */ - enum ReservedIds - { - COM_RESERVED_ID_TIMESTAMP = 0, // timestamp, max delta defines cert life - COM_RESERVED_ID_NETWORK_ID = 1 // network ID, max delta always 0 - }; - - CertificateOfMembership() { memset(_signature.data,0,_signature.size()); } - CertificateOfMembership(const char *s) { fromString(s); } - CertificateOfMembership(const std::string &s) { fromString(s.c_str()); } - - /** - * Add or update a qualifier in this certificate - * - * Any signature is invalidated and signedBy is set to null. - * - * @param id Qualifier ID - * @param value Qualifier value - * @param maxDelta Qualifier maximum allowed difference (absolute value of difference) - */ - void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta); - - /** - * @return String-serialized representation of this certificate - */ - std::string toString() const; - - /** - * Set this certificate equal to the hex-serialized string - * - * Invalid strings will result in invalid or undefined certificate - * contents. These will subsequently fail validation and comparison. - * - * @param s String to deserialize - */ - void fromString(const char *s); - inline void fromString(const std::string &s) { fromString(s.c_str()); } - - /** - * Compare two certificates for parameter agreement - * - * This compares this certificate with the other and returns true if all - * paramters in this cert are present in the other and if they agree to - * within this cert's max delta value for each given parameter. - * - * Tuples present in other but not in this cert are ignored, but any - * tuples present in this cert but not in other result in 'false'. - * - * @param other Cert to compare with - * @return True if certs agree and 'other' may be communicated with - */ - bool agreesWith(const CertificateOfMembership &other) const - throw(); - - /** - * Sign this certificate - * - * @param with Identity to sign with, must include private key - * @return True if signature was successful - */ - bool sign(const Identity &with); - - /** - * Verify certificate against an identity - * - * @param id Identity to verify against - * @return True if certificate is signed by this identity and verification was successful - */ - bool verify(const Identity &id) const; - - /** - * @return True if signed - */ - inline bool isSigned() const throw() { return (_signedBy); } - - /** - * @return Address that signed this certificate or null address if none - */ - inline const Address &signedBy() const throw() { return _signedBy; } - - private: - struct _Qualifier - { - _Qualifier() throw() {} - _Qualifier(uint64_t i,uint64_t v,uint64_t m) throw() : - id(i), - value(v), - maxDelta(m) {} - - uint64_t id; - uint64_t value; - uint64_t maxDelta; - - inline bool operator==(const _Qualifier &q) const throw() { return (id == q.id); } // for unique - inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort - }; - - std::vector<_Qualifier> _qualifiers; // sorted by id and unique - Address _signedBy; - C25519::Signature _signature; - }; - /** * Preload and rates of accrual for multicast group bandwidth limits * diff --git a/objects.mk b/objects.mk index e1280b5c..cd8f6e46 100644 --- a/objects.mk +++ b/objects.mk @@ -3,6 +3,7 @@ OBJS=\ ext/lz4/lz4hc.o \ ext/lz4/lz4.o \ node/C25519.o \ + node/CertificateOfMembership.o \ node/Defaults.o \ node/Demarc.o \ node/EthernetTap.o \ -- cgit v1.2.3