diff --git a/node/Identity.hpp b/node/Identity.hpp index 905218aa5..aeb8c0d96 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -52,30 +52,35 @@ public: }; ZT_ALWAYS_INLINE Identity() { memset(reinterpret_cast(this),0,sizeof(Identity)); } - ZT_ALWAYS_INLINE Identity(const char *str) - { - if (!fromString(str)) - throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE; - } + ZT_ALWAYS_INLINE ~Identity() { Utils::burn(reinterpret_cast(&this->_priv),sizeof(this->_priv)); } + + /** + * Construct identity from string + * + * If the identity is not basically valid (no deep checking is done) the result will + * be a null identity. + * + * @param str Identity in canonical string format + */ + ZT_ALWAYS_INLINE Identity(const char *str) { fromString(str); } + template ZT_ALWAYS_INLINE Identity(const Buffer &b,unsigned int startAt = 0) { deserialize(b,startAt); } - ZT_ALWAYS_INLINE ~Identity() { Utils::burn(reinterpret_cast(this),sizeof(Identity)); } - /** * Set identity to NIL value (all zero) */ ZT_ALWAYS_INLINE void zero() { memset(reinterpret_cast(this),0,sizeof(Identity)); } /** - * @return Identity type + * @return Identity type (undefined if identity is null or invalid) */ ZT_ALWAYS_INLINE Type type() const { return _type; } /** * Generate a new identity (address, key pair) * - * This is a time consuming operation. + * This is a time consuming operation taking up to 5-10 seconds on some slower systems. * * @param t Type of identity to generate */ @@ -94,10 +99,12 @@ public: ZT_ALWAYS_INLINE bool hasPrivate() const { return _hasPrivate; } /** + * This generates a SHA384 hash of this identity's keys. + * * @param h Buffer to receive SHA384 of public key(s) - * @param includePrivate If true, hash private key(s) too + * @param includePrivate If true, hash private key(s) as well */ - ZT_ALWAYS_INLINE bool hash(uint8_t h[48],const bool includePrivate) const + ZT_ALWAYS_INLINE bool hash(uint8_t h[48],const bool includePrivate = false) const { switch(_type) { @@ -142,8 +149,10 @@ public: 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. uint8_t h[48]; - SHA384(h,data,len); + SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); ECC384ECDSASign(_priv.p384,h,(uint8_t *)sig); return ZT_ECC384_SIGNATURE_SIZE; } @@ -165,15 +174,18 @@ public: ZT_ALWAYS_INLINE bool verify(const void *data,unsigned int len,const void *sig,unsigned int siglen) const { switch(_type) { + case C25519: return C25519::verify(_pub.c25519,data,len,sig,siglen); + case P384: if (siglen == ZT_ECC384_SIGNATURE_SIZE) { uint8_t h[48]; - SHA384(h,data,len); + SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); return ECC384ECDSAVerify(_pub.p384,h,(const uint8_t *)sig); } break; + } return false; } @@ -193,6 +205,7 @@ public: uint8_t h[64]; if (_hasPrivate) { if (_type == C25519) { + if ((id._type == C25519)||(id._type == P384)) { // If we are a C25519 key we can agree with another C25519 key or with only the // C25519 portion of a type 1 P-384 key. @@ -201,12 +214,10 @@ public: memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH); return true; } + } else if (_type == P384) { + if (id._type == P384) { - // Perform key agreement over both curves for the same reason that C25519 public - // keys are included in P-384 signature inputs: to bind the keys together so - // that a type 1 identity with the same C25519 public key (and therefore address) - // but a different P-384 key will not work. C25519::agree(_priv.c25519,id._pub.c25519,rawkey); ECC384ECDH(id._pub.p384,_priv.p384,rawkey + ZT_C25519_SHARED_KEY_LEN); SHA384(h,rawkey,ZT_C25519_SHARED_KEY_LEN + ZT_ECC384_SHARED_SECRET_SIZE); @@ -219,6 +230,7 @@ public: memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH); return true; } + } } return false; @@ -229,28 +241,6 @@ public: */ ZT_ALWAYS_INLINE const Address &address() const { return _address; } - /** - * Attempt to generate an older type identity from a newer type - * - * @param dest Destination to fill with downgraded identity - * @param toType Desired identity type - */ - ZT_ALWAYS_INLINE bool downgrade(Identity &dest,const Type toType) - { - if (_type == toType) { - return true; - } else if ((_type == P384)&&(toType == C25519)) { - dest._address = _address; - dest._type = C25519; - dest._hasPrivate = _hasPrivate; - memcpy(dest._pub.c25519,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN); - if (_hasPrivate) - memcpy(dest._priv.c25519,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN); - return true; - } - return false; - } - /** * Serialize this identity (binary) * @@ -284,7 +274,7 @@ public: } else { b.append((uint8_t)0); } - b.append((uint16_t)0); // size of additional fields + b.append((uint16_t)0); // size of additional fields (should have included such a thing in v0!) break; } @@ -358,7 +348,7 @@ public: * * @param includePrivate If true, include private key (if it exists) * @param buf Buffer to store string - * @return ASCII string representation of identity + * @return ASCII string representation of identity (pointer to buf) */ char *toString(bool includePrivate,char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const; @@ -382,12 +372,9 @@ public: { if ((_address == id._address)&&(_type == id._type)) { switch(_type) { - case C25519: - return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) == 0); - case P384: - return (memcmp(&_pub,&id._pub,sizeof(_pub)) == 0); - default: - return false; + case C25519: return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) == 0); + // case P384: + default: return (memcmp(&_pub,&id._pub,sizeof(_pub)) == 0); } } return false; @@ -401,10 +388,9 @@ public: return true; if (_type == id._type) { switch(_type) { - case C25519: - return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) < 0); - case P384: - return (memcmp(&_pub,&id._pub,sizeof(_pub)) < 0); + case C25519: return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) < 0); + // case P384: + default: return (memcmp(&_pub,&id._pub,sizeof(_pub)) < 0); } } } diff --git a/node/Packet.hpp b/node/Packet.hpp index b721dcb16..40bee5bda 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -424,10 +424,12 @@ public: * <[8] timestamp for determining latency> * <[...] binary serialized identity (see Identity)> * <[...] physical destination address of packet> - * [... begin encrypted section ...] + * [... begin encrypted region ...] * <[2] 16-bit reserved field, always 0> * <[2] 16-bit length of locator> * <[...] locator for this node> + * <[2] 16-bit length of meta-data dictionary> + * <[...] meta-data dictionary> * * HELLO is sent in the clear as it is how peers share their identity * public keys. @@ -441,6 +443,9 @@ public: * very sensitive, but hiding the locator and other meta-data slightly * improves privacy. * + * The 16-bit zero after encryption starts is for backward compatibility + * with pre-2.0 nodes. + * * OK payload: * <[8] HELLO timestamp field echo> * <[1] protocol version> @@ -578,6 +583,20 @@ public: */ VERB_ECHO = 0x08, + /** + * Announce interest in multicast group(s): + * <[8] 64-bit network ID> + * <[6] multicast Ethernet address> + * <[4] multicast additional distinguishing information (ADI)> + * [... additional tuples of network/address/adi ...] + * + * LIKEs may be sent to any peer, though a good implementation should + * restrict them to peers on the same network they're for and to network + * controllers and root servers. In the current network, root servers + * will provide the service of final multicast cache. + */ + VERB_MULTICAST_LIKE = 0x09, + /** * Network credentials push: * [<[...] one or more certificates of membership>] @@ -682,6 +701,73 @@ public: */ VERB_NETWORK_CONFIG = 0x0c, + /** + * Request endpoints for multicast distribution: + * <[8] 64-bit network ID> + * <[1] flags> + * <[6] MAC address of multicast group being queried> + * <[4] 32-bit ADI for multicast group being queried> + * <[4] 32-bit requested max number of multicast peers> + * + * This message asks a peer for additional known endpoints that have + * LIKEd a given multicast group. It's sent when the sender wishes + * to send multicast but does not have the desired number of recipient + * peers. + * + * OK response payload: (multiple OKs can be generated) + * <[8] 64-bit network ID> + * <[6] MAC address of multicast group being queried> + * <[4] 32-bit ADI for multicast group being queried> + * <[4] 32-bit total number of known members in this multicast group> + * <[2] 16-bit number of members enumerated in this packet> + * <[...] series of 5-byte ZeroTier addresses of enumerated members> + * + * ERROR is not generated; queries that return no response are dropped. + */ + VERB_MULTICAST_GATHER = 0x0d, + + /** *** DEPRECATED *** + * Multicast frame: + * <[8] 64-bit network ID> + * <[1] flags> + * [<[4] 32-bit implicit gather limit>] + * [<[6] source MAC>] + * <[6] destination MAC (multicast address)> + * <[4] 32-bit multicast ADI (multicast address extension)> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * Flags: + * 0x01 - Network certificate of membership attached (DEPRECATED) + * 0x02 - Implicit gather limit field is present + * 0x04 - Source MAC is specified -- otherwise it's computed from sender + * 0x08 - Please replicate (sent to multicast replicators) + * + * OK and ERROR responses are optional. OK may be generated if there are + * implicit gather results or if the recipient wants to send its own + * updated certificate of network membership to the sender. ERROR may be + * generated if a certificate is needed or if multicasts to this group + * are no longer wanted (multicast unsubscribe). + * + * OK response payload: + * <[8] 64-bit network ID> + * <[6] MAC address of multicast group> + * <[4] 32-bit ADI for multicast group> + * <[1] flags> + * [<[...] network certificate of membership (DEPRECATED)>] + * [<[...] implicit gather results if flag 0x01 is set>] + * + * OK flags (same bits as request flags): + * 0x01 - OK includes certificate of network membership (DEPRECATED) + * 0x02 - OK includes implicit gather results + * + * ERROR response payload: + * <[8] 64-bit network ID> + * <[6] multicast group MAC> + * <[4] 32-bit multicast group ADI> + */ + VERB_MULTICAST_FRAME = 0x0e, + /** * Push of potential endpoints for direct communication: * <[2] 16-bit number of paths> @@ -784,77 +870,67 @@ public: VERB_REMOTE_TRACE = 0x15, /** - * Multipurpose VL2 network multicast: - * <[5] start of range of addresses for propagation> - * <[5] end of range of addresses for propagation> - * <[1] 8-bit propagation depth / hops or 0xff to not propagate> - * <[1] 8-bit length of bloom filter in 256-byte/2048-bit chunks> - * <[...] propagation bloom filter> - * [... start of signed portion ...] - * <[8] 64-bit timestamp> + * Peer-to-peer propagated multicast packet: + * <[128] 1024-bit bloom filter> + * <[2] 16-bit perturbation coefficient to minimize bloom collisions> + * <[5] 40-bit start of range of recipient addresses> + * <[5] 40-bit end of range of recipient addresses> + * [... begin signed portion ...] + * <[1] 8-bit flags> + * <[5] 40-bit ZeroTier address of sender> * <[8] 64-bit network ID> - * <[5] 40-bit address of sender> - * <[2] 16-bit length of multicast payload> - * [... start multicast payload ...] - * <[1] 8-bit payload type> - * [... end multicast payload and signed portion ...] - * <[2] 16-bit length of signature or 0 if not present> - * <[...] signature of signed portion> - * - * Payload type 0x00: multicast frame: * <[6] MAC address of multicast group> - * <[4] 32-bit ADI of multicast group> - * <[6] 48-bit source MAC of packet or all 0 if from sender> + * <[4] 32-bit ADI for multicast group> + * <[6] MAC address of sender> * <[2] 16-bit ethertype> + * <[2] 16-bit length of ethernet payload> * <[...] ethernet payload> + * [... end signed portion ...] + * <[2] 16-bit length of signature or 0 if unsigned> + * [<[...] optional signature of multicast>] * - * Payload type 0x01: multicast subscribe: - * <[2] 16-bit number of multicast group IDs to subscribe> - * <[...] series of 32-bit multicast group IDs> - * - * Payload type 0x02: multicast unsubscribe: - * <[2] 16-bit number of multicast group IDs to unsubscribe> - * <[...] series of 32-bit multicast group IDs> - * - * This is the common packet structure for VL2 network-level multicasts - * and is used for multicast frames, multicast group subscribe and - * unsubscribe, and could be used in the future for other purposes such - * as credential propagation or diagnostics. - * - * The header contains an address range, bloom filter, and depth/hop - * counter. The bloom filter tracks which nodes have seen this multicast, - * with bits being set prior to send. The range allows the total set of - * subscribers to be partitioned in the case of huge networks that would - * saturate the bloom filter or have collisions. The propagation depth - * allows propagation to stop at some maximum value, and the value 0xff - * can be used to indicate that further propagation is not desired. - * - * Logic connected to the parsing of the multicast payload will determine - * whether or not and to whom this multicast is propagated. Subscribe and - * unsubscribe messages are propagated to online nodes up to a maximum - * depth, while frames have the added constraint of being propagated only - * to nodes that subscribe to the target multicast group. + * This packet contains a multicast that is to be peer-to-peer replicated. + * The range of recipient addresses is a subset of the global list of + * subscribers to this multicast group. As the packet is propagated bits + * in the bloom filter will be set. The sender may attempt to select a + * perturbation coefficient to prevent collisions within the selected + * recipient range. */ - VERB_VL2_MULTICAST = 0x16, + VERB_MULTICAST = 0x16, /** * Negotiate a new ephemeral key: - * <[8] first 64 bits of SHA-384 of currently known key for destination> - * <[...] ephemeral key for sender> + * <[48] SHA384 of ephemeral key we currently have for recipient> + * [<[...] sender's ephemeral key>] * - * If the 64-bit hash of the currently known key sent by the sender does - * not match the key the destination is currently using, the destination - * will send its own REKEY after sending OK to ensure that keys are up to - * date on both sides. This causes either side sending REKEY to trigger - * an automatic two-way handshake. Either side may therefore rekey at - * any time, though a rate limit should be in effect to prevent flooding. + * REKEY is used to negotiate ephemeral keys. The first byte is a step + * number from 0 to 2. Here's a new session initiated by Alice: * - * OK payload: - * <[8] first 64 bits of SHA-384 of received ephemeral key> + * Alice: REKEY[0x000...,AliceKey] -> Bob + * Bob: REKEY[SHA384(AliceKey),BobKey] -> Alice + * Alice: REKEY[SHA384(BobKey),(omitted)] -> Bob + * + * REKEY messages will continue until both sides have acknowledged each + * others' keys. Either Alice or Bob can send REKEY to negotiate a new + * ephemeral key pair at any time. + * + * OK isn't used because this is an ongoing handshake until both sides + * agree on a key. REKEY triggers a REKEY in reply if the hash for the + * recipient's ephemeral public key doesn't match the ephemeral key it + * wants to use. */ - VERB_REKEY = 0x17 + VERB_REKEY = 0x17, - // TODO: legacy multicast message types must be supported + /** + * Encapsulate a full ZeroTier packet in another: + * <[...] raw encapsulated packet> + * + * Encapsulation exists to enable secure relaying as opposed to the usual + * "dumb" relaying. The latter is faster but secure relaying has roles + * where endpoint privacy is desired. Multiply nested ENCAP packets + * could allow ZeroTier to act as an onion router. + */ + VERB_ENCAP = 0x18 // protocol max: 0x1f };