From bcd079b70eb984a44dc62b2f8aa591c4f9c57f8a Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 10 Jul 2013 22:58:43 -0400 Subject: [PATCH] Adding signatures to multicast frames, work in progress, does not build yet --- node/Constants.hpp | 2 +- node/Multicaster.hpp | 66 ++++++++++++++++++++++++ node/Network.hpp | 13 ----- node/Packet.hpp | 79 ++++++++++++++++++---------- node/Switch.cpp | 119 +++++++++++++++++++++++++++---------------- 5 files changed, 194 insertions(+), 85 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 61044fed8..67d84e3ec 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -240,7 +240,7 @@ error_no_ZT_ARCH_defined; /** * Expiration time in ms for multicast history items */ -#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 8000 +#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 4000 /** * Number of bits to randomly "decay" in bloom filter per hop diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index 190c30339..03a94ca2c 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -31,11 +31,14 @@ #include #include +#include + #include #include #include #include #include +#include #include "Constants.hpp" #include "Buffer.hpp" @@ -46,6 +49,7 @@ #include "Address.hpp" #include "SharedPtr.hpp" #include "BloomFilter.hpp" +#include "Identity.hpp" // Maximum sample size to pick during choice of multicast propagation peers #define ZT_MULTICAST_PICK_MAX_SAMPLE_SIZE 64 @@ -68,10 +72,49 @@ public: typedef BloomFilter MulticastBloomFilter; Multicaster() + throw() { memset(_multicastHistory,0,sizeof(_multicastHistory)); } + /** + * Generate a signature of a multicast packet using an identity + * + * @param id Identity to sign with (must have secret key portion) + * @param from MAC address of sender + * @param to Multicast group + * @param etherType 16-bit ethernet type + * @param data Ethernet frame data + * @param len Length of frame + * @return ECDSA signature + */ + static inline std::string signMulticastPacket(const Identity &id,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len) + { + unsigned char digest[32]; + _hashMulticastPacketForSig(from,to,etherType,data,len,digest); + return id.sign(digest); + } + + /** + * Verify a signature from a multicast packet + * + * @param id Identity of original signer + * @param from MAC address of sender + * @param to Multicast group + * @param etherType 16-bit ethernet type + * @param data Ethernet frame data + * @param len Length of frame + * @param signature ECDSA signature + * @param siglen Length of signature in bytes + * @return ECDSA signature + */ + static bool verifyMulticastPacket(const Identity &id,const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len,const void *signature,unsigned int siglen) + { + unsigned char digest[32]; + _hashMulticastPacketForSig(from,to,etherType,data,len,digest); + return id.verify(digest,signature,siglen); + } + /** * Update the most recent LIKE time for an address in a given multicast group on a given network * @@ -257,6 +300,29 @@ private: } }; + static inline void _hashMulticastPacketForSig(const MAC &from,const MulticastGroup &to,unsigned int etherType,const void *data,unsigned int len,unsigned char *digest) + throw() + { + unsigned char zero = 0; + SHA256_CTX sha; + SHA256_Init(&sha); + uint64_t _nwid = Utils::hton(network->id()); + SHA256_Update(&sha,(unsigned char *)&_nwid,sizeof(_nwid)); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,(unsigned char *)from.data,6); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,(unsigned char *)mg.mac().data,6); + SHA256_Update(&sha,&zero,1); + uint32_t _adi = Utils::hton(mg.adi()); + SHA256_Update(&sha,(unsigned char *)&_adi,sizeof(_adi)); + SHA256_Update(&sha,&zero,1); + uint16_t _etype = Utils::hton((uint16_t)etherType); + SHA256_Update(&sha,(unsigned char *)&_etype,sizeof(_etype)); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,(unsigned char *)data,len); + SHA256_Final(digest,&sha); + } + // [0] - CRC, [1] - timestamp uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2]; diff --git a/node/Network.hpp b/node/Network.hpp index ddb8930f6..417688f06 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -112,19 +112,6 @@ public: return ((_open)||(_members.count(addr) > 0)); } - /** - * Shortcut to check open(), whether MAC is ZeroTier, then isMember() - * - * @param mac MAC address to check - * @return True if MAC is allowed - */ - inline bool isAllowed(const MAC &mac) const - throw() - { - Mutex::Lock _l(_lock); - return ((_open)||((mac.isZeroTier())&&(_members.count(Address(mac)) > 0))); - } - /** * @return True if network is open (no membership required) */ diff --git a/node/Packet.hpp b/node/Packet.hpp index 05b4ef419..7eae98808 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -45,8 +45,13 @@ /** * Protocol version + * + * 1 - 0.2.0 ... 0.2.5 + * 2 - 0.3.0 ... + * * Added signature and originating peer to multicast frame + * * Double size of multicast frame bloom filter */ -#define ZT_PROTO_VERSION 1 +#define ZT_PROTO_VERSION 2 /** * Maximum hop count allowed by packet structure (3 bits, 0-7) @@ -123,8 +128,8 @@ #define ZT_PROTO_MIN_FRAGMENT_LENGTH ZT_PACKET_FRAGMENT_IDX_PAYLOAD // Size of bloom filter used in multicast propagation -#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE 32 -#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS 256 +#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BITS 512 +#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BYTES 64 // Field incides for parsing verbs #define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD) @@ -148,15 +153,18 @@ #define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) #define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8) #define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC + 6) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI + 4) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM + ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS + 1) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR + 2) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC + 6) -#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS + 5) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM_FILTER (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI + 4) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM_FILTER + 64) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH + 2) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH + 2) // Field indices for parsing OK and ERROR payloads of replies #define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD) @@ -415,20 +423,8 @@ public: */ VERB_FRAME = 6, - /* A multicast frame: - * <[8] 64-bit network ID> - * <[6] destination multicast Ethernet address> - * <[4] multicast additional distinguishing information (ADI)> - * <[32] multicast propagation bloom filter> - * <[1] 8-bit strict propagation hop count> - * <[2] reserved, must be 0> - * <[6] source Ethernet address> - * <[2] 16-bit ethertype> - * <[...] ethernet payload> - * - * No OK or ERROR is generated. - */ - VERB_MULTICAST_FRAME = 7, + /* 7 - old VERB_MULTICAST_FRAME, might be reused once all old 0.2 + * clients are off the net. */ /* Announce interest in multicast group(s): * <[8] 64-bit network ID> @@ -438,7 +434,36 @@ public: * * OK is generated on successful receipt. */ - VERB_MULTICAST_LIKE = 8 + VERB_MULTICAST_LIKE = 8, + + /* A multicast frame: + * <[1] flags, currently unused and must be 0> + * <[8] 64-bit network ID> + * <[5] ZeroTier address of original submitter of this multicast> + * <[6] source MAC address> + * <[6] destination multicast Ethernet address> + * <[4] multicast additional distinguishing information (ADI)> + * <[64] multicast propagation bloom filter> + * <[1] 8-bit propagation hop count> + * <[2] 16-bit ethertype> + * <[2] 16-bit length of payload> + * <[2] 16-bit length of signature> + * <[...] ethernet payload> + * <[...] ECDSA signature> + * + * The signature is made using the key of the original submitter, and + * can be used to authenticate the submitter for security and rate + * control purposes. Fields in the signature are: network ID, source + * MAC, destination MAC, multicast ADI, ethertype, and payload. All + * integers are hashed in big-endian byte order. A zero byte is added + * to the hash between each field. + * + * In the future flags could indicate additional fields appended to the + * end or a different signature algorithm. + * + * No OK or ERROR is generated. + */ + VERB_MULTICAST_FRAME = 9 }; /** diff --git a/node/Switch.cpp b/node/Switch.cpp index b1a557560..b839df054 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -219,8 +219,6 @@ Switch_onRemotePacket_complete_packet_handler: qe->second.localPort = localPort; qe->second.fromAddr = fromAddr; } - if (r == PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN) - _requestWhois(source); } } } catch (std::exception &ex) { @@ -569,17 +567,30 @@ void Switch::_propagateMulticast(const SharedPtr &network,const Address SharedPtr propPeers[ZT_MULTICAST_PROPAGATION_BREADTH]; unsigned int np = _multicaster.pickNextPropagationPeers(*(_r->topology),network->id(),mg,upstream,newBloom,ZT_MULTICAST_PROPAGATION_BREADTH,propPeers,Utils::now()); + if (!np) + return; + + std::string signature(Multicaster::signMulticastPacket(_r->identity,from,mg,etherType,data,len)); + if (!signature.length()) { + TRACE("failure signing multicast message!"); + return; + } + for(unsigned int i=0;iaddress(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME); + outp.append((uint8_t)0); outp.append((uint64_t)network->id()); + outp.append(_r->identity.address().data(),ZT_ADDRESS_LENGTH); + outp.append(from.data,6); outp.append(mg.mac().data,6); outp.append((uint32_t)mg.adi()); outp.append(newBloom.data(),ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE); outp.append((uint8_t)mcHops); - outp.append((unsigned char)0,2); // reserved, 0 - outp.append(from.data,6); outp.append((uint16_t)etherType); + outp.append((uint16_t)len); + outp.append((uint16_t)signature.length()); outp.append(data,len); + outp.append(signature.data(),signature.length()); outp.compress(); send(outp,true); } @@ -747,45 +758,6 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l TRACE("dropped FRAME from %s: unexpected exception: (unknown)",source.toString().c_str()); } break; - case Packet::VERB_MULTICAST_FRAME: - try { - SharedPtr network(_r->nc->network(packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID))); - if (network) { - if (network->isAllowed(source)) { - if (packet.size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) { - MulticastGroup mg(MAC(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC,6)),packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI)); - unsigned int hops = packet[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS]; - MAC fromMac(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC,6)); - unsigned int etherType = packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE); - unsigned int payloadLen = packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD; - unsigned char *payload = packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,payloadLen); - - if (fromMac == network->tap().mac()) { - TRACE("dropped boomerang MULTICAST_FRAME from %s",source.toString().c_str()); - } if (network->isAllowed(fromMac)) { - if (_multicaster.checkAndUpdateMulticastHistory(fromMac,mg,payload,payloadLen,network->id(),now)) { - // TODO: check if allowed etherType - network->tap().put(fromMac,mg.mac(),etherType,payload,payloadLen); - } else { - TRACE("duplicate MULTICAST_FRAME from %s: %s -> %s (adi: %.8lx), %u bytes, net: %llu",source.toString().c_str(),fromMac.toString().c_str(),mg.mac().toString().c_str(),(unsigned long)mg.adi(),packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,network->id()); - } - _propagateMulticast(network,source,packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE),mg,hops+1,fromMac,etherType,payload,payloadLen); - } else { - TRACE("dropped MULTICAST_FRAME from %s: ultimate sender %s not a member of closed network %llu",source.toString().c_str(),fromMac.toString().c_str(),network->id()); - } - } - } else { - TRACE("dropped MULTICAST_FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id()); - } - } else { - TRACE("dropped MULTICAST_FRAME from %s: network %llu unknown",source.toString().c_str(),packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID)); - } - } catch (std::exception &ex) { - TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); - } catch ( ... ) { - TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: (unknown)",source.toString().c_str()); - } - break; case Packet::VERB_MULTICAST_LIKE: try { unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; @@ -821,6 +793,62 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l TRACE("dropped MULTICAST_LIKE from %s: unexpected exception: (unknown)",source.toString().c_str()); } break; + case Packet::VERB_MULTICAST_FRAME: + try { + SharedPtr network(_r->nc->network(packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID))); + if (network) { + if (network->isAllowed(source)) { + if (packet.size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) { + Address originalSubmitterAddress(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH)); + if (originalSubmitterAddress == _r->identity.address()) { + TRACE("dropped boomerang MULTICAST_FRAME received from %s",source.toString().c_str()); + } else { + SharedPtr originalSubmitter(_r->topology->getPeer(originalSubmitterAddress)); + if (!originalSubmitter) { + // If we don't know the original submitter, try to look them up + // and abort. + // TODO: need to rearchitect how we wait for and handle whois + // responses so they trigger a re-eval of this packet instantly. + _requestWhois(originalSubmitterAddress); + return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN; + } else { + MAC fromMac(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC,6)); + MulticastGroup mg(MAC(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC,6)),packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI)); + unsigned int hops = packet[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT]; + unsigned int etherType = packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE); + unsigned int datalen = packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH); + unsigned int signaturelen = packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH); + unsigned char *dataAndSignature = packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,datalen + signaturelen); + + if (Multicaster::verifyMulticastPacket(originalSubmitter->identity(),fromMac,mg,etherType,data,datalen,dataAndSignature + datalen,signaturelen)) { + if (network->isAllowed(originalSubmitterAddress)) { + if (_multicaster.checkAndUpdateMulticastHistory(fromMac,mg,payload,payloadLen,network->id(),now)) { + // TODO: check if allowed etherType + network->tap().put(fromMac,mg.mac(),etherType,payload,payloadLen); + } else { + } + _propagateMulticast(network,source,packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE),mg,hops+1,fromMac,etherType,payload,payloadLen); + } else { + } + } else { + } + } + } + } else { + } + } else { + TRACE("dropped MULTICAST_FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id()); + } + } else { + TRACE("dropped MULTICAST_FRAME from %s: network %llu unknown or we are not a member",source.toString().c_str(),packet.at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID)); + } + } catch (std::exception &ex) { + TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + break; default: TRACE("ignored unrecognized verb %.2x from %s",(unsigned int)packet.verb(),source.toString().c_str()); break; @@ -828,7 +856,10 @@ Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port l // Update peer timestamps and learn new links peer->onReceive(_r,localPort,fromAddr,latency,packet.hops(),packet.verb(),now); - } else return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN; + } else { + _requestWhois(source); + return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN; + } return PACKET_SERVICE_ATTEMPT_OK; }