MembershipCredential.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. * Copyright (c)2013-2020 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. #include "MembershipCredential.hpp"
  14. namespace ZeroTier {
  15. MembershipCredential::MembershipCredential(const int64_t timestamp, const int64_t timestampMaxDelta, const uint64_t nwid, const Identity &issuedTo) noexcept: // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
  16. m_timestamp(timestamp),
  17. m_timestampMaxDelta(timestampMaxDelta),
  18. m_networkId(nwid),
  19. m_issuedTo(issuedTo.fingerprint()),
  20. m_signatureLength(0)
  21. {}
  22. bool MembershipCredential::agreesWith(const MembershipCredential &other) const noexcept
  23. {
  24. // NOTE: we always do explicit absolute value with an if() since llabs() can have overflow
  25. // conditions that could introduce a vulnerability.
  26. if (other.m_timestamp > m_timestamp) {
  27. if ((other.m_timestamp - m_timestamp) > std::min(m_timestampMaxDelta, other.m_timestampMaxDelta))
  28. return false;
  29. } else {
  30. if ((m_timestamp - other.m_timestamp) > std::min(m_timestampMaxDelta, other.m_timestampMaxDelta))
  31. return false;
  32. }
  33. // us <> them
  34. for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin()); i != m_additionalQualifiers.end(); ++i) {
  35. if (i->delta != 0xffffffffffffffffULL) {
  36. const uint64_t *v2 = nullptr;
  37. for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(other.m_additionalQualifiers.begin()); j != other.m_additionalQualifiers.end(); ++i) {
  38. if (j->id == i->id) {
  39. v2 = &(j->value);
  40. break;
  41. }
  42. }
  43. if (!v2)
  44. return false;
  45. if (*v2 > i->value) {
  46. if ((*v2 - i->value) > i->delta)
  47. return false;
  48. } else {
  49. if ((i->value - *v2) > i->delta)
  50. return false;
  51. }
  52. }
  53. }
  54. // them <> us (we need a second pass in case they have qualifiers we don't or vice versa)
  55. for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(other.m_additionalQualifiers.begin()); i != other.m_additionalQualifiers.end(); ++i) {
  56. if (i->delta != 0xffffffffffffffffULL) {
  57. const uint64_t *v2 = nullptr;
  58. for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(m_additionalQualifiers.begin()); j != m_additionalQualifiers.end(); ++i) {
  59. if (j->id == i->id) {
  60. v2 = &(j->value);
  61. break;
  62. }
  63. }
  64. if (!v2)
  65. return false;
  66. if (*v2 > i->value) {
  67. if ((*v2 - i->value) > i->delta)
  68. return false;
  69. } else {
  70. if ((i->value - *v2) > i->delta)
  71. return false;
  72. }
  73. }
  74. }
  75. // SECURITY: check for issued-to inequality is a sanity check. This should be impossible since elsewhere
  76. // in the code COMs are checked to ensure that they do in fact belong to their issued-to identities.
  77. return (other.m_networkId == m_networkId) && (m_networkId != 0) && (other.m_issuedTo.address != m_issuedTo.address);
  78. }
  79. bool MembershipCredential::sign(const Identity &with) noexcept
  80. {
  81. m_signedBy = with.address();
  82. uint64_t buf[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX / 8];
  83. const unsigned int bufSize = m_fillSigningBuf(buf);
  84. m_signatureLength = with.sign(buf, bufSize, m_signature, sizeof(m_signature));
  85. return m_signatureLength > 0;
  86. }
  87. int MembershipCredential::marshal(uint8_t data[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX], const bool v2) const noexcept
  88. {
  89. data[0] = v2 ? 2 : 1;
  90. // All formats start with the standard three qualifiers: timestamp with delta, network ID as a strict
  91. // equality compare, and the address of the issued-to node as an informational tuple.
  92. int p = 3;
  93. Utils::storeBigEndian<uint64_t>(data + p, 0);
  94. p += 8;
  95. Utils::storeBigEndian<uint64_t>(data + p, (uint64_t) m_timestamp);
  96. p += 8;
  97. Utils::storeBigEndian<uint64_t>(data + p, (uint64_t) m_timestampMaxDelta);
  98. p += 8;
  99. Utils::storeBigEndian<uint64_t>(data + p, 1);
  100. p += 8;
  101. Utils::storeBigEndian<uint64_t>(data + p, m_networkId);
  102. p += 8;
  103. Utils::storeBigEndian<uint64_t>(data + p, 0);
  104. p += 8;
  105. Utils::storeBigEndian<uint64_t>(data + p, 2);
  106. p += 8;
  107. Utils::storeBigEndian<uint64_t>(data + p, m_issuedTo.address);
  108. p += 8;
  109. Utils::storeMachineEndian< uint64_t >(data + p, 0xffffffffffffffffULL);
  110. p += 8;
  111. if (v2) {
  112. // V2 marshal format will have three tuples followed by the fingerprint hash.
  113. Utils::storeBigEndian<uint16_t>(data + 1, 3);
  114. Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(data + p, m_issuedTo.hash);
  115. p += 48;
  116. } else {
  117. // V1 marshal format must shove everything into tuples, resulting in nine.
  118. Utils::storeBigEndian<uint16_t>(data + 1, 9);
  119. for (int k = 0;k < 6;++k) {
  120. Utils::storeBigEndian<uint64_t>(data + p, (uint64_t) k + 3);
  121. p += 8;
  122. Utils::storeMachineEndian< uint64_t >(data + p, Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + (k * 8)));
  123. p += 8;
  124. Utils::storeMachineEndian< uint64_t >(data + p, 0xffffffffffffffffULL);
  125. p += 8;
  126. }
  127. }
  128. m_signedBy.copyTo(data + p);
  129. p += 5;
  130. if (v2) {
  131. // V2 marshal format prefixes signatures with a 16-bit length to support future signature types.
  132. Utils::storeBigEndian<uint16_t>(data + p, (uint16_t) m_signatureLength);
  133. p += 2;
  134. Utils::copy(data + p, m_signature, m_signatureLength);
  135. p += (int) m_signatureLength;
  136. } else {
  137. // V1 only supports 96-byte signature fields.
  138. Utils::copy<96>(data + p, m_signature);
  139. p += 96;
  140. }
  141. return p;
  142. }
  143. int MembershipCredential::unmarshal(const uint8_t *data, int len) noexcept
  144. {
  145. if (len < (1 + 2 + 72))
  146. return -1;
  147. TriviallyCopyable::memoryZero(this);
  148. const unsigned int numq = Utils::loadBigEndian<uint16_t>(data + 1);
  149. if ((numq < 3) || (numq > (ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS + 3)))
  150. return -1;
  151. int p = 3;
  152. for (unsigned int q = 0;q < numq;++q) {
  153. if ((p + 24) > len)
  154. return -1;
  155. const uint64_t id = Utils::loadBigEndian<uint64_t>(data + p);
  156. p += 8; // NOLINT(hicpp-use-auto,modernize-use-auto)
  157. const uint64_t value = Utils::loadBigEndian<uint64_t>(data + p);
  158. p += 8; // NOLINT(hicpp-use-auto,modernize-use-auto)
  159. const uint64_t delta = Utils::loadBigEndian<uint64_t>(data + p);
  160. p += 8; // NOLINT(hicpp-use-auto,modernize-use-auto)
  161. switch (id) {
  162. case 0:
  163. m_timestamp = (int64_t) value;
  164. m_timestampMaxDelta = (int64_t) delta;
  165. break;
  166. case 1:
  167. m_networkId = value;
  168. break;
  169. case 2:
  170. m_issuedTo.address = value;
  171. break;
  172. // V1 nodes will pack the hash into qualifier tuples.
  173. case 3:
  174. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash, value);
  175. break;
  176. case 4:
  177. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash + 8, value);
  178. break;
  179. case 5:
  180. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash + 16, value);
  181. break;
  182. case 6:
  183. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash + 24, value);
  184. break;
  185. case 7:
  186. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash + 32, value);
  187. break;
  188. case 8:
  189. Utils::storeBigEndian<uint64_t>(m_issuedTo.hash + 40, value);
  190. break;
  191. default:
  192. if (m_additionalQualifiers.size() >= ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS)
  193. return -1;
  194. m_additionalQualifiers.push_back(p_Qualifier(id, value, delta));
  195. break;
  196. }
  197. }
  198. std::sort(m_additionalQualifiers.begin(), m_additionalQualifiers.end());
  199. if (data[0] == 1) {
  200. if ((p + 96) > len)
  201. return -1;
  202. m_signatureLength = 96;
  203. Utils::copy<96>(m_signature, data + p);
  204. return p + 96;
  205. } else if (data[0] == 2) {
  206. if ((p + 48) > len)
  207. return -1;
  208. Utils::copy<48>(m_issuedTo.hash, data + p);
  209. p += 48;
  210. if ((p + 2) > len)
  211. return -1;
  212. m_signatureLength = Utils::loadBigEndian<uint16_t>(data + p);
  213. if ((m_signatureLength > (unsigned int) sizeof(m_signature)) || ((p + (int) m_signatureLength) > len))
  214. return -1;
  215. Utils::copy(m_signature, data + p, m_signatureLength);
  216. return p + (int) m_signatureLength;
  217. }
  218. return -1;
  219. }
  220. unsigned int MembershipCredential::m_fillSigningBuf(uint64_t *buf) const noexcept
  221. {
  222. const uint64_t informational = 0xffffffffffffffffULL;
  223. /*
  224. * Signing always embeds all data to be signed in qualifier tuple format for
  225. * backward compatibility with V1 nodes, since otherwise we'd need a signature
  226. * for v1 nodes to verify and another for v2 nodes to verify.
  227. */
  228. // The standard three tuples that must begin every COM.
  229. buf[0] = 0;
  230. buf[1] = Utils::hton((uint64_t) m_timestamp);
  231. buf[2] = Utils::hton((uint64_t) m_timestampMaxDelta);
  232. buf[3] = ZT_CONST_TO_BE_UINT64(1);
  233. buf[4] = Utils::hton(m_networkId);
  234. buf[5] = 0;
  235. buf[6] = ZT_CONST_TO_BE_UINT64(2);
  236. buf[7] = Utils::hton(m_issuedTo.address);
  237. buf[8] = informational;
  238. unsigned int p = 9;
  239. // The full identity fingerprint of the peer to whom the COM was issued,
  240. // embeded as a series of informational tuples.
  241. if (m_issuedTo.haveHash()) {
  242. buf[p++] = ZT_CONST_TO_BE_UINT64(3);
  243. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash);
  244. buf[p++] = informational;
  245. buf[p++] = ZT_CONST_TO_BE_UINT64(4);
  246. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + 8);
  247. buf[p++] = informational;
  248. buf[p++] = ZT_CONST_TO_BE_UINT64(5);
  249. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + 16);
  250. buf[p++] = informational;
  251. buf[p++] = ZT_CONST_TO_BE_UINT64(6);
  252. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + 24);
  253. buf[p++] = informational;
  254. buf[p++] = ZT_CONST_TO_BE_UINT64(7);
  255. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + 32);
  256. buf[p++] = informational;
  257. buf[p++] = ZT_CONST_TO_BE_UINT64(8);
  258. buf[p++] = Utils::loadMachineEndian< uint64_t >(m_issuedTo.hash + 40);
  259. buf[p++] = informational;
  260. }
  261. for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin()); i != m_additionalQualifiers.end(); ++i) { // NOLINT(modernize-loop-convert)
  262. buf[p++] = Utils::hton(i->id);
  263. buf[p++] = Utils::hton(i->value);
  264. buf[p++] = Utils::hton(i->delta);
  265. }
  266. return p * 8;
  267. }
  268. } // namespace ZeroTier