/* * 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::get(const H384& serial) const { RWMutex::RLock l(m_lock); Map >::const_iterator c(m_bySerial.find(serial)); return (c != m_bySerial.end()) ? c->second : SharedPtr(); } Map > TrustStore::roots() { RWMutex::RLock l(m_lock); Map > r; // Iterate using m_bySubjectIdentity to only scan certificates with subject identities. // This map also does not contian error or deprecated certificates. for (Map > >::const_iterator cv(m_bySubjectIdentity.begin()); cv != m_bySubjectIdentity.end(); ++cv) { for (Vector >::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((*c)->certificate().subject.identities[j].identity); if ((id) && (*id)) { // sanity check SharedPtr& existingLoc = r[*id]; const Locator* const loc = reinterpret_cast((*c)->certificate().subject.identities[j].locator); if (loc) { if ((! existingLoc) || (existingLoc->revision() < loc->revision())) existingLoc.set(new Locator(*loc)); } } } } } } return r; } Vector > TrustStore::all(const bool includeRejectedCertificates) const { RWMutex::RLock l(m_lock); Vector > r; r.reserve(m_bySerial.size()); for (Map >::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(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 >* 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 >::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& 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 >::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 visited; visited.reserve(8); for (Map >::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 >::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 >::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::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 >::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 = m_bySubjectUniqueId[Blob(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 >::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(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 >::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 TrustStore::save() const { Vector comp; int compSize; { RWMutex::RLock l(m_lock); Vector b; b.reserve(4096); // A version byte. b.push_back(0); // tuples terminated by a 0 size. for (Map >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { const Vector 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(b.data()), reinterpret_cast(comp.data() + 8), (int)b.size(), (int)(comp.size() - 8)); if (unlikely(compSize <= 0)) // shouldn't be possible return Vector(); 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& data) { if (data.size() < 8) return -1; const unsigned int uncompSize = Utils::loadBigEndian(data.data()); if ((uncompSize == 0) || (uncompSize > (unsigned int)(data.size() * 128))) return -1; Vector uncomp; uncomp.resize(uncompSize); if (LZ4_decompress_safe(reinterpret_cast(data.data() + 8), reinterpret_cast(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(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(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(b)); b += 2; ++readCount; } } return readCount; } } // namespace ZeroTier