Skip to content

Commit

Permalink
(a12) directory mode support for dynamic sources
Browse files Browse the repository at this point in the history
This adds all the back and forth needed to coordinate a sink requesting
to access a source attached to a directory. With this comes most of the
boilerplate needed for both nat punching and STUN as well.

In rough parts the dance goes like this:

{client-source}: register(source, name)
{server}: broadcast to all workers
{server-worker-n}: if listening, send name
{client-sink-listening}: get new source, check and a12(diropen, kpub)
{server-worker-sink}: netstate(kpub) -> {server}
{server}: netstate(kpub) find worker with kpub -> gen. secret, msg
source/sink with ip info
{server-worker-sink}: a12(diropened)
{server-worker-source}: a12(diropened)
{client-sink}: connect(diropened_rep, secret)
{client-source}: (diropened:expected-kpub,secret}
                 -> (fork+rexec-self)
                 -> listen(secret, kpub}
{client-source}: on connection: authenticate (expected-kpub, secret)

After this there is a direct connection between the source and the sink.
There is a lot of robustness work still and the handover to listen still
does not function.
  • Loading branch information
letoram committed Oct 13, 2023
1 parent ba00b99 commit 277a229
Show file tree
Hide file tree
Showing 11 changed files with 634 additions and 124 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Expand Up @@ -37,7 +37,12 @@
* add interactive accept/reject/trust for arcan-net use
* reserve 'outbound' domain for interactively added outbound keys
* enforce domain separation for allowed pubkeys
* server in directory mode now splits out into sandboxed worker processes
* directory mode now splits out into sandboxed worker processes
* directory mode can now specify permissions per tag-group
* directory mode dynamic push appl from client with permission
* directory mode notification of appl updates and sources coming / leaving
* directory mode registration of dynamic sinks
* directory mode sourcing dynamic sinks (sink-inbound only)

## Terminal
* SGR reset fix, add CNL / CPL
Expand Down
125 changes: 123 additions & 2 deletions src/a12/a12.c
Expand Up @@ -803,6 +803,68 @@ static void process_srvfirst(struct a12_state* S)
}
}

static void fill_diropened(struct a12_state* S, struct a12_dynreq r)
{
uint8_t outb[CONTROL_PACKET_SIZE];

build_control_header(S, outb, COMMAND_DIROPENED);
outb[18] = r.proto;
memcpy(&outb[19], r.host, 46);
pack_u16(r.port, &outb[65]);
memcpy(&outb[67], r.authk, 12);
memcpy(&outb[79], r.pubk, 32);

a12int_append_out(S, STATE_CONTROL_PACKET, outb, CONTROL_PACKET_SIZE, NULL, 0);
}

static void command_diropened(struct a12_state* S)
{
/* local- copy the members so the closure is allowed to queue a new one */
void(* oc)(struct a12_state*, struct a12_dynreq, void* tag) = S->pending_dynamic.closure;
void* tag = S->pending_dynamic.tag;

S->pending_dynamic.closure = NULL;
S->pending_dynamic.tag = NULL;

struct a12_dynreq rep = {
.proto = S->decode[18]
};

memcpy(rep.host, &S->decode[19], 46);
unpack_u16(&rep.port, &S->decode[65]);
memcpy(rep.authk, &S->decode[67], 12);
memcpy(rep.pubk, &S->decode[79], 32);

oc(S, rep, tag);
}

static void command_diropen(
struct a12_state* S, uint8_t mode,
uint8_t kpub_tgt[static 32], uint8_t kpub_src[static 32])
{
/* a12.h misuse */
struct a12_unpack_cfg* C = &S->channels[0].raw;
if (!C->directory_open){
a12int_trace(A12_TRACE_SECURITY, "kind=warning:diropen_no_handler");
return;
}

/* forward the request */
struct a12_dynreq out = {0};
uint8_t outb[CONTROL_PACKET_SIZE];

/* the implementation is expected to set the authk as it might be outsourced
* to an external oracle that coordinates with the partner in question */
if (C->directory_open(S, kpub_tgt, kpub_src, mode, &out, C->tag)){
fill_diropened(S, out);
}
/* failure is just an empty command */
else {
build_control_header(S, outb, COMMAND_DIROPENED);
a12int_append_out(S, STATE_CONTROL_PACKET, outb, CONTROL_PACKET_SIZE, NULL, 0);
}
}

static void command_cancelstream(
struct a12_state* S, uint32_t streamid, uint8_t reason, uint8_t stype)
{
Expand Down Expand Up @@ -2003,6 +2065,20 @@ static void process_control(struct a12_state* S, void (*on_event)
command_cancelstream(S, streamid, S->decode[22], S->decode[23]);
}
break;
case COMMAND_DIROPEN:{
if (S->opts->local_role == ROLE_DIR){
command_diropen(S, S->decode[18], &S->decode[19], &S->decode[52]);
}
else
a12int_trace(A12_TRACE_SECURITY, "diropen:wrong_role");
}
case COMMAND_DIROPENED:{
if (S->pending_dynamic.active)
command_diropened(S);
else
a12int_trace(A12_TRACE_SECURITY, "diropened:no_pending_request");
}
break;
case COMMAND_PING:{
uint32_t streamid;
unpack_u32(&streamid, &S->decode[18]);
Expand All @@ -2025,8 +2101,16 @@ static void process_control(struct a12_state* S, void (*on_event)
* 3. if this is not a rekey response package, send the new pubk in response */
break;
case COMMAND_DIRLIST:
/* force- synch dynamic entries */
S->notify_dynamic = S->decode[18];
a12int_trace(A12_TRACE_DIRECTORY, "dirlist:notify=%d", (int) S->notify_dynamic);
send_dirlist(S);
/* notify the directory side that this action occurred */
on_event(NULL, 0, &(struct arcan_event){
.category = EVENT_EXTERNAL,
.ext.kind = EVENT_EXTERNAL_MESSAGE,
.ext.message.data = "a12:dirlist"
}, tag);
break;
case COMMAND_DIRDISCOVER:
command_dirdiscover(S, on_event, tag);
Expand Down Expand Up @@ -3318,9 +3402,46 @@ void a12int_notify_dynamic_resource(struct a12_state* S,
}

bool a12_request_dynamic_resource(struct a12_state* S,
uint8_t req_pubk[static 32], uint8_t ident_pubk[static 32],
uint8_t ident_pubk[static 32],
void(*request_reply)(struct a12_state*, struct a12_dynreq, void* tag),
void* tag)
{
return false;
if (S->pending_dynamic.active || S->remote_mode != ROLE_DIR){
return false;
}

/* role source just need the closure tagged actually */
S->pending_dynamic.active = true;
S->pending_dynamic.closure = request_reply;
S->pending_dynamic.tag = tag;

if (S->opts->local_role != ROLE_SINK)
return true;

/* this key isn't as sensitive as it will only be used to authenticate the
* mediated nested connections ephemeral layer not added to the keystore */
uint8_t outb[CONTROL_PACKET_SIZE];
build_control_header(S, outb, COMMAND_DIROPEN);
arcan_random(S->pending_dynamic.key, 32);
memcpy(&outb[19], ident_pubk, 32);
x25519_public_key(S->pending_dynamic.key, &outb[52]);
a12int_append_out(S, STATE_CONTROL_PACKET, outb, CONTROL_PACKET_SIZE, NULL, 0);

return true;
}

void a12_set_endpoint(struct a12_state* S, const char* ep)
{
free(S->endpoint);
S->endpoint = ep ? strdup(ep) : NULL;
}

const char* a12_get_endpoint(struct a12_state* S)
{
return S->endpoint;
}

void a12_supply_dynamic_resource(struct a12_state* S, struct a12_dynreq r)
{
fill_diropened(S, r);
}
52 changes: 40 additions & 12 deletions src/a12/a12.h
Expand Up @@ -57,12 +57,21 @@ 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. */
struct a12_dynreq {
char host[46];
char pubk[32];
uint16_t port;
char authk[12];
int proto;
};

struct a12_context_options {
/* Provide to enable asymetric key authentication, set valid in the return to
* allow the key, otherwise the session may be continued for a random number of
* time or bytes before being terminated. If want_session is requested, the
* lookup function, if it is able to (legacy) should set got_session in the
* reply and calculate the x25519v shared secret itself. */
* reply and calculate the x25519 shared secret itself. */
struct pk_response (*pk_lookup)(uint8_t pub[static 32]);

/* Client only, provide the private key to use with the connection. All [0]
Expand Down Expand Up @@ -193,6 +202,15 @@ struct a12_unpack_cfg {
/* n BYTES have been written into the buffer allocated through
* request_audio_buffer */
void (*signal_audio)(size_t bytes, void* tag);

/* only used with local_role == MODE_DIRECTORY wherein someone requests
* to open a resource through us. */
bool (*directory_open)(struct a12_state*,
uint8_t ident_pubk[static 32],
uint8_t ident_req[static 32],
uint8_t mode,
struct a12_dynreq* out,
void* tag);
};

void a12_set_destination_raw(struct a12_state*,
Expand Down Expand Up @@ -588,28 +606,38 @@ struct a12_iostat {
size_t packets_pending; /* delta between seqnr and last-seen seqnr */
};

/* get / set a string representation for logging and similar operations
* where there is a need for a traceable origin e.g. IP address */
const char* a12_get_endpoint(struct a12_state* S);
void a12_set_endpoint(struct a12_state* S, const char*);

/*
* Sample the current rolling state statistics
*/
struct a12_iostat a12_state_iostat(struct a12_state* S);

/*
* Try to negotiate a connection for a directory resource based on an announced
* public key. Provide the public key (or =[0] to use the same key as the
* connection. Only one pending _dynamic is allowed. The callback will be
* triggered with the connection parameters for reaching the source. */
struct a12_dynreq {
char* host;
uint16_t port;
char* authk;
int proto;
};

* public key. An ephemeral keypair will be generated and part of the reply.
*
* 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,
uint8_t req_pubk[static 32], uint8_t ident_pubk[static 32],
uint8_t ident_pubk[static 32],
void(*request_reply)(struct a12_state*, struct a12_dynreq, void* tag),
void* tag);

void a12_supply_dynamic_resource(struct a12_state* S, struct a12_dynreq);

/*
* debugging / tracing bits, just define a namespace that can be used
* for wrapper tools to log with the same syntax and behaviour as the
Expand Down
11 changes: 11 additions & 0 deletions src/a12/a12_int.h
Expand Up @@ -77,6 +77,7 @@ enum control_commands {
COMMAND_DIRSTATE = 10,/* update / present a new appl */
COMMAND_DIRDISCOVER = 11,/* dynamic source/dir entry */
COMMAND_DIROPEN = 12,/* mediate access to a dyn src/dir */
COMMAND_DIROPENED = 13,/* replies to DIROPEN (src/sink) */
};

enum hello_mode {
Expand Down Expand Up @@ -261,6 +262,15 @@ struct a12_state {
} congestion_stats;
struct a12_iostat stats;

/* tracks a pending dynamic directory resource */
struct {
bool active;
uint8_t key[32];
void(* closure)(struct a12_state*, struct a12_dynreq, void* tag);
void* tag;

} pending_dynamic;

/* populate and forwarded output buffer */
size_t buf_sz[2];
uint8_t* bufs[2];
Expand Down Expand Up @@ -317,6 +327,7 @@ struct a12_state {
bool cl_firstout;
int authentic;
int remote_mode;
char* endpoint;

/* saved between calls to unpack, see end of a12_unpack for explanation */
bool auth_latched;
Expand Down
23 changes: 14 additions & 9 deletions src/a12/net/HACKING.md
Expand Up @@ -553,7 +553,8 @@ while Kpub-me is the public key that the source will connect through in order
to differentiate between the credential used to access the directory versus the
credential used to access the source.

Mode can be treated as a bitmap of supported open-modes.
Mode can be treated as a bitmap of supported open-modes but is a hint as
the initiator cannot necessarily know the topology of the source.

If mode is set to direct-inbound the request is that the other end connects to
the request originator (TCP). This will provide the source-IP that was used to
Expand All @@ -573,17 +574,21 @@ the server connection.

### command - 14, directory-opened
- [18 ] Status : (0 failed, 1 direct-in ok, 2 direct-out ok, 2 tunnel ok)
- [19 +16] Address : Status = 1, IPv6 address to the host,
Status = 2, IPv4 address to the host,
Status = 3, tunnel ID.
- [34 +12] Secret : alphanumerical random secret to use with first HELLO
- [19 +46] Address : (string representation, \0 terminated)
Status = 1, IPv3,6 address to the host,
Status = 2, IPv4,6 address to the host,
Status = 3, 0
- [65..66] Port : connection port or tunnel-id (status=2)
- [67 +12] Secret : alphanumerical random secret to use with first HELLO
packet to authenticate.
- [45 +16] Kpub : the other end key (for direct-in)
- [79 +32] Kpub : the other end key.

This will be sent to source/source-directory and to sink in response to a
directory-open request. The connection mode must skip the ephemeral handshake
hello-mode and instead use the secret to protect the negotiation packet and
to signal that the connection is mediated via this particular third party.
directory-open request. The connection handshake is just like the initial
one for the directory, but using the authentication secret to protect the
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.

## Event (2), fixed length
- [0..7] sequence number : uint64
Expand Down
13 changes: 10 additions & 3 deletions src/a12/net/dir_cl.c
Expand Up @@ -58,12 +58,17 @@ static struct {

static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag)
{
printf("waiting on the other end\n");

/* setup the request:
* we might need to listen
* we might need to connect
* we might need to tunnel via the directory
*/

/* wait for a bit */
/* if that fails, switch to tunnel processing */

/* when that is done (i.e. we get a connection), chain into the corresponding
* source to shmif handler for the new connection. */
}
Expand All @@ -73,7 +78,8 @@ static void on_cl_event(
{
if (ev->category == EVENT_EXTERNAL && ev->ext.kind == EVENT_EXTERNAL_NETSTATE){
struct ioloop_shared* I = tag;
printf("source:name=%s\n", ev->ext.netstate.name);
printf("source:name=%s:state=%s\n",
ev->ext.netstate.name, ev->ext.netstate.state == 0 ? "lost" : "found");
if (I->cbt->clopt->applname[0] == '*'){
/* split out Kpub, need to use that in the open request */
size_t i = 0;
Expand All @@ -86,9 +92,8 @@ static void on_cl_event(

/* found it, request to negotiate an open */
if (strcmp(&I->cbt->clopt->applname[1], ev->ext.netstate.name) == 0){
uint8_t nkey[32] = {0};
a12_request_dynamic_resource(I->S,
(uint8_t*)&ev->ext.netstate.name[i], nkey, on_source, I);
(uint8_t*)&ev->ext.netstate.name[i], on_source, I);
}
}
return;
Expand Down Expand Up @@ -806,6 +811,7 @@ void anet_directory_cl(
* it might be slightly cleaner having an actual directory command for the
* thing rather than (ab)using REGISTER here and IDENT for appl-messaging. */
if (opts.dir_source){
uint8_t nk[32] = {0};
struct arcan_event ev = {
.ext.kind = ARCAN_EVENT(REGISTER),
.category = EVENT_EXTERNAL,
Expand All @@ -814,6 +820,7 @@ 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);
}

struct ioloop_shared ioloop = {
Expand Down

0 comments on commit 277a229

Please sign in to comment.