Browse Source

Some cleanup, and fix a really obscure bug in Certificate.

Adam Ierymenko 5 years ago
parent
commit
1eacbdf374

+ 35 - 5
cmd/zt_service_tests/certificate.go

@@ -29,6 +29,12 @@ func TestCertificate() bool {
 		return false
 	}
 
+	uniqueId, uniqueIdPrivate, err := zerotier.NewCertificateSubjectUniqueId(zerotier.CertificateUniqueIdTypeNistP384)
+	if err != nil {
+		fmt.Printf("  Error generating unique ID: %s", err.Error())
+		return false
+	}
+
 	var c zerotier.Certificate
 
 	c.SerialNo = make([]byte, 48)
@@ -39,6 +45,7 @@ func TestCertificate() bool {
 	c.Timestamp = 5678
 	c.Validity[0] = 1010
 	c.Validity[1] = 2020
+
 	c.Subject.Timestamp = 31337
 	c.Subject.Identities = append(c.Subject.Identities, zerotier.CertificateIdentity{
 		Identity: id,
@@ -65,8 +72,8 @@ func TestCertificate() bool {
 	c.Subject.Name.Email = "j"
 	c.Subject.Name.URL = "k"
 	c.Subject.Name.Host = "l"
-	c.Subject.UniqueID = []byte("asdf")
-	c.Subject.UniqueIDProofSignature = []byte("ghij")
+	c.Subject.UniqueID = uniqueId
+
 	c.Issuer = id
 	c.IssuerName.SerialNo = "m"
 	c.IssuerName.CommonName = "n"
@@ -80,6 +87,7 @@ func TestCertificate() bool {
 	c.IssuerName.Email = "v"
 	c.IssuerName.URL = "w"
 	c.IssuerName.Host = "x"
+
 	c.ExtendedAttributes = c.SerialNo
 	c.MaxPathLength = 9999
 	c.Signature = []byte("qwerty")
@@ -96,9 +104,8 @@ func TestCertificate() bool {
 		return false
 	}
 
-	j, _ := json.MarshalIndent(c, "", "  ")
-	j2, _ := json.MarshalIndent(c2, "", "  ")
-
+	j, _ := json.Marshal(c)
+	j2, _ := json.Marshal(c2)
 	if !bytes.Equal(j, j2) {
 		j, _ = json.MarshalIndent(c, "", "  ")
 		fmt.Print("  Deep equality test failed: certificates do not match! (see dumps below)\n\n")
@@ -130,5 +137,28 @@ func TestCertificate() bool {
 	}
 	fmt.Println("OK")
 
+	fmt.Printf("Checking certificate CSR sign/verify... ")
+	csr, err := zerotier.NewCertificateCSR(&c.Subject, uniqueId, uniqueIdPrivate)
+	if err != nil {
+		fmt.Printf("CSR generate FAILED (%s)\n", err.Error())
+		return false
+	}
+	fmt.Printf("CSR size: %d ",len(csr))
+	csr2, err := zerotier.NewCertificateFromBytes(csr, false)
+	if err != nil {
+		fmt.Printf("CSR decode FAILED (%s)\n", err.Error())
+		return false
+	}
+	signedCert, err := csr2.Sign(id)
+	if err != nil {
+		fmt.Printf("CSR sign FAILED (%s)\n", err.Error())
+		return false
+	}
+	if len(signedCert.Signature) == 0 {
+		fmt.Println("CSR sign FAILED (no signature found)", err.Error())
+		return false
+	}
+	fmt.Println("OK")
+
 	return true
 }

+ 159 - 118
core/Certificate.cpp

@@ -14,6 +14,7 @@
 #include "Certificate.hpp"
 #include "SHA512.hpp"
 #include "ECC384.hpp"
+#include "ScopedPtr.hpp"
 
 namespace ZeroTier {
 
@@ -24,10 +25,7 @@ Certificate::Certificate() noexcept
 }
 
 Certificate::Certificate(const ZT_Certificate &apiCert)
-{
-	ZT_Certificate *const sup = this;
-	Utils::copy< sizeof(ZT_Certificate) >(sup, &apiCert);
-}
+{ *this = apiCert; }
 
 Certificate::Certificate(const Certificate &cert)
 { *this = cert; }
@@ -62,22 +60,30 @@ Certificate &Certificate::operator=(const ZT_Certificate &cert)
 	this->signature = nullptr;
 	this->signatureSize = 0;
 
-	for (unsigned int i = 0; i < cert.subject.identityCount; ++i) {
-		if (cert.subject.identities[i].identity) {
-			if (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 addSubjectIdentity(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity));
+	if (cert.subject.identities) {
+		for (unsigned int i = 0; i < cert.subject.identityCount; ++i) {
+			if (cert.subject.identities[i].identity) {
+				if (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 {
+					addSubjectIdentity(*reinterpret_cast<const Identity *>(cert.subject.identities[i].identity));
+				}
+			}
 		}
 	}
 
-	for (unsigned int i = 0; i < cert.subject.networkCount; ++i) {
-		if (cert.subject.networks[i].id)
-			addSubjectNetwork(cert.subject.networks[i].id, cert.subject.networks[i].controller);
+	if (cert.subject.networks) {
+		for (unsigned int i = 0; i < cert.subject.networkCount; ++i) {
+			if (cert.subject.networks[i].id)
+				addSubjectNetwork(cert.subject.networks[i].id, cert.subject.networks[i].controller);
+		}
 	}
 
-	for (unsigned int i = 0; i < cert.subject.certificateCount; ++i) {
-		if (cert.subject.certificates[i])
-			addSubjectCertificate(cert.subject.certificates[i]);
+	if (cert.subject.certificates) {
+		for (unsigned int i = 0; i < cert.subject.certificateCount; ++i) {
+			if (cert.subject.certificates[i])
+				addSubjectCertificate(cert.subject.certificates[i]);
+		}
 	}
 
 	if (cert.subject.updateURLs) {
@@ -99,8 +105,8 @@ Certificate &Certificate::operator=(const ZT_Certificate &cert)
 	}
 
 	if (cert.issuer) {
-		m_identities.push_back(*reinterpret_cast<const Identity *>(cert.issuer));
-		this->issuer = &(m_identities.back());
+		m_identities.push_front(*reinterpret_cast<const Identity *>(cert.issuer));
+		this->issuer = &(m_identities.front());
 	}
 
 	if ((cert.extendedAttributes) && (cert.extendedAttributesSize > 0)) {
@@ -120,16 +126,16 @@ Certificate &Certificate::operator=(const ZT_Certificate &cert)
 
 ZT_Certificate_Identity *Certificate::addSubjectIdentity(const Identity &id)
 {
-	// Enlarge array of ZT_Certificate_Identity structs and set pointer to potentially reallocated array.
-	m_subjectIdentities.resize(++this->subject.identityCount);
-	this->subject.identities = m_subjectIdentities.data();
-
 	// Store a local copy of the actual identity.
-	m_identities.push_back(id);
+	m_identities.push_front(id);
+	m_identities.front().erasePrivateKey();
 
-	// Set ZT_Certificate_Identity struct fields to point to local copy of identity.
-	m_subjectIdentities.back().identity = &(m_identities.back());
+	// Enlarge array of ZT_Certificate_Identity structs and set pointer to potentially reallocated array.
+	m_subjectIdentities.push_back(ZT_Certificate_Identity());
+	m_subjectIdentities.back().identity = &(m_identities.front());
 	m_subjectIdentities.back().locator = nullptr;
+	this->subject.identities = m_subjectIdentities.data();
+	this->subject.identityCount = (unsigned int)m_subjectIdentities.size();
 
 	return &(m_subjectIdentities.back());
 }
@@ -140,10 +146,10 @@ ZT_Certificate_Identity *Certificate::addSubjectIdentity(const Identity &id, con
 	ZT_Certificate_Identity *const n = addSubjectIdentity(id);
 
 	// Store local copy of locator.
-	m_locators.push_back(loc);
+	m_locators.push_front(loc);
 
 	// Set pointer to stored local copy of locator.
-	n->locator = &(m_locators.back());
+	n->locator = &(m_locators.front());
 
 	return n;
 }
@@ -164,23 +170,26 @@ ZT_Certificate_Network *Certificate::addSubjectNetwork(const uint64_t id, const
 void Certificate::addSubjectCertificate(const uint8_t serialNo[ZT_SHA384_DIGEST_SIZE])
 {
 	// Store local copy of serial in m_serials container.
-	m_serials.push_back(SHA384Hash(serialNo));
+	m_serials.push_front(SHA384Hash(serialNo));
 
 	// Enlarge array of uint8_t pointers, set new pointer to local copy of serial, and set
 	// certificates to point to potentially reallocated array.
 	m_subjectCertificates.resize(++this->subject.certificateCount);
-	m_subjectCertificates.back() = m_serials.back().bytes();
+	m_subjectCertificates.back() = m_serials.front().bytes();
 	this->subject.certificates = m_subjectCertificates.data();
 }
 
 void Certificate::addSubjectUpdateUrl(const char *url)
 {
+	if ((!url) || (!url[0]))
+		return;
+
 	// Store local copy of URL.
-	m_strings.push_back(url);
+	m_strings.push_front(url);
 
 	// Add pointer to local copy to pointer array and update C structure to point to
 	// potentially reallocated array.
-	m_updateUrls.push_back(m_strings.back().c_str());
+	m_updateUrls.push_back(m_strings.front().c_str());
 	this->subject.updateURLs = m_updateUrls.data();
 	this->subject.updateURLCount = (unsigned int)m_updateUrls.size();
 }
@@ -193,6 +202,28 @@ void Certificate::setExtendedAttributes(const Dictionary &x)
 	this->extendedAttributesSize = (unsigned int)m_extendedAttributes.size();
 }
 
+bool Certificate::setSubjectUniqueId(const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE])
+{
+	m_subjectUniqueId.assign(uniqueId, uniqueId + ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE);
+	this->subject.uniqueId = m_subjectUniqueId.data();
+	this->subject.uniqueIdSize = ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE;
+
+	Dictionary d;
+	m_encodeSubject(this->subject, d, true);
+	Vector< uint8_t > enc;
+	d.encode(enc);
+	uint8_t h[ZT_SHA384_DIGEST_SIZE];
+	SHA384(h, enc.data(), (unsigned int)enc.size());
+
+	m_subjectUniqueIdProofSignature.resize(ZT_ECC384_SIGNATURE_SIZE);
+	ECC384ECDSASign(uniqueIdPrivate, h, m_subjectUniqueIdProofSignature.data());
+
+	this->subject.uniqueIdProofSignature = m_subjectUniqueIdProofSignature.data();
+	this->subject.uniqueIdProofSignatureSize = ZT_ECC384_SIGNATURE_SIZE;
+
+	return true;
+}
+
 Vector< uint8_t > Certificate::encode(const bool omitSignature) const
 {
 	Vector< uint8_t > enc;
@@ -204,12 +235,14 @@ Vector< uint8_t > Certificate::encode(const bool omitSignature) const
 
 	if (this->flags != 0)
 		d.add("f", this->flags);
-	d.add("t", (uint64_t)this->timestamp);
-	d.add("v#0", (uint64_t)this->validity[0]);
-	d.add("v#1", (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);
+	if (this->timestamp > 0)
+		d.add("t", (uint64_t)this->timestamp);
+	if (this->validity[0] > 0)
+		d.add("v#0", (uint64_t)this->validity[0]);
+	if (this->validity[1] > 0)
+		d.add("v#1", (uint64_t)this->validity[1]);
+	if (this->maxPathLength > 0)
+		d.add("mP", (uint64_t)this->maxPathLength);
 
 	m_encodeSubject(this->subject, d, false);
 
@@ -244,8 +277,8 @@ Vector< uint8_t > Certificate::encode(const bool omitSignature) const
 	if ((this->extendedAttributes) && (this->extendedAttributesSize > 0))
 		d["x"].assign(this->extendedAttributes, this->extendedAttributes + this->extendedAttributesSize);
 
-	if ((!omitSignature) && (this->signatureSize > 0) && (this->signature))
-		d["si"].assign(this->signature, this->signature + this->signatureSize);
+	if ((!omitSignature) && (this->signature) && (this->signatureSize > 0))
+		d["S"].assign(this->signature, this->signature + this->signatureSize);
 
 	d.encode(enc);
 	return enc;
@@ -253,7 +286,7 @@ Vector< uint8_t > Certificate::encode(const bool omitSignature) const
 
 bool Certificate::decode(const void *const data, const unsigned int len)
 {
-	char tmp[256], tmp2[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
+	char tmp[32], tmp2[ZT_CERTIFICATE_MAX_STRING_LENGTH + 1];
 
 	Dictionary d;
 	if (!d.decode(data, len))
@@ -271,46 +304,52 @@ bool Certificate::decode(const void *const data, const unsigned int len)
 
 	unsigned int cnt = (unsigned int)d.getUI("s.i$");
 	for (unsigned int i = 0; i < cnt; ++i) {
-		const Vector< uint8_t > &identityData = d[Dictionary::arraySubscript(tmp, "s.i$.i", i)];
-		if (identityData.empty())
+		const Vector< uint8_t > &identityData = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "s.i$.i", i)];
+		if (identityData.empty()) {
 			return false;
+		}
 		Identity id;
-		if (id.unmarshal(identityData.data(), (unsigned int)identityData.size()) <= 0)
+		if (id.unmarshal(identityData.data(), (unsigned int)identityData.size()) <= 0) {
 			return false;
-		const Vector< uint8_t > &locatorData = d[Dictionary::arraySubscript(tmp, "s.i$.l", i)];
+		}
+		const Vector< uint8_t > &locatorData = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "s.i$.l", i)];
 		if (!locatorData.empty()) {
 			Locator loc;
-			if (loc.unmarshal(locatorData.data(), (unsigned int)locatorData.size()) <= 0)
+			if (loc.unmarshal(locatorData.data(), (unsigned int)locatorData.size()) <= 0) {
 				return false;
+			}
 			this->addSubjectIdentity(id, loc);
 		} else {
 			this->addSubjectIdentity(id);
 		}
 	}
 
-	cnt = (unsigned int)d.getUI("s.n$");
+	cnt = (unsigned int)d.getUI("s.nw$");
 	for (unsigned int i = 0; i < cnt; ++i) {
-		const uint64_t nwid = d.getUI(Dictionary::arraySubscript(tmp, "s.n$.i", i));
-		const Vector< uint8_t > &fingerprintData = d[Dictionary::arraySubscript(tmp, "s.n$.c", i)];
-		if ((nwid == 0) || (fingerprintData.empty()))
+		const uint64_t nwid = d.getUI(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.nw$.i", i));
+		const Vector< uint8_t > &fingerprintData = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "s.nw$.c", i)];
+		if ((nwid == 0) || (fingerprintData.empty())) {
 			return false;
+		}
 		Fingerprint fp;
-		if (fp.unmarshal(fingerprintData.data(), (unsigned int)fingerprintData.size()) <= 0)
+		if (fp.unmarshal(fingerprintData.data(), (unsigned int)fingerprintData.size()) <= 0) {
 			return false;
+		}
 		this->addSubjectNetwork(nwid, fp);
 	}
 
 	cnt = (unsigned int)d.getUI("s.c$");
 	for (unsigned int i = 0; i < cnt; ++i) {
-		const Vector< uint8_t > &serial = d[Dictionary::arraySubscript(tmp, "s.c$", i)];
-		if (serial.size() != ZT_SHA384_DIGEST_SIZE)
+		const Vector< uint8_t > &serial = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "s.c$", i)];
+		if (serial.size() != ZT_SHA384_DIGEST_SIZE) {
 			return false;
+		}
 		this->addSubjectCertificate(serial.data());
 	}
 
 	cnt = (unsigned int)d.getUI("s.u$");
 	for (unsigned int i = 0; i < cnt; ++i)
-		addSubjectUpdateUrl(d.getS(Dictionary::arraySubscript(tmp, "s.u$", i), tmp, sizeof(tmp)));
+		addSubjectUpdateUrl(d.getS(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.u$", i), tmp2, sizeof(tmp2)));
 
 	d.getS("s.n.sN", this->subject.name.serialNo, sizeof(this->subject.name.serialNo));
 	d.getS("s.n.cN", this->subject.name.commonName, sizeof(this->subject.name.commonName));
@@ -340,8 +379,8 @@ bool Certificate::decode(const void *const data, const unsigned int len)
 	if (!issuerData.empty()) {
 		Identity id;
 		if (id.unmarshal(issuerData.data(), (int)issuerData.size()) > 0) {
-			m_identities.push_back(id);
-			this->issuer = reinterpret_cast<const Identity *>(&(m_identities.back()));
+			m_identities.push_front(id);
+			this->issuer = reinterpret_cast<const Identity *>(&(m_identities.front()));
 		}
 	}
 
@@ -360,10 +399,12 @@ bool Certificate::decode(const void *const data, const unsigned int len)
 
 	cnt = (unsigned int)d.getUI("u$");
 	for (unsigned int i = 0; i < cnt; ++i) {
-		const char *const url = d.getS(Dictionary::arraySubscript(tmp, "u$", i), tmp2, sizeof(tmp2));
-		if (url)
-			addSubjectUpdateUrl(tmp2);
-		else return false;
+		const char *const url = d.getS(Dictionary::arraySubscript(tmp, sizeof(tmp), "u$", i), tmp2, sizeof(tmp2));
+		if ((url) && (*url != 0)) {
+			addSubjectUpdateUrl(url);
+		} else {
+			return false;
+		}
 	}
 
 	m_extendedAttributes = d["x"];
@@ -372,7 +413,7 @@ bool Certificate::decode(const void *const data, const unsigned int len)
 		this->extendedAttributesSize = (unsigned int)m_extendedAttributes.size();
 	}
 
-	m_signature = d["si"];
+	m_signature = d["S"];
 	if (!m_signature.empty()) {
 		this->signature = m_signature.data();
 		this->signatureSize = (unsigned int)m_signature.size();
@@ -395,16 +436,23 @@ Vector< uint8_t > Certificate::encodeCSR()
 
 bool Certificate::sign(const Identity &issuer)
 {
-	Vector< uint8_t > enc(encode(true));
+	m_identities.push_front(issuer);
+	m_identities.front().erasePrivateKey();
+	this->issuer = reinterpret_cast<const ZT_Identity *>(&(m_identities.front()));
+
+	const Vector< uint8_t > enc(encode(true));
 	SHA384(this->serialNo, enc.data(), (unsigned int)enc.size());
+
 	uint8_t sig[ZT_SIGNATURE_BUFFER_SIZE];
-	const unsigned int sigSize = issuer.sign(enc.data(), (unsigned int)enc.size(), sig, sizeof(sig));
+	const unsigned int sigSize = issuer.sign(enc.data(), (unsigned int)enc.size(), sig, ZT_SIGNATURE_BUFFER_SIZE);
+
 	if (sigSize > 0) {
 		m_signature.assign(sig, sig + sigSize);
 		this->signature = m_signature.data();
 		this->signatureSize = sigSize;
 		return true;
 	}
+
 	m_signature.clear();
 	this->signature = nullptr;
 	this->signatureSize = 0;
@@ -425,7 +473,7 @@ ZT_CertificateError Certificate::verify() const
 		if (this->subject.uniqueIdProofSignatureSize > 0) {
 			if (
 				(this->subject.uniqueIdProofSignatureSize != ZT_ECC384_SIGNATURE_SIZE) ||
-				(this->subject.uniqueIdSize != (ZT_ECC384_PUBLIC_KEY_SIZE + 1)) ||
+				(this->subject.uniqueIdSize != ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE) ||
 				(this->subject.uniqueId[0] != ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384))
 				return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
 			Dictionary tmp;
@@ -456,7 +504,7 @@ ZT_CertificateError Certificate::verify() const
 				return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
 		}
 
-		if (this->subject.updateURLCount) {
+		if (this->subject.updateURLCount > 0) {
 			if (!this->subject.updateURLs)
 				return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 			for (unsigned int i = 0; i < this->subject.updateURLCount; ++i) {
@@ -471,26 +519,6 @@ ZT_CertificateError Certificate::verify() const
 	return ZT_CERTIFICATE_ERROR_NONE;
 }
 
-bool Certificate::setSubjectUniqueId(const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE])
-{
-	m_subjectUniqueId.assign(uniqueId, uniqueId + ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE);
-	this->subject.uniqueId = m_subjectUniqueId.data();
-	this->subject.uniqueIdSize = ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE;
-
-	Dictionary d;
-	m_encodeSubject(this->subject, d, true);
-	Vector< uint8_t > enc;
-	d.encode(enc);
-	uint8_t h[ZT_SHA384_DIGEST_SIZE];
-	SHA384(h, enc.data(), (unsigned int)enc.size());
-	m_subjectUniqueIdProofSignature.resize(ZT_ECC384_SIGNATURE_SIZE);
-	ECC384ECDSASign(uniqueIdPrivate, h, m_subjectUniqueIdProofSignature.data());
-	this->subject.uniqueIdProofSignature = m_subjectUniqueIdProofSignature.data();
-	this->subject.uniqueIdProofSignatureSize = ZT_ECC384_SIGNATURE_SIZE;
-
-	return true;
-}
-
 void Certificate::m_clear()
 {
 	ZT_Certificate *const sup = this;
@@ -513,35 +541,41 @@ void Certificate::m_clear()
 
 void Certificate::m_encodeSubject(const ZT_Certificate_Subject &s, Dictionary &d, bool omitUniqueIdProofSignature)
 {
-	char tmp[64];
+	char tmp[32];
 
 	d.add("s.t", (uint64_t)s.timestamp);
 
-	d.add("s.i$", (uint64_t)s.identityCount);
-	for (unsigned int i = 0; i < s.identityCount; ++i) {
-		if (s.identities[i].identity)
-			d.addO(Dictionary::arraySubscript(tmp, "s.i$.i", i), *reinterpret_cast<const Identity *>(s.identities[i].identity));
-		if (s.identities[i].locator)
-			d.addO(Dictionary::arraySubscript(tmp, "s.i$.l", i), *reinterpret_cast<const Locator *>(s.identities[i].locator));
+	if (s.identities) {
+		d.add("s.i$", (uint64_t)s.identityCount);
+		for (unsigned int i = 0; i < s.identityCount; ++i) {
+			if (s.identities[i].identity)
+				d.addO(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.i$.i", i), *reinterpret_cast<const Identity *>(s.identities[i].identity));
+			if (s.identities[i].locator)
+				d.addO(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.i$.l", i), *reinterpret_cast<const Locator *>(s.identities[i].locator));
+		}
 	}
 
-	d.add("s.n$", (uint64_t)s.networkCount);
-	for (unsigned int i = 0; i < s.networkCount; ++i) {
-		d.add(Dictionary::arraySubscript(tmp, "s.n$.i", i), s.networks[i].id);
-		Fingerprint fp(s.networks[i].controller);
-		d.addO(Dictionary::arraySubscript(tmp, "s.n$.c", i), fp);
+	if (s.networks) {
+		d.add("s.nw$", (uint64_t)s.networkCount);
+		for (unsigned int i = 0; i < s.networkCount; ++i) {
+			d.add(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.nw$.i", i), s.networks[i].id);
+			Fingerprint fp(s.networks[i].controller);
+			d.addO(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.nw$.c", i), fp);
+		}
 	}
 
-	d.add("s.c$", (uint64_t)s.certificateCount);
-	for (unsigned int i = 0; i < s.certificateCount; ++i) {
-		if (s.certificates[i])
-			d[Dictionary::arraySubscript(tmp, "s.c$", i)].assign(s.certificates[i], s.certificates[i] + ZT_SHA384_DIGEST_SIZE);
+	if (s.certificates) {
+		d.add("s.c$", (uint64_t)s.certificateCount);
+		for (unsigned int i = 0; i < s.certificateCount; ++i) {
+			if (s.certificates[i])
+				d[Dictionary::arraySubscript(tmp, sizeof(tmp), "s.c$", i)].assign(s.certificates[i], s.certificates[i] + ZT_SHA384_DIGEST_SIZE);
+		}
 	}
 
-	d.add("s.u$", (uint64_t)s.updateURLCount);
 	if (s.updateURLs) {
+		d.add("s.u$", (uint64_t)s.updateURLCount);
 		for (unsigned int i = 0; i < s.updateURLCount; ++i)
-			d.add(Dictionary::arraySubscript(tmp, "s.u$", i), s.updateURLs[i]);
+			d.add(Dictionary::arraySubscript(tmp, sizeof(tmp), "s.u$", i), s.updateURLs[i]);
 	}
 
 	if (s.name.country[0])
@@ -569,9 +603,9 @@ void Certificate::m_encodeSubject(const ZT_Certificate_Subject &s, Dictionary &d
 	if (s.name.host[0])
 		d.add("s.n.h", s.name.host);
 
-	if ((s.uniqueIdSize > 0) && (s.uniqueId != nullptr))
+	if ((s.uniqueId) && (s.uniqueIdSize > 0))
 		d["s.uI"].assign(s.uniqueId, s.uniqueId + s.uniqueIdSize);
-	if ((!omitUniqueIdProofSignature) && (s.uniqueIdProofSignatureSize > 0) && (s.uniqueIdProofSignature != nullptr))
+	if ((!omitUniqueIdProofSignature) && (s.uniqueIdProofSignature) && (s.uniqueIdProofSignatureSize > 0))
 		d["s.uS"].assign(s.uniqueIdProofSignature, s.uniqueIdProofSignature + s.uniqueIdProofSignatureSize);
 }
 
@@ -586,7 +620,7 @@ int ZT_Certificate_newSubjectUniqueId(
 	void *uniqueIdPrivate,
 	int *uniqueIdPrivateSize)
 {
-	switch(type) {
+	switch (type) {
 		case ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384:
 			if ((*uniqueIdSize < ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE) || (*uniqueIdPrivateSize < ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE))
 				return ZT_RESULT_ERROR_BAD_PARAMETER;
@@ -631,15 +665,22 @@ int ZT_Certificate_sign(
 {
 	if (!cert)
 		return ZT_RESULT_ERROR_BAD_PARAMETER;
-	ZeroTier::Certificate c(*cert);
-	if (!c.sign(*reinterpret_cast<const ZeroTier::Identity *>(signer)))
-		return ZT_RESULT_ERROR_BAD_PARAMETER;
-	ZeroTier::Vector< uint8_t > enc(c.encode());
-	if ((int)enc.size() > *signedCertSize)
-		return ZT_RESULT_ERROR_BAD_PARAMETER;
-	ZeroTier::Utils::copy(signedCert, enc.data(), (unsigned int)enc.size());
-	*signedCertSize = (int)enc.size();
-	return ZT_RESULT_OK;
+
+	try {
+		const ZeroTier::ScopedPtr< ZeroTier::Certificate > c(new ZeroTier::Certificate(*cert));
+		if (!c->sign(*reinterpret_cast<const ZeroTier::Identity *>(signer)))
+			return ZT_RESULT_ERROR_INTERNAL;
+
+		const ZeroTier::Vector< uint8_t > enc(c->encode());
+		if ((int)enc.size() > *signedCertSize)
+			return ZT_RESULT_ERROR_BAD_PARAMETER;
+		ZeroTier::Utils::copy(signedCert, enc.data(), (unsigned int)enc.size());
+		*signedCertSize = (int)enc.size();
+
+		return ZT_RESULT_OK;
+	} catch ( ... ) {
+		return ZT_RESULT_FATAL_ERROR_INTERNAL;
+	}
 }
 
 enum ZT_CertificateError ZT_Certificate_decode(
@@ -649,7 +690,7 @@ enum ZT_CertificateError ZT_Certificate_decode(
 	int verify)
 {
 	try {
-		if (!decodedCert)
+		if ((!decodedCert) || (!cert) || (certSize <= 0))
 			return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 		*decodedCert = nullptr;
 		ZeroTier::Certificate *const c = new ZeroTier::Certificate();
@@ -658,7 +699,7 @@ enum ZT_CertificateError ZT_Certificate_decode(
 			return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 		}
 		if (verify) {
-			ZT_CertificateError err = c->verify();
+			const ZT_CertificateError err = c->verify();
 			if (err != ZT_CERTIFICATE_ERROR_NONE) {
 				delete c;
 				return err;
@@ -666,7 +707,7 @@ enum ZT_CertificateError ZT_Certificate_decode(
 		}
 		*decodedCert = c;
 		return ZT_CERTIFICATE_ERROR_NONE;
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 	}
 }
@@ -693,7 +734,7 @@ enum ZT_CertificateError ZT_Certificate_verify(const ZT_Certificate *cert)
 		if (!cert)
 			return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 		return ZeroTier::Certificate(*cert).verify();
-	} catch ( ... ) {
+	} catch (...) {
 		return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
 	}
 }
@@ -704,7 +745,7 @@ const ZT_Certificate *ZT_Certificate_clone(const ZT_Certificate *cert)
 		if (!cert)
 			return nullptr;
 		return (const ZT_Certificate *)(new ZeroTier::Certificate(*cert));
-	} catch ( ... ) {
+	} catch (...) {
 		return nullptr;
 	}
 }

+ 15 - 15
core/Certificate.hpp

@@ -114,6 +114,17 @@ public:
 	 */
 	void setExtendedAttributes(const Dictionary &x);
 
+	/**
+	 * Set the unique ID of this certificate's subject
+	 *
+	 * This must be done after all other fields in the subject are set.
+	 *
+	 * @param uniqueId Unique ID
+	 * @param uniqueIdPrivate Private key associated with unique ID to prove ownership of it
+	 * @return True if successful
+	 */
+	bool setSubjectUniqueId(const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE]);
+
 	/**
 	 * Marshal this certificate in binary form
 	 *
@@ -159,17 +170,6 @@ public:
 	 */
 	ZT_CertificateError verify() const;
 
-	/**
-	 * Set the unique ID of this certificate's subject
-	 *
-	 * This must be done after all other fields in the subject are set.
-	 *
-	 * @param uniqueId Unique ID
-	 * @param uniqueIdPrivate Private key associated with unique ID to prove ownership of it
-	 * @return True if successful
-	 */
-	bool setSubjectUniqueId(const uint8_t uniqueId[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE], const uint8_t uniqueIdPrivate[ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE]);
-
 	/**
 	 * Create a subject unique ID and corresponding private key required for use
 	 *
@@ -211,10 +211,10 @@ private:
 	// 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
 	// change.
-	List< Identity > m_identities;
-	List< Locator > m_locators;
-	List< String > m_strings;
-	List< SHA384Hash > m_serials;
+	ForwardList< Identity > m_identities;
+	ForwardList< Locator > m_locators;
+	ForwardList< String > m_strings;
+	ForwardList< SHA384Hash > m_serials;
 
 	// These are stored in a vector because the memory needs to be contiguous.
 	Vector< ZT_Certificate_Identity > m_subjectIdentities;

+ 15 - 0
core/Containers.hpp

@@ -28,6 +28,7 @@
 #ifdef __CPP11__
 #include <atomic>
 #include <unordered_map>
+#include <forward_list>
 #endif
 
 namespace ZeroTier {
@@ -128,6 +129,20 @@ template< typename V >
 class List : public std::list< V >
 {};
 
+#ifdef __CPP11__
+
+template< typename V >
+class ForwardList : public std::forward_list< V >
+{};
+
+#else
+
+template< typename V >
+class ForwardList : public std::list< V >
+{};
+
+#endif
+
 template< typename V >
 class Set : public std::set< V, std::less< V > >
 {};

+ 16 - 55
core/Dictionary.cpp

@@ -22,23 +22,15 @@ Dictionary::~Dictionary()
 {}
 
 Vector< uint8_t > &Dictionary::operator[](const char *const k)
-{ return m_entries[s_key(k)]; }
+{ return m_entries[k]; }
 
 const Vector< uint8_t > &Dictionary::operator[](const char *const k) const
 {
 	static const Vector< uint8_t > s_emptyEntry;
-	const SortedMap< String, Vector< uint8_t > >::const_iterator e(m_entries.find(s_key(k)));
+	const SortedMap< String, Vector< uint8_t > >::const_iterator e(m_entries.find(k));
 	return (e == m_entries.end()) ? s_emptyEntry : e->second;
 }
 
-void Dictionary::add(const char *k, bool v)
-{
-	Vector< uint8_t > &e = (*this)[k];
-	e.resize(2);
-	e[0] = (uint8_t)(v ? '1' : '0');
-	e[1] = 0;
-}
-
 void Dictionary::add(const char *k, const Address &v)
 {
 	char tmp[ZT_ADDRESS_STRING_SIZE_MAX];
@@ -66,24 +58,6 @@ void Dictionary::add(const char *k, const void *data, unsigned int len)
 	}
 }
 
-bool Dictionary::getB(const char *k, bool dfl) const
-{
-	const Vector< uint8_t > &e = (*this)[k];
-	if (!e.empty()) {
-		switch ((char)e[0]) {
-			case '1':
-			case 't':
-			case 'T':
-			case 'y':
-			case 'Y':
-				return true;
-			default:
-				return false;
-		}
-	}
-	return dfl;
-}
-
 uint64_t Dictionary::getUI(const char *k, uint64_t dfl) const
 {
 	char tmp[32];
@@ -97,23 +71,22 @@ char *Dictionary::getS(const char *k, char *v, const unsigned int cap) const
 {
 	if (cap == 0) // sanity check
 		return v;
+
 	const Vector< uint8_t > &e = (*this)[k];
 	if (e.empty()) {
 		v[0] = 0;
 		return v;
 	}
-	unsigned int i = 0;
-	const unsigned int last = cap - 1;
-	for (;;) {
+
+	for (unsigned int i = 0, last = (cap - 1);; ++i) {
 		if ((i >= last) || (i >= (unsigned int)e.size())) {
 			v[i] = 0;
 			break;
 		}
-		if ((v[i] = (char)e[i]) == 0) {
+		if ((v[i] = (char)e[i]) == 0)
 			break;
-		}
-		++i;
 	}
+
 	return v;
 }
 
@@ -131,7 +104,6 @@ void Dictionary::encode(Vector< uint8_t > &out) const
 			s_appendValueByte(out, *i);
 		out.push_back((uint8_t)'\n');
 	}
-	out.push_back(0);
 }
 
 bool Dictionary::decode(const void *data, unsigned int len)
@@ -176,20 +148,24 @@ bool Dictionary::decode(const void *data, unsigned int len)
 			} else {
 				if (c == (uint8_t)'=') {
 					v = &m_entries[k];
-				} else if ((c < 33) || (c > 126) || (c == 92)) {
-					return false;
 				} else {
 					k.push_back(c);
 				}
 			}
-		} else break;
+		} else {
+			break;
+		}
 	}
 	return true;
 }
 
-char *Dictionary::arraySubscript(char buf[256],const char *name,const unsigned long sub) noexcept
+char *Dictionary::arraySubscript(char *buf, unsigned int bufSize, const char *name, const unsigned long sub) noexcept
 {
-	for(unsigned int i=0;i<(256 - 17);++i) {
+	if (bufSize < 17) { // sanity check
+		buf[0] = 0;
+		return buf;
+	}
+	for (unsigned int i = 0; i < (bufSize - 17); ++i) {
 		if ((buf[i] = name[i]) == 0) {
 			buf[i++] = '#';
 			Utils::hex(sub, buf + i);
@@ -200,19 +176,4 @@ char *Dictionary::arraySubscript(char buf[256],const char *name,const unsigned l
 	return buf;
 }
 
-String Dictionary::s_key(const char *k) noexcept
-{
-	String buf;
-	if (likely(k != nullptr)) {
-		for (;;) {
-			const char c = *(k++);
-			if ((c >= 33) && (c <= 126) && (c != 61) && (c != 92)) // printable ASCII with no spaces, equals, or backslash
-				buf.push_back(c);
-			else if (c == 0)
-				break;
-		}
-	}
-	return buf;
-}
-
 } // namespace ZeroTier

+ 23 - 32
core/Dictionary.hpp

@@ -42,9 +42,10 @@ class Identity;
 class Dictionary
 {
 public:
-	typedef SortedMap< String, Vector < uint8_t > >::const_iterator const_iterator;
+	typedef SortedMap< String, Vector< uint8_t > >::const_iterator const_iterator;
 
 	Dictionary();
+
 	~Dictionary();
 
 	/**
@@ -53,7 +54,7 @@ public:
 	 * @param k Key to look up
 	 * @return Reference to value
 	 */
-	Vector <uint8_t> &operator[](const char *k);
+	Vector< uint8_t > &operator[](const char *k);
 
 	/**
 	 * Get a const reference to a value
@@ -61,7 +62,7 @@ public:
 	 * @param k Key to look up
 	 * @return Reference to value or to empty vector if not found
 	 */
-	const Vector <uint8_t> &operator[](const char *k) const;
+	const Vector< uint8_t > &operator[](const char *k) const;
 
 	/**
 	 * @return Start of key->value pairs
@@ -75,11 +76,6 @@ public:
 	ZT_INLINE const_iterator end() const noexcept
 	{ return m_entries.end(); }
 
-	/**
-	 * Add a boolean as '1' or '0'
-	 */
-	void add(const char *k, bool v);
-
 	/**
 	 * Add an integer as a hexadecimal string value
 	 *
@@ -87,7 +83,10 @@ public:
 	 * @param v Integer to set, will be cast to uint64_t and stored as hex
 	 */
 	ZT_INLINE void add(const char *const k, const uint64_t v)
-	{ char buf[17]; add(k, Utils::hex((uint64_t)(v), buf)); }
+	{
+		char buf[24];
+		add(k, Utils::hex((uint64_t)(v), buf));
+	}
 
 	/**
 	 * Add an integer as a hexadecimal string value
@@ -96,7 +95,10 @@ public:
 	 * @param v Integer to set, will be cast to uint64_t and stored as hex
 	 */
 	ZT_INLINE void add(const char *const k, const int64_t v)
-	{ char buf[17]; add(k, Utils::hex((uint64_t)(v), buf)); }
+	{
+		char buf[24];
+		add(k, Utils::hex((uint64_t)(v), buf));
+	}
 
 	/**
 	 * Add an address in 10-digit hex string format
@@ -113,15 +115,6 @@ public:
 	 */
 	void add(const char *k, const void *data, unsigned int len);
 
-	/**
-	 * Get a boolean
-	 *
-	 * @param k Key to look up
-	 * @param dfl Default value (default: false)
-	 * @return Value of key or default if not found
-	 */
-	bool getB(const char *k, bool dfl = false) const;
-
 	/**
 	 * Get an integer
 	 *
@@ -171,13 +164,14 @@ public:
 	template< typename T >
 	ZT_INLINE bool addO(const char *k, T &obj)
 	{
-		uint8_t tmp[4096];
-		static_assert(sizeof(tmp) >= T::marshalSizeMax(),"buffer too small");
-		int l = obj.marshal(tmp);
+		Vector< uint8_t > &d = (*this)[k];
+		d.resize(T::marshalSizeMax());
+		const int l = obj.marshal(d.data());
 		if (l > 0) {
-			(*this)[k].assign(tmp, tmp + l);
+			d.resize(l);
 			return true;
 		}
+		d.clear();
 		return false;
 	}
 
@@ -203,7 +197,7 @@ public:
 	 *
 	 * @param out String encoded dictionary
 	 */
-	void encode(Vector <uint8_t> &out) const;
+	void encode(Vector< uint8_t > &out) const;
 
 	/**
 	 * Decode a string encoded dictionary
@@ -362,7 +356,7 @@ public:
 	template< typename V, typename T >
 	static ZT_INLINE int appendObject(V &out, const char *const k, const T &v)
 	{
-		uint8_t tmp[4096]; // large enough for any current object
+		uint8_t tmp[2048]; // large enough for any current object
 		if (T::marshalSizeMax() > sizeof(tmp))
 			return -1;
 		const int mlen = v.marshal(tmp);
@@ -379,7 +373,7 @@ public:
 	 * @param sub Subscript index
 	 * @return Pointer to 'buf'
 	 */
-	static char *arraySubscript(char buf[256],const char *name,const unsigned long sub) noexcept;
+	static char *arraySubscript(char *buf, unsigned int bufSize, const char *name, const unsigned long sub) noexcept;
 
 private:
 	template< typename V >
@@ -417,20 +411,17 @@ private:
 	{
 		for (;;) {
 			const char c = *(k++);
-			if ((c >= 33) && (c <= 126) && (c != 61) && (c != 92)) // printable ASCII with no spaces, equals, or backslash
-				out.push_back((uint8_t)c);
-			else if (c == 0)
+			if (c == 0)
 				break;
+			out.push_back((uint8_t)c);
 		}
 		out.push_back((uint8_t)'=');
 	}
 
-	static String s_key(const char *k) noexcept;
-
 	// Dictionary maps need to be sorted so that they always encode in the same order
 	// to yield blobs that can be hashed and signed reproducibly. Other than for areas
 	// where dictionaries are signed and verified the order doesn't matter.
-	SortedMap < String, Vector< uint8_t > > m_entries;
+	SortedMap< String, Vector< uint8_t > > m_entries;
 };
 
 } // namespace ZeroTier

+ 12 - 7
core/Fingerprint.hpp

@@ -37,7 +37,7 @@ public:
 	{ memoryZero(this); }
 
 	ZT_INLINE Fingerprint(const ZT_Fingerprint &fp) noexcept
-	{ Utils::copy<sizeof(ZT_Fingerprint)>(this, &fp); }
+	{ Utils::copy< sizeof(ZT_Fingerprint) >(this, &fp); }
 
 	/**
 	 * @return True if hash is not all zero (missing/unspecified)
@@ -59,7 +59,12 @@ public:
 		}
 		return s;
 	}
-	ZT_INLINE String toString() const { char tmp[ZT_FINGERPRINT_STRING_SIZE_MAX]; return String(toString(tmp)); }
+
+	ZT_INLINE String toString() const
+	{
+		char tmp[ZT_FINGERPRINT_STRING_SIZE_MAX];
+		return String(toString(tmp));
+	}
 
 	/**
 	 * Set this fingerprint to a base32-encoded string
@@ -71,18 +76,18 @@ public:
 	{
 		if (!s)
 			return false;
-		const int l = (int) strlen(s);
+		const int l = (int)strlen(s);
 		if (l < ZT_ADDRESS_LENGTH_HEX)
 			return false;
 		char a[ZT_ADDRESS_LENGTH_HEX + 1];
-		Utils::copy<ZT_ADDRESS_LENGTH_HEX>(a, s);
+		Utils::copy< ZT_ADDRESS_LENGTH_HEX >(a, s);
 		a[ZT_ADDRESS_LENGTH_HEX] = 0;
 		this->address = Utils::hexStrToU64(a) & ZT_ADDRESS_MASK;
 		if (l > (ZT_ADDRESS_LENGTH_HEX + 1)) {
 			if (Utils::b32d(s + (ZT_ADDRESS_LENGTH_HEX + 1), this->hash, ZT_FINGERPRINT_HASH_SIZE) != ZT_FINGERPRINT_HASH_SIZE)
 				return false;
 		} else {
-			Utils::zero<ZT_FINGERPRINT_HASH_SIZE>(this->hash);
+			Utils::zero< ZT_FINGERPRINT_HASH_SIZE >(this->hash);
 		}
 		return true;
 	}
@@ -120,7 +125,7 @@ public:
 	ZT_INLINE int marshal(uint8_t data[ZT_FINGERPRINT_MARSHAL_SIZE]) const noexcept
 	{
 		Address(this->address).copyTo(data);
-		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(data + ZT_ADDRESS_LENGTH,this->hash);
+		Utils::copy< ZT_FINGERPRINT_HASH_SIZE >(data + ZT_ADDRESS_LENGTH, this->hash);
 		return ZT_FINGERPRINT_MARSHAL_SIZE;
 	}
 
@@ -129,7 +134,7 @@ public:
 		if (unlikely(len < ZT_FINGERPRINT_MARSHAL_SIZE))
 			return -1;
 		this->address = Address(data);
-		Utils::copy<ZT_FINGERPRINT_HASH_SIZE>(hash,data + ZT_ADDRESS_LENGTH);
+		Utils::copy< ZT_FINGERPRINT_HASH_SIZE >(hash, data + ZT_ADDRESS_LENGTH);
 		return ZT_FINGERPRINT_MARSHAL_SIZE;
 	}
 

+ 8 - 6
core/Identity.cpp

@@ -258,6 +258,7 @@ unsigned int Identity::sign(const void *data, unsigned int len, void *sig, unsig
 					C25519::sign(m_priv, m_pub, data, len, sig);
 					return ZT_C25519_SIGNATURE_LEN;
 				}
+				break;
 			case P384:
 				if (siglen >= ZT_ECC384_SIGNATURE_SIZE) {
 					// SECURITY: signatures also include the public keys to further enforce their coupling.
@@ -267,6 +268,7 @@ unsigned int Identity::sign(const void *data, unsigned int len, void *sig, unsig
 					ECC384ECDSASign(m_priv + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, h, (uint8_t *)sig);
 					return ZT_ECC384_SIGNATURE_SIZE;
 				}
+				break;
 		}
 	}
 	return 0;
@@ -350,6 +352,8 @@ char *Identity::toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER
 			*p = (char)0;
 			return buf;
 		}
+		default:
+			buf[0] = 0;
 	}
 
 	return nullptr;
@@ -445,10 +449,9 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], const bool inc
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE] = ZT_C25519_COMBINED_PRIVATE_KEY_SIZE;
 				Utils::copy< ZT_C25519_COMBINED_PRIVATE_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1, m_priv);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1 + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE;
-			} else {
-				data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE] = 0;
-				return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1;
 			}
+			data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE] = 0;
+			return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1;
 
 		case P384:
 			data[ZT_ADDRESS_LENGTH] = (uint8_t)P384;
@@ -457,10 +460,9 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], const bool inc
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
 				Utils::copy< ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1, m_priv);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
-			} else {
-				data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = 0;
-				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1;
 			}
+			data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = 0;
+			return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1;
 
 	}
 	return -1;

+ 29 - 0
core/Identity.hpp

@@ -68,6 +68,12 @@ public:
 		memoryZero(this);
 	}
 
+	ZT_INLINE Identity(const Identity &id) noexcept
+	{
+		Utils::memoryLock(this, sizeof(Identity));
+		Utils::copy< sizeof(Identity) >(this, &id);
+	}
+
 	/**
 	 * Construct identity from string
 	 *
@@ -88,6 +94,13 @@ public:
 		Utils::burn(reinterpret_cast<void *>(&this->m_priv), sizeof(this->m_priv));
 	}
 
+	ZT_INLINE Identity &operator=(const Identity &id) noexcept
+	{
+		if (likely(this != &id))
+			Utils::copy< sizeof(Identity) >(this, &id);
+		return *this;
+	}
+
 	/**
 	 * Set identity to NIL value (all zero)
 	 */
@@ -211,6 +224,15 @@ public:
 	 */
 	bool fromString(const char *str);
 
+	/**
+	 * Erase any private key in this identity object
+	 */
+	ZT_INLINE void erasePrivateKey() noexcept
+	{
+		Utils::burn(m_priv, sizeof(m_priv));
+		m_hasPrivate = false;
+	}
+
 	/**
 	 * @return True if this identity contains something
 	 */
@@ -222,20 +244,27 @@ public:
 
 	ZT_INLINE bool operator==(const Identity &id) const noexcept
 	{ return (m_fp == id.m_fp); }
+
 	ZT_INLINE bool operator!=(const Identity &id) const noexcept
 	{ return !(*this == id); }
+
 	ZT_INLINE bool operator<(const Identity &id) const noexcept
 	{ return (m_fp < id.m_fp); }
+
 	ZT_INLINE bool operator>(const Identity &id) const noexcept
 	{ return (id < *this); }
+
 	ZT_INLINE bool operator<=(const Identity &id) const noexcept
 	{ return !(id < *this); }
+
 	ZT_INLINE bool operator>=(const Identity &id) const noexcept
 	{ return !(*this < id); }
 
 	static constexpr int marshalSizeMax() noexcept
 	{ return ZT_IDENTITY_MARSHAL_SIZE_MAX; }
+
 	int marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], bool includePrivate = false) const noexcept;
+
 	int unmarshal(const uint8_t *data, int len) noexcept;
 
 private:

+ 8 - 8
core/Topology.cpp

@@ -20,7 +20,7 @@ static const SharedPtr< const Certificate > s_nullCert;
 Topology::Topology(const RuntimeEnvironment *renv, void *tPtr, const int64_t now) :
 	RR(renv)
 {
-	char tmp[256];
+	char tmp[32];
 	Vector< uint8_t > trustData(RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_TRUST_STORE, Utils::ZERO256));
 
 	Dictionary d;
@@ -30,20 +30,20 @@ Topology::Topology(const RuntimeEnvironment *renv, void *tPtr, const int64_t now
 		const unsigned long certCount = (unsigned long)d.getUI("c$");
 		for (unsigned long idx = 0; idx < certCount; ++idx) {
 			uint64_t id[6];
-			const Vector< uint8_t > &serialNo = d[Dictionary::arraySubscript(tmp, "c$.s", idx)];
+			const Vector< uint8_t > &serialNo = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.s", idx)];
 			if (serialNo.size() == ZT_SHA384_DIGEST_SIZE) {
 				Utils::copy< 48 >(id, serialNo.data());
 				Certificate cert;
 				Vector< uint8_t > enc(RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_CERT, id));
 				if (cert.decode(enc.data(), (unsigned int)enc.size()))
-					addCertificate(tPtr, cert, now, (unsigned int)d.getUI(Dictionary::arraySubscript(tmp, "c$.lt", idx)), false, false, false);
+					addCertificate(tPtr, cert, now, (unsigned int)d.getUI(Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.lt", idx)), false, false, false);
 			}
 		}
 
 		const unsigned long localRootCount = (unsigned long)d.getUI("lr$");
 		for (unsigned long idx = 0; idx < localRootCount; ++idx) {
 			Identity lr;
-			if (d.getO(Dictionary::arraySubscript(tmp, "lr$.i", idx), lr)) {
+			if (d.getO(Dictionary::arraySubscript(tmp, sizeof(tmp), "lr$.i", idx), lr)) {
 				if (lr)
 					m_roots[lr].insert(s_nullCert);
 			}
@@ -456,7 +456,7 @@ void Topology::m_writeTrustStore_l_roots_certs(void *tPtr) const
 {
 	// assumes m_roots_l and m_certs_l are locked for write
 
-	char tmp[256];
+	char tmp[32];
 	Dictionary d;
 
 	d.add("v", (uint64_t)0); // version
@@ -464,15 +464,15 @@ void Topology::m_writeTrustStore_l_roots_certs(void *tPtr) const
 	unsigned long idx = 0;
 	d.add("c$", (uint64_t)m_certs.size());
 	for (Map< SHA384Hash, std::pair< SharedPtr< const Certificate >, unsigned int > >::const_iterator c(m_certs.begin()); c != m_certs.end(); ++c) {
-		d[Dictionary::arraySubscript(tmp, "c$.s", idx)].assign(c->first.data, c->first.data + ZT_SHA384_DIGEST_SIZE);
-		d.add(Dictionary::arraySubscript(tmp, "c$.lt", idx), (uint64_t)c->second.second);
+		d[Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.s", idx)].assign(c->first.data, c->first.data + ZT_SHA384_DIGEST_SIZE);
+		d.add(Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.lt", idx), (uint64_t)c->second.second);
 		++idx;
 	}
 
 	unsigned long localRootCount = 0;
 	for (Map< Identity, Set< SharedPtr< const Certificate > > >::const_iterator r(m_roots.begin()); r != m_roots.end();) {
 		if (r->second.find(s_nullCert) != r->second.end())
-			d.addO(Dictionary::arraySubscript(tmp, "lr$.i", localRootCount++), r->first);
+			d.addO(Dictionary::arraySubscript(tmp, sizeof(tmp), "lr$.i", localRootCount++), r->first);
 	}
 	d.add("lr$", (uint64_t)localRootCount);
 

+ 2 - 8
core/Utils.hpp

@@ -733,7 +733,7 @@ static ZT_INLINE void copy(void *dest, const void *src) noexcept
 #endif
 }
 
-// Avoid rep/movsb startup time for some small common sizes.
+#ifndef ZT_NO_UNALIGNED_ACCESS
 template<>
 ZT_INLINE void copy<4>(void *dest, const void *src) noexcept
 {
@@ -756,13 +756,7 @@ ZT_INLINE void copy<16>(void *dest, const void *src) noexcept
 	*reinterpret_cast<uint64_t *>(dest) = *reinterpret_cast<const uint64_t *>(src);
 	*reinterpret_cast<uint64_t *>(reinterpret_cast<uint8_t *>(dest) + 8) = *reinterpret_cast<const uint64_t *>(reinterpret_cast<const uint8_t *>(src) + 8);
 }
-template<>
-ZT_INLINE void copy<24>(void *dest, const void *src) noexcept
-{
-	*reinterpret_cast<uint64_t *>(dest) = *reinterpret_cast<const uint64_t *>(src);
-	*reinterpret_cast<uint64_t *>(reinterpret_cast<uint8_t *>(dest) + 8) = *reinterpret_cast<const uint64_t *>(reinterpret_cast<const uint8_t *>(src) + 8);
-	*reinterpret_cast<uint64_t *>(reinterpret_cast<uint8_t *>(dest) + 16) = *reinterpret_cast<const uint64_t *>(reinterpret_cast<const uint8_t *>(src) + 16);
-}
+#endif
 
 /**
  * Copy memory block whose size is known at run time

+ 40 - 13
pkg/zerotier/certificate.go

@@ -18,6 +18,7 @@ package zerotier
 import "C"
 
 import (
+	"encoding/json"
 	"fmt"
 	"runtime"
 	"unsafe"
@@ -136,13 +137,11 @@ func NewCertificateFromBytes(cert []byte, verify bool) (*Certificate, error) {
 		ver = 1
 	}
 	cerr := C.ZT_Certificate_decode((**C.ZT_Certificate)(unsafe.Pointer(&dec)), unsafe.Pointer(&cert[0]), C.int(len(cert)), ver)
-	if dec != unsafe.Pointer(nil) {
-		defer C.ZT_Certificate_delete((*C.ZT_Certificate)(dec))
-	}
+	defer C.ZT_Certificate_delete((*C.ZT_Certificate)(dec))
 	if cerr != 0 {
 		return nil, certificateErrorToError(int(cerr))
 	}
-	if dec == unsafe.Pointer(nil) {
+	if dec == unsafe.Pointer(uintptr(0)) {
 		return nil, ErrInternal
 	}
 
@@ -309,7 +308,7 @@ func (c *Certificate) CCertificate() *CCertificate {
 			}
 		}
 		cc.subject.identities = &subjectIdentities[0]
-		cc.subject.identityCount = C.uint(len(c.Subject.Identities))
+		cc.subject.identityCount = C.uint(len(subjectIdentities))
 	}
 
 	if len(c.Subject.Networks) > 0 {
@@ -322,7 +321,7 @@ func (c *Certificate) CCertificate() *CCertificate {
 			}
 		}
 		cc.subject.networks = &subjectNetworks[0]
-		cc.subject.networkCount = C.uint(len(c.Subject.Networks))
+		cc.subject.networkCount = C.uint(len(subjectNetworks))
 	}
 
 	if len(c.Subject.Certificates) > 0 {
@@ -334,7 +333,7 @@ func (c *Certificate) CCertificate() *CCertificate {
 			subjectCertificates[i] = uintptr(unsafe.Pointer(&cert[0]))
 		}
 		cc.subject.certificates = (**C.uint8_t)(unsafe.Pointer(&subjectCertificates[0]))
-		cc.subject.certificateCount = C.uint(len(c.Subject.Certificates))
+		cc.subject.certificateCount = C.uint(len(subjectCertificates))
 	}
 
 	if len(c.Subject.UpdateURLs) > 0 {
@@ -345,7 +344,7 @@ func (c *Certificate) CCertificate() *CCertificate {
 			subjectUpdateURLs[i] = uintptr(unsafe.Pointer(&subjectUpdateURLsData[0][0]))
 		}
 		cc.subject.updateURLs = (**C.char)(unsafe.Pointer(&subjectUpdateURLs[0]))
-		cc.subject.updateURLCount = C.uint(len(c.Subject.UpdateURLs))
+		cc.subject.updateURLCount = C.uint(len(subjectUpdateURLs))
 	}
 
 	cStrCopy(unsafe.Pointer(&cc.subject.name.serialNo[0]), CertificateMaxStringLength+1, c.Subject.Name.SerialNo)
@@ -402,8 +401,8 @@ func (c *Certificate) CCertificate() *CCertificate {
 		cc.signatureSize = C.uint(len(c.Signature))
 	}
 
-	// HACK: pass pointer to cc as uintptr to disable Go's protection against go pointers to
-	// go pointers, as the C function called here will make a deep clone and then we are going
+	// HACK: pass pointer to cc as uintptr to disable Go's protection against "Go pointers to
+	// Go pointers," as the C function called here will make a deep clone and then we are going
 	// to throw away 'cc' and its components.
 	cc2 := &CCertificate{C: unsafe.Pointer(C._ZT_Certificate_clone2(C.uintptr_t(uintptr(unsafe.Pointer(&cc)))))}
 	runtime.SetFinalizer(cc2, func(obj interface{}) {
@@ -427,6 +426,27 @@ func (c *Certificate) Marshal() ([]byte, error) {
 	return append(make([]byte, 0, int(encodedSize)), encoded[0:int(encodedSize)]...), nil
 }
 
+// Sign signs this certificate and returns a new one with signature and issuer filled out.
+// This should only be used after decoding a CSR with NewCertificateFromBytes. The non-subject
+// parts of this Certificate, if any, are ignored. A new Certificate is returned with a completed
+// signature.
+func (c *Certificate) Sign(id *Identity) (*Certificate, error) {
+	if id == nil || !id.HasPrivate() || !id.initCIdentityPtr() {
+		return nil, ErrInvalidParameter
+	}
+	ctmp := c.CCertificate()
+	if ctmp == nil {
+		return nil, ErrInternal
+	}
+	var signedCert [16384]byte
+	signedCertSize := C.int(16384)
+	rv := int(C.ZT_Certificate_sign((*C.ZT_Certificate)(ctmp.C), id.cid, unsafe.Pointer(&signedCert[0]), &signedCertSize))
+	if rv != 0 {
+		return nil, fmt.Errorf("signing failed: error %d", rv)
+	}
+	return NewCertificateFromBytes(signedCert[0:int(signedCertSize)], true)
+}
+
 // Verify returns nil on success or a certificate error if there is a problem with this certificate.
 func (c *Certificate) Verify() error {
 	cc := c.CCertificate()
@@ -436,6 +456,12 @@ func (c *Certificate) Verify() error {
 	return certificateErrorToError(int(C.ZT_Certificate_verify((*C.ZT_Certificate)(cc.C))))
 }
 
+// JSON returns this certificate as a human-readable indented JSON string.
+func (c *Certificate) JSON() string {
+	j, _ := json.MarshalIndent(c, "", "  ")
+	return string(j)
+}
+
 // NewCertificateSubjectUniqueId creates a new certificate subject unique ID and corresponding private key.
 // Right now only one type is supported: CertificateUniqueIdTypeNistP384
 func NewCertificateSubjectUniqueId(uniqueIdType int) (id []byte, priv []byte, err error) {
@@ -444,13 +470,14 @@ func NewCertificateSubjectUniqueId(uniqueIdType int) (id []byte, priv []byte, er
 		return
 	}
 	id = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE))
-	priv = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE))
+	priv = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE))
 	idSize := C.int(len(id))
 	idPrivateSize := C.int(len(priv))
-	if C.ZT_Certificate_newSubjectUniqueId((C.enum_ZT_CertificateUniqueIdType)(uniqueIdType), unsafe.Pointer(&id[0]), &idSize, unsafe.Pointer(&priv[0]), &idPrivateSize) != 0 {
+	rv := int(C.ZT_Certificate_newSubjectUniqueId((C.enum_ZT_CertificateUniqueIdType)(uniqueIdType), unsafe.Pointer(&id[0]), &idSize, unsafe.Pointer(&priv[0]), &idPrivateSize))
+	if rv != 0 {
 		id = nil
 		priv = nil
-		err = ErrInvalidParameter
+		err = fmt.Errorf("error %d", rv)
 		return
 	}
 	if int(idSize) != len(id) || int(idPrivateSize) != len(priv) {

+ 5 - 1
pkg/zerotier/identity.go

@@ -78,7 +78,11 @@ func newIdentityFromCIdentity(cid unsafe.Pointer) (*Identity, error) {
 // initCIdentityPtr returns a pointer to the core ZT_Identity instance or nil/0 on error.
 func (id *Identity) initCIdentityPtr() bool {
 	if uintptr(id.cid) == 0 {
-		idCStr := C.CString(id.String())
+		str := id.PrivateKeyString()
+		if len(str) == 0 {
+			str = id.String()
+		}
+		idCStr := C.CString(str)
 		defer C.free(unsafe.Pointer(idCStr))
 		id.cid = C.ZT_Identity_fromString(idCStr)
 		if uintptr(id.cid) == 0 {