ephemeral_t ephemeral_new(remote_t remote, lob_t outer) { uint8_t ekey[uECC_BYTES*2], shared[uECC_BYTES+((uECC_BYTES+1)*2)], hash[32]; ephemeral_t ephem; if(!remote) return NULL; if(!outer || outer->body_len < (uECC_BYTES+1)) return LOG("invalid outer"); if(!(ephem = malloc(sizeof(struct ephemeral_struct)))) return NULL; memset(ephem,0,sizeof (struct ephemeral_struct)); // create and copy in the exchange routing token e3x_hash(outer->body,16,hash); memcpy(ephem->token,hash,16); // generate a random seq starting point for channel IV's e3x_rand((uint8_t*)&(ephem->seq),4); // decompress the exchange key and get the shared secret uECC_decompress(outer->body,ekey); if(!uECC_shared_secret(ekey, remote->esecret, shared)) return LOG("ECDH failed"); // combine inputs to create the digest memcpy(shared+uECC_BYTES,remote->ecomp,uECC_BYTES+1); memcpy(shared+uECC_BYTES+uECC_BYTES+1,outer->body,uECC_BYTES+1); e3x_hash(shared,uECC_BYTES+((uECC_BYTES+1)*2),hash); fold1(hash,ephem->enckey); memcpy(shared+uECC_BYTES,outer->body,uECC_BYTES+1); memcpy(shared+uECC_BYTES+uECC_BYTES+1,remote->ecomp,uECC_BYTES+1); e3x_hash(shared,uECC_BYTES+((uECC_BYTES+1)*2),hash); fold1(hash,ephem->deckey); return ephem; }
ephemeral_t ephemeral_new(remote_t remote, lob_t outer) { uint8_t shared[crypto_box_BEFORENMBYTES+(crypto_box_PUBLICKEYBYTES*2)], hash[32]; ephemeral_t ephem; if(!remote) return NULL; if(!outer || outer->body_len < crypto_box_PUBLICKEYBYTES) return LOG("invalid outer"); if(!(ephem = malloc(sizeof(struct ephemeral_struct)))) return NULL; memset(ephem,0,sizeof (struct ephemeral_struct)); // create and copy in the exchange routing token e3x_hash(outer->body,16,hash); memcpy(ephem->token,hash,16); // do the diffie hellman crypto_box_beforenm(shared, outer->body, remote->esecret); // combine inputs to create the digest-derived keys memcpy(shared+crypto_box_BEFORENMBYTES,remote->ekey,crypto_box_PUBLICKEYBYTES); memcpy(shared+crypto_box_BEFORENMBYTES+crypto_box_PUBLICKEYBYTES,outer->body,crypto_box_PUBLICKEYBYTES); e3x_hash(shared,crypto_box_BEFORENMBYTES+(crypto_box_PUBLICKEYBYTES*2),ephem->enckey); memcpy(shared+crypto_box_BEFORENMBYTES,outer->body,crypto_box_PUBLICKEYBYTES); memcpy(shared+crypto_box_BEFORENMBYTES+crypto_box_PUBLICKEYBYTES,remote->ekey,crypto_box_PUBLICKEYBYTES); e3x_hash(shared,crypto_box_BEFORENMBYTES+(crypto_box_PUBLICKEYBYTES*2),ephem->deckey); return ephem; }
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 local_sign(local_t local, lob_t args, uint8_t *data, size_t len) { uint8_t hash[32], sig[uECC_BYTES*2]; if(lob_get_cmp(args,"alg","HS256") == 0) { // LOG("[%.*s] [%.*s]",args->body_len,args->body,len,data); hmac_256(args->body,args->body_len,data,len,hash); lob_body(args,NULL,32); memcpy(args->body,hash,32); return args; } if(lob_get_cmp(args,"alg","ES160") == 0) { if(!local) return NULL; // hash data first, then sign it e3x_hash(data,len,hash); uECC_sign(local->secret,hash,sig); lob_body(args,NULL,uECC_BYTES*2); memcpy(args->body,sig,uECC_BYTES*2); return args; } return NULL; }
lob_t local_decrypt(local_t local, lob_t outer) { uint8_t key[uECC_BYTES*2], shared[uECC_BYTES], iv[16], hash[32]; lob_t inner, tmp; // * `KEY` - 21 bytes, the sender's ephemeral exchange public key in compressed format // * `IV` - 4 bytes, a random but unique value determined by the sender // * `INNER` - (minimum 21+2 bytes) the AES-128-CTR encrypted inner packet ciphertext // * `HMAC` - 4 bytes, the calculated HMAC of all of the previous KEY+INNER bytes if(outer->body_len <= (21+4+0+4)) return NULL; tmp = lob_new(); if(!lob_body(tmp,NULL,outer->body_len-(4+21+4))) return lob_free(tmp); // get the shared secret to create the iv+key for the open aes uECC_decompress(outer->body,key); if(!uECC_shared_secret(key, local->secret, shared)) return lob_free(tmp); e3x_hash(shared,uECC_BYTES,hash); fold1(hash,hash); memset(iv,0,16); memcpy(iv,outer->body+21,4); // decrypt the inner aes_128_ctr(hash,tmp->body_len,iv,outer->body+4+21,tmp->body); // load inner packet inner = lob_parse(tmp->body,tmp->body_len); lob_free(tmp); return inner; }
lob_t 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; }
// 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; }
uint8_t remote_validate(remote_t remote, lob_t args, lob_t sig, uint8_t *data, size_t len) { uint8_t hash[32]; if(!args || !sig || !data || !len) return 1; if(lob_get_cmp(args,"alg","HS256") == 0) { if(sig->body_len != 32 || !args->body_len) return 2; hmac_256(args->body,args->body_len,data,len,hash); return (util_ct_memcmp(sig->body,hash,32) == 0) ? 0 : 3; } if(lob_get_cmp(args,"alg","ES160") == 0) { if(!remote || sig->body_len != KEY_BYTES) return 2; // hash data first e3x_hash(data,len,hash); return (uECC_verify(remote->key, hash, 32, sig->body, curve) == 1) ? 0 : 3; } return 3; }
// 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); }
uint8_t remote_verify(remote_t remote, local_t local, lob_t outer) { uint8_t secret[crypto_box_BEFORENMBYTES], shared[24+crypto_box_BEFORENMBYTES], hash[32]; if(!remote || !local || !outer) return 1; if(outer->head_len != 1 || outer->head[0] != 0x3a) return 2; // generate secret and verify crypto_box_beforenm(secret, remote->key, local->secret); memcpy(shared,outer->body+32,24); // nonce memcpy(shared+24,secret,crypto_box_BEFORENMBYTES); e3x_hash(shared,24+crypto_box_BEFORENMBYTES,hash); if(crypto_onetimeauth_verify(outer->body+(outer->body_len-crypto_onetimeauth_BYTES), outer->body, outer->body_len-crypto_onetimeauth_BYTES, hash)) { LOG("OTA verify failed"); lob_free(outer); return 1; } return 0; }
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; }