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; }
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; }
// verify the signature on this token, optionally using key loaded in this exchange lob_t jwt_verify(lob_t token, e3x_exchange_t x) { size_t hlen, clen; char *encoded; uint8_t err; lob_t payload = lob_linked(token); if(!token || !payload) return LOG("bad args"); // generate the temporary encoded data clen = base64_encode_length(payload->head_len); hlen = base64_encode_length(token->head_len); encoded = (char*)malloc(hlen+1+clen); hlen = base64_encoder(token->head,token->head_len,encoded); encoded[hlen] = '.'; clen = base64_encoder(payload->head,payload->head_len,encoded+hlen+1); // do the validation err = e3x_exchange_validate(x, token, payload, (uint8_t*)encoded, hlen+1+clen); free(encoded); if(err) { LOG("validate failed: %d",err); return NULL; } return token; }
// 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; }
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; }
// 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; }
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; }
// 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 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; }
// 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 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; }
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,"1a"); if(!key || key->body_len != uECC_BYTES+1) return LOG("invalid key %d != %d",(key)?key->body_len:0,uECC_BYTES+1); secret = lob_get_base32(secrets,"1a"); if(!secret || secret->body_len != uECC_BYTES) return LOG("invalid secret len %d",(secret)?secret->body_len:0); if(!(local = malloc(sizeof(struct local_struct)))) return NULL; memset(local,0,sizeof (struct local_struct)); // copy in key/secret data uECC_decompress(key->body,local->key); memcpy(local->secret,secret->body,secret->body_len); lob_free(key); lob_free(secret); return local; }
// returns the base64 encoded token from a packet (return is cached/freed inside token) char *jwt_encode(lob_t token) { size_t hlen, clen, slen; char *encoded; lob_t payload = lob_linked(token); if(!payload) return NULL; // allocates space in the token body slen = base64_encode_length(payload->body_len); clen = base64_encode_length(payload->head_len); hlen = base64_encode_length(token->head_len); encoded = (char*)lob_body(token,NULL,hlen+1+clen+1+slen+1); // append all the base64 encoding hlen = base64_encoder(token->head,token->head_len,encoded); encoded[hlen] = '.'; clen = base64_encoder(payload->head,payload->head_len,encoded+hlen+1); encoded[hlen+1+clen] = '.'; slen = base64_encoder(payload->body,payload->body_len,encoded+hlen+1+clen+1); encoded[hlen+1+clen+1+slen] = 0; return encoded; }
// sign this token, adds signature to the claims body lob_t jwt_sign(lob_t token, e3x_self_t self) { size_t hlen, clen; char *encoded; lob_t payload = lob_linked(token); if(!token || !payload) return LOG("bad args"); // allocates space in the payload body for the encoded data to be signed clen = base64_encode_length(payload->head_len); hlen = base64_encode_length(token->head_len); encoded = (char*)lob_body(payload,NULL,hlen+1+clen); hlen = base64_encoder(token->head,token->head_len,encoded); encoded[hlen] = '.'; clen = base64_encoder(payload->head,payload->head_len,encoded+hlen+1); // e3x returns packet w/ signature if(!e3x_self_sign(self, token, payload->body, hlen+1+clen)) return LOG("signing failed"); // copy sig to payload lob_body(payload,token->body,token->body_len); return token; }
// generate an outgoing request, send the response attached to the note chan_t thtp_req(switch_t s, lob_t note) { char *uri, *path, *method; hashname_t to = NULL; lob_t req; chan_t c; if(!s || !note) return NULL; method = lob_get_str(note,"method"); path = lob_get_str(note,"path"); if((uri = lob_get_str(note,"uri")) && strncmp(uri,"thtp://",7) == 0) { uri += 7; path = strchr(uri,'/'); to = hashname_gethex(s->index,uri); } if(!to) to = hashname_gethex(s->index,lob_get_str(note,"to")); if(!to) return NULL; req = lob_linked(note); if(!req) { req = lob_chain(note); lob_set_str(req,"path",path?path:"/"); lob_set_str(req,"method",method?method:"get"); } DEBUG_PRINTF("thtp req %s %s %s %.*s",lob_get_str(req,"method"),lob_get_str(req,"path"),to->hexname,note->json_len,note->json); // open channel and send req c = chan_new(s, to, "thtp", 0); c->arg = lob_link(NULL,note); // create buffer packet w/ the note linked c->handler = ext_thtp; // shortcut chan_reliable(c,10); thtp_send(c,req); return c; }
// make sure all pipes have the current handshake lob_t link_sync(link_t link) { uint32_t at; seen_t seen; lob_t handshakes = NULL, hs = NULL; if(!link) return LOG("bad args"); if(!link->x) return LOG("no exchange"); at = e3x_exchange_out(link->x,0); LOG("link sync at %d",at); for(seen = link->pipes;seen;seen = seen->next) { if(!seen->pipe || !seen->pipe->send || seen->at == at) continue; // only create if we have to if(!handshakes) handshakes = link_handshakes(link); seen->at = at; for(hs = handshakes; hs; hs = lob_linked(hs)) seen->pipe->send(seen->pipe, lob_copy(hs), link); } // caller can re-use and must free return handshakes; }
// just returns the token->chain claims claim lob_t jwt_claims(lob_t token) { return lob_linked(token); }
// 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"); }
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); }
// 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; }