mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
366 lines
16 KiB
C++
366 lines
16 KiB
C++
/*
|
|
* Copyright (c)2013-2021 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: 2026-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.
|
|
*/
|
|
/****/
|
|
|
|
#include "TrustStore.hpp"
|
|
|
|
#include "LZ4.hpp"
|
|
|
|
namespace ZeroTier {
|
|
|
|
TrustStore::TrustStore()
|
|
{
|
|
}
|
|
|
|
TrustStore::~TrustStore()
|
|
{
|
|
}
|
|
|
|
SharedPtr<TrustStore::Entry> TrustStore::get(const H384& serial) const
|
|
{
|
|
RWMutex::RLock l(m_lock);
|
|
Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.find(serial));
|
|
return (c != m_bySerial.end()) ? c->second : SharedPtr<TrustStore::Entry>();
|
|
}
|
|
|
|
Map<Identity, SharedPtr<const Locator> > TrustStore::roots()
|
|
{
|
|
RWMutex::RLock l(m_lock);
|
|
Map<Identity, SharedPtr<const Locator> > r;
|
|
|
|
// Iterate using m_bySubjectIdentity to only scan certificates with subject identities.
|
|
// This map also does not contian error or deprecated certificates.
|
|
for (Map<Fingerprint, Vector<SharedPtr<Entry> > >::const_iterator cv(m_bySubjectIdentity.begin()); cv != m_bySubjectIdentity.end(); ++cv) {
|
|
for (Vector<SharedPtr<Entry> >::const_iterator c(cv->second.begin()); c != cv->second.end(); ++c) {
|
|
// A root set cert must be marked for this use and authorized to influence this node's config.
|
|
if ((((*c)->m_certificate.usageFlags & ZT_CERTIFICATE_USAGE_ZEROTIER_ROOT_SET) != 0) && (((*c)->m_localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_CONFIG) != 0)) {
|
|
// Add all identities to the root set, and for each entry in the set make sure we have the latest
|
|
// locator if there's more than one cert with one.
|
|
for (unsigned int j = 0; j < (*c)->certificate().subject.identityCount; ++j) {
|
|
const Identity* const id = reinterpret_cast<const Identity*>((*c)->certificate().subject.identities[j].identity);
|
|
if ((id) && (*id)) { // sanity check
|
|
SharedPtr<const Locator>& existingLoc = r[*id];
|
|
const Locator* const loc = reinterpret_cast<const Locator*>((*c)->certificate().subject.identities[j].locator);
|
|
if (loc) {
|
|
if ((! existingLoc) || (existingLoc->revision() < loc->revision()))
|
|
existingLoc.set(new Locator(*loc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
Vector<SharedPtr<TrustStore::Entry> > TrustStore::all(const bool includeRejectedCertificates) const
|
|
{
|
|
RWMutex::RLock l(m_lock);
|
|
Vector<SharedPtr<Entry> > r;
|
|
r.reserve(m_bySerial.size());
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
if ((includeRejectedCertificates) || (c->second->error() == ZT_CERTIFICATE_ERROR_NONE))
|
|
r.push_back(c->second);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void TrustStore::add(const Certificate& cert, const unsigned int localTrust)
|
|
{
|
|
RWMutex::Lock l(m_lock);
|
|
m_addQueue.push_front(SharedPtr<Entry>(new Entry(this->m_lock, cert, localTrust)));
|
|
}
|
|
|
|
void TrustStore::erase(const H384& serial)
|
|
{
|
|
RWMutex::Lock l(m_lock);
|
|
m_deleteQueue.push_front(serial);
|
|
}
|
|
|
|
bool TrustStore::update(const int64_t clock, Vector<SharedPtr<Entry> >* const purge)
|
|
{
|
|
RWMutex::Lock l(m_lock);
|
|
|
|
// Check for certificate time validity status changes. If any of these occur then
|
|
// full re-validation is required.
|
|
bool errorStateModified = false;
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
const bool timeValid = c->second->m_certificate.verifyTimeWindow(clock);
|
|
switch (c->second->m_error) {
|
|
case ZT_CERTIFICATE_ERROR_NONE:
|
|
case ZT_CERTIFICATE_ERROR_INVALID_CHAIN:
|
|
if (! timeValid) {
|
|
c->second->m_error = ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW;
|
|
errorStateModified = true;
|
|
}
|
|
break;
|
|
case ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW:
|
|
if (timeValid) {
|
|
c->second->m_error = c->second->m_certificate.verify(-1, false);
|
|
errorStateModified = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there were not any such changes and if the add and delete queues are empty,
|
|
// there is nothing more to be done.
|
|
if ((! errorStateModified) && (m_addQueue.empty()) && (m_deleteQueue.empty()))
|
|
return false;
|
|
|
|
// Add new certificates to m_bySerial, which is the master certificate set. They still
|
|
// have yet to have their full certificate chains validated. Full signature checking is
|
|
// performed here.
|
|
while (! m_addQueue.empty()) {
|
|
SharedPtr<Entry>& qi = m_addQueue.front();
|
|
qi->m_error = qi->m_certificate.verify(clock, true);
|
|
m_bySerial[H384(qi->m_certificate.serialNo)].move(qi);
|
|
m_addQueue.pop_front();
|
|
}
|
|
|
|
// Delete any certificates enqueued to be deleted.
|
|
while (! m_deleteQueue.empty()) {
|
|
m_bySerial.erase(m_deleteQueue.front());
|
|
m_deleteQueue.pop_front();
|
|
}
|
|
|
|
// Reset flags for deprecation and a cert being on a trust path, which are
|
|
// recomputed when chain and subjects are checked below.
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
if (c->second->m_error == ZT_CERTIFICATE_ERROR_NONE) {
|
|
c->second->m_subjectDeprecated = false;
|
|
c->second->m_onTrustPath = false;
|
|
}
|
|
}
|
|
|
|
// Validate certificate trust paths.
|
|
{
|
|
Vector<Entry*> visited;
|
|
visited.reserve(8);
|
|
for (Map<H384, SharedPtr<Entry> >::iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
if (c->second->m_error == ZT_CERTIFICATE_ERROR_NONE) {
|
|
if (c->second->m_certificate.isSelfSigned()) {
|
|
// If this is a self-signed certificate it's only valid if it's trusted as a CA.
|
|
if ((c->second->m_localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) == 0) {
|
|
c->second->m_error = ZT_CERTIFICATE_ERROR_INVALID_CHAIN;
|
|
}
|
|
}
|
|
else {
|
|
if ((! c->second->m_onTrustPath) && ((c->second->m_localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) == 0)) {
|
|
// Trace the path of each certificate all the way back to a trusted CA.
|
|
unsigned int pathLength = 0;
|
|
Map<H384, SharedPtr<Entry> >::const_iterator current(c);
|
|
visited.clear();
|
|
for (;;) {
|
|
if (pathLength <= current->second->m_certificate.maxPathLength) {
|
|
// Check if this cert isn't a CA or already part of a valid trust path. If so then step upward
|
|
// toward CA.
|
|
if (((current->second->m_localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) == 0) && (! current->second->m_onTrustPath)) {
|
|
// If the issuer (parent) certificiate is (1) valid, (2) not already visited (to prevent
|
|
// loops), and (3) has a public key that matches this cert's issuer public key (sanity
|
|
// check), proceed up the certificate graph toward a potential CA.
|
|
visited.push_back(current->second.ptr());
|
|
const Map<H384, SharedPtr<Entry> >::const_iterator prevChild(current);
|
|
current = m_bySerial.find(H384(current->second->m_certificate.issuer));
|
|
if ((current != m_bySerial.end()) && (std::find(visited.begin(), visited.end(), current->second.ptr()) == visited.end()) && (current->second->m_error == ZT_CERTIFICATE_ERROR_NONE)
|
|
&& (current->second->m_certificate.publicKeySize == prevChild->second->m_certificate.issuerPublicKeySize)
|
|
&& (memcmp(current->second->m_certificate.publicKey, prevChild->second->m_certificate.issuerPublicKey, current->second->m_certificate.publicKeySize) == 0)) {
|
|
++pathLength;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
// If we've traced this to a root CA, flag its parents as also being on a trust path. Then
|
|
// break the loop without setting an error. We don't flag the current cert as being on a
|
|
// trust path since no other certificates depend on it.
|
|
for (Vector<Entry*>::const_iterator v(visited.begin()); v != visited.end(); ++v) {
|
|
if (*v != c->second.ptr())
|
|
(*v)->m_onTrustPath = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we made it here without breaking or continuing, no path to a
|
|
// CA was found and the certificate's chain is invalid.
|
|
c->second->m_error = ZT_CERTIFICATE_ERROR_INVALID_CHAIN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Repopulate mapping of subject unique IDs to their certificates, marking older
|
|
// certificates for the same subject as deprecated. A deprecated certificate is not invalid
|
|
// but will be purged if it is also not part of a trust path. Error certificates are ignored.
|
|
m_bySubjectUniqueId.clear();
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
|
|
if (c->second->m_error == ZT_CERTIFICATE_ERROR_NONE) {
|
|
const unsigned int uniqueIdSize = c->second->m_certificate.subject.uniqueIdSize;
|
|
if ((uniqueIdSize > 0) && (uniqueIdSize <= ZT_CERTIFICATE_MAX_PUBLIC_KEY_SIZE)) {
|
|
SharedPtr<Entry>& entry = m_bySubjectUniqueId[Blob<ZT_CERTIFICATE_MAX_PUBLIC_KEY_SIZE>(c->second->m_certificate.subject.uniqueId, uniqueIdSize)];
|
|
if (entry) {
|
|
// If there's already an entry, see if there's a newer certificate for this subject.
|
|
if (c->second->m_certificate.subject.timestamp > entry->m_certificate.subject.timestamp) {
|
|
entry->m_subjectDeprecated = true;
|
|
entry = c->second;
|
|
}
|
|
else if (c->second->m_certificate.subject.timestamp < entry->m_certificate.subject.timestamp) {
|
|
c->second->m_subjectDeprecated = true;
|
|
}
|
|
else {
|
|
// Equal timestamps should never happen, but handle it anyway by comparing serials.
|
|
if (memcmp(c->second->m_certificate.serialNo, entry->m_certificate.serialNo, ZT_CERTIFICATE_HASH_SIZE) > 0) {
|
|
entry->m_subjectDeprecated = true;
|
|
entry = c->second;
|
|
}
|
|
else {
|
|
c->second->m_subjectDeprecated = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
entry = c->second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Populate mapping of identities to certificates whose subjects reference them, ignoring
|
|
// error or deprecated certificates.
|
|
m_bySubjectIdentity.clear();
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
if ((c->second->m_error == ZT_CERTIFICATE_ERROR_NONE) && (! c->second->m_subjectDeprecated)) {
|
|
for (unsigned int i = 0; i < c->second->m_certificate.subject.identityCount; ++i) {
|
|
const Identity* const id = reinterpret_cast<const Identity*>(c->second->m_certificate.subject.identities[i].identity);
|
|
if ((id) && (*id)) // sanity check
|
|
m_bySubjectIdentity[id->fingerprint()].push_back(c->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If purge is set, erase and return error and deprecated certs (that are not on a trust path).
|
|
if (purge) {
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
|
|
if ((c->second->error() != ZT_CERTIFICATE_ERROR_NONE) || ((c->second->m_subjectDeprecated) && (! c->second->m_onTrustPath))) {
|
|
purge->push_back(c->second);
|
|
m_bySerial.erase(c++);
|
|
}
|
|
else {
|
|
++c;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Vector<uint8_t> TrustStore::save() const
|
|
{
|
|
Vector<uint8_t> comp;
|
|
|
|
int compSize;
|
|
{
|
|
RWMutex::RLock l(m_lock);
|
|
|
|
Vector<uint8_t> b;
|
|
b.reserve(4096);
|
|
|
|
// A version byte.
|
|
b.push_back(0);
|
|
|
|
// <size[2]> <certificate[...]> <trust[2]> tuples terminated by a 0 size.
|
|
for (Map<H384, SharedPtr<Entry> >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
|
|
const Vector<uint8_t> cdata(c->second->certificate().encode());
|
|
const unsigned long size = (uint32_t)cdata.size();
|
|
if ((size > 0) && (size <= 0xffff)) {
|
|
b.push_back((uint8_t)(size >> 8U));
|
|
b.push_back((uint8_t)size);
|
|
b.insert(b.end(), cdata.begin(), cdata.end());
|
|
const uint32_t localTrust = (uint32_t)c->second->localTrust();
|
|
b.push_back((uint8_t)(localTrust >> 8U));
|
|
b.push_back((uint8_t)localTrust);
|
|
}
|
|
}
|
|
b.push_back(0);
|
|
b.push_back(0);
|
|
|
|
comp.resize((unsigned long)LZ4_COMPRESSBOUND(b.size()) + 8);
|
|
compSize = LZ4_compress_fast(reinterpret_cast<const char*>(b.data()), reinterpret_cast<char*>(comp.data() + 8), (int)b.size(), (int)(comp.size() - 8));
|
|
if (unlikely(compSize <= 0)) // shouldn't be possible
|
|
return Vector<uint8_t>();
|
|
|
|
const uint32_t uncompSize = (uint32_t)b.size();
|
|
Utils::storeBigEndian(comp.data(), uncompSize);
|
|
Utils::storeBigEndian(comp.data() + 4, Utils::fnv1a32(b.data(), (unsigned int)uncompSize));
|
|
compSize += 8;
|
|
}
|
|
|
|
comp.resize((unsigned long)compSize);
|
|
comp.shrink_to_fit();
|
|
|
|
return comp;
|
|
}
|
|
|
|
int TrustStore::load(const Vector<uint8_t>& data)
|
|
{
|
|
if (data.size() < 8)
|
|
return -1;
|
|
|
|
const unsigned int uncompSize = Utils::loadBigEndian<uint32_t>(data.data());
|
|
if ((uncompSize == 0) || (uncompSize > (unsigned int)(data.size() * 128)))
|
|
return -1;
|
|
|
|
Vector<uint8_t> uncomp;
|
|
uncomp.resize(uncompSize);
|
|
|
|
if (LZ4_decompress_safe(reinterpret_cast<const char*>(data.data() + 8), reinterpret_cast<char*>(uncomp.data()), (int)(data.size() - 8), (int)uncompSize) != (int)uncompSize)
|
|
return -1;
|
|
const uint8_t* b = uncomp.data();
|
|
if (Utils::fnv1a32(b, (unsigned int)uncompSize) != Utils::loadBigEndian<uint32_t>(data.data() + 4))
|
|
return -1;
|
|
const uint8_t* const eof = b + uncompSize;
|
|
|
|
if (*(b++) != 0) // unrecognized version
|
|
return -1;
|
|
|
|
int readCount = 0;
|
|
|
|
for (;;) {
|
|
if ((b + 2) > eof)
|
|
break;
|
|
const uint32_t certDataSize = Utils::loadBigEndian<uint16_t>(b);
|
|
b += 2;
|
|
|
|
if (certDataSize == 0)
|
|
break;
|
|
|
|
if ((b + certDataSize + 2) > eof) // certificate length + 2 bytes for trust flags
|
|
break;
|
|
Certificate c;
|
|
if (c.decode(b, (unsigned int)certDataSize)) {
|
|
b += certDataSize;
|
|
this->add(c, Utils::loadBigEndian<uint16_t>(b));
|
|
b += 2;
|
|
|
|
++readCount;
|
|
}
|
|
}
|
|
|
|
return readCount;
|
|
}
|
|
|
|
} // namespace ZeroTier
|