mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-05 03:53:44 +02:00
Certificates, etc... work in progress.
This commit is contained in:
parent
808ab715d9
commit
f447608d6b
24 changed files with 1502 additions and 865 deletions
4
Makefile
4
Makefile
|
@ -25,7 +25,7 @@ central-controller-docker:
|
|||
docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f controller/central-docker/Dockerfile .
|
||||
|
||||
clean:
|
||||
rm -rf ${BUILDDIR} cmake-build-*
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
distclean:
|
||||
rm -rf ${BUILDDIR} cmake-build-*
|
||||
rm -rf ${BUILDDIR}
|
||||
|
|
|
@ -34,7 +34,8 @@ Global Options:
|
|||
Commands:
|
||||
help Show this help
|
||||
version Print version
|
||||
service Start node (see below)
|
||||
service [-options] Start node (see below)
|
||||
-d Fork into background (Unix only)
|
||||
status Show node status and configuration
|
||||
join [-options] <network> Join a virtual network
|
||||
-a <token> Token to submit to controller
|
||||
|
@ -56,10 +57,8 @@ Commands:
|
|||
locator <locator> Explicitly update peer locator
|
||||
roots List root peers
|
||||
root [command] - Root management commands
|
||||
add <identity> [endpoint] Designate a peer as a root
|
||||
remove <address> Un-designate a peer as a root
|
||||
subscribe <url> [<key hash>] Subscribe to a set of roots
|
||||
unsubscribe <url | key hash> Unsubscribe from a set of roots
|
||||
add <identity | url> [endpoint] Add a root or a root set
|
||||
remove <address | url | serial> Remove a root or root set
|
||||
set [option] [value] - Get or set a core config option
|
||||
port <port> Primary P2P port
|
||||
secondaryport <port/0> Secondary P2P port (0 to disable)
|
||||
|
|
|
@ -15,8 +15,11 @@ package cli
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"zerotier/pkg/zerotier"
|
||||
|
@ -28,16 +31,20 @@ func Service(basePath, authToken string, args []string) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
pidPath := path.Join(basePath, "zerotier.pid")
|
||||
_ = ioutil.WriteFile(pidPath, []byte(strconv.FormatInt(int64(os.Getpid()), 10)), 0644)
|
||||
|
||||
node, err := zerotier.NewNode(basePath)
|
||||
if err != nil {
|
||||
fmt.Println("FATAL: error initializing node: " + err.Error())
|
||||
os.Exit(1)
|
||||
} else {
|
||||
osSignalChannel := make(chan os.Signal, 2)
|
||||
signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGSTOP)
|
||||
signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPIPE, syscall.SIGHUP)
|
||||
<-osSignalChannel
|
||||
node.Close()
|
||||
}
|
||||
|
||||
osSignalChannel := make(chan os.Signal, 2)
|
||||
signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
|
||||
signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)
|
||||
<-osSignalChannel
|
||||
node.Close()
|
||||
os.Exit(0)
|
||||
_ = os.Remove(pidPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
44
core/Blob.hpp
Normal file
44
core/Blob.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c)2013-2020 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2024-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_BLOB_HPP
|
||||
#define ZT_BLOB_HPP
|
||||
|
||||
#include "Constants.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Container for arbitrary bytes for use in collections
|
||||
*
|
||||
* @tparam S Size of container in bytes
|
||||
*/
|
||||
template<unsigned int S>
|
||||
struct Blob
|
||||
{
|
||||
uint8_t data[S];
|
||||
|
||||
ZT_INLINE Blob() { Utils::zero<S>(data); }
|
||||
|
||||
ZT_INLINE bool operator==(const Blob &b) const noexcept { return (memcmp(data,b.data,S) == 0); }
|
||||
ZT_INLINE bool operator!=(const Blob &b) const noexcept { return (memcmp(data,b.data,S) != 0); }
|
||||
ZT_INLINE bool operator<(const Blob &b) const noexcept { return (memcmp(data,b.data,S) < 0); }
|
||||
ZT_INLINE bool operator>(const Blob &b) const noexcept { return (memcmp(data,b.data,S) > 0); }
|
||||
ZT_INLINE bool operator<=(const Blob &b) const noexcept { return (memcmp(data,b.data,S) <= 0); }
|
||||
ZT_INLINE bool operator>=(const Blob &b) const noexcept { return (memcmp(data,b.data,S) >= 0); }
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
|
@ -2,13 +2,14 @@ cmake_minimum_required (VERSION 2.8)
|
|||
project(zt_core)
|
||||
|
||||
configure_file(
|
||||
version.h.in
|
||||
version.h
|
||||
version.h.in
|
||||
version.h
|
||||
)
|
||||
|
||||
set(core_headers
|
||||
zerotier.h
|
||||
Address.hpp
|
||||
Blob.hpp
|
||||
Buf.hpp
|
||||
C25519.hpp
|
||||
Capability.hpp
|
||||
|
@ -24,6 +25,7 @@ set(core_headers
|
|||
Expect.hpp
|
||||
FCV.hpp
|
||||
Fingerprint.hpp
|
||||
IdentificationCertificate.hpp
|
||||
Identity.hpp
|
||||
InetAddress.hpp
|
||||
Locator.hpp
|
||||
|
@ -67,6 +69,7 @@ set(core_src
|
|||
Dictionary.cpp
|
||||
ECC384.cpp
|
||||
Endpoint.cpp
|
||||
IdentificationCertificate.cpp
|
||||
Identity.cpp
|
||||
InetAddress.cpp
|
||||
Locator.cpp
|
||||
|
|
|
@ -12,56 +12,64 @@
|
|||
/****/
|
||||
|
||||
#include "Dictionary.hpp"
|
||||
#include "SHA512.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
Dictionary::Dictionary()
|
||||
{}
|
||||
|
||||
Dictionary::~Dictionary()
|
||||
{}
|
||||
|
||||
Vector< uint8_t > &Dictionary::operator[](const char *k)
|
||||
{
|
||||
if (k)
|
||||
return m_entries[s_key(k)];
|
||||
else return m_entries[""];
|
||||
}
|
||||
|
||||
Vector<uint8_t> &Dictionary::operator[](const char *k)
|
||||
const Vector< uint8_t > &Dictionary::operator[](const char *k) const
|
||||
{
|
||||
return m_entries[s_key(k)];
|
||||
}
|
||||
|
||||
const Vector<uint8_t> &Dictionary::operator[](const char *k) const
|
||||
{
|
||||
static const Vector<uint8_t> s_emptyEntry;
|
||||
SortedMap< String, Vector<uint8_t> >::const_iterator e(m_entries.find(s_key(k)));
|
||||
return (e == m_entries.end()) ? s_emptyEntry : e->second;
|
||||
static const Vector< uint8_t > s_emptyEntry;
|
||||
if (k) {
|
||||
SortedMap< String, Vector< uint8_t > >::const_iterator e(m_entries.find(s_key(k)));
|
||||
return (e == m_entries.end()) ? s_emptyEntry : e->second;
|
||||
} else {
|
||||
SortedMap< String, Vector< uint8_t > >::const_iterator e(m_entries.find(""));
|
||||
return (e == m_entries.end()) ? s_emptyEntry : e->second;
|
||||
}
|
||||
}
|
||||
|
||||
void Dictionary::add(const char *k, bool v)
|
||||
{
|
||||
Vector<uint8_t> &e = (*this)[k];
|
||||
Vector< uint8_t > &e = (*this)[k];
|
||||
e.resize(2);
|
||||
e[0] = (uint8_t) (v ? '1' : '0');
|
||||
e[0] = (uint8_t)(v ? '1' : '0');
|
||||
e[1] = 0;
|
||||
}
|
||||
|
||||
void Dictionary::add(const char *k, const Address &v)
|
||||
{
|
||||
Vector<uint8_t> &e = (*this)[k];
|
||||
Vector< uint8_t > &e = (*this)[k];
|
||||
e.resize(ZT_ADDRESS_STRING_SIZE_MAX);
|
||||
v.toString((char *) e.data());
|
||||
v.toString((char *)e.data());
|
||||
}
|
||||
|
||||
void Dictionary::add(const char *k, const char *v)
|
||||
{
|
||||
if ((v) && (*v)) {
|
||||
Vector<uint8_t> &e = (*this)[k];
|
||||
Vector< uint8_t > &e = (*this)[k];
|
||||
e.clear();
|
||||
while (*v)
|
||||
e.push_back((uint8_t) *(v++));
|
||||
e.push_back((uint8_t)*(v++));
|
||||
}
|
||||
}
|
||||
|
||||
void Dictionary::add(const char *k, const void *data, unsigned int len)
|
||||
{
|
||||
Vector<uint8_t> &e = (*this)[k];
|
||||
Vector< uint8_t > &e = (*this)[k];
|
||||
if (len != 0) {
|
||||
e.assign((const uint8_t *) data, (const uint8_t *) data + len);
|
||||
e.assign((const uint8_t *)data, (const uint8_t *)data + len);
|
||||
} else {
|
||||
e.clear();
|
||||
}
|
||||
|
@ -69,9 +77,9 @@ 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];
|
||||
const Vector< uint8_t > &e = (*this)[k];
|
||||
if (!e.empty()) {
|
||||
switch ((char) e[0]) {
|
||||
switch ((char)e[0]) {
|
||||
case '1':
|
||||
case 't':
|
||||
case 'T':
|
||||
|
@ -89,15 +97,15 @@ uint64_t Dictionary::getUI(const char *k, uint64_t dfl) const
|
|||
{
|
||||
uint8_t tmp[18];
|
||||
uint64_t v = dfl;
|
||||
const Vector<uint8_t> &e = (*this)[k];
|
||||
const Vector< uint8_t > &e = (*this)[k];
|
||||
if (!e.empty()) {
|
||||
if (e.back() != 0) {
|
||||
const unsigned long sl = e.size();
|
||||
Utils::copy(tmp, e.data(), (sl > 17) ? 17 : sl);
|
||||
tmp[17] = 0;
|
||||
return Utils::unhex((const char *) tmp);
|
||||
return Utils::unhex((const char *)tmp);
|
||||
}
|
||||
return Utils::unhex((const char *) e.data());
|
||||
return Utils::unhex((const char *)e.data());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
@ -106,13 +114,13 @@ 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];
|
||||
const Vector< uint8_t > &e = (*this)[k];
|
||||
unsigned int i = 0;
|
||||
const unsigned int last = cap - 1;
|
||||
for (;;) {
|
||||
if ((i == last) || (i >= (unsigned int)e.size()))
|
||||
break;
|
||||
v[i] = (char) e[i];
|
||||
v[i] = (char)e[i];
|
||||
++i;
|
||||
}
|
||||
v[i] = 0;
|
||||
|
@ -124,16 +132,14 @@ void Dictionary::clear()
|
|||
m_entries.clear();
|
||||
}
|
||||
|
||||
void Dictionary::encode(Vector<uint8_t> &out, const bool omitSignatureFields) const
|
||||
void Dictionary::encode(Vector< uint8_t > &out) const
|
||||
{
|
||||
out.clear();
|
||||
for (SortedMap< String, Vector<uint8_t> >::const_iterator ti(m_entries.begin());ti != m_entries.end();++ti) {
|
||||
if ((!omitSignatureFields) || ((ti->first != ZT_DICTIONARY_SIGNATURE_KEY))) {
|
||||
s_appendKey(out, ti->first.data());
|
||||
for (Vector<uint8_t>::const_iterator i(ti->second.begin());i != ti->second.end();++i)
|
||||
s_appendValueByte(out, *i);
|
||||
out.push_back((uint8_t) '\n');
|
||||
}
|
||||
for (SortedMap< String, Vector< uint8_t > >::const_iterator ti(m_entries.begin()); ti != m_entries.end(); ++ti) {
|
||||
s_appendKey(out, ti->first.data());
|
||||
for (Vector< uint8_t >::const_iterator i(ti->second.begin()); i != ti->second.end(); ++i)
|
||||
s_appendValueByte(out, *i);
|
||||
out.push_back((uint8_t)'\n');
|
||||
}
|
||||
out.push_back(0);
|
||||
}
|
||||
|
@ -142,9 +148,9 @@ bool Dictionary::decode(const void *data, unsigned int len)
|
|||
{
|
||||
clear();
|
||||
String k;
|
||||
Vector<uint8_t> *v = nullptr;
|
||||
Vector< uint8_t > *v = nullptr;
|
||||
bool escape = false;
|
||||
for (unsigned int di = 0;di < len;++di) {
|
||||
for (unsigned int di = 0; di < len; ++di) {
|
||||
uint8_t c = reinterpret_cast<const uint8_t *>(data)[di];
|
||||
if (!c) break;
|
||||
if (v) {
|
||||
|
@ -168,7 +174,7 @@ bool Dictionary::decode(const void *data, unsigned int len)
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
if (c == (uint8_t) '\n') {
|
||||
if (c == (uint8_t)'\n') {
|
||||
k.clear();
|
||||
v = nullptr;
|
||||
} else if (c == 92) { // backslash
|
||||
|
@ -180,7 +186,7 @@ bool Dictionary::decode(const void *data, unsigned int len)
|
|||
} else {
|
||||
if ((c < 33) || (c > 126) || (c == 92)) {
|
||||
return false;
|
||||
} else if (c == (uint8_t) '=') {
|
||||
} else if (c == (uint8_t)'=') {
|
||||
k.push_back(0);
|
||||
v = &m_entries[k];
|
||||
} else if (k.size() < 7) {
|
||||
|
@ -193,59 +199,4 @@ bool Dictionary::decode(const void *data, unsigned int len)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Dictionary::sign(
|
||||
const uint8_t c25519PrivateKey[ZT_C25519_COMBINED_PRIVATE_KEY_SIZE],
|
||||
const uint8_t c25519PublicKey[ZT_C25519_COMBINED_PUBLIC_KEY_SIZE],
|
||||
const uint8_t p384PrivateKey[ZT_ECC384_PRIVATE_KEY_SIZE],
|
||||
const uint8_t p384PublicKey[ZT_ECC384_PUBLIC_KEY_SIZE])
|
||||
{
|
||||
Vector<uint8_t> buf;
|
||||
encode(buf, true);
|
||||
|
||||
uint8_t c25519Signature[ZT_C25519_SIGNATURE_LEN];
|
||||
C25519::sign(c25519PrivateKey, c25519PublicKey, buf.data(), (unsigned int)buf.size(), c25519Signature);
|
||||
|
||||
uint8_t hbuf[ZT_ECC384_SIGNATURE_HASH_SIZE];
|
||||
static_assert(ZT_ECC384_SIGNATURE_HASH_SIZE == ZT_SHA384_DIGEST_SIZE,"size mismatch");
|
||||
SHA384(hbuf, buf.data(), (unsigned int)buf.size());
|
||||
uint8_t p384Signature[ZT_ECC384_SIGNATURE_SIZE];
|
||||
ECC384ECDSASign(p384PrivateKey, hbuf, p384Signature);
|
||||
|
||||
SHA384(hbuf, c25519PublicKey, ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, p384PublicKey, ZT_ECC384_PUBLIC_KEY_SIZE);
|
||||
|
||||
Dictionary signature;
|
||||
signature["kh"].assign(hbuf, hbuf + ZT_SHA384_DIGEST_SIZE);
|
||||
signature["ed25519"].assign(c25519Signature, c25519Signature + ZT_C25519_SIGNATURE_LEN);
|
||||
signature["p384"].assign(p384Signature, p384Signature + ZT_ECC384_SIGNATURE_SIZE);
|
||||
signature.encode((*this)[ZT_DICTIONARY_SIGNATURE_KEY], true);
|
||||
}
|
||||
|
||||
bool Dictionary::verify(
|
||||
const uint8_t c25519PublicKey[ZT_C25519_COMBINED_PUBLIC_KEY_SIZE],
|
||||
const uint8_t p384PublicKey[ZT_ECC384_PUBLIC_KEY_SIZE]) const
|
||||
{
|
||||
try {
|
||||
const Vector< uint8_t > &data = (*this)[ZT_DICTIONARY_SIGNATURE_KEY];
|
||||
if (data.empty())
|
||||
return false;
|
||||
Dictionary signature;
|
||||
if (!signature.decode(data.data(), (unsigned int)data.size()))
|
||||
return false;
|
||||
const Vector< uint8_t > &p384Signature = signature["p384"];
|
||||
const Vector< uint8_t > &c25519Signature = signature["ed25519"];
|
||||
if ((p384Signature.size() != ZT_ECC384_SIGNATURE_SIZE) || (c25519Signature.size() != ZT_C25519_SIGNATURE_LEN))
|
||||
return false;
|
||||
|
||||
Vector< uint8_t > buf;
|
||||
encode(buf, true);
|
||||
|
||||
if (C25519::verify(c25519PublicKey, buf.data(), (unsigned int)buf.size(), c25519Signature.data(), (unsigned int)c25519Signature.size())) {
|
||||
uint8_t hbuf[ZT_ECC384_SIGNATURE_HASH_SIZE];
|
||||
SHA384(hbuf, buf.data(), (unsigned int)buf.size());
|
||||
return ECC384ECDSAVerify(p384PublicKey, hbuf, p384Signature.data());
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
|
|
@ -19,10 +19,6 @@
|
|||
#include "Address.hpp"
|
||||
#include "Buf.hpp"
|
||||
#include "Containers.hpp"
|
||||
#include "C25519.hpp"
|
||||
#include "ECC384.hpp"
|
||||
|
||||
#define ZT_DICTIONARY_SIGNATURE_KEY "@S"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
|
@ -46,10 +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();
|
||||
|
||||
/**
|
||||
* Get a reference to a value
|
||||
|
@ -90,12 +86,17 @@ public:
|
|||
* @param k Key to set
|
||||
* @param v Integer to set, will be cast to uint64_t and stored as hex
|
||||
*/
|
||||
template< typename I >
|
||||
ZT_INLINE void add(const char *const k, I v)
|
||||
{
|
||||
char buf[17];
|
||||
add(k, Utils::hex((uint64_t)(v), buf));
|
||||
}
|
||||
ZT_INLINE void add(const char *const k, const uint64_t v)
|
||||
{ char buf[17]; add(k, Utils::hex((uint64_t)(v), buf)); }
|
||||
|
||||
/**
|
||||
* Add an integer as a hexadecimal string value
|
||||
*
|
||||
* @param k Key to set
|
||||
* @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)); }
|
||||
|
||||
/**
|
||||
* Add an address in 10-digit hex string format
|
||||
|
@ -145,6 +146,7 @@ public:
|
|||
/**
|
||||
* Get an object supporting the marshal/unmarshal interface pattern
|
||||
*
|
||||
* @tparam T Object type (inferred)
|
||||
* @param k Key to look up
|
||||
* @param obj Object to unmarshal() into
|
||||
* @return True if unmarshal was successful
|
||||
|
@ -158,6 +160,27 @@ public:
|
|||
return (obj.unmarshal(d.data(), (unsigned int)d.size()) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object supporting the marshal/unmarshal interface pattern
|
||||
*
|
||||
* @tparam T Object type (inferred)
|
||||
* @param k Key to add
|
||||
* @param obj Object to marshal() into vector
|
||||
* @return True if successful
|
||||
*/
|
||||
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);
|
||||
if (l > 0) {
|
||||
(*this)[k].assign(tmp, tmp + l);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase all entries in dictionary
|
||||
*/
|
||||
|
@ -179,9 +202,8 @@ public:
|
|||
* Encode to a string in the supplied vector
|
||||
*
|
||||
* @param out String encoded dictionary
|
||||
* @param omitSignatureFields If true omit the signature fields from encoding (default: false)
|
||||
*/
|
||||
void encode(Vector <uint8_t> &out, bool omitSignatureFields = false) const;
|
||||
void encode(Vector <uint8_t> &out) const;
|
||||
|
||||
/**
|
||||
* Decode a string encoded dictionary
|
||||
|
@ -195,34 +217,6 @@ public:
|
|||
*/
|
||||
bool decode(const void *data, unsigned int len);
|
||||
|
||||
/**
|
||||
* Sign this dictionary with both an Ed25519 key and a NIST P-384 key.
|
||||
*
|
||||
* This is currently used just for signing root sets for the root subscribe
|
||||
* feature. It uses both key types for more crypto cowbell.
|
||||
*
|
||||
* @param c25519PrivateKey Curve25519 combined key (C25519 and ed25519), though only ed25519 part is used
|
||||
* @param c25519PublicKey Public part of Curve25519 combined key
|
||||
* @param p384PrivateKey NIST P-384 private key
|
||||
* @param p384PublicKey NIST P-384 public key
|
||||
*/
|
||||
void sign(
|
||||
const uint8_t c25519PrivateKey[ZT_C25519_COMBINED_PRIVATE_KEY_SIZE],
|
||||
const uint8_t c25519PublicKey[ZT_C25519_COMBINED_PUBLIC_KEY_SIZE],
|
||||
const uint8_t p384PrivateKey[ZT_ECC384_PRIVATE_KEY_SIZE],
|
||||
const uint8_t p384PublicKey[ZT_ECC384_PUBLIC_KEY_SIZE]);
|
||||
|
||||
/**
|
||||
* Verify this dictionary's signature
|
||||
*
|
||||
* @param c25519PublicKey Curve25519 public key
|
||||
* @param p384PublicKey P-384 public key
|
||||
* @return True if signatures are valid
|
||||
*/
|
||||
bool verify(
|
||||
const uint8_t c25519PublicKey[ZT_C25519_COMBINED_PUBLIC_KEY_SIZE],
|
||||
const uint8_t p384PublicKey[ZT_ECC384_PUBLIC_KEY_SIZE]) const;
|
||||
|
||||
/**
|
||||
* Append a key=value pair to a buffer (vector or FCV)
|
||||
*
|
||||
|
@ -376,6 +370,19 @@ public:
|
|||
return mlen;
|
||||
}
|
||||
|
||||
static ZT_INLINE char *arraySubscript(char buf[256],const char *name,const unsigned long sub) noexcept
|
||||
{
|
||||
for(unsigned int i=0;i<(256 - 17);++i) {
|
||||
if ((buf[i] = name[i]) == 0) {
|
||||
buf[i++] = '#';
|
||||
Utils::hex(sub, buf + i);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
buf[0] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
private:
|
||||
template< typename V >
|
||||
ZT_INLINE static void s_appendValueByte(V &out, const uint8_t c)
|
||||
|
|
260
core/IdentificationCertificate.cpp
Normal file
260
core/IdentificationCertificate.cpp
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright (c)2013-2020 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2024-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#include "IdentificationCertificate.hpp"
|
||||
#include "SHA512.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
IdentificationCertificate &IdentificationCertificate::operator=(const ZT_IdentificationCertificate &apiCert)
|
||||
{
|
||||
Utils::copy< sizeof(ZT_IdentificationCertificate) >((ZT_IdentificationCertificate *)this, &apiCert);
|
||||
m_identities.clear();
|
||||
m_locators.clear();
|
||||
m_nodes.clear();
|
||||
m_networks.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
IdentificationCertificate &IdentificationCertificate::operator=(const IdentificationCertificate &cert)
|
||||
{
|
||||
Utils::copy< sizeof(ZT_IdentificationCertificate) >((ZT_IdentificationCertificate *)this, (const ZT_IdentificationCertificate *)(&cert));
|
||||
|
||||
m_identities.clear();
|
||||
m_locators.clear();
|
||||
m_nodes.clear();
|
||||
m_networks.clear();
|
||||
|
||||
this->subject.nodeCount = 0;
|
||||
this->subject.networkCount = 0;
|
||||
|
||||
if (cert.issuer) {
|
||||
m_identities.push_back(*reinterpret_cast<const Identity *>(cert.issuer));
|
||||
this->issuer = reinterpret_cast<ZT_Identity *>(&(m_identities.back()));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < cert.subject.nodeCount; ++i) {
|
||||
if (cert.subject.nodes[i].locator)
|
||||
addSubjectNode(*reinterpret_cast<const Identity *>(cert.subject.nodes[i].identity), *reinterpret_cast<const Locator *>(cert.subject.nodes[i].locator));
|
||||
else if (cert.subject.nodes[i].identity)
|
||||
addSubjectNode(*reinterpret_cast<const Identity *>(cert.subject.nodes[i].identity));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < cert.subject.networkCount; ++i)
|
||||
addSubjectNetwork(cert.subject.networks[i].id, cert.subject.networks[i].controller);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ZT_IdentificationCertificate_Node *IdentificationCertificate::addSubjectNode(const Identity &id)
|
||||
{
|
||||
m_nodes.resize(++this->subject.nodeCount);
|
||||
this->subject.nodes = m_nodes.data();
|
||||
m_identities.push_back(id);
|
||||
m_nodes.back().identity = reinterpret_cast<ZT_Identity *>(&(m_identities.back()));
|
||||
m_nodes.back().locator = nullptr;
|
||||
return &(m_nodes.back());
|
||||
}
|
||||
|
||||
ZT_IdentificationCertificate_Node *IdentificationCertificate::addSubjectNode(const Identity &id, const Locator &loc)
|
||||
{
|
||||
ZT_IdentificationCertificate_Node *n = addSubjectNode(id);
|
||||
m_locators.push_back(loc);
|
||||
n->locator = reinterpret_cast<ZT_Locator *>(&(m_locators.back()));
|
||||
return n;
|
||||
}
|
||||
|
||||
ZT_IdentificationCertificate_Network *IdentificationCertificate::addSubjectNetwork(const uint64_t id, const ZT_Fingerprint &controller)
|
||||
{
|
||||
m_networks.resize(++this->subject.networkCount);
|
||||
this->subject.networks = m_networks.data();
|
||||
m_networks.back().id = id;
|
||||
Utils::copy< sizeof(ZT_Fingerprint) >(&(m_networks.back().controller), &controller);
|
||||
return &(m_networks.back());
|
||||
}
|
||||
|
||||
Vector< uint8_t > IdentificationCertificate::encode(const bool omitSignature) const
|
||||
{
|
||||
char tmp[256];
|
||||
Vector< uint8_t > enc;
|
||||
Dictionary d;
|
||||
|
||||
d.add("v", (uint64_t)this->version);
|
||||
d.add("mP", (uint64_t)this->maxPathLength);
|
||||
d.add("f", this->flags);
|
||||
d.add("v0", this->validity[0]);
|
||||
d.add("v1", this->validity[1]);
|
||||
|
||||
d.add("s.n[]", (uint64_t)this->subject.nodeCount);
|
||||
for (unsigned int i = 0; i < this->subject.nodeCount; ++i) {
|
||||
d.addO(Dictionary::arraySubscript(tmp, "s.n[].i", i), *reinterpret_cast<const Identity *>(this->subject.nodes[i].identity));
|
||||
if (this->subject.nodes[i].locator)
|
||||
d.addO(Dictionary::arraySubscript(tmp, "s.n[].l", i), *reinterpret_cast<const Locator *>(this->subject.nodes[i].locator));
|
||||
}
|
||||
|
||||
d.add("s.nw[]", (uint64_t)this->subject.networkCount);
|
||||
for (unsigned int i = 0; i < this->subject.networkCount; ++i) {
|
||||
d.add(Dictionary::arraySubscript(tmp, "s.nw[].i", i), this->subject.networks[i].id);
|
||||
Fingerprint fp(this->subject.networks[i].controller);
|
||||
d.addO(Dictionary::arraySubscript(tmp, "s.nw[].c", i), fp);
|
||||
}
|
||||
|
||||
d.add("s.n.c", this->subject.name.country);
|
||||
d.add("s.n.o", this->subject.name.organization);
|
||||
d.add("s.n.u", this->subject.name.unit);
|
||||
d.add("s.n.l", this->subject.name.locality);
|
||||
d.add("s.n.p", this->subject.name.province);
|
||||
d.add("s.n.sA", this->subject.name.streetAddress);
|
||||
d.add("s.n.pC", this->subject.name.postalCode);
|
||||
d.add("s.n.cN", this->subject.name.commonName);
|
||||
d.add("s.n.sN", this->subject.name.serialNo);
|
||||
d.add("s.n.e", this->subject.name.email);
|
||||
d.add("s.n.ur", this->subject.name.url);
|
||||
|
||||
if (this->issuer)
|
||||
d.addO("i", *reinterpret_cast<const Identity *>(this->issuer));
|
||||
|
||||
d.add("iN.c", this->issuerName.country);
|
||||
d.add("iN.o", this->issuerName.organization);
|
||||
d.add("iN.u", this->issuerName.unit);
|
||||
d.add("iN.l", this->issuerName.locality);
|
||||
d.add("iN.p", this->issuerName.province);
|
||||
d.add("iN.sA", this->issuerName.streetAddress);
|
||||
d.add("iN.pC", this->issuerName.postalCode);
|
||||
d.add("iN.cN", this->issuerName.commonName);
|
||||
d.add("iN.sN", this->issuerName.serialNo);
|
||||
d.add("iN.e", this->issuerName.email);
|
||||
d.add("iN.ur", this->issuerName.url);
|
||||
|
||||
if ((!omitSignature) && (this->signatureSize > 0) && (this->signatureSize <= sizeof(this->signature)))
|
||||
d["si"].assign(this->signature, this->signature + this->signatureSize);
|
||||
|
||||
d.encode(enc);
|
||||
return enc;
|
||||
}
|
||||
|
||||
bool IdentificationCertificate::decode(const Vector< uint8_t > &data)
|
||||
{
|
||||
char tmp[256];
|
||||
|
||||
Utils::zero< sizeof(ZT_IdentificationCertificate) >((ZT_IdentificationCertificate *)this);
|
||||
m_identities.clear();
|
||||
m_locators.clear();
|
||||
m_nodes.clear();
|
||||
m_networks.clear();
|
||||
|
||||
Dictionary d;
|
||||
if (!d.decode(data.data(), (unsigned int)data.size()))
|
||||
return false;
|
||||
|
||||
this->version = (unsigned int)d.getUI("v");
|
||||
this->maxPathLength = (unsigned int)d.getUI("mP");
|
||||
this->flags = d.getUI("f");
|
||||
this->validity[0] = (int64_t)d.getUI("v0");
|
||||
this->validity[1] = (int64_t)d.getUI("v1");
|
||||
|
||||
unsigned int cnt = (unsigned int)d.getUI("s.n[]");
|
||||
for (unsigned int i = 0; i < cnt; ++i) {
|
||||
const Vector< uint8_t > &identityData = d[Dictionary::arraySubscript(tmp, "s.n[].i", i)];
|
||||
if (identityData.empty())
|
||||
return false;
|
||||
Identity id;
|
||||
if (id.unmarshal(identityData.data(), (unsigned int)identityData.size()) <= 0)
|
||||
return false;
|
||||
const Vector< uint8_t > &locatorData = d[Dictionary::arraySubscript(tmp, "s.n[].l", i)];
|
||||
if (!locatorData.empty()) {
|
||||
Locator loc;
|
||||
if (loc.unmarshal(locatorData.data(), (unsigned int)locatorData.size()) <= 0)
|
||||
return false;
|
||||
this->addSubjectNode(id, loc);
|
||||
} else {
|
||||
this->addSubjectNode(id);
|
||||
}
|
||||
}
|
||||
|
||||
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.nw[].i", i));
|
||||
if (nwid == 0)
|
||||
return false;
|
||||
const Vector< uint8_t > &fingerprintData = d[Dictionary::arraySubscript(tmp, "s.nw[].c", i)];
|
||||
if (fingerprintData.empty())
|
||||
return false;
|
||||
Fingerprint fp;
|
||||
if (fp.unmarshal(fingerprintData.data(), (unsigned int)fingerprintData.size()) <= 0)
|
||||
return false;
|
||||
this->addSubjectNetwork(nwid, fp);
|
||||
}
|
||||
|
||||
d.getS("s.n.c", this->subject.name.country, sizeof(this->subject.name.country));
|
||||
d.getS("s.n.o", this->subject.name.organization, sizeof(this->subject.name.organization));
|
||||
d.getS("s.n.u", this->subject.name.unit, sizeof(this->subject.name.unit));
|
||||
d.getS("s.n.l", this->subject.name.locality, sizeof(this->subject.name.locality));
|
||||
d.getS("s.n.p", this->subject.name.province, sizeof(this->subject.name.province));
|
||||
d.getS("s.n.sA", this->subject.name.streetAddress, sizeof(this->subject.name.streetAddress));
|
||||
d.getS("s.n.pC", this->subject.name.postalCode, sizeof(this->subject.name.postalCode));
|
||||
d.getS("s.n.cN", this->subject.name.commonName, sizeof(this->subject.name.commonName));
|
||||
d.getS("s.n.sN", this->subject.name.serialNo, sizeof(this->subject.name.serialNo));
|
||||
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));
|
||||
|
||||
const Vector< uint8_t > &issuerData = d["i"];
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
d.getS("iN.c", this->issuerName.country, sizeof(this->issuerName.country));
|
||||
d.getS("iN.o", this->issuerName.organization, sizeof(this->issuerName.organization));
|
||||
d.getS("iN.u", this->issuerName.unit, sizeof(this->issuerName.unit));
|
||||
d.getS("iN.l", this->issuerName.locality, sizeof(this->issuerName.locality));
|
||||
d.getS("iN.p", this->issuerName.province, sizeof(this->issuerName.province));
|
||||
d.getS("iN.sA", this->issuerName.streetAddress, sizeof(this->issuerName.streetAddress));
|
||||
d.getS("iN.pC", this->issuerName.postalCode, sizeof(this->issuerName.postalCode));
|
||||
d.getS("iN.cN", this->issuerName.commonName, sizeof(this->issuerName.commonName));
|
||||
d.getS("iN.sN", this->issuerName.serialNo, sizeof(this->issuerName.serialNo));
|
||||
d.getS("iN.e", this->issuerName.email, sizeof(this->issuerName.email));
|
||||
d.getS("iN.ur", this->issuerName.url, sizeof(this->issuerName.url));
|
||||
|
||||
const Vector< uint8_t > &sig = d["si"];
|
||||
if (sig.size() > sizeof(this->signature))
|
||||
return false;
|
||||
Utils::copy(this->signature, sig.data(), (unsigned int)sig.size());
|
||||
this->signatureSize = (unsigned int)sig.size();
|
||||
|
||||
Vector< uint8_t > enc(encode(true));
|
||||
SHA384(this->serialNo, enc.data(), (unsigned int)enc.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IdentificationCertificate::sign(const Identity &issuer)
|
||||
{
|
||||
Vector< uint8_t > enc(encode(true));
|
||||
SHA384(this->serialNo, enc.data(), (unsigned int)enc.size());
|
||||
return (this->signatureSize = issuer.sign(enc.data(), (unsigned int)enc.size(), this->signature, sizeof(this->signature))) > 0;
|
||||
}
|
||||
|
||||
bool IdentificationCertificate::verify() const
|
||||
{
|
||||
if (this->issuer) {
|
||||
Vector< uint8_t > enc(encode(true));
|
||||
return reinterpret_cast<const Identity *>(this->issuer)->verify(enc.data(), (unsigned int)enc.size(), this->signature, this->signatureSize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
135
core/IdentificationCertificate.hpp
Normal file
135
core/IdentificationCertificate.hpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright (c)2013-2020 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 2024-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2.0 of the Apache License.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#ifndef ZT_IDENTIFICATIONCERTIFICATE_HPP
|
||||
#define ZT_IDENTIFICATIONCERTIFICATE_HPP
|
||||
|
||||
#include "Constants.hpp"
|
||||
#include "SHA512.hpp"
|
||||
#include "C25519.hpp"
|
||||
#include "ECC384.hpp"
|
||||
#include "SharedPtr.hpp"
|
||||
#include "Identity.hpp"
|
||||
#include "Locator.hpp"
|
||||
#include "Dictionary.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Containers.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Certificate identifying the real world owner of an identity or network.
|
||||
*
|
||||
* This is a wrapper around the straight C ZT_IdentificationCertificate and
|
||||
* handles allocating memory for objects and disposing of it on GC. If filling
|
||||
* out a ZT_IdentificationCertificate structure, identities and other objects
|
||||
* should be attached via the addXXX() methods rather than by directly setting
|
||||
* the pointers in the C structure.
|
||||
*
|
||||
* If identities and similar objects are NOT added via the addXXX() methods,
|
||||
* this will not take care of de-allocating them when destroyed.
|
||||
*
|
||||
* The serialNo field is filled in automatically by sign() and decode(), so
|
||||
* it can be left undefined when building certificates.
|
||||
*/
|
||||
class IdentificationCertificate : public ZT_IdentificationCertificate
|
||||
{
|
||||
public:
|
||||
ZT_INLINE IdentificationCertificate() noexcept
|
||||
{ Utils::zero< sizeof(ZT_IdentificationCertificate) >((ZT_IdentificationCertificate *)this); }
|
||||
|
||||
ZT_INLINE IdentificationCertificate(const ZT_IdentificationCertificate &apiCert)
|
||||
{ Utils::copy< sizeof(ZT_IdentificationCertificate) >((ZT_IdentificationCertificate *)this, &apiCert); }
|
||||
|
||||
ZT_INLINE IdentificationCertificate(const IdentificationCertificate &cert)
|
||||
{ *this = cert; }
|
||||
|
||||
IdentificationCertificate &operator=(const ZT_IdentificationCertificate &apiCert);
|
||||
|
||||
IdentificationCertificate &operator=(const IdentificationCertificate &cert);
|
||||
|
||||
/**
|
||||
* Add a subject node/identity without a locator
|
||||
*
|
||||
* @param id Identity
|
||||
* @return Pointer to C struct
|
||||
*/
|
||||
ZT_IdentificationCertificate_Node *addSubjectNode(const Identity &id);
|
||||
|
||||
/**
|
||||
* Add a subject node/identity with a locator
|
||||
*
|
||||
* @param id Identity
|
||||
* @param loc Locator signed by identity (signature is NOT checked here)
|
||||
* @return Pointer to C struct
|
||||
*/
|
||||
ZT_IdentificationCertificate_Node *addSubjectNode(const Identity &id, const Locator &loc);
|
||||
|
||||
/**
|
||||
* Add a subject network
|
||||
*
|
||||
* @param id Network ID
|
||||
* @param controller Network controller's full fingerprint
|
||||
* @return Pointer to C struct
|
||||
*/
|
||||
ZT_IdentificationCertificate_Network *addSubjectNetwork(const uint64_t id, const ZT_Fingerprint &controller);
|
||||
|
||||
/**
|
||||
* Marshal this certificate in binary form
|
||||
*
|
||||
* The internal encoding used here is Dictionary to permit easy
|
||||
* extensibility.
|
||||
*
|
||||
* @param omitSignature If true omit the signature field (for signing and verification, default is false)
|
||||
* @return Marshaled certificate
|
||||
*/
|
||||
Vector< uint8_t > encode(bool omitSignature = false) const;
|
||||
|
||||
/**
|
||||
* Decode this certificate from marshaled bytes.
|
||||
*
|
||||
* @param data Marshalled certificate
|
||||
* @return True if input is valid and was unmarshalled (signature is NOT checked)
|
||||
*/
|
||||
bool decode(const Vector< uint8_t > &data);
|
||||
|
||||
/**
|
||||
* Sign this certificate (and also fill in serialNo).
|
||||
*
|
||||
* @param issuer Issuer identity (must have secret key)
|
||||
* @return True on success
|
||||
*/
|
||||
bool sign(const Identity &issuer);
|
||||
|
||||
/**
|
||||
* Verify certificate signature against the issuer contained therein
|
||||
*
|
||||
* @return True if certificate is signed and signature is valid
|
||||
*/
|
||||
bool verify() const;
|
||||
|
||||
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;
|
||||
|
||||
// These are stored in a vector because the memory needs to be contiguous.
|
||||
Vector< ZT_IdentificationCertificate_Node > m_nodes;
|
||||
Vector< ZT_IdentificationCertificate_Network > m_networks;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
|
@ -64,7 +64,7 @@ public:
|
|||
|
||||
ZT_INLINE Identity() noexcept
|
||||
{
|
||||
Utils::memoryLock(this,sizeof(Identity));
|
||||
Utils::memoryLock(this, sizeof(Identity));
|
||||
memoryZero(this);
|
||||
}
|
||||
|
||||
|
@ -78,25 +78,27 @@ public:
|
|||
*/
|
||||
explicit ZT_INLINE Identity(const char *str)
|
||||
{
|
||||
Utils::memoryLock(this,sizeof(Identity));
|
||||
Utils::memoryLock(this, sizeof(Identity));
|
||||
fromString(str);
|
||||
}
|
||||
|
||||
ZT_INLINE ~Identity()
|
||||
{
|
||||
Utils::memoryUnlock(this,sizeof(Identity));
|
||||
Utils::memoryUnlock(this, sizeof(Identity));
|
||||
Utils::burn(reinterpret_cast<void *>(&this->m_priv), sizeof(this->m_priv));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set identity to NIL value (all zero)
|
||||
*/
|
||||
ZT_INLINE void zero() noexcept { memoryZero(this); }
|
||||
ZT_INLINE void zero() noexcept
|
||||
{ memoryZero(this); }
|
||||
|
||||
/**
|
||||
* @return Identity type (undefined if identity is null or invalid)
|
||||
*/
|
||||
ZT_INLINE Type type() const noexcept { return m_type; }
|
||||
ZT_INLINE Type type() const noexcept
|
||||
{ return m_type; }
|
||||
|
||||
/**
|
||||
* Generate a new identity (address, key pair)
|
||||
|
@ -122,17 +124,20 @@ public:
|
|||
/**
|
||||
* @return True if this identity contains a private key
|
||||
*/
|
||||
ZT_INLINE bool hasPrivate() const noexcept { return m_hasPrivate; }
|
||||
ZT_INLINE bool hasPrivate() const noexcept
|
||||
{ return m_hasPrivate; }
|
||||
|
||||
/**
|
||||
* @return This identity's address
|
||||
*/
|
||||
ZT_INLINE Address address() const noexcept { return Address(m_fp.address); }
|
||||
ZT_INLINE Address address() const noexcept
|
||||
{ return Address(m_fp.address); }
|
||||
|
||||
/**
|
||||
* @return Full fingerprint of this identity (address plus SHA384 of keys)
|
||||
*/
|
||||
ZT_INLINE const Fingerprint &fingerprint() const noexcept { return m_fp; }
|
||||
ZT_INLINE const Fingerprint &fingerprint() const noexcept
|
||||
{ return m_fp; }
|
||||
|
||||
/**
|
||||
* Compute a hash of this identity's public and private keys.
|
||||
|
@ -155,7 +160,7 @@ public:
|
|||
* @param siglen Length of buffer
|
||||
* @return Number of bytes actually written to sig or 0 on error
|
||||
*/
|
||||
unsigned int sign(const void *data,unsigned int len,void *sig,unsigned int siglen) const;
|
||||
unsigned int sign(const void *data, unsigned int len, void *sig, unsigned int siglen) const;
|
||||
|
||||
/**
|
||||
* Verify a message signature against this identity
|
||||
|
@ -166,7 +171,7 @@ public:
|
|||
* @param siglen Length of signature in bytes
|
||||
* @return True if signature validates and data integrity checks
|
||||
*/
|
||||
bool verify(const void *data,unsigned int len,const void *sig,unsigned int siglen) const;
|
||||
bool verify(const void *data, unsigned int len, const void *sig, unsigned int siglen) const;
|
||||
|
||||
/**
|
||||
* Shortcut method to perform key agreement with another identity
|
||||
|
@ -177,7 +182,7 @@ public:
|
|||
* @param key Result parameter to fill with key bytes
|
||||
* @return Was agreement successful?
|
||||
*/
|
||||
bool agree(const Identity &id,uint8_t key[ZT_SYMMETRIC_KEY_SIZE]) const;
|
||||
bool agree(const Identity &id, uint8_t key[ZT_SYMMETRIC_KEY_SIZE]) const;
|
||||
|
||||
/**
|
||||
* Serialize to a more human-friendly string
|
||||
|
@ -186,8 +191,14 @@ public:
|
|||
* @param buf Buffer to store string
|
||||
* @return ASCII string representation of identity (pointer to buf)
|
||||
*/
|
||||
char *toString(bool includePrivate,char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const;
|
||||
ZT_INLINE String toString(const bool includePrivate = false) const { char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]; toString(includePrivate,buf); return String(buf); }
|
||||
char *toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const;
|
||||
|
||||
ZT_INLINE String toString(const bool includePrivate = false) const
|
||||
{
|
||||
char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH];
|
||||
toString(includePrivate, buf);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a human-friendly string
|
||||
|
@ -203,20 +214,36 @@ public:
|
|||
/**
|
||||
* @return True if this identity contains something
|
||||
*/
|
||||
explicit ZT_INLINE operator bool() const noexcept { return (m_fp); }
|
||||
explicit ZT_INLINE operator bool() const noexcept
|
||||
{ return (m_fp); }
|
||||
|
||||
ZT_INLINE unsigned long hashCode() const noexcept { return m_fp.hashCode(); }
|
||||
ZT_INLINE unsigned long hashCode() const noexcept
|
||||
{ return m_fp.hashCode(); }
|
||||
|
||||
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); }
|
||||
ZT_INLINE bool operator==(const Identity &id) const noexcept
|
||||
{ return (m_fp == id.m_fp); }
|
||||
|
||||
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;
|
||||
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:
|
||||
void m_computeHash();
|
||||
|
|
|
@ -119,9 +119,7 @@ Node::Node(
|
|||
stateObjectPut(tPtr, ZT_STATE_OBJECT_IDENTITY_PUBLIC, idtmp, RR->publicIdentityStr, (unsigned int)strlen(RR->publicIdentityStr));
|
||||
}
|
||||
|
||||
// 2X hash our identity private key(s) to obtain a symmetric key for encrypting
|
||||
// locally cached data at rest (as a defense in depth measure). This is not used
|
||||
// for any network level encryption or authentication.
|
||||
// Create a secret key for encrypting local data at rest.
|
||||
uint8_t tmph[ZT_SHA384_DIGEST_SIZE];
|
||||
RR->identity.hashWithPrivate(tmph);
|
||||
SHA384(tmph, tmph, ZT_SHA384_DIGEST_SIZE);
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
#define ZT_PROTO_PACKET_FRAGMENT_PAYLOAD_START_AT ZT_PROTO_MIN_FRAGMENT_LENGTH
|
||||
|
||||
/**
|
||||
* Header flag indicating that a packet is fragmented and more fragments should be expected
|
||||
* Outer flag indicating that a packet is fragmented and this is just the head.
|
||||
*/
|
||||
#define ZT_PROTO_FLAG_FRAGMENTED 0x40U
|
||||
|
||||
|
|
|
@ -145,7 +145,9 @@ static ZT_INLINE void p_salsaCrypt(p_SalsaState *const state, const uint8_t *m,
|
|||
|
||||
for (;;) {
|
||||
if (likely(bytes >= 64)) {
|
||||
#ifdef ZT_SALSA20_SSE
|
||||
_mm_prefetch(m + 128, _MM_HINT_T0);
|
||||
#endif
|
||||
} else {
|
||||
for (unsigned int i = 0;i < bytes;++i)
|
||||
tmp[i] = m[i];
|
||||
|
|
|
@ -29,7 +29,6 @@ Topology::Topology(const RuntimeEnvironment *renv, void *tPtr) :
|
|||
Identity id;
|
||||
int l = id.unmarshal(dptr, drem);
|
||||
if ((l > 0) && (id)) {
|
||||
m_roots.insert(id);
|
||||
ZT_SPEW("restored root %s", id.address().toString().c_str());
|
||||
if ((drem -= l) <= 0)
|
||||
break;
|
||||
|
@ -189,25 +188,25 @@ void Topology::m_updateRootPeers(void *tPtr)
|
|||
{
|
||||
// assumes m_peers_l is locked for write
|
||||
Vector< SharedPtr< Peer > > rp;
|
||||
for (Set< Identity >::iterator r(m_roots.begin()); r != m_roots.end(); ++r) {
|
||||
Map< Address, SharedPtr< Peer > >::iterator pp(m_peers.find(r->address()));
|
||||
for (Map< Identity, Set< SubscriptionKeyHash > >::iterator r(m_roots.begin()); r != m_roots.end(); ++r) {
|
||||
Map< Address, SharedPtr< Peer > >::iterator pp(m_peers.find(r->first.address()));
|
||||
SharedPtr< Peer > p;
|
||||
if (pp != m_peers.end())
|
||||
p = pp->second;
|
||||
|
||||
if (!p)
|
||||
m_loadCached(tPtr, r->address(), p);
|
||||
m_loadCached(tPtr, r->first.address(), p);
|
||||
|
||||
if ((!p) || (p->identity() != *r)) {
|
||||
if ((!p) || (p->identity() != r->first)) {
|
||||
p.set(new Peer(RR));
|
||||
p->init(*r);
|
||||
m_peers[r->address()] = p;
|
||||
p->init(r->first);
|
||||
m_peers[r->first.address()] = p;
|
||||
}
|
||||
|
||||
rp.push_back(p);
|
||||
}
|
||||
std::sort(rp.begin(), rp.end(), p_RootSortComparisonOperator());
|
||||
m_rootPeers.swap(rp);
|
||||
std::sort(m_rootPeers.begin(), m_rootPeers.end(), p_RootSortComparisonOperator());
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "ScopedPtr.hpp"
|
||||
#include "Fingerprint.hpp"
|
||||
#include "Containers.hpp"
|
||||
#include "Blob.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
|
@ -36,6 +37,11 @@ class RuntimeEnvironment;
|
|||
class Topology
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Hash of public keys for signing a root set definition
|
||||
*/
|
||||
typedef Blob<ZT_SHA384_DIGEST_SIZE> RootSetId;
|
||||
|
||||
Topology(const RuntimeEnvironment *renv, void *tPtr);
|
||||
|
||||
/**
|
||||
|
@ -237,7 +243,7 @@ private:
|
|||
RWMutex m_peers_l; // locks m_peers, m_roots, and m_rootPeers
|
||||
Map< uint64_t, SharedPtr< Path > > m_paths;
|
||||
Map< Address, SharedPtr< Peer > > m_peers;
|
||||
Set< Identity > m_roots;
|
||||
Map< Identity, Set< SubscriptionKeyHash > > m_roots;
|
||||
Vector< SharedPtr< Peer > > m_rootPeers;
|
||||
};
|
||||
|
||||
|
|
|
@ -132,14 +132,19 @@ char *decimal(unsigned long n, char s[24]) noexcept
|
|||
|
||||
char *hex(uint64_t i, char buf[17]) noexcept
|
||||
{
|
||||
if (i) {
|
||||
char *p = buf + 16;
|
||||
*p = 0;
|
||||
while (i) {
|
||||
*(--p) = HEXCHARS[i & 0xfU];
|
||||
i >>= 4;
|
||||
if (i != 0) {
|
||||
char *p = nullptr;
|
||||
for (int b = 60; b >= 0; b -= 4) {
|
||||
const unsigned int nyb = (unsigned int)(i >> (unsigned int)b) & 0xfU;
|
||||
if (p) {
|
||||
*(p++) = HEXCHARS[nyb];
|
||||
} else if (nyb != 0) {
|
||||
p = buf;
|
||||
*(p++) = HEXCHARS[nyb];
|
||||
}
|
||||
}
|
||||
return p;
|
||||
*p = 0;
|
||||
return buf;
|
||||
} else {
|
||||
buf[0] = '0';
|
||||
buf[1] = 0;
|
||||
|
@ -267,20 +272,18 @@ void getSecureRandom(void *const buf, unsigned int bytes) noexcept
|
|||
close(devURandomFd);
|
||||
#endif
|
||||
|
||||
// Mix in additional entropy from time, the address of 'buf', CPU RDRAND if present, etc.
|
||||
randomState[0] += (uint64_t)time(nullptr);
|
||||
randomState[1] += (uint64_t)((uintptr_t)buf);
|
||||
#ifdef __UNIX_LIKE__
|
||||
randomState[2] += (uint64_t)getpid();
|
||||
randomState[3] += (uint64_t)getppid();
|
||||
randomState[0] += (uint64_t)getpid();
|
||||
randomState[1] += (uint64_t)getppid();
|
||||
#endif
|
||||
#ifdef ZT_ARCH_X64
|
||||
if (CPUID.rdrand) {
|
||||
// RDRAND is very slow on some chips, so only sample it a little bit for extra entropy.
|
||||
uint64_t tmp = 0;
|
||||
for (int k = 0; k < ZT_GETSECURERANDOM_STATE_SIZE; ++k) {
|
||||
_rdrand64_step((unsigned long long *)&tmp);
|
||||
randomState[k] ^= tmp;
|
||||
}
|
||||
_rdrand64_step((unsigned long long *)&tmp);
|
||||
randomState[2] ^= tmp;
|
||||
_rdrand64_step((unsigned long long *)&tmp);
|
||||
randomState[3] ^= tmp;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -289,6 +292,9 @@ void getSecureRandom(void *const buf, unsigned int bytes) noexcept
|
|||
// replacing the first 64 bytes with this hash, and then re-initializing
|
||||
// AES with the first 32 bytes.
|
||||
randomByteCounter = 0;
|
||||
randomState[4] += (uint64_t)((uintptr_t)buf);
|
||||
randomState[5] += (uint64_t)bytes;
|
||||
randomState[6] += (uint64_t)time(nullptr);
|
||||
SHA512(randomState, randomState, sizeof(randomState));
|
||||
randomGen.init(randomState);
|
||||
}
|
||||
|
|
|
@ -144,9 +144,6 @@ char *decimal(unsigned long n, char s[24]) noexcept;
|
|||
/**
|
||||
* Convert an unsigned integer into hex
|
||||
*
|
||||
* The returned pointer won't point to the start of 'buf', since
|
||||
* hex writing is done in reverse order.
|
||||
*
|
||||
* @param i Any unsigned integer
|
||||
* @param s Buffer to receive hex, must be at least (2*sizeof(i))+1 in size or overflow will occur.
|
||||
* @return Pointer to s containing hex string with trailing zero byte
|
||||
|
|
191
core/zerotier.h
191
core/zerotier.h
|
@ -276,6 +276,181 @@ typedef void ZT_Identity;
|
|||
*/
|
||||
typedef void ZT_Locator;
|
||||
|
||||
/**
|
||||
* Full identity fingerprint with address and 384-bit hash of public key(s)
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Short address (only least significant 40 bits are used)
|
||||
*/
|
||||
uint64_t address;
|
||||
|
||||
/**
|
||||
* 384-bit hash of identity public key(s)
|
||||
*/
|
||||
uint8_t hash[48];
|
||||
} ZT_Fingerprint;
|
||||
|
||||
/**
|
||||
* Maximum length of string fields in identification certificates
|
||||
*/
|
||||
#define ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH 127
|
||||
|
||||
/**
|
||||
* Maximum length of a signature
|
||||
*/
|
||||
#define ZT_IDENTIFICATION_CERTIFICATE_MAX_SIGNATURE_SIZE 256
|
||||
|
||||
/**
|
||||
* Information about a real world entity.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char country[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char organization[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char unit[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char locality[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char province[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char streetAddress[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char postalCode[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char commonName[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char serialNo[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char email[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
char url[ZT_IDENTIFICATION_CERTIFICATE_MAX_STRING_LENGTH + 1];
|
||||
} ZT_IdentificationCertificate_Name;
|
||||
|
||||
/**
|
||||
* Identity and optional locator for a node
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Identity (never NULL)
|
||||
*/
|
||||
const ZT_Identity *identity;
|
||||
|
||||
/**
|
||||
* Locator or NULL if not specified
|
||||
*/
|
||||
const ZT_Locator *locator;
|
||||
} ZT_IdentificationCertificate_Node;
|
||||
|
||||
/**
|
||||
* ID and primary controller for a network
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Network ID
|
||||
*/
|
||||
uint64_t id;
|
||||
|
||||
/**
|
||||
* Full fingerprint of primary controller
|
||||
*/
|
||||
ZT_Fingerprint controller;
|
||||
} ZT_IdentificationCertificate_Network;
|
||||
|
||||
/**
|
||||
* Identification certificate subject
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Identities and optional locators of nodes
|
||||
*/
|
||||
ZT_IdentificationCertificate_Node *nodes;
|
||||
|
||||
/**
|
||||
* Number of nodes
|
||||
*/
|
||||
unsigned int nodeCount;
|
||||
|
||||
/**
|
||||
* Networks owned by this entity
|
||||
*/
|
||||
ZT_IdentificationCertificate_Network *networks;
|
||||
|
||||
/**
|
||||
* Number of networks
|
||||
*/
|
||||
unsigned int networkCount;
|
||||
|
||||
/**
|
||||
* Information about owner of items.
|
||||
*/
|
||||
ZT_IdentificationCertificate_Name name;
|
||||
} ZT_IdentificationCertificate_Subject;
|
||||
|
||||
/**
|
||||
* Identification certificate
|
||||
*
|
||||
* This is designed so it could be converted to/from an X509 format
|
||||
* for interoperability with X509 systems. OCSP could be implemented
|
||||
* too, though it would probably require the development of an OCSP
|
||||
* proxy server that queried the issuer via the ZeroTier protocol.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Serial number, a SHA384 hash of this certificate.
|
||||
*/
|
||||
uint8_t serialNo[48];
|
||||
|
||||
/**
|
||||
* Certificate version
|
||||
*/
|
||||
unsigned int version;
|
||||
|
||||
/**
|
||||
* Maximum path length from this certificate toward further certificates.
|
||||
*
|
||||
* Subjects may sign other certificates whose path lengths are less than
|
||||
* this value. A value of zero indicates that no identification certificates
|
||||
* may be signed (not a CA).
|
||||
*/
|
||||
unsigned int maxPathLength;
|
||||
|
||||
/**
|
||||
* Flags (for future use, currently zero).
|
||||
*
|
||||
* This could be used to implement key usage flags similar to X509 if
|
||||
* these are needed.
|
||||
*/
|
||||
uint64_t flags;
|
||||
|
||||
/**
|
||||
* Valid time range: not before, not after.
|
||||
*/
|
||||
int64_t validity[2];
|
||||
|
||||
/**
|
||||
* Subject of certificate
|
||||
*/
|
||||
ZT_IdentificationCertificate_Subject subject;
|
||||
|
||||
/**
|
||||
* Issuer node identity and public key(s).
|
||||
*/
|
||||
const ZT_Identity *issuer;
|
||||
|
||||
/**
|
||||
* Issuer information
|
||||
*/
|
||||
ZT_IdentificationCertificate_Name issuerName;
|
||||
|
||||
/**
|
||||
* Signature by issuer (algorithm determined by identity type).
|
||||
*/
|
||||
uint8_t signature[ZT_IDENTIFICATION_CERTIFICATE_MAX_SIGNATURE_SIZE];
|
||||
|
||||
/**
|
||||
* Size of signature in bytes.
|
||||
*/
|
||||
unsigned int signatureSize;
|
||||
} ZT_IdentificationCertificate;
|
||||
|
||||
/**
|
||||
* Credential type IDs
|
||||
*/
|
||||
|
@ -308,22 +483,6 @@ enum ZT_EndpointType
|
|||
ZT_ENDPOINT_TYPE_IP_HTTP = 8 // IP/HTTP encapsulation
|
||||
};
|
||||
|
||||
/**
|
||||
* Full identity fingerprint with address and 384-bit hash of public key(s)
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Short address (only least significant 40 bits are used)
|
||||
*/
|
||||
uint64_t address;
|
||||
|
||||
/**
|
||||
* 384-bit hash of identity public key(s)
|
||||
*/
|
||||
uint8_t hash[48];
|
||||
} ZT_Fingerprint;
|
||||
|
||||
/**
|
||||
* Flag indicating that VL1 tracing should be generated
|
||||
*/
|
||||
|
|
38
main.cpp
38
main.cpp
|
@ -11,9 +11,41 @@
|
|||
*/
|
||||
/****/
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#include "zerotier_cgo.h"
|
||||
|
||||
int main() {
|
||||
ZeroTierMain();
|
||||
return 0;
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
// Fork into background if run with 'service -d'. This is best done prior
|
||||
// to launching the Go code, since Go likes to start thread pools and stuff
|
||||
// that don't play nice with fork. This is obviously unsupported on Windows.
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
for(int i=1;i<argc;) {
|
||||
if (strcmp(argv[i++], "service") == 0) {
|
||||
for(;i<argc;) {
|
||||
if (strcmp(argv[i++], "-d") == 0) {
|
||||
long p = (long)fork();
|
||||
if (p < 0) {
|
||||
fprintf(stderr,"FATAL: fork() failed!\n");
|
||||
return -1;
|
||||
} else if (p > 0) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ZeroTierMain();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ var nullLogger = log.New(ioutil.Discard, "", 0)
|
|||
|
||||
const (
|
||||
NetworkIDStringLength = 16
|
||||
NEtworkIDLength = 8
|
||||
NetworkIDLength = 8
|
||||
AddressStringLength = 10
|
||||
AddressLength = 5
|
||||
|
||||
|
@ -883,7 +883,7 @@ func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, dataP unsafe.Poi
|
|||
*((*uintptr)(dataP)) = 0
|
||||
tmp, found := node.stateObjectGet(int(objType), *((*[2]uint64)(id)))
|
||||
if found && len(tmp) > 0 {
|
||||
cData := C.malloc(C.ulong(len(tmp))) // GoGlue sends free() to the core as the free function
|
||||
cData := C.ZT_malloc(C.ulong(len(tmp))) // GoGlue sends free() to the core as the free function
|
||||
if uintptr(cData) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
|
|
@ -724,3 +724,6 @@ extern "C" int ZT_isTemporaryV6Address(const char *ifname, const struct sockaddr
|
|||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void *ZT_malloc(unsigned long s)
|
||||
{ return (void *)malloc((size_t)s); }
|
||||
|
|
|
@ -50,6 +50,8 @@ void ZT_GoTap_setMtu(ZT_GoTap *tap,unsigned int mtu);
|
|||
|
||||
int ZT_isTemporaryV6Address(const char *ifname,const struct sockaddr_storage *a);
|
||||
|
||||
void *ZT_malloc(unsigned long s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue