Skip to content

Commit

Permalink
(a12) directory source-listen working
Browse files Browse the repository at this point in the history
This patch adds all the necessary plumbing to negotiate a connection
between source and sink where the sink and directory is on a public
facing IP and listens for an inbound connection.

While a lot of testing remains, particularly cleaning up timeouts
and error handling - the hard work for this feature is done with.

Next up would be to do the same for tunneled connections where the
dir_srv_worker reserves a tunnel-ID, bchunks source and sink together
with sockets that copy directly between the workers, with two commands
added to the protocol - one for saying how many bytes should be written
in the tunnel before resuming normal a12 processing, and one where
the same goes for reading.

Thereafter we have nat-punch and sink listen left for all the basic
communications. One part that is still in question is whether it should
be allowed for the source to specify / override the ephemeral key for
the outer connection. The reason for that would be to allow the source
to expose itself with one identity to the directory and another to
the sink.
  • Loading branch information
letoram committed Oct 17, 2023
1 parent 85c8564 commit e8a484e
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 157 deletions.
72 changes: 37 additions & 35 deletions src/a12/a12.c
Expand Up @@ -15,6 +15,7 @@
#include <string.h>
#include <math.h>
#include <assert.h>
#include <ctype.h>

#include "a12.h"
#include "a12_int.h"
Expand Down Expand Up @@ -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]
Expand All @@ -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;
Expand All @@ -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 */
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/a12/a12.h
Expand Up @@ -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. */
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion src/a12/a12_int.h
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
16 changes: 13 additions & 3 deletions src/a12/net/HACKING.md
Expand Up @@ -545,16 +545,15 @@ 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
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
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/a12/net/a12_helper_srv.c
Expand Up @@ -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. */
Expand Down

0 comments on commit e8a484e

Please sign in to comment.