diff --git a/src/a12/a12.c b/src/a12/a12.c index d6ca4dffd..2b33aeb48 100644 --- a/src/a12/a12.c +++ b/src/a12/a12.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "a12.h" #include "a12_int.h" @@ -829,6 +830,7 @@ static void command_diropened(struct a12_state* S) S->pending_dynamic.closure = NULL; S->pending_dynamic.tag = NULL; + S->pending_dynamic.active = false; struct a12_dynreq rep = { .proto = S->decode[18] @@ -837,14 +839,19 @@ static void command_diropened(struct a12_state* S) 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); + +/* this is used both for source and sink, for sink swap in the pubk of the request */ + uint8_t nullk[32] = {0}; + if (memcmp(nullk, &S->decode[79], 32) == 0) + memcpy(rep.pubk, S->pending_dynamic.req_key, 32); + else + 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]) + struct a12_state* S, uint8_t mode, uint8_t kpub_tgt[static 32]) { /* a12.h misuse */ struct a12_unpack_cfg* C = &S->channels[0].raw; @@ -859,7 +866,7 @@ static void command_diropen( /* 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)){ + if (C->directory_open(S, kpub_tgt, mode, &out, C->tag)){ fill_diropened(S, out); } /* failure is just an empty command */ @@ -1633,11 +1640,16 @@ static void hello_auth_server_hello(struct a12_state* S) 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 @@ -1664,7 +1676,7 @@ static void hello_auth_server_hello(struct a12_state* S) /* the lookup function returns the key that should be used in the reply * and to calculate the shared secret */ trace_crypto_key(S->server, "state=client_pk", remote_pubk, 32); - struct pk_response res = S->opts->pk_lookup(remote_pubk); + struct pk_response res = S->opts->pk_lookup(remote_pubk, S->opts->pk_lookup_tag); if (!res.authentic){ a12int_trace(A12_TRACE_CRYPTO, "state=eperm:kind=x25519-pk-fail"); fail_state(S); @@ -1703,7 +1715,7 @@ static void hello_auth_client_hello(struct a12_state* S) } trace_crypto_key(S->server, "server_pk", &S->decode[21], 32); - struct pk_response res = S->opts->pk_lookup(&S->decode[21]); + struct pk_response res = S->opts->pk_lookup(&S->decode[21], S->opts->pk_lookup_tag); if (!res.authentic){ a12int_trace(A12_TRACE_CRYPTO, "state=eperm:kind=25519-pk-fail"); fail_state(S); @@ -1940,38 +1952,27 @@ static void send_dirlist(struct a12_state* S) /* just unpack and forward to the event handler, arcan proper can deal with the * event as it is mostly verbatim when it passes through afsrv_net and * arcan-net has the same structures available */ -static void command_dirdiscover(struct a12_state* S, void (*on_event) - (struct arcan_shmif_cont*, int chid, struct arcan_event*, void*), void* tag) +static void command_dirdiscover(struct a12_state* S) { + if (!S->on_discover) + return; + uint8_t type = S->decode[18]; bool added = S->decode[19]; -/* block separator */ +/* grab petname, sanitize to 7-bit alnum + _ */ char petname[17] = {0}; memcpy(petname, &S->decode[20], 16); - for (size_t i = 0; petname[i]; i++) - if (petname[i] == ':') - petname[i] = '_'; - -/* netstate event type model match that of a12 dirdiscover command */ - struct arcan_event ev = { - .category = EVENT_EXTERNAL, - .ext.kind = EVENT_EXTERNAL_NETSTATE, - .ext.netstate = { - .space = 5, - .state = added ? 1 : 0, - .type = type + for (size_t i = 0; petname[i]; i++){ + if (!isalnum(petname[i]) && petname[i] != '_'){ + a12int_trace(A12_TRACE_SECURITY, "discover:malformed_petname=%s", petname); + return; } - }; - -/* pack kpub as base64 (43 bytes) */ - size_t outl; - unsigned char* pk = - a12helper_tob64(&S->decode[36], 32, &outl); - snprintf((char*)&ev.ext.netstate.name, 66, "%s:%s", petname, pk); - free(pk); + } - on_event(S->channels[0].cont, 0, &ev, tag); + uint8_t pubk[32]; + memcpy(pubk, &S->decode[36], 32); + S->on_discover(S, type, petname, added, pubk, S->discover_tag); } static void add_dirent(struct a12_state* S) @@ -2080,7 +2081,7 @@ static void process_control(struct a12_state* S, void (*on_event) break; case COMMAND_DIROPEN:{ if (S->opts->local_role == ROLE_DIR){ - command_diropen(S, S->decode[18], &S->decode[19], &S->decode[52]); + command_diropen(S, S->decode[18], &S->decode[19]); } else a12int_trace(A12_TRACE_SECURITY, "diropen:wrong_role"); @@ -2126,7 +2127,7 @@ static void process_control(struct a12_state* S, void (*on_event) }, tag); break; case COMMAND_DIRDISCOVER: - command_dirdiscover(S, on_event, tag); + command_dirdiscover(S); break; /* Security notice: this allows the remote end to update / populate the @@ -3427,17 +3428,18 @@ bool a12_request_dynamic_resource(struct a12_state* S, S->pending_dynamic.active = true; S->pending_dynamic.closure = request_reply; S->pending_dynamic.tag = tag; + memcpy(S->pending_dynamic.req_key, ident_pubk, 32); 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 */ + * 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); + arcan_random(S->pending_dynamic.priv_key, 32); memcpy(&outb[19], ident_pubk, 32); - x25519_public_key(S->pending_dynamic.key, &outb[52]); + x25519_public_key(S->pending_dynamic.priv_key, &outb[52]); a12int_append_out(S, STATE_CONTROL_PACKET, outb, CONTROL_PACKET_SIZE, NULL, 0); return true; diff --git a/src/a12/a12.h b/src/a12/a12.h index 1bae34b0e..f54228f66 100644 --- a/src/a12/a12.h +++ b/src/a12/a12.h @@ -72,7 +72,8 @@ struct a12_context_options { * 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 x25519 shared secret itself. */ - struct pk_response (*pk_lookup)(uint8_t pub[static 32]); + struct pk_response (*pk_lookup)(uint8_t pub[static 32], void*); + void* pk_lookup_tag; /* Client only, provide the private key to use with the connection. All [0] * key will disable attempts at asymetric operation. */ @@ -211,7 +212,6 @@ struct a12_unpack_cfg { /* 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, diff --git a/src/a12/a12_int.h b/src/a12/a12_int.h index fae16bdda..f7753ab29 100644 --- a/src/a12/a12_int.h +++ b/src/a12/a12_int.h @@ -265,7 +265,8 @@ struct a12_state { /* tracks a pending dynamic directory resource */ struct { bool active; - uint8_t key[32]; + uint8_t priv_key[32]; + uint8_t req_key[32]; void(* closure)(struct a12_state*, struct a12_dynreq, void* tag); void* tag; @@ -297,6 +298,10 @@ struct a12_state { /* current encoding state, manipulate with set_channel */ int out_channel; + void (*on_discover)(struct a12_state*, int type, + const char* petname, bool found, uint8_t pubk[static 32], void*); + void* discover_tag; + void (*on_auth)(struct a12_state*, void*); void* auth_tag; diff --git a/src/a12/net/HACKING.md b/src/a12/net/HACKING.md index 76bca1e9e..034c311f0 100644 --- a/src/a12/net/HACKING.md +++ b/src/a12/net/HACKING.md @@ -545,7 +545,6 @@ policy or name collision. ### command - 12, directory-open - [18 ] Mode : (1: direct-inbound, 2: direct-outbound, 4: tunnel) - [19+ 32] Kpub-tgt : (x25519) -- [52+ 32] Kpub-me : (x25519) This is used to request a connection / connection request to the provided petname. Kpub-tgt is the identifier previously received from a discover event @@ -553,8 +552,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 but is a hint as -the initiator cannot necessarily know the topology of the source. +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 @@ -590,6 +589,17 @@ 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 + ## Event (2), fixed length - [0..7] sequence number : uint64 - [8 ] channel-id : uint8 diff --git a/src/a12/net/a12_helper_srv.c b/src/a12/net/a12_helper_srv.c index 05eb8d530..157883171 100644 --- a/src/a12/net/a12_helper_srv.c +++ b/src/a12/net/a12_helper_srv.c @@ -655,7 +655,6 @@ void a12helper_a12cl_shmifsrv(struct a12_state* S, uint8_t* outbuf = NULL; size_t outbuf_sz = 0; - /* tie an empty context as channel destination, we use this as a type- wrapper * for the shmifsrv_client now, this logic is slightly different on the * shmifsrv_client side. */ diff --git a/src/a12/net/dir_cl.c b/src/a12/net/dir_cl.c index ee0d880fc..2617b38e3 100644 --- a/src/a12/net/dir_cl.c +++ b/src/a12/net/dir_cl.c @@ -20,8 +20,11 @@ #include "../a12.h" #include "../a12_int.h" +#include "../external/x25519.h" + #include "anet_helper.h" #include "directory.h" +#include "a12_helper.h" #include #include @@ -56,49 +59,79 @@ static struct { } active_appls = { }; -static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag) +static struct pk_response key_auth_fixed(uint8_t pk[static 32], void* tag) { - printf("waiting on the other end\n"); + struct a12_dynreq* key_auth_req = tag; + + struct pk_response auth = {}; + if (memcmp(key_auth_req->pubk, pk, 32) == 0){ + auth.authentic = true; + } + return auth; +} + +/* + * right now just initialise the outbound connection, in reality the req.mode + * should be respected to swap over to the client processing + */ /* setup the request: * we might need to listen - * we might need to connect + * (ok) we might need to connect * we might need to tunnel via the directory */ +static void on_source(struct a12_state* S, struct a12_dynreq req, void* tag) +{ +/* security: + * disable the ephemeral exchange for now, this means the announced identity + * when we connect to the directory server will be the one used for the x25519 + * exchange instead of a generated intermediate. + */ + struct a12_context_options a12opts = { + .local_role = ROLE_SINK, + .pk_lookup = key_auth_fixed, + .pk_lookup_tag = &req, + .disable_ephemeral_k = true, +/* .force_ephemeral_k = true, */ + }; -/* wait for a bit */ -/* if that fails, switch to tunnel processing */ +/* 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); -/* when that is done (i.e. we get a connection), chain into the corresponding - * source to shmif handler for the new connection. */ -} + char port[sizeof("65535")]; + snprintf(port, sizeof(port), "%"PRIu16, req.port); -static void on_cl_event( - struct arcan_shmif_cont* cont, int chid, struct arcan_event* ev, void* tag) -{ - if (ev->category == EVENT_EXTERNAL && ev->ext.kind == EVENT_EXTERNAL_NETSTATE){ - struct ioloop_shared* I = tag; - 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; - for (size_t i = 0; i < COUNT_OF(ev->ext.netstate.name); i++) - if (ev->ext.netstate.name[i] == ':'){ - ev->ext.netstate.name[i] = '\0'; - i++; - break; - } + struct anet_options anet = { + .retry_count = 10, + .opts = &a12opts, + .host = req.host, + .port = port + }; -/* found it, request to negotiate an open */ - if (strcmp(&I->cbt->clopt->applname[1], ev->ext.netstate.name) == 0){ - a12_request_dynamic_resource(I->S, - (uint8_t*)&ev->ext.netstate.name[i], on_source, I); - } - } + snprintf(a12opts.secret, sizeof(a12opts.secret), "%s", req.authk); + struct anet_cl_connection con = anet_cl_setup(&anet); + if (con.errmsg || !con.state){ + fprintf(stderr, "%s", con.errmsg ? con.errmsg : "broken connection state\n"); return; } + if (a12_remote_mode(con.state) != ROLE_SOURCE){ + fprintf(stderr, "remote endpoint is not a source\n"); + shutdown(con.fd, SHUT_RDWR); + return; + } + + a12helper_a12srv_shmifcl(NULL, con.state, NULL, con.fd, con.fd); + shutdown(con.fd, SHUT_RDWR); +} + +static void on_cl_event( + struct arcan_shmif_cont* cont, int chid, struct arcan_event* ev, void* tag) +{ +/* main use would be the appl- runner forwarding messages that direction */ a12int_trace(A12_TRACE_DIRECTORY, "event=%s", arcan_shmif_eventstr(ev, NULL, 0)); } @@ -711,6 +744,22 @@ struct a12_bhandler_res anet_directory_cl_bhandler( return res; } +static void cl_got_dyn(struct a12_state* S, int type, + const char* petname, bool found, uint8_t pubk[static 32], void* tag) +{ + struct ioloop_shared* I = tag; + struct directory_meta* cbt = I->cbt; + 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); + free(req); + a12_request_dynamic_resource(S, pubk, on_source, I); +} + static bool cl_got_dir(struct ioloop_shared* I, struct appl_meta* dir) { struct directory_meta* cbt = I->cbt; @@ -807,6 +856,20 @@ void anet_directory_cl( sigaction(SIGPIPE,&(struct sigaction){.sa_handler = SIG_IGN}, 0); + struct ioloop_shared ioloop = { + .S = S, + .fdin = fdin, + .fdout = fdout, + .userfd = -1, + .on_event = on_cl_event, + .on_directory = cl_got_dir, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cbt = &cbt, + }; + + S->on_discover = cl_got_dyn; + S->discover_tag = &ioloop; + /* send REGISTER event with our ident, this is a convenience thing right now, * it might be slightly cleaner having an actual directory command for the * thing rather than (ab)using REGISTER here and IDENT for appl-messaging. */ @@ -822,19 +885,8 @@ void anet_directory_cl( a12_channel_enqueue(S, &ev); a12_request_dynamic_resource(S, nk, opts.dir_source, opts.dir_source_tag); } + else + a12int_request_dirlist(S, !opts.die_on_list); - struct ioloop_shared ioloop = { - .S = S, - .fdin = fdin, - .fdout = fdout, - .userfd = -1, - .on_event = on_cl_event, - .on_directory = cl_got_dir, - .lock = PTHREAD_MUTEX_INITIALIZER, - .cbt = &cbt, - }; - -/* always request dirlist so we can resolve applname against the server-local */ - a12int_request_dirlist(S, !opts.die_on_list); anet_directory_ioloop(&ioloop); } diff --git a/src/a12/net/dir_srv.c b/src/a12/net/dir_srv.c index 71a00bf6e..3ac57bd27 100644 --- a/src/a12/net/dir_srv.c +++ b/src/a12/net/dir_srv.c @@ -185,7 +185,7 @@ static void dynopen_to_worker(struct dircl* C, struct arg_arr* entry) * now in the PoC we assume the source is the listening end and the sink the * outbound one. We also have a default port for the source (6680) that should * be possible to change. */ - if (memcmp(cur->pubk, C->pubk, 32) == 0){ + if (memcmp(pubk_dec, cur->pubk, 32) == 0){ arcan_event to_src = { .category = EVENT_EXTERNAL, .ext.kind = EVENT_EXTERNAL_NETSTATE, @@ -231,6 +231,7 @@ static void dynopen_to_worker(struct dircl* C, struct arg_arr* entry) free(b64); break; } + cur = cur->next; } pthread_mutex_unlock(&active_clients.sync); return; @@ -381,7 +382,10 @@ static void register_source(struct dircl* C, struct arcan_event ev) return; } - A12INT_DIRTRACE("dirsv:kind=register:name=%s", ev.ext.registr.title); + unsigned char* b64 = a12helper_tob64(C->pubk, 32, &(size_t){0}); + A12INT_DIRTRACE( + "dirsv:kind=register:name=%s:pubk=%s", ev.ext.registr.title, b64); + free(b64); /* if we just change state / availability, don't permit rename */ if (C->petname.ext.netstate.name[0]){ @@ -391,8 +395,8 @@ static void register_source(struct dircl* C, struct arcan_event ev) } } - C->petname = ev; ev.ext.netstate.state = 1; + C->petname = ev; /* finally ack the petname and broadcast */ if (!tag_outbound_name(&ev, C->pubk)) @@ -405,7 +409,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){ + if (cur != C && cur->C && cur->type == ROLE_SINK){ shmifsrv_enqueue_event(cur->C, &ev, -1); } cur = cur->next; @@ -642,7 +646,8 @@ static void dircl_message(struct dircl* C, struct arcan_event ev) goto send_fail; pthread_mutex_lock(&active_clients.sync); - struct pk_response rep = active_clients.opts->a12_cfg->pk_lookup(pubk_dec); + struct a12_context_options* aopt = active_clients.opts->a12_cfg; + struct pk_response rep = aopt->pk_lookup(pubk_dec, aopt->pk_lookup_tag); pthread_mutex_unlock(&active_clients.sync); if (!rep.authentic) @@ -696,6 +701,7 @@ void handle_netstate(struct dircl* C, arcan_event ev) { ev.ext.netstate.name[COUNT_OF(ev.ext.netstate.name)-1] = '\0'; +/* update ip <-> port mapping */ if (ev.ext.netstate.type == 3 || ev.ext.netstate.type == 4){ A12INT_DIRTRACE("dirsv:kind=worker:set_endpoint=%s", ev.ext.netstate.name); C->endpoint = ev; @@ -709,7 +715,10 @@ void handle_netstate(struct dircl* C, arcan_event ev) } /* set sink key */ else if (ev.ext.netstate.type == 2){ - A12INT_DIRTRACE("dirsv:kind=worker:update_sink_pk"); + unsigned char* b64 = a12helper_tob64( + (unsigned char*)ev.ext.netstate.name, 32, &(size_t){0}); + A12INT_DIRTRACE("dirsv:kind=worker:update_sink_pk=%s", b64); + free(b64); memcpy(C->pubk, ev.ext.netstate.name, 32); } else diff --git a/src/a12/net/dir_srv_worker.c b/src/a12/net/dir_srv_worker.c index 76c41eb13..dc4482e79 100644 --- a/src/a12/net/dir_srv_worker.c +++ b/src/a12/net/dir_srv_worker.c @@ -421,7 +421,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]) +static struct pk_response key_auth_worker(uint8_t pk[static 32], void*) { struct pk_response reply = {0}; struct arcan_event req = { @@ -497,43 +497,21 @@ static struct pk_response key_auth_worker(uint8_t pk[static 32]) } static bool req_open(struct a12_state* S, - uint8_t ident_pubk[static 32], uint8_t ident_req[static 32], + uint8_t ident_req[static 32], uint8_t mode, struct a12_dynreq* out, void* tag) { struct directory_meta* cbt = tag; -/* This is annoying as the sum of the key sizes doesn't fit one message with - * both keys and we don't have a filesystem to use for temporary storage due to - * the sandboxing. Thus we use one netstate to set the current diropen key. - * - * (ok case) - * [wrk_sink] (NETSTATE:space=5:sink) + message(diropen:req) -> [srv] -> - * NETSTATE:space=1:host + NETSTATE:sink -> [wrk_source] - * NETSTATE:space=1:host + NETSTATE:source -> [wrk_sink] - * - connection is setup - - * - * (fail case) - * [wrk_sink] (NETSTATE:sink) + message(diropen:req) -> [srv] -> NETSTATE:lost(pubk) - * - */ - - uint8_t nullk[32] = {0}; - if (memcmp(nullk, ident_pubk, 32) != 0){ - arcan_event idev = { - .category = EVENT_EXTERNAL, - .ext.kind = EVENT_EXTERNAL_NETSTATE, - .ext.netstate = { - .type = 1, - .space = 5 - } - }; - memcpy(idev.ext.netstate.name, ident_pubk, 32); - arcan_shmif_enqueue(cbt->C, &idev); - } - size_t outl; unsigned char* req_b64 = a12helper_tob64(ident_req, 32, &outl); + if (!req_b64){ + a12int_trace(A12_TRACE_SYSTEM, "diropen:bad_pubk"); + return false; + } + + a12int_trace(A12_TRACE_DIRECTORY, "diropen:req_pubk=%s", req_b64); + arcan_event reqmsg = { .category = EVENT_EXTERNAL, .ext.kind = EVENT_EXTERNAL_MESSAGE @@ -578,7 +556,7 @@ static bool req_open(struct a12_state* S, _Static_assert(sizeof(rq.host) == 46); strncpy(rq.host, repev.ext.netstate.name, 45); - a12_supply_dynamic_resource(S, rq); + *out = rq; } else { a12int_trace(A12_TRACE_DIRECTORY, "diropen:kind=rejected"); @@ -607,7 +585,6 @@ void anet_directory_srv( SEGID_NETWORK_SERVER, SHMIF_ACQUIRE_FATALFAIL | SHMIF_NOACTIVATE | - SHMIF_DISABLE_GUARD | SHMIF_NOREGISTER, &args ); diff --git a/src/a12/net/net.c b/src/a12/net/net.c index 3db6f74ea..f55a595fd 100644 --- a/src/a12/net/net.c +++ b/src/a12/net/net.c @@ -83,16 +83,18 @@ static struct a12_vframe_opts vcodec_tuning( struct a12_state* S, int segid, struct shmifsrv_vbuffer* vb, void* tag); /* keystore is singleton global */ +static struct keystore_provider prov = { + .type = A12HELPER_PROVIDER_BASEDIR, + .directory.dirfd = -1 +}; + static bool open_keystore(const char** err) { - int dir = a12helper_keystore_dirfd(err); - if (-1 == dir) - return false; - - struct keystore_provider prov = { - .directory.dirfd = dir, - .type = A12HELPER_PROVIDER_BASEDIR - }; + if (-1 == prov.directory.dirfd){ + prov.directory.dirfd = a12helper_keystore_dirfd(err); + if (-1 == prov.directory.dirfd) + return false; + } if (!a12helper_keystore_open(&prov)){ *err = "Couldn't open keystore from basedir (ARCAN_STATEPATH)"; @@ -118,11 +120,6 @@ static int tracestr_to_bitmap(char* work) return res; } -/* - * pull in from arcan codebase, chacha based CSPRNG - */ -extern void arcan_random(uint8_t* dst, size_t ntc); - /* * Since we pull in some functions from the main arcan codebase, we need to * define this symbol, used if the random function has problems with entropy @@ -423,6 +420,7 @@ struct dirstate { int fd; struct anet_options* aopts; struct shmifsrv_client* shmif; + struct a12_dynreq req; }; static void a12cl_dispatch( @@ -467,10 +465,65 @@ static void a12cl_dispatch( close(fd); } +static struct pk_response key_auth_dir(uint8_t pk[static 32], void* tag) +{ + struct dirstate* ds = tag; + struct pk_response auth = {.authentic = true}; + +/* We need the key we registered with the dirsrv with as the sink will be + * expecting it, while we trust whatever as the outer packet authk combine with + * the ephemeral key. + * + * The ephemeral key match what the source (us) registered as, the same goes + * for the inner. The reason for this is to be able to determine via a sideband + * if the directory is trying to MITM. Since the Kpub is announced via the + * directory the source can fire up another client and check that the announced + * key is the same as the one it actually announced. + * + * At the samw time the sink can provide a different Kpub to the directory than + * it is using to request to open the source in order to be able to compartment + * keys used for directory access vs discovering sources. + */ + + uint8_t my_private_key[32] = {0}; + a12_set_session(&auth, pk, ds->aopts->opts->priv_ephem_key); + return auth; +} + +static void dir_a12srv(struct a12_state* S, int fd, void* tag) +{ + struct dirstate* ds = tag; + a12int_trace(A12_TRACE_DIRECTORY, "source_inbound_connected"); + +/* should not be able to happen at this stage, hello won't go through without + * the right authk and if you have that you're either source, sink or dirsrv + * and both sink and dirsrv would be able to math the pubk. */ + char* msg; + if (!anet_authenticate(S, fd, fd, &msg)){ + a12int_trace(A12_TRACE_SECURITY, "authentication_failed"); + return; + } + + a12int_trace(A12_TRACE_DIRECTORY, "source_inbound_ready"); + a12helper_a12cl_shmifsrv(S, ds->shmif, fd, fd, (struct a12helper_opts){ + .redirect_exit = ds->aopts->redirect_exit, + .devicehint_cp = ds->aopts->devicehint_cp, + .vframe_block = global.backpressure, + .vframe_soft_block = global.backpressure_soft, + .eval_vcodec = vcodec_tuning, + .bcache_dir = get_bcache_dir() + }); + shmifsrv_free(ds->shmif, SHMIFSRV_FREE_NO_DMS); + + shutdown(fd, SHUT_RDWR); + exit(EXIT_SUCCESS); +} + static void dir_to_shmifsrv(struct a12_state* S, struct a12_dynreq a, void* tag) { a12int_trace(A12_TRACE_DIRECTORY, "open_request_negotiated"); struct dirstate* ds = tag; + ds->req = a; pid_t fpid = fork(); @@ -514,28 +567,45 @@ static void dir_to_shmifsrv(struct a12_state* S, struct a12_dynreq a, void* tag) * directory versus the one it uses with us. */ snprintf(ds->aopts->opts->secret, 32, "%s", a.authk); ds->aopts->opts->force_ephemeral_k = true; -// ds->aopts->opts->expect_ephem_pubkey; + char* outhost; + uint16_t outport; + a12helper_keystore_hostkey("default", 0, + ds->aopts->opts->priv_ephem_key, &outhost, &outport); -/* a subtle difference here is that the the private key we should use in the - * setup (here same for ephem and real) is the one used to outbound connect to - * the directory and not a listening default. */ + ds->aopts->opts->pk_lookup = key_auth_dir; + ds->aopts->opts->pk_lookup_tag = ds; + memcpy(ds->aopts->opts->expect_ephem_pubkey, ds->req.pubk, 32); - a12cl_dispatch(ds->aopts, S, ds->shmif, ds->fd); +/* A subtle difference here is that the the private key we should use in the + * setup (here same for ephem and real) is the one used to outbound connect to + * the directory and not a listening default. We also want a timeout for the + * case where we listen but nobody connects to us (reachability?). Remove the + * host so that we don't try to bind the old outbound ip. */ + char* errmsg = NULL; + ds->aopts->host = NULL; + anet_listen(ds->aopts, &errmsg, dir_a12srv, ds); + fprintf(stderr, "%s", errmsg ? errmsg : ""); exit(EXIT_SUCCESS); } else if (fpid == -1){ fprintf(stderr, "fork_a12cl() couldn't fork new process, check ulimits\n"); shmifsrv_free(ds->shmif, SHMIFSRV_FREE_NO_DMS); - a12_channel_close(S); + shutdown(ds->fd, SHUT_RDWR); close(ds->fd); return; } else { -/* just ignore and return to caller, we might need a monitoring channel - * for multiplexing in tunnel- traffic */ +/* just ignore and return to caller, we might need a monitoring channel for + * multiplexing in tunnel- traffic - and in those cases we need to keep the + * ds->fd (directory connection) alive. the same goes for --exec mode, as we + * can supply more clients through the same directory setup we can keep it + * alive, but that has more considerations -- how many do we want? the other + * connections might require tunneling, so at first it is better to just go 1:1 + * for dirsrv connection and --exec pass. */ a12int_trace(A12_TRACE_SYSTEM, "client handed off to %d", (int)fpid); - a12_channel_close(S); shmifsrv_free(ds->shmif, SHMIFSRV_FREE_LOCAL); + shutdown(ds->fd, SHUT_RDWR); + wait(NULL); close(ds->fd); } } @@ -553,6 +623,7 @@ static void fork_a12cl_dispatch( fprintf(stderr, "fork_a12cl() couldn't fork new process, check ulimits\n"); shmifsrv_free(cl, SHMIFSRV_FREE_NO_DMS); a12_channel_close(S); + shutdown(fd, SHUT_RDWR); close(fd); return; } @@ -561,6 +632,7 @@ static void fork_a12cl_dispatch( a12int_trace(A12_TRACE_SYSTEM, "client handed off to %d", (int)fpid); a12_channel_close(S); shmifsrv_free(cl, SHMIFSRV_FREE_LOCAL); + shutdown(fd, SHUT_RDWR); close(fd); } } @@ -747,7 +819,7 @@ static bool tag_host(struct anet_options* anet, char* hoststr, const char** err) static bool show_usage(const char* msg) { - fprintf(stderr, "%s%sUsage:\n" + fprintf(stderr, "Usage:\n" "Forward local arcan applications (push): \n" " arcan-net [-Xtd] -s connpoint [tag@]host port\n" " (keystore-mode) -s connpoint tag@\n" @@ -809,7 +881,7 @@ static bool show_usage(const char* msg) "\nTrace groups (stderr):\n" "\tvideo:1 audio:2 system:4 event:8 transfer:16\n" "\tdebug:32 missing:64 alloc:128 crypto:256 vdetail:512\n" - "\tbinary:1024 security:2048 directory:4096\n\n", + "\tbinary:1024 security:2048 directory:4096\n\n%s%s", msg ? msg : "", msg ? "\n\n" : "" ); @@ -1094,6 +1166,7 @@ static int apply_commandline(int argc, char** argv, struct arcan_net_meta* meta) } else if (strcmp(argv[i], "-X") == 0){ opts->redirect_exit = NULL; + opts->devicehint_cp = NULL; } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--auth") == 0){ char msg[32]; @@ -1226,7 +1299,7 @@ static int apply_keystore_command(int argc, char** argv) * For inbound, there is an option of differentiation - a different keypair * could be returned based on the public key that the connecting party uses. */ -static struct pk_response key_auth_local(uint8_t pk[static 32]) +static struct pk_response key_auth_local(uint8_t pk[static 32], void* tag) { struct pk_response auth = {}; uint8_t my_private_key[32]; diff --git a/src/frameserver/net/default/net.c b/src/frameserver/net/default/net.c index 23fa57c96..3408edaa2 100644 --- a/src/frameserver/net/default/net.c +++ b/src/frameserver/net/default/net.c @@ -56,7 +56,7 @@ static struct { .trust_domain = "outbound" }; -static struct pk_response key_auth_local(uint8_t pk[static 32]) +static struct pk_response key_auth_local(uint8_t pk[static 32], void* tag) { struct pk_response auth = {0}; char* tmp; diff --git a/src/frameserver/util/anet_helper.c b/src/frameserver/util/anet_helper.c index d8c2a7d7c..e0560390f 100644 --- a/src/frameserver/util/anet_helper.c +++ b/src/frameserver/util/anet_helper.c @@ -119,9 +119,15 @@ static struct anet_cl_connection connect_to(struct anet_options* arg) return res; } -/* related to keystore - if we get multiple options for one key and don't - * have a provided host, port, enumerate those before failing */ - res.fd = anet_clfd(addr); +/* related to keystore - if we get multiple options for one key and don't have + * a provided host, port, enumerate those before failing. The retry count can + * be set to negative to go on practically indefinitely or to a > 0 number of + * tries. */ + while ((res.fd = anet_clfd(addr)) == -1 && arg->retry_count != 0){ + arg->retry_count--; + sleep(1); + } + if (-1 == res.fd){ char buf[64]; snprintf(buf, sizeof(buf), "couldn't connect to %s:%s\n", arg->host, arg->port); @@ -290,10 +296,10 @@ bool anet_listen(struct anet_options* args, char** errdst, /* build state machine, accept and dispatch */ for(;;){ - struct sockaddr_storage in_addr; + struct sockaddr in_addr; socklen_t addrlen = sizeof(addr); - int infd = accept(sockin_fd, (struct sockaddr*) &in_addr, &addrlen); + int infd = accept(sockin_fd, &in_addr, &addrlen); struct a12_state* ast = a12_server(args->opts); if (!ast){ if (errdst) @@ -302,6 +308,12 @@ bool anet_listen(struct anet_options* args, char** errdst, return false; } + char hostaddr[NI_MAXHOST]; + + int ec = getnameinfo(&in_addr, addrlen, + hostaddr, sizeof(hostaddr), NULL, 0, NI_NUMERICHOST); + a12_set_endpoint(ast, strdup(hostaddr)); + dispatch(ast, infd, tag); } } diff --git a/src/shmif/arcan_shmif_control.c b/src/shmif/arcan_shmif_control.c index 8d981b93b..435ebd85e 100644 --- a/src/shmif/arcan_shmif_control.c +++ b/src/shmif/arcan_shmif_control.c @@ -3004,6 +3004,12 @@ static char* spawn_arcan_net(const char* conn_src, int* dsock) if (!work) return NULL; +/* quick-workaround, the url format for keyid@ is in conflict with + * other forms like ident@key@ */ + const char* ident = getenv("A12_IDENT"); + if (!ident) + ident = "anon"; + bool weak; size_t start = a12_cp(conn_src, &weak); @@ -3054,11 +3060,13 @@ static char* spawn_arcan_net(const char* conn_src, int* dsock) if (pid == 0){ if (0 == fork()){ if (weak){ - execlp("arcan-net", "arcan-net", "--soft-auth", + execlp("arcan-net", "arcan-net", "-X", + "--ident", ident, "--soft-auth", "-S", tmpbuf, &work[start], port, (char*) NULL); } else { - execlp("arcan-net", "arcan-net", "-S", + execlp("arcan-net", "arcan-net", "-X", + "--ident", ident, "-S", tmpbuf, &work[start], port, (char*) NULL); }