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; }
// 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); }
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; }
lob_t util_fjson(char *file) { unsigned char *buf; size_t len; struct stat fs; FILE *fd; lob_t p; fd = fopen(file,"rb"); if(!fd) return LOG("fopen error %s: %s",file,strerror(errno)); if(fstat(fileno(fd),&fs) < 0) { fclose(fd); return LOG("fstat error %s: %s",file,strerror(errno)); } if(!(buf = malloc((size_t)fs.st_size))) { fclose(fd); return LOG("OOM"); } len = fread(buf,1,(size_t)fs.st_size,fd); fclose(fd); if(len != (size_t)fs.st_size) { free(buf); return LOG("fread %d != %d for %s: %s",len,fs.st_size,file,strerror(errno)); } p = lob_new(); lob_head(p, buf, len); if(!p) LOG("json failed %s parsing %.*s",file,len,buf); free(buf); return p; }
// decode base64 into the pair of lob packets lob_t jwt_decode(char *encoded, size_t len) { lob_t header, claims; char *dot1, *dot2, *end; size_t dlen; if(!encoded) return NULL; if(!len) len = strlen(encoded); end = encoded+(len-1); // make sure the dot separators are there dot1 = strchr(encoded,'.'); if(!dot1 || dot1+1 >= end) return LOG("missing/bad first separator"); dot1++; dot2 = strchr(dot1,'.'); if(!dot2 || (dot2+1) >= end) return LOG("missing/bad second separator"); dot2++; claims = lob_new(); header = lob_link(NULL, claims); // decode claims sig first dlen = base64_decoder(dot2, (end-dot2)+1, (uint8_t*)dot2); lob_body(claims, (uint8_t*)dot2, dlen); // decode claims json dlen = base64_decoder(dot1, (dot2-dot1), (uint8_t*)dot1); lob_head(claims, (uint8_t*)dot1, dlen); // decode header json dlen = base64_decoder(encoded, (dot1-encoded), (uint8_t*)encoded); lob_head(header, (uint8_t*)encoded, dlen); return header; }
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; }
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 util_uri_add_path(lob_t uri, lob_t path) { lob_t keys; lob_t query = lob_linked(uri); if(!uri || !path) return NULL; if(!query) { if(!(query = lob_new())) return LOG("OOM"); lob_link(uri, query); } // encode and add to chain after query if(!(keys = lob_new())) return LOG("OOM"); lob_set_base32(keys,"paths",path->head,path->head_len); lob_link(keys, lob_linked(query)); lob_link(query, keys); return uri; }
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; }
int main(int argc, char **argv) { hashname_t hn; fail_unless(e3x_init(NULL) == 0); hn = hashname_vchar("uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g"); fail_unless(!hn); hn = hashname_vchar("jvdoio6kjvf3yqnxfvck43twaibbg4pmb7y3mqnvxafb26rqllwa"); fail_unless(hn); fail_unless(strlen(hashname_char(hn)) == 52); hashname_free(hn); // create intermediate fixture lob_t im = lob_new(); lob_set(im,"1a","ym7p66flpzyncnwkzxv2qk5dtosgnnstgfhw6xj2wvbvm7oz5oaq"); lob_set(im,"3a","bmxelsxgecormqjlnati6chxqua7wzipxliw5le35ifwxlge2zva"); hn = hashname_vkey(im, 0); fail_unless(hn); fail_unless(util_cmp(hashname_char(hn),"jvdoio6kjvf3yqnxfvck43twaibbg4pmb7y3mqnvxafb26rqllwa") == 0); lob_t keys = lob_new(); lob_set(keys,"1a","vgjz3yjb6cevxjomdleilmzasbj6lcc7"); lob_set(keys,"3a","hp6yglmmqwcbw5hno37uauh6fn6dx5oj7s5vtapaifrur2jv6zha"); hn = hashname_vkeys(keys); fail_unless(hn); fail_unless(util_cmp(hashname_char(hn),"jvdoio6kjvf3yqnxfvck43twaibbg4pmb7y3mqnvxafb26rqllwa") == 0); fail_unless(hashname_id(NULL,NULL) == 0); fail_unless(hashname_id(im,keys) == 0x3a); lob_t test = lob_new(); lob_set(test,"1a","test"); lob_set(test,"2a","test"); fail_unless(hashname_id(keys,test) == 0x1a); // check short utils hn = hashname_schar("uvabrvfq"); fail_unless(hn); fail_unless(hashname_isshort(hn)); fail_unless(util_cmp(hashname_short(hn),"uvabrvfq") == 0); return 0; }
// 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; }
// 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; }
// generate json of mesh keys and current paths lob_t mesh_json(mesh_t mesh) { lob_t json, paths; if(!mesh) return LOG_ERROR("bad args"); json = lob_new(); lob_set(json,"hashname",hashname_char(mesh->id)); lob_set_raw(json,"keys",0,(char*)mesh->keys->head,mesh->keys->head_len); paths = lob_array(mesh->paths); lob_set_raw(json,"paths",0,(char*)paths->head,paths->head_len); lob_free(paths); return json; }
// 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; }
// get keys from query lob_t util_uri_keys(lob_t uri) { uint32_t i; char *key, *value; lob_t keys, query = lob_linked(uri); if(!query) return NULL; keys = lob_new(); // loop through all keyval pairs to find cs** for(i=0;(key = lob_get_index(query,i));i+=2) { value = lob_get_index(query,i+1); if(strlen(key) != 4 || strncmp(key,"cs",2) != 0 || !value) continue; // skip non-csid keys lob_set_len(keys,key+2,2,value,strlen(value)); } return keys; }
net_tcp4_t net_tcp4_new(mesh_t mesh, lob_t options) { int port, sock, pipes, opt = 1; net_tcp4_t net; struct sockaddr_in sa; socklen_t size = sizeof(struct sockaddr_in); port = lob_get_int(options,"port"); pipes = lob_get_int(options,"pipes"); if(!pipes) pipes = 11; // hashtable for active pipes // create a udp socket if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) ) < 0 ) return LOG("failed to create socket %s",strerror(errno)); memset(&sa,0,sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sock, (struct sockaddr*)&sa, size) < 0) return LOG("bind failed %s",strerror(errno)); getsockname(sock, (struct sockaddr*)&sa, &size); if(listen(sock, 10) < 0) return LOG("listen failed %s",strerror(errno)); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt , sizeof(int)); fcntl(sock, F_SETFL, O_NONBLOCK); if(!(net = malloc(sizeof (struct net_tcp4_struct)))) return LOG("OOM"); memset(net,0,sizeof (struct net_tcp4_struct)); net->server = sock; net->port = ntohs(sa.sin_port); net->pipes = xht_new(pipes); // connect us to this mesh net->mesh = mesh; xht_set(mesh->index, MUID, net); mesh_on_path(mesh, MUID, tcp4_path); // convenience net->path = lob_new(); lob_set(net->path,"type","tcp4"); lob_set(net->path,"ip","127.0.0.1"); lob_set_int(net->path,"port",net->port); return net; }
lob_t util_uri_add_keys(lob_t uri, lob_t keys) { uint32_t i; char *key, *value, csid[5]; lob_t query = lob_linked(uri); if(!uri || !keys) return NULL; if(!query) { query = lob_new(); lob_link(uri, query); } for(i=0;(key = lob_get_index(keys,i));i+=2) { value = lob_get_index(keys,i+1); if(strlen(key) != 2 || !value) continue; // paranoid snprintf(csid,5,"cs%s",key); lob_set(query,csid,value); } return uri; }
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; }
// decode base64 into the pair of lob packets lob_t jwt_decode(char *encoded, size_t len) { lob_t header, claims; char *dot1, *dot2, *end; if(!encoded) return NULL; if(!len) len = strlen(encoded); end = encoded+len; // make sure the dot separators are there dot1 = memchr(encoded,'.',(end-encoded)); if(!dot1) return LOG_INFO("missing/bad first separator"); dot1++; dot2 = memchr(dot1,'.',(end-dot1)); if(!dot2) return LOG_INFO("missing/bad second separator"); dot2++; // quick sanity check of the base64 if(!base64_decoder(dot2, (end-dot2), NULL)) return LOG_INFO("invalid sig base64: %.*s",(end-dot2),dot2); if(!base64_decoder(dot1, (dot2-dot1)-1, NULL)) return LOG_INFO("invalid claims base64: %.*s",(dot2-dot1)-1,dot1); if(!base64_decoder(encoded, (dot1-encoded)-1, NULL)) return LOG_INFO("invalid header b64: %.*s",(dot1-encoded)-1,encoded); claims = lob_new(); header = lob_link(NULL, claims); // decode claims json lob_head(claims, NULL, base64_decoder(dot1, (dot2-dot1)-1, NULL)); base64_decoder(dot1, (dot2-dot1)-1, lob_head_get(claims)); // decode claims sig lob_body(claims, NULL, base64_decoder(dot2, (end-dot2), NULL)); base64_decoder(dot2, (end-dot2), lob_body_get(claims)); // decode header json lob_head(header, NULL, base64_decoder(encoded, (dot1-encoded)-1, NULL)); base64_decoder(encoded, (dot1-encoded)-1, lob_head_get(header)); return header; }
// 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; }
// 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; }
// just a convenience, generates handshake w/ current e3x_exchange_at value lob_t e3x_exchange_handshake(e3x_exchange_t x, lob_t inner) { lob_t tmp; uint8_t i; uint8_t local = 0; if(!x) return LOG("invalid args"); if(!x->out) return LOG("no out set"); // create deprecated key handshake inner from all supported csets if(!inner) { local = 1; inner = lob_new(); lob_set(inner, "type", "key"); // loop through all ciphersets for any keys for(i=0; i<CS_MAX; i++) { if(!(tmp = x->self->keys[i])) continue; // this csid's key is the body, rest is intermediate in json if(e3x_cipher_sets[i] == x->cs) { lob_body(inner,tmp->body,tmp->body_len); }else{ uint8_t hash[32]; e3x_hash(tmp->body,tmp->body_len,hash); lob_set_base32(inner,e3x_cipher_sets[i]->hex,hash,32); } } } // set standard values lob_set_uint(inner,"at",x->out); tmp = e3x_exchange_message(x, inner); if(!local) return tmp; return lob_link(tmp, inner); }
int main(int argc, char *argv[]) { lob_t id, options, json; mesh_t mesh; net_udp4_t udp4; net_tcp4_t tcp4; int port = 0; if(argc==2) { port = atoi(argv[1]); } id = util_fjson("id.json"); if(!id) return -1; mesh = mesh_new(0); mesh_load(mesh,lob_get_json(id,"secrets"),lob_get_json(id,"keys")); mesh_on_discover(mesh,"auto",mesh_add); // auto-link anyone mesh_on_open(mesh,"path",path_on_open); // add path support options = lob_new(); lob_set_int(options,"port",port); udp4 = net_udp4_new(mesh, options); util_sock_timeout(udp4->server,100); tcp4 = net_tcp4_new(mesh, options); json = mesh_json(mesh); printf("%s\n",lob_json(json)); printf("%s\n",mesh_uri(mesh, NULL)); while(net_udp4_receive(udp4) && net_tcp4_loop(tcp4)); /* if(util_loadjson(s) != 0 || (sock = util_server(0,1000)) <= 0) { printf("failed to startup %s or %s\n", strerror(errno), crypt_err()); return -1; } printf("loaded hashname %s\n",s->id->hexname); // create/send a ping packet c = chan_new(s, bucket_get(s->seeds, 0), "link", 0); p = chan_packet(c); chan_send(c, p); util_sendall(s,sock); in = path_new("ipv4"); while(util_readone(s, sock, in) == 0) { switch_loop(s); while((c = switch_pop(s))) { printf("channel active %d %s %s\n",c->ended,c->hexid,c->to->hexname); if(util_cmp(c->type,"connect") == 0) ext_connect(c); if(util_cmp(c->type,"link") == 0) ext_link(c); if(util_cmp(c->type,"path") == 0) ext_path(c); while((p = chan_pop(c))) { printf("unhandled channel packet %.*s\n", p->json_len, p->json); lob_free(p); } } util_sendall(s,sock); } */ perror("exiting"); return 0; }
// 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; }
// serialize out from lob format to "uri" key and return it char *util_uri_format(lob_t uri) { char *part, *key, *value; uint32_t i, prev = 0; lob_t buf, query; if(!uri) return NULL; // use a lob body as the buffer to build it up buf = lob_new(); part = lob_get(uri, "protocol"); if(part) { lob_append_str(buf, part); }else{ lob_append_str(buf, "link"); } lob_append_str(buf, "://"); part = lob_get(uri, "hostname"); if(part) { lob_append_str(buf, part); part = lob_get(uri, "port"); if(part) { lob_append_str(buf, ":"); lob_append_str(buf, part); } }else{ part = lob_get(uri, "host"); if(part) lob_append_str(buf, part); } part = lob_get(uri, "path"); if(part) { lob_append_str(buf, part); }else{ lob_append_str(buf, "/"); } // append on any query string for(query = lob_linked(uri); query; query = lob_linked(query)) { for(i=0;(key = lob_get_index(query,i));i+=2) { value = lob_get_index(query,i+1); if(!strlen(key) || !value) continue; // paranoid lob_append_str(buf,(prev++)?"&":"?"); lob_append_str(buf,key); lob_append_str(buf,"="); lob_append_str(buf,value); } } if((part = lob_get(uri, "hash"))) { lob_append_str(buf, "#"); lob_append_str(buf, part); } lob_set_len(uri,"uri",3,(char*)buf->body,buf->body_len); lob_free(buf); return lob_get(uri,"uri"); }
// this is a very simple single-pass telehash uri parser, no magic lob_t util_uri_parse(char *encoded) { size_t klen, vlen; char *at, *val; lob_t uri; if(!encoded) return LOG("bad args"); uri = lob_new(); lob_set(uri,"orig",encoded); // check for protocol:// prefix first if(!(at = strstr(encoded,"://"))) { lob_set(uri, "protocol", "link"); }else{ lob_set_len(uri, "protocol", 0, encoded, (size_t)(at - encoded)); encoded = at+3; } // check for user@ prefix next if((at = strchr(encoded,'@'))) { lob_set_len(uri, "auth", 0, encoded, (size_t)(at - encoded)); encoded = at+1; } // ensure there's at least a host if(!strlen(encoded) || !isalnum((int)encoded[0])) { lob_free(uri); return LOG("invalid host: '%s'",encoded); } // copy in host and parse hostname/port if((at = strchr(encoded,'/')) || (at = strchr(encoded,'?')) || (at = strchr(encoded,'#'))) { lob_set_len(uri, "host", 0, encoded, (size_t)(at - encoded)); }else{ lob_set_len(uri, "host", 0, encoded, strlen(encoded)); } // hostname+port val = lob_get(uri, "host"); if(val && (at = strchr(val,':'))) { lob_set_len(uri, "hostname", 0, val, (size_t)(at - val)); lob_set_uint(uri, "port", (uint16_t)strtoul(at+1,NULL,10)); }else{ lob_set(uri, "hostname", val); } // optional path if((at = strchr(encoded,'/'))) { encoded = at; if((at = strchr(encoded+1,'?')) || (at = strchr(encoded+1,'#'))) { lob_set_len(uri, "path", 0, encoded, (size_t)(at - encoded)); }else{ lob_set_len(uri, "path", 0, encoded, strlen(encoded)); } } // optional hash at the end if((at = strchr(encoded,'#'))) { lob_set_len(uri, "hash", 0, at+1, strlen(at+1)); } // optional query string if((at = strchr(encoded,'?'))) { uri->chain = lob_new(); encoded = at+1; if((at = strchr(encoded,'#'))) { klen = (size_t)(at - encoded); }else{ klen = strlen(encoded); } while(klen) { // skip any separator if(*encoded == '&') { encoded++; klen--; } // require the equals if(!(val = strchr(encoded,'='))) break; val++; if((at = strchr(val,'&'))) { vlen = (size_t)(at - val); }else{ vlen = strlen(val); } lob_set_len(uri->chain, encoded, (size_t)(val-encoded)-1, val, vlen); // skip past whole block klen -= (size_t)((val+vlen) - encoded); encoded = val + vlen; } } return uri; }
int main(int argc, char **argv) { fail_unless(!e3x_init(NULL)); // random seed mesh_t meshA = mesh_new(); fail_unless(meshA); lob_t keyA = lob_new(); lob_set(keyA,"1a",A_KEY); lob_t secA = lob_new(); lob_set(secA,"1a",A_SEC); fail_unless(!mesh_load(meshA,secA,keyA)); mesh_on_discover(meshA,"auto",mesh_add); lob_t keyB = lob_new(); lob_set(keyB,"1a",B_KEY); hashname_t hnB = hashname_vkeys(keyB); fail_unless(hnB); link_t linkAB = link_get(meshA,hnB); fail_unless(linkAB); netA = tmesh_new(meshA, "test", NULL); fail_unless(netA); netA->sort = driver_sort; netA->schedule = driver_schedule; netA->advance = driver_advance; netA->medium = driver_medium; netA->free = driver_free; fail_unless(netA->knock); fail_unless(strcmp(netA->community,"test") == 0); // create outgoing beacon fail_unless(tmesh_schedule(netA,1)); fail_unless(netA->beacon); fail_unless(!netA->beacon->frames); fail_unless(!netA->beacon->mote); fail_unless(netA->beacon->medium == 1); // should have schedule a beacon rx fail_unless(scheduled == 1); fail_unless(netA->knock->is_active); fail_unless(netA->knock->tempo == netA->beacon); fail_unless(netA->knock->tempo->at == 2); fail_unless(netA->knock->tempo->chan == 1); mote_t moteB = tmesh_mote(netA, linkAB); fail_unless(moteB); fail_unless(moteB->link == linkAB); fail_unless(moteB->signal); fail_unless(moteB->signal->medium == 1); fail_unless(moteB->signal->driver == (void*)1); /* cmnty_t c = tmesh_join(netA,"qzjb5f4t","foo"); fail_unless(c); fail_unless(c->medium->bin[0] == 134); fail_unless(c->medium->radio == devA->id); fail_unless(c->beacons == NULL); fail_unless(c->pipe->path); LOG("netA %.*s",c->pipe->path->head_len,c->pipe->path->head); fail_unless(tmesh_leave(netA,c)); char hex[256]; netA->last = 1; fail_unless(!tmesh_join(netA, "azdhpa5r", NULL)); fail_unless((c = tmesh_join(netA, "azdhpa5n", "Public"))); fail_unless(c->beacons); fail_unless(c->beacons->public); mote_t m = c->beacons; mote_t mpub = m; memset(m->nonce,42,8); // nonce is random, force stable for fixture testing LOG("nonce %s",util_hex(m->nonce,8,hex)); fail_unless(util_cmp(hex,"2a2a2a2a2a2a2a2a") == 0); LOG("public at is now %lu",c->beacons->at); m = tmesh_link(netA, c, link); fail_unless(m); fail_unless(m->link == link); fail_unless(m == tmesh_link(netA, c, link)); fail_unless(m->order == 0); memset(m->nonce,3,8); // nonce is random, force stable for fixture testing LOG("secret %s",util_hex(m->secret,32,hex)); fail_unless(util_cmp(hex,"b7bc9e4f1f128f49a3bcef321450b996600987b129723cc7ae752d6500883c65") == 0); LOG("public at is now %lu",mpub->at); fail_unless(mote_reset(m)); memset(m->nonce,0,8); // nonce is random, force stable for fixture testing knock_t knock = devA->knock; fail_unless(mote_advance(m)); LOG("next is %lld",m->at); fail_unless(m->at == 8369); fail_unless(mote_knock(m,knock)); fail_unless(!knock->tx); fail_unless(mote_advance(m)); fail_unless(mote_advance(m)); fail_unless(mote_advance(m)); fail_unless(mote_advance(m)); fail_unless(mote_knock(m,knock)); fail_unless(knock->tx); LOG("next is %lld",knock->start); fail_unless(knock->start == 29905); LOG("public at is now %lu",mpub->at); fail_unless(mpub->at == 1); mpub->at = 5; mote_t bm = tmesh_seek(netA,c,link->id); memset(bm->nonce,2,8); // nonce is random, force stable for fixture testing mote_reset(m); memset(m->nonce,2,8); // nonce is random, force stable for fixture testing m->at = 10; fail_unless(tmesh_process(netA,1,1)); fail_unless(m->at == 9); fail_unless(knock->mote == mpub); LOG("tx %d start %lld stop %lld chan %d at %lld",knock->tx,knock->start,knock->stop,knock->chan,m->at); fail_unless(!knock->tx); fail_unless(knock->start == 4); fail_unless(knock->stop == 4+1000); fail_unless(knock->chan == 27); // fail_unless(tmesh_knocked(netA,knock)); LOG("public at is now %lu",c->beacons->at); fail_unless(mote_advance(m)); LOG("seek %s",util_hex(m->nonce,8,hex)); fail_unless(util_cmp(hex,"dc09a8ca7f5cb75e") == 0); fail_unless(mote_advance(m)); LOG("at is %lu",m->at); fail_unless(m->at >= 6037); // public ping now // m->at = 0xffffffff; memset(knock,0,sizeof(struct knock_struct)); fail_unless(tmesh_process(netA,8050,0)); LOG("beacon at is now %lu",bm->at); fail_unless(knock->mote == bm); LOG("tx %d start %lld stop %lld chan %d",knock->tx,knock->start,knock->stop,knock->chan); fail_unless(!knock->tx); fail_unless(knock->start == 9040); fail_unless(knock->stop == 9040+1000); fail_unless(knock->chan == 19); // TEMP DISABLED return 0; // public ping tx memset(m->nonce,4,8); // fixture for testing m->order = 1; memset(knock,0,sizeof(struct knock_struct)); LOG("public at is now %lu",m->at); fail_unless(tmesh_process(netA,20000,0)); fail_unless(knock->mote == bm); LOG("tx %d start %lld stop %lld chan %d",knock->tx,knock->start,knock->stop,knock->chan); fail_unless(knock->ready); fail_unless(knock->tx); fail_unless(knock->start == 21128); fail_unless(knock->chan == 19); // frame would be random ciphered, but we fixed it to test LOG("frame %s",util_hex(knock->frame,32+8,hex)); // just the stable part fail_unless(util_cmp(hex,"6c265ac8d9a533a1bc7c7f49ed83ae5d32d31b4b9b76c485b182d649c91deb08a160aab63ee8212c") == 0); // let's preted it's an rx now knock->tx = 0; knock->stopped = knock->stop; // fake rx good LOG("faking rx in"); fail_unless(!tmesh_knocked(netA,knock)); // identity crisis fail_unless(tmesh_process(netA,42424,0)); LOG("tx %d start %lld stop %lld chan %d",knock->tx,knock->start,knock->stop,knock->chan); fail_unless(knock->stopped); fail_unless(knock->start == 43444); // leave public community fail_unless(tmesh_leave(netA,c)); // two motes meshing mesh_t meshB = mesh_new(); fail_unless(meshB); lob_t secB = lob_new(); lob_set(secB,"1a",B_SEC); fail_unless(!mesh_load(meshB,secB,keyB)); mesh_on_discover(meshB,"auto",mesh_add); hashname_t hnA = hashname_vkeys(keyA); fail_unless(hnA); link_t linkBA = link_get(meshB,hnA); fail_unless(linkBA); netB = tmesh_new(meshB, NULL); fail_unless(netB); cmnty_t cB = tmesh_join(netB,"qzjb5f4t","test"); fail_unless(cB); fail_unless(cB->pipe->path); LOG("netB %s",lob_json(cB->pipe->path)); cmnty_t cA = tmesh_join(netA,"qzjb5f4t","test"); fail_unless(cA); fail_unless(cA->pipe->path); LOG("netA %s",lob_json(cA->pipe->path)); mote_t bmBA = tmesh_seek(netB, cB, linkBA->id); fail_unless(bmBA); fail_unless(bmBA->order == 1); LOG("bmBA %s secret %s",hashname_short(bmBA->beacon),util_hex(bmBA->secret,32,hex)); fail_unless(util_cmp(hex,"9a972d28dcc211d43eafdca7877bed1bbeaec30fd3740f4b787355d10423ad12") == 0); mote_t mBA = tmesh_link(netB, cB, linkBA); fail_unless(mBA); fail_unless(mBA->order == 1); LOG("mBA %s secret %s",hashname_short(mBA->link->id),util_hex(mBA->secret,32,hex)); fail_unless(util_cmp(hex,"9a972d28dcc211d43eafdca7877bed1bbeaec30fd3740f4b787355d10423ad12") == 0); mote_t bmAB = tmesh_seek(netA, cA, link->id); fail_unless(bmAB); fail_unless(bmAB->order == 0); LOG("bmBA %s secret %s",hashname_short(bmAB->beacon),util_hex(bmAB->secret,32,hex)); fail_unless(util_cmp(hex,"9a972d28dcc211d43eafdca7877bed1bbeaec30fd3740f4b787355d10423ad12") == 0); mote_t mAB = tmesh_link(netA, cA, link); fail_unless(mAB); fail_unless(mAB->order == 0); LOG("mAB %s secret %s",hashname_short(mAB->link->id),util_hex(mAB->secret,32,hex)); fail_unless(util_cmp(hex,"9a972d28dcc211d43eafdca7877bed1bbeaec30fd3740f4b787355d10423ad12") == 0); knock_t knBA = devB->knock; knBA->ready = 0; memset(mBA->nonce,0,8); memset(bmBA->nonce,2,8); fail_unless(tmesh_process(netB,10,0)); fail_unless(knBA->mote == bmBA); LOG("BA tx is %d chan %d at %lu",knBA->tx,knBA->chan,knBA->start); fail_unless(knBA->chan == 35); fail_unless(knBA->tx == 0); knock_t knAB = devA->knock; knAB->ready = 0; memset(mAB->nonce,10,8); memset(bmAB->nonce,15,8); fail_unless(tmesh_process(netA,44444,0)); fail_unless(knAB->mote == bmAB); LOG("AB tx is %d chan %d at %lu nonce %s",knAB->tx,knAB->chan,knAB->start,util_hex(mAB->nonce,8,NULL)); fail_unless(knAB->chan == 35); fail_unless(knAB->tx == 1); // fake reception, with fake cake LOG("process netA"); RXTX(knAB,knBA); fail_unless(tmesh_knocked(netA,knAB)); fail_unless(tmesh_process(netA,67689,0)); fail_unless(knAB->mote == bmAB); fail_unless(!bmAB->chunks); LOG("process netB"); RXTX(knAB,knBA); fail_unless(tmesh_knocked(netB,knBA)); fail_unless(tmesh_process(netB,22000,0)); // dummy data for sync send netA->pubim = hashname_im(netA->mesh->keys, hashname_id(netA->mesh->keys,netA->mesh->keys)); netB->pubim = hashname_im(netB->mesh->keys, hashname_id(netB->mesh->keys,netB->mesh->keys)); // back to the future RXTX(knAB,knBA); fail_unless(tmesh_knocked(netA,knAB)); LOG("mAB %lu mBA %lu",mAB->at,mBA->at); while(knBA->mote != bmBA) { knBA->ready = 0; fail_unless(tmesh_process(netB,knBA->stop,0)); } LOG("BA tx is %d chan %d at %lu nonce %s",knBA->tx,knBA->chan,knAB->start,util_hex(mBA->nonce,8,NULL)); // fail_unless(knBA->tx == 1); RXTX(knAB,knBA); LOG("mAB %lu mBA %lu",mAB->at,mBA->at); fail_unless(tmesh_knocked(netB,knBA)); while(knAB->mote != bmAB) { knAB->ready = 0; fail_unless(tmesh_process(netA,knAB->stop,0)); } LOG("AB tx is %d chan %d at %lu nonce %s",knAB->tx,knAB->chan,knAB->start,util_hex(mAB->nonce,8,NULL)); // fail_unless(knAB->tx == 0); // in sync! fail_unless(!mBA->chunks); fail_unless(!mAB->chunks); mAB->at = mBA->at; LOG("mAB %lu mBA %lu",mAB->at,mBA->at); fail_unless(mAB->at == mBA->at); // continue establishing link printf("\n\n"); int max = 40; mote_reset(mAB); mote_reset(mBA); mote_reset(bmAB); mote_reset(bmBA); mAB->at = mBA->at; bmAB->at = bmBA->at; uint32_t step = mAB->at; while(--max > 0 && !link_up(mBA->link) && !link_up(mAB->link)) { printf("\n\n%d %u\n",max,step); tmesh_process(netA,step,0); tmesh_process(netB,step,0); LOG("AB %d %d/%d BA %d %d/%d",knAB->tx,knAB->start,knAB->stop,knBA->tx,knBA->start,knBA->stop); if(knAB->stop > step) step = knAB->stop; if(knBA->stop > step) step = knBA->stop; if(knAB->chan == knBA->chan) { printf("~~~~RXTX %u\n",step); RXTX(knAB,knBA); knAB->stopped = knAB->stop; knBA->stopped = knBA->stop; }else{ knAB->err = knBA->err = 1; } tmesh_knocked(netA,knAB); tmesh_knocked(netB,knBA); } LOG("TODO linked by %d",max); // fail_unless(max); */ return 0; }
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; }