Interim commit of some cert and cert testing work, also other cleanup in Utils.

This commit is contained in:
Adam Ierymenko 2020-06-26 13:09:24 -07:00
parent 5e1b7f2ba6
commit 73d0e2e7e0
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
11 changed files with 743 additions and 440 deletions

View file

@ -80,6 +80,14 @@ Commands:
sign <identity> <file> Sign a file with an identity's key sign <identity> <file> Sign a file with an identity's key
verify <identity> <file> <sig> Verify a signature verify <identity> <file> <sig> Verify a signature
certificate <command> [args] - Certificate commands certificate <command> [args] - Certificate commands
newid Create a new unique subject ID
newcsr <settings> Create a new CSR (signing request)
sign <crl path> <identity path> Sign a CRL and create a certificate
verify <certificate> Verify a certificate
show List certificate for current node
import <certificate> [<trust>] Import certificate into this node
export <serial> Export a certificate from this node
delete <serial> Delete certificate from this node
An <address> may be specified as a 10-digit short ZeroTier address, a An <address> may be specified as a 10-digit short ZeroTier address, a
fingerprint containing both an address and a SHA384 hash, or an identity. fingerprint containing both an address and a SHA384 hash, or an identity.

View file

@ -30,6 +30,7 @@ void Certificate::clear()
m_subjectNetworks.clear(); m_subjectNetworks.clear();
m_updateUrls.clear(); m_updateUrls.clear();
m_subjectCertificates.clear(); m_subjectCertificates.clear();
m_extendedAttributes.clear();
} }
Certificate &Certificate::operator=(const ZT_Certificate &apiCert) Certificate &Certificate::operator=(const ZT_Certificate &apiCert)
@ -45,16 +46,23 @@ Certificate &Certificate::operator=(const Certificate &cert)
// Zero these since we must explicitly attach all the objects from // Zero these since we must explicitly attach all the objects from
// the other certificate to copy them into our containers. // the other certificate to copy them into our containers.
this->subject.identities = nullptr;
this->subject.identityCount = 0; this->subject.identityCount = 0;
this->subject.networks = nullptr;
this->subject.networkCount = 0; this->subject.networkCount = 0;
this->subject.certificates = nullptr;
this->subject.certificateCount = 0; this->subject.certificateCount = 0;
this->subject.updateUrls = nullptr;
this->subject.updateUrlCount = 0; this->subject.updateUrlCount = 0;
this->extendedAttributes = nullptr;
this->extendedAttributesSize = 0;
this->issuer = nullptr;
for (unsigned int i = 0; i < cert.subject.identityCount; ++i) { for (unsigned int i = 0; i < cert.subject.identityCount; ++i) {
if (cert.subject.identities[i].identity) { if (cert.subject.identities[i].identity) {
if (cert.subject.identities[i].locator) if (cert.subject.identities[i].locator)
addSubjectNode(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity), *reinterpret_cast<const Locator *>(cert.subject.identities[i].locator)); addSubjectIdentity(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity), *reinterpret_cast<const Locator *>(cert.subject.identities[i].locator));
else addSubjectNode(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity)); else addSubjectIdentity(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity));
} }
} }
@ -71,10 +79,16 @@ Certificate &Certificate::operator=(const Certificate &cert)
if (cert.subject.updateUrls) { if (cert.subject.updateUrls) {
for (unsigned int i = 0; i < cert.subject.updateUrlCount; ++i) { for (unsigned int i = 0; i < cert.subject.updateUrlCount; ++i) {
if (cert.subject.updateUrls[i]) if (cert.subject.updateUrls[i])
addUpdateUrl(cert.subject.updateUrls[i]); addSubjectUpdateUrl(cert.subject.updateUrls[i]);
} }
} }
if ((cert.extendedAttributes) && (cert.extendedAttributesSize > 0)) {
m_extendedAttributes.assign(cert.extendedAttributes, cert.extendedAttributes + cert.extendedAttributesSize);
this->extendedAttributes = m_extendedAttributes.data();
this->extendedAttributesSize = (unsigned int)m_extendedAttributes.size();
}
if (cert.issuer) { if (cert.issuer) {
m_identities.push_back(*reinterpret_cast<const Identity *>(cert.issuer)); m_identities.push_back(*reinterpret_cast<const Identity *>(cert.issuer));
this->issuer = &(m_identities.back()); this->issuer = &(m_identities.back());
@ -83,7 +97,7 @@ Certificate &Certificate::operator=(const Certificate &cert)
return *this; return *this;
} }
ZT_Certificate_Identity *Certificate::addSubjectNode(const Identity &id) ZT_Certificate_Identity *Certificate::addSubjectIdentity(const Identity &id)
{ {
// Enlarge array of ZT_Certificate_Identity structs and set pointer to potentially reallocated array. // Enlarge array of ZT_Certificate_Identity structs and set pointer to potentially reallocated array.
m_subjectIdentities.resize(++this->subject.identityCount); m_subjectIdentities.resize(++this->subject.identityCount);
@ -99,10 +113,10 @@ ZT_Certificate_Identity *Certificate::addSubjectNode(const Identity &id)
return &(m_subjectIdentities.back()); return &(m_subjectIdentities.back());
} }
ZT_Certificate_Identity *Certificate::addSubjectNode(const Identity &id, const Locator &loc) ZT_Certificate_Identity *Certificate::addSubjectIdentity(const Identity &id, const Locator &loc)
{ {
// Add identity as above. // Add identity as above.
ZT_Certificate_Identity *const n = addSubjectNode(id); ZT_Certificate_Identity *const n = addSubjectIdentity(id);
// Store local copy of locator. // Store local copy of locator.
m_locators.push_back(loc); m_locators.push_back(loc);
@ -138,7 +152,7 @@ void Certificate::addSubjectCertificate(const uint8_t serialNo[ZT_SHA384_DIGEST_
this->subject.certificates = m_subjectCertificates.data(); this->subject.certificates = m_subjectCertificates.data();
} }
void Certificate::addUpdateUrl(const char *url) void Certificate::addSubjectUpdateUrl(const char *url)
{ {
// Store local copy of URL. // Store local copy of URL.
m_strings.push_back(url); m_strings.push_back(url);
@ -159,28 +173,44 @@ Vector< uint8_t > Certificate::encode(const bool omitSignature) const
// format. Custom packed formats are used for credentials as these are smaller // format. Custom packed formats are used for credentials as these are smaller
// and faster to marshal/unmarshal. // and faster to marshal/unmarshal.
d.add("f", this->flags); if (this->flags != 0)
d.add("f", this->flags);
d.add("t", (uint64_t)this->timestamp); d.add("t", (uint64_t)this->timestamp);
d.add("v0", (uint64_t)this->validity[0]); d.add("v0", (uint64_t)this->validity[0]);
d.add("v1", (uint64_t)this->validity[1]); d.add("v1", (uint64_t)this->validity[1]);
if ((this->extendedAttributes) && (this->extendedAttributesSize > 0))
d["x"].assign(this->extendedAttributes, this->extendedAttributes + this->extendedAttributesSize);
d.add("mP", (uint64_t)this->maxPathLength); d.add("mP", (uint64_t)this->maxPathLength);
m_encodeSubject(d, false); m_encodeSubject(this->subject, d, false);
if (this->issuer) if (this->issuer)
d.addO("i", *reinterpret_cast<const Identity *>(this->issuer)); d.addO("i", *reinterpret_cast<const Identity *>(this->issuer));
d.add("iN.c", this->issuerName.country); if (this->issuerName.country[0])
d.add("iN.o", this->issuerName.organization); d.add("iN.c", this->issuerName.country);
d.add("iN.u", this->issuerName.unit); if (this->issuerName.organization[0])
d.add("iN.l", this->issuerName.locality); d.add("iN.o", this->issuerName.organization);
d.add("iN.p", this->issuerName.province); if (this->issuerName.unit[0])
d.add("iN.sA", this->issuerName.streetAddress); d.add("iN.u", this->issuerName.unit);
d.add("iN.pC", this->issuerName.postalCode); if (this->issuerName.locality[0])
d.add("iN.cN", this->issuerName.commonName); d.add("iN.l", this->issuerName.locality);
d.add("iN.sN", this->issuerName.serialNo); if (this->issuerName.province[0])
d.add("iN.e", this->issuerName.email); d.add("iN.p", this->issuerName.province);
d.add("iN.ur", this->issuerName.url); if (this->issuerName.streetAddress[0])
d.add("iN.sA", this->issuerName.streetAddress);
if (this->issuerName.postalCode[0])
d.add("iN.pC", this->issuerName.postalCode);
if (this->issuerName.commonName[0])
d.add("iN.cN", this->issuerName.commonName);
if (this->issuerName.serialNo[0])
d.add("iN.sN", this->issuerName.serialNo);
if (this->issuerName.email[0])
d.add("iN.e", this->issuerName.email);
if (this->issuerName.url[0])
d.add("iN.ur", this->issuerName.url);
if (this->issuerName.host[0])
d.add("iN.h", this->issuerName.host);
if ((!omitSignature) && (this->signatureSize > 0) && (this->signatureSize <= sizeof(this->signature))) if ((!omitSignature) && (this->signatureSize > 0) && (this->signatureSize <= sizeof(this->signature)))
d["si"].assign(this->signature, this->signature + this->signatureSize); d["si"].assign(this->signature, this->signature + this->signatureSize);
@ -204,7 +234,11 @@ bool Certificate::decode(const Vector< uint8_t > &data)
this->validity[0] = (int64_t)d.getUI("v0"); this->validity[0] = (int64_t)d.getUI("v0");
this->validity[1] = (int64_t)d.getUI("v1"); this->validity[1] = (int64_t)d.getUI("v1");
this->maxPathLength = (unsigned int)d.getUI("mP"); this->maxPathLength = (unsigned int)d.getUI("mP");
m_extendedAttributes = d["x"];
if (!m_extendedAttributes.empty()) {
this->extendedAttributes = m_extendedAttributes.data();
this->extendedAttributesSize = (unsigned int)m_extendedAttributes.size();
}
this->subject.timestamp = (int64_t)d.getUI("s.t"); this->subject.timestamp = (int64_t)d.getUI("s.t");
unsigned int cnt = (unsigned int)d.getUI("s.i$"); unsigned int cnt = (unsigned int)d.getUI("s.i$");
@ -220,9 +254,9 @@ bool Certificate::decode(const Vector< uint8_t > &data)
Locator loc; Locator loc;
if (loc.unmarshal(locatorData.data(), (unsigned int)locatorData.size()) <= 0) if (loc.unmarshal(locatorData.data(), (unsigned int)locatorData.size()) <= 0)
return false; return false;
this->addSubjectNode(id, loc); this->addSubjectIdentity(id, loc);
} else { } else {
this->addSubjectNode(id); this->addSubjectIdentity(id);
} }
} }
@ -257,6 +291,7 @@ bool Certificate::decode(const Vector< uint8_t > &data)
d.getS("s.n.pC", this->subject.name.postalCode, sizeof(this->subject.name.postalCode)); d.getS("s.n.pC", this->subject.name.postalCode, sizeof(this->subject.name.postalCode));
d.getS("s.n.e", this->subject.name.email, sizeof(this->subject.name.email)); d.getS("s.n.e", this->subject.name.email, sizeof(this->subject.name.email));
d.getS("s.n.ur", this->subject.name.url, sizeof(this->subject.name.url)); d.getS("s.n.ur", this->subject.name.url, sizeof(this->subject.name.url));
d.getS("s.n.h", this->subject.name.host, sizeof(this->subject.name.host));
const Vector< uint8_t > &issuerData = d["i"]; const Vector< uint8_t > &issuerData = d["i"];
if (!issuerData.empty()) { if (!issuerData.empty()) {
@ -278,12 +313,13 @@ bool Certificate::decode(const Vector< uint8_t > &data)
d.getS("iN.pC", this->issuerName.postalCode, sizeof(this->issuerName.postalCode)); d.getS("iN.pC", this->issuerName.postalCode, sizeof(this->issuerName.postalCode));
d.getS("iN.e", this->issuerName.email, sizeof(this->issuerName.email)); d.getS("iN.e", this->issuerName.email, sizeof(this->issuerName.email));
d.getS("iN.ur", this->issuerName.url, sizeof(this->issuerName.url)); d.getS("iN.ur", this->issuerName.url, sizeof(this->issuerName.url));
d.getS("iN.h", this->issuerName.host, sizeof(this->issuerName.host));
cnt = (unsigned int)d.getUI("u$"); cnt = (unsigned int)d.getUI("u$");
for (unsigned int i = 0; i < cnt; ++i) { for (unsigned int i = 0; i < cnt; ++i) {
const char *const url = d.getS(Dictionary::arraySubscript(tmp, "u$", i), tmp2, sizeof(tmp2)); const char *const url = d.getS(Dictionary::arraySubscript(tmp, "u$", i), tmp2, sizeof(tmp2));
if (url) if (url)
addUpdateUrl(tmp2); addSubjectUpdateUrl(tmp2);
else return false; else return false;
} }
@ -326,7 +362,7 @@ ZT_CertificateError Certificate::verify() const
(this->subject.uniqueId[0] != ZT_CERTIFICATE_UNIQUE_ID_PUBLIC_KEY_TYPE_NIST_P_384)) (this->subject.uniqueId[0] != ZT_CERTIFICATE_UNIQUE_ID_PUBLIC_KEY_TYPE_NIST_P_384))
return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF; return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
Dictionary tmp; Dictionary tmp;
m_encodeSubject(tmp, true); m_encodeSubject(this->subject, tmp, true);
Vector< uint8_t > enc; Vector< uint8_t > enc;
tmp.encode(enc); tmp.encode(enc);
uint8_t h[ZT_SHA384_DIGEST_SIZE]; uint8_t h[ZT_SHA384_DIGEST_SIZE];
@ -368,55 +404,86 @@ ZT_CertificateError Certificate::verify() const
return ZT_CERTIFICATE_ERROR_NONE; return ZT_CERTIFICATE_ERROR_NONE;
} }
void Certificate::m_encodeSubject(Dictionary &d, bool omitUniqueIdProofSignature) const bool Certificate::setSubjectUniqueId(ZT_Certificate_Subject &s, const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_PRIVATE_KEY_SIZE_TYPE_NIST_P_384])
{
Utils::copy<ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384>(s.uniqueId, uniqueId);
s.uniqueIdSize = ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384;
Dictionary d;
m_encodeSubject(s, d, true);
Vector< uint8_t > enc;
d.encode(enc);
uint8_t h[ZT_ECC384_SIGNATURE_HASH_SIZE];
SHA384(h, enc.data(), (unsigned int)enc.size());
ECC384ECDSASign(uniqueIdPrivate, h, s.uniqueIdProofSignature);
s.uniqueIdProofSignatureSize = ZT_ECC384_SIGNATURE_SIZE;
return true;
}
void Certificate::m_encodeSubject(const ZT_Certificate_Subject &s, Dictionary &d, bool omitUniqueIdProofSignature)
{ {
char tmp[256]; char tmp[256];
d.add("s.t", (uint64_t)this->subject.timestamp); d.add("s.t", (uint64_t)s.timestamp);
d.add("s.i$", (uint64_t)this->subject.identityCount); d.add("s.i$", (uint64_t)s.identityCount);
for (unsigned int i = 0; i < this->subject.identityCount; ++i) { for (unsigned int i = 0; i < s.identityCount; ++i) {
if (this->subject.identities[i].identity) if (s.identities[i].identity)
d.addO(Dictionary::arraySubscript(tmp, "s.i$.i", i), *reinterpret_cast<const Identity *>(this->subject.identities[i].identity)); d.addO(Dictionary::arraySubscript(tmp, "s.i$.i", i), *reinterpret_cast<const Identity *>(s.identities[i].identity));
if (this->subject.identities[i].locator) if (s.identities[i].locator)
d.addO(Dictionary::arraySubscript(tmp, "s.i$.l", i), *reinterpret_cast<const Locator *>(this->subject.identities[i].locator)); d.addO(Dictionary::arraySubscript(tmp, "s.i$.l", i), *reinterpret_cast<const Locator *>(s.identities[i].locator));
} }
d.add("s.n$", (uint64_t)this->subject.networkCount); d.add("s.n$", (uint64_t)s.networkCount);
for (unsigned int i = 0; i < this->subject.networkCount; ++i) { for (unsigned int i = 0; i < s.networkCount; ++i) {
d.add(Dictionary::arraySubscript(tmp, "s.n$.i", i), this->subject.networks[i].id); d.add(Dictionary::arraySubscript(tmp, "s.n$.i", i), s.networks[i].id);
Fingerprint fp(this->subject.networks[i].controller); Fingerprint fp(s.networks[i].controller);
d.addO(Dictionary::arraySubscript(tmp, "s.n$.c", i), fp); d.addO(Dictionary::arraySubscript(tmp, "s.n$.c", i), fp);
} }
d.add("s.c$", (uint64_t)this->subject.certificateCount); d.add("s.c$", (uint64_t)s.certificateCount);
for (unsigned int i = 0; i < this->subject.certificateCount; ++i) { for (unsigned int i = 0; i < s.certificateCount; ++i) {
if (this->subject.certificates[i]) if (s.certificates[i])
d[Dictionary::arraySubscript(tmp, "s.c$", i)].assign(this->subject.certificates[i], this->subject.certificates[i] + ZT_SHA384_DIGEST_SIZE); d[Dictionary::arraySubscript(tmp, "s.c$", i)].assign(s.certificates[i], s.certificates[i] + ZT_SHA384_DIGEST_SIZE);
} }
d.add("s.u$", (uint64_t)this->subject.updateUrlCount); d.add("s.u$", (uint64_t)s.updateUrlCount);
if (this->subject.updateUrls) { if (s.updateUrls) {
for (unsigned int i = 0; i < this->subject.updateUrlCount; ++i) for (unsigned int i = 0; i < s.updateUrlCount; ++i)
d.add(Dictionary::arraySubscript(tmp, "s.u$", i), this->subject.updateUrls[i]); d.add(Dictionary::arraySubscript(tmp, "s.u$", i), s.updateUrls[i]);
} }
d.add("s.n.c", this->subject.name.country); if (s.name.country[0])
d.add("s.n.o", this->subject.name.organization); d.add("s.n.c", s.name.country);
d.add("s.n.u", this->subject.name.unit); if (s.name.organization[0])
d.add("s.n.l", this->subject.name.locality); d.add("s.n.o", s.name.organization);
d.add("s.n.p", this->subject.name.province); if (s.name.unit[0])
d.add("s.n.sA", this->subject.name.streetAddress); d.add("s.n.u", s.name.unit);
d.add("s.n.pC", this->subject.name.postalCode); if (s.name.locality[0])
d.add("s.n.cN", this->subject.name.commonName); d.add("s.n.l", s.name.locality);
d.add("s.n.sN", this->subject.name.serialNo); if (s.name.province[0])
d.add("s.n.e", this->subject.name.email); d.add("s.n.p", s.name.province);
d.add("s.n.ur", this->subject.name.url); if (s.name.streetAddress[0])
d.add("s.n.sA", s.name.streetAddress);
if (s.name.postalCode[0])
d.add("s.n.pC", s.name.postalCode);
if (s.name.commonName[0])
d.add("s.n.cN", s.name.commonName);
if (s.name.serialNo[0])
d.add("s.n.sN", s.name.serialNo);
if (s.name.email[0])
d.add("s.n.e", s.name.email);
if (s.name.url[0])
d.add("s.n.ur", s.name.url);
if (s.name.host[0])
d.add("s.n.h", s.name.host);
if ((this->subject.uniqueIdSize > 0) && (this->subject.uniqueIdSize <= ZT_CERTIFICATE_MAX_UNIQUE_ID_SIZE)) if ((s.uniqueIdSize > 0) && (s.uniqueIdSize <= ZT_CERTIFICATE_MAX_UNIQUE_ID_SIZE))
d["s.uI"].assign(this->subject.uniqueId, this->subject.uniqueId + this->subject.uniqueIdSize); d["s.uI"].assign(s.uniqueId, s.uniqueId + s.uniqueIdSize);
if ((!omitUniqueIdProofSignature) && (this->subject.uniqueIdProofSignatureSize > 0) && (this->subject.uniqueIdProofSignatureSize <= ZT_CERTIFICATE_MAX_SIGNATURE_SIZE)) if ((!omitUniqueIdProofSignature) && (s.uniqueIdProofSignatureSize > 0) && (s.uniqueIdProofSignatureSize <= ZT_CERTIFICATE_MAX_SIGNATURE_SIZE))
d["s.uS"].assign(this->subject.uniqueIdProofSignature, this->subject.uniqueIdProofSignature + this->subject.uniqueIdProofSignatureSize); d["s.uS"].assign(s.uniqueIdProofSignature, s.uniqueIdProofSignature + s.uniqueIdProofSignatureSize);
} }
} // namespace ZeroTier } // namespace ZeroTier

View file

@ -74,7 +74,7 @@ public:
* @param id Identity * @param id Identity
* @return Pointer to C struct * @return Pointer to C struct
*/ */
ZT_Certificate_Identity *addSubjectNode(const Identity &id); ZT_Certificate_Identity *addSubjectIdentity(const Identity &id);
/** /**
* Add a subject node/identity with a locator * Add a subject node/identity with a locator
@ -83,7 +83,7 @@ public:
* @param loc Locator signed by identity (signature is NOT checked here) * @param loc Locator signed by identity (signature is NOT checked here)
* @return Pointer to C struct * @return Pointer to C struct
*/ */
ZT_Certificate_Identity *addSubjectNode(const Identity &id, const Locator &loc); ZT_Certificate_Identity *addSubjectIdentity(const Identity &id, const Locator &loc);
/** /**
* Add a subject network * Add a subject network
@ -106,7 +106,20 @@ public:
* *
* @param url Update URL * @param url Update URL
*/ */
void addUpdateUrl(const char *url); void addSubjectUpdateUrl(const char *url);
/**
* Set the extended attributes of this certificate
*
* @param x Extended attributes (set by issuer)
*/
ZT_INLINE void setExtendedAttributes(const Dictionary &x)
{
m_extendedAttributes.clear();
x.encode(m_extendedAttributes);
this->extendedAttributes = m_extendedAttributes.data();
this->extendedAttributesSize = (unsigned int)m_extendedAttributes.size();
}
/** /**
* Marshal this certificate in binary form * Marshal this certificate in binary form
@ -145,6 +158,28 @@ public:
*/ */
ZT_CertificateError verify() const; ZT_CertificateError verify() const;
/**
* Create a subject unique ID and corresponding private key required for use
*
* @param uniqueId Buffer to receive unique ID
* @param uniqueIdPrivate Buffer to receive private key
*/
static ZT_INLINE void createSubjectUniqueId(uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384], uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_PRIVATE_KEY_SIZE_TYPE_NIST_P_384])
{
uniqueId[0] = ZT_CERTIFICATE_UNIQUE_ID_PUBLIC_KEY_TYPE_NIST_P_384;
ECC384GenerateKey(uniqueId + 1, uniqueIdPrivate);
}
/**
* Set the unique ID and unique ID proof signature fields in a subject.
*
* @param s Subject to set
* @param uniqueId Unique ID (public)
* @param uniqueIdPrivate Unique ID private key
* @return True on success
*/
static bool setSubjectUniqueId(ZT_Certificate_Subject &s, const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_PRIVATE_KEY_SIZE_TYPE_NIST_P_384]);
ZT_INLINE unsigned long hashCode() const noexcept ZT_INLINE unsigned long hashCode() const noexcept
{ return (unsigned long)Utils::loadAsIsEndian< uint32_t >(this->serialNo); } { return (unsigned long)Utils::loadAsIsEndian< uint32_t >(this->serialNo); }
@ -162,7 +197,7 @@ public:
{ return memcmp(this->serialNo, c.serialNo, ZT_SHA384_DIGEST_SIZE) >= 0; } { return memcmp(this->serialNo, c.serialNo, ZT_SHA384_DIGEST_SIZE) >= 0; }
private: private:
void m_encodeSubject(Dictionary &d, bool omitUniqueIdProofSignature) const; static void m_encodeSubject(const ZT_Certificate_Subject &s, Dictionary &d, bool omitUniqueIdProofSignature);
// These hold any identity or locator objects that are owned by and should // These hold any identity or locator objects that are owned by and should
// be deleted with this certificate. Lists are used so the pointers never // be deleted with this certificate. Lists are used so the pointers never
@ -177,6 +212,7 @@ private:
Vector< ZT_Certificate_Network > m_subjectNetworks; Vector< ZT_Certificate_Network > m_subjectNetworks;
Vector< const uint8_t * > m_subjectCertificates; Vector< const uint8_t * > m_subjectCertificates;
Vector< const char * > m_updateUrls; Vector< const char * > m_updateUrls;
Vector< uint8_t > m_extendedAttributes;
std::atomic<int> __refCount; std::atomic<int> __refCount;
}; };

View file

@ -36,8 +36,8 @@
#if ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE #if ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE
#error ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX exceeds maximum buffer size #error ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX exceeds maximum buffer size
#endif #endif
#if ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE #if ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE
#error ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX exceeds maximum buffer size #error ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX exceeds maximum buffer size
#endif #endif
namespace ZeroTier { namespace ZeroTier {
@ -83,7 +83,7 @@ Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *const RR,
return Credential::VERIFY_NEED_IDENTITY; return Credential::VERIFY_NEED_IDENTITY;
// Now verify the controller's signature. // Now verify the controller's signature.
uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8]; uint64_t buf[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX / 8];
const unsigned int bufSize = credential.m_fillSigningBuf(buf); const unsigned int bufSize = credential.m_fillSigningBuf(buf);
return peer->identity().verify(buf, bufSize, credential.m_signature, credential.m_signatureLength) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE; return peer->identity().verify(buf, bufSize, credential.m_signature, credential.m_signatureLength) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE;
} }

View file

@ -115,15 +115,22 @@ char *Dictionary::getS(const char *k, char *v, const unsigned int cap) const
if (cap == 0) // sanity check if (cap == 0) // sanity check
return v; return v;
const Vector< uint8_t > &e = (*this)[k]; const Vector< uint8_t > &e = (*this)[k];
if (e.empty()) {
v[0] = 0;
return v;
}
unsigned int i = 0; unsigned int i = 0;
const unsigned int last = cap - 1; const unsigned int last = cap - 1;
for (;;) { for (;;) {
if ((i == last) || (i >= (unsigned int)e.size())) if ((i >= last) || (i >= (unsigned int)e.size())) {
v[i] = 0;
break; break;
v[i] = (char)e[i]; }
if ((v[i] = (char)e[i]) == 0) {
break;
}
++i; ++i;
} }
v[i] = 0;
return v; return v;
} }

View file

@ -134,6 +134,17 @@ public:
int marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], bool excludeSignature = false) const noexcept; int marshal(uint8_t data[ZT_LOCATOR_MARSHAL_SIZE_MAX], bool excludeSignature = false) const noexcept;
int unmarshal(const uint8_t *data, int len) noexcept; int unmarshal(const uint8_t *data, int len) noexcept;
ZT_INLINE bool operator==(const Locator &l) const noexcept
{
return (
(m_ts == l.m_ts) &&
(m_signer == l.m_signer) &&
(m_endpoints == l.m_endpoints) &&
(m_signature == l.m_signature));
}
ZT_INLINE bool operator!=(const Locator &l) const noexcept
{ return !(*this == l); }
private: private:
int64_t m_ts; int64_t m_ts;
Fingerprint m_signer; Fingerprint m_signer;

View file

@ -37,10 +37,10 @@ bool MembershipCredential::agreesWith(const MembershipCredential &other) const n
} }
// us <> them // us <> them
for (FCV<p_Qualifier, ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin());i != m_additionalQualifiers.end();++i) { for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin()); i != m_additionalQualifiers.end(); ++i) {
if (i->delta != 0xffffffffffffffffULL) { if (i->delta != 0xffffffffffffffffULL) {
const uint64_t *v2 = nullptr; const uint64_t *v2 = nullptr;
for (FCV<p_Qualifier, ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(other.m_additionalQualifiers.begin());j != other.m_additionalQualifiers.end();++i) { for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(other.m_additionalQualifiers.begin()); j != other.m_additionalQualifiers.end(); ++i) {
if (j->id == i->id) { if (j->id == i->id) {
v2 = &(j->value); v2 = &(j->value);
break; break;
@ -59,10 +59,10 @@ bool MembershipCredential::agreesWith(const MembershipCredential &other) const n
} }
// them <> us (we need a second pass in case they have qualifiers we don't or vice versa) // them <> us (we need a second pass in case they have qualifiers we don't or vice versa)
for (FCV<p_Qualifier, ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(other.m_additionalQualifiers.begin());i != other.m_additionalQualifiers.end();++i) { for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(other.m_additionalQualifiers.begin()); i != other.m_additionalQualifiers.end(); ++i) {
if (i->delta != 0xffffffffffffffffULL) { if (i->delta != 0xffffffffffffffffULL) {
const uint64_t *v2 = nullptr; const uint64_t *v2 = nullptr;
for (FCV<p_Qualifier, ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(m_additionalQualifiers.begin());j != m_additionalQualifiers.end();++i) { for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator j(m_additionalQualifiers.begin()); j != m_additionalQualifiers.end(); ++i) {
if (j->id == i->id) { if (j->id == i->id) {
v2 = &(j->value); v2 = &(j->value);
break; break;
@ -88,13 +88,13 @@ bool MembershipCredential::agreesWith(const MembershipCredential &other) const n
bool MembershipCredential::sign(const Identity &with) noexcept bool MembershipCredential::sign(const Identity &with) noexcept
{ {
m_signedBy = with.address(); m_signedBy = with.address();
uint64_t buf[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX / 8]; uint64_t buf[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX / 8];
const unsigned int bufSize = m_fillSigningBuf(buf); const unsigned int bufSize = m_fillSigningBuf(buf);
m_signatureLength = with.sign(buf, bufSize, m_signature, sizeof(m_signature)); m_signatureLength = with.sign(buf, bufSize, m_signature, sizeof(m_signature));
return m_signatureLength > 0; return m_signatureLength > 0;
} }
int MembershipCredential::marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX], const bool v2) const noexcept int MembershipCredential::marshal(uint8_t data[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX], const bool v2) const noexcept
{ {
data[0] = v2 ? 2 : 1; data[0] = v2 ? 2 : 1;
@ -164,7 +164,7 @@ int MembershipCredential::unmarshal(const uint8_t *data, int len) noexcept
TriviallyCopyable::memoryZero(this); TriviallyCopyable::memoryZero(this);
const unsigned int numq = Utils::loadBigEndian<uint16_t>(data + 1); const unsigned int numq = Utils::loadBigEndian<uint16_t>(data + 1);
if ((numq < 3) || (numq > (ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS + 3))) if ((numq < 3) || (numq > (ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS + 3)))
return -1; return -1;
int p = 3; int p = 3;
for (unsigned int q = 0;q < numq;++q) { for (unsigned int q = 0;q < numq;++q) {
@ -209,7 +209,7 @@ int MembershipCredential::unmarshal(const uint8_t *data, int len) noexcept
break; break;
default: default:
if (m_additionalQualifiers.size() >= ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS) if (m_additionalQualifiers.size() >= ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS)
return -1; return -1;
m_additionalQualifiers.push_back(p_Qualifier(id, value, delta)); m_additionalQualifiers.push_back(p_Qualifier(id, value, delta));
break; break;
@ -287,7 +287,7 @@ unsigned int MembershipCredential::m_fillSigningBuf(uint64_t *buf) const noexcep
buf[p++] = informational; buf[p++] = informational;
} }
for (FCV<p_Qualifier, ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin());i != m_additionalQualifiers.end();++i) { // NOLINT(modernize-loop-convert) for (FCV<p_Qualifier, ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS>::const_iterator i(m_additionalQualifiers.begin()); i != m_additionalQualifiers.end(); ++i) { // NOLINT(modernize-loop-convert)
buf[p++] = Utils::hton(i->id); buf[p++] = Utils::hton(i->id);
buf[p++] = Utils::hton(i->value); buf[p++] = Utils::hton(i->value);
buf[p++] = Utils::hton(i->delta); buf[p++] = Utils::hton(i->delta);

View file

@ -32,10 +32,10 @@
#include "FCV.hpp" #include "FCV.hpp"
// Maximum number of additional tuples beyond the standard always-present three. // Maximum number of additional tuples beyond the standard always-present three.
#define ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS 8 #define ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS 8
// version + qualifier count + three required qualifiers + additional qualifiers + // version + qualifier count + three required qualifiers + additional qualifiers +
#define ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX (1 + 2 + (3 * 3 * 8) + (ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS * 3 * 8) + 144 + 5 + 2 + 96) #define ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX (1 + 2 + (3 * 3 * 8) + (ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS * 3 * 8) + 144 + 5 + 2 + 96)
namespace ZeroTier { namespace ZeroTier {
@ -186,8 +186,8 @@ public:
// NOTE: right now we use v1 serialization format which works with both ZeroTier 1.x and 2.x. V2 format // NOTE: right now we use v1 serialization format which works with both ZeroTier 1.x and 2.x. V2 format
// will be switched on once 1.x is pretty much dead and out of support. // will be switched on once 1.x is pretty much dead and out of support.
static constexpr int marshalSizeMax() noexcept { return ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX; } static constexpr int marshalSizeMax() noexcept { return ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX; }
int marshal(uint8_t data[ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX],bool v2 = false) const noexcept; int marshal(uint8_t data[ZT_MEMBERSHIP_CREDENTIAL_MARSHAL_SIZE_MAX], bool v2 = false) const noexcept;
int unmarshal(const uint8_t *data,int len) noexcept; int unmarshal(const uint8_t *data,int len) noexcept;
private: private:
@ -203,7 +203,7 @@ private:
ZT_INLINE bool operator<(const p_Qualifier &q) const noexcept { return (id < q.id); } // sort order ZT_INLINE bool operator<(const p_Qualifier &q) const noexcept { return (id < q.id); } // sort order
}; };
FCV<p_Qualifier,ZT_CERTIFICATEOFMEMBERSHIP_MAX_ADDITIONAL_QUALIFIERS> m_additionalQualifiers; FCV<p_Qualifier,ZT_MEMBERSHIP_CREDENTIAL_MAX_ADDITIONAL_QUALIFIERS> m_additionalQualifiers;
int64_t m_timestamp; int64_t m_timestamp;
int64_t m_timestampMaxDelta; int64_t m_timestampMaxDelta;
uint64_t m_networkId; uint64_t m_networkId;

File diff suppressed because it is too large Load diff

View file

@ -702,58 +702,21 @@ static ZT_INLINE void storeLittleEndian(void *const p, const I i) noexcept
* @param dest Destination memory * @param dest Destination memory
* @param src Source memory * @param src Source memory
*/ */
template< unsigned int L > template< unsigned long L >
static ZT_INLINE void copy(void *const dest, const void *const src) noexcept static ZT_INLINE void copy(void *dest, const void *src) noexcept
{ {
#ifdef ZT_ARCH_X64 #if defined(ZT_ARCH_X64) && defined(__GNUC__)
uint8_t *volatile d = reinterpret_cast<uint8_t *>(dest); unsigned long l = L;
const uint8_t *s = reinterpret_cast<const uint8_t *>(src); asm volatile ("rep movsb"
for (unsigned int i = 0; i < (L >> 6U); ++i) { : "=D" (dest),
__m128i x0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s)); "=S" (src),
__m128i x1 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s + 16)); "=c" (l)
__m128i x2 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s + 32)); : "0" (dest),
__m128i x3 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s + 48)); "1" (src),
s += 64; "2" (l)
_mm_storeu_si128(reinterpret_cast<__m128i *>(d), x0); : "memory");
_mm_storeu_si128(reinterpret_cast<__m128i *>(d + 16), x1);
_mm_storeu_si128(reinterpret_cast<__m128i *>(d + 32), x2);
_mm_storeu_si128(reinterpret_cast<__m128i *>(d + 48), x3);
d += 64;
}
if ((L & 32U) != 0) {
__m128i x0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s));
__m128i x1 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s + 16));
s += 32;
_mm_storeu_si128(reinterpret_cast<__m128i *>(d), x0);
_mm_storeu_si128(reinterpret_cast<__m128i *>(d + 16), x1);
d += 32;
}
if ((L & 16U) != 0) {
__m128i x0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(s));
s += 16;
_mm_storeu_si128(reinterpret_cast<__m128i *>(d), x0);
d += 16;
}
if ((L & 8U) != 0) {
*reinterpret_cast<volatile uint64_t *>(d) = *reinterpret_cast<const uint64_t *>(s);
s += 8;
d += 8;
}
if ((L & 4U) != 0) {
*reinterpret_cast<volatile uint32_t *>(d) = *reinterpret_cast<const uint32_t *>(s);
s += 4;
d += 4;
}
if ((L & 2U) != 0) {
*reinterpret_cast<volatile uint16_t *>(d) = *reinterpret_cast<const uint16_t *>(s);
s += 2;
d += 2;
}
if ((L & 1U) != 0) {
*d = *s;
}
#else #else
memcpy(dest,src,L); memcpy(dest, src, L);
#endif #endif
} }
@ -764,8 +727,21 @@ static ZT_INLINE void copy(void *const dest, const void *const src) noexcept
* @param src Source memory * @param src Source memory
* @param len Bytes to copy * @param len Bytes to copy
*/ */
static ZT_INLINE void copy(void *const dest, const void *const src, unsigned int len) noexcept static ZT_INLINE void copy(void *dest, const void *src, unsigned long len) noexcept
{ memcpy(dest, src, len); } {
#if defined(ZT_ARCH_X64) && defined(__GNUC__)
asm volatile ("rep movsb"
: "=D" (dest),
"=S" (src),
"=c" (len)
: "0" (dest),
"1" (src),
"2" (len)
: "memory");
#else
memcpy(dest, src, len);
#endif
}
/** /**
* Zero memory block whose size is known at compile time * Zero memory block whose size is known at compile time
@ -773,7 +749,7 @@ static ZT_INLINE void copy(void *const dest, const void *const src, unsigned int
* @tparam L Size in bytes * @tparam L Size in bytes
* @param dest Memory to zero * @param dest Memory to zero
*/ */
template< unsigned int L > template< unsigned long L >
static ZT_INLINE void zero(void *const dest) noexcept static ZT_INLINE void zero(void *const dest) noexcept
{ memset(dest, 0, L); } { memset(dest, 0, L); }
@ -783,7 +759,7 @@ static ZT_INLINE void zero(void *const dest) noexcept
* @param dest Memory to zero * @param dest Memory to zero
* @param len Size in bytes * @param len Size in bytes
*/ */
static ZT_INLINE void zero(void *const dest, const unsigned int len) noexcept static ZT_INLINE void zero(void *const dest, const unsigned long len) noexcept
{ memset(dest, 0, len); } { memset(dest, 0, len); }
/** /**

View file

@ -323,6 +323,16 @@ typedef struct
*/ */
#define ZT_CERTIFICATE_UNIQUE_ID_PUBLIC_KEY_TYPE_NIST_P_384 1 #define ZT_CERTIFICATE_UNIQUE_ID_PUBLIC_KEY_TYPE_NIST_P_384 1
/**
* Size of a unique ID of the given key type (with type prefix byte)
*/
#define ZT_CERTIFICATE_UNIQUE_ID_SIZE_TYPE_NIST_P_384 50
/**
* Size of the private key corresponding to a unique ID of the given type.
*/
#define ZT_CERTIFICATE_UNIQUE_ID_PRIVATE_KEY_SIZE_TYPE_NIST_P_384 48
/** /**
* Errors returned by functions that verify or handle certificates. * Errors returned by functions that verify or handle certificates.
*/ */
@ -368,24 +378,22 @@ enum ZT_CertificateError
*/ */
ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF = -6, ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF = -6,
/**
* Certificate is not appropriate for this use
*/
ZT_CERTIFICATE_ERROR_INAPPROPRIATE_FOR_USE = -7,
/** /**
* Certificate is missing a required field * Certificate is missing a required field
*/ */
ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS = -8, 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 = -9 ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW = -8
}; };
/** /**
* Information about a real world entity. * Information about a real world entity.
*
* These fields are all optional and are all taken from the
* most common fields present in X509 certificates.
*/ */
typedef struct typedef struct
{ {
@ -400,6 +408,7 @@ typedef struct
char postalCode[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1]; char postalCode[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
char email[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1]; char email[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
char url[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1]; char url[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
char host[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
} ZT_Certificate_Name; } ZT_Certificate_Name;
/** /**
@ -490,12 +499,28 @@ typedef struct
ZT_Certificate_Name name; ZT_Certificate_Name name;
/** /**
* Unique ID, which can be a public key prefixed by a key type. * Globally unique ID for this subject
*
* Unique IDs are actually public keys. Their size makes them globally
* unique (if generated from good randomness) to within ridiculous
* probability bounds. If a subject has a unique ID it must also have
* a unique ID proof signature, which is the signature of the subject
* with the private key corresponding to its unique ID.
*
* This allows subjects to "own" themselves and exist independent of
* CAs or delegated signers. It also allows a certificate for a given
* subject to be updated.
*
* Subject unique IDs are optional. If no unique ID is specified these
* and their corresponding size fields must be empty/zero.
*
* A subject is valid if it has no unique ID or has one with a valid
* proof signature.
*/ */
uint8_t uniqueId[ZT_CERTIFICATE_MAX_UNIQUE_ID_SIZE]; uint8_t uniqueId[ZT_CERTIFICATE_MAX_UNIQUE_ID_SIZE];
/** /**
* If unique ID is a public key, this can be a signature of the subject. * Signature proving ownership of unique ID.
*/ */
uint8_t uniqueIdProofSignature[ZT_CERTIFICATE_MAX_SIGNATURE_SIZE]; uint8_t uniqueIdProofSignature[ZT_CERTIFICATE_MAX_SIGNATURE_SIZE];
@ -557,6 +582,16 @@ typedef struct
*/ */
ZT_Certificate_Name issuerName; ZT_Certificate_Name issuerName;
/**
* Extended attributes set by issuer (in Dictionary format, NULL if none)
*/
uint8_t *extendedAttributes;
/**
* Size of extended attributes field in bytes
*/
unsigned int extendedAttributesSize;
/** /**
* Maximum path length from this certificate toward further certificates. * Maximum path length from this certificate toward further certificates.
* *
@ -1627,7 +1662,7 @@ enum ZT_StateObjectType
ZT_STATE_OBJECT_NETWORK_CONFIG = 6, ZT_STATE_OBJECT_NETWORK_CONFIG = 6,
/** /**
* List of certificates and their local trust, and locally added roots * List of certificates, their local trust, and locally added roots
* *
* Object ID: (none) * Object ID: (none)
* Canonical path: <HOME>/trust * Canonical path: <HOME>/trust