SymmetricKey.hpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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: 2024-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. #ifndef ZT_SYMMETRICKEY_HPP
  14. #define ZT_SYMMETRICKEY_HPP
  15. #include "Constants.hpp"
  16. #include "Utils.hpp"
  17. #include "InetAddress.hpp"
  18. namespace ZeroTier {
  19. #define ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX 52
  20. /**
  21. * Container for symmetric keys and ciphers initialized with them
  22. *
  23. * This container is responsible for tracking key TTL to maintain it
  24. * below our security bounds and tell us when it's time to re-key.
  25. *
  26. * Set TTL and TTLM to 0 for permanent keys. These still track uses
  27. * but do not signal expiration.
  28. *
  29. * @tparam C Cipher to embed (must accept key in constructor and/or init() method)
  30. * @tparam TTL Maximum time to live in milliseconds or 0 for a permanent key with unlimited TTL
  31. * @tparam TTLM Maximum time to live in messages or 0 for a permanent key with unlimited TTL
  32. */
  33. template<typename C,int64_t TTL,uint64_t TTLM>
  34. class SymmetricKey
  35. {
  36. public:
  37. /**
  38. * Symmetric cipher keyed with this key
  39. */
  40. const C cipher;
  41. /**
  42. * Construct an uninitialized symmetric key container
  43. */
  44. ZT_INLINE SymmetricKey() noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init,hicpp-use-equals-default,modernize-use-equals-default)
  45. cipher(),
  46. _ts(0),
  47. _nonceBase(0),
  48. _odometer(0)
  49. {
  50. Utils::memoryLock(_k,sizeof(_k));
  51. }
  52. /**
  53. * Construct a new symmetric key
  54. *
  55. * @param ts Current time (must still be given for permanent keys even though there is no expiry checking)
  56. * @param key 32-byte / 256-bit key
  57. */
  58. explicit ZT_INLINE SymmetricKey(const int64_t ts,const void *const key) noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
  59. cipher(key),
  60. _ts(ts),
  61. _nonceBase((uint64_t)ts << 22U), // << 22 to shift approximately the seconds since epoch into the most significant 32 bits
  62. _odometer(0)
  63. {
  64. Utils::memoryLock(_k,sizeof(_k));
  65. Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(_k,key);
  66. }
  67. ZT_INLINE SymmetricKey(const SymmetricKey &k) noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
  68. cipher(k._k),
  69. _ts(k.ts),
  70. _nonceBase(k._nonceBase),
  71. _odometer(k._odometer)
  72. {
  73. Utils::memoryLock(_k,sizeof(_k));
  74. Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(_k,k._k);
  75. }
  76. ZT_INLINE ~SymmetricKey() noexcept
  77. {
  78. Utils::burn(_k,sizeof(_k));
  79. Utils::memoryUnlock(_k,sizeof(_k));
  80. }
  81. ZT_INLINE SymmetricKey &operator=(const SymmetricKey &k) noexcept
  82. {
  83. if (&k != this) {
  84. cipher.init(k._k);
  85. _ts = k._ts;
  86. _nonceBase = k._nonceBase;
  87. _odometer = k._odometer;
  88. Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(_k,k._k);
  89. }
  90. return *this;
  91. }
  92. /**
  93. * Initialize or change the key wrapped by this SymmetricKey object
  94. *
  95. * If the supplied key is identical to the current key, no change occurs and false is returned.
  96. *
  97. * @param ts Current time
  98. * @param key 32-byte / 256-bit key
  99. * @return True if the symmetric key was changed
  100. */
  101. ZT_INLINE bool init(const int64_t ts,const void *const key) noexcept
  102. {
  103. if ((_ts > 0)&&(memcmp(_k,key,ZT_SYMMETRIC_KEY_SIZE) == 0))
  104. return false;
  105. cipher.init(key);
  106. _ts = ts;
  107. _nonceBase = (uint64_t)ts << 22U; // << 22 to shift approximately the seconds since epoch into the most significant 32 bits;
  108. _odometer = 0;
  109. Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(_k,key);
  110. return true;
  111. }
  112. /**
  113. * Clear key and set to NIL value (boolean evaluates to false)
  114. */
  115. ZT_INLINE void clear() noexcept
  116. {
  117. _ts = 0;
  118. _nonceBase = 0;
  119. _odometer = 0;
  120. Utils::zero<ZT_SYMMETRIC_KEY_SIZE>(_k);
  121. }
  122. /**
  123. * Check whether this symmetric key may be expiring soon
  124. *
  125. * @param now Current time
  126. * @return True if re-keying should happen
  127. */
  128. ZT_INLINE bool expiringSoon(const int64_t now) const noexcept
  129. {
  130. return (TTL > 0) && ( ((now - _ts) >= (TTL / 2)) || (_odometer >= (TTLM / 2)) );
  131. }
  132. /**
  133. * Check whether this symmetric key is expired due to too much time or too many messages
  134. *
  135. * @param now Current time
  136. * @return True if this symmetric key should no longer be used
  137. */
  138. ZT_INLINE bool expired(const int64_t now) const noexcept
  139. {
  140. return (TTL > 0) && ( ((now - _ts) >= TTL) || (_odometer >= TTLM) );
  141. }
  142. /**
  143. * @return True if this is a never-expiring key, such as the identity key created by identity key agreement
  144. */
  145. constexpr bool permanent() const noexcept
  146. {
  147. return TTL == 0;
  148. }
  149. /**
  150. * Get the raw key that was used to initialize the cipher.
  151. *
  152. * @return 32-byte / 256-bit symmetric key
  153. */
  154. ZT_INLINE const uint8_t *key() const noexcept
  155. {
  156. return _k;
  157. }
  158. /**
  159. * Advance usage counter by one and return the next unique initialization vector for a new message.
  160. *
  161. * @return Next unique IV for next message
  162. */
  163. ZT_INLINE uint64_t nextMessageIv() noexcept
  164. {
  165. return _nonceBase + _odometer++;
  166. }
  167. /**
  168. * @return True if this object is not NIL
  169. */
  170. ZT_INLINE operator bool() const noexcept { return (_ts > 0); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
  171. static constexpr int marshalSizeMax() noexcept { return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX; }
  172. /**
  173. * Marshal with encryption at rest
  174. *
  175. * @tparam MC Cipher type (AES in our code) to use for encryption at rest
  176. * @param keyEncCipher Initialized cipher
  177. * @param data Destination for marshaled key
  178. * @return Bytes written or -1 on error
  179. */
  180. template<typename MC>
  181. ZT_INLINE int marshal(const MC &keyEncCipher,uint8_t data[ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX]) const noexcept
  182. {
  183. Utils::storeBigEndian<uint64_t>(data,(uint64_t)_ts);
  184. Utils::storeBigEndian<uint64_t>(data + 8,_odometer.load());
  185. Utils::storeBigEndian<uint32_t>(data + 16,Utils::fnv1a32(_k,sizeof(_k)));
  186. // Key encryption at rest is CBC using the last 32 bits of the timestamp, the odometer,
  187. // and the FNV1a checksum as a 128-bit IV. A duplicate IV wouldn't matter much anyway since
  188. // keys should be unique. Simple ECB would be fine as they also have no structure, but this
  189. // looks better.
  190. uint8_t tmp[16];
  191. for(int i=0;i<16;++i)
  192. tmp[i] = data[i + 4] ^ _k[i];
  193. keyEncCipher.encrypt(tmp,data + 20);
  194. for(int i=0;i<16;++i)
  195. tmp[i] = data[i + 20] ^ _k[i + 16];
  196. keyEncCipher.encrypt(tmp,data + 36);
  197. return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX;
  198. }
  199. /**
  200. * Unmarshal, decrypt, and verify key checksum
  201. *
  202. * Key checksum verification failure results in the SymmetricKey being zeroed out to its
  203. * nil value, but the bytes read are still returned. The caller must check this if it
  204. * requires the key to be present and verified.
  205. *
  206. * @tparam MC Cipher type (AES in our code) to use for encryption at rest
  207. * @param keyDecCipher Initialized cipher for decryption
  208. * @param data Source to read
  209. * @param len Bytes remaining at source
  210. * @return Bytes read from source
  211. */
  212. template<typename MC>
  213. ZT_INLINE int unmarshal(const MC &keyDecCipher,const uint8_t *restrict data,int len) noexcept
  214. {
  215. if (len < ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX)
  216. return -1;
  217. _ts = (int64_t)Utils::loadBigEndian<uint64_t>(data);
  218. _odometer = (uint64_t)Utils::loadBigEndian<uint64_t>(data + 8);
  219. const uint32_t fnv = Utils::loadBigEndian<uint32_t>(data + 16); // NOLINT(hicpp-use-auto,modernize-use-auto)
  220. uint8_t tmp[16];
  221. keyDecCipher.decrypt(data + 20,tmp);
  222. for(int i=0;i<16;++i)
  223. _k[i] = data[i + 4] ^ tmp[i];
  224. keyDecCipher.decrypt(data + 36,tmp);
  225. for(int i=0;i<16;++i)
  226. _k[i + 16] = data[i + 20] ^ tmp[i];
  227. if (Utils::fnv1a32(_k,sizeof(_k)) != fnv)
  228. clear();
  229. return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX;
  230. }
  231. private:
  232. int64_t _ts;
  233. uint64_t _nonceBase;
  234. std::atomic<uint64_t> _odometer;
  235. uint8_t _k[ZT_SYMMETRIC_KEY_SIZE];
  236. };
  237. } // namespace ZeroTier
  238. #endif