static inline bool authValid(Dict* message, uint8_t* buffer, uint32_t length, struct Admin* admin) { String* cookieStr = Dict_getString(message, String_CONST("cookie")); uint32_t cookie = (cookieStr != NULL) ? strtoll(cookieStr->bytes, NULL, 10) : 0; if (!cookie) { int64_t* cookieInt = Dict_getInt(message, String_CONST("cookie")); cookie = (cookieInt) ? *cookieInt : 0; } uint64_t nowSecs = Time_currentTimeSeconds(admin->eventBase); String* submittedHash = Dict_getString(message, String_CONST("hash")); if (cookie > nowSecs || cookie < nowSecs - 20 || !submittedHash || submittedHash->len != 64) { return false; } uint8_t* hashPtr = (uint8_t*) strstr((char*) buffer, submittedHash->bytes); if (!hashPtr || !admin->password) { return false; } uint8_t passAndCookie[64]; snprintf((char*) passAndCookie, 64, "%s%u", admin->password->bytes, cookie); uint8_t hash[32]; crypto_hash_sha256(hash, passAndCookie, strlen((char*) passAndCookie)); Hex_encode(hashPtr, 64, hash, 32); crypto_hash_sha256(hash, buffer, length); Hex_encode(hashPtr, 64, hash, 32); return memcmp(hashPtr, submittedHash->bytes, 64) == 0; }
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* displayName) { 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->pub.displayName = String_new(displayName, alloc); 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; }
static void handleRequestFromChild(struct Admin* admin, uint8_t buffer[MAX_API_REQUEST_SIZE], size_t amount, struct Allocator* allocator) { String* txid = NULL; int skip = 0; if (!memcmp(buffer, "0123", 4)) { // out of band txid txid = &(String) { .len = 4, .bytes = (char*) buffer + 4 }; skip = 8; } struct Reader* reader = ArrayReader_new(buffer + skip, amount - skip, allocator); Dict message; if (List_getStandardBencSerializer()->parseDictionary(reader, allocator, &message)) { return; } String* query = Dict_getString(&message, CJDHTConstants_QUERY); if (!query) { return; } // If they're asking for a cookie then lets give them one. String* cookie = BSTR("cookie"); if (String_equals(query, cookie)) { Dict* d = Dict_new(allocator); char bytes[32]; snprintf(bytes, 32, "%u", (uint32_t) Time_currentTimeSeconds(admin->eventBase)); String* theCookie = &(String) { .len = strlen(bytes), .bytes = bytes }; Dict_putString(d, cookie, theCookie, allocator); Admin_sendMessage(d, txid, admin); return; } // If this is a permitted query, make sure the cookie is right. String* auth = BSTR("auth"); bool authed = false; if (String_equals(query, auth)) { if (!authValid(&message, buffer + skip, reader->bytesRead(reader), admin)) { Dict* d = Dict_new(allocator); Dict_putString(d, BSTR("error"), BSTR("Auth failed."), allocator); Admin_sendMessage(d, txid, admin); return; } query = Dict_getString(&message, BSTR("aq")); authed = true; } for (int i = 0; i < admin->functionCount; i++) { if (String_equals(query, admin->functions[i].name) && (authed || !admin->functions[i].needsAuth)) { admin->functions[i].call(&message, admin->functions[i].context, txid); } } return; }
/** Call the external interface and tell it that a message has been received. */ static inline uint8_t callReceivedMessage(struct CryptoAuth_Wrapper* wrapper, struct Message* message) { wrapper->timeOfLastPacket = Time_currentTimeSeconds(wrapper->context->eventBase); uint8_t ret = 0; if (wrapper->externalInterface.receiveMessage != NULL) { ret = wrapper->externalInterface.receiveMessage(message, &wrapper->externalInterface); } return ret; }
static void cleanup(void* vsm) { struct SessionManager* sm = (struct SessionManager*) vsm; uint64_t nowSecs = Time_currentTimeSeconds(sm->eventBase); for (uint32_t i = 0; i < sm->ifaceMap.count; i++) { if (sm->ifaceMap.values[i].lastMessageTime < (nowSecs - SESSION_TIMEOUT_SECONDS)) { struct Allocator* ifAllocator = sm->ifaceMap.values[i].iface.allocator; ifAllocator->free(ifAllocator); Map_OfSessionsByIp6_remove(i, &sm->ifaceMap); i--; } } }
/** Call the external interface and tell it that a message has been received. */ static inline uint8_t callReceivedMessage(struct Wrapper* wrapper, struct Message* message) { uint8_t ret = 0; if (wrapper->externalInterface.receiveMessage != NULL) { ret = wrapper->externalInterface.receiveMessage(message, &wrapper->externalInterface); } // If the message is authenticated OR if the packet is considered valid by the next level, // then don't allow the connection to timeout. if (!ret || wrapper->authenticatePackets) { wrapper->timeOfLastPacket = Time_currentTimeSeconds(wrapper->context->eventBase); } return ret; }
static uint8_t sendMessage(struct Message* message, struct Interface* interface) { struct CryptoAuth_Wrapper* wrapper = Identity_cast((struct CryptoAuth_Wrapper*) interface->senderContext); // 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. uint64_t nowSecs = Time_currentTimeSeconds(wrapper->context->eventBase); if (nowSecs - wrapper->timeOfLastPacket > wrapper->context->pub.resetAfterInactivitySeconds) { Log_debug(wrapper->context->logger, "No traffic in [%d] seconds, resetting connection.", (int) (nowSecs - wrapper->timeOfLastPacket)); wrapper->timeOfLastPacket = nowSecs; CryptoAuth_reset(interface); return encryptHandshake(message, wrapper); } #ifdef Log_DEBUG Assert_true(!((uintptr_t)message->bytes % 4) || !"alignment fault"); #endif // 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 (wrapper->nextNonce < 5) { if (wrapper->nextNonce < 4) { return encryptHandshake(message, wrapper); } else { Log_debug(wrapper->context->logger, "@%p Doing final step to send message. nonce=4\n", (void*) wrapper); uint8_t secret[32]; getSharedSecret(secret, wrapper->secret, wrapper->tempKey, NULL, wrapper->context->logger); Bits_memcpyConst(wrapper->secret, secret, 32); } } return encryptMessage(message, wrapper); }
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); } }
cleanup(sm); struct Allocator* ifAllocator = Allocator_child(sm->allocator); struct Interface* outsideIf = ifAllocator->clone(sizeof(struct Interface), ifAllocator, &(struct Interface) { .sendMessage = sm->encryptedOutgoing, .senderContext = sm->interfaceContext, .allocator = ifAllocator }); struct Interface* insideIf = CryptoAuth_wrapInterface(outsideIf, cryptoKey, false, true, sm->cryptoAuth); insideIf->receiveMessage = sm->decryptedIncoming; insideIf->receiverContext = sm->interfaceContext; struct SessionManager_Session s = { .lastMessageTime = Time_currentTimeSeconds(sm->eventBase), // Create a trick interface which pretends to be on both sides of the crypto. .iface = { .sendMessage = insideIf->sendMessage, .senderContext = insideIf->senderContext, .receiveMessage = outsideIf->receiveMessage, .receiverContext = outsideIf->receiverContext, .allocator = ifAllocator } }; int index = Map_OfSessionsByIp6_put((struct Ip6*)lookupKey, &s, &sm->ifaceMap); struct SessionManager_Session* sp = &sm->ifaceMap.values[index]; sp->receiveHandle_be = Endian_hostToBigEndian32(sm->ifaceMap.handles[index]); Bits_memcpyConst(sp->ip6, lookupKey, 16);
const bool requireAuth, char* name, struct CryptoAuth* ca) { struct CryptoAuth_pvt* context = Identity_check((struct CryptoAuth_pvt*) ca); struct CryptoAuth_Wrapper* wrapper = Allocator_clone(toWrap->allocator, (&(struct CryptoAuth_Wrapper) { .user = NULL, .nextNonce = 0, .context = context, .wrappedInterface = toWrap, .requireAuth = requireAuth, .name = name })); wrapper->timeOfLastPacket = Time_currentTimeSeconds(context->eventBase); Identity_set(wrapper); toWrap->receiverContext = wrapper; toWrap->receiveMessage = receiveMessage; struct Interface iface = { .senderContext = wrapper, .sendMessage = sendMessage, .allocator = toWrap->allocator }; Bits_memcpyConst(&wrapper->externalInterface, &iface, sizeof(struct Interface)); if (herPublicKey != NULL) { Bits_memcpyConst(wrapper->herPerminentPubKey, herPublicKey, 32); uint8_t calculatedIp6[16]; AddressCalc_addressForPublicKey(calculatedIp6, herPublicKey);
/** 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 void handleRequestFromChild(struct Admin* admin, union Admin_TxidPrefix* txid_prefix, Dict* message, uint8_t* buffer, size_t amount, struct Allocator* allocator) { String* query = Dict_getString(message, CJDHTConstants_QUERY); if (!query) { Log_info(admin->logger, "Got a non-query from admin interface on channel [%u].", admin->messageHeader.channelNum); adminChannelClose(admin, admin->messageHeader.channelNum); return; } // txid becomes the user supplied txid combined with the inter-process txid. String* userTxid = Dict_getString(message, TXID); uint32_t txidlen = ((userTxid) ? userTxid->len : 0) + Admin_TxidPrefix_SIZE; String* txid = String_newBinary(NULL, txidlen, allocator); Bits_memcpyConst(txid->bytes, txid_prefix->raw, Admin_TxidPrefix_SIZE); if (userTxid) { Bits_memcpy(txid->bytes + Admin_TxidPrefix_SIZE, userTxid->bytes, userTxid->len); } // If they're asking for a cookie then lets give them one. String* cookie = String_CONST("cookie"); if (String_equals(query, cookie)) { Dict* d = Dict_new(allocator); char bytes[32]; snprintf(bytes, 32, "%u", (uint32_t) Time_currentTimeSeconds(admin->eventBase)); String* theCookie = &(String) { .len = strlen(bytes), .bytes = bytes }; Dict_putString(d, cookie, theCookie, allocator); Admin_sendMessage(d, txid, admin); return; } // If this is a permitted query, make sure the cookie is right. String* auth = String_CONST("auth"); bool authed = false; if (String_equals(query, auth)) { if (!authValid(message, buffer, amount, admin)) { Dict* d = Dict_new(allocator); Dict_putString(d, String_CONST("error"), String_CONST("Auth failed."), allocator); Admin_sendMessage(d, txid, admin); return; } query = Dict_getString(message, String_CONST("aq")); authed = true; } Dict* args = Dict_getDict(message, String_CONST("args")); bool noFunctionsCalled = true; for (int i = 0; i < admin->functionCount; i++) { if (String_equals(query, admin->functions[i].name) && (authed || !admin->functions[i].needsAuth)) { if (checkArgs(args, &admin->functions[i], txid, admin)) { admin->functions[i].call(args, admin->functions[i].context, txid); } noFunctionsCalled = false; } } if (noFunctionsCalled) { Dict* d = Dict_new(allocator); Dict_putString(d, String_CONST("error"), String_CONST("No functions matched your request."), allocator); Dict* functions = Dict_new(allocator); for (int i = 0; i < admin->functionCount; i++) { Dict_putDict(functions, admin->functions[i].name, admin->functions[i].args, allocator); } if (functions) { Dict_putDict(d, String_CONST("availableFunctions"), functions, allocator); } Admin_sendMessage(d, txid, admin); return; } return; }
/** * send a message */ static void adminChannelSendData(struct Admin* admin, uint32_t channelNum, const void *data, uint32_t length) { /* if this changes, we need to fragment the messages * into MAX_MESSAGE_SIZE chunks */ Assert_compileTime(MAX_API_REQUEST_SIZE == MAX_MESSAGE_SIZE); Assert_true(length <= MAX_MESSAGE_SIZE); struct Admin_MessageHeader header = { .magic = admin->pipeMagic, .length = length, .channelNum = channelNum }; const uint8_t* buf = (const uint8_t*) data; // TODO: check result, buffer writes write(admin->outFd, &header, Admin_MessageHeader_SIZE); if (length > 0) { write(admin->outFd, buf, length); } } /** * public function to send responses */ void Admin_sendMessage(Dict* message, String* txid, struct Admin* admin) { if (!admin) { return; } Assert_true(txid); uint32_t channelNum; struct Admin_Channel* channel = adminChannelFindByTxid(admin, txid, &channelNum); if (!channel) { // txid too short, invalid channel number, closed channel or not matching serial Log_debug(admin->logger, "Dropped response because channel isn't open anymore."); return; } uint8_t buff[MAX_API_REQUEST_SIZE]; uint8_t allocBuff[256]; struct Allocator* allocator = BufferAllocator_new(allocBuff, 256); // Bounce back the user-supplied txid. String userTxid = { .bytes = txid->bytes + Admin_TxidPrefix_SIZE, .len = txid->len - Admin_TxidPrefix_SIZE }; if (txid->len > Admin_TxidPrefix_SIZE) { Dict_putString(message, TXID, &userTxid, allocator); } struct Writer* w = ArrayWriter_new(buff, sizeof(buff), allocator); StandardBencSerializer_get()->serializeDictionary(w, message); adminChannelSendData(admin, channelNum, buff, w->bytesWritten(w)); } /** * close a channel (for example if an error happened or we received non-empty * messages on a invalid channel number) * also used to cleanup if we receive a close message */ static void adminChannelClose(struct Admin* admin, uint32_t channelNum) { struct Admin_Channel* channel = adminChannelFindById(admin, channelNum); if (channel) { switch (channel->state) { case Admin_ChannelState_OPEN: break; case Admin_ChannelState_CLOSED: case Admin_ChannelState_WAIT_FOR_CLOSE: return; // already sent close, nothing to do } channel->state = Admin_ChannelState_WAIT_FOR_CLOSE; // clean up bufers channel->bufferLen = 0; channel->buffer = NULL; if (channel->allocator) { channel->allocator->free(channel->allocator); channel->allocator = NULL; } } adminChannelSendData(admin, channelNum, NULL, 0); } /** * handle a received channel close (the received header is in admin->messageHeader) * as invalid channels are never OPEN we never have to ACK a close on them */ static void adminChannelHandleClose(struct Admin* admin) { uint32_t channelNum = admin->messageHeader.channelNum; struct Admin_Channel* channel = adminChannelFindById(admin, channelNum); if (channel) { switch (channel->state) { case Admin_ChannelState_OPEN: // close active channel adminChannelClose(admin, channelNum); // now the state is WAIT_FOR_CLOSE, set it to CLOSED channel->state = Admin_ChannelState_CLOSED; break; case Admin_ChannelState_WAIT_FOR_CLOSE: channel->state = Admin_ChannelState_CLOSED; channel->serial++; break; case Admin_ChannelState_CLOSED: // nothing to do break; } } } static inline bool authValid(Dict* message, uint8_t* buffer, uint32_t length, struct Admin* admin) { String* cookieStr = Dict_getString(message, String_CONST("cookie")); uint32_t cookie = (cookieStr != NULL) ? strtoll(cookieStr->bytes, NULL, 10) : 0; if (!cookie) { int64_t* cookieInt = Dict_getInt(message, String_CONST("cookie")); cookie = (cookieInt) ? *cookieInt : 0; } uint64_t nowSecs = Time_currentTimeSeconds(admin->eventBase); String* submittedHash = Dict_getString(message, String_CONST("hash")); if (cookie > nowSecs || cookie < nowSecs - 20 || !submittedHash || submittedHash->len != 64) { return false; } uint8_t* hashPtr = (uint8_t*) strstr((char*) buffer, submittedHash->bytes); if (!hashPtr || !admin->password) { return false; } uint8_t passAndCookie[64]; snprintf((char*) passAndCookie, 64, "%s%u", admin->password->bytes, cookie); uint8_t hash[32]; crypto_hash_sha256(hash, passAndCookie, strlen((char*) passAndCookie)); Hex_encode(hashPtr, 64, hash, 32); crypto_hash_sha256(hash, buffer, length); Hex_encode(hashPtr, 64, hash, 32); return memcmp(hashPtr, submittedHash->bytes, 64) == 0; }
static void handleRequestFromChild(struct Admin* admin, uint8_t buffer[MAX_API_REQUEST_SIZE], size_t amount, struct Allocator* allocator) { struct Reader* reader = ArrayReader_new(buffer + TXID_LEN, amount - TXID_LEN, allocator); Dict message; if (StandardBencSerializer_get()->parseDictionary(reader, allocator, &message)) { Log_info(admin->logger, "Got unparsable data from admin interface."); return; } String* query = Dict_getString(&message, CJDHTConstants_QUERY); if (!query) { Log_info(admin->logger, "Got a non-query from admin interface."); return; } // txid becomes the user supplied txid combined with the inter-process txid. String* userTxid = Dict_getString(&message, TXID); String* txid = String_newBinary((char*)buffer, ((userTxid) ? userTxid->len : 0) + TXID_LEN, allocator); if (userTxid) { Bits_memcpy(txid->bytes + TXID_LEN, userTxid->bytes, userTxid->len); } // If they're asking for a cookie then lets give them one. String* cookie = String_CONST("cookie"); if (String_equals(query, cookie)) { Dict* d = Dict_new(allocator); char bytes[32]; snprintf(bytes, 32, "%u", (uint32_t) Time_currentTimeSeconds(admin->eventBase)); String* theCookie = &(String) { .len = strlen(bytes), .bytes = bytes }; Dict_putString(d, cookie, theCookie, allocator); Admin_sendMessage(d, txid, admin); return; } // If this is a permitted query, make sure the cookie is right. String* auth = String_CONST("auth"); bool authed = false; if (String_equals(query, auth)) { if (!authValid(&message, buffer + TXID_LEN, reader->bytesRead(reader), admin)) { Dict* d = Dict_new(allocator); Dict_putString(d, String_CONST("error"), String_CONST("Auth failed."), allocator); Admin_sendMessage(d, txid, admin); return; } query = Dict_getString(&message, String_CONST("aq")); authed = true; } Dict* args = Dict_getDict(&message, String_CONST("args")); bool noFunctionsCalled = true; for (int i = 0; i < admin->functionCount; i++) { if (String_equals(query, admin->functions[i].name) && (authed || !admin->functions[i].needsAuth)) { if (checkArgs(args, &admin->functions[i], txid, admin)) { admin->functions[i].call(args, admin->functions[i].context, txid); } noFunctionsCalled = false; } } if (noFunctionsCalled) { Dict* d = Dict_new(allocator); Dict_putString(d, String_CONST("error"), String_CONST("No functions matched your request."), allocator); Dict* functions = Dict_new(allocator); for (int i = 0; i < admin->functionCount; i++) { Dict_putDict(functions, admin->functions[i].name, admin->functions[i].args, allocator); } if (functions) { Dict_putDict(d, String_CONST("availableFunctions"), functions, allocator); } Admin_sendMessage(d, txid, admin); return; } return; }