1 /* 2 * Copyright (c)2019 ZeroTier, Inc. 3 * 4 * Use of this software is governed by the Business Source License included 5 * in the LICENSE.TXT file in the project's root directory. 6 * 7 * Change Date: 2025-01-01 8 * 9 * On the date above, in accordance with the Business Source License, use 10 * of this software will be governed by version 2.0 of the Apache License. 11 */ 12 /****/ 13 14 #ifndef ZT_CERTIFICATEOFMEMBERSHIP_HPP 15 #define ZT_CERTIFICATEOFMEMBERSHIP_HPP 16 17 #include <stdint.h> 18 #include <string.h> 19 20 #include <string> 21 #include <stdexcept> 22 #include <algorithm> 23 24 #include "Constants.hpp" 25 #include "Credential.hpp" 26 #include "Buffer.hpp" 27 #include "Address.hpp" 28 #include "C25519.hpp" 29 #include "Identity.hpp" 30 #include "Utils.hpp" 31 32 /** 33 * Maximum number of qualifiers allowed in a COM (absolute max: 65535) 34 */ 35 #define ZT_NETWORK_COM_MAX_QUALIFIERS 8 36 37 namespace ZeroTier { 38 39 class RuntimeEnvironment; 40 41 /** 42 * Certificate of network membership 43 * 44 * The COM contains a sorted set of three-element tuples called qualifiers. 45 * These contain an id, a value, and a maximum delta. 46 * 47 * The ID is arbitrary and should be assigned using a scheme that makes 48 * every ID globally unique. IDs beneath 65536 are reserved for global 49 * assignment by ZeroTier Networks. 50 * 51 * The value's meaning is ID-specific and isn't important here. What's 52 * important is the value and the third member of the tuple: the maximum 53 * delta. The maximum delta is the maximum difference permitted between 54 * values for a given ID between certificates for the two certificates to 55 * themselves agree. 56 * 57 * Network membership is checked by checking whether a peer's certificate 58 * agrees with your own. The timestamp provides the fundamental criterion-- 59 * each member of a private network must constantly obtain new certificates 60 * often enough to stay within the max delta for this qualifier. But other 61 * criteria could be added in the future for very special behaviors, things 62 * like latitude and longitude for instance. 63 * 64 * This is a memcpy()'able structure and is safe (in a crash sense) to modify 65 * without locks. 66 */ 67 class CertificateOfMembership : public Credential 68 { 69 public: credentialType()70 static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COM; } 71 72 /** 73 * Reserved qualifier IDs 74 * 75 * IDs below 1024 are reserved for use as standard IDs. Others are available 76 * for user-defined use. 77 * 78 * Addition of new required fields requires that code in hasRequiredFields 79 * be updated as well. 80 */ 81 enum ReservedId 82 { 83 /** 84 * Timestamp of certificate 85 */ 86 COM_RESERVED_ID_TIMESTAMP = 0, 87 88 /** 89 * Network ID for which certificate was issued 90 */ 91 COM_RESERVED_ID_NETWORK_ID = 1, 92 93 /** 94 * ZeroTier address to whom certificate was issued 95 */ 96 COM_RESERVED_ID_ISSUED_TO = 2 97 98 // IDs 3-6 reserved for full hash of identity to which this COM was issued. 99 }; 100 101 /** 102 * Create an empty certificate of membership 103 */ CertificateOfMembership()104 CertificateOfMembership() : 105 _qualifierCount(0) {} 106 107 /** 108 * Create from required fields common to all networks 109 * 110 * @param timestamp Timestamp of certificate 111 * @param timestampMaxDelta Maximum variation between timestamps on this net 112 * @param nwid Network ID 113 * @param issuedTo Certificate recipient 114 */ 115 CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Identity &issuedTo); 116 117 /** 118 * Create from binary-serialized COM in buffer 119 * 120 * @param b Buffer to deserialize from 121 * @param startAt Position to start in buffer 122 */ 123 template<unsigned int C> CertificateOfMembership(const Buffer<C> & b,unsigned int startAt=0)124 CertificateOfMembership(const Buffer<C> &b,unsigned int startAt = 0) 125 { 126 deserialize(b,startAt); 127 } 128 129 /** 130 * @return True if there's something here 131 */ operator bool() const132 inline operator bool() const { return (_qualifierCount != 0); } 133 134 /** 135 * @return Credential ID, always 0 for COMs 136 */ id() const137 inline uint32_t id() const { return 0; } 138 139 /** 140 * @return Timestamp for this cert and maximum delta for timestamp 141 */ timestamp() const142 inline int64_t timestamp() const 143 { 144 for(unsigned int i=0;i<_qualifierCount;++i) { 145 if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) 146 return _qualifiers[i].value; 147 } 148 return 0; 149 } 150 151 /** 152 * @return Address to which this cert was issued 153 */ issuedTo() const154 inline Address issuedTo() const 155 { 156 for(unsigned int i=0;i<_qualifierCount;++i) { 157 if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) 158 return Address(_qualifiers[i].value); 159 } 160 return Address(); 161 } 162 163 /** 164 * @return Network ID for which this cert was issued 165 */ networkId() const166 inline uint64_t networkId() const 167 { 168 for(unsigned int i=0;i<_qualifierCount;++i) { 169 if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) 170 return _qualifiers[i].value; 171 } 172 return 0ULL; 173 } 174 175 /** 176 * Compare two certificates for parameter agreement 177 * 178 * This compares this certificate with the other and returns true if all 179 * parameters in this cert are present in the other and if they agree to 180 * within this cert's max delta value for each given parameter. 181 * 182 * Tuples present in other but not in this cert are ignored, but any 183 * tuples present in this cert but not in other result in 'false'. 184 * 185 * @param other Cert to compare with 186 * @param otherIdentity Identity of other node 187 * @return True if certs agree and 'other' may be communicated with 188 */ 189 bool agreesWith(const CertificateOfMembership &other, const Identity &otherIdentity) const; 190 191 /** 192 * Sign this certificate 193 * 194 * @param with Identity to sign with, must include private key 195 * @return True if signature was successful 196 */ 197 bool sign(const Identity &with); 198 199 /** 200 * Verify this COM and its signature 201 * 202 * @param RR Runtime environment for looking up peers 203 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call 204 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential 205 */ 206 int verify(const RuntimeEnvironment *RR,void *tPtr) const; 207 208 /** 209 * @return True if signed 210 */ isSigned() const211 inline bool isSigned() const { return (_signedBy); } 212 213 /** 214 * @return Address that signed this certificate or null address if none 215 */ signedBy() const216 inline const Address &signedBy() const { return _signedBy; } 217 218 template<unsigned int C> serialize(Buffer<C> & b) const219 inline void serialize(Buffer<C> &b) const 220 { 221 b.append((uint8_t)1); 222 b.append((uint16_t)_qualifierCount); 223 for(unsigned int i=0;i<_qualifierCount;++i) { 224 b.append(_qualifiers[i].id); 225 b.append(_qualifiers[i].value); 226 b.append(_qualifiers[i].maxDelta); 227 } 228 _signedBy.appendTo(b); 229 if (_signedBy) 230 b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); 231 } 232 233 template<unsigned int C> deserialize(const Buffer<C> & b,unsigned int startAt=0)234 inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) 235 { 236 unsigned int p = startAt; 237 238 _qualifierCount = 0; 239 _signedBy.zero(); 240 241 if (b[p++] != 1) 242 throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; 243 244 unsigned int numq = b.template at<uint16_t>(p); p += sizeof(uint16_t); 245 uint64_t lastId = 0; 246 for(unsigned int i=0;i<numq;++i) { 247 const uint64_t qid = b.template at<uint64_t>(p); 248 if (qid < lastId) 249 throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING; 250 else lastId = qid; 251 if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { 252 _qualifiers[_qualifierCount].id = qid; 253 _qualifiers[_qualifierCount].value = b.template at<uint64_t>(p + 8); 254 _qualifiers[_qualifierCount].maxDelta = b.template at<uint64_t>(p + 16); 255 p += 24; 256 ++_qualifierCount; 257 } else { 258 throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW; 259 } 260 } 261 262 _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); 263 p += ZT_ADDRESS_LENGTH; 264 265 if (_signedBy) { 266 memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); 267 p += ZT_C25519_SIGNATURE_LEN; 268 } 269 270 return (p - startAt); 271 } 272 operator ==(const CertificateOfMembership & c) const273 inline bool operator==(const CertificateOfMembership &c) const 274 { 275 if (_signedBy != c._signedBy) 276 return false; 277 if (_qualifierCount != c._qualifierCount) 278 return false; 279 for(unsigned int i=0;i<_qualifierCount;++i) { 280 const _Qualifier &a = _qualifiers[i]; 281 const _Qualifier &b = c._qualifiers[i]; 282 if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) 283 return false; 284 } 285 return (memcmp(_signature.data,c._signature.data,ZT_C25519_SIGNATURE_LEN) == 0); 286 } operator !=(const CertificateOfMembership & c) const287 inline bool operator!=(const CertificateOfMembership &c) const { return (!(*this == c)); } 288 289 private: 290 struct _Qualifier 291 { _QualifierZeroTier::CertificateOfMembership::_Qualifier292 _Qualifier() : id(0),value(0),maxDelta(0) {} 293 uint64_t id; 294 uint64_t value; 295 uint64_t maxDelta; operator <ZeroTier::CertificateOfMembership::_Qualifier296 inline bool operator<(const _Qualifier &q) const { return (id < q.id); } // sort order 297 }; 298 299 Address _signedBy; 300 _Qualifier _qualifiers[ZT_NETWORK_COM_MAX_QUALIFIERS]; 301 unsigned int _qualifierCount; 302 C25519::Signature _signature; 303 }; 304 305 } // namespace ZeroTier 306 307 #endif 308