Пример #1
0
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;
}
Пример #2
0
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;
}
Пример #3
0
// 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;
}
Пример #4
0
// process an incoming handshake
link_t link_receive_handshake(link_t link, lob_t inner)
{
  uint32_t out, at, err;
  uint8_t csid = 0;
  lob_t outer = lob_linked(inner);

  if(!link || !inner || !outer) return LOG("bad args");

  // inner/link must be validated by caller already, we just load if missing
  if(!link->key)
  {
    util_unhex(lob_get(inner, "csid"), 2, &csid);
    if(!link_load(link, csid, inner))
    {
      lob_free(inner);
      return LOG("load key failed for %s %u %s",hashname_short(link->id),csid,util_hex(inner->body,inner->body_len,NULL));
    }
  }

  if((err = e3x_exchange_verify(link->x,outer)))
  {
    lob_free(inner);
    return LOG("handshake verification fail: %d",err);
  }

  out = e3x_exchange_out(link->x,0);
  at = lob_get_uint(inner,"at");
  link_t ready = link_up(link);

  // if bad at, always send current handshake
  if(e3x_exchange_in(link->x, at) < out)
  {
    LOG("old handshake: %s (%d,%d,%d)",lob_json(inner),at,out);
    link_sync(link);
    lob_free(inner);
    return link;
  }

  // try to sync ephemeral key
  if(!e3x_exchange_sync(link->x,outer))
  {
    lob_free(inner);
    return LOG("sync failed");
  }

  // we may need to re-sync
  if(out != e3x_exchange_out(link->x,0)) link_sync(link);

  // notify of ready state change
  if(!ready && link_up(link))
  {
    LOG("link ready");
    mesh_link(link->mesh, link);
  }

  link->handshake = lob_free(link->handshake);
  link->handshake = inner;
  return link;
}
Пример #5
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;
}
Пример #6
0
// 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;
}
Пример #7
0
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;
}
Пример #8
0
// 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;
}
Пример #9
0
// 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;
}
Пример #10
0
// 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;
}
Пример #11
0
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;
}
Пример #12
0
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;
}
Пример #13
0
// 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;
}
Пример #14
0
// 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;
}
Пример #15
0
// 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;
}
Пример #16
0
// 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;
}
Пример #17
0
// just returns the token->chain claims claim
lob_t jwt_claims(lob_t token)
{
  return lob_linked(token);
}
Пример #18
0
// 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");
}
Пример #19
0
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);
}
Пример #20
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;
}
Пример #21
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;
}