lob_t local_decrypt(local_t local, lob_t outer) { uint8_t secret[crypto_box_BEFORENMBYTES]; lob_t inner, tmp; // * `KEY` - 32 bytes, the sending exchange's ephemeral public key // * `NONCE` - 24 bytes, randomly generated // * `CIPHERTEXT` - the inner packet bytes encrypted using secretbox() using the `NONCE` as the nonce and the shared secret (derived from the recipients endpoint key and the included ephemeral key) as the key // * `AUTH` - 16 bytes, the calculated onetimeauth(`KEY` + `INNER`, SHA256(`NONCE` + secret)) using the shared secret derived from both endpoint keys, the hashing is to minimize the chance that the same key input is ever used twice if(outer->body_len <= (32+24+crypto_secretbox_MACBYTES+16)) return NULL; tmp = lob_new(); if(!lob_body(tmp,NULL,outer->body_len-(32+24+crypto_secretbox_MACBYTES+16))) return lob_free(tmp); // get the shared secret crypto_box_beforenm(secret, outer->body, local->secret); // decrypt the inner if(crypto_secretbox_open_easy(tmp->body, outer->body+32+24, tmp->body_len+crypto_secretbox_MACBYTES, outer->body+32, secret) != 0) return lob_free(tmp); // load inner packet inner = lob_parse(tmp->body,tmp->body_len); lob_free(tmp); return inner; }
lob_t local_decrypt(local_t local, lob_t outer) { uint8_t key[uECC_BYTES*2], shared[uECC_BYTES], iv[16], hash[32]; lob_t inner, tmp; // * `KEY` - 21 bytes, the sender's ephemeral exchange public key in compressed format // * `IV` - 4 bytes, a random but unique value determined by the sender // * `INNER` - (minimum 21+2 bytes) the AES-128-CTR encrypted inner packet ciphertext // * `HMAC` - 4 bytes, the calculated HMAC of all of the previous KEY+INNER bytes if(outer->body_len <= (21+4+0+4)) return NULL; tmp = lob_new(); if(!lob_body(tmp,NULL,outer->body_len-(4+21+4))) return lob_free(tmp); // get the shared secret to create the iv+key for the open aes uECC_decompress(outer->body,key); if(!uECC_shared_secret(key, local->secret, shared)) return lob_free(tmp); e3x_hash(shared,uECC_BYTES,hash); fold1(hash,hash); memset(iv,0,16); memcpy(iv,outer->body+21,4); // decrypt the inner aes_128_ctr(hash,tmp->body_len,iv,outer->body+4+21,tmp->body); // load inner packet inner = lob_parse(tmp->body,tmp->body_len); lob_free(tmp); return inner; }
lob_t ephemeral_decrypt(ephemeral_t ephem, lob_t outer) { crypto_secretbox_open_easy(outer->body+16+24, outer->body+16+24, outer->body_len-(16+24), outer->body+16, ephem->deckey); return lob_parse(outer->body+16+24, outer->body_len-(16+24+crypto_secretbox_MACBYTES)); }
// process an incoming handshake link_t link_receive_handshake(link_t link, lob_t inner, pipe_t pipe) { link_t ready; uint32_t out, err; seen_t seen; uint8_t csid = 0; char *hexid; lob_t attached, outer = lob_linked(inner); if(!link || !inner || !outer) return LOG("bad args"); hexid = lob_get(inner, "csid"); if(!lob_get(link->mesh->keys, hexid)) return LOG("unsupported csid %s",hexid); util_unhex(hexid, 2, &csid); attached = lob_parse(inner->body, inner->body_len); if(!link->key && link_key(link->mesh, attached, csid) != link) return LOG("invalid/mismatch link handshake"); if((err = e3x_exchange_verify(link->x,outer))) return LOG("handshake verification fail: %d",err); out = e3x_exchange_out(link->x,0); ready = link_up(link); // if bad at, always send current handshake if(e3x_exchange_in(link->x, lob_get_uint(inner,"at")) < out) { LOG("old/bad at: %s (%d,%d,%d)",lob_json(inner),lob_get_int(inner,"at"),e3x_exchange_in(link->x,0),e3x_exchange_out(link->x,0)); // just reset pipe seen and call link_sync to resend handshake for(seen = link->pipes;pipe && seen;seen = seen->next) if(seen->pipe == pipe) seen->at = 0; lob_free(link_sync(link)); return NULL; } // trust/add this pipe if(pipe) link_pipe(link,pipe); // try to sync ephemeral key if(!e3x_exchange_sync(link->x,outer)) return LOG("sync failed"); // we may need to re-sync if(out != e3x_exchange_out(link->x,0)) lob_free(link_sync(link)); // notify of ready state change if(!ready && link_up(link)) { LOG("link ready"); mesh_link(link->mesh, link); } return link; }
// get paths from host and query lob_t util_uri_paths(lob_t uri) { uint32_t i; uint16_t port; uint8_t *buf; size_t len; char *key, *value; lob_t paths, query = lob_linked(uri); if(!query) return NULL; paths = NULL; // gen paths from host/port if((port = lob_get_uint(uri,"port"))) { key = lob_get(uri,"host"); paths = lob_chain(paths); lob_set(paths,"type","upd4"); lob_set(paths,"ip",key); lob_set_uint(paths,"port",port); paths = lob_chain(paths); lob_set(paths,"type","tcp4"); lob_set(paths,"ip",key); lob_set_uint(paths,"port",port); } // loop through all keyval pairs to find paths buf = NULL; for(i=0;(key = lob_get_index(query,i));i+=2) { value = lob_get_index(query,i+1); if(util_cmp(key,"paths") != 0 || !value) continue; len = base32_decode_floor(strlen(value)); buf = util_reallocf(buf,len); if(!buf) continue; if(base32_decode(value,strlen(value),buf,len) < len) continue; paths = lob_link(lob_parse(buf,len), paths); } free(buf); return paths; }
lob_t ephemeral_decrypt(ephemeral_t ephem, lob_t outer) { uint8_t iv[16], hmac[32]; memset(iv,0,16); memcpy(iv,outer->body+16,4); memcpy(hmac,ephem->deckey,16); memcpy(hmac+16,iv,4); // mac just the ciphertext hmac_256(hmac,16+4,outer->body+16+4,outer->body_len-(4+16+4),hmac); fold3(hmac,hmac); if(util_ct_memcmp(hmac,outer->body+(outer->body_len-4),4) != 0) return LOG("hmac failed"); // decrypt in place aes_128_ctr(ephem->deckey,outer->body_len-(16+4+4),iv,outer->body+16+4,outer->body+16+4); // return parse attempt return lob_parse(outer->body+16+4, outer->body_len-(16+4+4)); }
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; }
// process any unencrypted handshake packet link_t mesh_receive_handshake(mesh_t mesh, lob_t handshake, pipe_t pipe) { uint32_t now; hashname_t from; link_t link; if(!mesh || !handshake) return LOG("bad args"); if(!lob_get(handshake,"id")) { LOG("bad handshake, no id: %s",lob_json(handshake)); lob_free(handshake); return NULL; } now = util_sys_seconds(); // normalize handshake handshake->id = now; // save when we cached it if(!lob_get(handshake,"type")) lob_set(handshake,"type","link"); // default to link type if(!lob_get_uint(handshake,"at")) lob_set_uint(handshake,"at",now); // require an at LOG("handshake at %d id %s",now,lob_get(handshake,"id")); // validate/extend link handshakes immediately if(util_cmp(lob_get(handshake,"type"),"link") == 0) { // get the csid uint8_t csid = 0; lob_t outer; if((outer = lob_linked(handshake))) { csid = outer->head[0]; }else if(lob_get(handshake,"csid")){ util_unhex(lob_get(handshake,"csid"),2,&csid); } if(!csid) { LOG("bad link handshake, no csid: %s",lob_json(handshake)); lob_free(handshake); return NULL; } char hexid[3] = {0}; util_hex(&csid, 1, hexid); // get attached hashname lob_t tmp = lob_parse(handshake->body, handshake->body_len); from = hashname_vkey(tmp, csid); if(!from) { LOG("bad link handshake, no hashname: %s",lob_json(handshake)); lob_free(tmp); lob_free(handshake); return NULL; } lob_set(handshake,"csid",hexid); lob_set(handshake,"hashname",hashname_char(from)); lob_set_raw(handshake,hexid,2,"true",4); // intermediate format lob_body(handshake, tmp->body, tmp->body_len); // re-attach as raw key lob_free(tmp); // short-cut, if it's a key from an existing link, pass it on // TODO: using mesh_linked here is a stack issue during loopback peer test! if((link = mesh_linkid(mesh,from))) return link_receive_handshake(link, handshake, pipe); LOG("no link found for handshake from %s",hashname_char(from)); // extend the key json to make it compatible w/ normal patterns tmp = lob_new(); lob_set_base32(tmp,hexid,handshake->body,handshake->body_len); lob_set_raw(handshake,"keys",0,(char*)tmp->head,tmp->head_len); lob_free(tmp); // add the path if one if(pipe && pipe->path) { char *paths = malloc(pipe->path->head_len+3); sprintf(paths,"[%.*s]",(int)pipe->path->head_len,(char*)pipe->path->head); lob_set_raw(handshake,"paths",0,paths,pipe->path->head_len+2); free(paths); } } // always add to the front of the cached list if needed in the future mesh->cached = lob_unshift(mesh->cached, handshake); // tell anyone listening about the newly discovered handshake mesh_discover(mesh, handshake, pipe); return NULL; }
void ext_thtp(chan_t c) { lob_t p, buf, req, match, note; char *path; thtp_t t = thtp_get(c->s); // incoming note as an answer if((note = chan_notes(c))) { DEBUG_PRINTF("got note resp %.*s",note->json_len,note->json); thtp_send(c,lob_linked(note)); lob_free(note); return; } while((p = chan_pop(c))) { if(!c->arg) { c->arg = buf = p; }else{ buf = c->arg; lob_append(buf,p->body,p->body_len); lob_free(p); } // for now we're processing whole-requests-at-once, to do streaming we can try parsing note->body for the headers anytime if(c->ended) continue; // parse the payload p = lob_parse(buf->body,buf->body_len); // this is a response, send it if((note = lob_unlink(buf))) { lob_free(buf); if(p) { DEBUG_PRINTF("got response %.*s for %.*s",p->json_len,p->json,note->json_len,note->json); } lob_link(note,p); lob_set_str(note,"thtp","resp"); chan_reply(c,note); chan_end(c,NULL); return; } // this is an incoming request lob_free(buf); if(!p) return (void)chan_fail(c,"422"); req = p; DEBUG_PRINTF("thtp req packet %.*s", req->json_len, req->json); path = lob_get_str(req,"path"); match = xht_get(t->index,path); if(!match) match = _thtp_glob(t,path); if(!match) { chan_fail(c,"404"); lob_free(req); return; } // built in response if(lob_linked(match)) { thtp_send(c,lob_linked(match)); lob_free(req); return; } // attach and route request to a new note note = lob_copy(match); lob_link(note,req); lob_set_str(note,"thtp","req"); if(chan_reply(c,note) == 0) return; chan_fail(c,"500"); lob_free(req); } // optionally sends ack if needed chan_ack(c); }