/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "crypto/CryptoAuth_pvt.h" #include "crypto/AddressCalc.h" #include "crypto/ReplayProtector.h" #include "crypto/random/Random.h" #include "benc/Dict.h" #include "benc/List.h" #include "benc/String.h" #include "util/log/Log.h" #include "memory/Allocator.h" #include "util/Assert.h" #include "util/AddrTools.h" #include "util/Bits.h" #include "util/Defined.h" #include "util/Endian.h" #include "util/Hex.h" #include "util/events/Time.h" #include "wire/Error.h" #include "wire/Headers.h" #include "wire/Message.h" #include "crypto_box_curve25519xsalsa20poly1305.h" #include "crypto_hash_sha256.h" #include "crypto_scalarmult_curve25519.h" #include #include #define USE_RES __attribute__ ((warn_unused_result)) static inline void printHexKey(uint8_t output[65], uint8_t key[32]) { if (key) { Hex_encode(output, 65, key, 32); } else { Bits_memcpyConst(output, "NULL", 5); } } static inline void printHexPubKey(uint8_t output[65], uint8_t privateKey[32]) { if (privateKey) { uint8_t publicKey[32]; crypto_scalarmult_curve25519_base(publicKey, privateKey); printHexKey(output, publicKey); } else { printHexKey(output, NULL); } } /** * Get a shared secret. * * @param outputSecret an array to place the shared secret in. * @param myPrivateKey * @param herPublicKey * @param logger * @param passwordHash a 32 byte value known to both ends, this must be provably pseudorandom * the first 32 bytes of a sha256 output from hashing a password is ok, * whatever she happens to send me in the Auth field is NOT ok. * If this field is null, the secret will be generated without the password. */ static inline void getSharedSecret(uint8_t outputSecret[32], uint8_t myPrivateKey[32], uint8_t herPublicKey[32], uint8_t passwordHash[32], struct Log* logger) { if (passwordHash == NULL) { crypto_box_curve25519xsalsa20poly1305_beforenm(outputSecret, herPublicKey, myPrivateKey); } else { union { struct { uint8_t key[32]; uint8_t passwd[32]; } components; uint8_t bytes[64]; } buff; crypto_scalarmult_curve25519(buff.components.key, myPrivateKey, herPublicKey); Bits_memcpyConst(buff.components.passwd, passwordHash, 32); crypto_hash_sha256(outputSecret, buff.bytes, 64); } if (Defined(Log_KEYS)) { uint8_t myPublicKeyHex[65]; printHexPubKey(myPublicKeyHex, myPrivateKey); uint8_t herPublicKeyHex[65]; printHexKey(herPublicKeyHex, herPublicKey); uint8_t passwordHashHex[65]; printHexKey(passwordHashHex, passwordHash); uint8_t outputSecretHex[65] = "NULL"; printHexKey(outputSecretHex, outputSecret); Log_keys(logger, "Generated a shared secret:\n" " myPublicKey=%s\n" " herPublicKey=%s\n" " passwordHash=%s\n" " outputSecret=%s\n", myPublicKeyHex, herPublicKeyHex, passwordHashHex, outputSecretHex); } } static inline void hashPassword_sha256(struct CryptoAuth_Auth* auth, const String* password) { uint8_t tempBuff[32]; crypto_hash_sha256(auth->secret, (uint8_t*) password->bytes, password->len); crypto_hash_sha256(tempBuff, auth->secret, 32); Bits_memcpyConst(auth->challenge.bytes, tempBuff, CryptoHeader_Challenge_SIZE); CryptoHeader_setAuthChallengeDerivations(&auth->challenge, 0); auth->challenge.challenge.type = 1; } static inline uint8_t* hashPassword(struct CryptoAuth_Auth* auth, const String* password, const uint8_t authType) { switch (authType) { case 1: hashPassword_sha256(auth, password); break; default: Assert_true(!"Unsupported auth type."); }; return auth->secret; } /** * Search the authorized passwords for one matching this auth header. * * @param auth the auth header. * @param context the CryptoAuth engine to search in. * @return an Auth struct with a if one is found, otherwise NULL. */ static inline struct CryptoAuth_Auth* getAuth(union CryptoHeader_Challenge auth, struct CryptoAuth_pvt* context) { if (auth.challenge.type != 1) { return NULL; } for (uint32_t i = 0; i < context->passwordCount; i++) { if (Bits_memcmp(auth.bytes, &context->passwords[i], CryptoHeader_Challenge_KEYSIZE) == 0) { return &context->passwords[i]; } } Log_debug(context->logger, "Got unrecognized auth, password count = [%d]", context->passwordCount); return NULL; } static inline void getPasswordHash_typeOne(uint8_t output[32], uint16_t derivations, struct CryptoAuth_Auth* auth) { Bits_memcpyConst(output, auth->secret, 32); if (derivations) { union { uint8_t bytes[2]; uint8_t asShort; } deriv = { .asShort = derivations }; output[0] ^= deriv.bytes[0]; output[1] ^= deriv.bytes[1]; crypto_hash_sha256(output, output, 32); } } static inline uint8_t* tryAuth(union CryptoHeader* cauth, uint8_t hashOutput[32], struct CryptoAuth_Session_pvt* session, struct CryptoAuth_Auth** retAuth) { struct CryptoAuth_Auth* auth = getAuth(cauth->handshake.auth, session->context); if (auth != NULL) { uint16_t deriv = CryptoHeader_getAuthChallengeDerivations(&cauth->handshake.auth); getPasswordHash_typeOne(hashOutput, deriv, auth); if (deriv == 0) { *retAuth = auth; } return hashOutput; } return NULL; } /** * Decrypt and authenticate. * * @param nonce a 24 byte number, may be random, cannot repeat. * @param msg a message to encipher and authenticate. * @param secret a shared secret. * @return 0 if decryption is succeddful, otherwise -1. */ static inline USE_RES int decryptRndNonce(uint8_t nonce[24], struct Message* msg, uint8_t secret[32]) { if (msg->length < 16) { return -1; } Assert_true(msg->padding >= 16); uint8_t* startAt = msg->bytes - 16; uint8_t paddingSpace[16]; Bits_memcpyConst(paddingSpace, startAt, 16); Bits_memset(startAt, 0, 16); if (crypto_box_curve25519xsalsa20poly1305_open_afternm( startAt, startAt, msg->length + 16, nonce, secret) != 0) { return -1; } Bits_memcpyConst(startAt, paddingSpace, 16); Message_shift(msg, -16, NULL); return 0; } /** * Encrypt and authenticate. * Shifts the message by 16 bytes. * * @param nonce a 24 byte number, may be random, cannot repeat. * @param msg a message to encipher and authenticate. * @param secret a shared secret. */ static inline void encryptRndNonce(uint8_t nonce[24], struct Message* msg, uint8_t secret[32]) { Assert_true(msg->padding >= 32); uint8_t* startAt = msg->bytes - 32; // This function trashes 16 bytes of the padding so we will put it back uint8_t paddingSpace[16]; Bits_memcpyConst(paddingSpace, startAt, 16); Bits_memset(startAt, 0, 32); crypto_box_curve25519xsalsa20poly1305_afternm( startAt, startAt, msg->length + 32, nonce, secret); Bits_memcpyConst(startAt, paddingSpace, 16); Message_shift(msg, 16, NULL); } /** * Decrypt a packet. * * @param nonce a counter. * @param msg the message to decrypt, decrypted in place. * @param secret the shared secret. * @param isInitiator true if we started the connection. */ static inline USE_RES int decrypt(uint32_t nonce, struct Message* msg, uint8_t secret[32], bool isInitiator) { union { uint32_t ints[2]; uint8_t bytes[24]; } nonceAs = { .ints = {0, 0} }; nonceAs.ints[!isInitiator] = Endian_hostToLittleEndian32(nonce); return decryptRndNonce(nonceAs.bytes, msg, secret); } /** * Encrypt a packet. * * @param nonce a counter. * @param msg the message to decrypt, decrypted in place. * @param secret the shared secret. * @param isInitiator true if we started the connection. */ static inline void encrypt(uint32_t nonce, struct Message* msg, uint8_t secret[32], bool isInitiator) { union { uint32_t ints[2]; uint8_t bytes[24]; } nonceAs = { .ints = {0, 0} }; nonceAs.ints[isInitiator] = Endian_hostToLittleEndian32(nonce); encryptRndNonce(nonceAs.bytes, msg, secret); } static inline bool knowHerKey(struct CryptoAuth_Session_pvt* session) { return !Bits_isZero(session->pub.herPublicKey, 32); } static void getIp6(struct CryptoAuth_Session_pvt* session, uint8_t* addr) { if (knowHerKey(session)) { uint8_t ip6[16]; AddressCalc_addressForPublicKey(ip6, session->pub.herPublicKey); AddrTools_printIp(addr, ip6); } } #define cryptoAuthDebug(wrapper, format, ...) \ { \ uint8_t addr[40] = "unknown"; \ getIp6((session), addr); \ Log_debug((session)->context->logger, \ "%p %s [%s]: " format, (void*)(session), (session)->name, addr, __VA_ARGS__); \ } #define cryptoAuthDebug0(wrapper, format) \ cryptoAuthDebug(session, format "%s", "") static void reset(struct CryptoAuth_Session_pvt* session) { session->nextNonce = 0; session->isInitiator = false; Bits_memset(session->ourTempPrivKey, 0, 32); Bits_memset(session->ourTempPubKey, 0, 32); Bits_memset(session->herTempPubKey, 0, 32); Bits_memset(session->sharedSecret, 0, 32); session->established = false; Bits_memset(&session->pub.replayProtector, 0, sizeof(struct ReplayProtector)); } static void resetIfTimeout(struct CryptoAuth_Session_pvt* session) { if (session->nextNonce == 1) { // Lets not reset the session, we just sent one or more hello packets and // have not received a response, if they respond after we reset then we'll // be in a tough state. return; } uint64_t nowSecs = Time_currentTimeSeconds(session->context->eventBase); if (nowSecs - session->timeOfLastPacket > session->context->pub.resetAfterInactivitySeconds) { cryptoAuthDebug(session, "No traffic in [%d] seconds, resetting connection.", (int) (nowSecs - session->timeOfLastPacket)); session->timeOfLastPacket = nowSecs; reset(session); } } static void encryptHandshake(struct Message* message, struct CryptoAuth_Session_pvt* session, int setupMessage) { Message_shift(message, sizeof(union CryptoHeader), NULL); union CryptoHeader* header = (union CryptoHeader*) message->bytes; // garbage the auth challenge and set the nonce which follows it Random_bytes(session->context->rand, (uint8_t*) &header->handshake.auth, sizeof(union CryptoHeader_Challenge) + 24); // set the permanent key Bits_memcpyConst(&header->handshake.publicKey, session->context->pub.publicKey, 32); Assert_true(knowHerKey(session)); uint8_t calculatedIp6[16]; AddressCalc_addressForPublicKey(calculatedIp6, session->pub.herPublicKey); if (!Bits_isZero(session->pub.herIp6, 16)) { // If someone starts a CA session and then discovers the key later and memcpy's it into the // result of getHerPublicKey() then we want to make sure they didn't memcpy in an invalid // key. Assert_true(!Bits_memcmp(session->pub.herIp6, calculatedIp6, 16)); } // Password auth uint8_t* passwordHash = NULL; struct CryptoAuth_Auth auth; if (session->password != NULL) { passwordHash = hashPassword(&auth, session->password, session->authType); Bits_memcpyConst(header->handshake.auth.bytes, &auth.challenge, sizeof(union CryptoHeader_Challenge)); } header->handshake.auth.challenge.type = session->authType; // Packet authentication option is deprecated, it must always be enabled. CryptoHeader_setPacketAuthRequired(&header->handshake.auth, 1); header->handshake.auth.challenge.additional = 0; // Set the session state header->nonce = Endian_hostToBigEndian32(session->nextNonce); if (session->nextNonce == 0 || session->nextNonce == 2) { // If we're sending a hello or a key // Here we make up a temp keypair Random_bytes(session->context->rand, session->ourTempPrivKey, 32); crypto_scalarmult_curve25519_base(session->ourTempPubKey, session->ourTempPrivKey); if (Defined(Log_KEYS)) { uint8_t tempPrivateKeyHex[65]; Hex_encode(tempPrivateKeyHex, 65, session->ourTempPrivKey, 32); uint8_t tempPubKeyHex[65]; Hex_encode(tempPubKeyHex, 65, session->ourTempPubKey, 32); Log_keys(session->context->logger, "Generating temporary keypair\n" " myTempPrivateKey=%s\n" " myTempPublicKey=%s\n", tempPrivateKeyHex, tempPubKeyHex); } } Bits_memcpyConst(header->handshake.encryptedTempKey, session->ourTempPubKey, 32); if (Defined(Log_KEYS)) { uint8_t tempKeyHex[65]; Hex_encode(tempKeyHex, 65, header->handshake.encryptedTempKey, 32); Log_keys(session->context->logger, "Wrapping temp public key:\n" " %s\n", tempKeyHex); } cryptoAuthDebug(session, "Sending %s%s packet", ((session->nextNonce & 1) ? "repeat " : ""), ((session->nextNonce < 2) ? "hello" : "key")); uint8_t sharedSecret[32]; if (session->nextNonce < 2) { getSharedSecret(sharedSecret, session->context->privateKey, session->pub.herPublicKey, passwordHash, session->context->logger); session->isInitiator = true; Assert_true(session->nextNonce <= 1); session->nextNonce = 1; } else { // Handshake2 // herTempPubKey was set by decryptHandshake() Assert_ifParanoid(!Bits_isZero(session->herTempPubKey, 32)); getSharedSecret(sharedSecret, session->context->privateKey, session->herTempPubKey, passwordHash, session->context->logger); Assert_true(session->nextNonce <= 3); session->nextNonce = 3; if (Defined(Log_KEYS)) { uint8_t tempKeyHex[65]; Hex_encode(tempKeyHex, 65, session->herTempPubKey, 32); Log_keys(session->context->logger, "Using their temp public key:\n" " %s\n", tempKeyHex); } } // Shift message over the encryptedTempKey field. Message_shift(message, 32 - CryptoHeader_SIZE, NULL); encryptRndNonce(header->handshake.nonce, message, sharedSecret); if (Defined(Log_KEYS)) { uint8_t sharedSecretHex[65]; printHexKey(sharedSecretHex, sharedSecret); uint8_t nonceHex[49]; Hex_encode(nonceHex, 49, header->handshake.nonce, 24); uint8_t cipherHex[65]; printHexKey(cipherHex, message->bytes); Log_keys(session->context->logger, "Encrypting message with:\n" " nonce: %s\n" " secret: %s\n" " cipher: %s\n", nonceHex, sharedSecretHex, cipherHex); } // Shift it back -- encryptRndNonce adds 16 bytes of authenticator. Message_shift(message, CryptoHeader_SIZE - 32 - 16, NULL); } /** @return 0 on success, -1 otherwise. */ int CryptoAuth_encrypt(struct CryptoAuth_Session* sessionPub, struct Message* msg) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*) sessionPub); // If there has been no incoming traffic for a while, reset the connection to state 0. // This will prevent "connection in bad state" situations from lasting forever. // this will reset the session if it has timed out. resetIfTimeout(session); // If the nonce wraps, start over. if (session->nextNonce >= 0xfffffff0) { reset(session); } Assert_true(!((uintptr_t)msg->bytes % 4) || !"alignment fault"); // nextNonce 0: sending hello, we are initiating connection. // nextNonce 1: sending another hello, nothing received yet. // nextNonce 2: sending key, hello received. // nextNonce 3: sending key again, no data packet recieved yet. // nextNonce >3: handshake complete // // if it's a blind handshake, every message will be empty and nextNonce will remain // zero until the first message is received back. if (session->nextNonce < 5) { if (session->nextNonce < 4) { encryptHandshake(msg, session, 0); return 0; } else { cryptoAuthDebug0(session, "Doing final step to send message. nonce=4"); Assert_ifParanoid(!Bits_isZero(session->ourTempPrivKey, 32)); Assert_ifParanoid(!Bits_isZero(session->herTempPubKey, 32)); getSharedSecret(session->sharedSecret, session->ourTempPrivKey, session->herTempPubKey, NULL, session->context->logger); } } Assert_true(msg->length > 0 && "Empty packet during handshake"); Assert_true(msg->padding >= 36 || !"not enough padding"); encrypt(session->nextNonce, msg, session->sharedSecret, session->isInitiator); Message_push32(msg, session->nextNonce, NULL); session->nextNonce++; return 0; } /** Call the external interface and tell it that a message has been received. */ static inline void updateTime(struct CryptoAuth_Session_pvt* session, struct Message* message) { session->timeOfLastPacket = Time_currentTimeSeconds(session->context->eventBase); } static inline USE_RES bool decryptMessage(struct CryptoAuth_Session_pvt* session, uint32_t nonce, struct Message* content, uint8_t secret[32]) { // Decrypt with authentication and replay prevention. if (decrypt(nonce, content, secret, session->isInitiator)) { cryptoAuthDebug0(session, "DROP authenticated decryption failed"); return false; } if (!ReplayProtector_checkNonce(nonce, &session->pub.replayProtector)) { cryptoAuthDebug(session, "DROP nonce checking failed nonce=[%u]", nonce); return false; } return true; } static bool ip6MatchesKey(uint8_t ip6[16], uint8_t key[32]) { uint8_t calculatedIp6[16]; AddressCalc_addressForPublicKey(calculatedIp6, key); return !Bits_memcmp(ip6, calculatedIp6, 16); } static USE_RES int decryptHandshake(struct CryptoAuth_Session_pvt* session, const uint32_t nonce, struct Message* message, union CryptoHeader* header) { if (message->length < CryptoHeader_SIZE) { cryptoAuthDebug0(session, "DROP runt"); return -1; } // handshake // nextNonce 0: recieving hello. // nextNonce 1: recieving key, we sent hello. // nextNonce 2: recieving first data packet or duplicate hello. // nextNonce 3: recieving first data packet. // nextNonce >3: handshake complete if (knowHerKey(session)) { if (Bits_memcmp(session->pub.herPublicKey, header->handshake.publicKey, 32)) { cryptoAuthDebug0(session, "DROP a packet with different public key than this session"); return -1; } } else if (Bits_isZero(session->pub.herIp6, 16)) { // ok fallthrough } else if (!ip6MatchesKey(session->pub.herIp6, header->handshake.publicKey)) { cryptoAuthDebug0(session, "DROP packet with public key not matching ip6 for session"); return -1; } String* user = NULL; struct CryptoAuth_Auth* auth = NULL; uint8_t passwordHashStore[32]; uint8_t* passwordHash = tryAuth(header, passwordHashStore, session, &auth); uint8_t* restrictedToip6 = NULL; if (auth) { user = auth->user; if (auth->restrictedToip6) { restrictedToip6 = auth->restrictedToip6; if (!ip6MatchesKey(auth->restrictedToip6, header->handshake.publicKey)) { cryptoAuthDebug0(session, "DROP packet with key not matching restrictedToip6"); return -1; } } } if (session->requireAuth && !user) { cryptoAuthDebug0(session, "DROP message because auth was not given"); return -1; } if (passwordHash == NULL && header->handshake.auth.challenge.type != 0) { cryptoAuthDebug0(session, "DROP message with unrecognized authenticator"); return -1; } // What the nextNonce will become if this packet is valid. uint32_t nextNonce; // The secret for decrypting this message. uint8_t sharedSecret[32]; uint8_t* herPermKey = NULL; if (nonce < 2) { if (nonce == 0) { cryptoAuthDebug(session, "Received a hello packet, using auth: %d", (passwordHash != NULL)); } else { cryptoAuthDebug0(session, "Received a repeat hello packet"); } // Decrypt message with perminent keys. if (!knowHerKey(session) || session->nextNonce == 0) { herPermKey = header->handshake.publicKey; if (Defined(Log_DEBUG) && Bits_isZero(header->handshake.publicKey, 32)) { cryptoAuthDebug0(session, "Node sent public key of ZERO!"); } } else { herPermKey = session->pub.herPublicKey; if (Bits_memcmp(header->handshake.publicKey, herPermKey, 32)) { cryptoAuthDebug0(session, "DROP packet contains different perminent key"); return -1; } } getSharedSecret(sharedSecret, session->context->privateKey, herPermKey, passwordHash, session->context->logger); nextNonce = 2; } else { if (nonce == 2) { cryptoAuthDebug0(session, "Received a key packet"); } else if (nonce == 3) { cryptoAuthDebug0(session, "Received a repeat key packet"); } else { cryptoAuthDebug(session, "Received a packet of unknown type! nonce=%u", nonce); } if (Bits_memcmp(header->handshake.publicKey, session->pub.herPublicKey, 32)) { cryptoAuthDebug0(session, "DROP packet contains different perminent key"); return -1; } if (!session->isInitiator) { cryptoAuthDebug0(session, "DROP a stray key packet"); return -1; } // We sent the hello, this is a key getSharedSecret(sharedSecret, session->ourTempPrivKey, session->pub.herPublicKey, passwordHash, session->context->logger); nextNonce = 4; } // Shift it on top of the authenticator before the encrypted public key Message_shift(message, 48 - CryptoHeader_SIZE, NULL); if (Defined(Log_KEYS)) { uint8_t sharedSecretHex[65]; printHexKey(sharedSecretHex, sharedSecret); uint8_t nonceHex[49]; Hex_encode(nonceHex, 49, header->handshake.nonce, 24); uint8_t cipherHex[65]; printHexKey(cipherHex, message->bytes); Log_keys(session->context->logger, "Decrypting message with:\n" " nonce: %s\n" " secret: %s\n" " cipher: %s\n", nonceHex, sharedSecretHex, cipherHex); } // Decrypt her temp public key and the message. if (decryptRndNonce(header->handshake.nonce, message, sharedSecret)) { // just in case Bits_memset(header, 0, CryptoHeader_SIZE); cryptoAuthDebug(session, "DROP message with nonce [%d], decryption failed", nonce); return -1; } if (Bits_isZero(header->handshake.encryptedTempKey, 32)) { // we need to reject 0 public keys outright because they will be confused with "unknown" return -1; } if (Defined(Log_KEYS)) { uint8_t tempKeyHex[65]; Hex_encode(tempKeyHex, 65, header->handshake.encryptedTempKey, 32); Log_keys(session->context->logger, "Unwrapping temp public key:\n" " %s\n", tempKeyHex); } Message_shift(message, -32, NULL); // Post-decryption checking if (nonce == 0) { // A new hello packet if (!Bits_memcmp(session->herTempPubKey, header->handshake.encryptedTempKey, 32)) { // possible replay attack or duped packet cryptoAuthDebug0(session, "DROP dupe hello packet with same temp key"); return -1; } } else if (nonce == 2 && session->nextNonce >= 4) { // we accept a new key packet and let it change the session since the other end might have // killed off the session while it was in the midst of setting up. if (!Bits_memcmp(session->herTempPubKey, header->handshake.encryptedTempKey, 32)) { Assert_true(!Bits_isZero(session->herTempPubKey, 32)); cryptoAuthDebug0(session, "DROP dupe key packet with same temp key"); return -1; } } else if (nonce == 3 && session->nextNonce >= 4) { // Got a repeat key packet, make sure the temp key is the same as the one we know. if (Bits_memcmp(session->herTempPubKey, header->handshake.encryptedTempKey, 32)) { Assert_true(!Bits_isZero(session->herTempPubKey, 32)); cryptoAuthDebug0(session, "DROP repeat key packet with different temp key"); return -1; } } // If Alice sent a hello packet then Bob sent a hello packet and they crossed on the wire, // somebody has to yield and the other has to stand firm otherwise they will either deadlock // each believing their hello packet is superior or they will livelock, each switching to the // other's session and never synchronizing. // In this event whoever has the lower permanent public key wins. // If we receive a (possibly repeat) key packet if (nextNonce == 4) { if (session->nextNonce <= 4) { // and have not yet begun sending "run" data Assert_true(session->nextNonce <= nextNonce); session->nextNonce = nextNonce; session->pub.userName = user; Bits_memcpyConst(session->herTempPubKey, header->handshake.encryptedTempKey, 32); } else { // It's a (possibly repeat) key packet and we have begun sending run data. // We will change the shared secret to the one specified in the new key packet but // intentionally avoid de-incrementing the nonce just in case getSharedSecret(session->sharedSecret, session->ourTempPrivKey, header->handshake.encryptedTempKey, NULL, session->context->logger); cryptoAuthDebug0(session, "New key packet but we are already sending data"); } } else if (nextNonce != 2) { Assert_true(!"should never happen"); } else if (!session->isInitiator || session->established) { // This is a hello packet and we are either in ESTABLISHED state or we are // not the initiator of the connection. // If the case is that we are in ESTABLISHED state, the other side tore down the session // and we have not so lets tear it down. // If we are not in ESTABLISHED state then we don't allow resetting of the session unless // they are the sender of the hello packet or their permanent public key is lower. // this is a tie-breaker in case hello packets cross on the wire. if (session->established) { reset(session); } // We got a (possibly repeat) hello packet and we have not sent any hello packet, // new session. if (session->nextNonce == 3) { // We sent a key packet so the next packet is a repeat key but we got another hello // We'll just keep steaming along sending repeat key packets nextNonce = 3; } Assert_true(session->nextNonce <= nextNonce); session->nextNonce = nextNonce; session->pub.userName = user; if (restrictedToip6) { Bits_memcpyConst(session->pub.herIp6, restrictedToip6, 16); } Bits_memcpyConst(session->herTempPubKey, header->handshake.encryptedTempKey, 32); } else if (Bits_memcmp(header->handshake.publicKey, session->context->pub.publicKey, 32) < 0) { // It's a hello and we are the initiator but their permant public key is numerically lower // than ours, this is so that in the event of two hello packets crossing on the wire, the // nodes will agree on who is the initiator. cryptoAuthDebug0(session, "Incoming hello from node with lower key, resetting"); reset(session); Assert_true(session->nextNonce <= nextNonce); session->nextNonce = nextNonce; session->pub.userName = user; if (restrictedToip6) { Bits_memcpyConst(session->pub.herIp6, restrictedToip6, 16); } Bits_memcpyConst(session->herTempPubKey, header->handshake.encryptedTempKey, 32); } else { cryptoAuthDebug0(session, "Incoming hello from node with higher key, not resetting"); } if (herPermKey && herPermKey != session->pub.herPublicKey) { Bits_memcpyConst(session->pub.herPublicKey, herPermKey, 32); } Bits_memset(&session->pub.replayProtector, 0, sizeof(struct ReplayProtector)); return 0; } /** @return 0 on success, -1 otherwise. */ int CryptoAuth_decrypt(struct CryptoAuth_Session* sessionPub, struct Message* msg) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*) sessionPub); union CryptoHeader* header = (union CryptoHeader*) msg->bytes; if (msg->length < 20) { cryptoAuthDebug0(session, "DROP runt"); return -1; } Assert_true(msg->padding >= 12 || "need at least 12 bytes of padding in incoming message"); Assert_true(!((uintptr_t)msg->bytes % 4) || !"alignment fault"); Assert_true(!(msg->capacity % 4) || !"length fault"); Message_shift(msg, -4, NULL); uint32_t nonce = Endian_bigEndianToHost32(header->nonce); if (!session->established) { if (nonce > 3 && nonce != UINT32_MAX) { if (session->nextNonce < 3) { // This is impossible because we have not exchanged hello and key messages. cryptoAuthDebug0(session, "DROP Received a run message to an un-setup session"); return -1; } cryptoAuthDebug(session, "Trying final handshake step, nonce=%u\n", nonce); uint8_t secret[32]; Assert_ifParanoid(!Bits_isZero(session->ourTempPrivKey, 32)); Assert_ifParanoid(!Bits_isZero(session->herTempPubKey, 32)); getSharedSecret(secret, session->ourTempPrivKey, session->herTempPubKey, NULL, session->context->logger); // We'll optimistically advance the nextNonce value because decryptMessage() // passes the message on to the upper level and if this message causes a // response, we want the CA to be in ESTABLISHED state. // if the decryptMessage() call fails, we CryptoAuth_reset() it back. session->nextNonce += 3; if (decryptMessage(session, nonce, msg, secret)) { cryptoAuthDebug0(session, "Final handshake step succeeded"); Bits_memcpyConst(session->sharedSecret, secret, 32); // Now we're in run mode, no more handshake packets will be accepted Bits_memset(session->ourTempPrivKey, 0, 32); Bits_memset(session->ourTempPubKey, 0, 32); Bits_memset(session->herTempPubKey, 0, 32); session->established = true; updateTime(session, msg); return 0; } reset(session); cryptoAuthDebug0(session, "DROP Final handshake step failed"); return -1; } Message_shift(msg, 4, NULL); return decryptHandshake(session, nonce, msg, header); } else if (nonce > 3 && nonce != UINT32_MAX) { Assert_ifParanoid(!Bits_isZero(session->sharedSecret, 32)); if (decryptMessage(session, nonce, msg, session->sharedSecret)) { updateTime(session, msg); return 0; } else { cryptoAuthDebug0(session, "DROP Failed to decrypt message"); return -1; } } else if (nonce < 2) { cryptoAuthDebug(session, "hello packet during established session nonce=[%d]", nonce); Message_shift(msg, 4, NULL); return decryptHandshake(session, nonce, msg, header); } else { // setup keys are already zeroed, not much we can do here. cryptoAuthDebug(session, "DROP key packet during established session nonce=[%d]", nonce); return -1; } Assert_true(0); } ///////////////////////////////////////////////////////////////////////////////////////////////// struct CryptoAuth* CryptoAuth_new(struct Allocator* allocator, const uint8_t* privateKey, struct EventBase* eventBase, struct Log* logger, struct Random* rand) { struct CryptoAuth_pvt* ca = Allocator_calloc(allocator, sizeof(struct CryptoAuth_pvt), 1); Identity_set(ca); ca->allocator = allocator; ca->passwords = Allocator_calloc(allocator, sizeof(struct CryptoAuth_Auth), 232); ca->passwordCount = 0; ca->passwordCapacity = 232; ca->eventBase = eventBase; ca->logger = logger; ca->pub.resetAfterInactivitySeconds = CryptoAuth_DEFAULT_RESET_AFTER_INACTIVITY_SECONDS; ca->rand = rand; if (privateKey != NULL) { Bits_memcpyConst(ca->privateKey, privateKey, 32); } else { Random_bytes(rand, ca->privateKey, 32); } crypto_scalarmult_curve25519_base(ca->pub.publicKey, ca->privateKey); if (Defined(Log_KEYS)) { uint8_t publicKeyHex[65]; printHexKey(publicKeyHex, ca->pub.publicKey); uint8_t privateKeyHex[65]; printHexKey(privateKeyHex, ca->privateKey); Log_keys(logger, "Initialized CryptoAuth:\n myPrivateKey=%s\n myPublicKey=%s\n", privateKeyHex, publicKeyHex); } return &ca->pub; } int32_t CryptoAuth_addUser(String* password, uint8_t authType, String* user, struct CryptoAuth* ca) { return CryptoAuth_addUser_ipv6(password, authType, user, NULL, ca); } int32_t CryptoAuth_addUser_ipv6(String* password, uint8_t authType, String* user, String* ipv6, struct CryptoAuth* ca) { struct CryptoAuth_pvt* context = Identity_check((struct CryptoAuth_pvt*) ca); if (authType != 1) { return CryptoAuth_addUser_INVALID_AUTHTYPE; } if (context->passwordCount == context->passwordCapacity) { // TODO(cjd): realloc password space and increase buffer. return CryptoAuth_addUser_OUT_OF_SPACE; } struct CryptoAuth_Auth a; hashPassword_sha256(&a, password); for (uint32_t i = 0; i < context->passwordCount; i++) { if (!Bits_memcmp(a.secret, context->passwords[i].secret, 32) || String_equals(user, context->passwords[i].user)) { return CryptoAuth_addUser_DUPLICATE; } } a.user = String_new(user->bytes, context->allocator); if (ipv6) { a.restrictedToip6 = Allocator_malloc(context->allocator, 16); if (AddrTools_parseIp(a.restrictedToip6,ipv6->bytes) < 0) { Log_debug(context->logger, "Ipv6 parsing error!"); return CryptoAuth_addUser_INVALID_IP; } } else { a.restrictedToip6 = NULL; } Bits_memcpyConst(&context->passwords[context->passwordCount], &a, sizeof(struct CryptoAuth_Auth)); context->passwordCount++; return 0; } int CryptoAuth_removeUsers(struct CryptoAuth* context, String* user) { struct CryptoAuth_pvt* ctx = Identity_check((struct CryptoAuth_pvt*) context); if (!user) { int count = ctx->passwordCount; Log_debug(ctx->logger, "Flushing [%d] users", count); ctx->passwordCount = 0; return count; } int count = 0; int i = 0; while (i < (int)ctx->passwordCount) { if (String_equals(ctx->passwords[i].user, user)) { Bits_memcpyConst(&ctx->passwords[i], &ctx->passwords[ctx->passwordCount--], sizeof(struct CryptoAuth_Auth)); count++; } else { i++; } } Log_debug(ctx->logger, "Removing [%d] user(s) identified by [%s]", count, user->bytes); return count; } List* CryptoAuth_getUsers(struct CryptoAuth* context, struct Allocator* alloc) { struct CryptoAuth_pvt* ctx = Identity_check((struct CryptoAuth_pvt*) context); uint32_t count = ctx->passwordCount; List* users = List_new(alloc); for (uint32_t i = 0; i < count; i++) { List_addString(users, String_clone(ctx->passwords[i].user, alloc), alloc); } return users; } struct CryptoAuth_Session* CryptoAuth_newSession(struct CryptoAuth* ca, struct Allocator* alloc, const uint8_t herPublicKey[32], const uint8_t herIp6[16], const bool requireAuth, char* name) { struct CryptoAuth_pvt* context = Identity_check((struct CryptoAuth_pvt*) ca); struct CryptoAuth_Session_pvt* session = Allocator_calloc(alloc, sizeof(struct CryptoAuth_Session_pvt), 1); Identity_set(session); session->context = context; session->requireAuth = requireAuth; session->name = name; session->timeOfLastPacket = Time_currentTimeSeconds(context->eventBase); session->alloc = alloc; if (herPublicKey != NULL) { Bits_memcpyConst(session->pub.herPublicKey, herPublicKey, 32); uint8_t calculatedIp6[16]; AddressCalc_addressForPublicKey(calculatedIp6, herPublicKey); Bits_memcpyConst(session->pub.herIp6, calculatedIp6, 16); if (herIp6 != NULL) { Assert_true(!Bits_memcmp(calculatedIp6, herIp6, 16)); } } else if (herIp6) { Bits_memcpyConst(session->pub.herIp6, herIp6, 16); } return &session->pub; } void CryptoAuth_setAuth(const String* password, const uint8_t authType, struct CryptoAuth_Session* caSession) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*)caSession); if (!password && (session->password || session->authType)) { session->password = NULL; session->authType = 0; } else if (!session->password || !String_equals(session->password, password)) { session->password = String_clone(password, session->alloc); session->authType = authType; } else if (authType != session->authType) { session->authType = authType; } else { return; } reset(session); } int CryptoAuth_getState(struct CryptoAuth_Session* caSession) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*)caSession); switch (session->nextNonce) { case 0: return CryptoAuth_NEW; case 1: // Sent a hello, waiting for the key return CryptoAuth_HANDSHAKE1; case 2: // Received a hello, sent a key packet. case 3: // Received a hello, sent multiple key packets. return CryptoAuth_HANDSHAKE2; case 4: // state 4 = waiting for first data packet to prove the handshake succeeded. // At this point you have sent a challenge and received a response so it is safe // to assume you are not being hit with replay packets. // // Sent a hello, received one or more keys, waiting for data. // In this state data packets will be sent but no data packets have yet been received. return CryptoAuth_HANDSHAKE3; default: // Received data. return (session->established) ? CryptoAuth_ESTABLISHED : CryptoAuth_HANDSHAKE3; } } void CryptoAuth_resetIfTimeout(struct CryptoAuth_Session* caSession) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*)caSession); resetIfTimeout(session); } void CryptoAuth_reset(struct CryptoAuth_Session* caSession) { struct CryptoAuth_Session_pvt* session = Identity_check((struct CryptoAuth_Session_pvt*)caSession); reset(session); } // For testing: void CryptoAuth_encryptRndNonce(uint8_t nonce[24], struct Message* msg, uint8_t secret[32]) { encryptRndNonce(nonce, msg, secret); } int CryptoAuth_decryptRndNonce(uint8_t nonce[24], struct Message* msg, uint8_t secret[32]) { return decryptRndNonce(nonce, msg, secret); }