// load in the key to existing link link_t link_load(link_t link, uint8_t csid, lob_t key) { char hex[3]; lob_t copy; if(!link || !csid || !key) return LOG("bad args"); if(link->x) return link; LOG("adding %x key to link %s",csid,link->id->hashname); // key must be bin if(key->body_len) { copy = lob_copy(key); }else{ util_hex(&csid,1,hex); copy = lob_get_base32(key,hex); } link->x = exchange3_new(link->mesh->self, csid, copy); if(!link->x) { lob_free(copy); return LOG("invalid %x key %d %s",csid,key->body_len,lob_json(key)); } link->csid = csid; link->key = copy; // route packets to this token util_hex(exchange3_token(link->x),16,link->token); xht_set(link->mesh->index,link->token,link); exchange3_out(link->x, platform_seconds()); LOG("delivering session token %s to %s",link->token,link->id->hashname); return link; }
// open must be e3x_channel_receive or e3x_channel_send next yet e3x_channel_t e3x_channel_new(lob_t open) { uint32_t id; char *type; e3x_channel_t c; if(!open) return LOG("open packet required"); id = (uint32_t)lob_get_int(open,"c"); if(!id) return LOG("invalid channel id"); type = lob_get(open,"type"); if(!type) return LOG("missing channel type"); c = malloc(sizeof (struct e3x_channel_struct)); memset(c,0,sizeof (struct e3x_channel_struct)); c->state = OPENING; c->id = id; sprintf(c->c,"%u",id); c->open = lob_copy(open); c->type = lob_get(open,"type"); c->capacity = 1024*1024; // 1MB total default // generate a unique id in hex _uids++; util_hex((uint8_t*)&_uids,4,c->uid); // reliability if(lob_get(open,"seq")) { c->seq = 1; } LOG("new %s channel %s %d %s",c->seq?"reliable":"unreliable",c->uid,id,type); return c; }
int crypt_new_1a(crypt_t c, unsigned char *key, int len) { unsigned char hash[32]; crypt_1a_t cs; if(!key || len <= 0) return 1; c->cs = malloc(sizeof(struct crypt_1a_struct)); memset(c->cs, 0, sizeof (struct crypt_1a_struct)); cs = (crypt_1a_t)c->cs; if(len == uECC_BYTES*2) { memcpy(cs->id_public,key,uECC_BYTES*2); }else{ // try to base64 decode in case that's the incoming format if(key[len] != 0 || base64_binlength((char*)key,0) != uECC_BYTES*2 || base64dec(cs->id_public,(char*)key,0)) return -1; } // generate fingerprint crypt_hash(cs->id_public,uECC_BYTES*2,hash); // create line ephemeral key uECC_make_key(cs->line_public, cs->line_private); // alloc/copy in the public values (free'd by crypt_free) c->part = malloc(32*2+1); c->keylen = uECC_BYTES*2; c->key = malloc(c->keylen); memcpy(c->key,cs->id_public,uECC_BYTES*2); util_hex(hash,32,(unsigned char*)c->part); return 0; }
// process an incoming handshake link_t link_receive_handshake(link_t link, lob_t inner) { uint32_t out, at, err; uint8_t csid = 0; lob_t outer = lob_linked(inner); if(!link || !inner || !outer) return LOG("bad args"); // inner/link must be validated by caller already, we just load if missing if(!link->key) { util_unhex(lob_get(inner, "csid"), 2, &csid); if(!link_load(link, csid, inner)) { lob_free(inner); return LOG("load key failed for %s %u %s",hashname_short(link->id),csid,util_hex(inner->body,inner->body_len,NULL)); } } if((err = e3x_exchange_verify(link->x,outer))) { lob_free(inner); return LOG("handshake verification fail: %d",err); } out = e3x_exchange_out(link->x,0); at = lob_get_uint(inner,"at"); link_t ready = link_up(link); // if bad at, always send current handshake if(e3x_exchange_in(link->x, at) < out) { LOG("old handshake: %s (%d,%d,%d)",lob_json(inner),at,out); link_sync(link); lob_free(inner); return link; } // try to sync ephemeral key if(!e3x_exchange_sync(link->x,outer)) { lob_free(inner); return LOG("sync failed"); } // we may need to re-sync if(out != e3x_exchange_out(link->x,0)) link_sync(link); // notify of ready state change if(!ready && link_up(link)) { LOG("link ready"); mesh_link(link->mesh, link); } link->handshake = lob_free(link->handshake); link->handshake = inner; return link; }
// load in the key to existing link link_t link_load(link_t link, uint8_t csid, lob_t key) { char hex[3]; lob_t copy; if(!link || !csid || !key) return LOG("bad args"); if(link->x) { link->csid = link->x->csid; // repair in case mesh_unlink was called, any better place? return link; } LOG("adding %x key to link %s",csid,hashname_short(link->id)); // key must be bin if(key->body_len) { copy = lob_new(); lob_body(copy,key->body,key->body_len); }else{ util_hex(&csid,1,hex); copy = lob_get_base32(key,hex); } link->x = e3x_exchange_new(link->mesh->self, csid, copy); if(!link->x) { LOG("invalid %x key %s %s",csid,util_hex(copy->body,copy->body_len,NULL),lob_json(key)); lob_free(copy); return NULL; } link->csid = csid; link->key = copy; e3x_exchange_out(link->x, util_sys_seconds()); LOG("new exchange session to %s",hashname_short(link->id)); return link; }
// get link info json lob_t link_json(link_t link) { char hex[3]; lob_t json; if(!link) return LOG("bad args"); json = lob_new(); lob_set(json,"hashname",hashname_char(link->id)); lob_set(json,"csid",util_hex(&link->csid, 1, hex)); lob_set_base32(json,"key",link->key->body,link->key->body_len); // paths = lob_array(mesh->paths); // lob_set_raw(json,"paths",0,(char*)paths->head,paths->head_len); // lob_free(paths); return json; }
int main(int argc, char **argv) { char hex[32]; uint8_t *str = (uint8_t*)"foo bar"; uint32_t len = strlen((char*)str); fail_unless(strcmp("666f6f20626172",util_hex(str,len,hex)) == 0); uint64_t at = util_at(); fail_unless(at > 0); sleep(1); fail_unless(util_since(at)); return 0; }
packet_t crypt_deopenize_1a(crypt_t self, packet_t open) { unsigned char secret[uECC_BYTES], iv[16], b64[uECC_BYTES*2*2], hash[32]; packet_t inner, tmp; crypt_1a_t cs = (crypt_1a_t)self->cs; if(open->body_len <= (4+40)) return NULL; inner = packet_new(); if(!packet_body(inner,NULL,open->body_len-(4+40))) return packet_free(inner); // get the shared secret to create the iv+key for the open aes if(!uECC_shared_secret(open->body+4, cs->id_private, secret)) return packet_free(inner); crypt_hash(secret,uECC_BYTES,hash); fold1(hash,hash); memset(iv,0,16); iv[15] = 1; // decrypt the inner aes_128_ctr(hash,inner->body_len,iv,open->body+4+40,inner->body); // load inner packet if((tmp = packet_parse(inner->body,inner->body_len)) == NULL) return packet_free(inner); packet_free(inner); inner = tmp; // generate secret for hmac if(inner->body_len != uECC_BYTES*2) return packet_free(inner); if(!uECC_shared_secret(inner->body, cs->id_private, secret)) return packet_free(inner); // verify hmac_256(secret,uECC_BYTES,open->body+4,open->body_len-4,hash); fold3(hash,hash); if(memcmp(hash,open->body,4) != 0) return packet_free(inner); // stash the hex line key w/ the inner util_hex(open->body+4,40,b64); packet_set_str(inner,"ecc",(char*)b64); return inner; }
// create hashname from intermediate values as hex/base32 key/value pairs hashname_t hashname_key(lob_t key, uint8_t csid) { unsigned int i, start; uint8_t hash[64]; char *id, *value, hexid[3]; hashname_t hn = NULL; if(!key) return LOG("invalid args"); util_hex(&csid, 1, hexid); // get in sorted order lob_sort(key); // loop through all keys rolling up for(i=0;(id = lob_get_index(key,i));i+=2) { value = lob_get_index(key,i+1); if(strlen(id) != 2 || !util_ishex(id,2) || !value) continue; // skip non-id keys // hash the id util_unhex(id,2,hash+32); start = (i == 0) ? 32 : 0; // only first one excludes previous rollup e3x_hash(hash+start,(32-start)+1,hash); // hash in place // get the value from the body if matching csid arg if(util_cmp(id, hexid) == 0) { if(key->body_len == 0) return LOG("missing key body"); // hash the body e3x_hash(key->body,key->body_len,hash+32); }else{ if(strlen(value) != 52) return LOG("invalid value %s %d",value,strlen(value)); if(base32_decode(value,52,hash+32,32) != 32) return LOG("base32 decode failed %s",value); } e3x_hash(hash,64,hash); } if(!i || i % 2 != 0) return LOG("invalid keys %d",i); hn = hashname_new(hash); return hn; }
// intermediate hashes in the json, if id is given it is attached as BODY instead lob_t hashname_im(lob_t keys, uint8_t id) { uint32_t i; size_t len; uint8_t *buf, hash[32]; char *key, *value, hex[3]; lob_t im; if(!keys) return LOG("bad args"); // loop through all keys and create intermediates im = lob_new(); buf = NULL; util_hex(&id,1,hex); for(i=0;(key = lob_get_index(keys,i));i+=2) { value = lob_get_index(keys,i+1); if(strlen(key) != 2 || !value) continue; // skip non-csid keys len = base32_decode_floor(strlen(value)); // save to body raw or as a base32 intermediate value if(id && util_cmp(hex,key) == 0) { lob_body(im,NULL,len); if(base32_decode(value,strlen(value),im->body,len) != len) continue; lob_set_raw(im,key,0,"true",4); }else{ buf = util_reallocf(buf,len); if(!buf) return lob_free(im); if(base32_decode(value,strlen(value),buf,len) != len) continue; // store the hash intermediate value e3x_hash(buf,len,hash); lob_set_base32(im,key,hash,32); } } if(buf) free(buf); return im; }
mesh_t mesh_forward(mesh_t mesh, uint8_t *token, link_t link, uint8_t flag) { // set up route for link's token route_t r, empty = NULL; // ignore any existing routes uint8_t max=0; for(r=mesh->routes;r;r=r->next) { if(memcmp(token,r->token,8) == 0) return mesh; if(flag && r->link == link) r->link = NULL; // clear any existing for this link when flagged if(!empty && !r->link) empty = r; max++; } // create new route if(!empty) { if(max > 32) { LOG("too many routes, can't add forward (TODO: add eviction)"); return NULL; } LOG("adding new forwarding route to %s token %s",hashname_short(link->id),util_hex(token,8,NULL)); empty = malloc(sizeof(struct route_struct)); memset(empty,0,sizeof(struct route_struct)); empty->next = mesh->routes; mesh->routes = empty; } memcpy(empty->token,token,8); empty->link = link; empty->flag = flag; return mesh; }
// 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; }
// 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; }
// 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; }
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; }