Stub out certificate revocations and harden certificate structure verification to make sure there are no dangling pointers.

This commit is contained in:
Adam Ierymenko 2021-04-20 17:32:56 -04:00
parent 0acc731bf4
commit e7b7d7444e
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
8 changed files with 396 additions and 232 deletions

View file

@ -360,8 +360,64 @@ ZT_CertificateError Certificate::verify(const int64_t clock, const bool checkSig
if (this->validity[0] > this->validity[1]) { if (this->validity[0] > this->validity[1]) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
} }
if ((clock >= 0) && ((this->validity[0] > clock) || (this->validity[1] < clock))) {
return ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW; if (this->subject.identityCount > 0) {
if (this->subject.identities) {
for (unsigned int i = 0; i < this->subject.identityCount; ++i) {
if (!this->subject.identities[i].identity) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
if (checkSignatures) {
if (!reinterpret_cast<const Identity *>(this->subject.identities[i].identity)->locallyValidate()) {
return ZT_CERTIFICATE_ERROR_INVALID_IDENTITY;
}
if ((this->subject.identities[i].locator) && (!reinterpret_cast<const Locator *>(this->subject.identities[i].locator)->verify(*reinterpret_cast<const Identity *>(this->subject.identities[i].identity)))) {
return ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE;
}
}
}
} else {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
}
if (this->subject.networkCount > 0) {
if (this->subject.networks) {
for (unsigned int i = 0; i < this->subject.networkCount; ++i) {
if (!this->subject.networks[i].id) {
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
}
} else {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
}
if (this->subject.updateURLCount > 0) {
if (this->subject.updateURLs) {
for (unsigned int i = 0; i < this->subject.updateURLCount; ++i) {
if (!this->subject.updateURLs[i])
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
} else {
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
}
if ((this->subject.uniqueIdSize > sizeof(this->subject.uniqueId)) || (this->subject.uniqueIdSignatureSize > sizeof(this->subject.uniqueIdSignature))) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
if ((this->issuerPublicKeySize > sizeof(this->issuerPublicKey)) || (this->publicKeySize > sizeof(this->publicKey))) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
if ((this->extendedAttributesSize > 0) && (!this->extendedAttributes)) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
if (this->signatureSize > sizeof(this->signature)) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
} }
if (checkSignatures) { if (checkSignatures) {
@ -423,33 +479,9 @@ ZT_CertificateError Certificate::verify(const int64_t clock, const bool checkSig
} }
} }
for (unsigned int i = 0; i < this->subject.identityCount; ++i) { if (clock >= 0) {
if (!this->subject.identities[i].identity) if (!this->verifyTimeWindow(clock))
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS; return ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW;
if (checkSignatures) {
if (!reinterpret_cast<const Identity *>(this->subject.identities[i].identity)->locallyValidate()) {
return ZT_CERTIFICATE_ERROR_INVALID_IDENTITY;
}
if ((this->subject.identities[i].locator) && (!reinterpret_cast<const Locator *>(this->subject.identities[i].locator)->verify(*reinterpret_cast<const Identity *>(this->subject.identities[i].identity)))) {
return ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE;
}
}
}
for (unsigned int i = 0; i < this->subject.networkCount; ++i) {
if (!this->subject.networks[i].id) {
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
}
if (this->subject.updateURLCount > 0) {
if (!this->subject.updateURLs) {
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
for (unsigned int i = 0; i < this->subject.updateURLCount; ++i) {
if (!this->subject.updateURLs[i])
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
}
} }
} catch (...) { } catch (...) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;

View file

@ -143,15 +143,24 @@ public:
/** /**
* Verify self-contained signatures and validity of certificate structure * Verify self-contained signatures and validity of certificate structure
* *
* This doesn't check the entire certificate chain, just the validity of * This cannot check the chain of trust back to a CA, only the internal validity
* the certificate's internal signature and fields. * of this certificate.
* *
* @param clock If non-negative, check that certificate is in valid time window * @param clock If non-negative, also do verifyTimeWindow()
* @param checkSignatures If true, perform full signature check (which is more expensive than other checks) * @param checkSignatures If true, perform full signature check (which is more expensive than other checks)
* @return OK (0) or error code indicating why certificate failed verification. * @return OK (0) or error code indicating why certificate failed verification.
*/ */
ZT_CertificateError verify(int64_t clock, bool checkSignatures) const; ZT_CertificateError verify(int64_t clock, bool checkSignatures) const;
/**
* Check this certificate's expiration status
*
* @param clock Current real world time in milliseconds since epoch
* @return True if certificate is not expired or outside window
*/
ZT_INLINE bool verifyTimeWindow(int64_t clock) const noexcept
{ return ((clock >= this->validity[0]) && (clock <= this->validity[1]) && (this->validity[0] <= this->validity[1])); }
/** /**
* Create a new certificate public/private key pair * Create a new certificate public/private key pair
* *

View file

@ -49,9 +49,6 @@ public:
class Key class Key
{ {
public: public:
/**
* Construct key with undefined value
*/
ZT_INLINE Key() noexcept ZT_INLINE Key() noexcept
{} {}
@ -61,7 +58,7 @@ public:
if (family == AF_INET) { if (family == AF_INET) {
const uint16_t p = (uint16_t)ip.as.sa_in.sin_port; const uint16_t p = (uint16_t)ip.as.sa_in.sin_port;
m_hashCode = Utils::hash64((((uint64_t)ip.as.sa_in.sin_addr.s_addr) << 16U) ^ ((uint64_t)p) ^ Utils::s_mapNonce); m_hashCode = Utils::hash64((((uint64_t)ip.as.sa_in.sin_addr.s_addr) << 16U) ^ ((uint64_t)p) ^ Utils::s_mapNonce);
m_v664 = 0; // IPv6 /64 is 0 for IPv4 m_ipv6Net64 = 0; // 0 for IPv4, never 0 for IPv6
m_port = p; m_port = p;
} else { } else {
if (likely(family == AF_INET6)) { if (likely(family == AF_INET6)) {
@ -69,13 +66,12 @@ public:
const uint64_t b = Utils::loadMachineEndian< uint64_t >(reinterpret_cast<const uint8_t *>(ip.as.sa_in6.sin6_addr.s6_addr) + 8); const uint64_t b = Utils::loadMachineEndian< uint64_t >(reinterpret_cast<const uint8_t *>(ip.as.sa_in6.sin6_addr.s6_addr) + 8);
const uint16_t p = ip.as.sa_in6.sin6_port; const uint16_t p = ip.as.sa_in6.sin6_port;
m_hashCode = Utils::hash64(a ^ b ^ ((uint64_t)p) ^ Utils::s_mapNonce); m_hashCode = Utils::hash64(a ^ b ^ ((uint64_t)p) ^ Utils::s_mapNonce);
m_v664 = a; // IPv6 /64 m_ipv6Net64 = a; // IPv6 /64
m_port = p; m_port = p;
} else { } else {
// This isn't reachable since only IPv4 and IPv6 are used with InetAddress, but implement // This is not reachable since InetAddress can only be AF_INET or AF_INET6, but implement something.
// something here for technical completeness.
m_hashCode = Utils::fnv1a32(&ip, sizeof(InetAddress)); m_hashCode = Utils::fnv1a32(&ip, sizeof(InetAddress));
m_v664 = Utils::fnv1a32(ip.as.sa.sa_data, sizeof(ip.as.sa.sa_data)); m_ipv6Net64 = 0;
m_port = (uint16_t)family; m_port = (uint16_t)family;
} }
} }
@ -85,7 +81,7 @@ public:
{ return (unsigned long)m_hashCode; } { return (unsigned long)m_hashCode; }
ZT_INLINE bool operator==(const Key &k) const noexcept ZT_INLINE bool operator==(const Key &k) const noexcept
{ return (m_hashCode == k.m_hashCode) && (m_v664 == k.m_v664) && (m_port == k.m_port); } { return (m_hashCode == k.m_hashCode) && (m_ipv6Net64 == k.m_ipv6Net64) && (m_port == k.m_port); }
ZT_INLINE bool operator!=(const Key &k) const noexcept ZT_INLINE bool operator!=(const Key &k) const noexcept
{ return (!(*this == k)); } { return (!(*this == k)); }
@ -95,9 +91,9 @@ public:
if (m_hashCode < k.m_hashCode) { if (m_hashCode < k.m_hashCode) {
return true; return true;
} else if (m_hashCode == k.m_hashCode) { } else if (m_hashCode == k.m_hashCode) {
if (m_v664 < k.m_v664) { if (m_ipv6Net64 < k.m_ipv6Net64) {
return true; return true;
} else if (m_v664 == k.m_v664) { } else if (m_ipv6Net64 == k.m_ipv6Net64) {
return (m_port < k.m_port); return (m_port < k.m_port);
} }
} }
@ -115,7 +111,7 @@ public:
private: private:
uint64_t m_hashCode; uint64_t m_hashCode;
uint64_t m_v664; uint64_t m_ipv6Net64;
uint16_t m_port; uint16_t m_port;
}; };
@ -167,11 +163,11 @@ public:
*/ */
ZT_INLINE void updateLatency(const unsigned int newMeasurement) noexcept ZT_INLINE void updateLatency(const unsigned int newMeasurement) noexcept
{ {
int lat = m_latency; const int lat = m_latency.load(std::memory_order_relaxed);
if (likely(lat > 0)) { if (likely(lat > 0)) {
m_latency = (lat + newMeasurement) / 2; m_latency.store((lat + (int)newMeasurement) >> 1U, std::memory_order_relaxed);
} else { } else {
m_latency = newMeasurement; m_latency.store((int)newMeasurement, std::memory_order_relaxed);
} }
} }
@ -179,7 +175,7 @@ public:
* @return Latency in milliseconds or -1 if unknown * @return Latency in milliseconds or -1 if unknown
*/ */
ZT_INLINE int latency() const noexcept ZT_INLINE int latency() const noexcept
{ return m_latency; } { return m_latency.load(std::memory_order_relaxed); }
/** /**
* Check path aliveness * Check path aliveness
@ -187,7 +183,7 @@ public:
* @param now Current time * @param now Current time
*/ */
ZT_INLINE bool alive(const CallContext &cc) const noexcept ZT_INLINE bool alive(const CallContext &cc) const noexcept
{ return ((cc.ticks - m_lastIn.load()) < ZT_PATH_ALIVE_TIMEOUT); } { return ((cc.ticks - m_lastIn.load(std::memory_order_relaxed)) < ZT_PATH_ALIVE_TIMEOUT); }
/** /**
* @return Physical address * @return Physical address
@ -205,13 +201,13 @@ public:
* @return Last time we received anything * @return Last time we received anything
*/ */
ZT_INLINE int64_t lastIn() const noexcept ZT_INLINE int64_t lastIn() const noexcept
{ return m_lastIn.load(); } { return m_lastIn.load(std::memory_order_relaxed); }
/** /**
* @return Last time we sent something * @return Last time we sent something
*/ */
ZT_INLINE int64_t lastOut() const noexcept ZT_INLINE int64_t lastOut() const noexcept
{ return m_lastOut.load(); } { return m_lastOut.load(std::memory_order_relaxed); }
private: private:
const int64_t m_localSocket; const int64_t m_localSocket;

View file

@ -45,11 +45,11 @@ public:
/** /**
* Add peer to database * Add peer to database
* *
* This will not replace existing peers. In that case the existing peer * If there's already a peer with this address, the existing peer is
* record is returned. * returned. Otherwise the new peer is added and returned.
* *
* @param peer Peer to add * @param peer Peer to add
* @return New or existing peer (should replace 'peer') * @return New or existing peer
*/ */
SharedPtr< Peer > add(const CallContext &cc, const SharedPtr< Peer > &peer); SharedPtr< Peer > add(const CallContext &cc, const SharedPtr< Peer > &peer);
@ -118,7 +118,8 @@ public:
} }
/** /**
* @param allPeers vector to fill with all current peers * @param allPeers Vector to fill with all current peers
* @param rootPeers Vector to fill with peers that are roots
*/ */
void allPeers(Vector< SharedPtr< Peer > > &allPeers, Vector< SharedPtr< Peer > > &rootPeers) const; void allPeers(Vector< SharedPtr< Peer > > &allPeers, Vector< SharedPtr< Peer > > &rootPeers) const;
@ -129,8 +130,6 @@ public:
/** /**
* Rank root servers in descending order of quality * Rank root servers in descending order of quality
*
* @param now Current time
*/ */
ZT_INLINE void rankRoots(const CallContext &cc) ZT_INLINE void rankRoots(const CallContext &cc)
{ {
@ -162,7 +161,7 @@ private:
RWMutex m_peers_l; // m_peers RWMutex m_peers_l; // m_peers
RWMutex m_paths_l; // m_paths RWMutex m_paths_l; // m_paths
Mutex m_roots_l; // m_roots and m_lastRankedRoots Mutex m_roots_l; // m_roots
SharedPtr< Peer > m_bestRoot; SharedPtr< Peer > m_bestRoot;
Spinlock l_bestRoot; Spinlock l_bestRoot;

View file

@ -33,19 +33,27 @@ Map< Identity, SharedPtr< const Locator > > TrustStore::roots()
{ {
RWMutex::RLock l(m_lock); RWMutex::RLock l(m_lock);
Map< Identity, SharedPtr< const Locator > > r; 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 (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) { 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)) {
if (((*c)->m_localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET) != 0) {
for (unsigned int j = 0; j < (*c)->certificate().subject.identityCount; ++j) { 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); const Identity *const id = reinterpret_cast<const Identity *>((*c)->certificate().subject.identities[j].identity);
if (likely((id != nullptr) && (*id))) { // sanity check if ((id) && (*id)) { // sanity check
SharedPtr< const Locator > &existingLoc = r[*id]; SharedPtr< const Locator > &existingLoc = r[*id];
const Locator *const loc = reinterpret_cast<const Locator *>((*c)->certificate().subject.identities[j].locator); const Locator *const loc = reinterpret_cast<const Locator *>((*c)->certificate().subject.identities[j].locator);
if ((loc != nullptr) && ((!existingLoc) || (existingLoc->revision() < loc->revision()))) if (loc) {
existingLoc.set(new Locator(*loc)); // If more than one certificate names a root, this ensures that the newest locator is used.
if ((!existingLoc) || (existingLoc->revision() < loc->revision()))
existingLoc.set(new Locator(*loc));
}
} }
} }
} }
} }
} }
return r; return r;
@ -66,7 +74,7 @@ Vector< SharedPtr< TrustStore::Entry > > TrustStore::all(const bool includeRejec
void TrustStore::add(const Certificate &cert, const unsigned int localTrust) void TrustStore::add(const Certificate &cert, const unsigned int localTrust)
{ {
RWMutex::Lock l(m_lock); RWMutex::Lock l(m_lock);
m_addQueue.push_front(SharedPtr< Entry >(new Entry(cert, localTrust))); m_addQueue.push_front(SharedPtr< Entry >(new Entry(this->m_lock, cert, localTrust)));
} }
void TrustStore::erase(const H384 &serial) void TrustStore::erase(const H384 &serial)
@ -79,23 +87,32 @@ bool TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const
{ {
RWMutex::Lock l(m_lock); RWMutex::Lock l(m_lock);
// (Re)compute error codes for existing certs, but we don't have to do a full // Check for certificate time validity status changes. If any of these occur then
// signature check here since that's done when they're taken out of the add queue. // full re-validation is required.
bool errorStateModified = false; bool errorStateModified = false;
for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
const ZT_CertificateError err = c->second->error(); const bool timeValid = c->second->m_certificate.verifyTimeWindow(clock);
if ((err != ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT) && (err != ZT_CERTIFICATE_ERROR_INVALID_CHAIN)) { switch (c->second->m_error) {
const ZT_CertificateError newErr = c->second->m_certificate.verify(clock, false); case ZT_CERTIFICATE_ERROR_NONE:
if (newErr != err) { case ZT_CERTIFICATE_ERROR_INVALID_CHAIN:
c->second->m_error.store((int)newErr, std::memory_order_relaxed); if (!timeValid) {
errorStateModified = true; 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 no certificate error statuses changed and there are no new certificates to // If there were not any such changes and if the add and delete queues are empty,
// add, there is nothing to do and we don't need to do more expensive path validation // there is nothing more to be done.
// and structure rebuilding.
if ((!errorStateModified) && (m_addQueue.empty()) && (m_deleteQueue.empty())) if ((!errorStateModified) && (m_addQueue.empty()) && (m_deleteQueue.empty()))
return false; return false;
@ -103,8 +120,9 @@ bool TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const
// have yet to have their full certificate chains validated. Full signature checking is // have yet to have their full certificate chains validated. Full signature checking is
// performed here. // performed here.
while (!m_addQueue.empty()) { while (!m_addQueue.empty()) {
m_addQueue.front()->m_error.store((int)m_addQueue.front()->m_certificate.verify(clock, true), std::memory_order_relaxed); SharedPtr< Entry > &qi = m_addQueue.front();
m_bySerial[H384(m_addQueue.front()->m_certificate.serialNo)].move(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(); m_addQueue.pop_front();
} }
@ -114,81 +132,102 @@ bool TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const
m_deleteQueue.pop_front(); m_deleteQueue.pop_front();
} }
Map< H384, Vector< SharedPtr< Entry > > > bySignedCert; // Reset flags for deprecation and a cert being on a trust path, which are
for (;;) { // recomputed when chain and subjects are checked below.
// Validate certificate paths and reject any certificates that do not trace back to a CA. for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
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->error() == ZT_CERTIFICATE_ERROR_NONE) { c->second->m_subjectDeprecated = false;
unsigned int pathLength = 0; c->second->m_onTrustPath = false;
Map< H384, SharedPtr< Entry > >::const_iterator current(c);
Set< H384 > visited; // prevent infinite loops if there's a cycle
for (;;) {
if (pathLength <= current->second->m_certificate.maxPathLength) {
if ((current->second->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) == 0) {
visited.insert(current->second->m_certificate.getSerialNo());
const H384 next(current->second->m_certificate.issuer);
if (visited.find(next) == visited.end()) {
current = m_bySerial.find(next);
if ((current != m_bySerial.end()) && (current->second->error() == ZT_CERTIFICATE_ERROR_NONE)) {
++pathLength;
continue;
}
}
} else {
break; // traced to root CA, abort without setting error
}
}
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< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
if (c->second->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 > &current = m_bySubjectUniqueId[Blob< ZT_CERTIFICATE_MAX_PUBLIC_KEY_SIZE >(c->second->m_certificate.subject.uniqueId, uniqueIdSize)];
if (current) {
exitLoop = false;
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;
}
}
}
}
// 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 mapping of identities to certificates whose subjects reference them. // 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) && (!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 (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(); m_bySubjectIdentity.clear();
for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
if (c->second->error() == ZT_CERTIFICATE_ERROR_NONE) { 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) { 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); const Identity *const id = reinterpret_cast<const Identity *>(c->second->m_certificate.subject.identities[i].identity);
if ((id) && (*id)) // sanity check if ((id) && (*id)) // sanity check
@ -197,10 +236,12 @@ bool TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const
} }
} }
// Purge and return purged certificates if this option is selected. // Purge error certificates and return them if 'purge' is non-NULL. This purges error certificates
// and deprecated certificates not on a trust path. Deprecated certificates on a trust path remain
// as they are still technically valid and other possibly wanted certificates depend on them.
if (purge) { if (purge) {
for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) { for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
if (c->second->error() != ZT_CERTIFICATE_ERROR_NONE) { if ( (c->second->error() != ZT_CERTIFICATE_ERROR_NONE) || ((c->second->m_subjectDeprecated) && (!c->second->m_onTrustPath)) ) {
purge->push_back(c->second); purge->push_back(c->second);
m_bySerial.erase(c++); m_bySerial.erase(c++);
} else { } else {
@ -214,7 +255,7 @@ bool TrustStore::update(const int64_t clock, Vector< SharedPtr< Entry > > *const
Vector< uint8_t > TrustStore::save() const Vector< uint8_t > TrustStore::save() const
{ {
Vector< uint8_t> comp; Vector< uint8_t > comp;
int compSize; int compSize;
{ {
@ -223,24 +264,26 @@ Vector< uint8_t > TrustStore::save() const
RWMutex::RLock l(m_lock); RWMutex::RLock l(m_lock);
b.push_back(0); // version byte // A version byte.
b.push_back(0);
// <size> <certificate> <trust> tuples terminated by a 0 size.
for (Map< H384, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) { 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 Vector< uint8_t > cdata(c->second->certificate().encode());
if (!cdata.empty()) { 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(); const uint32_t localTrust = (uint32_t)c->second->localTrust();
b.push_back((uint8_t)(localTrust >> 24U)); b.push_back((uint8_t)(localTrust >> 24U));
b.push_back((uint8_t)(localTrust >> 16U)); b.push_back((uint8_t)(localTrust >> 16U));
b.push_back((uint8_t)(localTrust >> 8U)); b.push_back((uint8_t)(localTrust >> 8U));
b.push_back((uint8_t)localTrust); b.push_back((uint8_t)localTrust);
const uint32_t size = (uint32_t)cdata.size();
b.push_back((uint8_t)(size >> 24U));
b.push_back((uint8_t)(size >> 16U));
b.push_back((uint8_t)(size >> 8U));
b.push_back((uint8_t)size);
b.insert(b.end(), cdata.begin(), cdata.end());
} }
} }
b.push_back(0);
b.push_back(0);
comp.resize((unsigned long)LZ4_COMPRESSBOUND(b.size()) + 8); 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)); compSize = LZ4_compress_fast(reinterpret_cast<const char *>(b.data()), reinterpret_cast<char *>(comp.data() + 8), (int)b.size(), (int)(comp.size() - 8));
@ -271,10 +314,8 @@ int TrustStore::load(const Vector< uint8_t > &data)
if (data.size() < 8) if (data.size() < 8)
return -1; return -1;
const unsigned long uncompSize = Utils::loadBigEndian<uint32_t>(data.data()); const unsigned long uncompSize = Utils::loadBigEndian< uint32_t >(data.data());
if (uncompSize > (data.size() * 256)) // sanity check if ((uncompSize == 0) || (uncompSize > (data.size() * 128)))
return -1;
if (uncompSize < 1) // no room for at least version and count
return -1; return -1;
Vector< uint8_t > uncomp; Vector< uint8_t > uncomp;
@ -283,7 +324,7 @@ int TrustStore::load(const Vector< uint8_t > &data)
if (LZ4_decompress_safe(reinterpret_cast<const char *>(data.data() + 8), reinterpret_cast<char *>(uncomp.data()), (int)(data.size() - 8), (int)uncompSize) != (int)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; return -1;
const uint8_t *b = uncomp.data(); const uint8_t *b = uncomp.data();
if (Utils::fnv1a32(b, (unsigned int)uncompSize) != Utils::loadBigEndian<uint32_t>(data.data() + 4)) if (Utils::fnv1a32(b, (unsigned int)uncompSize) != Utils::loadBigEndian< uint32_t >(data.data() + 4))
return -1; return -1;
const uint8_t *const eof = b + uncompSize; const uint8_t *const eof = b + uncompSize;
@ -291,23 +332,28 @@ int TrustStore::load(const Vector< uint8_t > &data)
return -1; return -1;
int readCount = 0; int readCount = 0;
for(;;) {
if ((b + 8) > eof)
break;
const uint32_t localTrust = Utils::loadBigEndian<uint32_t>(b);
b += 4;
const uint32_t cdataSize = Utils::loadBigEndian<uint32_t>(b);
b += 4;
if ((b + cdataSize) > eof) for (;;) {
if ((b + 2) > eof)
break;
const uint32_t cdataSize = Utils::loadBigEndian< uint16_t >(b);
b += 2;
if (cdataSize == 0)
break;
if ((b + cdataSize + 4) > eof)
break; break;
Certificate c; Certificate c;
if (c.decode(b, (unsigned int)cdataSize)) { if (c.decode(b, (unsigned int)cdataSize)) {
b += cdataSize;
const uint32_t localTrust = Utils::loadBigEndian< uint32_t >(b);
b += 4;
this->add(c, (unsigned int)localTrust); this->add(c, (unsigned int)localTrust);
++readCount; ++readCount;
} }
b += cdataSize;
} }
return readCount; return readCount;
} }

View file

@ -28,7 +28,20 @@
namespace ZeroTier { namespace ZeroTier {
/** /**
* Certificate store and chain validator * Certificate store and chain validator.
*
* WARNING: SharedPtr<Entry> entries returned from a trust store are valid
* only as long as the trust store exists. The trust store is a core object
* that lives as long as a Node, so this isn't an issue in the core, but it
* should be remembered when testing.
*
* This is because each Entry includes a reference to its parent's mutex and
* is synchronized by this mutex so its fields are safe to access while the
* parent trust store is being modified or synchronized.
*
* This also means entries can't be moved between TrustStore instances,
* hence there are no methods for doing that. There's only one instance in a
* node anyway.
*/ */
class TrustStore class TrustStore
{ {
@ -57,7 +70,10 @@ public:
* @return Local trust bit mask * @return Local trust bit mask
*/ */
ZT_INLINE unsigned int localTrust() const noexcept ZT_INLINE unsigned int localTrust() const noexcept
{ return m_localTrust.load(std::memory_order_relaxed); } {
RWMutex::RLock l(m_lock);
return m_localTrust;
}
/** /**
* Change the local trust of this entry * Change the local trust of this entry
@ -65,7 +81,10 @@ public:
* @param lt New local trust bit mask * @param lt New local trust bit mask
*/ */
ZT_INLINE void setLocalTrust(const unsigned int lt) noexcept ZT_INLINE void setLocalTrust(const unsigned int lt) noexcept
{ m_localTrust.store(lt, std::memory_order_relaxed); } {
RWMutex::Lock l(m_lock);
m_localTrust = lt;
}
/** /**
* Get the error code for this certificate * Get the error code for this certificate
@ -73,23 +92,30 @@ public:
* @return Error or ZT_CERTIFICATE_ERROR_NONE if none * @return Error or ZT_CERTIFICATE_ERROR_NONE if none
*/ */
ZT_INLINE ZT_CertificateError error() const noexcept ZT_INLINE ZT_CertificateError error() const noexcept
{ return (ZT_CertificateError)m_error.load(std::memory_order_relaxed); } {
RWMutex::RLock l(m_lock);
return m_error;
}
private: private:
Entry() {}
Entry(const Entry &) {}
Entry &operator=(const Entry &) { return *this; } Entry &operator=(const Entry &) { return *this; }
ZT_INLINE Entry(const Certificate &cert, const unsigned int lt) noexcept: ZT_INLINE Entry(RWMutex &l, const Certificate &cert, const unsigned int lt) noexcept:
m_lock(l),
m_certificate(cert), m_certificate(cert),
m_localTrust(lt), m_localTrust(lt),
m_error((int)ZT_CERTIFICATE_ERROR_NONE) m_error(ZT_CERTIFICATE_ERROR_NONE),
m_subjectDeprecated(false),
m_onTrustPath(false)
{} {}
Certificate m_certificate; RWMutex &m_lock;
std::atomic< unsigned int > m_localTrust; const Certificate m_certificate;
std::atomic< int > m_error; unsigned int m_localTrust;
ZT_CertificateError m_error;
std::atomic< int > __refCount; std::atomic< int > __refCount;
bool m_subjectDeprecated;
bool m_onTrustPath;
}; };
TrustStore(); TrustStore();

View file

@ -322,11 +322,6 @@ typedef struct
*/ */
#define ZT_CERTIFICATE_MAX_STRING_LENGTH 127 #define ZT_CERTIFICATE_MAX_STRING_LENGTH 127
/**
* Maximum certificate path length to CA (a sanity limit value)
*/
#define ZT_CERTIFICATE_MAX_PATH_LENGTH 256
/** /**
* Certificate is a root CA (local trust flag) * Certificate is a root CA (local trust flag)
*/ */
@ -347,50 +342,50 @@ enum ZT_CertificateError
*/ */
ZT_CERTIFICATE_ERROR_NONE = 0, ZT_CERTIFICATE_ERROR_NONE = 0,
/**
* A newer certificate with the same issuer and subject serial plus CN exists.
*/
ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT = 1,
/** /**
* Certificate format is invalid or required fields are missing * Certificate format is invalid or required fields are missing
*/ */
ZT_CERTIFICATE_ERROR_INVALID_FORMAT = -1, ZT_CERTIFICATE_ERROR_INVALID_FORMAT = 1,
/** /**
* One or more identities in the certificate are invalid or fail consistency check * One or more identities in the certificate are invalid or fail consistency check
*/ */
ZT_CERTIFICATE_ERROR_INVALID_IDENTITY = -2, ZT_CERTIFICATE_ERROR_INVALID_IDENTITY = 2,
/** /**
* Certificate primary signature is invalid * Certificate primary signature is invalid
*/ */
ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE = -3, ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE = 3,
/** /**
* Full chain validation of certificate failed * Full chain validation of certificate failed
*/ */
ZT_CERTIFICATE_ERROR_INVALID_CHAIN = -4, ZT_CERTIFICATE_ERROR_INVALID_CHAIN = 4,
/** /**
* One or more signed components (e.g. a Locator) has an invalid signature. * One or more signed components (e.g. a Locator) has an invalid signature.
*/ */
ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE = -5, ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE = 5,
/** /**
* Unique ID proof signature in subject was not valid. * Unique ID proof signature in subject was not valid.
*/ */
ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF = -6, ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF = 6,
/** /**
* Certificate is missing a required field * Certificate is missing a required field
*/ */
ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS = -7, ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS = 7,
/** /**
* Certificate is expired or not yet in effect * Certificate is expired or not yet in effect
*/ */
ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW = -8 ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW = 8,
/**
* Certificate explicitly revoked
*/
ZT_CERTIFICATE_ERROR_REVOKED = 9
}; };
/** /**
@ -429,6 +424,13 @@ enum ZT_CertificatePublicKeyAlgorithm
*/ */
#define ZT_CERTIFICATE_HASH_SIZE 48 #define ZT_CERTIFICATE_HASH_SIZE 48
/*
* Maximum number of certificates that can be revoked at once.
*
* This shouldn't be changed and is set to be small enough to fit in a packet.
*/
#define ZT_CERTIFICATE_REVOCATION_MAX_CERTIFICATES 24
/** /**
* Information about a real world entity. * Information about a real world entity.
* *
@ -506,7 +508,7 @@ typedef struct
/** /**
* URLs that can be consulted for updates to this certificate. * URLs that can be consulted for updates to this certificate.
*/ */
const char *const *updateURLs; const char **updateURLs;
/** /**
* Number of identities * Number of identities
@ -644,6 +646,47 @@ typedef struct
unsigned int maxPathLength; unsigned int maxPathLength;
} ZT_Certificate; } ZT_Certificate;
/**
* A revocation for one or more certificates.
*/
typedef struct
{
/**
* Certificate issuing this revocation.
*/
uint8_t issuer[ZT_CERTIFICATE_HASH_SIZE];
/**
* Timestamp in milliseconds since epoch.
*/
int64_t timestamp;
/**
* Revoked certificate serials.
*/
uint8_t serials[ZT_CERTIFICATE_REVOCATION_MAX_CERTIFICATES][ZT_CERTIFICATE_HASH_SIZE];
/**
* Short optional human-readable reason or URL.
*/
char reason[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
/**
* Signature of revocation by revoking issuer.
*/
uint8_t signature[ZT_CERTIFICATE_MAX_SIGNATURE_SIZE];
/**
* Number of revoked certificates.
*/
unsigned int count;
/**
* Size of signature in bytes.
*/
unsigned int signatureSize;
} ZT_CertificateRevocation;
/** /**
* A list of certificates * A list of certificates
*/ */

View file

@ -105,7 +105,6 @@ impl<'de> serde::Deserialize<'de> for CertificateSerialNo {
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)] #[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum CertificateError { pub enum CertificateError {
None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE as isize, None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE as isize,
HaveNewerCert = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT as isize,
InvalidFormat = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_FORMAT as isize, InvalidFormat = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_FORMAT as isize,
InvalidIdentity = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_IDENTITY as isize, InvalidIdentity = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_IDENTITY as isize,
InvalidPrimarySignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE as isize, InvalidPrimarySignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE as isize,
@ -114,6 +113,7 @@ pub enum CertificateError {
InvalidUniqueIdProof = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF as isize, InvalidUniqueIdProof = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF as isize,
MissingRequiredFields = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS as isize, MissingRequiredFields = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS as isize,
OutOfValidTimeWindow = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW as isize, OutOfValidTimeWindow = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW as isize,
Revoked = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_REVOKED as isize,
} }
impl ToString for CertificateError { impl ToString for CertificateError {
@ -121,7 +121,6 @@ impl ToString for CertificateError {
String::from( String::from(
match self { match self {
CertificateError::None => "None", CertificateError::None => "None",
CertificateError::HaveNewerCert => "HaveNewerCert",
CertificateError::InvalidFormat => "InvalidFormat", CertificateError::InvalidFormat => "InvalidFormat",
CertificateError::InvalidIdentity => "InvalidIdentity", CertificateError::InvalidIdentity => "InvalidIdentity",
CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature", CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature",
@ -129,7 +128,8 @@ impl ToString for CertificateError {
CertificateError::InvalidComponentSignature => "InvalidComponentSignature", CertificateError::InvalidComponentSignature => "InvalidComponentSignature",
CertificateError::InvalidUniqueIdProof => "InvalidUniqueIdProof", CertificateError::InvalidUniqueIdProof => "InvalidUniqueIdProof",
CertificateError::MissingRequiredFields => "MissingRequiredFields", CertificateError::MissingRequiredFields => "MissingRequiredFields",
CertificateError::OutOfValidTimeWindow => "OutOfValidTimeWindow" CertificateError::OutOfValidTimeWindow => "OutOfValidTimeWindow",
CertificateError::Revoked => "Revoked",
} }
) )
} }
@ -138,7 +138,6 @@ impl ToString for CertificateError {
impl<S: AsRef<str>> From<S> for CertificateError { impl<S: AsRef<str>> From<S> for CertificateError {
fn from(s: S) -> CertificateError { fn from(s: S) -> CertificateError {
match s.as_ref().to_ascii_lowercase().as_str() { match s.as_ref().to_ascii_lowercase().as_str() {
"havenewercert" => CertificateError::HaveNewerCert,
"invalidformat" => CertificateError::InvalidFormat, "invalidformat" => CertificateError::InvalidFormat,
"invalididentity" => CertificateError::InvalidIdentity, "invalididentity" => CertificateError::InvalidIdentity,
"invalidprimarysignature" => CertificateError::InvalidPrimarySignature, "invalidprimarysignature" => CertificateError::InvalidPrimarySignature,
@ -147,6 +146,7 @@ impl<S: AsRef<str>> From<S> for CertificateError {
"invaliduniqueidproof" => CertificateError::InvalidUniqueIdProof, "invaliduniqueidproof" => CertificateError::InvalidUniqueIdProof,
"missingrequiredfields" => CertificateError::MissingRequiredFields, "missingrequiredfields" => CertificateError::MissingRequiredFields,
"outofvalidtimewindow" => CertificateError::OutOfValidTimeWindow, "outofvalidtimewindow" => CertificateError::OutOfValidTimeWindow,
"revoked" => CertificateError::Revoked,
_ => CertificateError::None _ => CertificateError::None
} }
} }
@ -348,10 +348,10 @@ pub struct CertificateSubject {
#[allow(unused)] #[allow(unused)]
pub(crate) struct CertificateSubjectCAPIContainer { pub(crate) struct CertificateSubjectCAPIContainer {
pub(crate) subject: ztcore::ZT_Certificate_Subject, pub(crate) subject: ztcore::ZT_Certificate_Subject,
subject_identities: Pin<Box<[ztcore::ZT_Certificate_Identity]>>, subject_identities: Option<Pin<Box<[ztcore::ZT_Certificate_Identity]>>>,
subject_networks: Pin<Box<[ztcore::ZT_Certificate_Network]>>, subject_networks: Option<Pin<Box<[ztcore::ZT_Certificate_Network]>>>,
subject_urls: Pin<Box<[*const c_char]>>, subject_urls: Option<Pin<Box<[*const c_char]>>>,
subject_urls_strs: Pin<Box<[CString]>>, subject_urls_strs: Option<Pin<Box<[CString]>>>,
} }
impl CertificateSubject { impl CertificateSubject {
@ -407,51 +407,64 @@ impl CertificateSubject {
} }
pub(crate) unsafe fn to_capi(&self) -> CertificateSubjectCAPIContainer { pub(crate) unsafe fn to_capi(&self) -> CertificateSubjectCAPIContainer {
let mut capi_identities: Vec<ztcore::ZT_Certificate_Identity> = Vec::new(); let mut identity_count: c_uint = 0;
let mut capi_networks: Vec<ztcore::ZT_Certificate_Network> = Vec::new(); let mut network_count: c_uint = 0;
let mut capi_urls: Vec<*const c_char> = Vec::new(); let mut update_url_count: c_uint = 0;
let mut capi_urls_strs: Vec<CString> = Vec::new();
if !self.identities.is_empty() { let mut capi_identities = if self.identities.is_empty() {
None
} else {
let mut capi_identities: Vec<ztcore::ZT_Certificate_Identity> = Vec::new();
capi_identities.reserve(self.identities.len()); capi_identities.reserve(self.identities.len());
for i in self.identities.iter() { for i in self.identities.iter() {
capi_identities.push((*i).to_capi()); capi_identities.push((*i).to_capi());
} }
} identity_count = capi_identities.len() as c_uint;
if !self.networks.is_empty() { Some(Pin::from(capi_identities.into_boxed_slice()))
};
let mut capi_networks = if self.networks.is_empty() {
None
} else {
let mut capi_networks: Vec<ztcore::ZT_Certificate_Network> = Vec::new();
capi_networks.reserve(self.networks.len()); capi_networks.reserve(self.networks.len());
for i in self.networks.iter() { for i in self.networks.iter() {
capi_networks.push((*i).to_capi()); capi_networks.push((*i).to_capi());
} }
} network_count = capi_networks.len() as c_uint;
if !self.update_urls.is_empty() { Some(Pin::from(capi_networks.into_boxed_slice()))
capi_urls.reserve(self.update_urls.len()); };
let (mut capi_urls, capi_urls_strs) = if self.update_urls.is_empty() {
(None, None)
} else {
let mut capi_urls_strs: Vec<CString> = Vec::new();
let mut capi_urls: Vec<*const c_char> = Vec::new();
capi_urls_strs.reserve(self.update_urls.len()); capi_urls_strs.reserve(self.update_urls.len());
capi_urls.reserve(self.update_urls.len());
for i in self.update_urls.iter() { for i in self.update_urls.iter() {
let cs = CString::new((*i).as_str()); let cs = CString::new((*i).as_str());
if cs.is_ok() { if cs.is_ok() {
capi_urls_strs.push(cs.unwrap()); capi_urls_strs.push(cs.unwrap());
} }
} }
let capi_urls_strs = Pin::from(capi_urls_strs.into_boxed_slice());
for i in capi_urls_strs.iter() { for i in capi_urls_strs.iter() {
capi_urls.push((*i).as_ptr()); capi_urls.push((*i).as_ptr());
} }
} update_url_count = capi_urls.len() as c_uint;
(Some(Pin::from(capi_urls.into_boxed_slice())), Some(capi_urls_strs))
let mut capi_identities = Pin::from(capi_identities.into_boxed_slice()); };
let mut capi_networks = Pin::from(capi_networks.into_boxed_slice());
let mut capi_urls = Pin::from(capi_urls.into_boxed_slice());
let mut capi_urls_strs = Pin::from(capi_urls_strs.into_boxed_slice());
CertificateSubjectCAPIContainer { CertificateSubjectCAPIContainer {
subject: ztcore::ZT_Certificate_Subject { subject: ztcore::ZT_Certificate_Subject {
timestamp: self.timestamp, timestamp: self.timestamp,
identities: capi_identities.as_mut_ptr(), identities: capi_identities.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
networks: capi_networks.as_mut_ptr(), networks: capi_networks.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
updateURLs: capi_urls.as_ptr(), updateURLs: capi_urls.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
identityCount: capi_identities.len() as c_uint, identityCount: identity_count,
networkCount: capi_networks.len() as c_uint, networkCount: network_count,
updateURLCount: capi_urls.len() as c_uint, updateURLCount: update_url_count,
name: self.name.to_capi(), name: self.name.to_capi(),
uniqueId: vec_to_array(&self.unique_id), uniqueId: vec_to_array(&self.unique_id),
uniqueIdSignature: vec_to_array(&self.unique_id_signature), uniqueIdSignature: vec_to_array(&self.unique_id_signature),