From e94518590de48e2718539f98b87e2f617fa02f75 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 14 May 2015 17:41:05 -0700 Subject: [PATCH] First stab of PFS design work with PKC security -- may not implement in 1.0.3 but stubbing out. --- node/Packet.cpp | 1 + node/Packet.hpp | 114 +++++++++++++++++++++++++++++++++++------------- node/Utils.cpp | 9 ++++ selftest.cpp | 18 ++++++++ 4 files changed, 112 insertions(+), 30 deletions(-) diff --git a/node/Packet.cpp b/node/Packet.cpp index f72f64b27..a81873ffd 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -50,6 +50,7 @@ const char *Packet::verbString(Verb v) case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH"; case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER"; case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME"; + case VERB_SET_EPHEMERAL_KEY: return "SET_EPHEMERAL_KEY"; } return "(unknown)"; } diff --git a/node/Packet.hpp b/node/Packet.hpp index 76f849963..efe58c787 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -98,6 +98,17 @@ */ #define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 1 +/** + * Cipher suite: PFS negotiated ephemeral cipher suite and authentication + * + * This message is encrypted with the latest negotiated ephemeral (PFS) + * key pair and cipher suite. If authentication fails, VERB_SET_EPHEMERAL_KEY + * may be sent to renegotiate ephemeral keys. To prevent attacks, this + * attempted renegotiation should be limited to some sane rate such as + * once per second. + */ +#define ZT_PROTO_CIPHER_SUITE__EPHEMERAL 7 + /** * DEPRECATED payload encrypted flag, will be removed for re-use soon. * @@ -114,13 +125,6 @@ */ #define ZT_PROTO_FLAG_FRAGMENTED 0x40 -/** - * Flag indicating encryption with a PFS session key - * - * Not used yet -- for future PFS session re-keying support. - */ -#define ZT_PROTO_FLAG_PFS_SESSION 0x20 - /** * Verb flag indicating payload is compressed with LZ4 */ @@ -186,6 +190,17 @@ #define ZT_PROTO_DEST_ADDRESS_TYPE_IPV4 4 #define ZT_PROTO_DEST_ADDRESS_TYPE_IPV6 6 +// Ephemeral key record flags +#define ZT_PROTO_EPHEMERAL_KEY_FLAG_FIPS 0x01 + +// Ephemeral key record symmetric cipher types +#define ZT_PROTO_EPHEMERAL_KEY_SYMMETRIC_CIPHER_SALSA2012_POLY1305 0x01 +#define ZT_PROTO_EPHEMERAL_KEY_SYMMETRIC_CIPHER_AES256_GCM 0x02 + +// Ephemeral key record public key types +#define ZT_PROTO_EPHEMERAL_KEY_PK_C25519 0x01 +#define ZT_PROTO_EPHEMERAL_KEY_PK_NISTP256 0x02 + // Field incides for parsing verbs ------------------------------------------- // Some verbs have variable-length fields. Those aren't fully defined here @@ -298,8 +313,8 @@ namespace ZeroTier { * * Packets smaller than 28 bytes are invalid and silently discarded. * - * The flags/cipher/hops bit field is: FFFCCHHH where C is a 2-bit cipher - * selection allowing up to 4 cipher suites, F is outside-envelope flags, + * The flags/cipher/hops bit field is: FFCCCHHH where C is a 3-bit cipher + * selection allowing up to 7 cipher suites, F is outside-envelope flags, * and H is hop count. * * The three-bit hop count is the only part of a packet that is mutable in @@ -752,7 +767,59 @@ public: * <[6] multicast group MAC> * <[4] 32-bit multicast group ADI> */ - VERB_MULTICAST_FRAME = 14 + VERB_MULTICAST_FRAME = 14, + + /* Ephemeral (PFS) key push: + * <[8] 64-bit PFS key set ID sender holds for recipient (0==none)> + * <[8] 64-bit PFS key set ID of this key set> + * [... begin PFS key record ...] + * <[1] flags> + * <[1] symmetric cipher ID> + * <[1] public key type ID> + * <[2] public key length in bytes> + * <[2] identity signature length in bytes (0 for none)> + * <[...] public key> + * <[...] signature of sender's ZT identity with public key> + * [... additional records may follow up to max packet length ...] + * + * This message is sent to negotiate an ephemeral key. If the recipient's + * current key pair for the sender does not match the one the sender + * claims to have on file, it must respond with its own SET_EPHEMERAL_KEY. + * + * PFS key IDs are random and must not be zero, since zero indicates that + * the sender does not have an ephemeral key on file for the recipient. + * + * For each public key, the sender may sign its ZeroTier identity (public + * portion only) using the associated digital signature algorithm. This + * permits the extension of FIPS-compliant cryptographic algorithms to + * cover verification of the identity for full FIPS compliant mode. For + * non-FIPS mode, this is optional. If no signature is included the + * signature length field must be zero. + * + * One or more records may be sent. If multiple records are present, + * the first record with common symmetric cipher, public key type, + * and relevant flags must be used. + * + * Flags (all unspecified flags must be zero): + * 0x01 - FIPS mode, only use record if FIPS compliant crypto in use + * + * Symmetric cipher IDs: + * 0x01 - Salsa20/12 with Poly1305 authentication (ZT default) + * 0x02 - AES256-GCM combined crypto and authentication + * + * Public key types: + * 0x01 - Curve25519 ECDH with SHA-512 KDF, Ed25519 signatures + * 0x02 - NIST P-256 ECDH with SHA-512 KDF, ECDSA signatures + * + * Once both peers have a PFS key, they will attempt to send PFS key + * encrypted messages with the PFS flag set using the negotiated + * cipher/auth type. + * + * Note: most of these features such as FIPS and other cipher suites are + * not implemented yet. They're just specified in the protocol for future + * use to support e.g. FIPS requirements. + */ + VERB_SET_EPHEMERAL_KEY = 15 }; /** @@ -824,7 +891,7 @@ public: Buffer(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); - (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops } /** @@ -873,7 +940,7 @@ public: Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); setSource(source); - (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops setVerb(v); } @@ -884,34 +951,21 @@ public: * technically different but otherwise identical copies of the same * packet. */ - inline void newInitializationVector() - { - Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); - } + inline void newInitializationVector() { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); } /** * Set this packet's destination * * @param dest ZeroTier address of destination */ - inline void setDestination(const Address &dest) - { - unsigned char *d = field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH); - for(unsigned int i=0;i> 3); + return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3); } /** @@ -983,7 +1037,7 @@ public: inline void setCipher(unsigned int c) { unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; - b = (b & 0xe7) | (unsigned char)((c << 3) & 0x18); // bits: FFFCCHHH + b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH // DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) b |= ZT_PROTO_FLAG_ENCRYPTED; diff --git a/node/Utils.cpp b/node/Utils.cpp index 3380b324b..9630e6b30 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -49,6 +49,7 @@ #include "Utils.hpp" #include "Mutex.hpp" +#include "Salsa20.hpp" namespace ZeroTier { @@ -152,6 +153,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) static HCRYPTPROV cryptProvider = NULL; static Mutex globalLock; + static Salsa20 s20; Mutex::Lock _l(globalLock); @@ -161,12 +163,19 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) exit(1); return; } + char s20key[32]; + if (!CryptGenRandom(cryptProvider,(DWORD)sizeof(s20key),(BYTE *)s20key)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); + exit(1); + } + s20.init(s20key,256,s20key,8); } if (!CryptGenRandom(cryptProvider,(DWORD)bytes,(BYTE *)buf)) { fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); exit(1); } + s20.encrypt(buf,buf,bytes); #else // not __WINDOWS__ diff --git a/selftest.cpp b/selftest.cpp index 319271f32..5d5067fd8 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -204,6 +204,24 @@ static int testCrypto() ::free((void *)bb); } + std::cout << "[crypto] Benchmarking Salsa20/20... "; std::cout.flush(); + { + unsigned char *bb = (unsigned char *)::malloc(1234567); + for(unsigned int i=0;i<1234567;++i) + bb[i] = (unsigned char)i; + Salsa20 s20(s20TV0Key,256,s20TV0Iv,20); + double bytes = 0.0; + uint64_t start = OSUtils::now(); + for(unsigned int i=0;i<200;++i) { + s20.encrypt(bb,bb,1234567); + bytes += 1234567.0; + } + uint64_t end = OSUtils::now(); + SHA512::hash(buf1,bb,1234567); + std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1000.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; + ::free((void *)bb); + } + std::cout << "[crypto] Testing SHA-512... "; std::cout.flush(); SHA512::hash(buf1,sha512TV0Input,(unsigned int)strlen(sha512TV0Input)); if (memcmp(buf1,sha512TV0Digest,64)) {