From 4f9d9096f11186277ef55095d13002a58245a7ec Mon Sep 17 00:00:00 2001 From: bjornstahl Date: Thu, 19 Oct 2023 20:02:39 +0200 Subject: [PATCH] (a12) warning nits, src-port ctrl, prep-tunnel 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() --- src/a12/a12.c | 45 +++++++++++++++++++----------------- src/a12/a12.h | 29 ++++++++++++----------- src/a12/net/HACKING.md | 19 +++++---------- src/a12/net/dir_cl.c | 21 +++++++---------- src/a12/net/dir_srv.c | 15 ++++++++---- src/a12/net/dir_srv_worker.c | 35 ++++++++++++++++++++++++---- src/a12/net/directory.h | 4 ++++ src/a12/net/net.c | 45 +++++++++++++++++++++++++++--------- 8 files changed, 132 insertions(+), 81 deletions(-) diff --git a/src/a12/a12.c b/src/a12/a12.c index 2b33aeb48..d55300df9 100644 --- a/src/a12/a12.c +++ b/src/a12/a12.c @@ -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; } @@ -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); @@ -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); @@ -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) { @@ -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); @@ -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; +} diff --git a/src/a12/a12.h b/src/a12/a12.h index f54228f66..f80851e79 100644 --- a/src/a12/a12.h +++ b/src/a12/a12.h @@ -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]; @@ -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; @@ -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, @@ -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. * @@ -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); diff --git a/src/a12/net/HACKING.md b/src/a12/net/HACKING.md index 034c311f0..29c65dae2 100644 --- a/src/a12/net/HACKING.md +++ b/src/a12/net/HACKING.md @@ -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. @@ -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 diff --git a/src/a12/net/dir_cl.c b/src/a12/net/dir_cl.c index 2617b38e3..621ad6036 100644 --- a/src/a12/net/dir_cl.c +++ b/src/a12/net/dir_cl.c @@ -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) { @@ -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); @@ -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) @@ -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; @@ -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); } diff --git a/src/a12/net/dir_srv.c b/src/a12/net/dir_srv.c index 3ac57bd27..4786952aa 100644 --- a/src/a12/net/dir_srv.c +++ b/src/a12/net/dir_srv.c @@ -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]; @@ -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] = '_'; } @@ -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; diff --git a/src/a12/net/dir_srv_worker.c b/src/a12/net/dir_srv_worker.c index dc4482e79..d9fe8194f 100644 --- a/src/a12/net/dir_srv_worker.c +++ b/src/a12/net/dir_srv_worker.c @@ -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( @@ -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); @@ -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 @@ -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 = { @@ -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) @@ -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); @@ -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; } @@ -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) ); diff --git a/src/a12/net/directory.h b/src/a12/net/directory.h index da33853d1..d4f90c35a 100644 --- a/src/a12/net/directory.h +++ b/src/a12/net/directory.h @@ -9,6 +9,8 @@ struct anet_dirsrv_opts { int basedir; struct appl_meta dir; size_t dir_count; + + bool allow_tunnel; const char* allow_src; const char* allow_dir; const char* allow_appl; @@ -42,10 +44,12 @@ struct anet_dircl_opts { bool block_state; bool block_log; bool keep_appl; + bool request_tunnel; char ident[16]; void (*dir_source)(struct a12_state*, struct a12_dynreq req, void* tag); void *dir_source_tag; + uint16_t source_port; struct appl_meta outapp; diff --git a/src/a12/net/net.c b/src/a12/net/net.c index f55a595fd..0d3ce750c 100644 --- a/src/a12/net/net.c +++ b/src/a12/net/net.c @@ -55,12 +55,19 @@ static struct { struct anet_dircl_opts dircl; char* trust_domain; char* path_self; + char* outbound_tag; volatile bool flag_rescan; } global = { .backpressure_soft = 2, .backpressure = 6, - .directory = -1 + .directory = -1, + .outbound_tag = "default", + .dircl = { + .source_port = 6681 + .dirsrv = { + .allow_tunnel = true + } }; static const char* trace_groups[] = { @@ -479,14 +486,15 @@ static struct pk_response key_auth_dir(uint8_t pk[static 32], void* tag) * 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); + char* tmp; + uint16_t tmpport; + uint8_t my_private_key[32]; + a12helper_keystore_hostkey( + global.outbound_tag, 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); + return auth; } @@ -566,15 +574,11 @@ static void dir_to_shmifsrv(struct a12_state* S, struct a12_dynreq a, void* tag) * the sink in order to let them differentiate the identity it uses with the * directory versus the one it uses with us. */ snprintf(ds->aopts->opts->secret, 32, "%s", a.authk); - ds->aopts->opts->force_ephemeral_k = true; char* outhost; uint16_t outport; - a12helper_keystore_hostkey("default", 0, - ds->aopts->opts->priv_ephem_key, &outhost, &outport); 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); /* 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 @@ -811,6 +815,7 @@ static bool tag_host(struct anet_options* anet, char* hoststr, const char** err) *toksep = '\0'; anet->key = hoststr; + global.outbound_tag = hoststr; anet->keystore.type = A12HELPER_PROVIDER_BASEDIR; anet->keystore.directory.dirfd = a12helper_keystore_dirfd(err); @@ -860,12 +865,15 @@ static bool show_usage(const char* msg) "\t --ident name \t When attaching as a source or directory, identify as [name]\n" "\t --keep-alive \t Keep connection alive and print changes to the directory\n" "\t --push-appl s \t Push [s] from APPLBASE to the server\n" + "\t --tunnel \t Default request tunnelling as source/sink connection\n" "\t --block-log \t Don't attempt to forward script errors or crash logs\n" + "\t --source-port \t When sourcing use this port for listening\n" "\t --block-state \t Don't attempt to synch state before/after running appl\n\n" "Directory server options: \n" "\t --allow-src s \t Let clients in trust group [s, all=*] register as sources\n" "\t --allow-appl s \t Let clients in trust group [s, all=*] update appls and resources\n" "\t --allow-dir s \t Let clients in trust group [s, all=*] register as directories\n\n" + "\t --block-tunnel \t Disallow tunneling traffic between isolated sources/sinks\n\n" "Environment variables:\n" "\tARCAN_STATEPATH\t Used for keystore and state blobs (sensitive)\n" #ifdef WANT_H264_ENC @@ -1019,6 +1027,12 @@ static int apply_commandline(int argc, char** argv, struct arcan_net_meta* meta) return show_usage("--allow-dir: missing group tag name"); global.dirsrv.allow_dir = argv[i]; } + else if (strcmp(argv[i], "--tunnel") == 0){ + global.dircl.request_tunnel = true; + } + else if (strcmp(argv[i], "--block-tunnel") == 0){ + global.dirsrv.allow_tunnel = false; + } else if (strcmp(argv[i], "--keep-alive") == 0){ global.keep_alive = true; } @@ -1151,6 +1165,15 @@ static int apply_commandline(int argc, char** argv, struct arcan_net_meta* meta) opts->opts->local_role = ROLE_SOURCE; return i; } + else if (strcmp(argv[i], "--source-port") == 0){ + i++; + if (i == argc) + return show_usage("--source-port without port argument"); + + global.dircl.source_port = (uint16_t) strtoul(argv[i], NULL, 10); + if (!global.dircl.source_port) + return show_usage("--source-port invalid"); + } else if (strcmp(argv[i], "--directory") == 0){ if (!getenv("ARCAN_APPLBASEPATH")){ return show_usage("--directory without ARCAN_APPLBASEPATH set");