Implement AES-GMAC-SIV and benchmark, rework COM and add a lot of comments and docs, and comments and docs elsewhere too.

This commit is contained in:
Adam Ierymenko 2020-03-25 09:05:44 -07:00
parent f3540a5c7c
commit d3a7468e83
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
12 changed files with 452 additions and 267 deletions

View file

@ -118,11 +118,15 @@ public:
_decryptSW(reinterpret_cast<const uint8_t *>(in),reinterpret_cast<uint8_t *>(out));
}
class GMACSIVEncryptor;
/**
* Streaming GMAC calculator
*/
class GMAC
{
friend class GMACSIVEncryptor;
public:
/**
* Create a new instance of GMAC (must be initialized with init() before use)
@ -190,6 +194,8 @@ public:
*/
class CTR
{
friend class GMACSIVEncryptor;
public:
ZT_INLINE CTR(const AES &aes) noexcept : _aes(aes) {}
@ -229,6 +235,76 @@ public:
unsigned int _len;
};
/**
* Encrypt with AES-GMAC-SIV
*/
class GMACSIVEncryptor
{
public:
/**
* Create a new AES-GMAC-SIV encryptor keyed with the provided AES instances
*
* @param k0 First of two AES instances keyed with K0
* @param k1 Second of two AES instances keyed with K1
*/
ZT_INLINE GMACSIVEncryptor(const AES &k0,const AES &k1) noexcept :
_gmac(k0),
_ctr(k1) {}
/*
* Initialize AES-GMAC-SIV
*
* @param iv IV in network byte order (byte order in which it will appear on the wire)
* @param output Pointer to buffer to receive ciphertext, must be large enough for all to-be-processed data!
*/
ZT_INLINE void init(const uint64_t iv,void *const output) noexcept
{
_output = output;
_iv[0] = iv;
_iv[1] = 0;
_gmac.init(reinterpret_cast<const uint8_t *>(_iv));
}
ZT_INLINE void update1(const void *const input,const unsigned int len) noexcept
{
_gmac.update(input,len);
}
/**
* Finish first pass, compute CTR IV, initialize second pass.
*/
ZT_INLINE void finish1() noexcept
{
uint64_t gmacTag[2];
_gmac.finish(reinterpret_cast<uint8_t *>(gmacTag));
_iv[1] = gmacTag[0];
_ctr._aes.encrypt(_iv,_iv);
_ctr.init(reinterpret_cast<const uint8_t *>(_iv),_output);
}
ZT_INLINE void update2(const void *const input,const unsigned int len) noexcept
{
_ctr.crypt(input,len);
}
/**
* Finish second pass and return a pointer to the opaque 128-bit IV+MAC block
*
* @return Pointer to 128-bit opaque IV+MAC
*/
ZT_INLINE const uint8_t *finish2()
{
_ctr.finish();
return reinterpret_cast<const uint8_t *>(_iv);
}
private:
void *_output;
uint64_t _iv[2];
AES::GMAC _gmac;
AES::CTR _ctr;
};
private:
static const uint32_t Te0[256];
static const uint32_t Te1[256];

View file

@ -15,167 +15,267 @@
namespace ZeroTier {
CertificateOfMembership::CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo)
CertificateOfMembership::CertificateOfMembership(const int64_t timestamp,const int64_t timestampMaxDelta,const uint64_t nwid,const Identity &issuedTo) noexcept :
_timestamp(timestamp),
_timestampMaxDelta(timestampMaxDelta),
_networkId(nwid),
_issuedTo(issuedTo.fingerprint()),
_signatureLength(0) {}
bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const noexcept
{
_qualifiers[COM_RESERVED_ID_TIMESTAMP].id = COM_RESERVED_ID_TIMESTAMP;
_qualifiers[COM_RESERVED_ID_TIMESTAMP].value = timestamp;
_qualifiers[COM_RESERVED_ID_TIMESTAMP].maxDelta = timestampMaxDelta;
_qualifiers[COM_RESERVED_ID_NETWORK_ID].id = COM_RESERVED_ID_NETWORK_ID;
_qualifiers[COM_RESERVED_ID_NETWORK_ID].value = nwid;
_qualifiers[COM_RESERVED_ID_NETWORK_ID].maxDelta = 0;
_qualifiers[COM_RESERVED_ID_ISSUED_TO].id = COM_RESERVED_ID_ISSUED_TO;
_qualifiers[COM_RESERVED_ID_ISSUED_TO].value = issuedTo.toInt();
_qualifiers[COM_RESERVED_ID_ISSUED_TO].maxDelta = 0xffffffffffffffffULL;
_qualifierCount = 3;
_signatureLength = 0;
}
// NOTE: we always do explicit absolute value with an if() since llabs() can have overflow
// conditions that could introduce a vulnerability.
void CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta)
{
_signedBy.zero();
for(unsigned int i=0;i<_qualifierCount;++i) {
if (_qualifiers[i].id == id) {
_qualifiers[i].value = value;
_qualifiers[i].maxDelta = maxDelta;
return;
}
}
if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) {
_qualifiers[_qualifierCount].id = id;
_qualifiers[_qualifierCount].value = value;
_qualifiers[_qualifierCount].maxDelta = maxDelta;
++_qualifierCount;
std::sort(&(_qualifiers[0]),&(_qualifiers[_qualifierCount]));
}
}
bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const
{
unsigned int myidx = 0;
unsigned int otheridx = 0;
if ((_qualifierCount == 0)||(other._qualifierCount == 0))
return false;
while (myidx < _qualifierCount) {
// Fail if we're at the end of other, since this means the field is
// missing.
if (otheridx >= other._qualifierCount)
if (other._timestamp > _timestamp) {
if ((other._timestamp - _timestamp) > _timestampMaxDelta)
return false;
} else {
if ((_timestamp - other._timestamp) > _timestampMaxDelta)
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._qualifierCount)
// us <> them
for(FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(_additionalQualifiers.begin());i != _additionalQualifiers.end();++i) {
if (i->delta != 0xffffffffffffffffULL) {
const uint64_t *v2 = nullptr;
for(FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(other._additionalQualifiers.begin());j != other._additionalQualifiers.end();++i) {
if (j->id == i->id) {
v2 = &(j->value);
break;
}
}
if (!v2)
return false;
if (*v2 > i->value) {
if ((*v2 - i->value) > i->delta)
return false;
} else {
if ((i->value - *v2) > i->delta)
return false;
}
}
// Compare to determine if the absolute value of the difference
// between these two parameters is within our maxDelta.
const uint64_t a = _qualifiers[myidx].value;
const uint64_t b = other._qualifiers[myidx].value;
if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[myidx].maxDelta)
return false;
++myidx;
}
return true;
// them <> us
for(FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(other._additionalQualifiers.begin());i != other._additionalQualifiers.end();++i) {
if (i->delta != 0xffffffffffffffffULL) {
const uint64_t *v2 = nullptr;
for(FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(_additionalQualifiers.begin());j != _additionalQualifiers.end();++i) {
if (j->id == i->id) {
v2 = &(j->value);
break;
}
}
if (!v2)
return false;
if (*v2 > i->value) {
if ((*v2 - i->value) > i->delta)
return false;
} else {
if ((i->value - *v2) > i->delta)
return false;
}
}
}
// SECURITY: check for issued-to inequality is a sanity check. This should be impossible since elsewhere
// in the code COMs are checked to ensure that they do in fact belong to their issued-to identities.
return (other._networkId != _networkId) && (other._issuedTo.address() != _issuedTo.address());
}
bool CertificateOfMembership::sign(const Identity &with)
bool CertificateOfMembership::sign(const Identity &with) noexcept
{
uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
unsigned int ptr = 0;
for(unsigned int i=0;i<_qualifierCount;++i) {
buf[ptr++] = Utils::hton(_qualifiers[i].id);
buf[ptr++] = Utils::hton(_qualifiers[i].value);
buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
}
try {
_signatureLength = with.sign(buf,ptr * sizeof(uint64_t),_signature,sizeof(_signature));
_signedBy = with.address();
return true;
} catch ( ... ) {
_signedBy.zero();
return false;
}
_signedBy = with.address();
uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8];
const unsigned int bufSize = _fillSigningBuf(buf);
_signatureLength = with.sign(buf,bufSize,_signature,sizeof(_signature));
return _signatureLength > 0;
}
int CertificateOfMembership::marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX]) const noexcept
int CertificateOfMembership::marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX],const bool v2) const noexcept
{
data[0] = 1; // 96-byte signature length; a v2 is supported in unmarshal code where signature is length prefixed
Utils::storeBigEndian<uint16_t>(data + 1,(uint16_t)_qualifierCount);
data[0] = v2 ? 2 : 1;
// All formats start with the standard three qualifiers: timestamp with delta, network ID as a strict
// equality compare, and the address of the issued-to node as an informational tuple.
int p = 3;
for(unsigned int i=0;i<_qualifierCount;++i) {
Utils::storeBigEndian<uint64_t>(data + p,_qualifiers[i].id); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,_qualifiers[i].value); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,_qualifiers[i].maxDelta); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,0); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,(uint64_t)_timestamp); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,(uint64_t)_timestampMaxDelta); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,1); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,_networkId); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,0); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,2); p += 8;
Utils::storeBigEndian<uint64_t>(data + p,_issuedTo.address().toInt()); p += 8;
Utils::storeAsIsEndian<uint64_t>(data + p,0xffffffffffffffffULL); p += 8;
if (v2) {
// V2 marshal format will have three tuples followed by the fingerprint hash.
Utils::storeBigEndian<uint16_t>(data + 1,3);
memcpy(data + p,_issuedTo.hash(),48);
p += 48;
} else {
// V1 marshal format must shove everything into tuples, resulting in nine.
Utils::storeBigEndian<uint16_t>(data + 1,9);
for(int k=0;k<6;++k) {
Utils::storeBigEndian<uint64_t>(data + p,(uint64_t)k + 3); p += 8;
Utils::storeAsIsEndian<uint64_t>(data + p,Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + (k * 8))); p += 8;
Utils::storeAsIsEndian<uint64_t>(data + p,0xffffffffffffffffULL); p += 8;
}
}
_signedBy.copyTo(data + p); p += ZT_ADDRESS_LENGTH;
if ((_signedBy)&&(_signatureLength == 96)) {
memcpy(data + p,_signature,96); p += 96;
_signedBy.copyTo(data + p); p += 5;
if (v2) {
// V2 marshal format prefixes signatures with a 16-bit length to support future signature types.
Utils::storeBigEndian<uint16_t>(data + p,(uint16_t)_signatureLength); p += 2;
memcpy(data + p,_signature,_signatureLength);
p += (int)_signatureLength;
} else {
// V1 only supports 96-byte signature fields.
memcpy(data + p,_signature,96);
p += 96;
}
return p;
}
int CertificateOfMembership::unmarshal(const uint8_t *data,int len) noexcept
{
if ((len < 3)||(data[0] == 0))
if (len < (1 + 2 + 72))
return -1;
unsigned int numq = Utils::loadBigEndian<uint16_t>(data + 1);
if (numq > ZT_NETWORK_COM_MAX_QUALIFIERS)
TriviallyCopyable::memoryZero(this);
const unsigned int numq = Utils::loadBigEndian<uint16_t>(data + 1);
if ((numq < 3)||(numq > (ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS + 3)))
return -1;
_qualifierCount = numq;
int p = 3;
for(unsigned int i=0;i<numq;++i) {
for(unsigned int q=0;q<numq;++q) {
if ((p + 24) > len)
return -1;
_qualifiers[i].id = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
_qualifiers[i].value = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
_qualifiers[i].maxDelta = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
}
if ((p + ZT_ADDRESS_LENGTH) > len)
return -1;
_signedBy.setTo(data + p); p += ZT_ADDRESS_LENGTH;
if (_signedBy) {
_signatureLength = 96;
if (data[0] > 1) {
// If the version byte is >1, signatures come prefixed by a length. This is the
// way it should have been in the first place. Version byte 1 indicates 96 byte
// signatures and is backward compatible with <2.x nodes.
if ((p + 2) >= len)
return -1;
_signatureLength = Utils::loadBigEndian<uint16_t>(data + p); p += 2;
if (_signatureLength == 0)
return -1;
const uint64_t id = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
const uint64_t value = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
const uint64_t delta = Utils::loadBigEndian<uint64_t>(data + p); p += 8;
switch(id) {
case 0:
_timestamp = (int64_t)value;
_timestampMaxDelta = (int64_t)delta;
break;
case 1:
_networkId = value;
break;
case 2:
_issuedTo.apiFingerprint()->address = value;
break;
// V1 nodes will pack the hash into qualifier tuples.
case 3:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash,value);
break;
case 4:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash + 8,value);
break;
case 5:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash + 16,value);
break;
case 6:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash + 24,value);
break;
case 7:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash + 32,value);
break;
case 8:
Utils::storeBigEndian<uint64_t>(_issuedTo.apiFingerprint()->hash + 40,value);
break;
default:
if (_additionalQualifiers.size() == _additionalQualifiers.capacity())
return -1;
_additionalQualifiers.push_back(_Qualifier(id,value,delta));
break;
}
if ((int)(p + _signatureLength) > len)
return -1;
memcpy(_signature,data + p,96);
p += 96;
}
return p;
std::sort(_additionalQualifiers.begin(),_additionalQualifiers.end());
if (data[0] == 1) {
if ((p + 96) > len)
return -1;
_signatureLength = 96;
memcpy(_signature,data + p,96);
return p + 96;
} else if (data[0] == 2) {
if ((p + 48) > len)
return -1;
memcpy(_issuedTo.apiFingerprint()->hash,data + p,48);
p += 48;
if ((p + 2) > len)
return -1;
_signatureLength = Utils::loadBigEndian<uint16_t>(data + p);
if ((_signatureLength > sizeof(_signature))||((p + _signatureLength) > len))
return -1;
memcpy(_signature,data + p,_signatureLength);
return p + (int)_signatureLength;
}
return -1;
}
bool CertificateOfMembership::operator==(const CertificateOfMembership &c) const
unsigned int CertificateOfMembership::_fillSigningBuf(uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8]) const noexcept
{
if (_signedBy != c._signedBy)
return false;
if (_qualifierCount != c._qualifierCount)
return false;
if (_signatureLength != c._signatureLength)
return false;
for(unsigned int i=0;i<_qualifierCount;++i) {
const _Qualifier &a = _qualifiers[i];
const _Qualifier &b = c._qualifiers[i];
if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta))
return false;
const uint64_t informational = 0xffffffffffffffffULL;
/*
* Signing always embeds all data to be signed in qualifier tuple format for
* backward compatibility with V1 nodes, since otherwise we'd need a signature
* for v1 nodes to verify and another for v2 nodes to verify.
*/
// The standard three tuples that must begin every COM.
buf[0] = 0;
buf[1] = Utils::hton((uint64_t)_timestamp);
buf[2] = Utils::hton((uint64_t)_timestampMaxDelta);
buf[3] = ZT_CONST_TO_BE_UINT64(1);
buf[4] = Utils::hton(_networkId);
buf[5] = 0;
buf[6] = ZT_CONST_TO_BE_UINT64(2);
buf[7] = Utils::hton(_issuedTo.address().toInt());
buf[8] = informational;
unsigned int p = 9;
// The full identity fingerprint of the peer to whom the COM was issued,
// embeded as a series of informational tuples.
if (_issuedTo.haveHash()) {
buf[p++] = ZT_CONST_TO_BE_UINT64(3);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash());
buf[p++] = informational;
buf[p++] = ZT_CONST_TO_BE_UINT64(4);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + 8);
buf[p++] = informational;
buf[p++] = ZT_CONST_TO_BE_UINT64(5);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + 16);
buf[p++] = informational;
buf[p++] = ZT_CONST_TO_BE_UINT64(6);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + 24);
buf[p++] = informational;
buf[p++] = ZT_CONST_TO_BE_UINT64(7);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + 32);
buf[p++] = informational;
buf[p++] = ZT_CONST_TO_BE_UINT64(8);
buf[p++] = Utils::loadAsIsEndian<uint64_t>(_issuedTo.hash() + 40);
buf[p++] = informational;
}
return (memcmp(_signature,c._signature,_signatureLength) == 0);
for(FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(_additionalQualifiers.begin());i != _additionalQualifiers.end();++i) {
buf[p++] = Utils::hton(i->id);
buf[p++] = Utils::hton(i->value);
buf[p++] = Utils::hton(i->delta);
}
return p * 8;
}
} // namespace ZeroTier

View file

@ -14,6 +14,8 @@
#ifndef ZT_CERTIFICATEOFMEMBERSHIP_HPP
#define ZT_CERTIFICATEOFMEMBERSHIP_HPP
// TODO: redo
#include <cstdint>
#include <cstring>
@ -27,13 +29,13 @@
#include "C25519.hpp"
#include "Identity.hpp"
#include "Utils.hpp"
#include "FCV.hpp"
/**
* Maximum number of qualifiers allowed in a COM (absolute max: 65535)
*/
#define ZT_NETWORK_COM_MAX_QUALIFIERS 8
// Maximum number of additional tuples beyond the standard always-present three.
#define ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS 8
#define ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX (1 + 2 + (24 * ZT_NETWORK_COM_MAX_QUALIFIERS) + 5 + ZT_SIGNATURE_BUFFER_SIZE)
// version + qualifier count + three required qualifiers + additional qualifiers +
#define ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX (1 + 2 + (3 * 3 * 8) + (ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS * 3 * 8) + 144 + 5 + 2 + 96)
namespace ZeroTier {
@ -42,28 +44,62 @@ class RuntimeEnvironment;
/**
* 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.
* This is the fundamental permission object issued by network controllers to members of networks
* to admit them into networks.
*
* The ID is arbitrary and should be assigned using a scheme that makes
* every ID globally unique. IDs beneath 65536 are reserved for global
* assignment by ZeroTier Networks.
* A certificate of membership (COM) consists of a series of tuples called qualifiers as well
* as the full identity fingerprint of the node being admitted, the address of the controller
* (for sanity checking), and a signature.
*
* 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.
* A qualifier is a tuple of three 64-bit unsigned integers: an id, a value, and a delta.
*
* 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.
* Certiciates are checked between peers by determining if they agree. If the absolute value
* of the difference between any two qualifier values exceeds its delta, the certificates do
* not agree. A delta if 1 for example means that the values of two peers may differ by no more
* than one. A delta of 0 indicates values that must be the same. A delta of uint64_max is for
* informational tuples that are not included in certificate checking, as this means they may
* differ by any amount.
*
* This is a memcpy()'able structure and is safe (in a crash sense) to modify
* without locks.
* All COMs contain three initial tuples: timestamp, network ID, and the address of the
* issued-to node. The latter is informational. The network ID must equal exactly, though in
* theory a controller could allow a delta there to e.g. allow cross-communication between all
* of its networks. (This has never been done in practice.) The most important field is the
* timestamp, whose delta defines a moving window within which certificates must be timestamped
* by the network controller to agree. A certificate that is too old will fall out of this
* window vs its peers and will no longer be considered valid.
*
* (Revocations are a method to rapidly revoke access that works alongside this slower but
* more definitive method.)
*
* Certificate of membership wire format:
*
* This wire format comes in two versions: version 1 for ZeroTier 1.x, which will
* eventually go away once 1.x is out of support, and version 2 for ZeroTier 2.x and later.
*
* Version 2:
*
* <[1] wire format type byte: 1 or 2>
* <[2] 16-bit number of qualifier tuples>
* <[...] qualifier tuples>
* <[48] fingerprint hash of identity of peer to whom COM was issued>
* <[5] address of network controller>
* <[2] 16-bit size of signature>
* <[...] signature>
*
* Version 1 is identical except the fingerprint hash is omitted and is instead loaded
* into a series of six informational tuples. The signature size is also omitted and a
* 96-byte signature field is assumed.
*
* Qualifier tuples must appear in numeric order of ID, and the first three tuples
* must have IDs 0, 1, and 2 being the timestamp, network ID, and issued-to address
* respectively. In version 1 COMs the IDs 3-8 are used to pack in the full identity
* fingerprint, so these are reserved as well. Optional additional tuples (not currently
* used) must use ID 65536 or higher.
*
* Signatures are computed over tuples only for backward compatibility with v1, and we
* don't plan to change this. Tuples are emitted into a buffer in ascending numeric
* order with the fingerprint hash being packed into tuple IDs 3-8 and this buffer is
* then signed.
*/
class CertificateOfMembership : public Credential
{
@ -72,33 +108,6 @@ class CertificateOfMembership : public Credential
public:
static constexpr ZT_CredentialType credentialType() noexcept { return ZT_CREDENTIAL_TYPE_COM; }
/**
* Reserved qualifier IDs
*
* IDs below 1024 are reserved for use as standard IDs. Others are available
* for user-defined use.
*
* Addition of new required fields requires that code in hasRequiredFields
* be updated as well.
*/
enum ReservedId
{
/**
* Timestamp of certificate
*/
COM_RESERVED_ID_TIMESTAMP = 0,
/**
* Network ID for which certificate was issued
*/
COM_RESERVED_ID_NETWORK_ID = 1,
/**
* ZeroTier address to whom certificate was issued
*/
COM_RESERVED_ID_ISSUED_TO = 2
};
/**
* Create an empty certificate of membership
*/
@ -112,12 +121,12 @@ public:
* @param nwid Network ID
* @param issuedTo Certificate recipient
*/
CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo);
CertificateOfMembership(int64_t timestamp,int64_t timestampMaxDelta,uint64_t nwid,const Identity &issuedTo) noexcept;
/**
* @return True if there's something here
*/
ZT_INLINE operator bool() const noexcept { return (_qualifierCount != 0); }
ZT_INLINE operator bool() const noexcept { return (_networkId != 0); }
/**
* @return Credential ID, always 0 for COMs
@ -127,57 +136,17 @@ public:
/**
* @return Timestamp for this cert and maximum delta for timestamp
*/
ZT_INLINE int64_t timestamp() const noexcept
{
if (_qualifiers[COM_RESERVED_ID_TIMESTAMP].id == COM_RESERVED_ID_TIMESTAMP)
return (int64_t)_qualifiers[0].value;
for(unsigned int i=0;i<_qualifierCount;++i) {
if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP)
return (int64_t)_qualifiers[i].value;
}
return 0;
}
ZT_INLINE int64_t timestamp() const noexcept { return _timestamp; }
/**
* @return Address to which this cert was issued
* @return Fingerprint of identity to which this cert was issued
*/
ZT_INLINE Address issuedTo() const noexcept
{
if (_qualifiers[COM_RESERVED_ID_ISSUED_TO].id == COM_RESERVED_ID_ISSUED_TO)
return Address(_qualifiers[2].value);
for(unsigned int i=0;i<_qualifierCount;++i) {
if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO)
return Address(_qualifiers[i].value);
}
return Address();
}
ZT_INLINE const Fingerprint &issuedTo() const noexcept { return _issuedTo; }
/**
* @return Network ID for which this cert was issued
*/
ZT_INLINE uint64_t networkId() const noexcept
{
if (_qualifiers[COM_RESERVED_ID_NETWORK_ID].id == COM_RESERVED_ID_NETWORK_ID)
return _qualifiers[COM_RESERVED_ID_NETWORK_ID].value;
for(unsigned int i=0;i<_qualifierCount;++i) {
if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID)
return _qualifiers[i].value;
}
return 0ULL;
}
/**
* 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);
ZT_INLINE void setQualifier(ReservedId id,uint64_t value,uint64_t maxDelta) { setQualifier((uint64_t)id,value,maxDelta); }
ZT_INLINE uint64_t networkId() const noexcept { return _networkId; }
/**
* Compare two certificates for parameter agreement
@ -192,7 +161,7 @@ public:
* @param other Cert to compare with
* @return True if certs agree and 'other' may be communicated with
*/
bool agreesWith(const CertificateOfMembership &other) const;
bool agreesWith(const CertificateOfMembership &other) const noexcept;
/**
* Sign this certificate
@ -200,7 +169,7 @@ public:
* @param with Identity to sign with, must include private key
* @return True if signature was successful
*/
bool sign(const Identity &with);
bool sign(const Identity &with) noexcept;
/**
* Verify this COM and its signature
@ -210,31 +179,29 @@ public:
*/
ZT_INLINE Credential::VerifyResult verify(const RuntimeEnvironment *RR,void *tPtr) const { return _verify(RR,tPtr,*this); }
/**
* @return Address that signed this certificate or null address if none
*/
ZT_INLINE const Address &signedBy() const noexcept { return _signedBy; }
static constexpr int marshalSizeMax() noexcept { return ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX; }
int marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX]) const noexcept;
int marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX],bool v2) const noexcept;
int unmarshal(const uint8_t *data,int len) noexcept;
bool operator==(const CertificateOfMembership &c) const;
ZT_INLINE bool operator!=(const CertificateOfMembership &c) const { return (!(*this == c)); }
private:
unsigned int _fillSigningBuf(uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8]) const noexcept;
struct _Qualifier
{
ZT_INLINE _Qualifier() noexcept : id(0),value(0),maxDelta(0) {}
ZT_INLINE _Qualifier() noexcept : id(0),value(0),delta(0) {}
ZT_INLINE _Qualifier(const uint64_t id_,const uint64_t value_,const uint64_t delta_) noexcept : id(id_),value(value_),delta(delta_) {}
uint64_t id;
uint64_t value;
uint64_t maxDelta;
uint64_t delta;
ZT_INLINE bool operator<(const _Qualifier &q) const noexcept { return (id < q.id); } // sort order
};
FCV<_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS> _additionalQualifiers;
int64_t _timestamp;
int64_t _timestampMaxDelta;
uint64_t _networkId;
Fingerprint _issuedTo;
Address _signedBy;
_Qualifier _qualifiers[ZT_NETWORK_COM_MAX_QUALIFIERS];
unsigned int _qualifierCount;
unsigned int _signatureLength;
uint8_t _signature[ZT_SIGNATURE_BUFFER_SIZE];
};

View file

@ -72,22 +72,19 @@ Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *const RR,
Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *const RR,void *tPtr,const CertificateOfMembership &credential) const
{
if ((!credential._signedBy)||(credential._signedBy != Network::controllerFor(credential.networkId()))||(credential._qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS))
// Sanity check network ID.
if ((!credential._signedBy)||(credential._signedBy != Network::controllerFor(credential._networkId)))
return Credential::VERIFY_BAD_SIGNATURE;
// If we don't know the peer, get its identity. This shouldn't happen here but should be handled.
const SharedPtr<Peer> peer(RR->topology->peer(tPtr,credential._signedBy));
if (!peer)
return Credential::VERIFY_NEED_IDENTITY;
uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
unsigned int ptr = 0;
for(unsigned int i=0;i<credential._qualifierCount;++i) {
buf[ptr++] = Utils::hton(credential._qualifiers[i].id);
buf[ptr++] = Utils::hton(credential._qualifiers[i].value);
buf[ptr++] = Utils::hton(credential._qualifiers[i].maxDelta);
}
return (peer->identity().verify(buf,ptr * sizeof(uint64_t),credential._signature,credential._signatureLength) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE);
// Now verify the controller's signature.
uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8];
const unsigned int bufSize = credential._fillSigningBuf(buf);
return peer->identity().verify(buf,bufSize,credential._signature,credential._signatureLength) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE;
}
Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *RR,void *tPtr,const Capability &credential) const

View file

@ -36,6 +36,11 @@ class RuntimeEnvironment;
/**
* Base class for credentials
*
* Note that all credentials are and must be trivially copyable.
*
* All credential verification methods are implemented in Credential.cpp as they share a lot
* of common code and logic and grouping them makes auditing easier.
*/
class Credential : public TriviallyCopyable
{

View file

@ -145,7 +145,7 @@ public:
const unsigned int fragmentNo,
const unsigned int totalFragmentsExpected,
const int64_t now,
const SharedPtr< Path > &via,
const SharedPtr<Path> &via,
const unsigned int maxIncomingFragmentsPerPath)
{
// Sanity checks for malformed fragments or invalid input parameters.

View file

@ -33,6 +33,9 @@ namespace ZeroTier {
* This doesn't implement everything in std::vector, just what we need. It
* also adds a few special things for use in ZT core code.
*
* Note that an FCV will be TriviallyCopyable IF and only if its contained
* type is TriviallyCopyable. There's a const static checker for this.
*
* @tparam T Type to contain
* @tparam C Maximum capacity of vector
*/
@ -43,6 +46,11 @@ public:
typedef T * iterator;
typedef const T * const_iterator;
/**
* @return True if this FCV is trivially copyable, which means its type is also.
*/
static constexpr bool isTriviallyCopyable() noexcept { return isTriviallyCopyable(reinterpret_cast<const T *>(0)); }
ZT_INLINE FCV() noexcept : _s(0) {}
ZT_INLINE FCV(const FCV &v) : _s(0) { *this = v; }

View file

@ -45,19 +45,14 @@ public:
ZT_INLINE Address address() const noexcept { return Address(_fp.address); }
ZT_INLINE const uint8_t *hash() const noexcept { return _fp.hash; }
/**
* Copy into ZT_Fingerprint struct as used in API and trace messages
*
* @param fp ZT_Fingerprint
*/
ZT_INLINE void getAPIFingerprint(ZT_Fingerprint *fp) const noexcept { memcpy(fp,&_fp,sizeof(ZT_Fingerprint)); }
/**
* @return Pointer to ZT_Fingerprint for API use
*/
ZT_INLINE ZT_Fingerprint *apiFingerprint() noexcept { return &_fp; }
ZT_INLINE const ZT_Fingerprint *apiFingerprint() const noexcept { return &_fp; }
/**
* @return True if hash is not all zero (missing/unspecified)
*/
ZT_INLINE bool haveHash() const noexcept { return (!Utils::allZero(_fp.hash,sizeof(_fp.hash))); }
/**
* Get a base32-encoded representation of this fingerprint
*

View file

@ -23,6 +23,8 @@
#include "Address.hpp"
#include "Identity.hpp"
// TODO: mlock
/*
* Core ZeroTier protocol packet formats ------------------------------------------------------------------------------
*

View file

@ -1039,6 +1039,24 @@ extern "C" const char *ZTT_benchmarkCrypto()
ZT_T_PRINTF("%.4f MiB/sec" ZT_EOL_S,((16384.0 * 350000.0) / 1048576.0) / ((double)(end - start) / 1000.0));
}
{
ZT_T_PRINTF("[crypto] Benchmarking AES-GMAC-SIV... ");
AES k0(AES_CTR_TEST_VECTOR_0_KEY);
AES k1(AES_GMAC_VECTOR_0_KEY);
AES::GMACSIVEncryptor enc(k0,k1);
int64_t start = now();
for(long i=0;i<350000;++i) {
enc.init((uint64_t)i,tmp);
enc.update1(tmp,sizeof(tmp));
enc.finish1();
enc.update2(tmp,sizeof(tmp));
enc.finish2();
}
int64_t end = now();
foo = tmp[0]; // prevent optimization
ZT_T_PRINTF("%.4f MiB/sec" ZT_EOL_S,((16384.0 * 350000.0) / 1048576.0) / ((double)(end - start) / 1000.0));
}
{
ZT_T_PRINTF("[crypto] Benchmarking Poly1305... ");
int64_t start = now();

View file

@ -166,6 +166,12 @@ ZT_PACKED_STRUCT(struct TriviallyCopyable
}
});
static constexpr bool isTriviallyCopyable(const TriviallyCopyable *const anything) noexcept { return true; }
static constexpr bool isTriviallyCopyable(const void *const anything) noexcept { return false; }
template<typename T>
static constexpr bool isTriviallyCopyable(const T &anything) noexcept { return isTriviallyCopyable(&anything); }
} // namespace ZeroTier
#endif

View file

@ -16,16 +16,27 @@
#include "Constants.hpp"
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)((uint16_t)((uint16_t)(x) << 8U) | (uint16_t)((uint16_t)(x) >> 8U)))
#else
#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)(x))
#endif
namespace ZeroTier {
namespace Utils {
// Macros to convert endian-ness at compile time for constants.
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)((uint16_t)((uint16_t)(x) << 8U) | (uint16_t)((uint16_t)(x) >> 8U)))
#define ZT_CONST_TO_BE_UINT64(x) ( \
(((uint64_t)(x) & 0x00000000000000ffULL) << 56U) | \
(((uint64_t)(x) & 0x000000000000ff00ULL) << 40U) | \
(((uint64_t)(x) & 0x0000000000ff0000ULL) << 24U) | \
(((uint64_t)(x) & 0x00000000ff000000ULL) << 8U) | \
(((uint64_t)(x) & 0x000000ff00000000ULL) >> 8U) | \
(((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24U) | \
(((uint64_t)(x) & 0x00ff000000000000ULL) >> 40U) | \
(((uint64_t)(x) & 0xff00000000000000ULL) >> 56U))
#else
#define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)(x))
#define ZT_CONST_TO_BE_UINT64(x) ((uint64_t)(x))
#endif
#ifdef ZT_ARCH_X64
struct CPUIDRegisters
{