// size (in bytes) of buffered data in or out uint32_t e3x_channel_size(e3x_channel_t c, uint32_t max) { uint32_t size = 0; lob_t cur; if(!c) return 0; // update max capacity if given if(max) c->capacity = max; // add up the sizes of the in and out buffers cur = c->in; while(cur) { size += lob_len(cur); cur = cur->next; } cur = c->out; while(cur) { size += lob_len(cur); cur = cur->next; } return size; }
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; }
// chunkize a packet void serial_send(pipe_t pipe, lob_t packet, link_t link) { pipe_serial_t to; if(!pipe || !packet || !(to = (pipe_serial_t)pipe->arg)) return; LOG("chunking a packet of len %d",lob_len(packet)); util_chunks_send(to->chunks, packet); serial_flush(pipe); }
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; }
// is there an expectation of an incoming frame static util_frames_t util_frames_await(util_frames_t frames) { if(!frames) return NULL; if(frames->err) return LOG("frame state error"); // need more to complete inbox if(frames->cache) return frames; // outbox is complete, awaiting flush if((frames->out * PAYLOAD(frames)) > lob_len(frames->outbox)) return frames; return NULL; }
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 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; }
size_t util_frames_outlen(util_frames_t frames) { if(!frames) return 0; size_t len = 0; lob_t cur = frames->outbox; do { len += lob_len(cur); cur = lob_next(cur); }while(cur); // subtract sent if(frames->outbox) len -= frames->outbox->id; return len; }
// total bytes in the inbox/outbox size_t util_frames_inlen(util_frames_t frames) { if(!frames) return 0; size_t len = 0; lob_t cur = frames->inbox; do { len += lob_len(cur); cur = lob_next(cur); }while(cur); // add cached frames len += (frames->in * PAYLOAD(frames)); return len; }
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; }
// out state changes util_frames_t util_frames_sent(util_frames_t frames) { if(!frames) return LOG("bad args"); if(frames->err) return LOG("frame state error"); uint8_t size = PAYLOAD(frames); uint32_t len = lob_len(frames->outbox); uint32_t at = frames->out * size; // we sent a meta-frame, clear flush and done, else advance payload if(frames->flush || !len || at > len) { frames->flush = 0; }else{ frames->outbox->id = at + size; // track exact sent bytes frames->out++; // advance sent frames counter } return frames; }
// 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; }
int main(int argc, char **argv) { lob_t id; mesh_t mesh; lob_t opts = lob_new(); fail_unless(e3x_init(opts) == 0); fail_unless(!e3x_err()); // need cs1a support to continue testing e3x_cipher_t cs = e3x_cipher_set(0x1a,NULL); if(!cs) return 0; cs = e3x_cipher_set(0,"1a"); fail_unless(cs); fail_unless(cs->id == CS_1a); uint8_t buf[32]; fail_unless(e3x_rand(buf,32)); char hex[65]; util_hex(e3x_hash((uint8_t*)"foo",3,buf),32,hex); fail_unless(strcmp(hex,"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae") == 0); id = util_fjson("/Users/chrigel/.id.json"); if(!id) return -1; lob_t secrets = lob_get_json(id,"secrets"); fail_unless(secrets); fail_unless(lob_get(secrets,"1a")); lob_t keys = lob_get_json(id,"keys"); fail_unless(keys); fail_unless(lob_get(keys,"1a")); LOG("generated key %s secret %s",lob_get(keys,"1a"),lob_get(secrets,"1a")); local_t localA = cs->local_new(keys,secrets); fail_unless(localA); remote_t remoteA = cs->remote_new(lob_get_base32(keys,"1a"), NULL); fail_unless(remoteA); // create another to start testing real packets lob_t secretsB = e3x_generate(); fail_unless(lob_linked(secretsB)); printf("XX %s\n",lob_json(lob_linked(secretsB))); local_t localB = cs->local_new(lob_linked(secretsB),secretsB); fail_unless(localB); remote_t remoteB = cs->remote_new(lob_get_base32(lob_linked(secretsB),"1a"), NULL); fail_unless(remoteB); // generate a message lob_t messageAB = lob_new(); lob_set_int(messageAB,"a",42); lob_t outerAB = cs->remote_encrypt(remoteB,localA,messageAB); fail_unless(outerAB); fail_unless(lob_len(outerAB) == 42); // decrypt and verify it lob_t innerAB = cs->local_decrypt(localB,outerAB); fail_unless(innerAB); fail_unless(lob_get_int(innerAB,"a") == 42); fail_unless(cs->remote_verify(remoteA,localB,outerAB) == 0); ephemeral_t ephemBA = cs->ephemeral_new(remoteA,outerAB); fail_unless(ephemBA); lob_t channelBA = lob_new(); lob_set(channelBA,"type","foo"); lob_t couterBA = cs->ephemeral_encrypt(ephemBA,channelBA); fail_unless(couterBA); fail_unless(lob_len(couterBA) == 42); lob_t outerBA = cs->remote_encrypt(remoteA,localB,messageAB); fail_unless(outerBA); ephemeral_t ephemAB = cs->ephemeral_new(remoteB,outerBA); fail_unless(ephemAB); lob_t cinnerAB = cs->ephemeral_decrypt(ephemAB,couterBA); fail_unless(cinnerAB); fail_unless(util_cmp(lob_get(cinnerAB,"type"),"foo") == 0); return 0; }
// processes incoming packet, it will take ownership of p link_t mesh_receive(mesh_t mesh, lob_t outer, pipe_t pipe) { lob_t inner; link_t link; char token[17]; hashname_t id; if(!mesh || !outer || !pipe) return LOG("bad args"); LOG("mesh receiving %s to %s via pipe %s",outer->head_len?"handshake":"channel",hashname_short(mesh->id),pipe->id); // process handshakes if(outer->head_len == 1) { inner = e3x_self_decrypt(mesh->self, outer); if(!inner) { LOG("%02x handshake failed %s",outer->head[0],e3x_err()); lob_free(outer); return NULL; } // couple the two together, inner->outer lob_link(inner,outer); // set the unique id string based on some of the first 16 (routing token) bytes in the body base32_encode(outer->body,10,token,17); lob_set(inner,"id",token); // process the handshake return mesh_receive_handshake(mesh, inner, pipe); } // handle channel packets if(outer->head_len == 0) { if(outer->body_len < 16) { LOG("packet too small %d",outer->body_len); lob_free(outer); return NULL; } route_t route; for(route = mesh->routes;route;route = route->next) if(memcmp(route->token,outer->body,8) == 0) break; link = route ? route->link : NULL; if(!link) { LOG("dropping, no link for token %s",util_hex(outer->body,16,NULL)); lob_free(outer); return NULL; } // forward packet if(!route->flag) { LOG("forwarding route token %s via %s len %d",util_hex(route->token,8,NULL),hashname_short(link->id),lob_len(outer)); return link_send(link, outer); } inner = e3x_exchange_receive(link->x, outer); lob_free(outer); if(!inner) return LOG("channel decryption fail for link %s %s",hashname_short(link->id),e3x_err()); LOG("channel packet %d bytes from %s",lob_len(inner),hashname_short(link->id)); return link_receive(link,inner,pipe); } // transform incoming bare link json format into handshake for discovery if((inner = lob_get_json(outer,"keys"))) { if((id = hashname_vkeys(inner))) { lob_set(outer,"hashname",hashname_char(id)); lob_set_int(outer,"at",0); lob_set(outer,"type","link"); LOG("bare incoming link json being discovered %s",lob_json(outer)); } lob_free(inner); } // run everything else through discovery, usually plain handshakes mesh_discover(mesh, outer, pipe); link = mesh_linked(mesh, lob_get(outer,"hashname"), 0); lob_free(outer); return link; }
int main(int argc, char **argv) { lob_t packet; packet = lob_new(); fail_unless(packet); lob_free(packet); uint8_t buf[1024]; char *hex = "001d7b2274797065223a2274657374222c22666f6f223a5b22626172225d7d616e792062696e61727921"; uint8_t len = strlen(hex)/2; util_unhex(hex,strlen(hex),buf); packet = lob_parse(buf,len); fail_unless(packet); fail_unless(lob_len(packet)); fail_unless(packet->head_len == 29); fail_unless(packet->body_len == 11); fail_unless(util_cmp(lob_get(packet,"type"),"test") == 0); fail_unless(util_cmp(lob_get(packet,"foo"),"[\"bar\"]") == 0); lob_free(packet); packet = lob_new(); lob_set_base32(packet,"32",buf,len); fail_unless(lob_get(packet,"32")); fail_unless(strlen(lob_get(packet,"32")) == (base32_encode_length(len)-1)); lob_t bin = lob_get_base32(packet,"32"); fail_unless(bin); fail_unless(bin->body_len == len); lob_set(packet,"key","value"); fail_unless(lob_keys(packet) == 2); // test sorting lob_set(packet,"zz","value"); lob_set(packet,"a","value"); lob_set(packet,"z","value"); lob_sort(packet); fail_unless(util_cmp(lob_get_index(packet,0),"32") == 0); fail_unless(util_cmp(lob_get_index(packet,2),"a") == 0); fail_unless(util_cmp(lob_get_index(packet,4),"key") == 0); fail_unless(util_cmp(lob_get_index(packet,6),"z") == 0); fail_unless(util_cmp(lob_get_index(packet,8),"zz") == 0); lob_free(packet); // minimal comparison test lob_t a = lob_new(); lob_set(a,"foo","bar"); lob_t b = lob_new(); lob_set(b,"foo","bar"); fail_unless(lob_cmp(a,b) == 0); lob_set(b,"bar","foo"); fail_unless(lob_cmp(a,b) != 0); // lots of basic list testing lob_t list = lob_new(); lob_t item = lob_new(); fail_unless(lob_push(list,item)); fail_unless(lob_pop(list) == item); list = item->next; fail_unless((list = lob_unshift(list,item))); fail_unless(lob_shift(list) == item); list = item->next; fail_unless(lob_push(list,item)); fail_unless(list->next == item); lob_t insert = lob_new(); fail_unless(lob_insert(list,list,insert)); fail_unless(list->next == insert); fail_unless(insert->next == item); fail_unless(lob_splice(list,insert)); fail_unless(list->next == item); lob_t array = lob_array(list); fail_unless(array); fail_unless(util_cmp(lob_json(array),"[,]") == 0); fail_unless(lob_freeall(list) == NULL); // simple index testing lob_t index = lob_new(); lob_t c1 = lob_new(); lob_set(c1,"id","c1"); lob_push(index,c1); lob_t c2 = lob_new(); lob_set(c2,"id","c2"); lob_push(index,c2); fail_unless(lob_match(index,"id","c1") == c1); fail_unless(lob_match(index,"id","c2") == c2); float f = 42.42; lob_t ft = lob_new(); lob_head(ft,(uint8_t*)"{\"foo\":42.42}",13); fail_unless(lob_get_float(ft,"foo") == f); lob_set_float(ft,"bar2",f,2); fail_unless(lob_get_float(ft,"bar2") == f); lob_set_float(ft,"bar1",f,1); fail_unless(lob_get_cmp(ft,"bar1","42.4") == 0); lob_set_float(ft,"bar0",f,0); fail_unless(lob_get_int(ft,"bar0") == 42); LOG("floats %s",lob_json(ft)); return 0; }
// 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; }