From a0ac4a744e5ca84000d113e90cf7add616a583c3 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 17 Feb 2020 15:09:26 -0800 Subject: [PATCH] Change the type 1 identity a bit to make locallyValidate() super fast, eliminating a scaling issue with v0. --- include/ZeroTierCore.h | 3 +- node/Identity.cpp | 217 +++++++++++++++++++++++------------------ node/Identity.hpp | 47 +++++---- node/VL1.cpp | 9 ++ 4 files changed, 162 insertions(+), 114 deletions(-) diff --git a/include/ZeroTierCore.h b/include/ZeroTierCore.h index 3d685c625..ae3b5dd3f 100644 --- a/include/ZeroTierCore.h +++ b/include/ZeroTierCore.h @@ -357,7 +357,8 @@ enum ZT_TracePacketDropReason ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED = 5, ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT = 6, ZT_TRACE_PACKET_DROP_REASON_INVALID_COMPRESSED_DATA = 7, - ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB = 8 + ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB = 8, + ZT_TRACE_PACKET_DROP_REASON_REPLY_NOT_EXPECTED = 9 }; /** diff --git a/node/Identity.cpp b/node/Identity.cpp index 4f2e4d86d..a03d10232 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -11,27 +11,29 @@ */ /****/ -#include -#include - #include "Constants.hpp" #include "Identity.hpp" #include "SHA512.hpp" #include "Salsa20.hpp" +#include "AES.hpp" #include "Utils.hpp" +#include +#include +#include + namespace ZeroTier { namespace { -// These can't be changed without a new identity type. They define the -// parameters of the hashcash hashing/searching algorithm for type 0 -// identities. -#define ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN 17 -#define ZT_IDENTITY_GEN_MEMORY 2097152 +// -------------------------------------------------------------------------------------------------------------------- -// A memory-hard composition of SHA-512 and Salsa20 for hashcash hashing -static void _computeMemoryHardHash(const void *publicKey,unsigned int publicKeyBytes,void *digest,void *genmem) +// This is the memory-intensive hash function used to compute v0 identities +// from v0 public keys. + +#define ZT_V0_IDENTITY_GEN_MEMORY 2097152 + +static void _computeMemoryHardHash(const void *const publicKey,unsigned int publicKeyBytes,void *const digest,void *const genmem) noexcept { // Digest publicKey[] to obtain initial digest SHA512(digest,publicKey,publicKeyBytes); @@ -39,10 +41,10 @@ static void _computeMemoryHardHash(const void *publicKey,unsigned int publicKeyB // Initialize genmem[] using Salsa20 in a CBC-like configuration since // ordinary Salsa20 is randomly seek-able. This is good for a cipher // but is not what we want for sequential memory-hardness. - memset(genmem,0,ZT_IDENTITY_GEN_MEMORY); + memset(genmem,0,ZT_V0_IDENTITY_GEN_MEMORY); Salsa20 s20(digest,(char *)digest + 32); s20.crypt20((char *)genmem,(char *)genmem,64); - for(unsigned long i=64;ihash(); + return ((hash[47] == 0)&&(Address(hash) == _address)); + } + + default: + return false; + } +} + +const uint8_t *Identity::hash() const +{ + uint8_t *const hash = const_cast(reinterpret_cast(_hash)); + switch(_type) { + default: + memset(hash,0,48); + break; + + case C25519: + if (_hash[0] == 0) + SHA384(hash,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); + break; + + case P384: + if (_hash[0] == 0) { + SHA384(hash,&_pub,sizeof(_pub)); + AES c(hash); + std::sort(hash,hash + 48); + c.encrypt(hash,hash); + c.encrypt(hash + 16,hash + 16); + c.encrypt(hash + 32,hash + 32); + SHA384(hash,hash,48); + } + break; + } + return hash; } void Identity::hashWithPrivate(uint8_t h[48]) const @@ -167,8 +213,7 @@ unsigned int Identity::sign(const void *data,unsigned int len,void *sig,unsigned case P384: if (siglen >= ZT_ECC384_SIGNATURE_SIZE) { - // When signing with P384 we also hash the C25519 public key as an - // extra measure to ensure that only this identity can verify. + // When signing with P-384 we also include the C25519 public key in the hash. uint8_t h[48]; SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); ECC384ECDSASign(_priv.p384,h,(uint8_t *)sig); @@ -265,12 +310,12 @@ char *Identity::toString(bool includePrivate,char buf[ZT_IDENTITY_STRING_BUFFER_ *(p++) = ':'; *(p++) = '1'; *(p++) = ':'; - int el = Utils::b32e((const uint8_t *)(&_pub),sizeof(_pub),p,(unsigned int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf))); + int el = Utils::b32e((const uint8_t *)(&_pub),sizeof(_pub),p,(int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf))); if (el <= 0) return nullptr; p += el; if ((_hasPrivate)&&(includePrivate)) { *(p++) = ':'; - el = Utils::b32e((const uint8_t *)(&_priv),sizeof(_priv),p,(unsigned int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf))); + el = Utils::b32e((const uint8_t *)(&_priv),sizeof(_priv),p,(int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf))); if (el <= 0) return nullptr; p += el; } @@ -383,7 +428,6 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX],const bool incl { _address.copyTo(data); switch(_type) { - case C25519: data[ZT_ADDRESS_LENGTH] = (uint8_t)C25519; memcpy(data + ZT_ADDRESS_LENGTH + 1,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); @@ -393,21 +437,18 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX],const bool incl return (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1 + ZT_C25519_PRIVATE_KEY_LEN); } data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN] = 0; - return (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1); + return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1; case P384: data[ZT_ADDRESS_LENGTH] = (uint8_t)P384; - memcpy(data + ZT_ADDRESS_LENGTH + 1,&_pub,ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE); + memcpy(data + 1 + ZT_ADDRESS_LENGTH,&_pub,ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE); if ((includePrivate)&&(_hasPrivate)) { - data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE; + data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE; memcpy(data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1,&_priv,ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE); - data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE] = 0; - return (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE + 1); + return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE; } data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = 0; - data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1] = 0; - return (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 2); - + return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1; } return -1; } @@ -430,30 +471,29 @@ int Identity::unmarshal(const uint8_t *data,const int len) noexcept return -1; _hasPrivate = true; memcpy(_priv.c25519,data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1,ZT_C25519_PRIVATE_KEY_LEN); - return (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1 + ZT_C25519_PRIVATE_KEY_LEN); + return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1 + ZT_C25519_PRIVATE_KEY_LEN; } else if (privlen == 0) { _hasPrivate = false; - return (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1); + return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_PUBLIC_KEY_LEN + 1; } break; case P384: - if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 2)) + if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1)) return -1; memcpy(&_pub,data + ZT_ADDRESS_LENGTH + 1,ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE); privlen = data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE]; if (privlen == ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE) { - if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE + 1)) + if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE)) return -1; _hasPrivate = true; memcpy(&_priv,data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1,ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE); - privlen = data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE]; - if (len < (int)(privlen + (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE + 1))) + if (!this->locallyValidate()) // for P384 we do this always return -1; - return (int)(privlen + (unsigned int)(ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE + 1)); + return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE; } else if (privlen == 0) { _hasPrivate = false; - return (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 2); + return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1; } break; @@ -461,15 +501,6 @@ int Identity::unmarshal(const uint8_t *data,const int len) noexcept return -1; } -void Identity::_computeHash() -{ - switch(_type) { - case C25519: SHA384(_hash,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); break; - case P384: SHA384(_hash,&_pub,sizeof(_pub)); break; - default: memset(_hash,0,48); - } -} - } // namespace ZeroTier extern "C" { diff --git a/node/Identity.hpp b/node/Identity.hpp index 458f96da0..6f0d0bf64 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -26,10 +26,8 @@ #include "TriviallyCopyable.hpp" #define ZT_IDENTITY_STRING_BUFFER_LENGTH 1024 - -#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE) +#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE) #define ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE) - #define ZT_IDENTITY_MARSHAL_SIZE_MAX (ZT_ADDRESS_LENGTH + 4 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE) namespace ZeroTier { @@ -37,12 +35,20 @@ namespace ZeroTier { /** * A ZeroTier identity * - * An identity consists of a public key, a 40-bit ZeroTier address computed - * from that key in a collision-resistant fashion, and a self-signature. + * Identities currently come in two types: type 0 identities based on just Curve25519 + * and Ed25519 and type 1 identities that include both a 25519 key pair and a NIST P-384 + * key pair. Type 1 identities use P-384 for signatures but use both key pairs at once + * (hashing their results) for key agreement with other type 1 identities, and can agree + * with type 0 identities using only their Curve25519 keys. The ability of type 0 and 1 + * identities to agree will allow type 0 identities to keep being used even after type + * 1 becomes the default. * - * The address derivation algorithm makes it computationally very expensive to - * search for a different public key that duplicates an existing address. (See - * code for deriveAddress() for this algorithm.) + * Type 1 identities also use a simpler mechanism to rate limit identity generation (as + * a defense in depth against intentional collision) that makes local identity validation + * faster, allowing full identity validation on all unmarshal() operations. + * + * The default is still type 0, but this may change in future versions once 1.x is no + * longer common in the wild. */ class Identity : public TriviallyCopyable { @@ -90,8 +96,9 @@ public: * This is a time consuming operation taking up to 5-10 seconds on some slower systems. * * @param t Type of identity to generate + * @return False if there was an error such as type being an invalid value */ - void generate(Type t); + bool generate(Type t); /** * Check the validity of this identity's pairing of key to address @@ -106,14 +113,18 @@ public: ZT_ALWAYS_INLINE bool hasPrivate() const noexcept { return _hasPrivate; } /** - * @return 384-bit/48-byte hash of this identity's public key(s) + * This gets (computing if needed) a hash of this identity's public key(s). + * + * The hash returned by this function differs by identity type. For C25519 (type 0) + * identities this returns a simple SHA384 of the public key, which is NOT the same + * as the hash used to generate the address. For type 1 C25519+P384 identities this + * returns the same compoound SHA384 hash that is used for purposes of hashcash + * and address computation. This difference is because the v0 hash is expensive while + * the v1 hash is fast. + * + * @return 384-bit/48-byte hash (pointer remains valid as long as Identity object exists) */ - ZT_ALWAYS_INLINE const uint8_t *hash() const - { - if (_hash[0] == 0) - const_cast(this)->_computeHash(); - return reinterpret_cast(_hash); - } + const uint8_t *hash() const; /** * Compute a hash of this identity's public and private keys @@ -228,8 +239,6 @@ public: int unmarshal(const uint8_t *data,int len) noexcept; private: - void _computeHash(); // recompute _hash - Address _address; uint64_t _hash[6]; // hash of public key memo-ized for performance, recalculated when _hash[0] == 0 Type _type; // _type determines which fields in _priv and _pub are used @@ -241,8 +250,6 @@ private: ZT_PACKED_STRUCT(struct { // don't re-order these uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN]; // Curve25519 and Ed25519 public keys uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE]; // NIST P-384 public key - uint8_t c25519s[ZT_C25519_SIGNATURE_LEN]; // signature of both keys with ed25519 - uint8_t p384s[ZT_ECC384_SIGNATURE_SIZE]; // signature of both keys with p384 }) _pub; }; diff --git a/node/VL1.cpp b/node/VL1.cpp index aabf07adb..df0e1a7c0 100644 --- a/node/VL1.cpp +++ b/node/VL1.cpp @@ -24,6 +24,7 @@ #include "SHA512.hpp" #include "Peer.hpp" #include "Path.hpp" +#include "Expect.hpp" namespace ZeroTier { @@ -469,6 +470,7 @@ void VL1::_sendPendingWhois(void *const tPtr,const int64_t now) if (outl > sizeof(Protocol::Header)) { Protocol::armor(outp,outl,root->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + RR->expect->sending(ph.packetId,now); rootPath->send(RR,tPtr,outp.b,outl,now); } } @@ -711,6 +713,13 @@ bool VL1::_OK(void *tPtr,const SharedPtr &path,const SharedPtr &peer return false; } Protocol::OK::Header &oh = pkt.as(); + + const int64_t now = RR->node->now(); + if (!RR->expect->expecting(oh.inRePacketId,now)) { + RR->t->incomingPacketDropped(tPtr,0x4c1f1ff7,0,0,identityFromPeerPtr(peer),path->address(),0,Protocol::VERB_OK,ZT_TRACE_PACKET_DROP_REASON_REPLY_NOT_EXPECTED); + return false; + } + switch(oh.inReVerb) { case Protocol::VERB_HELLO: