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 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; }
local_t local_new(lob_t keys, lob_t secrets) { local_t local; lob_t key, secret; if(!keys) keys = lob_linked(secrets); // for convenience key = lob_get_base32(keys,"3a"); if(!key) return LOG("missing key"); if(key->body_len != crypto_box_PUBLICKEYBYTES) return LOG("invalid key %d != %d",key->body_len,crypto_box_PUBLICKEYBYTES); secret = lob_get_base32(secrets,"3a"); if(!secret) return LOG("missing secret"); if(secret->body_len != crypto_box_SECRETKEYBYTES) return LOG("invalid secret %d != %d",secret->body_len,crypto_box_SECRETKEYBYTES); if(!(local = malloc(sizeof(struct local_struct)))) return NULL; memset(local,0,sizeof (struct local_struct)); // copy in key/secret data memcpy(local->key,key->body,key->body_len); memcpy(local->secret,secret->body,secret->body_len); lob_free(key); lob_free(secret); return local; }
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 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; }
local_t local_new(lob_t keys, lob_t secrets) { local_t local = NULL; lob_t key, secret; if(!keys) keys = lob_linked(secrets); // for convenience key = lob_get_base32(keys,"1c"); if(!key) return LOG("invalid key"); secret = lob_get_base32(secrets,"1c"); if(!secret) return LOG("invalid secret"); if(key->body_len == COMP_BYTES && secret->body_len == SECRET_BYTES) { if((local = malloc(sizeof(struct local_struct)))) { memset(local,0,sizeof (struct local_struct)); // copy in key/secret data uECC_decompress(key->body,local->key, curve); memcpy(local->secret,secret->body,secret->body_len); }else{ LOG("OOM"); } }else{ LOG("invalid sizes key %d=%d secret %d=%d",key->body_len,COMP_BYTES,secret->body_len,SECRET_BYTES); } lob_free(key); lob_free(secret); return local; }
// 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; }
// ack/miss only base packet lob_t e3x_channel_oob(e3x_channel_t c) { lob_t ret, cur; char *miss; uint32_t seq, last, delta; size_t len; if(!c) return NULL; ret = lob_new(); lob_set_uint(ret,"c",c->id); if(!c->seq) return ret; // check for ack/miss if(c->ack != c->acked) { lob_set_uint(ret,"ack",c->ack); // also check to include misses cur = c->in; last = c->ack; if(cur && (cur->id - last) != 1) { // I'm so tired of strings in c len = 2; if(!(miss = malloc(len))) return lob_free(ret); len = (size_t)snprintf(miss,len,"["); for(seq=c->ack+1; cur; seq++) { // LOG("ack %d seq %d last %d cur %d",c->ack,seq,last,cur->id); // if we have this seq, skip to next packet if(cur->id <= seq) { cur = cur->next; continue; } // insert this missing seq delta delta = seq - last; last = seq; len += (size_t)snprintf(NULL, 0, "%u,", delta) + 1; if(!(miss = realloc(miss, len))) return lob_free(ret); sprintf(miss+strlen(miss),"%u,", delta); } // add current window at the end delta = 100; // TODO calculate this from actual space avail len += (size_t)snprintf(NULL, 0, "%u]", delta) + 1; if(!(miss = realloc(miss, len))) return lob_free(ret); sprintf(miss+strlen(miss),"%u]", delta); lob_set_raw(ret,"miss",4,miss,strlen(miss)); } c->acked = c->ack; } return ret; }
// 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; }
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; }
// 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; }
// add a pipe to this link if not yet link_t link_pipe(link_t link, pipe_t pipe) { seen_t seen; if(!link || !pipe) return LOG("bad args"); // see if we've seen it already for(seen = link->pipes; seen; seen = seen->next) { if(seen->pipe == pipe) return link; } // add this pipe to this link LOG("adding pipe %s",pipe->id); if(!(seen = malloc(sizeof (struct seen_struct)))) return NULL; memset(seen,0,sizeof (struct seen_struct)); seen->pipe = pipe; seen->next = link->pipes; link->pipes = seen; // make sure it gets sync'd lob_free(link_sync(link)); return link; }
// process a decrypted channel packet link_t link_receive(link_t link, lob_t inner, pipe_t pipe) { chan_t chan; if(!link || !inner) return LOG("bad args"); // see if existing channel and send there if((chan = xht_get(link->index, lob_get(inner,"c")))) { if(channel3_receive(chan->c3, inner)) return LOG("channel receive error, dropping %s",lob_json(inner)); link_pipe(link,pipe); // we trust the pipe at this point if(chan->handle) chan->handle(link, chan->c3, chan->arg); // check if there's any packets to be sent back return link_flush(link, chan->c3, NULL); } // if it's an open, validate and fire event if(!lob_get(inner,"type")) return LOG("invalid channel open, no type %s",lob_json(inner)); if(!exchange3_cid(link->x, inner)) return LOG("invalid channel open id %s",lob_json(inner)); link_pipe(link,pipe); // we trust the pipe at this point inner = mesh_open(link->mesh,link,inner); if(inner) { LOG("unhandled channel open %s",lob_json(inner)); lob_free(inner); return NULL; } return link; }
link_t mesh_add(mesh_t mesh, lob_t json, pipe_t pipe) { link_t link; lob_t keys, paths; uint8_t csid; if(!mesh || !json) return LOG("bad args"); LOG("mesh add %s",lob_json(json)); link = link_get(mesh, hashname_vchar(lob_get(json,"hashname"))); keys = lob_get_json(json,"keys"); paths = lob_get_array(json,"paths"); if(!link) link = link_keys(mesh, keys); if(!link) LOG("no hashname"); LOG("loading keys/paths"); if(keys && (csid = hashname_id(mesh->keys,keys))) link_load(link, csid, keys); // handle any pipe/paths if(pipe) link_pipe(link, pipe); lob_t path; for(path=paths;path;path = lob_next(path)) link_path(link,path); lob_free(keys); lob_freeall(paths); return link; }
int main(int argc, char **argv) { fail_unless(e3x_init(NULL) == 0); lob_t id = e3x_generate(); fail_unless(id); e3x_self_t self = e3x_self_new(id,NULL); fail_unless(self); int i, count = 0; for(i = 0; i < CS_MAX; i++) { if(!self->locals[i]) continue; LOG("self testing CS %d",i); count++; fail_unless(self->locals[i]); fail_unless(self->keys[i]); fail_unless(self->keys[i]->body_len); fail_unless(lob_get(self->keys[i],"key")); fail_unless(lob_get(self->keys[i],"hash")); fail_unless(strlen(lob_get(self->keys[i],"hash")) == 52); } fail_unless(count); e3x_self_free(self); lob_free(id); return 0; }
// this will set the default inactivity timeout using this event timer and our uid uint32_t e3x_channel_timeout(e3x_channel_t c, e3x_event_t ev, uint32_t timeout) { if(!c) return 0; // un-set any if(ev != c->ev) { // cancel and clearn any previous timer state c->timeout = 0; if(c->ev) e3x_event_set(c->ev,NULL,c->uid,0); c->ev = NULL; lob_free(c->timer); c->timer = NULL; } // no event manager, no timeouts if(!ev) return 0; // no timeout, just return how much time is left if(!timeout) return _time_left(c); // add/update new timeout c->tsince = util_sys_seconds(); // start timer now c->timeout = timeout; c->ev = ev; c->timer = lob_new(); lob_set_uint(c->timer,"c",c->id); lob_set(c->timer, "id", c->uid); lob_set(c->timer, "err", "timeout"); e3x_event_set(c->ev, c->timer, lob_get(c->timer, "id"), timeout*1000); // ms in the future return _time_left(c); }
// deliver this packet link_t link_send(link_t link, lob_t outer) { if(!outer) return LOG_INFO("send packet missing"); if(!link || !link->send_cb) { lob_free(outer); return LOG_WARN("no network"); } if(!link->send_cb(link, outer, link->send_arg)) { lob_free(outer); return LOG_WARN("delivery failed"); } return link; }
void net_tcp4_free(net_tcp4_t net) { if(!net) return; close(net->server); xht_free(net->pipes); lob_free(net->path); free(net); return; }
// creates a new mesh identity, returns secrets lob_t mesh_generate(mesh_t mesh) { lob_t secrets; if(!mesh || mesh->self) return LOG_ERROR("invalid mesh"); secrets = e3x_generate(); if(!secrets) return LOG_ERROR("failed to generate %s",e3x_err()); if(mesh_load(mesh, secrets, lob_linked(secrets))) return lob_free(secrets); return secrets; }
void pair_send(pipe_t pipe, lob_t packet, link_t link) { net_loopback_t pair = (net_loopback_t)pipe->arg; if(!pair || !packet || !link) return; LOG("pair pipe from %s",link->id->hashname); if(link->mesh == pair->a) mesh_receive(pair->b,packet,pipe); else if(link->mesh == pair->b) mesh_receive(pair->a,packet,pipe); else lob_free(packet); }
int main(int argc, char **argv) { lob_t opts = lob_new(); fail_unless(e3x_init(opts) == 0); fail_unless(!lob_get(opts,"err")); fail_unless(!e3x_err()); lob_free(opts); return 0; }
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 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; }
// return >0 if this alg is supported uint8_t jwt_alg(char *alg) { uint8_t err; lob_t test = lob_new(); lob_set(test,"alg",alg); // TODO refactor how this is checked err = e3x_exchange_validate(NULL, test, NULL, (uint8_t*)"x", 1); lob_free(test); return (err == 1) ? 0 : 1; }
pipe_t pipe_free(pipe_t p) { free(p->type); if(p->id) free(p->id); if(p->path) lob_free(p->path); if(p->notify) LOG("pipe free'd leaking notifications"); if(p->free) p->free(p); free(p); return NULL; }
mesh_t util_links(mesh_t mesh, char *file) { lob_t links = util_fjson(file); if(!links) return NULL; // TODO iterate and link lob_free(links); return mesh; }
hashname_t hashname_keys(lob_t keys) { hashname_t hn; lob_t im; if(!keys) return LOG("bad args"); im = hashname_im(keys,0); hn = hashname_key(im,0); lob_free(im); return hn; }
// process a decrypted channel packet link_t link_receive(link_t link, lob_t inner) { chan_t c; if(!link || !inner) return LOG("bad args"); LOG("<-- %d",lob_get_int(inner,"c")); // see if existing channel and send there if((c = link_chan_get(link, lob_get_int(inner,"c")))) { LOG("found chan"); // consume inner chan_receive(c, inner); // process any changes link->chans = link_process_chan(link->chans, 0); return link; } // if it's an open, validate and fire event if(!lob_get(inner,"type")) { LOG("invalid channel open, no type %s",lob_json(inner)); lob_free(inner); return NULL; } if(!e3x_exchange_cid(link->x, inner)) { LOG("invalid channel open id %s",lob_json(inner)); lob_free(inner); return NULL; } inner = mesh_open(link->mesh,link,inner); if(inner) { LOG("unhandled channel open %s",lob_json(inner)); lob_free(inner); return NULL; } return link; }
void e3x_channel_free(e3x_channel_t c) { if(!c) return; // cancel timeouts e3x_channel_timeout(c,NULL,0); // free cached packet lob_free(c->open); // free any other queued packets lob_freeall(c->in); lob_freeall(c->out); free(c); }
link_t link_get_keys(mesh_t mesh, lob_t keys) { uint8_t csid; if(!mesh || !keys) return LOG("invalid args"); csid = hashname_id(mesh->keys,keys); if(!csid) return LOG("no supported key"); lob_t key = hashname_im(keys,csid); link_t ret = link_get_key(mesh, key, csid); lob_free(key); return ret; }