From 7ad660c3efc5a869d47bdc9e96551dcb2ffe7047 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 31 Mar 2021 22:15:26 -0400 Subject: [PATCH] Break out certificate trust store into a separate object and simplify and clean up certificate verification code. --- core/CAPI.cpp | 8 +- core/Certificate.hpp | 6 + core/Containers.hpp | 3 + core/Node.cpp | 35 ++-- core/RuntimeEnvironment.hpp | 9 +- core/Tests.cpp | 11 +- core/TrustStore.cpp | 234 +++++++++++++------------- core/TrustStore.hpp | 107 ++++++++---- core/zerotier.h | 5 +- rust-zerotier-core/src/certificate.rs | 4 +- 10 files changed, 250 insertions(+), 172 deletions(-) diff --git a/core/CAPI.cpp b/core/CAPI.cpp index 94ba8252c..0f03a5a02 100644 --- a/core/CAPI.cpp +++ b/core/CAPI.cpp @@ -669,7 +669,7 @@ enum ZT_CertificateError ZT_Certificate_decode( return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; } if (verify) { - const ZT_CertificateError err = c->verify(); + const ZT_CertificateError err = c->verify(-1, true); if (err != ZT_CERTIFICATE_ERROR_NONE) { delete c; return err; @@ -702,12 +702,14 @@ int ZT_Certificate_encode( } } -enum ZT_CertificateError ZT_Certificate_verify(const ZT_Certificate *cert) +enum ZT_CertificateError ZT_Certificate_verify( + const ZT_Certificate *cert, + int64_t clock) { try { if (!cert) return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; - return ZeroTier::Certificate(*cert).verify(); + return ZeroTier::Certificate(*cert).verify(clock, true); } catch (...) { return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; } diff --git a/core/Certificate.hpp b/core/Certificate.hpp index 3dd97e9d7..ab5c051d9 100644 --- a/core/Certificate.hpp +++ b/core/Certificate.hpp @@ -63,6 +63,12 @@ public: return *this; } + /** + * @return SHA384Hash containing serial number + */ + ZT_INLINE SHA384Hash getSerialNo() const noexcept + { return SHA384Hash(this->serialNo); } + /** * Add a subject node/identity without a locator * diff --git a/core/Containers.hpp b/core/Containers.hpp index f3a156b64..6a8999cce 100644 --- a/core/Containers.hpp +++ b/core/Containers.hpp @@ -59,6 +59,9 @@ struct intl_MapHasher std::size_t operator()(const O &obj) const noexcept { return (std::size_t)obj.hashCode(); } + std::size_t operator()(const Vector< uint8_t > &bytes) const noexcept + { return (std::size_t)Utils::fnv1a32(bytes.data(), (unsigned int)bytes.size()); } + std::size_t operator()(const uint64_t i) const noexcept { return (std::size_t)Utils::hash64(i ^ Utils::s_mapNonce); } diff --git a/core/Node.cpp b/core/Node.cpp index 245a58ea2..48e788783 100644 --- a/core/Node.cpp +++ b/core/Node.cpp @@ -26,6 +26,7 @@ #include "VL1.hpp" #include "VL2.hpp" #include "Buf.hpp" +#include "TrustStore.hpp" namespace ZeroTier { @@ -41,23 +42,26 @@ struct _NodeObjects expect(), vl2(RR), vl1(RR), + topology(RR, tPtr, now), sa(RR), - topology(RR, tPtr, now) + ts() { RR->t = &t; RR->expect = &expect; RR->vl2 = &vl2; RR->vl1 = &vl1; - RR->sa = &sa; RR->topology = &topology; + RR->sa = &sa; + RR->ts = &ts; } Trace t; Expect expect; VL2 vl2; VL1 vl1; - SelfAwareness sa; Topology topology; + SelfAwareness sa; + TrustStore ts; }; } // anonymous namespace @@ -153,7 +157,7 @@ Node::~Node() m_networks_l.unlock(); m_networks.clear(); - delete (_NodeObjects *)m_objects; + delete reinterpret_cast<_NodeObjects *>(m_objects); // Let go of cached Buf objects. If other nodes happen to be running in this // same process space new Bufs will be allocated as needed, but this is almost @@ -584,7 +588,10 @@ ZT_CertificateError Node::addCertificate( if (!c.decode(certData, certSize)) return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; } - return RR->topology->addCertificate(tptr, c, now, localTrust, true, true, true); + RR->ts->add(c, localTrust); + RR->ts->update(now, nullptr); + SharedPtr< TrustStore::Entry > ent(RR->ts->get(c.getSerialNo())); + return (ent) ? ent->error() : ZT_CERTIFICATE_ERROR_INVALID_FORMAT; // should never be null, but if so it means invalid } ZT_ResultCode Node::deleteCertificate( @@ -593,13 +600,15 @@ ZT_ResultCode Node::deleteCertificate( { if (!serialNo) return ZT_RESULT_ERROR_BAD_PARAMETER; - RR->topology->deleteCertificate(tptr, reinterpret_cast(serialNo)); + RR->ts->erase(SHA384Hash(serialNo)); + RR->ts->update(-1, nullptr); return ZT_RESULT_OK; } struct p_certificateListInternal { - Vector< SharedPtr< const Certificate > > c; + Vector< SharedPtr< TrustStore::Entry > > entries; + Vector< const ZT_Certificate * > c; Vector< unsigned int > t; }; @@ -619,11 +628,17 @@ ZT_CertificateList *Node::listCertificates() p_certificateListInternal *const clint = reinterpret_cast(reinterpret_cast(cl) + sizeof(ZT_CertificateList)); new (clint) p_certificateListInternal; - RR->topology->allCerts(clint->c, clint->t); + + clint->entries = RR->ts->all(false); + clint->c.reserve(clint->entries.size()); + clint->t.reserve(clint->entries.size()); + for(Vector< SharedPtr< TrustStore::Entry > >::const_iterator i(clint->entries.begin()); i!=clint->entries.end(); ++i) { + clint->c.push_back(&((*i)->certificate())); + clint->t.push_back((*i)->localTrust()); + } cl->freeFunction = p_freeCertificateList; - static_assert(sizeof(SharedPtr< const Certificate >) == sizeof(void *), "SharedPtr<> is not just a wrapped pointer"); - cl->certs = reinterpret_cast(clint->c.data()); + cl->certs = clint->c.data(); cl->localTrust = clint->t.data(); cl->certCount = (unsigned long)clint->c.size(); diff --git a/core/RuntimeEnvironment.hpp b/core/RuntimeEnvironment.hpp index 976eb93a8..34287e5d5 100644 --- a/core/RuntimeEnvironment.hpp +++ b/core/RuntimeEnvironment.hpp @@ -29,6 +29,7 @@ class NetworkController; class SelfAwareness; class Trace; class Expect; +class TrustStore; /** * ZeroTier::Node execution context @@ -44,13 +45,13 @@ public: instanceId(Utils::getSecureRandomU64()), node(n), localNetworkController(nullptr), - rtmem(nullptr), t(nullptr), expect(nullptr), vl2(nullptr), vl1(nullptr), topology(nullptr), - sa(nullptr) + sa(nullptr), + ts(nullptr) { publicIdentityStr[0] = 0; secretIdentityStr[0] = 0; @@ -70,15 +71,13 @@ public: // This is set externally to an instance of this base class NetworkController *localNetworkController; - // Memory actually occupied by Trace, Switch, etc. - void *rtmem; - Trace *t; Expect *expect; VL2 *vl2; VL1 *vl1; Topology *topology; SelfAwareness *sa; + TrustStore *ts; // This node's identity and string representations thereof Identity identity; diff --git a/core/Tests.cpp b/core/Tests.cpp index 3401be60c..99a2028b7 100644 --- a/core/Tests.cpp +++ b/core/Tests.cpp @@ -44,6 +44,7 @@ #include "Locator.hpp" #include "Certificate.hpp" #include "MIMC52.hpp" +#include "ScopedPtr.hpp" #ifdef __UNIX_LIKE__ @@ -1210,7 +1211,7 @@ extern "C" const char *ZTT_crypto() ZT_T_PRINTF("OK %s" ZT_EOL_S, tmp); ZT_T_PRINTF(" Create and sign certificate... "); - SharedPtr< Certificate > cert(new Certificate()); + ScopedPtr< Certificate > cert(new Certificate()); cert->subject.timestamp = now(); cert->addSubjectIdentity(testSubjectId); cert->addSubjectNetwork(12345, testSubjectId.fingerprint()); @@ -1237,7 +1238,7 @@ extern "C" const char *ZTT_crypto() ZT_T_PRINTF("OK (%d bytes)" ZT_EOL_S, (int)enc.size()); ZT_T_PRINTF(" Testing certificate verify... "); - ZT_CertificateError vr = cert->verify(); + ZT_CertificateError vr = cert->verify(-1, true); if (vr != ZT_CERTIFICATE_ERROR_NONE) { ZT_T_PRINTF("FAILED (verify original) (%d)" ZT_EOL_S, (int)vr); return "Verify original certificate"; @@ -1245,12 +1246,12 @@ extern "C" const char *ZTT_crypto() ZT_T_PRINTF("OK" ZT_EOL_S); ZT_T_PRINTF(" Test certificate decode from marshaled format... "); - SharedPtr< Certificate > cert2(new Certificate()); + ScopedPtr< Certificate > cert2(new Certificate()); if (!cert2->decode(enc.data(), (unsigned int)enc.size())) { ZT_T_PRINTF("FAILED (decode)" ZT_EOL_S); return "Certificate decode"; } - if (cert2->verify() != ZT_CERTIFICATE_ERROR_NONE) { + if (cert2->verify(-1, true) != ZT_CERTIFICATE_ERROR_NONE) { ZT_T_PRINTF("FAILED (verify decoded certificate)" ZT_EOL_S); return "Verify decoded certificate"; } @@ -1261,7 +1262,7 @@ extern "C" const char *ZTT_crypto() ZT_T_PRINTF("OK" ZT_EOL_S); ZT_T_PRINTF(" Test certificate copy/construct... "); - SharedPtr< Certificate > cert3(new Certificate(*cert2)); + ScopedPtr< Certificate > cert3(new Certificate(*cert2)); if (!ZTT_deepCompareCertificates(*cert2, *cert3)) { ZT_T_PRINTF("FAILED (compare copy with original)" ZT_EOL_S); return "Certificate copy"; diff --git a/core/TrustStore.cpp b/core/TrustStore.cpp index ae4f35a43..49782d739 100644 --- a/core/TrustStore.cpp +++ b/core/TrustStore.cpp @@ -12,7 +12,6 @@ /****/ #include "TrustStore.hpp" -#include "Topology.hpp" namespace ZeroTier { @@ -22,63 +21,73 @@ TrustStore::TrustStore() TrustStore::~TrustStore() {} -SharedPtr< const TrustStore::Entry > TrustStore::get(const SHA384Hash &serial) const +SharedPtr< TrustStore::Entry > TrustStore::get(const SHA384Hash &serial) const { RWMutex::RLock l(m_lock); Map< SHA384Hash, SharedPtr< Entry > >::const_iterator i(m_bySerial.find(serial)); - return (i == m_bySerial.end()) ? SharedPtr< const TrustStore::Entry >() : i->second.constify(); + return (i != m_bySerial.end()) ? i->second : SharedPtr< TrustStore::Entry >(); } -Vector< SharedPtr< Peer > > TrustStore::roots(void *const tPtr, const RuntimeEnvironment *RR) +Map< Identity, SharedPtr< const Locator > > TrustStore::roots() { RWMutex::RLock l(m_lock); - - Vector< SharedPtr< Peer > > r; - r.reserve(m_bySerial.size()); - - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { - if ((c->second->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET) != 0) { - for (unsigned int j = 0; j < c->second->certificate().subject.identityCount; ++j) { - const Identity *const id = reinterpret_cast(c->second->certificate().subject.identities[j].identity); - if ((id != nullptr) && (*id)) { // sanity check - SharedPtr< Peer > peer(RR->topology->peer(tPtr, id->address(), true)); - if (!peer) { - peer.set(new Peer(RR)); - peer->init(*id); - peer = RR->topology->add(tPtr, peer); + Map< Identity, SharedPtr< const Locator > > r; + 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) { + if (((*c)->error() == ZT_CERTIFICATE_ERROR_NONE) && (((*c)->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET) != 0)) { + for (unsigned int j = 0; j < (*c)->certificate().subject.identityCount; ++j) { + const Identity *const id = reinterpret_cast((*c)->certificate().subject.identities[j].identity); + if (likely((id != nullptr) && (*id))) { // sanity check + SharedPtr< const Locator > &existingLoc = r[*id]; + const Locator *const loc = reinterpret_cast((*c)->certificate().subject.identities[j].locator); + if ((loc != nullptr) && ((!existingLoc) || (existingLoc->timestamp() < loc->timestamp()))) + existingLoc.set(new Locator(*loc)); } - - const Locator *const loc = reinterpret_cast(c->second->certificate().subject.identities[j].locator); - if (loc) - peer->setLocator(SharedPtr< const Locator >(new Locator(*loc)), true); - - r.push_back(peer); } } } } - return r; } -Vector< SharedPtr< const TrustStore::Entry > > TrustStore::all() const +Vector< SharedPtr< TrustStore::Entry > > TrustStore::all(const bool includeRejectedCertificates) const { - Vector< SharedPtr< const TrustStore::Entry > > r; RWMutex::RLock l(m_lock); + Vector< SharedPtr< Entry > > r; r.reserve(m_bySerial.size()); - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator i(m_bySerial.begin()); i != m_bySerial.end(); ++i) - r.push_back(i->second.constify()); + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator i(m_bySerial.begin()); i != m_bySerial.end(); ++i) { + if ((includeRejectedCertificates) || (i->second->error() == ZT_CERTIFICATE_ERROR_NONE)) + r.push_back(i->second); + } + return r; +} + +Vector< SharedPtr< TrustStore::Entry > > TrustStore::rejects() const +{ + RWMutex::RLock l(m_lock); + Vector< SharedPtr< Entry > > r; + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { + if (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(cert, localTrust))); + m_addQueue.push_front(SharedPtr< Entry >(new Entry(cert, localTrust))); +} + +void TrustStore::erase(const SHA384Hash &serial) +{ + RWMutex::Lock l(m_lock); + m_deleteQueue.push_front(serial); } // Recursive function to trace a certificate up the chain to a CA, returning true -// if the CA is reached and the path length is less than the maximum. +// if the CA is reached and the path length is less than the maximum. Note that only +// non-rejected (no errors) certificates will be in bySignedCert. static bool p_validatePath(const Map< SHA384Hash, Vector< SharedPtr< TrustStore::Entry > > > &bySignedCert, const SharedPtr< TrustStore::Entry > &entry, unsigned int pathLength) { if (((entry->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) != 0) && (pathLength <= entry->certificate().maxPathLength)) @@ -95,124 +104,123 @@ static bool p_validatePath(const Map< SHA384Hash, Vector< SharedPtr< TrustStore: return false; } -void TrustStore::update(const int64_t clock, Vector< std::pair< SharedPtr< Entry >, ZT_CertificateError > > *const purge) +void TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const purge) { RWMutex::Lock l(m_lock); - // Re-verify existing and rejected certificates, excluding signatures which - // will have already been checked (and checking these is CPU-intensive). This - // catches certificate expiry and un-expiry if the system's clock has been - // changed. When a formerly rejected cert is revived it ends up getting - // checked twice, but optimizing this out would be about as costly as just - // doing this as verify() without signature check is cheap. - for (Map< SharedPtr< Entry >, ZT_CertificateError >::iterator c(m_rejected.begin()); c != m_rejected.end();) { - const ZT_CertificateError err = c->first->m_certificate.verify(clock, false); - if (err == ZT_CERTIFICATE_ERROR_NONE) { - m_bySerial[SHA384Hash(c->first->m_certificate.serialNo)] = c->first; - m_rejected.erase(c++); - } else { - ++c; - } - } - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { + // (Re)compute error codes for existing certs, but we don't have to do a full + // signature check here since that's done when they're taken out of the add queue. + bool errorStateModified = false; + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { const ZT_CertificateError err = c->second->m_certificate.verify(clock, false); - if (err == ZT_CERTIFICATE_ERROR_NONE) { - ++c; - } else { - m_rejected[c->second] = err; - m_bySerial.erase(c++); - } + errorStateModified |= (c->second->m_error.exchange((int)err, std::memory_order_relaxed) != (int)err); } + // If no certificate error statuses changed and there are no new certificates to + // add, there is nothing to do and we don't need to do more expensive path validation + // and structure rebuilding. + if ((!errorStateModified) && (m_addQueue.empty()) && (m_deleteQueue.empty())) + return; + // 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()) { - const ZT_CertificateError err = m_addQueue.front()->m_certificate.verify(clock, true); - if (err == ZT_CERTIFICATE_ERROR_NONE) { - m_bySerial[SHA384Hash(m_addQueue.front()->m_certificate.serialNo)].move(m_addQueue.front()); - } else { - m_rejected[m_addQueue.front()] = err; - } + m_addQueue.front()->m_error.store((int)m_addQueue.front()->m_certificate.verify(clock, true), std::memory_order_relaxed); + m_bySerial[SHA384Hash(m_addQueue.front()->m_certificate.serialNo)].move(m_addQueue.front()); m_addQueue.pop_front(); } - // Verify certificate paths and replace old certificates with newer certificates - // when subject unique ID mapping dictates, repeating the process until a stable - // state is achieved. A loop is needed because deleting old certs when new - // certs (with the same subject unique ID) replace them could in theory alter - // certificate validation path checking outcomes, though in practice it should - // not since mixing certificate roles this way would be strange. - for (;;) { - // Create a reverse lookup mapping from signed certs to signer certs for - // certificate path validation. - Map< SHA384Hash, Vector< SharedPtr< Entry > > > bySignedCert; - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { - for (unsigned int j = 0; j < c->second->m_certificate.subject.certificateCount; ++j) - bySignedCert[SHA384Hash(c->second->m_certificate.subject.certificates[j])].push_back(c->second); - } + // Delete any certificates enqueued to be deleted. + while (!m_deleteQueue.empty()) { + m_bySerial.erase(m_deleteQueue.front()); + m_deleteQueue.pop_front(); + } - // Validate certificate paths and reject any certificates that do not trace - // back to a CA. - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { - if (p_validatePath(bySignedCert, c->second, 0)) { - ++c; - } else { - m_rejected[c->second] = ZT_CERTIFICATE_ERROR_INVALID_CHAIN; - m_bySerial.erase(c++); + Map< SHA384Hash, Vector< SharedPtr< Entry > > > bySignedCert; + for (;;) { + // Create a reverse lookup mapping from signed certs to signer certs for certificate + // path validation. Only include good certificates. + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { + if (c->second->error() == ZT_CERTIFICATE_ERROR_NONE) { + for (unsigned int j = 0; j < c->second->m_certificate.subject.certificateCount; ++j) + bySignedCert[SHA384Hash(c->second->m_certificate.subject.certificates[j])].push_back(c->second); } } - // Populate mapping of subject unique IDs to certificates and reject any - // certificates that have been superseded by newly issued certificates with - // the same subject. + // Validate certificate paths and reject any certificates that do not trace back to a CA. + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { + if (c->second->error() == ZT_CERTIFICATE_ERROR_NONE) { + if (!p_validatePath(bySignedCert, c->second, 0)) + c->second->m_error.store((int)ZT_CERTIFICATE_ERROR_INVALID_CHAIN, std::memory_order_relaxed); + } + } + + // Populate mapping of subject unique IDs to certificates and reject any certificates + // that have been superseded by newly issued certificates with the same subject. bool exitLoop = true; m_bySubjectUniqueId.clear(); for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { - const unsigned int uniqueIdSize = c->second->m_certificate.subject.uniqueIdSize; - if ((uniqueIdSize > 0) && (uniqueIdSize <= 1024)) { // 1024 is a sanity check value, actual unique IDs are <100 bytes - SharedPtr< Entry > ¤t = m_bySubjectUniqueId[Vector< uint8_t >(c->second->m_certificate.subject.uniqueId, c->second->m_certificate.subject.uniqueId + uniqueIdSize)]; - if (current) { - if (c->second->m_certificate.subject.timestamp > current->m_certificate.subject.timestamp) { + if (c->second->error() == ZT_CERTIFICATE_ERROR_NONE) { + const unsigned int uniqueIdSize = c->second->m_certificate.subject.uniqueIdSize; + if ((uniqueIdSize > 0) && (uniqueIdSize <= 1024)) { // 1024 is a sanity check value, actual unique IDs are <100 bytes + SharedPtr< Entry > ¤t = m_bySubjectUniqueId[Vector< uint8_t >(c->second->m_certificate.subject.uniqueId, c->second->m_certificate.subject.uniqueId + uniqueIdSize)]; + if (current) { exitLoop = false; - m_rejected[current] = ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT; - m_bySerial.erase(SHA384Hash(current->m_certificate.serialNo)); + if (c->second->m_certificate.subject.timestamp > current->m_certificate.subject.timestamp) { + current->m_error.store((int)ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT, std::memory_order_relaxed); + current = c->second; + } else if (c->second->m_certificate.subject.timestamp < current->m_certificate.subject.timestamp) { + c->second->m_error.store((int)ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT, std::memory_order_relaxed); + } else { + // Equal timestamps should never happen, but handle it by comparing serials for deterministic completeness. + if (memcmp(c->second->m_certificate.serialNo, current->m_certificate.serialNo, ZT_SHA384_DIGEST_SIZE) > 0) { + current->m_error.store((int)ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT, std::memory_order_relaxed); + current = c->second; + } else { + c->second->m_error.store((int)ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT, std::memory_order_relaxed); + } + } + } else { current = c->second; } - } else { - current = c->second; } } } - if (exitLoop) + // If no certificates were tagged out during the unique ID pass, we can exit. Otherwise + // the last few steps have to be repeated because removing any certificate could in + // theory affect the result of certificate path validation. + if (exitLoop) { break; + } else { + bySignedCert.clear(); + } } - // Populate a mapping of identities to certificates whose subjects reference them. + // Populate mapping of identities to certificates whose subjects reference them. m_bySubjectIdentity.clear(); - for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { - for (unsigned int i = 0; i < c->second->m_certificate.subject.identityCount; ++i) - m_bySubjectIdentity[reinterpret_cast(c->second->m_certificate.subject.identities[i].identity)->fingerprint()].push_back(c->second); + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { + if (c->second->error() == ZT_CERTIFICATE_ERROR_NONE) { + 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); + } + } } // Purge and return purged certificates if this option is selected. if (purge) { - purge->reserve(m_rejected.size()); - for (Map< SharedPtr< Entry >, ZT_CertificateError >::const_iterator c(m_rejected.begin()); c != m_rejected.end(); ++c) - purge->push_back(std::pair< SharedPtr< Entry >, ZT_CertificateError >(c->first, c->second)); - m_rejected.clear(); + for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { + if (c->second->error() != ZT_CERTIFICATE_ERROR_NONE) { + purge->push_back(c->second); + m_bySerial.erase(c++); + } else { + ++c; + } + } } } -Vector< std::pair< SharedPtr, ZT_CertificateError > > TrustStore::rejects() const -{ - Vector< std::pair< SharedPtr, ZT_CertificateError > > r; - RWMutex::RLock l(m_lock); - r.reserve(m_rejected.size()); - for (Map< SharedPtr< Entry >, ZT_CertificateError >::const_iterator c(m_rejected.begin()); c != m_rejected.end(); ++c) - r.push_back(std::pair< SharedPtr< Entry >, ZT_CertificateError >(c->first, c->second)); - return r; -} - } // namespace ZeroTier diff --git a/core/TrustStore.hpp b/core/TrustStore.hpp index ce2282220..b92f7cd74 100644 --- a/core/TrustStore.hpp +++ b/core/TrustStore.hpp @@ -43,61 +43,100 @@ public: friend class SharedPtr< const TrustStore::Entry >; friend class TrustStore; - private: - ZT_INLINE Entry(const Certificate &cert, const unsigned int lt) noexcept: - m_certificate(cert), - m_localTrust(lt) - {} - public: + /** + * @return Reference to held certificate + */ ZT_INLINE const Certificate &certificate() const noexcept { return m_certificate; } + /** + * Get the local trust for this certificate + * + * This value may be changed dynamically by calls to update(). + * + * @return Local trust bit mask + */ ZT_INLINE unsigned int localTrust() const noexcept { return m_localTrust.load(std::memory_order_relaxed); } + /** + * Change the local trust of this entry + * + * @param lt New local trust bit mask + */ + ZT_INLINE void setLocalTrust(const unsigned int lt) noexcept + { m_localTrust.store(lt, std::memory_order_relaxed); } + + /** + * Get the error code for this certificate + * + * @return Error or ZT_CERTIFICATE_ERROR_NONE if none + */ + ZT_INLINE ZT_CertificateError error() const noexcept + { return (ZT_CertificateError)m_error.load(std::memory_order_relaxed); } + private: + Entry() {} + Entry(const Entry &) {} + Entry &operator=(const Entry &) { return *this; } + + ZT_INLINE Entry(const Certificate &cert, const unsigned int lt) noexcept: + m_certificate(cert), + m_localTrust(lt), + m_error((int)ZT_CERTIFICATE_ERROR_NONE) + {} + Certificate m_certificate; std::atomic< unsigned int > m_localTrust; + std::atomic< int > m_error; std::atomic< int > __refCount; }; TrustStore(); - ~TrustStore(); /** * Get certificate by certificate serial number * + * Note that the error code should be checked. The certificate may be + * rejected and may still be in the store unless the store has been + * purged. + * * @param serial SHA384 hash of certificate * @return Entry or empty/nil if not found */ - SharedPtr< const Entry > get(const SHA384Hash &serial) const; + SharedPtr< Entry > get(const SHA384Hash &serial) const; /** - * Get current root peers based on root-enumerating certs in trust store + * Get roots specified by root set certificates in the local store. * - * Root peers are created or obtained via this node's Topology. This should - * never be called while relevant data structures in Topology are locked. + * If more than one certificate locally trusted as a root set specifies + * the root, it will be returned once (as per Map behavior) but the latest + * locator will be returned from among those available. * - * Locators in root peers are also updated if the locator present in the - * certificate is valid and newer. - * - * @param tPtr Caller pointer - * @param RR Runtime environment - * @return All roots (sort order undefined) + * @return Roots and the latest locator specified for each (if any) */ - Vector< SharedPtr< Peer > > roots(void *tPtr, const RuntimeEnvironment *RR); + Map< Identity, SharedPtr< const Locator > > roots(); /** + * @param includeRejectedCertificates If true, also include certificates with error codes * @return All certificates in asecending sort order by serial */ - Vector< SharedPtr< const Entry > > all() const; + Vector< SharedPtr< Entry > > all(bool includeRejectedCertificates) const; + + /** + * Get a copy of the current rejected certificate set. + * + * @return Rejected certificates + */ + Vector< SharedPtr< Entry > > rejects() const; /** * Add a certificate * - * A copy is made so it's fine if the original is freed after this call. + * A copy is made so it's fine if the original is freed after this call. If + * the certificate already exists its local trust flags are updated. * * IMPORTANT: The caller MUST also call update() after calling add() one or * more times to actually add and revalidate certificates and their signature @@ -107,29 +146,31 @@ public: */ void add(const Certificate &cert, unsigned int localTrust); + /** + * Queue a certificate to be deleted + * + * Actual delete does not happen until the next update(). + * + * @param serial Serial of certificate to delete + */ + void erase(const SHA384Hash &serial); + /** * Validate all certificates and their certificate chains * * This also processes any certificates added with add() since the last call to update(). * - * @param clock Current time in milliseconds since epoch + * @param clock Current time in milliseconds since epoch, or -1 to not check times on this pass * @param purge If non-NULL, purge rejected certificates and return them in this vector (vector should be empty) */ - void update(int64_t clock, Vector< std::pair< SharedPtr, ZT_CertificateError > > *purge); - - /** - * Get a copy of the current rejected certificate set. - * - * @return Rejected certificates - */ - Vector< std::pair< SharedPtr, ZT_CertificateError > > rejects() const; + void update(int64_t clock, Vector< SharedPtr< Entry > > *purge); private: - Map< SHA384Hash, SharedPtr< Entry > > m_bySerial; - Map< Vector< uint8_t >, SharedPtr< Entry > > m_bySubjectUniqueId; - Map< Fingerprint, Vector< SharedPtr< Entry > > > m_bySubjectIdentity; + Map< SHA384Hash, SharedPtr< Entry > > m_bySerial; // all certificates + Map< Vector< uint8_t >, SharedPtr< Entry > > m_bySubjectUniqueId; // non-rejected certificates only + Map< Fingerprint, Vector< SharedPtr< Entry > > > m_bySubjectIdentity; // non-rejected certificates only ForwardList< SharedPtr< Entry > > m_addQueue; - Map< SharedPtr< Entry >, ZT_CertificateError > m_rejected; + ForwardList< SHA384Hash > m_deleteQueue; RWMutex m_lock; }; diff --git a/core/zerotier.h b/core/zerotier.h index 9ecc457fe..26407d48c 100644 --- a/core/zerotier.h +++ b/core/zerotier.h @@ -2900,9 +2900,12 @@ ZT_SDK_API int ZT_Certificate_encode( * Verify certificate signatures and internal structure. * * @param cert Certificate to verify + * @param clock Clock to check timestamp or -1 to skip this check * @return Certificate error or ZT_CERTIFICATE_ERROR_NONE if no errors found. */ -ZT_SDK_API enum ZT_CertificateError ZT_Certificate_verify(const ZT_Certificate *cert); +ZT_SDK_API enum ZT_CertificateError ZT_Certificate_verify( + const ZT_Certificate *cert, + int64_t clock); /** * Deep clone a certificate, returning one allocated C-side. diff --git a/rust-zerotier-core/src/certificate.rs b/rust-zerotier-core/src/certificate.rs index ed7692cd9..139e91719 100644 --- a/rust-zerotier-core/src/certificate.rs +++ b/rust-zerotier-core/src/certificate.rs @@ -694,10 +694,10 @@ impl Certificate { return Ok(signed_cert); } - pub fn verify(&self) -> CertificateError { + pub fn verify(&self, clock: i64) -> CertificateError { unsafe { let capi = self.to_capi(); - return CertificateError::from_i32(ztcore::ZT_Certificate_verify(&capi.certificate as *const ztcore::ZT_Certificate) as i32).unwrap_or(CertificateError::InvalidFormat); + return CertificateError::from_i32(ztcore::ZT_Certificate_verify(&capi.certificate as *const ztcore::ZT_Certificate, clock) as i32).unwrap_or(CertificateError::InvalidFormat); } } }