lob_t remote_encrypt(remote_t remote, local_t local, lob_t inner) { uint8_t shared[uECC_BYTES+4], iv[16], hash[32], csid = 0x1a; lob_t outer; size_t inner_len; outer = lob_new(); lob_head(outer,&csid,1); inner_len = lob_len(inner); if(!lob_body(outer,NULL,21+4+inner_len+4)) return lob_free(outer); // copy in the ephemeral public key memcpy(outer->body, remote->ecomp, uECC_BYTES+1); // get the shared secret to create the iv+key for the open aes if(!uECC_shared_secret(remote->key, remote->esecret, shared)) return lob_free(outer); e3x_hash(shared,uECC_BYTES,hash); fold1(hash,hash); memset(iv,0,16); memcpy(iv,&(remote->seq),4); remote->seq++; // increment seq after every use memcpy(outer->body+21,iv,4); // send along the used IV // encrypt the inner into the outer aes_128_ctr(hash,inner_len,iv,lob_raw(inner),outer->body+21+4); // generate secret for hmac if(!uECC_shared_secret(remote->key, local->secret, shared)) return lob_free(outer); memcpy(shared+uECC_BYTES,outer->body+21,4); // use the IV too hmac_256(shared,uECC_BYTES+4,outer->body,21+4+inner_len,hash); fold3(hash,outer->body+21+4+inner_len); // write into last 4 bytes return outer; }
lob_t ephemeral_encrypt(ephemeral_t ephem, lob_t inner) { lob_t outer; uint8_t iv[16], hmac[32]; size_t inner_len; outer = lob_new(); inner_len = lob_len(inner); if(!lob_body(outer,NULL,16+4+inner_len+4)) return lob_free(outer); // copy in token and create/copy iv memcpy(outer->body,ephem->token,16); memset(iv,0,16); memcpy(iv,&(ephem->seq),4); ephem->seq++; memcpy(outer->body+16,iv,4); // encrypt full inner into the outer aes_128_ctr(ephem->enckey,inner_len,iv,lob_raw(inner),outer->body+16+4); // generate mac key and mac the ciphertext memcpy(hmac,ephem->enckey,16); memcpy(hmac+16,iv,4); hmac_256(hmac,16+4,outer->body+16+4,inner_len,hmac); fold3(hmac,outer->body+16+4+inner_len); return outer; }
lob_t remote_encrypt(remote_t remote, local_t local, lob_t inner) { uint8_t secret[crypto_box_BEFORENMBYTES], nonce[24], shared[24+crypto_box_BEFORENMBYTES], hash[32], csid = 0x3a; lob_t outer; size_t inner_len; outer = lob_new(); lob_head(outer,&csid,1); inner_len = lob_len(inner); if(!lob_body(outer,NULL,32+24+inner_len+crypto_secretbox_MACBYTES+16)) return lob_free(outer); // copy in the ephemeral public key/nonce memcpy(outer->body, remote->ekey, 32); randombytes(nonce,24); memcpy(outer->body+32, nonce, 24); // get the shared secret to create the nonce+key for the open aes crypto_box_beforenm(secret, remote->key, remote->esecret); // encrypt the inner if(crypto_secretbox_easy(outer->body+32+24, lob_raw(inner), inner_len, nonce, secret) != 0) return lob_free(outer); // generate secret for hmac crypto_box_beforenm(secret, remote->key, local->secret); memcpy(shared,nonce,24); memcpy(shared+24,secret,crypto_box_BEFORENMBYTES); e3x_hash(shared,24+crypto_box_BEFORENMBYTES,hash); crypto_onetimeauth(outer->body+32+24+inner_len+crypto_secretbox_MACBYTES, outer->body, outer->body_len-16, hash); return outer; }
lob_t link_handshakes(link_t link) { uint32_t i; uint8_t csid; char *key; lob_t tmp, hs = NULL, handshakes = NULL; if(!link) return NULL; // no keys means we have to generate a handshake for each key if(!link->x) { for(i=0;(key = lob_get_index(link->mesh->keys,i));i+=2) { util_unhex(key,2,&csid); hs = lob_new(); tmp = hashname_im(link->mesh->keys, csid); lob_body(hs, lob_raw(tmp), lob_len(tmp)); lob_free(tmp); handshakes = lob_link(hs, handshakes); } }else{ // generate one just for this csid handshakes = lob_new(); tmp = hashname_im(link->mesh->keys, link->csid); lob_body(handshakes, lob_raw(tmp), lob_len(tmp)); lob_free(tmp); } // add any custom per-link for(hs = link->handshakes; hs; hs = lob_linked(hs)) handshakes = lob_link(lob_copy(hs), handshakes); // add any mesh-wide handshakes for(hs = link->mesh->handshakes; hs; hs = lob_linked(hs)) handshakes = lob_link(lob_copy(hs), handshakes); // encrypt them if we can if(link->x) { tmp = handshakes; handshakes = NULL; for(hs = tmp; hs; hs = lob_linked(hs)) handshakes = lob_link(e3x_exchange_handshake(link->x, hs), handshakes); lob_free(tmp); } return handshakes; }
util_frames_t util_frames_outbox(util_frames_t frames, uint8_t *data, uint8_t *meta) { if(!frames) return LOG("bad args"); if(frames->err) return LOG("frame state error"); if(!data) return util_frames_ready(frames); // just a ready check uint8_t size = PAYLOAD(frames); uint8_t *out = lob_raw(frames->outbox); uint32_t len = lob_len(frames->outbox); // clear/init uint32_t hash = frames->outbase; // first get the last sent hash if(len) { // safely only hash the packet size correctly uint32_t at, i; for(i = at = 0;at < len && i < frames->out;i++,at += size) { hash ^= murmur4((out+at), ((at - len) < size) ? (at - len) : size); hash += i; } } // if flushing, or nothing to send, just send meta frame w/ hashes if(frames->flush || !len || (frames->out * size) > len) { memset(data,0,size+4); memcpy(data,&(frames->inlast),4); memcpy(data+4,&(hash),4); if(meta) memcpy(data+10,meta,size-10); murmur(data,size,data+size); // LOG("sending meta frame inlast %lu cur %lu",frames->inlast,hash); return frames; } // send next frame memset(data,0,size+4); uint32_t at = frames->out * size; if((at + size) > len) { size = len - at; data[PAYLOAD(frames)-1] = size; } memcpy(data,out+at,size); hash ^= murmur4(data,size); hash += frames->out; memcpy(data+PAYLOAD(frames),&(hash),4); LOG("sending data frame %u %lu",frames->out,hash); return frames; }
lob_t link_handshake(link_t link) { if(!link) return NULL; if(!link->x) return LOG_DEBUG("no exchange"); LOG_DEBUG("generating a new handshake in %lu out %lu",link->x->in,link->x->out); lob_t handshake = lob_copy(link->mesh->handshake); lob_t tmp = hashname_im(link->mesh->keys, link->csid); lob_body(handshake, lob_raw(tmp), lob_len(tmp)); lob_free(tmp); // encrypt it tmp = handshake; handshake = e3x_exchange_handshake(link->x, tmp); lob_free(tmp); return handshake; }
lob_t ephemeral_encrypt(ephemeral_t ephem, lob_t inner) { lob_t outer; size_t inner_len; outer = lob_new(); inner_len = lob_len(inner); if(!lob_body(outer,NULL,16+24+inner_len+crypto_secretbox_MACBYTES)) return lob_free(outer); // copy in token and create nonce memcpy(outer->body,ephem->token,16); randombytes(outer->body+16,24); crypto_secretbox_easy(outer->body+16+24, lob_raw(inner), lob_len(inner), outer->body+16, ephem->enckey); return outer; }
// chunk the packet out void thtp_send(chan_t c, lob_t req) { lob_t chunk; unsigned char *raw; unsigned short len, space; if(!c || !req) return; DEBUG_PRINTF("THTP sending %.*s %.*s",req->json_len,req->json,req->body_len,req->body); raw = lob_raw(req); len = lob_len(req); while(len) { chunk = chan_packet(c); if(!chunk) return; // TODO backpressure space = lob_space(chunk); if(space > len) space = len; lob_body(chunk,raw,space); if(len==space) lob_set(chunk,"end","true",4); chan_send(c,chunk); raw+=space; len-=space; } }
// turn this packet into chunks util_chunks_t util_chunks_send(util_chunks_t chunks, lob_t out) { uint32_t start, at; size_t len; uint8_t *raw, size, rounds = 1; // TODO random rounds? // validate and gc first if(!_util_chunks_gc(chunks) || !(len = lob_len(out))) return chunks; if(chunks->cloak) len += (8*rounds); start = chunks->writelen; chunks->writelen += len; chunks->writelen += CEIL(len,chunks->space); // include space for per-chunk start byte chunks->writelen++; // space for terminating 0 if(!(chunks->writing = util_reallocf(chunks->writing, chunks->writelen))) { chunks->writelen = chunks->writeat = 0; return LOG("OOM"); } raw = lob_raw(out); if(chunks->cloak) raw = lob_cloak(out, rounds); for(at = 0; at < len;) { size = ((len-at) < chunks->space) ? (uint8_t)(len-at) : chunks->space; chunks->writing[start] = size; start++; memcpy(chunks->writing+start,raw+at,size); at += size; start += size; } chunks->writing[start] = 0; // end of chunks, full packet if(chunks->cloak) free(raw); return chunks; }
// the next frame of data in/out, if data NULL bool is just ready check util_frames_t util_frames_inbox(util_frames_t frames, uint8_t *data, uint8_t *meta) { if(!frames) return LOG("bad args"); if(frames->err) return LOG("frame state error"); if(!data) return util_frames_await(frames); // conveniences for code readability uint8_t size = PAYLOAD(frames); uint32_t hash1; memcpy(&(hash1),data+size,4); uint32_t hash2 = murmur4(data,size); // LOG("frame sz %u hash rx %lu check %lu",size,hash1,hash2); // meta frames are self contained if(hash1 == hash2) { // LOG("meta frame %s",util_hex(data,size+4,NULL)); // if requested, copy in metadata block if(meta) memcpy(meta,data+10,size-10); // verify sender's last rx'd hash uint32_t rxd; memcpy(&rxd,data,4); uint8_t *bin = lob_raw(frames->outbox); uint32_t len = lob_len(frames->outbox); uint32_t rxs = frames->outbase; uint8_t i; for(i = 0;i <= frames->out;i++) { // verify/reset to last rx'd frame if(rxd == rxs) { frames->out = i; break; } // handle tail hash correctly like sender uint32_t at = i * size; rxs ^= murmur4((bin+at), ((at+size) > len) ? (len - at) : size); rxs += i; } if(rxd != rxs) { LOG("invalid received frame hash %lu check %lu",rxd,rxs); frames->err = 1; return NULL; } // advance full packet once confirmed if((frames->out * size) > len) { frames->out = 0; frames->outbase = rxd; lob_t done = lob_shift(frames->outbox); frames->outbox = done->next; done->next = NULL; lob_free(done); } // sender's last tx'd hash changes flush state if(memcmp(data+4,&(frames->inlast),4) == 0) { frames->flush = 0; }else{ frames->flush = 1; LOG("flushing mismatch, last %lu",frames->inlast); } return frames; } // dedup, if identical to last received one if(hash1 == frames->inlast) return frames; // full data frames must match combined w/ previous hash2 ^= frames->inlast; hash2 += frames->in; if(hash1 == hash2) { if(!util_frame_new(frames)) return LOG("OOM"); // append, update inlast, continue memcpy(frames->cache->data,data,size); frames->flush = 0; frames->inlast = hash1; // LOG("got data frame %lu",hash1); return frames; } // check if it's a tail data frame uint8_t tail = data[size-1]; if(tail >= size) { frames->flush = 1; return LOG("invalid frame data length: %u %s",tail,util_hex(data+(size-4),8,NULL)); } // hash must match hash2 = murmur4(data,tail); hash2 ^= frames->inlast; hash2 += frames->in; if(hash1 != hash2) { frames->flush = 1; return LOG("invalid frame %u tail (%u) hash %lu != %lu last %lu",frames->in,tail,hash1,hash2,frames->inlast); } // process full packet w/ tail, update inlast, set flush // LOG("got frame tail of %u",tail); frames->flush = 1; frames->inlast = hash1; size_t tlen = (frames->in * size) + tail; // TODO make a lob_new that creates space to prevent double-copy here uint8_t *buf = malloc(tlen); if(!buf) return LOG("OOM"); // copy in tail memcpy(buf+(frames->in * size), data, tail); // eat cached frames copying in reverse util_frame_t frame = frames->cache; while(frames->in && frame) { frames->in--; memcpy(buf+(frames->in*size),frame->data,size); frame = frame->prev; } frames->cache = util_frame_free(frames->cache); lob_t packet = lob_parse(buf,tlen); if(!packet) LOG("packet parsing failed: %s",util_hex(buf,tlen,NULL)); free(buf); frames->inbox = lob_push(frames->inbox,packet); return frames; }