diff --git a/src/a12/a12.c b/src/a12/a12.c index eefa12ec8..d6ca4dffd 100644 --- a/src/a12/a12.c +++ b/src/a12/a12.c @@ -610,7 +610,11 @@ struct a12_state* a12_client(struct a12_context_options* opt) /* double-round, start by generating ephemeral key */ else { mode = HELLO_MODE_EPHEMPK; - x25519_private_key(S->keys.ephem_priv); + if (opt->force_ephemeral_k){ + memcpy(S->keys.ephem_priv, opt->priv_ephem_key, 32); + } + else + x25519_private_key(S->keys.ephem_priv); x25519_public_key(S->keys.ephem_priv, outpk); S->authentic = AUTH_POLITE_HELLO_SENT; } @@ -1623,12 +1627,21 @@ static void hello_auth_server_hello(struct a12_state* S) /* public key is ephemeral, generate new pair, send a hello out with the new * one THEN derive new keys for authentication and so on. After this the - * conneciton is flows just like if the ephem mode wasn't used - the client + * connection flows just like if the ephem mode wasn't used - the client * will send its real Pk and we respond in kind. The protection this affords us * is that you need an active MiM to gather Pks for tracking. */ if (cfl == HELLO_MODE_EPHEMPK){ uint8_t ek[32]; - x25519_private_key(ek); + if (S->opts->force_ephemeral_k){ + if (memcmp(S->opts->expect_ephem_pubkey, remote_pubk, 32) != 0){ + a12int_trace(A12_TRACE_SECURITY, "force_ephem_fail"); + fail_state(S); + return; + } + memcpy(ek, S->opts->priv_ephem_key, 32); + } + else + x25519_private_key(ek); x25519_public_key(ek, pubk); arcan_random(nonce, 8); send_hello_packet(S, HELLO_MODE_EPHEMPK, pubk, nonce); diff --git a/src/a12/a12.h b/src/a12/a12.h index 31255b89d..1bae34b0e 100644 --- a/src/a12/a12.h +++ b/src/a12/a12.h @@ -83,6 +83,11 @@ 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; diff --git a/src/a12/net/net.c b/src/a12/net/net.c index 4170222b0..3db6f74ea 100644 --- a/src/a12/net/net.c +++ b/src/a12/net/net.c @@ -60,7 +60,7 @@ static struct { } global = { .backpressure_soft = 2, .backpressure = 6, - .directory = -1, + .directory = -1 }; static const char* trace_groups[] = { @@ -418,13 +418,12 @@ static void single_a12srv(struct a12_state* S, int fd, void* tag) } } -static void dir_to_shmifsrv(struct a12_state* S, struct a12_dynreq, void* tag) -{ - a12int_trace(A12_TRACE_DIRECTORY, "open_request_negotiated"); -/* here, we fork() into listening on our registered port, with the prefilled - * authk and a specialised key-auth that only ephemerally accepts (unless set - * to trust-transitive), passing the shmifsrv connection along */ -} +static void dir_to_shmifsrv(struct a12_state* S, struct a12_dynreq a, void* tag); +struct dirstate { + int fd; + struct anet_options* aopts; + struct shmifsrv_client* shmif; +}; static void a12cl_dispatch( struct anet_options* args, @@ -444,9 +443,17 @@ static void a12cl_dispatch( * special in the sense that we hold on to the shmifsrv_client for a bit and * if we get a dir-open we latch the two together. */ if (a12_remote_mode(S) == ROLE_DIR){ + struct dirstate* ds = malloc(sizeof(struct dirstate)); + *ds = (struct dirstate){ + .fd = fd, + .aopts = args, + .shmif = cl + }; + global.dircl.dir_source = dir_to_shmifsrv; - global.dircl.dir_source_tag = &cl; + global.dircl.dir_source_tag = ds; anet_directory_cl(S, global.dircl, fd, fd); + free(ds); } else /* note that the a12helper will do the cleanup / free */ @@ -460,6 +467,79 @@ static void a12cl_dispatch( close(fd); } +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; + + pid_t fpid = fork(); + +/* Here, we fork() into listening on our registered port, this is the same as + * the cldispatch that forks. We shouldn't be in a path where any substantial + * POSIX 'technically UB territory buuuut' approach to the fork pattern has any + * real effect. + * + * When things are a bit more robust, switch to the fork-fexec self approach + * anyhow for IPC cleanliness, it's just inheriting the shmifsrc-client setup + * that is a bit of a hassle. */ + + if (fpid == 0){ +/* Trust the directory server provided secret. + * + * A nuanced option here is if to set the accept_n_pk_unknown to 1 or not. + * The consequence is that the directory or the sink gets to install one key + * we trust for inbound use. + * + * One probably would want to set that to a specific group for this context + * of use, or mark the transitive origin / history. If that doesn't happen + * and the keystore gets re-used for sourcing something else, that installed + * key is part of the general trusted base. + * + * At the same time tracking the key, and depending on connection mode, add + * that as a possible outbound target with the IP it connected from if an + * interesting option for future discovery both LAN and WAN as well as + * attribution. + * + * Block the behaviour outright for now, and don't forget that the parent might + * actually be running / bleeding some state into us from the command line that + * applied to the outbound connection when registering to the directory that + * should not apply now in the role of a source. + */ + global.soft_auth = true; + global.accept_n_pk_unknown = 0; + +/* the shared secret to protect the initial hello is no longer optional, and we + * have an added touch of forcing the 'ephemeral' key to be the one supplied by + * 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; +// ds->aopts->opts->expect_ephem_pubkey; + +/* 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. */ + + a12cl_dispatch(ds->aopts, S, ds->shmif, ds->fd); + 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); + close(ds->fd); + return; + } + else { +/* just ignore and return to caller, we might need a monitoring channel + * for multiplexing in tunnel- traffic */ + a12int_trace(A12_TRACE_SYSTEM, "client handed off to %d", (int)fpid); + a12_channel_close(S); + shmifsrv_free(ds->shmif, SHMIFSRV_FREE_LOCAL); + close(ds->fd); + } +} + static void fork_a12cl_dispatch( struct anet_options* args, struct a12_state* S, struct shmifsrv_client* cl, int fd) @@ -692,6 +772,7 @@ static bool show_usage(const char* msg) "\t-a, --auth n \t Read authentication secret from stdin (maxlen:32)\n" "\t \t if [n] is provided, n keys added to trusted\n" "\t --soft-auth \t Permit unknown via authentication secret (password)\n" + "\t --allow-pk n \t Explicitly permit (b64-encoded) Kpub\n" "\t-T, --trust s \t Specify trust domain for splitting keystore\n" "\t \t outbound connections default to 'outbound' while\n" "\t \t serving/listening defaults to a wildcard ('*')\n\n"