mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
268 lines
7.9 KiB
C++
268 lines
7.9 KiB
C++
/*
|
|
* Copyright (c)2013-2020 ZeroTier, Inc.
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file in the project's root directory.
|
|
*
|
|
* Change Date: 2024-01-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2.0 of the Apache License.
|
|
*/
|
|
/****/
|
|
|
|
#ifndef ZT_SYMMETRICKEY_HPP
|
|
#define ZT_SYMMETRICKEY_HPP
|
|
|
|
#include "Constants.hpp"
|
|
#include "Utils.hpp"
|
|
#include "InetAddress.hpp"
|
|
|
|
namespace ZeroTier {
|
|
|
|
#define ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX 52
|
|
|
|
/**
|
|
* Container for symmetric keys and ciphers initialized with them
|
|
*
|
|
* This container is responsible for tracking key TTL to maintain it
|
|
* below our security bounds and tell us when it's time to re-key.
|
|
*
|
|
* Set TTL and TTLM to 0 for permanent keys. These still track uses
|
|
* but do not signal expiration.
|
|
*
|
|
* @tparam C Cipher to embed (must accept key in constructor and/or init() method)
|
|
* @tparam TTL Maximum time to live in milliseconds or 0 for a permanent key with unlimited TTL
|
|
* @tparam TTLM Maximum time to live in messages or 0 for a permanent key with unlimited TTL
|
|
*/
|
|
template<typename C,int64_t TTL,uint64_t TTLM>
|
|
class SymmetricKey
|
|
{
|
|
public:
|
|
/**
|
|
* Symmetric cipher keyed with this key
|
|
*/
|
|
const C cipher;
|
|
|
|
/**
|
|
* Construct an uninitialized symmetric key container
|
|
*/
|
|
ZT_INLINE SymmetricKey() noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init,hicpp-use-equals-default,modernize-use-equals-default)
|
|
cipher(),
|
|
m_ts(0),
|
|
m_nonceBase(0),
|
|
m_odometer(0)
|
|
{
|
|
Utils::memoryLock(m_secret, sizeof(m_secret));
|
|
}
|
|
|
|
/**
|
|
* Construct a new symmetric key
|
|
*
|
|
* @param ts Current time (must still be given for permanent keys even though there is no expiry checking)
|
|
* @param key 32-byte / 256-bit key
|
|
*/
|
|
explicit ZT_INLINE SymmetricKey(const int64_t ts,const void *const key) noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
cipher(key),
|
|
m_ts(ts),
|
|
m_nonceBase((uint64_t)ts << 22U), // << 22 to shift approximately the seconds since epoch into the most significant 32 bits
|
|
m_odometer(0)
|
|
{
|
|
Utils::memoryLock(m_secret, sizeof(m_secret));
|
|
Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(m_secret, key);
|
|
}
|
|
|
|
ZT_INLINE SymmetricKey(const SymmetricKey &k) noexcept : // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
cipher(k.m_secret),
|
|
m_ts(k.ts),
|
|
m_nonceBase(k.m_nonceBase),
|
|
m_odometer(k.m_odometer)
|
|
{
|
|
Utils::memoryLock(m_secret, sizeof(m_secret));
|
|
Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(m_secret, k.m_secret);
|
|
}
|
|
|
|
ZT_INLINE ~SymmetricKey() noexcept
|
|
{
|
|
Utils::burn(m_secret, sizeof(m_secret));
|
|
Utils::memoryUnlock(m_secret, sizeof(m_secret));
|
|
}
|
|
|
|
ZT_INLINE SymmetricKey &operator=(const SymmetricKey &k) noexcept
|
|
{
|
|
if (&k != this) {
|
|
cipher.init(k.m_secret);
|
|
m_ts = k.m_ts;
|
|
m_nonceBase = k.m_nonceBase;
|
|
m_odometer = k.m_odometer;
|
|
Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(m_secret, k.m_secret);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Initialize or change the key wrapped by this SymmetricKey object
|
|
*
|
|
* If the supplied key is identical to the current key, no change occurs and false is returned.
|
|
*
|
|
* @param ts Current time
|
|
* @param key 32-byte / 256-bit key
|
|
* @return True if the symmetric key was changed
|
|
*/
|
|
ZT_INLINE bool init(const int64_t ts,const void *const key) noexcept
|
|
{
|
|
if ((m_ts > 0) && (memcmp(m_secret, key, ZT_SYMMETRIC_KEY_SIZE) == 0))
|
|
return false;
|
|
cipher.init(key);
|
|
m_ts = ts;
|
|
m_nonceBase = (uint64_t)ts << 22U; // << 22 to shift approximately the seconds since epoch into the most significant 32 bits;
|
|
m_odometer = 0;
|
|
Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(m_secret, key);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Clear key and set to NIL value (boolean evaluates to false)
|
|
*/
|
|
ZT_INLINE void clear() noexcept
|
|
{
|
|
m_ts = 0;
|
|
m_nonceBase = 0;
|
|
m_odometer = 0;
|
|
Utils::zero<ZT_SYMMETRIC_KEY_SIZE>(m_secret);
|
|
}
|
|
|
|
/**
|
|
* Check whether this symmetric key may be expiring soon
|
|
*
|
|
* @param now Current time
|
|
* @return True if re-keying should happen
|
|
*/
|
|
ZT_INLINE bool expiringSoon(const int64_t now) const noexcept
|
|
{
|
|
return (TTL > 0) && (((now - m_ts) >= (TTL / 2)) || (m_odometer >= (TTLM / 2)) );
|
|
}
|
|
|
|
/**
|
|
* Check whether this symmetric key is expired due to too much time or too many messages
|
|
*
|
|
* @param now Current time
|
|
* @return True if this symmetric key should no longer be used
|
|
*/
|
|
ZT_INLINE bool expired(const int64_t now) const noexcept
|
|
{
|
|
return (TTL > 0) && (((now - m_ts) >= TTL) || (m_odometer >= TTLM) );
|
|
}
|
|
|
|
/**
|
|
* @return True if this is a never-expiring key, such as the identity key created by identity key agreement
|
|
*/
|
|
constexpr bool permanent() const noexcept
|
|
{
|
|
return TTL == 0;
|
|
}
|
|
|
|
/**
|
|
* Get the raw key that was used to initialize the cipher.
|
|
*
|
|
* @return 32-byte / 256-bit symmetric key
|
|
*/
|
|
ZT_INLINE const uint8_t *key() const noexcept
|
|
{
|
|
return m_secret;
|
|
}
|
|
|
|
/**
|
|
* Advance usage counter by one and return the next unique initialization vector for a new message.
|
|
*
|
|
* @return Next unique IV for next message
|
|
*/
|
|
ZT_INLINE uint64_t nextMessageIv() noexcept
|
|
{
|
|
return m_nonceBase + m_odometer++;
|
|
}
|
|
|
|
/**
|
|
* @return True if this object is not NIL
|
|
*/
|
|
ZT_INLINE operator bool() const noexcept { return (m_ts > 0); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
|
|
static constexpr int marshalSizeMax() noexcept { return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX; }
|
|
|
|
/**
|
|
* Marshal with encryption at rest
|
|
*
|
|
* @tparam MC Cipher type (AES in our code) to use for encryption at rest
|
|
* @param keyEncCipher Initialized cipher
|
|
* @param data Destination for marshaled key
|
|
* @return Bytes written or -1 on error
|
|
*/
|
|
template<typename MC>
|
|
ZT_INLINE int marshal(const MC &keyEncCipher,uint8_t data[ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX]) const noexcept
|
|
{
|
|
Utils::storeBigEndian<uint64_t>(data,(uint64_t)m_ts);
|
|
Utils::storeBigEndian<uint64_t>(data + 8, m_odometer.load());
|
|
Utils::storeBigEndian<uint32_t>(data + 16,Utils::fnv1a32(m_secret, sizeof(m_secret)));
|
|
|
|
// Key encryption at rest is CBC using the last 32 bits of the timestamp, the odometer,
|
|
// and the FNV1a checksum as a 128-bit IV. A duplicate IV wouldn't matter much anyway since
|
|
// keys should be unique. Simple ECB would be fine as they also have no structure, but this
|
|
// looks better.
|
|
uint8_t tmp[16];
|
|
for(int i=0;i<16;++i)
|
|
tmp[i] = data[i + 4] ^ m_secret[i];
|
|
keyEncCipher.encrypt(tmp,data + 20);
|
|
for(int i=0;i<16;++i)
|
|
tmp[i] = data[i + 20] ^ m_secret[i + 16];
|
|
keyEncCipher.encrypt(tmp,data + 36);
|
|
|
|
return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX;
|
|
}
|
|
|
|
/**
|
|
* Unmarshal, decrypt, and verify key checksum
|
|
*
|
|
* Key checksum verification failure results in the SymmetricKey being zeroed out to its
|
|
* nil value, but the bytes read are still returned. The caller must check this if it
|
|
* requires the key to be present and verified.
|
|
*
|
|
* @tparam MC Cipher type (AES in our code) to use for encryption at rest
|
|
* @param keyDecCipher Initialized cipher for decryption
|
|
* @param data Source to read
|
|
* @param len Bytes remaining at source
|
|
* @return Bytes read from source
|
|
*/
|
|
template<typename MC>
|
|
ZT_INLINE int unmarshal(const MC &keyDecCipher,const uint8_t *restrict data,int len) noexcept
|
|
{
|
|
if (len < ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX)
|
|
return -1;
|
|
|
|
m_ts = (int64_t)Utils::loadBigEndian<uint64_t>(data);
|
|
m_odometer = (uint64_t)Utils::loadBigEndian<uint64_t>(data + 8);
|
|
const uint32_t fnv = Utils::loadBigEndian<uint32_t>(data + 16); // NOLINT(hicpp-use-auto,modernize-use-auto)
|
|
|
|
uint8_t tmp[16];
|
|
keyDecCipher.decrypt(data + 20,tmp);
|
|
for(int i=0;i<16;++i)
|
|
m_secret[i] = data[i + 4] ^ tmp[i];
|
|
keyDecCipher.decrypt(data + 36,tmp);
|
|
for(int i=0;i<16;++i)
|
|
m_secret[i + 16] = data[i + 20] ^ tmp[i];
|
|
|
|
if (Utils::fnv1a32(m_secret, sizeof(m_secret)) != fnv)
|
|
clear();
|
|
|
|
return ZT_SYMMETRICKEY_MARSHAL_SIZE_MAX;
|
|
}
|
|
|
|
private:
|
|
int64_t m_ts;
|
|
uint64_t m_nonceBase;
|
|
std::atomic<uint64_t> m_odometer;
|
|
uint8_t m_secret[ZT_SYMMETRIC_KEY_SIZE];
|
|
};
|
|
|
|
} // namespace ZeroTier
|
|
|
|
#endif
|