diff --git a/node/AES.hpp b/node/AES.hpp index afcc982ae..ffe6a6c8c 100644 --- a/node/AES.hpp +++ b/node/AES.hpp @@ -155,6 +155,77 @@ public: #endif } + /** + * Encrypt with AES256-GCM-DDS + * + * DDS stands for Data Dependent Scramble and refers to our scheme for nonce + * duplication resistance. + * + * @param iv IV (usually random) + * @param in Input plaintext + * @param inlen Length of plaintext + * @param assoc Associated data that won't be encrypted + * @param assoclen Length of associated data + * @param out Output ciphertext buffer (must be at least inlen in size) + * @param combinedTag Buffer to receive 128-bit encrypted combined IV and MAC + */ + inline void gcmDdsEncrypt(const uint64_t iv,const void *in,unsigned int inlen,const void *assoc,unsigned int assoclen,void *out,uint64_t combinedTag[2]) + { + // Make 12-byte GCM IV (use combinedTag as tmp buffer) + combinedTag[0] = iv; + ((uint8_t *)combinedTag)[8] = (uint8_t)(inlen >> 16); + ((uint8_t *)combinedTag)[9] = (uint8_t)(inlen >> 8); + ((uint8_t *)combinedTag)[10] = (uint8_t)inlen; + ((uint8_t *)combinedTag)[11] = (uint8_t)assoclen; + + // Encrypt data and store 64-bit tag/MAC code in second 64 bits of combinedTag. + gcmEncrypt((const uint8_t *)combinedTag,in,inlen,assoc,assoclen,out,((uint8_t *)&(combinedTag[1])),8); + + // Encrypt combinedTag once to get scramble key + encrypt((const uint8_t *)combinedTag,(uint8_t *)combinedTag); + + // Scramble ciphertext + scramble((const uint8_t *)combinedTag,out,inlen,out); + + // Encrypt combinedTag again to get masked tag to include with message + encrypt((const uint8_t *)combinedTag,(uint8_t *)combinedTag); + } + + /** + * Decrypt with AES256-GCM-DDS + * + * @param combinedTag Encrypted combined tag + * @param in Input ciphertext + * @param inlen Length of ciphertext + * @param assoc Associated data that wasn't encrypted + * @param assoclen Length of associated data + * @param out Output plaintext buffer (must be at least inlen in size) + * @return True if GCM authentication check succeeded (if false, discard packet) + */ + inline bool gcmDdsDecrypt(const uint64_t combinedTag[2],const void *in,unsigned int inlen,const void *assoc,unsigned int assoclen,void *out) + { + uint64_t tmp[2],gcmIv[2]; + + // Decrypt combinedTag to get scramble key + decrypt((const uint8_t *)combinedTag,(uint8_t *)tmp); + + // Unscramble ciphertext + unscramble((const uint8_t *)tmp,in,inlen,out); + + // Decrypt combinedTag again to get original IV and AES-GCM MAC + decrypt((const uint8_t *)tmp,(uint8_t *)tmp); + + // Make 12-byte GCM IV + gcmIv[0] = tmp[0]; + ((uint8_t *)gcmIv)[8] = (uint8_t)(inlen >> 16); + ((uint8_t *)gcmIv)[9] = (uint8_t)(inlen >> 8); + ((uint8_t *)gcmIv)[10] = (uint8_t)inlen; + ((uint8_t *)gcmIv)[11] = (uint8_t)assoclen; + + // Perform GCM decryption and authentication + return gcmDecrypt((const uint8_t *)gcmIv,out,inlen,assoc,assoclen,out,(const uint8_t *)&(tmp[1]),8); + } + private: static const uint32_t Te0[256]; static const uint32_t Te1[256]; diff --git a/node/ECC384.hpp b/node/ECC384.hpp index a56cd0eb9..25eebf2c0 100644 --- a/node/ECC384.hpp +++ b/node/ECC384.hpp @@ -63,7 +63,7 @@ namespace ZeroTier { /** * Generate a NIST P-384 key pair - * + * * @param pub Buffer to receive point compressed public key * @param priv Buffer to receiver private key */ @@ -71,10 +71,10 @@ void ECC384GenerateKey(uint8_t pub[ZT_ECC384_PUBLIC_KEY_SIZE],uint8_t priv[ZT_EC /** * Sign a hash with a NIST P-384 private key - * - * The hash must be 48 bytes in size and is typically the first 48 bytes - * of a SHA512 hash or something similar. Extra bytes of course are ignored. - * + * + * The hash must be 48 bytes in size. If it's longer only the first 48 + * bytes are used. + * * @param priv Private key * @param hash 48-byte hash * @param sig Buffer to receive signature @@ -83,7 +83,7 @@ void ECC384ECDSASign(const uint8_t priv[ZT_ECC384_PRIVATE_KEY_SIZE],const uint8_ /** * Verify a signature - * + * * @param pub Public key * @param hash 48-byte hash (usually first 48 bytes of SHA512(msg)) * @param sig Signature to check @@ -93,10 +93,10 @@ bool ECC384ECDSAVerify(const uint8_t pub[ZT_ECC384_PUBLIC_KEY_SIZE],const uint8_ /** * Perform ECDH key agreement - * + * * The secret generated here is the raw 48-byte result of ECDH. * It's typically hashed prior to use. - * + * * @param theirPub Remote public key * @param ourPriv Local private key * @param secret Buffer to receive 48-byte secret diff --git a/node/Identity.hpp b/node/Identity.hpp index 028e818f8..dc767da96 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -218,31 +218,36 @@ public: uint8_t rawkey[128]; uint8_t h[64]; if (_hasPrivate) { - switch(_type) { - - case C25519: + 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. C25519::agree(_priv.c25519,id._pub.c25519,rawkey); SHA512(h,rawkey,ZT_C25519_SHARED_KEY_LEN); - memcpy(key,h,32); + memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH); return true; - - case 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); - for(unsigned int i=0;i<32;++i) - key[i] = h[i]; - for(unsigned int i=0;i<16;++i) - key[i] ^= h[32+i]; - return true; - } - return false; - + } + } 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); + for(unsigned int i=0;i<32;++i) + key[i] = h[i]; + for(unsigned int i=0;i<16;++i) + key[i] ^= h[32+i]; + return true; + } else if (id._type == C25519) { + // If the other identity is a C25519 identity we can agree using only that type. + C25519::agree(_priv.c25519,id._pub.c25519,rawkey); + SHA512(h,rawkey,ZT_C25519_SHARED_KEY_LEN); + memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH); + return true; + } } } return false; diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 152e9579b..7b770d3ea 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -41,7 +41,6 @@ #include "NetworkController.hpp" #include "SelfAwareness.hpp" #include "Salsa20.hpp" -#include "SHA512.hpp" #include "Node.hpp" #include "CertificateOfMembership.hpp" #include "Capability.hpp" @@ -377,38 +376,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); - - if (protoVersion >= 5) { - _path->address().serialize(outp); - } else { - /* LEGACY COMPATIBILITY HACK: - * - * For a while now (since 1.0.3), ZeroTier has recognized changes in - * its network environment empirically by examining its external network - * address as reported by trusted peers. In versions prior to 1.1.0 - * (protocol version < 5), they did this by saving a snapshot of this - * information (in SelfAwareness.hpp) keyed by reporting device ID and - * address type. - * - * This causes problems when clustering is combined with symmetric NAT. - * Symmetric NAT remaps ports, so different endpoints in a cluster will - * report back different exterior addresses. Since the old code keys - * this by device ID and not sending physical address and compares the - * entire address including port, it constantly thinks its external - * surface is changing and resets connections when talking to a cluster. - * - * In new code we key by sending physical address and device and we also - * take the more conservative position of only interpreting changes in - * IP address (neglecting port) as a change in network topology that - * necessitates a reset. But we can make older clients work here by - * nulling out the port field. Since this info is only used for empirical - * detection of link changes, it doesn't break anything else. - */ - InetAddress tmpa(_path->address()); - tmpa.setPort(0); - tmpa.serialize(outp); - } - + _path->address().serialize(outp); outp.armor(peer->key(),true); _path->send(RR,tPtr,outp.data(),outp.size(),now); diff --git a/node/Packet.hpp b/node/Packet.hpp index 51be67ac5..177a65caa 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -455,7 +455,7 @@ public: * <[8] timestamp for determining latency> * <[...] binary serialized identity (see Identity)> * <[...] physical destination address of packet> - * + * * HELLO is sent in the clear as it is how peers share their identity * public keys. * @@ -724,7 +724,7 @@ public: * * Flags: * 0x01 - COM is attached (DEPRECATED) - * + * * More than one OK response can occur if the response is broken up across * multiple packets or if querying a clustered node. * @@ -759,7 +759,7 @@ public: * 0x02 - Implicit gather limit field is present (DEPRECATED) * 0x04 - Source MAC is specified -- otherwise it's computed from sender * 0x08 - Explicit recipient list included for P2P/HS replication - * + * * Explicit recipient lists are used for peer to peer or hub and spoke * replication. *