Skip to content

Commit

Permalink
(a12) warning nits, src-port ctrl, prep-tunnel
Browse files Browse the repository at this point in the history
Drop some dead code that didn't pan out, add controls for the listening
port when sourcing so that it's possible to test / run on the same
machine. Prepare for directory server tunnelling.

The plan for this is to simply reserve a transfer channel similar to how
bstream is already used, but without a bounded transfer length.

It'll require another thread in both dir_srv_worker and dir_cl to handle
the bidirectionality requirement, as well as a two specialised a12.h
functions, one for writing into the state machine and one for setting
the target descriptor for the inbound data.

When those work on a test- 'AAAA' stream, it should just be a matter
of having an extended form of the -S argument for the forked subchild
that substitutes inherited socket numbers for the matching socketpair()
  • Loading branch information
letoram committed Oct 19, 2023
1 parent e8a484e commit 4f9d909
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 81 deletions.
45 changes: 24 additions & 21 deletions src/a12/a12.c
Expand Up @@ -611,11 +611,7 @@ struct a12_state* a12_client(struct a12_context_options* opt)
/* double-round, start by generating ephemeral key */
else {
mode = HELLO_MODE_EPHEMPK;
if (opt->force_ephemeral_k){
memcpy(S->keys.ephem_priv, opt->priv_ephem_key, 32);
}
else
x25519_private_key(S->keys.ephem_priv);
x25519_private_key(S->keys.ephem_priv);
x25519_public_key(S->keys.ephem_priv, outpk);
S->authentic = AUTH_POLITE_HELLO_SENT;
}
Expand Down Expand Up @@ -1639,21 +1635,7 @@ static void hello_auth_server_hello(struct a12_state* S)
* is that you need an active MiM to gather Pks for tracking. */
if (cfl == HELLO_MODE_EPHEMPK){
uint8_t ek[32];
if (S->opts->force_ephemeral_k){
trace_crypto_key(S->server,
"force_ephem_expect_pub", S->opts->expect_ephem_pubkey, 32);
if (memcmp(S->opts->expect_ephem_pubkey, remote_pubk, 32) != 0){
a12int_trace(A12_TRACE_SECURITY, "force_ephem_fail");
fail_state(S);
return;
}

trace_crypto_key(S->server,
"force_ephem_priv", S->opts->priv_ephem_key, 32);
memcpy(ek, S->opts->priv_ephem_key, 32);
}
else
x25519_private_key(ek);
x25519_private_key(ek);
x25519_public_key(ek, pubk);
arcan_random(nonce, 8);
send_hello_packet(S, HELLO_MODE_EPHEMPK, pubk, nonce);
Expand Down Expand Up @@ -3397,8 +3379,10 @@ int a12_remote_mode(struct a12_state* S)
void a12int_notify_dynamic_resource(struct a12_state* S,
const char* petname, uint8_t kpub[static 32], uint8_t role, bool added)
{
if (!S->notify_dynamic)
if (!S->notify_dynamic){
a12int_trace(A12_TRACE_DIRECTORY, "ignore_no_dynamic");
return;
}

a12int_trace(A12_TRACE_DIRECTORY,
"dynamic:forward:name=%s:role=%d:added=%d", petname,(int)role,(int)added);
Expand All @@ -3417,6 +3401,7 @@ void a12int_notify_dynamic_resource(struct a12_state* S,

bool a12_request_dynamic_resource(struct a12_state* S,
uint8_t ident_pubk[static 32],
bool prefer_tunnel,
void(*request_reply)(struct a12_state*, struct a12_dynreq, void* tag),
void* tag)
{
Expand All @@ -3439,6 +3424,7 @@ bool a12_request_dynamic_resource(struct a12_state* S,
build_control_header(S, outb, COMMAND_DIROPEN);
arcan_random(S->pending_dynamic.priv_key, 32);
memcpy(&outb[19], ident_pubk, 32);
outb[18] = prefer_tunnel ? 4 : 2;
x25519_public_key(S->pending_dynamic.priv_key, &outb[52]);
a12int_append_out(S, STATE_CONTROL_PACKET, outb, CONTROL_PACKET_SIZE, NULL, 0);

Expand All @@ -3460,3 +3446,20 @@ void a12_supply_dynamic_resource(struct a12_state* S, struct a12_dynreq r)
{
fill_diropened(S, r);
}

bool
a12_write_tunnel(
struct a12_state* S, uint8_t chid, const char* const buf, size_t buf_sz)
{
return false;
}

bool
a12_set_tunnel_sink(struct a12_state* S, uint8_t chid, int fd)
{
if (!S->channels[chid].active)
return false;

S->channels[chid].unpack_state.bframe.tmp_fd = fd;
return true;
}
29 changes: 16 additions & 13 deletions src/a12/a12.h
Expand Up @@ -57,7 +57,9 @@ struct pk_response {
const uint8_t pub[static 32], const char* name, size_t sz, const char* mode);
};

/* response structure for a directory-open request. */
/* response structure for a directory-open request.
* proto 1,2 = ipvN address
* proto 3 = tunnel */
struct a12_dynreq {
char host[46];
char pubk[32];
Expand All @@ -84,11 +86,6 @@ struct a12_context_options {
* (re-)use. */
bool disable_ephemeral_k;

/* these two are used in directory mode to handle key differentiation. */
bool force_ephemeral_k;
uint8_t priv_ephem_key[32];
uint8_t expect_ephem_pubkey[32];

/* This allows the server end to transition to authenticated state based on
* password alone, low-security / debugging situations only */
bool allow_symmetric_auth;
Expand Down Expand Up @@ -295,6 +292,7 @@ enum a12_bstream_type {
A12_BTYPE_APPL_CONTROLLER = 7
};

/* BCHUNKSTATE response/initiator */
void
a12_enqueue_bstream(struct a12_state*,
int fd, int type, uint32_t id, bool streaming,
Expand All @@ -305,6 +303,15 @@ a12_enqueue_blob(
struct a12_state*, const char* const, size_t, uint32_t id,
int type, const char extid[static 16]);

/* Used on a channel mapped for use as a tunnel as a response to
* request_dynamic_resource when there is no direct / usable network path.
* Returns false if the channel isn't mapped for that kind of use. */
bool
a12_write_tunnel(struct a12_state*, uint8_t chid, const char* const, size_t);

bool
a12_set_tunnel_sink(struct a12_state*, uint8_t chid, int fd);

/*
* Get a status code indicating the state of the connection.
*
Expand Down Expand Up @@ -627,17 +634,13 @@ struct a12_iostat a12_state_iostat(struct a12_state* S);
*
* This should be used either directly and lets the outer key.
*
* in the a12_state after key derivation. This can be a different keypair if
* you want to be known to the source by a different identity than to the
* directory. The directory will discover the public part of this key however.
* Only one pending _dynamic is allowed. The callback will be triggered with
* the connection parameters for reaching the source or with a null host.
*
* The contents of dynreq will be free:d automatically after callback
* completion.
*/
bool a12_request_dynamic_resource(struct a12_state* S,
bool a12_request_dynamic_resource(
struct a12_state* S,
uint8_t ident_pubk[static 32],
bool prefer_tunnel,
void(*request_reply)(struct a12_state*, struct a12_dynreq, void* tag),
void* tag);

Expand Down
19 changes: 6 additions & 13 deletions src/a12/net/HACKING.md
Expand Up @@ -572,11 +572,11 @@ traversal (UDP blocked, misconfigured routers) and might not be permitted by
the server connection.

### command - 14, directory-opened
- [18 ] Status : (0 failed, 1 direct-in ok, 2 direct-out ok, 2 tunnel ok)
- [18 ] Status : (0 failed, 1 direct-in ok, 2 direct-out ok, 3 tunnel ok)
- [19 +46] Address : (string representation, \0 terminated)
Status = 1, IPv3,6 address to the host,
Status = 1, IPv4,6 address to the host,
Status = 2, IPv4,6 address to the host,
Status = 3, 0
Status = 3, channel-id (>= 1)
- [65..66] Port : connection port or tunnel-id (status=2)
- [67 +12] Secret : alphanumerical random secret to use with first HELLO
packet to authenticate.
Expand All @@ -589,16 +589,9 @@ initial HELLO. It is advised to use an ephemeral keypair and the two stage
HELLO to be able to differentiate the keypair used when authenticating to
the directory versus authenticating to the source.

### command - 15, write-tunnel command - 16, read-tunnel
The tunnel commands are used when a directory-opened negotiation results in a
STUN like forwarding setup for a proxied a12 session. The next [count] bytes
is to be fed into a discrete a12 state machine seeded through the directory-
opened command primitives.

If the count is 0, the effect is that the tunnel connection has been severed.

- [18..19] Tunnel : ID
- [20 ] Count : uint32, number of bytes to forward as a tunneled packet
If the Address is a tunnel, a channel is reserved similarly to define-bstream.6
data transfers work the same, except it is always streaming with no finite
limit and data can flow in both directions.

## Event (2), fixed length
- [0..7] sequence number : uint64
Expand Down
21 changes: 8 additions & 13 deletions src/a12/net/dir_cl.c
Expand Up @@ -78,7 +78,7 @@ static struct pk_response key_auth_fixed(uint8_t pk[static 32], void* tag)
/* setup the request:
* we might need to listen
* (ok) we might need to connect
* we might need to tunnel via the directory
* (ok) we might need to tunnel via the directory
*/
static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag)
{
Expand All @@ -92,15 +92,8 @@ static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag)
.pk_lookup = key_auth_fixed,
.pk_lookup_tag = &req,
.disable_ephemeral_k = true,
/* .force_ephemeral_k = true, */
};

/* the other end will use the same pubkey twice (source always exposes itself
* unless we simply skip the ephemeral round in the HELLO - something to do if
* the other endpoint is trusted. */
memcpy(&a12opts.expect_ephem_pubkey, req.pubk, 32);
x25519_private_key(a12opts.priv_ephem_key);

char port[sizeof("65535")];
snprintf(port, sizeof(port), "%"PRIu16, req.port);

Expand Down Expand Up @@ -749,15 +742,17 @@ static void cl_got_dyn(struct a12_state* S, int type,
{
struct ioloop_shared* I = tag;
struct directory_meta* cbt = I->cbt;
printf("source-%s=*%s\n", found ? "found" : "lost", petname);

if (cbt->clopt->applname[0] != '*' ||
strcmp(&I->cbt->clopt->applname[1], petname) != 0)
return;

size_t outl;
unsigned char* req = a12helper_tob64(pubk, 32, &outl);
a12int_trace(A12_TRACE_DIRECTORY, "request:petname=%s:pubk=%s", petname, req);
a12int_trace(A12_TRACE_DIRECTORY, "request:petname=*%s:pubk=%s", petname, req);
free(req);
a12_request_dynamic_resource(S, pubk, on_source, I);
a12_request_dynamic_resource(S, pubk, cbt->clopt->request_tunnel, on_source, I);
}

static bool cl_got_dir(struct ioloop_shared* I, struct appl_meta* dir)
Expand Down Expand Up @@ -834,7 +829,7 @@ static bool cl_got_dir(struct ioloop_shared* I, struct appl_meta* dir)
return false;
}

if (cbt->clopt->die_on_list)
if (cbt->clopt->die_on_list && cbt->clopt->applname[0] != '*')
return false;

return true;
Expand Down Expand Up @@ -883,10 +878,10 @@ void anet_directory_cl(
snprintf(
(char*)ev.ext.registr.title, 64, "%s", opts.ident);
a12_channel_enqueue(S, &ev);
a12_request_dynamic_resource(S, nk, opts.dir_source, opts.dir_source_tag);
a12_request_dynamic_resource(S, nk, false, opts.dir_source, opts.dir_source_tag);
}
else
a12int_request_dirlist(S, !opts.die_on_list);
a12int_request_dirlist(S, !opts.die_on_list || opts.applname[0]);

anet_directory_ioloop(&ioloop);
}
15 changes: 10 additions & 5 deletions src/a12/net/dir_srv.c
Expand Up @@ -214,7 +214,7 @@ static void dynopen_to_worker(struct dircl* C, struct arg_arr* entry)
* increasing the cost somewhat. */
arcan_event ss = {
.category = EVENT_TARGET,
.ext.kind = TARGET_COMMAND_MESSAGE
.tgt.kind = TARGET_COMMAND_MESSAGE
};

uint8_t secret[8];
Expand Down Expand Up @@ -359,18 +359,23 @@ static bool tag_outbound_name(struct arcan_event* ev, uint8_t kpub[static 32])
static void register_source(struct dircl* C, struct arcan_event ev)
{
if (!a12helper_keystore_accepted(C->pubk, active_clients.opts->allow_src)){
unsigned char* b64 = a12helper_tob64(C->pubk, 32, &(size_t){0});

A12INT_DIRTRACE(
"dirsv:kind=reject_register:title=%s:role=%d:permission",
"dirsv:kind=reject_register:title=%s:role=%d:eperm:key=%s",
ev.ext.registr.title,
ev.ext.registr.kind
ev.ext.registr.kind,
b64
);

free(b64);
return;
}

/* sanitize, name identifiers should be alnum and short for compatiblity */
char* title = ev.ext.registr.title;
for (size_t i = 0; i < COUNT_OF(ev.ext.registr.title) && title[i]; i++){
if (!isalnum(title[i]))
if (!isdigit(title[i]) && !isalpha(title[i]))
title[i] = '_';
}

Expand Down Expand Up @@ -409,7 +414,7 @@ static void register_source(struct dircl* C, struct arcan_event ev)
pthread_mutex_lock(&active_clients.sync);
struct dircl* cur = active_clients.root.next;
while (cur){
if (cur != C && cur->C && cur->type == ROLE_SINK){
if (cur != C && cur->C && !(cur->type || cur->type == ROLE_SINK)){
shmifsrv_enqueue_event(cur->C, &ev, -1);
}
cur = cur->next;
Expand Down
35 changes: 30 additions & 5 deletions src/a12/net/dir_srv_worker.c
Expand Up @@ -290,6 +290,14 @@ static void bchunk_event(struct a12_state *S,
if (strcmp(ev->tgt.message, ".index") == 0){
unpack_index(S, C, ev);
}
/* Only single channel handled for now, 1:1 source-sink connections. Multiple
* ones are not difficult as such but evaluate the need experimentally first. */
else if (strcmp(ev->tgt.message, ".tun") == 0){
a12int_trace(A12_TRACE_DIRECTORY, "worker:tunnel_acquired:channel=1");
S->channels[1].active = true;
S->channels[1].unpack_state.bframe.tmp_fd =
arcan_shmif_dupfd(ev->tgt.ioevs[0].iv, -1, true);
}
}

static bool wait_for_activation(
Expand Down Expand Up @@ -344,6 +352,7 @@ static void do_event(
size_t i = 0;

if (a12_remote_mode(S) == ROLE_SOURCE){
a12int_trace(A12_TRACE_DIRECTORY, "open_to_src");
struct a12_dynreq dynreq = (struct a12_dynreq){0};
snprintf(dynreq.authk, 12, "%s", cbt->secret);
memcpy(dynreq.pubk, ev->ext.netstate.name, 32);
Expand All @@ -366,6 +375,7 @@ static void do_event(
return;
}

a12int_trace(A12_TRACE_DIRECTORY, "notify:name=%s", ev->ext.netstate.name);
a12int_notify_dynamic_resource(S,
ev->ext.netstate.name, (uint8_t*)&ev->ext.netstate.name[i],
ev->ext.netstate.type, ev->ext.netstate.state != 0
Expand Down Expand Up @@ -421,7 +431,7 @@ static void on_shmif(struct ioloop_shared* S, bool ok)
* with kpub=%s and wait for kpriv or fail. Re-keying doesn't need this as we
* just generate new keys locally.
*/
static struct pk_response key_auth_worker(uint8_t pk[static 32], void*)
static struct pk_response key_auth_worker(uint8_t pk[static 32], void* tag)
{
struct pk_response reply = {0};
struct arcan_event req = {
Expand Down Expand Up @@ -496,7 +506,7 @@ static struct pk_response key_auth_worker(uint8_t pk[static 32], void*)
return reply;
}

static bool req_open(struct a12_state* S,
static bool dirsrv_req_open(struct a12_state* S,
uint8_t ident_req[static 32],
uint8_t mode,
struct a12_dynreq* out, void* tag)
Expand All @@ -517,7 +527,8 @@ static bool req_open(struct a12_state* S,
.ext.kind = EVENT_EXTERNAL_MESSAGE
};
snprintf((char*)reqmsg.ext.message.data,
COUNT_OF(reqmsg.ext.message.data), "a12:diropen:pubk=%s", req_b64);
COUNT_OF(reqmsg.ext.message.data),
"a12:diropen:%spubk=%s", mode == 4 ? "tunnel:" : "", req_b64);
free(req_b64);
arcan_shmif_enqueue(cbt->C, &reqmsg);

Expand Down Expand Up @@ -554,7 +565,21 @@ static bool req_open(struct a12_state* S,
if (cbt->secret)
snprintf(rq.authk, 12, "%s", cbt->secret);

_Static_assert(sizeof(rq.host) == 46);
_Static_assert(sizeof(rq.host) == 46, "wrong host-length");

/* reserved .tun as hostname is to tell that we have set a channel as tunnel,
* then spawn a processing thread that reads from the tunnel and injects into
* the state machine. */
if (strcmp(repev.ext.netstate.name, ".tun") == 0){
a12int_trace(A12_TRACE_DIRECTORY, "diropen:tunnel");
rq.proto = 3;
pthread_t pth;
pthread_attr_t pthattr;
pthread_attr_init(&pthattr);
pthread_attr_setdetachstate(&pthattr, PTHREAD_CREATE_DETACHED);
return rv;
}

strncpy(rq.host, repev.ext.netstate.name, 45);
*out = rq;
}
Expand Down Expand Up @@ -631,7 +656,7 @@ void anet_directory_srv(

a12_set_destination_raw(S, 0,
(struct a12_unpack_cfg){
.directory_open = req_open,
.directory_open = dirsrv_req_open,
.tag = &cbt
}, sizeof(struct a12_unpack_cfg)
);
Expand Down

0 comments on commit 4f9d909

Please sign in to comment.