Docs, code cleanup, and protect the extra new fields of HELLO with encryption as a precaution.

This commit is contained in:
Adam Ierymenko 2017-02-05 16:19:03 -08:00
parent 594cb1fad8
commit 43182f8f57
13 changed files with 163 additions and 116 deletions

View file

@ -72,13 +72,11 @@ This manual describes the design and operation of ZeroTier and its associated se
ZeroTier is a smart Ethernet switch for planet Earth. ZeroTier is a smart Ethernet switch for planet Earth.
We've re-thought networking from first principles to deliver the flat end-to-end simplicity of the original pre-NAT pre-mobility Internet in a way that meets the security and mobility requirements of the 21st century. ZeroTier transforms the world into a unified modern data center where VPN, SDN, SD-WAN, and application peer to peer networking converge and where the distinction between the cloud and the endpoint largely disappears. All the complexity of managing these networking aspects as disparate systems is replaced by the simplicity of a single virtual cloud. We've re-thought networking from first principles to deliver the flat end-to-end simplicity of the original pre-NAT pre-mobility Internet, yet in a way that meets the security and mobility needs of the 21st century. ZeroTier erases the distinction between cloud and endpoint, bringing modern data center SDN to every device regardless of its physical location. The objectives of VPN, SDN, SD-WAN, and application peer to peer networking can all be achieved together and with reduced complexity.
At first some users struggle with this paradigm, finding it difficult to forget the fragmentation and complexity that has accreted around networking over the past decade or two. We urge skeptical users to just try it and see how many networking acronyms vanish before their eyes. Unlike most networking products it won't take you hours, days, or weeks to test or deploy ZeroTier. Most of the time everything just works with zero configuration, and most users with some level of TCP/IP knowledge can get up and running in minutes. More advanced features like federation, rules, micro-segmentation, capability based security credentials, network monitoring, and clustering are available but you don't need to worry about them until they're needed.
Unlike most networking products it won't take you hours, days, or weeks to test or deploy ZeroTier. Most of the time everything just works with zero configuration, and most users with some level of TCP/IP knowledge can get up and running in minutes. More advanced features like rules, micro-segmentation, capability based security credentials, network monitoring, and clustering are available but you don't need to worry about them until they're needed. The first section (2) of this guide explains ZeroTier's design and operation in detail and is written for users with at least an intermediate knowledge of topics like TCP/IP and Ethernet networking. All of it is not required reading for most users, but we've created a deep technical dive to satisfy the desire of IT professionals to understand the systems that they use. Sections 3 and 4 deal more concretely with the ZeroTier One endpoint service software and how to deploy for common use cases.
The first section (2) of this guide explains ZeroTier's design and operation in detail and is written for users with at least an intermediate knowledge of topics like TCP/IP and Ethernet networking. Reading and understanding everything in it is not mandatory but we've written it as a deep technical dive as serious IT users typically like to understand the systems they deploy and use. Sections 3 and 4 deal more concretely with the ZeroTier One endpoint service software and how to deploy for common use cases.
## **2.** How it Works <a name="2"></a> ## **2.** How it Works <a name="2"></a>
@ -88,7 +86,7 @@ ZeroTier is comprised of two closely coupled but conceptually distinct layers [i
To build a planetary data center we first had to begin with the wiring. Tunneling into the Earth's core and putting a giant wire closet down there wasn't an option, so we decided to use software to build virtual wires over the existing Internet instead. To build a planetary data center we first had to begin with the wiring. Tunneling into the Earth's core and putting a giant wire closet down there wasn't an option, so we decided to use software to build virtual wires over the existing Internet instead.
In conventional networks L1 (OSI layer 1) refers to the actual CAT5/CAT6 cables or wireless radio channels over which data is carried and the physical transciever chips that modulate and demodulate it. VL1 is a peer to peer network that does the same thing by using encryption, authentication, and a lot of networking tricks to create virtual wires as needed. In conventional networks L1 (OSI layer 1) refers to the actual CAT5/CAT6 cables or wireless radio channels over which data is carried and the physical transciever chips that modulate and demodulate it. VL1 is a peer to peer network that does the same thing by using encryption, authentication, and a lot of networking tricks to create virtual wires on a dyniamic as-needed basis.
### **2.1.1.** Network Topology and Peer Discovery ### **2.1.1.** Network Topology and Peer Discovery
@ -98,7 +96,7 @@ Roots run the same software as regular endpoints but reside at fast stable locat
There is only one planet. Earth's root servers are operated by ZeroTier, Inc. as a free service. Their presence defines and unifies the global data center where we all reside. There is only one planet. Earth's root servers are operated by ZeroTier, Inc. as a free service. Their presence defines and unifies the global data center where we all reside.
Users can create "moons." These nominate additional roots for redundancy or performance. The most common reasons for doing this are to eliminate hard dependency on ZeroTier's third party infrastructure or to designate local roots inside your building or cloud so ZeroTier can work without a connection to the Internet. Moons are by no means required and most of our users get by just fine without them. Moons can be created by users. These nominate additional roots for redundancy or performance. The most common reasons for doing this are to eliminate hard dependency on ZeroTier's third party infrastructure or to designate local roots inside your building or cloud so ZeroTier can work without a connection to the Internet. Moons are by no means required and most of our users get by just fine without them.
When peers start out they have no direct links to one another, only upstream to roots. Every peer on VL1 possesses a globally unique address, but unlike IP addresses these are opaque cryptographic identifiers that encode no routing information. To communicate peers first send packets "up" the tree, and as these packets traverse the network they trigger the opportunistic creation of direct links along the way. The tree is constantly trying to "collapse itself" to optimize itself to the pattern of traffic it is carrying. When peers start out they have no direct links to one another, only upstream to roots. Every peer on VL1 possesses a globally unique address, but unlike IP addresses these are opaque cryptographic identifiers that encode no routing information. To communicate peers first send packets "up" the tree, and as these packets traverse the network they trigger the opportunistic creation of direct links along the way. The tree is constantly trying to "collapse itself" to optimize itself to the pattern of traffic it is carrying.

View file

@ -293,6 +293,11 @@
*/ */
#define ZT_PEER_PATH_EXPIRATION ((ZT_PEER_PING_PERIOD * 4) + 3000) #define ZT_PEER_PATH_EXPIRATION ((ZT_PEER_PING_PERIOD * 4) + 3000)
/**
* Send a full HELLO every this often (ms)
*/
#define ZT_PEER_SEND_FULL_HELLO_EVERY (ZT_PEER_PING_PERIOD * 2)
/** /**
* How often to retry expired paths that we're still remembering * How often to retry expired paths that we're still remembering
*/ */

View file

@ -46,7 +46,7 @@ static inline void _computeMemoryHardHash(const void *publicKey,unsigned int pub
// but is not what we want for sequential memory-harndess. // but is not what we want for sequential memory-harndess.
memset(genmem,0,ZT_IDENTITY_GEN_MEMORY); memset(genmem,0,ZT_IDENTITY_GEN_MEMORY);
Salsa20 s20(digest,256,(char *)digest + 32); Salsa20 s20(digest,256,(char *)digest + 32);
s20.encrypt20((char *)genmem,(char *)genmem,64); s20.crypt20((char *)genmem,(char *)genmem,64);
for(unsigned long i=64;i<ZT_IDENTITY_GEN_MEMORY;i+=64) { for(unsigned long i=64;i<ZT_IDENTITY_GEN_MEMORY;i+=64) {
unsigned long k = i - 64; unsigned long k = i - 64;
*((uint64_t *)((char *)genmem + i)) = *((uint64_t *)((char *)genmem + k)); *((uint64_t *)((char *)genmem + i)) = *((uint64_t *)((char *)genmem + k));
@ -57,7 +57,7 @@ static inline void _computeMemoryHardHash(const void *publicKey,unsigned int pub
*((uint64_t *)((char *)genmem + i + 40)) = *((uint64_t *)((char *)genmem + k + 40)); *((uint64_t *)((char *)genmem + i + 40)) = *((uint64_t *)((char *)genmem + k + 40));
*((uint64_t *)((char *)genmem + i + 48)) = *((uint64_t *)((char *)genmem + k + 48)); *((uint64_t *)((char *)genmem + i + 48)) = *((uint64_t *)((char *)genmem + k + 48));
*((uint64_t *)((char *)genmem + i + 56)) = *((uint64_t *)((char *)genmem + k + 56)); *((uint64_t *)((char *)genmem + i + 56)) = *((uint64_t *)((char *)genmem + k + 56));
s20.encrypt20((char *)genmem + i,(char *)genmem + i,64); s20.crypt20((char *)genmem + i,(char *)genmem + i,64);
} }
// Render final digest using genmem as a lookup table // Render final digest using genmem as a lookup table
@ -67,7 +67,7 @@ static inline void _computeMemoryHardHash(const void *publicKey,unsigned int pub
uint64_t tmp = ((uint64_t *)genmem)[idx2]; uint64_t tmp = ((uint64_t *)genmem)[idx2];
((uint64_t *)genmem)[idx2] = ((uint64_t *)digest)[idx1]; ((uint64_t *)genmem)[idx2] = ((uint64_t *)digest)[idx1];
((uint64_t *)digest)[idx1] = tmp; ((uint64_t *)digest)[idx1] = tmp;
s20.encrypt20(digest,digest,64); s20.crypt20(digest,digest,64);
} }
} }

View file

@ -213,44 +213,19 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION); const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION);
const uint64_t timestamp = at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP); const uint64_t timestamp = at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
Identity id;
InetAddress externalSurfaceAddress;
uint64_t planetWorldId = 0;
uint64_t planetWorldTimestamp = 0;
std::vector< std::pair<uint64_t,uint64_t> > moonIdsAndTimestamps;
{
unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY);
// Get external surface address if present (was not in old versions)
if (ptr < size())
ptr += externalSurfaceAddress.deserialize(*this,ptr);
// Get primary planet world ID and world timestamp if present
if ((ptr + 16) <= size()) {
planetWorldId = at<uint64_t>(ptr); ptr += 8;
planetWorldTimestamp = at<uint64_t>(ptr);
}
// Get moon IDs and timestamps if present
if ((ptr + 2) <= size()) {
unsigned int numMoons = at<uint16_t>(ptr); ptr += 2;
for(unsigned int i=0;i<numMoons;++i) {
if ((World::Type)(*this)[ptr++] == World::TYPE_MOON)
moonIdsAndTimestamps.push_back(std::pair<uint64_t,uint64_t>(at<uint64_t>(ptr),at<uint64_t>(ptr + 8)));
ptr += 16;
}
}
}
if (fromAddress != id.address()) {
TRACE("dropped HELLO from %s(%s): identity not for sending address",fromAddress.toString().c_str(),_path->address().toString().c_str());
return true;
}
if (protoVersion < ZT_PROTO_VERSION_MIN) { if (protoVersion < ZT_PROTO_VERSION_MIN) {
TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_path->address().toString().c_str()); TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_path->address().toString().c_str());
return true; return true;
} }
Identity id;
unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY);
if (fromAddress != id.address()) {
TRACE("dropped HELLO from %s(%s): identity does not match packet source address",fromAddress.toString().c_str(),_path->address().toString().c_str());
return true;
}
SharedPtr<Peer> peer(RR->topology->getPeer(id.address())); SharedPtr<Peer> peer(RR->topology->getPeer(id.address()));
if (peer) { if (peer) {
// We already have an identity with this address -- check for collisions // We already have an identity with this address -- check for collisions
@ -324,6 +299,43 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
// VALID -- if we made it here, packet passed identity and authenticity checks! // VALID -- if we made it here, packet passed identity and authenticity checks!
// Get external surface address if present (was not in old versions)
InetAddress externalSurfaceAddress;
if (ptr < size())
ptr += externalSurfaceAddress.deserialize(*this,ptr);
// Get primary planet world ID and world timestamp if present
uint64_t planetWorldId = 0;
uint64_t planetWorldTimestamp = 0;
if ((ptr + 16) <= size()) {
planetWorldId = at<uint64_t>(ptr); ptr += 8;
planetWorldTimestamp = at<uint64_t>(ptr);
}
std::vector< std::pair<uint64_t,uint64_t> > moonIdsAndTimestamps;
if (ptr < size()) {
// Remainder of packet, if present, is encrypted
cryptField(peer->key(),ptr,size() - ptr);
// Get moon IDs and timestamps if present
if ((ptr + 2) <= size()) {
unsigned int numMoons = at<uint16_t>(ptr); ptr += 2;
for(unsigned int i=0;i<numMoons;++i) {
if ((World::Type)(*this)[ptr++] == World::TYPE_MOON)
moonIdsAndTimestamps.push_back(std::pair<uint64_t,uint64_t>(at<uint64_t>(ptr),at<uint64_t>(ptr + 8)));
ptr += 16;
}
}
// Handle COR if present (older versions don't send this)
if ((ptr + 2) <= size()) {
//const unsigned int corSize = at<uint16_t>(ptr); ptr += 2;
ptr += 2;
CertificateOfRepresentation cor;
ptr += cor.deserialize(*this,ptr);
}
}
// Learn our external surface address from other peers to help us negotiate symmetric NATs // Learn our external surface address from other peers to help us negotiate symmetric NATs
// and detect changes to our global IP that can trigger path renegotiation. // and detect changes to our global IP that can trigger path renegotiation.
if ((externalSurfaceAddress)&&(hops() == 0)) if ((externalSurfaceAddress)&&(hops() == 0))
@ -337,6 +349,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
if (protoVersion >= 5) { if (protoVersion >= 5) {
_path->address().serialize(outp); _path->address().serialize(outp);
} else { } else {
@ -387,6 +400,11 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
} }
outp.setAt<uint16_t>(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2))); outp.setAt<uint16_t>(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2)));
const unsigned int corSizeAt = outp.size();
outp.addSize(2);
RR->topology->appendCertificateOfRepresentation(outp);
outp.setAt(corSizeAt,(uint16_t)(outp.size() - (corSizeAt + 2)));
outp.armor(peer->key(),true); outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),now); _path->send(RR,outp.data(),outp.size(),now);
@ -586,7 +604,7 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<
const InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); const InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
if (RR->node->shouldUsePathForZeroTierTraffic(with,_path->localAddress(),atAddr)) { if (RR->node->shouldUsePathForZeroTierTraffic(with,_path->localAddress(),atAddr)) {
RR->node->putPacket(_path->localAddress(),atAddr,"ABRE",4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls RR->node->putPacket(_path->localAddress(),atAddr,"ABRE",4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
rendezvousWith->attemptToContactAt(_path->localAddress(),atAddr,RR->node->now()); rendezvousWith->attemptToContactAt(_path->localAddress(),atAddr,RR->node->now(),false);
TRACE("RENDEZVOUS from %s says %s might be at %s, sent verification attempt",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); TRACE("RENDEZVOUS from %s says %s might be at %s, sent verification attempt",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
} else { } else {
TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since path is not suitable",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since path is not suitable",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
@ -1155,7 +1173,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha
if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(peer->address(),_path->localAddress(),a)) ) { if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(peer->address(),_path->localAddress(),a)) ) {
if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
peer->attemptToContactAt(InetAddress(),a,now); peer->attemptToContactAt(InetAddress(),a,now,false);
} else { } else {
TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str()); TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str());
} }
@ -1174,7 +1192,7 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha
if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(peer->address(),_path->localAddress(),a)) ) { if ( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && (!redundant) && (RR->node->shouldUsePathForZeroTierTraffic(peer->address(),_path->localAddress(),a)) ) {
if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
peer->attemptToContactAt(InetAddress(),a,now); peer->attemptToContactAt(InetAddress(),a,now,false);
} else { } else {
TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str()); TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str());
} }

View file

@ -70,7 +70,7 @@ Node::Node(void *uptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now) :
Utils::getSecureRandom(foo,32); Utils::getSecureRandom(foo,32);
_prng.init(foo,256,foo); _prng.init(foo,256,foo);
memset(_prngStream,0,sizeof(_prngStream)); memset(_prngStream,0,sizeof(_prngStream));
_prng.encrypt12(_prngStream,_prngStream,sizeof(_prngStream)); _prng.crypt12(_prngStream,_prngStream,sizeof(_prngStream));
std::string idtmp(dataStoreGet("identity.secret")); std::string idtmp(dataStoreGet("identity.secret"));
if ((!idtmp.length())||(!RR->identity.fromString(idtmp))||(!RR->identity.hasPrivate())) { if ((!idtmp.length())||(!RR->identity.fromString(idtmp))||(!RR->identity.hasPrivate())) {
@ -686,7 +686,7 @@ uint64_t Node::prng()
{ {
unsigned int p = (++_prngStreamPtr % ZT_NODE_PRNG_BUF_SIZE); unsigned int p = (++_prngStreamPtr % ZT_NODE_PRNG_BUF_SIZE);
if (!p) if (!p)
_prng.encrypt12(_prngStream,_prngStream,sizeof(_prngStream)); _prng.crypt12(_prngStream,_prngStream,sizeof(_prngStream));
return _prngStream[p]; return _prngStream[p];
} }

View file

@ -18,9 +18,21 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Packet.hpp" #include "Packet.hpp"
#ifdef _MSC_VER
#define FORCE_INLINE static __forceinline
#include <intrin.h>
#pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
#pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */
#else
#define FORCE_INLINE static inline
#endif
namespace ZeroTier { namespace ZeroTier {
/************************************************************************** */ /************************************************************************** */
@ -367,7 +379,7 @@ LZ4_decompress_*_continue() :
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ #define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
#include <stdint.h> //#include <stdint.h>
typedef struct { typedef struct {
uint32_t hashTable[LZ4_HASH_SIZE_U32]; uint32_t hashTable[LZ4_HASH_SIZE_U32];
@ -536,6 +548,7 @@ union LZ4_streamDecode_u {
/*-************************************ /*-************************************
* Compiler Options * Compiler Options
**************************************/ **************************************/
#if 0
#ifdef _MSC_VER /* Visual Studio */ #ifdef _MSC_VER /* Visual Studio */
# define FORCE_INLINE static __forceinline # define FORCE_INLINE static __forceinline
# include <intrin.h> # include <intrin.h>
@ -550,6 +563,7 @@ union LZ4_streamDecode_u {
# define FORCE_INLINE static # define FORCE_INLINE static
# endif # endif
#endif /* _MSC_VER */ #endif /* _MSC_VER */
#endif
#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) #if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__)
# define expect(expr,value) (__builtin_expect ((expr),(value)) ) # define expect(expr,value) (__builtin_expect ((expr),(value)) )
@ -564,38 +578,39 @@ union LZ4_streamDecode_u {
/*-************************************ /*-************************************
* Memory routines * Memory routines
**************************************/ **************************************/
#include <stdlib.h> /* malloc, calloc, free */ //#include <stdlib.h> /* malloc, calloc, free */
#define ALLOCATOR(n,s) calloc(n,s) #define ALLOCATOR(n,s) calloc(n,s)
#define FREEMEM free #define FREEMEM free
#include <string.h> /* memset, memcpy */ //#include <string.h> /* memset, memcpy */
#define MEM_INIT memset #define MEM_INIT memset
/*-************************************ /*-************************************
* Basic Types * Basic Types
**************************************/ **************************************/
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) //#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
# include <stdint.h> //# include <stdint.h>
typedef uint8_t BYTE; typedef uint8_t BYTE;
typedef uint16_t U16; typedef uint16_t U16;
typedef uint32_t U32; typedef uint32_t U32;
typedef int32_t S32; typedef int32_t S32;
typedef uint64_t U64; typedef uint64_t U64;
typedef uintptr_t uptrval; typedef uintptr_t uptrval;
#else /*#else
typedef unsigned char BYTE; typedef unsigned char BYTE;
typedef unsigned short U16; typedef unsigned short U16;
typedef unsigned int U32; typedef unsigned int U32;
typedef signed int S32; typedef signed int S32;
typedef unsigned long long U64; typedef unsigned long long U64;
typedef size_t uptrval; /* generally true, except OpenVMS-64 */ typedef size_t uptrval;
#endif #endif */
#if defined(__x86_64__) typedef uintptr_t reg_t;
typedef U64 reg_t; /* 64-bits in x32 mode */ //#if defined(__x86_64__)
#else // typedef U64 reg_t; /* 64-bits in x32 mode */
typedef size_t reg_t; /* 32-bits in x32 mode */ //#else
#endif // typedef size_t reg_t; /* 32-bits in x32 mode */
//#endif
/*-************************************ /*-************************************
* Reading and writing into memory * Reading and writing into memory
@ -606,7 +621,6 @@ static unsigned LZ4_isLittleEndian(void)
return one.c[0]; return one.c[0];
} }
#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) #if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2)
/* lie to the compiler about data alignment; use with caution */ /* lie to the compiler about data alignment; use with caution */
@ -1975,10 +1989,10 @@ void Packet::armor(const void *key,bool encryptPayload)
// MAC key is always the first 32 bytes of the Salsa20 key stream // MAC key is always the first 32 bytes of the Salsa20 key stream
// This is the same construction DJB's NaCl library uses // This is the same construction DJB's NaCl library uses
s20.encrypt12(ZERO_KEY,macKey,sizeof(macKey)); s20.crypt12(ZERO_KEY,macKey,sizeof(macKey));
if (encryptPayload) if (encryptPayload)
s20.encrypt12(payload,payload,payloadLen); s20.crypt12(payload,payload,payloadLen);
Poly1305::compute(mac,payload,payloadLen,macKey); Poly1305::compute(mac,payload,payloadLen,macKey);
memcpy(field(ZT_PACKET_IDX_MAC,8),mac,8); memcpy(field(ZT_PACKET_IDX_MAC,8),mac,8);
@ -1995,20 +2009,30 @@ bool Packet::dearmor(const void *key)
if ((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)||(cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)) { if ((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)||(cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)) {
_salsa20MangleKey((const unsigned char *)key,mangledKey); _salsa20MangleKey((const unsigned char *)key,mangledKey);
Salsa20 s20(mangledKey,256,field(ZT_PACKET_IDX_IV,8)/*,ZT_PROTO_SALSA20_ROUNDS*/); Salsa20 s20(mangledKey,256,field(ZT_PACKET_IDX_IV,8));
s20.encrypt12(ZERO_KEY,macKey,sizeof(macKey)); s20.crypt12(ZERO_KEY,macKey,sizeof(macKey));
Poly1305::compute(mac,payload,payloadLen,macKey); Poly1305::compute(mac,payload,payloadLen,macKey);
if (!Utils::secureEq(mac,field(ZT_PACKET_IDX_MAC,8),8)) if (!Utils::secureEq(mac,field(ZT_PACKET_IDX_MAC,8),8))
return false; return false;
if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)
s20.decrypt12(payload,payload,payloadLen); s20.crypt12(payload,payload,payloadLen);
return true; return true;
} else return false; // unrecognized cipher suite } else return false; // unrecognized cipher suite
} }
void Packet::cryptField(const void *key,unsigned int start,unsigned int len)
{
unsigned char mangledKey[32];
uint64_t iv = Utils::hton((uint64_t)start ^ at<uint64_t>(ZT_PACKET_IDX_IV));
_salsa20MangleKey((const unsigned char *)key,mangledKey);
Salsa20 s20(mangledKey,256,&iv);
unsigned char *const ptr = field(start,len);
s20.crypt12(ptr,ptr,len);
}
bool Packet::compress() bool Packet::compress()
{ {
unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH * 2]; unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH * 2];

View file

@ -542,6 +542,7 @@ public:
* [<[...] destination address to which packet was sent>] * [<[...] destination address to which packet was sent>]
* <[8] 64-bit world ID of current planet> * <[8] 64-bit world ID of current planet>
* <[8] 64-bit timestamp of current planet> * <[8] 64-bit timestamp of current planet>
* [... remainder if packet is encrypted using cryptField() ...]
* <[2] 16-bit number of moons> * <[2] 16-bit number of moons>
* [<[1] 8-bit type ID of moon>] * [<[1] 8-bit type ID of moon>]
* [<[8] 64-bit world ID of moon>] * [<[8] 64-bit world ID of moon>]
@ -550,9 +551,10 @@ public:
* <[2] 16-bit length of certificate of representation> * <[2] 16-bit length of certificate of representation>
* [... certificate of representation ...] * [... certificate of representation ...]
* *
* HELLO is sent in the clear, and therefore cannot contain anything * The initial fields of HELLO are sent in the clear. Fields after the
* secret or highly confidential. It should contain nothing that is * planet definition (which are common knowledge) are however encrypted
* not either public or easy to obtain via other means. * using the cryptField() function. The packet is MAC'd as usual using
* the same MAC construct as other packets.
* *
* The destination address is the wire address to which this packet is * The destination address is the wire address to which this packet is
* being sent, and in OK is *also* the destination address of the OK * being sent, and in OK is *also* the destination address of the OK
@ -566,7 +568,7 @@ public:
* 0x04 - 6-byte IPv4 UDP address/port -- format: <[4] IP>, <[2] port> * 0x04 - 6-byte IPv4 UDP address/port -- format: <[4] IP>, <[2] port>
* 0x06 - 18-byte IPv6 UDP address/port -- format: <[16] IP>, <[2] port> * 0x06 - 18-byte IPv6 UDP address/port -- format: <[16] IP>, <[2] port>
* *
* OK payload: * OK payload (note that OK is encrypted):
* <[8] timestamp (echoed from original HELLO)> * <[8] timestamp (echoed from original HELLO)>
* <[1] protocol version (of responder)> * <[1] protocol version (of responder)>
* <[1] software major version (of responder)> * <[1] software major version (of responder)>
@ -576,6 +578,8 @@ public:
* [<[...] destination address>] * [<[...] destination address>]
* <[2] 16-bit length of world update or 0 if none> * <[2] 16-bit length of world update or 0 if none>
* [[...] updates to planets and/or moons] * [[...] updates to planets and/or moons]
* <[2] 16-bit length of certificate of representation (of responder)>
* [... certificate of representation ...]
* *
* ERROR has no payload. * ERROR has no payload.
*/ */
@ -1348,6 +1352,25 @@ public:
*/ */
bool dearmor(const void *key); bool dearmor(const void *key);
/**
* Encrypt/decrypt a separately armored portion of a packet
*
* This keys using the same key in the same way as armor/dearmor, but
* uses a different IV computed from the packet's IV plus the starting
* point index.
*
* This currently uses Salsa20/12, but any message that uses this should
* incorporate a cipher selector to permit this to be changed later.
*
* This is currently only used to mask portions of HELLO as an extra
* security precation since most of that message is sent in the clear.
*
* @param key 32-byte key
* @param start Start of encrypted portion
* @param len Length of encrypted portion
*/
void cryptField(const void *key,unsigned int start,unsigned int len);
/** /**
* Attempt to compress payload if not already (must be unencrypted) * Attempt to compress payload if not already (must be unencrypted)
* *

View file

@ -203,7 +203,7 @@ void Peer::received(
#endif #endif
} else { } else {
TRACE("got %s via unknown path %s(%s), confirming...",Packet::verbString(verb),_id.address().toString().c_str(),path->address().toString().c_str()); TRACE("got %s via unknown path %s(%s), confirming...",Packet::verbString(verb),_id.address().toString().c_str(),path->address().toString().c_str());
attemptToContactAt(path->localAddress(),path->address(),now); attemptToContactAt(path->localAddress(),path->address(),now,true);
path->sent(now); path->sent(now);
} }
} }
@ -357,6 +357,8 @@ void Peer::sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,u
outp.append((uint64_t)RR->topology->planetWorldId()); outp.append((uint64_t)RR->topology->planetWorldId());
outp.append((uint64_t)RR->topology->planetWorldTimestamp()); outp.append((uint64_t)RR->topology->planetWorldTimestamp());
const unsigned int startCryptedPortionAt = outp.size();
std::vector<World> moons(RR->topology->moons()); std::vector<World> moons(RR->topology->moons());
outp.append((uint16_t)moons.size()); outp.append((uint16_t)moons.size());
for(std::vector<World>::const_iterator m(moons.begin());m!=moons.end();++m) { for(std::vector<World>::const_iterator m(moons.begin());m!=moons.end();++m) {
@ -368,21 +370,23 @@ void Peer::sendHELLO(const InetAddress &localAddr,const InetAddress &atAddress,u
const unsigned int corSizeAt = outp.size(); const unsigned int corSizeAt = outp.size();
outp.addSize(2); outp.addSize(2);
RR->topology->appendCertificateOfRepresentation(outp); RR->topology->appendCertificateOfRepresentation(outp);
outp.setAt(corSizeAt,(uint16_t)((outp.size() - corSizeAt) - 2)); outp.setAt(corSizeAt,(uint16_t)(outp.size() - (corSizeAt + 2)));
outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt);
RR->node->expectReplyTo(outp.packetId()); RR->node->expectReplyTo(outp.packetId());
if (atAddress) { if (atAddress) {
outp.armor(_key,false); outp.armor(_key,false); // false == don't encrypt full payload, but add MAC
RR->node->putPacket(localAddr,atAddress,outp.data(),outp.size()); RR->node->putPacket(localAddr,atAddress,outp.data(),outp.size());
} else { } else {
RR->sw->send(outp,false); RR->sw->send(outp,false); // false == don't encrypt full payload, but add MAC
} }
} }
void Peer::attemptToContactAt(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now) void Peer::attemptToContactAt(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,bool sendFullHello)
{ {
if ( (_vProto >= 5) && ( !((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0)) ) ) { if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) {
Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO);
RR->node->expectReplyTo(outp.packetId()); RR->node->expectReplyTo(outp.packetId());
outp.armor(_key,true); outp.armor(_key,true);
@ -398,7 +402,7 @@ void Peer::tryMemorizedPath(uint64_t now)
_lastTriedMemorizedPath = now; _lastTriedMemorizedPath = now;
InetAddress mp; InetAddress mp;
if (RR->node->externalPathLookup(_id.address(),-1,mp)) if (RR->node->externalPathLookup(_id.address(),-1,mp))
attemptToContactAt(InetAddress(),mp,now); attemptToContactAt(InetAddress(),mp,now,true);
} }
} }
@ -420,7 +424,7 @@ bool Peer::doPingAndKeepalive(uint64_t now,int inetAddressFamily)
if (bestp >= 0) { if (bestp >= 0) {
if ( ((now - _paths[bestp].lastReceive) >= ZT_PEER_PING_PERIOD) || (_paths[bestp].path->needsHeartbeat(now)) ) { if ( ((now - _paths[bestp].lastReceive) >= ZT_PEER_PING_PERIOD) || (_paths[bestp].path->needsHeartbeat(now)) ) {
attemptToContactAt(_paths[bestp].path->localAddress(),_paths[bestp].path->address(),now); attemptToContactAt(_paths[bestp].path->localAddress(),_paths[bestp].path->address(),now,false);
_paths[bestp].path->sent(now); _paths[bestp].path->sent(now);
} }
return true; return true;
@ -444,7 +448,7 @@ void Peer::resetWithinScope(InetAddress::IpScope scope,int inetAddressFamily,uin
Mutex::Lock _l(_paths_m); Mutex::Lock _l(_paths_m);
for(unsigned int p=0;p<_numPaths;++p) { for(unsigned int p=0;p<_numPaths;++p) {
if ( (_paths[p].path->address().ss_family == inetAddressFamily) && (_paths[p].path->address().ipScope() == scope) ) { if ( (_paths[p].path->address().ss_family == inetAddressFamily) && (_paths[p].path->address().ipScope() == scope) ) {
attemptToContactAt(_paths[p].path->localAddress(),_paths[p].path->address(),now); attemptToContactAt(_paths[p].path->localAddress(),_paths[p].path->address(),now,false);
_paths[p].path->sent(now); _paths[p].path->sent(now);
_paths[p].lastReceive = 0; // path will not be used unless it speaks again _paths[p].lastReceive = 0; // path will not be used unless it speaks again
} }

View file

@ -161,8 +161,9 @@ public:
* @param localAddr Local address * @param localAddr Local address
* @param atAddress Destination address * @param atAddress Destination address
* @param now Current time * @param now Current time
* @param sendFullHello If true, always send a full HELLO instead of just an ECHO
*/ */
void attemptToContactAt(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now); void attemptToContactAt(const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,bool sendFullHello);
/** /**
* Try a memorized or statically defined path if any are known * Try a memorized or statically defined path if any are known

View file

@ -123,7 +123,7 @@ void Salsa20::init(const void *key,unsigned int kbits,const void *iv)
#endif #endif
} }
void Salsa20::encrypt12(const void *in,void *out,unsigned int bytes) void Salsa20::crypt12(const void *in,void *out,unsigned int bytes)
throw() throw()
{ {
uint8_t tmp[64]; uint8_t tmp[64];
@ -623,7 +623,7 @@ void Salsa20::encrypt12(const void *in,void *out,unsigned int bytes)
} }
} }
void Salsa20::encrypt20(const void *in,void *out,unsigned int bytes) void Salsa20::crypt20(const void *in,void *out,unsigned int bytes)
throw() throw()
{ {
uint8_t tmp[64]; uint8_t tmp[64];

View file

@ -56,51 +56,25 @@ public:
throw(); throw();
/** /**
* Encrypt data using Salsa20/12 * Encrypt/decrypt data using Salsa20/12
* *
* @param in Input data * @param in Input data
* @param out Output buffer * @param out Output buffer
* @param bytes Length of data * @param bytes Length of data
*/ */
void encrypt12(const void *in,void *out,unsigned int bytes) void crypt12(const void *in,void *out,unsigned int bytes)
throw(); throw();
/** /**
* Encrypt data using Salsa20/20 * Encrypt/decrypt data using Salsa20/20
* *
* @param in Input data * @param in Input data
* @param out Output buffer * @param out Output buffer
* @param bytes Length of data * @param bytes Length of data
*/ */
void encrypt20(const void *in,void *out,unsigned int bytes) void crypt20(const void *in,void *out,unsigned int bytes)
throw(); throw();
/**
* Decrypt data
*
* @param in Input data
* @param out Output buffer
* @param bytes Length of data
*/
inline void decrypt12(const void *in,void *out,unsigned int bytes)
throw()
{
encrypt12(in,out,bytes);
}
/**
* Decrypt data
*
* @param in Input data
* @param out Output buffer
* @param bytes Length of data
*/
inline void decrypt20(const void *in,void *out,unsigned int bytes)
throw()
{
encrypt20(in,out,bytes);
}
private: private:
union { union {
#ifdef ZT_SALSA20_SSE #ifdef ZT_SALSA20_SSE

View file

@ -777,7 +777,7 @@ bool Switch::_trySend(Packet &packet,bool encrypt)
if ((clusterMostRecentMemberId < 0)||(viaPath->lastIn() > clusterMostRecentTs)) { if ((clusterMostRecentMemberId < 0)||(viaPath->lastIn() > clusterMostRecentTs)) {
#endif #endif
if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) * 4,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL)) { if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) * 4,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL)) {
peer->attemptToContactAt(viaPath->localAddress(),viaPath->address(),now); peer->attemptToContactAt(viaPath->localAddress(),viaPath->address(),now,false);
viaPath->sent(now); viaPath->sent(now);
} }
#ifdef ZT_ENABLE_CLUSTER #ifdef ZT_ENABLE_CLUSTER

View file

@ -182,7 +182,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes)
#else // not __WINDOWS__ #else // not __WINDOWS__
static char randomBuf[131072]; static char randomBuf[65536];
static unsigned int randomPtr = sizeof(randomBuf); static unsigned int randomPtr = sizeof(randomBuf);
static int devURandomFd = -1; static int devURandomFd = -1;
@ -215,7 +215,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes)
#endif // __WINDOWS__ or not #endif // __WINDOWS__ or not
s20.encrypt12(buf,buf,bytes); s20.crypt12(buf,buf,bytes);
} }
bool Utils::scopy(char *dest,unsigned int len,const char *src) bool Utils::scopy(char *dest,unsigned int len,const char *src)