Browse Source

AES-GMAC-CTR tweaks, self test tweaks, debian typo fix.

Adam Ierymenko 6 years ago
parent
commit
185e90c40f
5 changed files with 57 additions and 24 deletions
  1. 1 1
      debian/control
  2. 43 19
      node/AES.hpp
  3. 9 1
      node/Packet.cpp
  4. 3 2
      root/root.cpp
  5. 1 1
      selftest.cpp

+ 1 - 1
debian/control

@@ -11,7 +11,7 @@ Homepage: https://www.zerotier.com/
 Package: zerotier-one
 Architecture: any
 Depends:  ${shlibs:Depends}, ${misc:Depends}, iproute2, adduser, libstdc++6
-apt-caHomepage: https://www.zerotier.com/
+Homepage: https://www.zerotier.com/
 Description: ZeroTier network virtualization service
  ZeroTier One lets you join ZeroTier virtual networks and
  have them appear as tun/tap ports on your system. See

+ 43 - 19
node/AES.hpp

@@ -150,37 +150,56 @@ public:
 	}
 
 	/**
-	 * Perform AES-256-GMAC-CTR encryption
+	 * Perform AES-GMAC-CTR encryption
 	 *
-	 * This mode combines the two standard modes AES256-GMAC and AES256-CTR to
-	 * yield a mode similar to AES256-GCM-SIV that is resistant to accidental
-	 * message IV duplication. This is good because ZeroTier is stateless and
-	 * uses a small (64-bit) IV to reduce bandwidth overhead.
+	 * This is an AES mode built from GMAC and AES-CTR that is similar to the
+	 * various SIV (synthetic IV) modes for AES and is resistant to nonce
+	 * re-use. It's specifically tweaked for ZeroTier's packet structure with
+	 * a 64-bit IV (extended to 96 bits by including packet size and other info)
+	 * and a 64-bit auth tag.
 	 *
+	 * The use of separate keys for MAC and encrypt is precautionary. It
+	 * ensures that the CTR IV (and CTR output) are always secrets regardless
+	 * of what an attacker might do with accumulated IVs and auth tags.
+	 *
+	 * @param k1 MAC key
+	 * @param k2 Encryption key
 	 * @param iv 96-bit message IV
 	 * @param in Message plaintext
 	 * @param len Length of plaintext
 	 * @param out Output buffer to receive ciphertext
 	 * @param tag Output buffer to receive 64-bit authentication tag
 	 */
-	inline void ztGmacCtrEncrypt(const uint8_t iv[12],const void *in,unsigned int len,void *out,uint8_t tag[8]) const
+	static inline void ztGmacCtrEncrypt(const AES &k1,const AES &k2,const uint8_t iv[12],const void *in,unsigned int len,void *out,uint8_t tag[8])
 	{
 		uint8_t ctrIv[16];
 
-		gmac(iv,in,len,ctrIv);
-		encrypt(ctrIv,ctrIv);
+		// Compute AES[k1](GMAC[k1](iv,plaintext))
+		k1.gmac(iv,in,len,ctrIv);
+		k1.encrypt(ctrIv,ctrIv); // ECB mode encrypt step is because GMAC is not a PRF
 
+		// Auth tag for packet is first 64 bits of AES(GMAC) (rest is discarded)
+#ifdef ZT_NO_TYPE_PUNNING
 		for(unsigned int i=0;i<8;++i) tag[i] = ctrIv[i];
+#else
+		*((uint64_t *)tag) = *((uint64_t *)ctrIv);
+#endif
 
+		// Synthetic CTR IV is AES[k2](AES[k1]( tag[0..4] | tag[4..8]^iv[0..4] | iv[4..12] ))
 		for(unsigned int i=4;i<8;++i) ctrIv[i] ^= iv[i - 4];
 		for(unsigned int i=8;i<16;++i) ctrIv[i] = iv[i - 4];
-		encrypt(ctrIv,ctrIv);
-		ctr(ctrIv,in,len,out);
+		k1.encrypt(ctrIv,ctrIv);
+		k2.encrypt(ctrIv,ctrIv); // ECB mode encrypt here makes CTR IV itself a secret and mixes bits
+
+		// Encrypt with AES[k2]-CTR
+		k2.ctr(ctrIv,in,len,out);
 	}
 
 	/**
-	 * Decrypt a message encrypted with AES-256-GMAC-CTR and check its authenticity
+	 * Decrypt a message encrypted with AES-GMAC-CTR and check its authenticity
 	 *
+	 * @param k1 MAC key
+	 * @param k2 Encryption key
 	 * @param iv 96-bit message IV
 	 * @param in Message ciphertext
 	 * @param len Length of ciphertext
@@ -188,20 +207,25 @@ public:
 	 * @param tag Authentication tag supplied with message
 	 * @return True if authentication tags match and message appears authentic
 	 */
-	inline bool ztGmacCtrDecrypt(const uint8_t iv[12],const void *in,unsigned int len,void *out,const uint8_t tag[8]) const
+	static inline bool ztGmacCtrDecrypt(const AES &k1,const AES &k2,const uint8_t iv[12],const void *in,unsigned int len,void *out,const uint8_t tag[8])
 	{
 		uint8_t ctrIv[16],gmacOut[16];
 
-		for(unsigned int i=0;i<8;++i) ctrIv[i] = tag[i];
-
-		for(unsigned int i=4;i<8;++i) ctrIv[i] ^= iv[i - 4];
+		// Recover synthetic and secret CTR IV from auth tag and packet IV
+		for(unsigned int i=0;i<4;++i) ctrIv[i] = tag[i];
+		for(unsigned int i=4;i<8;++i) ctrIv[i] = tag[i] ^ iv[i - 4];
 		for(unsigned int i=8;i<16;++i) ctrIv[i] = iv[i - 4];
-		encrypt(ctrIv,ctrIv);
-		ctr(ctrIv,in,len,out);
+		k1.encrypt(ctrIv,ctrIv);
+		k2.encrypt(ctrIv,ctrIv);
+
+		// Decrypt with AES[k2]-CTR
+		k2.ctr(ctrIv,in,len,out);
 
-		gmac(iv,out,len,gmacOut);
-		encrypt(gmacOut,gmacOut);
+		// Compute AES[k1](GMAC[k1](iv,plaintext))
+		k1.gmac(iv,out,len,gmacOut);
+		k1.encrypt(gmacOut,gmacOut);
 
+		// Check that packet's auth tag matches first 64 bits of AES(GMAC)
 #ifdef ZT_NO_TYPE_PUNNING
 		return Utils::secureEq(gmacOut,tag,8);
 #else

+ 9 - 1
node/Packet.cpp

@@ -935,11 +935,19 @@ bool Packet::uncompress()
 
 uint64_t Packet::nextPacketId()
 {
+	// The packet ID which is also the packet's nonce/IV can be sequential but
+	// it should never repeat. This scheme minimizes the chance of nonce
+	// repetition if (as will usually be the case) the clock is relatively
+	// accurate.
+
 	static uint64_t ctr = 0;
 	static Mutex lock;
 	lock.lock();
-	while (ctr == 0)
+	while (ctr == 0) {
 		Utils::getSecureRandom(&ctr,sizeof(ctr));
+		ctr <<= 32;
+		ctr |= ((uint64_t)time(nullptr)) & 0x00000000ffffffffULL;
+	}
 	const uint64_t i = ctr++;
 	lock.unlock();
 	return i;

+ 3 - 2
root/root.cpp

@@ -316,8 +316,9 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 			switch(pkt.verb()) {
 				case Packet::VERB_HELLO:
 					try {
-						if ((now - peer->lastHello) > 1000) {
+						if ((now - peer->lastHello) > 500) {
 							peer->lastHello = now;
+
 							peer->vProto = (int)pkt[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION];
 							peer->vMajor = (int)pkt[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION];
 							peer->vMinor = (int)pkt[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION];
@@ -354,7 +355,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 
 				case Packet::VERB_ECHO:
 					try {
-						if ((now - peer->lastEcho) > 1000) {
+						if ((now - peer->lastEcho) > 500) {
 							peer->lastEcho = now;
 
 							Packet outp(source,s_self.address(),Packet::VERB_OK);

+ 1 - 1
selftest.cpp

@@ -224,7 +224,7 @@ static int testCrypto()
 		std::cout << "  AES-256-GMAC-CTR (benchmark): "; std::cout.flush();
 		start = OSUtils::now();
 		for(unsigned long i=0;i<200000;++i) {
-			tv.ztGmacCtrEncrypt((const uint8_t *)hexbuf,buf1,sizeof(buf1),buf2,(uint8_t *)(hexbuf + 8));
+			AES::ztGmacCtrEncrypt(tv,tv,(const uint8_t *)hexbuf,buf1,sizeof(buf1),buf2,(uint8_t *)(hexbuf + 8));
 			hexbuf[0] = buf2[0];
 		}
 		end = OSUtils::now();