diff --git a/faithd/src/client/client.c b/faithd/src/client/client.c index 4277342..c20d7a6 100644 --- a/faithd/src/client/client.c +++ b/faithd/src/client/client.c @@ -68,12 +68,15 @@ struct faith_client { uint16_t ev_queue_back; uint16_t ev_queue_len; - SSL *ssl; + SSL *ssl; + SSL_CTX *ssl_ctx; uint16_t proto_ver; client_identity_t ident; + faith_client_device_link_req_t *pending_device_link_req; + uint64_t nonce_tmp; }; @@ -88,7 +91,8 @@ client_push_event_ex(faith_client_t *client, faith_event_type_t type, /* Enqueue event */ - if (client->ev_queue_len == MAX_QUEUED_EVENTS) { + if (client->ev_queue_len == MAX_QUEUED_EVENTS || + client->ev_queue_back > MAX_QUEUED_EVENTS - 1) { pthread_mutex_unlock(&client->lock); nob_log(ERROR, "Event queue overflow."); return FAITH_ERR_OVERFLOW; @@ -251,6 +255,108 @@ static SSL_CTX *client_create_ssl_ctx(faith_client_t *client) { return ctx; } +static SSL *client_create_ssl_conn(faith_client_t *client, SSL_CTX *ctx, + int sockfd) { + if (!client || !ctx || sockfd < 0) + return NULL; + + SSL *ssl = NULL; + + ssl = SSL_new(ctx); + if (ssl == NULL) { + nob_log(ERROR, "Failed to create SSL object for client"); + return NULL; + } + + if (SSL_set_fd(ssl, sockfd) != 1) { + nob_log(ERROR, "Failed to set FD of SSL connection for client"); + return NULL; + } + + if (!client->insecure_skip_verify && client->server_name[0] != '\0') { + if (SSL_set_tlsext_host_name(ssl, client->server_name) != 1) { + nob_log(ERROR, "Failed to set TLS hostname to '%s'", client->server_name); + return NULL; + } + + if (SSL_set1_host(ssl, client->server_name) != 1) { + nob_log(ERROR, "Failed to set TLS hostname to '%s'", client->server_name); + return NULL; + } + } + + if (SSL_connect(ssl) <= 0) { + nob_log(ERROR, "TLS handshake failed"); + return NULL; + } + + return ssl; +} + +static void client_cleanup_connection(faith_client_t *client) { + if (!client) { + return; + } + + pthread_mutex_lock(&client->write_lock); + + if (client->ssl) { + SSL_free(client->ssl); + client->ssl = NULL; + } + + if (client->ssl_ctx) { + SSL_CTX_free(client->ssl_ctx); + client->ssl_ctx = NULL; + } + + pthread_mutex_unlock(&client->write_lock); + + pthread_mutex_lock(&client->lock); + int fd = client->sockfd; + client->sockfd = -1; + pthread_mutex_unlock(&client->lock); + + if (fd >= 0) { + close(fd); + } +} + +static faith_status_code_t client_init_ssl(faith_client_t *client) { + if (!client) + return FAITH_ERR_INVALID; + + int fd = client_init_sock(client->host, client->port); + + if (fd < 0) { + client_push_event(client, FAITH_EVENT_ERROR, 0, 0, + "Client connection failed. Server down?"); + return FAITH_ERR_IO; + } + + pthread_mutex_lock(&client->lock); + client->sockfd = fd; + pthread_mutex_unlock(&client->lock); + + SSL_CTX *ctx = NULL; + ctx = client_create_ssl_ctx(client); + if (!ctx) { + client_push_event(client, FAITH_EVENT_ERROR, 0, 0, + "TLS context creation failed"); + return FAITH_ERR_SSL; + } + SSL *ssl = client_create_ssl_conn(client, ctx, fd); + if (!ssl) { + client_push_event(client, FAITH_EVENT_ERROR, 0, 0, "TLS connection failed"); + return FAITH_ERR_SSL; + } + + client->ssl = ssl; + client->ssl_ctx = ctx; + + return FAITH_OK; +} + static faith_status_code_t encode_ping(uint8_t *out_buf, size_t *out_size, size_t buf_cap_in_bytes, uint64_t nonce, uint64_t sent_at_ms) { @@ -381,42 +487,90 @@ static void client_handle_pong(faith_client_t *client, faith_frame_t *frame) { } } -static void client_handle_envl(faith_client_t *client, faith_frame_t *frame) { +static faith_status_code_t client_handle_envl(faith_client_t *client, + faith_frame_t *frame) { faith_envelope_t envl; _FH_CHECK(faith_decode_envelope(frame->payload, frame->payload_size, &envl)); - switch (envl.type) { case FAITH_ENVELOPE_MSG_SEND: { if (!faith_client_id_equal(envl.recipient_id, client->ident.auth_id) || - _fh_rc != FAITH_OK || faith_client_id_equal(envl.sender_id, FAITH_CLIENT_ID_NONE)) { - nob_log(ERROR, "[client - reader] Got sent invalid message envelope."); - break; + nob_log(ERROR, "[client - reader] Got sent invalid MESSAGE envelope."); + return _fh_rc; } if (envl.body_size > FAITH_MAX_MSG_SIZE) { nob_log(ERROR, "[client - reader] Got sent too large message."); - break; + return FAITH_ERR_OVERFLOW; } char *msg = malloc(envl.body_size + 1); if (!msg) { nob_log( ERROR, "[client - reader] Failed to allocate memory for received message."); - break; + return FAITH_ERR_NOMEM; } memcpy(msg, envl.body, envl.body_size); msg[envl.body_size] = '\0'; - _FH_CHECK(client_push_event_ex(client, FAITH_EVENT_MESSAGE_RECEIVED, 0, 0, - NULL, msg, envl.body_size)); + _FH_CHECK_RETURN(client_push_event_ex(client, FAITH_EVENT_MESSAGE_RECEIVED, + 0, 0, NULL, msg, envl.body_size)); + + break; + } + case FAITH_ENVELOPE_DEVICE_LINK_REQUEST: { + /* Client-side rejection of multiple pending device link requests. TOOD: + * This has to also be server enforced.*/ + if (client->pending_device_link_req != NULL) { + if(g_log_enable_tracing) { + nob_log(WARNING, "[client - reader] Rejecting new DEVICE_LINK_REQUEST; " + "A link request is already pending for this client."); + } + return FAITH_OK; + } + + if (!faith_client_id_equal(envl.recipient_id, client->ident.auth_id)) { + char recipient_id_hex[33]; + char auth_id_hex[33]; + _FH_CHECK_RETURN( + faith_id128_to_hex(envl.recipient_id.bytes, recipient_id_hex)); + _FH_CHECK_RETURN( + faith_id128_to_hex(client->ident.auth_id.bytes, auth_id_hex)); + + nob_log(ERROR, + "[client - reader] Got sent invalid DEVICE_LINK_REQUEST envelope. Expected " + "envelope recipient_id to be %s but got %s.", + auth_id_hex, recipient_id_hex); + return _fh_rc; + } + + // Allocate pending device link request + faith_client_device_link_req_t* req = calloc(1, sizeof(*req)); + _FH_CHECK_RETURN(faith_decode_device_link_req(envl.body, envl.body_size, req)); + + client->pending_device_link_req = req; + + char msg[512]; + char device_id_hex[33]; + + _FH_CHECK_RETURN( + faith_id128_to_hex(req->device_id_new.bytes, device_id_hex)); + + snprintf(msg, sizeof(msg), + "A new device requested to log in (device_id=%s). Accept?", + device_id_hex); + _FH_CHECK_RETURN(client_push_event_ex(client, + FAITH_EVENT_DEVICE_LINK_REQUEST, 0, 0, + msg, NULL, envl.body_size)); break; } default: break; } + + return FAITH_OK; } static void *reader(void *arg) { @@ -438,6 +592,7 @@ static void *reader(void *arg) { break; } case FAITH_MSG_ENVL: { + printf("HANDLING ENVELOPE.\n"); client_handle_envl(client, &frame); break; } @@ -508,6 +663,34 @@ static faith_status_code_t client_send_envelope(SSL *ssl, return rc; } +static faith_status_code_t +client_send_envelope_locked(faith_client_t *client, + const faith_envelope_t *envl) { + if (!client || !envl) { + return FAITH_ERR_INVALID; + } + + pthread_mutex_lock(&client->write_lock); + + if (!atomic_load(&client->connected) || !client->ssl) { + pthread_mutex_unlock(&client->write_lock); + return FAITH_ERR_NOT_CONNECTED; + } + + _FH_CHECK(client_send_envelope(client->ssl, envl)); + faith_status_code_t rc = _fh_rc; + + if (rc != FAITH_OK) { + nob_log(ERROR, + "[client] Failed to send envelope (type=%s, body_size=%" PRIu32 + ") to server.", + faith_envelope_name(envl->type), envl->body_size); + } + + pthread_mutex_unlock(&client->write_lock); + return rc; +} + static faith_status_code_t faith_client_send_hello(faith_client_t *client) { if (!client) return FAITH_ERR_INVALID; @@ -554,44 +737,33 @@ static faith_status_code_t faith_client_send_hello(faith_client_t *client) { sizeof(envl.sender_id)); envl.body = body; - printf("Envl body size: %zu\n", sizeof(body)); envl.body_size = sizeof(body); - char auth_id_hex[33]; - char device_id_hex[33]; - _FH_CHECK_RETURN( - faith_id128_to_hex(client->ident.auth_id.bytes, auth_id_hex)); - _FH_CHECK_RETURN( - faith_id128_to_hex(client->ident.device_id.bytes, device_id_hex)); - - printf("CLIENT AUTH ID: %s\n", auth_id_hex); - printf("DEVICE AUTH ID: %s\n", device_id_hex); - - printf("PUBKEY =====================\n"); - for (size_t i = 0; i < FAITH_ED25519_PUBLIC_KEY_SIZE; i++) { - printf("%02x", (unsigned int)client->ident.public_key[i]); - } - printf("\n"); - printf("===========================\n"); - - pthread_mutex_lock(&client->write_lock); - _FH_CHECK(client_send_envelope(client->ssl, &envl)); - pthread_mutex_unlock(&client->write_lock); - - return _fh_rc; + return client_send_envelope_locked(client, &envl); } static faith_status_code_t client_handle_challenge(faith_client_t *client, faith_envelope_t *challenge_envl) { + if (!client || !challenge_envl) + return FAITH_ERR_INVALID; int ok = challenge_envl->type == FAITH_ENVELOPE_CHALLENGE && + challenge_envl->body && challenge_envl->body_size == FAITH_ENVL_HELLO_CHALLENGE_BODY_SIZE; if (!ok) { - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Unexpected or malformed HELLO envelope response"); + nob_log(ERROR, + "Unexpected or malformed server envelope response to HELLO " + "envelope. Expected: " + "type=FAITH_ENVELOPE_CHALLENGE, body_size=%" PRIu32 + ", Got: type=%s, body_size=%" PRIu32, + FAITH_ENVL_HELLO_CHALLENGE_BODY_SIZE, + faith_envelope_name(challenge_envl->type), + challenge_envl->body_size); return FAITH_ERR_INVALID; } + + /* Bail if the CHALLENGE recipient ID does not equal the client auth ID */ if (!faith_client_id_equal(challenge_envl->recipient_id, client->ident.auth_id)) { char auth_id_hex[33]; @@ -602,17 +774,15 @@ client_handle_challenge(faith_client_t *client, _FH_CHECK_RETURN(faith_id128_to_hex(challenge_envl->recipient_id.bytes, recipient_id_hex)); - char msg[512]; - snprintf(msg, sizeof(msg), - "Invalid Auth ID in CHALLENGE envelope. Auth ID (%s) does not " - "match our own one (%s).", - recipient_id_hex, auth_id_hex); - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, msg); + nob_log(ERROR, + "Invalid Auth ID in CHALLENGE envelope. Recipient auth ID (%s) " + "does not " + "match our own one (%s).", + recipient_id_hex, auth_id_hex); return FAITH_ERR_INVALID; } - /* 1. Read CHALLENGE from server, 64 bit integer of server nonce in envelope - * body. */ + /* 1. Read CHALLENGE contents from server: 64 bit integer server nonce */ uint64_t server_nonce = faith_read_u64_be(challenge_envl->body); @@ -623,200 +793,183 @@ client_handle_challenge(faith_client_t *client, * client_nonce, * server_nonce * } */ - uint8_t msg_buf[FAITH_HELLO_HANDSHAKE_SIGNATURE_SIZE]; + + faith_signature_hello_handshake_t sign_msg = {0}; + + sign_msg.auth_id = client->ident.auth_id; + + memcpy(sign_msg.public_key, client->ident.public_key, + FAITH_ED25519_PUBLIC_KEY_SIZE); + + sign_msg.client_nonce = client->nonce_tmp; + sign_msg.server_nonce = server_nonce; + + uint8_t msg_buf[sizeof(sign_msg)]; { - _FH_CHECK(faith_gen_signing_msg_buf( - msg_buf, sizeof(msg_buf), &client->ident.auth_id, - client->ident.public_key, client->nonce_tmp, server_nonce)); + _FH_CHECK(faith_gen_sign_buf_hello_handshake(msg_buf, sizeof(msg_buf), + &sign_msg)); + if (_fh_rc != FAITH_OK) { - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Failed to generate signing message buffer."); - return FAITH_ERR_CRYPTO; + nob_log(ERROR, + "Failed to generate HELLO handshake signing message buffer."); + return _fh_rc; } } /* 3. Generating the 64 byte cryptographic signature from the message buffer */ uint8_t signature[FAITH_ED25519_SIGNATURE_SIZE]; - size_t signature_size; + size_t signature_size = 0; { + /* This returns FAITH_ERR_SSL if signature_size != + * FAITH_ED25519_SIGNATURE_SIZE */ _FH_CHECK(faith_gen_signature(client->ident.keypair, signature, &signature_size, msg_buf, sizeof(msg_buf))); if (_fh_rc != FAITH_OK) { - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Failed to generate signature."); - return FAITH_ERR_CRYPTO; + nob_log(ERROR, "Failed to generate signature for CHALLENGE_RESPONSE."); + return _fh_rc; } } /* 4. Send CHALLENGE_RESPONSE back to server. Contains 64 byte client * signature in envelope body*/ - printf("SIGNATURE =====================\n"); - for (size_t i = 0; i < FAITH_ED25519_SIGNATURE_SIZE; i++) { - printf("%02x", (unsigned int)signature[i]); - } - printf("\n"); - printf("========================[i]===\n"); - - faith_envelope_t challenge_response_envl; - challenge_response_envl.type = FAITH_ENVELOPE_CHALLENGE_RESPONSE; - challenge_response_envl.sender_id = client->ident.auth_id; + faith_envelope_t envl = {0}; + envl.type = FAITH_ENVELOPE_CHALLENGE_RESPONSE; + envl.sender_id = client->ident.auth_id; - challenge_response_envl.body = signature; - challenge_response_envl.body_size = sizeof(signature); + envl.body = signature; + envl.body_size = sizeof(signature); - { - pthread_mutex_lock(&client->write_lock); - _FH_CHECK(client_send_envelope(client->ssl, &challenge_response_envl)); - pthread_mutex_unlock(&client->write_lock); - } - - return FAITH_OK; + return client_send_envelope_locked(client, &envl); } static faith_status_code_t client_make_handshake(faith_client_t *client) { if (!client) return FAITH_ERR_INVALID; + if (g_log_enable_tracing) nob_log(INFO, "[client] Sending HELLO..."); - { - _FH_CHECK(faith_client_send_hello(client)); - if (_fh_rc == FAITH_OK) { - if (g_log_enable_tracing) - nob_log(INFO, "[client] Sent HELLO."); - } else { - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Failed to send HELLO"); - return _fh_rc; - } - } + faith_status_code_t _fh_result = FAITH_OK; + + /* 1. Send HELLO envelope to the server */ + _FH_CHECK_RETURN(faith_client_send_hello(client)); + + if (g_log_enable_tracing) + nob_log(INFO, "[client] Sent HELLO."); + /* 2. Wait for server response by synchronously reading the next server + * frame. An evelope frame of type CHALLENGE is expected. */ if (g_log_enable_tracing) - nob_log(INFO, "[client] Waiting for server HELLO_OK response ..."); + nob_log(INFO, "[client] Waiting for server CHALLENGE response ..."); faith_frame_t frame = {0}; - { - _FH_CHECK(faith_read_frame_sync(client->ssl, &frame)); - if (_fh_rc != FAITH_OK) { - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Failed to read HELLO response"); - return _fh_rc; - } - } + _FH_CHECK_RETURN(faith_read_frame_sync(client->ssl, &frame)); - if (frame.msg_type != FAITH_MSG_ENVL) { - faith_frame_free(&frame); - client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Unexpected HELLO response"); - return FAITH_ERR_INVALID; - } + if (frame.msg_type != FAITH_MSG_ENVL) + _FH_RETURN_DEFER(FAITH_ERR_INVALID); + printf("GOT SENT CHALLENGE.\n"); faith_envelope_t envl; - { - _FH_CHECK(faith_decode_envelope(frame.payload, frame.payload_size, &envl)); - } + _FH_CHECK_DEFER( + faith_decode_envelope(frame.payload, frame.payload_size, &envl)); - { - _FH_CHECK(client_handle_challenge(client, &envl)); - if (_fh_rc != FAITH_OK) { - faith_frame_free(&frame); - return _fh_rc; - } - } + /* 3. Handle the CHALLENGE envelope sent by the server. This verifies the type + * of the envelope (ensures CHALLENGE). */ + _FH_CHECK_DEFER(client_handle_challenge(client, &envl)); - if (g_log_enable_tracing) - nob_log(INFO, "[client] Got HELLO_OK response. Server acknowledged client " - "successfully"); + faith_frame_free(&frame); + + /* ========================== */ + /* Read next frame */ + /* ========================== */ + + printf("Waiting for new frame...\n"); + _FH_CHECK_RETURN(faith_read_frame_sync(client->ssl, &frame)); + printf("Got new frame...\n"); + + _FH_CHECK_DEFER( + faith_decode_envelope(frame.payload, frame.payload_size, &envl)); + + switch (envl.type) { + case FAITH_ENVELOPE_HELLO_OK: { + /* Authorization successful */ + if (g_log_enable_tracing) + nob_log(INFO, + "[client] Got HELLO_OK response. Server acknowledged client " + "successfully."); + _FH_RETURN_DEFER(FAITH_OK); + } + case FAITH_ENVELOPE_DEVICE_AUTH_PENDING: { + if (g_log_enable_tracing) + nob_log( + INFO, + "Device authorization pending. Waiting for an already authorization " + "device to respond to the request."); + _FH_RETURN_DEFER(FAITH_OK); + } + default: + nob_log(ERROR, + "Received invalid server envelope response to " + "CHALLENGE_RESPONSE. Got %s", + faith_envelope_name(envl.type)); + _FH_RETURN_DEFER(FAITH_ERR_INVALID); + } return FAITH_OK; + +defer: + faith_frame_free(&frame); + return _fh_result; } static void *faith_client_thread_routine(void *arg) { faith_client_t *client = arg; - - uint32_t backoff_ms = 250; + uint32_t backoff_ms = 250; while (client_is_running(client)) { { _FH_CHECK(client_push_event(client, FAITH_EVENT_CONNECTING, 0, 0, NULL)); } - int fd = client_init_sock(client->host, client->port); - - if (fd < 0) { - _FH_CHECK(client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, - "Connect failed. Server down?")); - _sleep_ms(backoff_ms); - backoff_ms = client_next_backoff_ms(backoff_ms); - continue; - } - - pthread_mutex_lock(&client->lock); - client->sockfd = fd; - pthread_mutex_unlock(&client->lock); - - SSL_CTX *ctx = NULL; - SSL *ssl = NULL; - - ctx = client_create_ssl_ctx(client); - if (!ctx) { - client_push_event(client, FAITH_EVENT_ERROR, 0, 0, "TLS context failed"); - goto fail; - } - - ssl = SSL_new(ctx); - if (ssl == NULL) { - nob_log(ERROR, "Failed to create SSL object for client"); - goto fail; - } - - if (SSL_set_fd(ssl, fd) != 1) { - nob_log(ERROR, "Failed to set FD of SSL connection for client"); - goto fail; - } - - if (!client->insecure_skip_verify && client->server_name[0] != '\0') { - if (SSL_set_tlsext_host_name(ssl, client->server_name) != 1) { - nob_log(ERROR, "Failed to set TLS hostname to '%s'", - client->server_name); - goto fail; - } - - if (SSL_set1_host(ssl, client->server_name) != 1) { - nob_log(ERROR, "Failed to set TLS hostname to '%s'", - client->server_name); + /* Init SSL for client connection (SSL* and SSL_CTX*) */ + { + _FH_CHECK(client_init_ssl(client)); + if (_fh_rc != FAITH_OK) { + ERR_print_errors_fp(stderr); goto fail; } } - client->ssl = ssl; + atomic_store(&client->connected, true); - if (SSL_connect(ssl) <= 0) { - nob_log(ERROR, "TLS handshake failed"); - goto fail; - } backoff_ms = 250; + /* Make protocol handshake with server */ { - _FH_CHECK(client_push_event(client, FAITH_EVENT_CONNECTED, 0, 0, NULL)); + _FH_CHECK(client_make_handshake(client)); if (_fh_rc != FAITH_OK) { goto fail; } } + /* If successful, push CONNECTED event */ { - _FH_CHECK(client_make_handshake(client)); + _FH_CHECK(client_push_event(client, FAITH_EVENT_CONNECTED, 0, 0, NULL)); if (_fh_rc != FAITH_OK) { goto fail; } } + /* Main client loop, starts reader_thread and pinger_thread */ client_run_connected(client); + atomic_store(&client->connected, false); + + /* Send DISCONNECTED event when client closed */ { _FH_CHECK(client_push_event(client, FAITH_EVENT_DISCONNECTED, 0, 0, "connection closed")); @@ -826,26 +979,7 @@ static void *faith_client_thread_routine(void *arg) { } fail: - ERR_print_errors_fp(stderr); - - if (ssl) { - SSL_shutdown(ssl); - SSL_free(ssl); - ssl = NULL; - client->ssl = NULL; - } - - if (ctx) { - SSL_CTX_free(ctx); - ctx = NULL; - } - - close(fd); - - pthread_mutex_lock(&client->lock); - client->ssl = NULL; - client->sockfd = -1; - pthread_mutex_unlock(&client->lock); + client_cleanup_connection(client); if (client_is_running(client)) { _sleep_ms(backoff_ms); @@ -930,8 +1064,7 @@ static faith_status_code_t client_new_identity(client_identity_t *o_ident) { return FAITH_ERR_INVALID; /* Generate 128 bit random device & auth identities */ - _FH_CHECK_RETURN(faith_random_bytes(o_ident->auth_id.bytes, - sizeof(o_ident->auth_id.bytes))); + client_id_from_hex("88d9296cf57e1538fc16c8a3f6c64567", &o_ident->auth_id); _FH_CHECK_RETURN(faith_random_bytes(o_ident->device_id.bytes, sizeof(o_ident->device_id.bytes))); @@ -940,6 +1073,16 @@ static faith_status_code_t client_new_identity(client_identity_t *o_ident) { _FH_CHECK_RETURN(faith_gen_ed25519_keypair( &o_ident->keypair, o_ident->private_key, o_ident->public_key)); + if(g_log_enable_tracing) { + char auth_id_hex[33]; + char device_id_hex[33]; + _FH_CHECK_RETURN(faith_id128_to_hex(o_ident->auth_id.bytes, auth_id_hex)); + _FH_CHECK_RETURN( + faith_id128_to_hex(o_ident->device_id.bytes, device_id_hex)); + nob_log(INFO, "[faith] Generated new client identity (auth_id=%s, device_id=%s).", + auth_id_hex, device_id_hex); + } + return FAITH_OK; } @@ -1059,25 +1202,41 @@ faith_status_code_t faith_client_start(faith_client_t *client) { } faith_status_code_t faith_client_stop(faith_client_t *client) { - if (!client) + if (!client) { return FAITH_ERR_INVALID; + } + + if (pthread_equal(pthread_self(), client->thread)) { + nob_log(ERROR, "faith_client_stop called from client thread." + "Use faith_client_cleanup_connection instead."); + return FAITH_ERR_INVALID; + } pthread_mutex_lock(&client->lock); + int was_running = client->running; client->running = 0; + int fd = client->sockfd; + pthread_mutex_unlock(&client->lock); + if (was_running && fd >= 0) { + shutdown(fd, SHUT_RDWR); + } + if (was_running) { - pthread_mutex_lock(&client->lock); - int fd = client->sockfd; - pthread_mutex_unlock(&client->lock); - if (fd >= 0) { - shutdown(fd, SHUT_RDWR); + int err = pthread_join(client->thread, NULL); + if (err != 0) { + nob_log(ERROR, + "Failed to join client thread during client stop: " + "pthread_join returned %d.", + err); + return FAITH_ERR_THREAD; } - - pthread_join(client->thread, NULL); } + client_cleanup_connection(client); + return FAITH_OK; } @@ -1101,11 +1260,7 @@ faith_client_send_message(faith_client_t *client, envl.body_size = msg_size; envl.body = (uint8_t *)msg; - pthread_mutex_lock(&client->write_lock); - faith_status_code_t rc = client_send_envelope(client->ssl, &envl); - pthread_mutex_unlock(&client->write_lock); - - return rc; + return client_send_envelope_locked(client, &envl); } int faith_client_event_fd(faith_client_t *client) { @@ -1145,3 +1300,103 @@ faith_status_code_t faith_client_free_event(faith_event_t *ev) { return FAITH_OK; } + +faith_status_code_t +faith_client_approve_pending_device_auth(faith_client_t *client) { + if (!client) + return FAITH_ERR_INVALID; + + faith_client_device_link_req_t* req = client->pending_device_link_req; + if (!req) { + nob_log(ERROR, "[client] Cannot approve pending device authorization; No " + "authorization pending."); + return FAITH_ERR_INVALID; + } + /* Construct message buffer for signature generation + * Message Buffer { + * auth_id, + * device_id_new, + * public_key_new_device, + * code, + * expires_at_ms, + * device_id_approving (device ID of the approving device) + * } */ + + faith_signature_device_link_approval_t sign_msg = {0}; + sign_msg.auth_id = req->auth_id; + sign_msg.device_id_new = req->device_id_new; + + memcpy(sign_msg.public_key_new_device, req->public_key_new_device, + FAITH_ED25519_PUBLIC_KEY_SIZE); + memcpy(sign_msg.code, req->code, sizeof(req->code)); + + sign_msg.expires_at_ms = req->expires_at_ms; + sign_msg.device_id_approving = client->ident.device_id; + + uint8_t msg_buf[sizeof(faith_signature_device_link_approval_t)]; + { + _FH_CHECK(faith_gen_sign_buf_device_link_approval( + msg_buf, sizeof(msg_buf), &sign_msg)); + + if (_fh_rc != FAITH_OK) { + nob_log(ERROR, "Failed to generate DEVICE_AUTH_APPROVE signing " + "message buffer."); + + return _fh_rc; + } + } + + /* Generating the 64 byte cryptographic signature from the message buffer + */ + uint8_t signature[FAITH_ED25519_SIGNATURE_SIZE]; + size_t signature_size = 0; + { + + /* This returns FAITH_ERR_SSL if signature_size != + * FAITH_ED25519_SIGNATURE_SIZE */ + _FH_CHECK(faith_gen_signature(client->ident.keypair, signature, + &signature_size, msg_buf, sizeof(msg_buf))); + if (_fh_rc != FAITH_OK) { + nob_log(ERROR, "Failed to generate signature for DEVICE_AUTH_APPROE."); + return _fh_rc; + } + } + + faith_envelope_t approval_envl = {0}; + approval_envl.type = FAITH_ENVELOPE_DEVICE_AUTH_APPROVE; + + /* Serialize faith_client_device_link_approval_t */ + + faith_client_device_link_approval_t approval_body = {0}; + approval_body.device_id_new = req->device_id_new; + memcpy(approval_body.signature_approval, signature, signature_size); + + size_t body_size = 0; + uint8_t body[sizeof(approval_body)]; + + faith_encode_device_link_approval(body, &body_size, sizeof(body), + &approval_body); + + approval_envl.body = body; + approval_envl.body_size = body_size; + + /* Free allocated pending device link request */ + free(client->pending_device_link_req); + client->pending_device_link_req = NULL; + + return client_send_envelope_locked(client, &approval_envl); +} + +faith_status_code_t +faith_client_deny_pending_device_auth(faith_client_t *client) { + if (!client) + return FAITH_ERR_INVALID; + + if (!client->pending_device_link_req) { + nob_log(ERROR, "[client] Cannot deny pending device authorization; No " + "authorization pending."); + return FAITH_ERR_INVALID; + } + + return FAITH_OK; +} diff --git a/faithd/src/client/client.h b/faithd/src/client/client.h index 6dcc04a..4f8f877 100644 --- a/faithd/src/client/client.h +++ b/faithd/src/client/client.h @@ -46,3 +46,7 @@ int faith_client_event_fd(faith_client_t *client); faith_status_code_t faith_client_next_event(faith_client_t *client, faith_event_t *out); faith_status_code_t faith_client_free_event(faith_event_t *ev); + +faith_status_code_t faith_client_approve_pending_device_auth(faith_client_t* client); + +faith_status_code_t faith_client_deny_pending_device_auth(faith_client_t* client); diff --git a/faithd/src/client_test_suite.c b/faithd/src/client_test_suite.c index 0ce162d..664f035 100644 --- a/faithd/src/client_test_suite.c +++ b/faithd/src/client_test_suite.c @@ -1,4 +1,5 @@ #include "client/client.h" +#include "shared.h" #include #include @@ -95,6 +96,8 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } + bool pending_device_link_request = false; + printf("Type messages and press Enter. Type /quit to exit.\n"); printf("> "); @@ -134,6 +137,37 @@ int main(int argc, char **argv) { break; } + if (pending_device_link_request) { + if (strcmp(line, "y") == 0 || strcmp(line, "Y") == 0 || + strcmp(line, "yes") == 0 || strcmp(line, "YES") == 0) { + pending_device_link_request = false; + + faith_status_code_t approve_rc = + faith_client_approve_pending_device_auth(client); + + if (approve_rc != FAITH_OK) { + fprintf(stderr, "faith_client_approve_device_link() failed: %s\n", + faith_status_code_name(approve_rc)); + } else { + printf("Device link approved.\n"); + } + } else if (strcmp(line, "n") == 0 || strcmp(line, "N") == 0 || + strcmp(line, "no") == 0 || strcmp(line, "NO") == 0) { + pending_device_link_request = false; + + printf("Device link ignored.\n"); + } else { + printf("Please answer y or n.\n"); + printf("(y)es/n(o) ? "); + fflush(stdout); + continue; + } + + printf("> "); + fflush(stdout); + continue; + } + if (line[0] != '\0') { faith_status_code_t send_rc = faith_client_send_message(client, recipient_id, line); @@ -166,8 +200,23 @@ int main(int argc, char **argv) { printf("> "); fflush(stdout); } else { + if (ev.type == FAITH_EVENT_PONG) + continue; + + printf("Got event: %s", faith_event_name(ev.type)); if (strlen(ev.message) != 0) { - printf("GOT MESSAGE: %s\n", ev.message); + printf(" message => %s\n", ev.message); + } else { + printf("\n"); + } + + if (ev.type == FAITH_EVENT_DEVICE_LINK_REQUEST) { + pending_device_link_request = true; + printf("(y)es/n(o) ? "); + fflush(stdout); + } else { + printf("> "); + fflush(stdout); } } } diff --git a/faithd/src/faithd.c b/faithd/src/faithd.c index 8a18758..10d489b 100644 --- a/faithd/src/faithd.c +++ b/faithd/src/faithd.c @@ -81,6 +81,8 @@ struct client_conn_t { struct client_conn_t *prev; struct client_temporary_handshake_params_t temp_handshake_params; + faith_client_device_link_req_t *pending_device_link_req; + struct client_conn_t *pending_device_link_conn; int authorized; }; @@ -219,9 +221,9 @@ routing_get_user_from_auth_id(struct routing_state_t *rt, } static faith_status_code_t -routing_get_device_sessions(struct routing_state_t *rt, - const faith_client_id_t *auth_id, - struct client_route_device_t **o_devmap) { +routing_get_devices(struct routing_state_t *rt, + const faith_client_id_t *auth_id, + struct client_route_device_t **o_devmap) { if (!rt || !o_devmap || faith_client_id_equal(*auth_id, FAITH_CLIENT_ID_NONE)) return FAITH_ERR_INVALID; @@ -238,7 +240,7 @@ routing_get_device_sessions(struct routing_state_t *rt, static faith_status_code_t routing_register_session( struct routing_state_t *rt, const faith_client_id_t *auth_id, const faith_device_id_t *device_id, struct client_conn_t *cl, - uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE]) { + const uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE]) { if (!rt || !auth_id || !device_id) return FAITH_ERR_INVALID; @@ -300,6 +302,7 @@ routing_get_session(struct routing_state_t *rt, struct client_route_device_t *devmap = user->value; struct client_route_device_t *dev = hmgetp_null(devmap, *device_id); + // Session is registered, but exact device not registered yet, *o_sess will be // NULL to indicate this state. if (!dev) @@ -372,7 +375,9 @@ static faith_status_code_t close_client(struct server_state_t *s, /* Unlink client from linked list of clients */ server_remove_client(s, cl); - _FH_CHECK(routing_unregister_session(&s->rt, &cl->auth_id, &cl->device_id)); + if (cl->authorized) { + _FH_CHECK(routing_unregister_session(&s->rt, &cl->auth_id, &cl->device_id)); + } epoll_ctl(s->epoll_fd, EPOLL_CTL_DEL, cl->fd, NULL); @@ -707,10 +712,12 @@ static faith_status_code_t server_send_envelope(struct client_conn_t *cl, rc = _fh_rc; } - nob_log( - INFO, - "[client=%" PRIu64 " fd=%i] Server sent envelope: type=%s body_size=%u", - cl->conn_id, cl->fd, faith_envelope_name(envl->type), envl->body_size); + if (rc == FAITH_OK) { + nob_log( + INFO, + "[client=%" PRIu64 " fd=%i] Server sent envelope: type=%s body_size=%u", + cl->conn_id, cl->fd, faith_envelope_name(envl->type), envl->body_size); + } free(payload); return rc; @@ -776,10 +783,6 @@ server_handle_hello(struct server_state_t *s, struct client_conn_t *cl, return FAITH_ERR_INVALID; } - if (cl->authorized != 1) { - return FAITH_ERR_UNAUTHORIZED; - } - if (faith_client_id_equal(hello_envl->sender_id, FAITH_CLIENT_ID_NONE)) { return FAITH_ERR_INVALID; } @@ -842,14 +845,202 @@ server_handle_hello(struct server_state_t *s, struct client_conn_t *cl, return FAITH_OK; } +static faith_status_code_t +server_send_device_auth_pending(struct server_state_t *s, + struct client_conn_t *recipient_cl) { + if (!s || !recipient_cl) + return FAITH_ERR_INVALID; + + faith_envelope_t device_auth_pending_envl = {0}; + device_auth_pending_envl.type = FAITH_ENVELOPE_DEVICE_AUTH_PENDING; + _FH_CHECK_RETURN(server_send_envelope(recipient_cl, &device_auth_pending_envl, + s->epoll_fd)); + + return FAITH_OK; +} +static faith_status_code_t server_send_device_link_request( + struct server_state_t *s, struct client_conn_t *recipient_cl, + struct client_conn_t *request_cl, const faith_client_id_t *auth_id, + const uint8_t public_key_new_device[FAITH_ED25519_PUBLIC_KEY_SIZE], + const faith_device_id_t *new_device_id) { + if (!s || !auth_id || !public_key_new_device || !new_device_id || + !recipient_cl) { + return FAITH_ERR_INVALID; + } + + if (!recipient_cl->authorized) { + char auth_id_hex[33]; + char device_id_hex[33]; + _FH_CHECK_RETURN( + faith_id128_to_hex(recipient_cl->auth_id.bytes, auth_id_hex)); + _FH_CHECK_RETURN( + faith_id128_to_hex(recipient_cl->device_id.bytes, device_id_hex)); + nob_log(ERROR, + "Not sending device link request to" + "device with device_id: %s (auth_id: %s). Client connection is " + "not authorized.", + device_id_hex, auth_id_hex); + + return FAITH_ERR_UNAUTHORIZED; + } + if (recipient_cl->pending_device_link_req != NULL) { + char auth_id_hex[33]; + char device_id_hex[33]; + _FH_CHECK_RETURN( + faith_id128_to_hex(recipient_cl->auth_id.bytes, auth_id_hex)); + _FH_CHECK_RETURN( + faith_id128_to_hex(recipient_cl->device_id.bytes, device_id_hex)); + nob_log(ERROR, + "Not sending device link request to" + "device with device_id: %s (auth_id: %s). Another device link " + "request is already pending.", + device_id_hex, auth_id_hex); + + return FAITH_ERR_ALREADY_STARTED; + } + + /* Allocate device link request */ + + faith_client_device_link_req_t *req = calloc(1, sizeof(*req)); + + req->device_id_new = *new_device_id; + + memcpy(req->public_key_new_device, public_key_new_device, + FAITH_ED25519_PUBLIC_KEY_SIZE); + + req->auth_id = *auth_id; + + _FH_CHECK_RETURN(faith_random_bytes(req->code, sizeof(req->code))); + + req->expires_at_ms = + faith_now_ms() + FAITH_DEVICE_LINK_REQ_EXPIRATION_TIME_MS; + + /* Set pending device link request of receiving client connection. We also + * store the client connection that sent the device link request. */ + recipient_cl->pending_device_link_req = req; + recipient_cl->pending_device_link_conn = request_cl; + + /* Serialize device link reqest */ + + uint8_t body[sizeof(faith_client_device_link_req_t)]; + size_t body_size = 0; + _FH_CHECK_RETURN( + faith_encode_device_link_req(body, &body_size, sizeof(body), req)); + + faith_envelope_t device_link_req_envl = {0}; + device_link_req_envl.body = body; + device_link_req_envl.body_size = body_size; + device_link_req_envl.recipient_id = *auth_id; + device_link_req_envl.type = FAITH_ENVELOPE_DEVICE_LINK_REQUEST; + + _FH_CHECK( + server_send_envelope(recipient_cl, &device_link_req_envl, s->epoll_fd)); + + return FAITH_OK; +} + +static faith_status_code_t server_handle_newly_joined_device( + struct server_state_t *s, struct client_conn_t *cl, + const struct client_temporary_handshake_params_t *params) { + if (!s || !cl || !params) + return FAITH_ERR_INVALID; + + if (s->cfg.verbose_logging) { + char cl_auth_id_hex[33]; + char new_device_id_hex[33]; + _FH_CHECK_RETURN(faith_id128_to_hex(params->sender_auth_id.bytes, cl_auth_id_hex)); + _FH_CHECK_RETURN(faith_id128_to_hex(params->device_id.bytes, new_device_id_hex)); + + nob_log( + INFO, + "[client=%" PRIu64 + " fd=%i] Handling newly joined device (device_id=%s) for auth_id=%s.", + cl->conn_id, cl->fd, new_device_id_hex, cl_auth_id_hex); + } + + struct client_route_device_t *authorized_devices = NULL; + + _FH_CHECK_RETURN(routing_get_devices(&s->rt, ¶ms->sender_auth_id, + &authorized_devices)); + + size_t n_authorized_devices = hmlen(authorized_devices); + if (!authorized_devices || n_authorized_devices < 1) { + return FAITH_ERR_INVALID; + } + + /* Send device authorization request to every already registered device + for that auth ID */ + for (ptrdiff_t i = 0; i < hmlen(authorized_devices); i++) { + + _FH_CHECK(server_send_device_link_request( + s, authorized_devices[i].value->conn, cl, ¶ms->sender_auth_id, + params->public_key, ¶ms->device_id)); + + if (_fh_rc != FAITH_OK) { + + char sender_auth_id_hex[33]; + _FH_CHECK_RETURN( + faith_id128_to_hex(params->sender_auth_id.bytes, sender_auth_id_hex)); + char device_id_hex[33]; + _FH_CHECK_RETURN( + faith_id128_to_hex(params->device_id.bytes, device_id_hex)); + nob_log(ERROR, + "Failed to send device link request to authorized " + "device with device_id: %s (auth_id: %s)", + device_id_hex, sender_auth_id_hex); + + return _fh_rc; + } + } + + /* Send DEVICE_AUTH_PENDING to the connection that requested the + * new device */ + _FH_CHECK_RETURN(server_send_device_auth_pending(s, cl)); + set_client_state(s, cl, CLIENT_OPEN); + + return FAITH_OK; +} + +static faith_status_code_t server_authorize_client( + struct server_state_t *s, struct client_conn_t *cl, + const faith_client_id_t *auth_id, const faith_device_id_t *device_id, + uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE], int register_session) { + if (!s || !cl || !auth_id || !device_id || !public_key) + return FAITH_ERR_INVALID; + + cl->authorized = 1; + + /* Accept the hello */ + _FH_CHECK_RETURN(server_accept_hello(s, cl, auth_id, device_id)); + + if (register_session) { + /* register client session */ + _FH_CHECK_RETURN(routing_register_session(&s->rt, &cl->auth_id, + &cl->device_id, cl, public_key)); + } + + char cl_auth_id_hex[33]; + char cl_device_id_hex[33]; + _FH_CHECK_RETURN(faith_id128_to_hex(cl->auth_id.bytes, cl_auth_id_hex)); + _FH_CHECK_RETURN(faith_id128_to_hex(cl->device_id.bytes, cl_device_id_hex)); + + nob_log(INFO, + "[client=%" PRIu64 " fd=%i] Client passed authorization for " + "requested routing session. (auth_id=%s, device_id=%s)", + cl->conn_id, cl->fd, cl_auth_id_hex, cl_device_id_hex); + + return FAITH_OK; +} + static faith_status_code_t server_handle_challenge_response( struct server_state_t *s, struct client_conn_t *cl, const faith_envelope_t *challenge_response_envl) { if (!s || !cl || !challenge_response_envl) return FAITH_ERR_INVALID; + if (challenge_response_envl->type != FAITH_ENVELOPE_CHALLENGE_RESPONSE) { nob_log(ERROR, - "[client=%" PRIu64 " fd=%i] INvalid envelope type. Expected " + "[client=%" PRIu64 " fd=%i] Invalid envelope type. Expected " "FAITH_ENVELOPE_CHALLENGE_RESPONSE, got %s", cl->conn_id, cl->fd, faith_envelope_name(challenge_response_envl->type)); @@ -859,15 +1050,12 @@ static faith_status_code_t server_handle_challenge_response( if (cl->state != CLIENT_WAIT_FOR_HELLO_ACK) { nob_log(ERROR, "[client=%" PRIu64 - " fd=%i] Server got invalid CHALLENGE_RESPONSE from client.", + " fd=%i] Server got invalid CHALLENGE_RESPONSE from client. Client " + "is not in WAIT_FOR_HELLO_ACK state. ", cl->conn_id, cl->fd); return FAITH_ERR_INVALID; } - if (cl->authorized != 1) { - return FAITH_ERR_UNAUTHORIZED; - } - struct client_temporary_handshake_params_t *params = &cl->temp_handshake_params; @@ -879,7 +1067,9 @@ static faith_status_code_t server_handle_challenge_response( cl->conn_id, cl->fd); return FAITH_ERR_INVALID; } - if (challenge_response_envl->body_size != FAITH_ED25519_SIGNATURE_SIZE) { + + if (!challenge_response_envl->body || + challenge_response_envl->body_size != FAITH_ED25519_SIGNATURE_SIZE) { nob_log(ERROR, "[client=%" PRIu64 " fd=%i] Server got invalid CHALLENGE_RESPONSE envelope contents.", @@ -911,16 +1101,14 @@ static faith_status_code_t server_handle_challenge_response( if (sess_rc == FAITH_ERR_NOT_FOUND) { /* Session not registered yet, use sent public key for verification */ - verification_public_key = params->public_key; } else if (sess_rc == FAITH_OK) { /* Session already registered */ if (!sess) { - /* TODO: This means the auth ID is already registered but this a new - * device that wants to join, we will need to handle this by signed - * authorization from existing device. For now, we reject joining from new - * devices */ - goto reject; + /* This means the auth ID is already registered but this is a new + * device that wants to join. */ + _FH_CHECK_RETURN(server_handle_newly_joined_device(s, cl, params)); + return FAITH_OK; } /* Check if the sent public key and the public key of the registered @@ -945,7 +1133,6 @@ static faith_status_code_t server_handle_challenge_response( /* Passed, fine. Use public key of the already verified session identity for * verification. We do not trust the sent public key. */ verification_public_key = sess->ident.public_key; - printf("Session already registered.\n"); } if (!verification_public_key) { @@ -963,11 +1150,21 @@ static faith_status_code_t server_handle_challenge_response( * client_nonce, * server_nonce * } */ - uint8_t msg_buf[FAITH_HELLO_HANDSHAKE_SIGNATURE_SIZE]; + + faith_signature_hello_handshake_t sign_msg = {0}; + + sign_msg.auth_id = params->sender_auth_id; + + memcpy(sign_msg.public_key, verification_public_key, + FAITH_ED25519_PUBLIC_KEY_SIZE); + + sign_msg.client_nonce = params->nonce; + sign_msg.server_nonce = params->server_nonce; + + uint8_t msg_buf[sizeof(faith_signature_hello_handshake_t)]; { - _FH_CHECK(faith_gen_signing_msg_buf( - msg_buf, sizeof(msg_buf), ¶ms->sender_auth_id, - verification_public_key, params->nonce, params->server_nonce)); + _FH_CHECK(faith_gen_sign_buf_hello_handshake(msg_buf, sizeof(msg_buf), + &sign_msg)); if (_fh_rc != FAITH_OK) { nob_log(ERROR, "[client=%" PRIu64 @@ -986,14 +1183,6 @@ static faith_status_code_t server_handle_challenge_response( sizeof(client_signature))); verification_rc = _fh_rc; - - if (_fh_rc != FAITH_OK) { - nob_log(ERROR, - "[client=%" PRIu64 - " fd=%i] Failed to create EVP_PKEY from raw public key", - cl->conn_id, cl->fd); - return _fh_rc; - } } int authorized = verification_rc == FAITH_OK; @@ -1005,26 +1194,16 @@ static faith_status_code_t server_handle_challenge_response( /* Client passed authorization */ /* =============================== */ - _FH_CHECK_RETURN( - server_accept_hello(s, cl, ¶ms->sender_auth_id, ¶ms->device_id)); - - if (!sess) { - /* register client session */ - _FH_CHECK_RETURN(routing_register_session( - &s->rt, &cl->auth_id, &cl->device_id, cl, params->public_key)); - } + _FH_CHECK(server_authorize_client(s, cl, ¶ms->sender_auth_id, + ¶ms->device_id, verification_public_key, + sess == NULL)); + return _fh_rc; +reject: { - char cl_auth_id_hex[33]; - char cl_device_id_hex[33]; - _FH_CHECK_RETURN(faith_id128_to_hex(cl->auth_id.bytes, cl_auth_id_hex)); - _FH_CHECK_RETURN(faith_id128_to_hex(cl->device_id.bytes, cl_device_id_hex)); - nob_log(INFO, - "[client=%" PRIu64 " fd=%i] Client passed authorization for " - "requested routing session. (auth_id=%s, device_id=%s)", - cl->conn_id, cl->fd, cl_auth_id_hex, cl_device_id_hex); + /* =============================== */ + /* Client failed authorization */ + /* =============================== */ - return FAITH_OK; -reject: { char sender_auth_id_hex[33]; char device_id_hex[33]; _FH_CHECK_RETURN( @@ -1039,6 +1218,224 @@ reject: { } } +static faith_status_code_t +server_handle_device_link_approval(struct server_state_t *s, + struct client_conn_t *cl, + const faith_envelope_t *approval_envl) { + if (!s || !cl || !approval_envl) + return FAITH_ERR_INVALID; + + faith_status_code_t _fh_result = FAITH_OK; + + faith_client_device_link_req_t *req = cl->pending_device_link_req; + struct client_conn_t *req_cl = cl->pending_device_link_conn; + + if (!req || !req_cl) { + nob_log(ERROR, + "[client=%" PRIu64 + " fd=%i] Server got DEVICE_AUTH_APPROVE but there is no " + "device link request pending. Rejecting envelope.", + cl->conn_id, cl->fd); + return FAITH_ERR_INVALID; + } + + if (approval_envl->type != FAITH_ENVELOPE_DEVICE_AUTH_APPROVE) { + nob_log(ERROR, + "[client=%" PRIu64 " fd=%i] Invalid envelope type. Expected " + "FAITH_ENVELOPE_DEVICE_AUTH_APPROVE, got %s", + cl->conn_id, cl->fd, faith_envelope_name(approval_envl->type)); + return FAITH_ERR_INVALID; + } + + if (!approval_envl->body || + approval_envl->body_size != sizeof(faith_client_device_link_approval_t)) { + nob_log(ERROR, + "[client=%" PRIu64 + " fd=%i] Server got invalid DEVICE_AUTH_APPROVE envelope contents.", + cl->conn_id, cl->fd); + return FAITH_ERR_INVALID; + } + + if (!cl->authorized) { + char auth_id_hex[33]; + char device_id_hex[33]; + _FH_CHECK_RETURN(faith_id128_to_hex(cl->auth_id.bytes, auth_id_hex)); + _FH_CHECK_RETURN(faith_id128_to_hex(cl->device_id.bytes, device_id_hex)); + + nob_log(ERROR, + "[client=%" PRIu64 + " fd=%i] Server got unauthorized DEVICE_AUTH_APPROVE " + "from client. Client (auth_id=%s, device_id=%s) is not authorized.", + cl->conn_id, cl->fd, auth_id_hex, device_id_hex); + + /* Return UNAUTHORIZED without rejecting/closing the client connection that + * requested the device link. */ + return FAITH_ERR_UNAUTHORIZED; + } + + if (cl->state != CLIENT_OPEN) { + nob_log(ERROR, + "[client=%" PRIu64 " fd=%i] Server got invalid DEVICE_AUTH_APPROVE " + "from client. Authorizing client is not in OPEN state.", + cl->conn_id, cl->fd); + + /* Return INVALID without rejecting/closing the client connection that + * requested the device link. */ + return FAITH_ERR_INVALID; + } + + if (faith_now_ms() > cl->pending_device_link_req->expires_at_ms) { + nob_log(ERROR, + "[client=%" PRIu64 " fd=%i] Server got DEVICE_AUTH_APPROVE but the " + "link request has already expired.", + cl->conn_id, cl->fd); + + /* Reject the requesting client conection if the request has expired. */ + _FH_RETURN_DEFER(FAITH_ERR_EXPIRED); + } + + faith_client_device_link_approval_t approval = {0}; + _FH_CHECK_RETURN(faith_decode_device_link_approval( + approval_envl->body, approval_envl->body_size, &approval)); + + if (!faith_device_id_equal(approval.device_id_new, req->device_id_new)) { + nob_log( + ERROR, + "[client=%" PRIu64 " fd=%i] Server got DEVICE_AUTH_APPROVE but the sent" + "device_id does not match the device_id that requested the approval.", + cl->conn_id, cl->fd); + + /* Return INVALID without rejecting/closing the client connection that + * requested the device link. */ + return FAITH_ERR_INVALID; + } + + /* Construct message buffer for signature generation + * Message Buffer { + * auth_id, + * device_id_new, + * public_key_new_device, + * code, + * expires_at_ms, + * device_id_approving (device ID of the approving device) + * } */ + + faith_signature_device_link_approval_t sign_msg = {0}; + sign_msg.auth_id = req->auth_id; + sign_msg.device_id_new = req->device_id_new; + + memcpy(sign_msg.public_key_new_device, req->public_key_new_device, + FAITH_ED25519_PUBLIC_KEY_SIZE); + memcpy(sign_msg.code, req->code, sizeof(req->code)); + + sign_msg.expires_at_ms = req->expires_at_ms; + sign_msg.device_id_approving = cl->device_id; + + uint8_t msg_buf[sizeof(faith_signature_device_link_approval_t)]; + { + _FH_CHECK(faith_gen_sign_buf_device_link_approval(msg_buf, sizeof(msg_buf), + &sign_msg)); + + if (_fh_rc != FAITH_OK) { + nob_log(ERROR, "Failed to generate DEVICE_AUTH_APPROVE signing " + "message buffer."); + + /* Return _fh_rc without rejecting/closing the client connection that + * requested the device link. */ + return _fh_rc; + } + } + + struct client_device_session_data_t *sess = NULL; + /* We specifically need routing_get_session() to return FAITH_OK. */ + { + _FH_CHECK(routing_get_session(&s->rt, &cl->auth_id, &cl->device_id, &sess)); + if (_fh_rc != FAITH_OK) { + _fh_result = _fh_rc; + /* Reject the requesting client conection if the authorized connection + * does not actually have an authorized session. */ + _FH_RETURN_DEFER(FAITH_ERR_UNAUTHORIZED); + } + } + + /* This means cl->auth_id is registered but cl->device_id is not, effectively + * telling us that the client connection is not yet authorized. Because we + * checked cl->authorized above, this should never happend with correct + * behaviour.*/ + if (!sess) { + nob_log(ERROR, + "[client=%" PRIu64 + " fd=%i] Server got DEVICE_AUTH_APPROVE but client connection that " + "sent the envelope does not have registered session data. " + "However, the client connection IS authorized, so there is " + "probably a deeper issue.", + cl->conn_id, cl->fd); + return FAITH_ERR_INVALID; + } + + /* Verify the signature */ + faith_status_code_t verification_rc = FAITH_ERR_UNAUTHORIZED; + { + _FH_CHECK(faith_verify_signature_raw_pubkey( + sess->ident.public_key, msg_buf, sizeof(msg_buf), + approval.signature_approval, sizeof(approval.signature_approval))); + + verification_rc = _fh_rc; + } + + int authorized = verification_rc == FAITH_OK; + if (!authorized) { + _FH_RETURN_DEFER(verification_rc == FAITH_ERR_NOT_EQUAL + ? FAITH_ERR_UNAUTHORIZED + : verification_rc); + } + + /* =============================== */ + /* Client passed authorization */ + /* =============================== */ + + _FH_CHECK_DEFER(server_authorize_client(s, req_cl, &req->auth_id, + &req->device_id_new, + req->public_key_new_device, 1)); + + /* Remove the pending device link request */ + free(cl->pending_device_link_req); + cl->pending_device_link_req = NULL; + + return FAITH_OK; + +defer: { + + /* =============================== */ + /* Client failed authorization */ + /* =============================== */ + + faith_client_id_t client_id = req->auth_id; + faith_device_id_t device_id_new = req->device_id_new; + + /* Close the connection that requested the device link */ + close_client(s, req_cl); + + /* Remove the pending device link request */ + free(cl->pending_device_link_req); + cl->pending_device_link_req = NULL; + + char auth_id_hex[33]; + char device_id_hex[33]; + _FH_CHECK_RETURN(faith_id128_to_hex(client_id.bytes, auth_id_hex)); + _FH_CHECK_RETURN(faith_id128_to_hex(device_id_new.bytes, device_id_hex)); + + nob_log(ERROR, + "[client=%" PRIu64 " fd=%i] Client connection" + "(auth_id=%s, device_id=%s) failed authorization for device link " + "request. Device with device_id=%s will not be linked to auth_id=%s. " + "Closing connection. ", + req_cl->conn_id, req_cl->fd, auth_id_hex, device_id_hex, + device_id_hex, auth_id_hex); + + return _fh_result; +} +} static faith_status_code_t server_route_msg_envl(struct server_state_t *s, struct client_conn_t *cl, faith_envelope_t *envl) { @@ -1056,8 +1453,8 @@ static faith_status_code_t server_route_msg_envl(struct server_state_t *s, struct client_route_device_t *recipient_devices = NULL; - faith_status_code_t rc = routing_get_device_sessions( - &s->rt, &envl->recipient_id, &recipient_devices); + faith_status_code_t rc = + routing_get_devices(&s->rt, &envl->recipient_id, &recipient_devices); if (rc == FAITH_ERR_INVALID) { return rc; @@ -1131,10 +1528,12 @@ static faith_status_code_t server_handle_envl(struct server_state_t *s, _FH_CHECK_RETURN( faith_decode_envelope(frame->payload, frame->payload_size, &envl)); + int is_handshake_state = cl->state != CLIENT_OPEN; + + int is_handshake_envl = envl.type == FAITH_ENVELOPE_HELLO || + envl.type == FAITH_ENVELOPE_CHALLENGE_RESPONSE; faith_status_code_t rc = FAITH_OK; - if (cl->state != CLIENT_OPEN && - (envl.type != FAITH_ENVELOPE_HELLO && - envl.type != FAITH_ENVELOPE_CHALLENGE_RESPONSE)) { + if (is_handshake_state && !is_handshake_envl) { nob_log(ERROR, "[client=%" PRIu64 " fd=%i] Client is not acknowledged yet. Will " "not handle envelope (%s).", @@ -1165,6 +1564,11 @@ static faith_status_code_t server_handle_envl(struct server_state_t *s, rc = _fh_rc; break; } + case FAITH_ENVELOPE_DEVICE_AUTH_APPROVE: { + _FH_CHECK(server_handle_device_link_approval(s, cl, &envl)); + rc = _fh_rc; + break; + } default: rc = FAITH_ERR_BAD_FRAME; break; @@ -1266,8 +1670,13 @@ static faith_status_code_t try_parse_frame_from_buffer(const uint8_t *buf, if (frame_size < FAITH_HEADER_SIZE - (sizeof(uint16_t) + sizeof(uint16_t))) return FAITH_ERR_BAD_FRAME; - if (frame_size > FAITH_MAX_FRAME_LEN) + if (frame_size > FAITH_MAX_FRAME_LEN) { + nob_log(ERROR, + "Failed to parse frame from buffer; Frame is too large, " + "frame_size=%i MAX_FRAME_LEN=%i", + (int32_t)frame_size, (int32_t)FAITH_MAX_FRAME_LEN); return FAITH_ERR_FRAME_TOO_LARGE; + } size_t total_frame_size = sizeof(uint32_t) + frame_size; @@ -1388,8 +1797,8 @@ try_read_one_frame(struct client_conn_t *cl, faith_frame_t *frame, nob_log(ERROR, "[client=%" PRIu64 - " fd=%i] Error while reading incomplete frame.", - cl->conn_id, cl->fd); + " fd=%i] Error while reading incomplete frame. Error=%i", + cl->conn_id, cl->fd, rr); return rr; } @@ -1403,66 +1812,68 @@ try_read_one_frame(struct client_conn_t *cl, faith_frame_t *frame, static int drive_client_read(struct server_state_t *s, struct client_conn_t *cl, const struct server_cfg_t *cfg) { - for (;;) { - faith_frame_t frame; - - enum read_frame_result_t rr = try_read_one_frame(cl, &frame, cfg); + faith_frame_t frame; - if (rr == READ_FRAME_OK) { - /* Handle protocol frame */ - { - _FH_CHECK(handle_frame(s, cl, &frame, s->epoll_fd)); - if (_fh_rc) { + enum read_frame_result_t rr = try_read_one_frame(cl, &frame, cfg); - faith_frame_free(&frame); - return -1; - } - } + if (rr == READ_FRAME_OK) { + _FH_CHECK(handle_frame(s, cl, &frame, s->epoll_fd)); + if (_fh_rc != FAITH_OK) { faith_frame_free(&frame); + return -1; + } - if (cfg->verbose_logging) { - nob_log(INFO, - "[client=%" PRIu64 " fd=%i]: SUCCESS HANDLING CLIENT FRAME.", - cl->conn_id, cl->fd); - } + faith_frame_free(&frame); - if (cl->out_buf && cl->out_off < cl->out_size) { - return 0; - } - continue; + if (cfg->verbose_logging) { + nob_log(INFO, + "[client=%" PRIu64 " fd=%i]: Success handling client frame.", + cl->conn_id, cl->fd); } - if (rr == READ_FRAME_WANT_READ) { - uint32_t mask = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + uint32_t mask = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; - if (cl->out_buf && cl->out_off < cl->out_size) { - mask |= EPOLLOUT; - } + if (cl->out_buf && cl->out_off < cl->out_size) { + mask |= EPOLLOUT; + } - if (modify_client_ev_mask(s->epoll_fd, cl, mask) < 0) { - return -1; - } - return 0; + if (modify_client_ev_mask(s->epoll_fd, cl, mask) < 0) { + return -1; } - if (rr == READ_FRAME_WANT_WRITE) { - if (modify_client_ev_mask(s->epoll_fd, cl, - EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLERR | - EPOLLHUP) < 0) { - nob_log(ERROR, "modify_client_ev_mask failed: %s", strerror(errno)); - return -1; - } - return 0; + return 0; + } + + if (rr == READ_FRAME_WANT_READ) { + uint32_t mask = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + + if (cl->out_buf && cl->out_off < cl->out_size) { + mask |= EPOLLOUT; } - if (rr == READ_FRAME_CLOSED) + if (modify_client_ev_mask(s->epoll_fd, cl, mask) < 0) { return -1; + } + return 0; + } - nob_log(ERROR, "[client=%" PRIu64 " fd=%i]: Failed to read client frame.", - cl->conn_id, cl->fd); + if (rr == READ_FRAME_WANT_WRITE) { + if (modify_client_ev_mask(s->epoll_fd, cl, + EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLERR | + EPOLLHUP) < 0) { + nob_log(ERROR, "modify_client_ev_mask failed: %s", strerror(errno)); + return -1; + } + return 0; + } + if (rr == READ_FRAME_CLOSED) return -1; - } + + nob_log(ERROR, "[client=%" PRIu64 " fd=%i]: Failed to read client frame.", + cl->conn_id, cl->fd); + + return -1; } static void accept_clients(struct server_state_t *s) { @@ -1502,9 +1913,6 @@ static void accept_clients(struct server_state_t *s) { /* Add client to linked list of clients */ server_add_client(s, cl); - // TODO: temporary - cl->authorized = 1; - // create SSL object for client cl->ssl = SSL_new(s->ssl_ctx); if (!cl->ssl) { diff --git a/faithd/src/shared.c b/faithd/src/shared.c index ab2e389..fde3dee 100644 --- a/faithd/src/shared.c +++ b/faithd/src/shared.c @@ -22,6 +22,32 @@ inline uint8_t faith_version_patch(uint16_t v) { return (uint8_t)(v & 0x3f); } const char *faith_strerror(int code) { return strerror(code); } +int faith_buf_append(uint8_t *buf, size_t cap, size_t *offset, + const void *src, size_t len) { + if (!buf || !offset || !src) + return 0; + + if (*offset > cap || len > cap - *offset) + return 0; + + memcpy(buf + *offset, src, len); + *offset += len; + return 1; +} + +int faith_buf_decode(const uint8_t *buf, size_t size, size_t *offset, + void *dst, size_t len) { + if (!buf || !offset || !dst) + return 0; + + if (*offset > size || len > size - *offset) + return 0; + + memcpy(dst, buf + *offset, len); + *offset += len; + return 1; +} + faith_status_code_t faith_write_bytes_sync(SSL *ssl, const uint8_t *buf, size_t size) { if (!ssl || !buf) @@ -52,30 +78,56 @@ faith_status_code_t faith_write_bytes_sync(SSL *ssl, const uint8_t *buf, return FAITH_OK; } -faith_status_code_t faith_read_bytes_sync(SSL *ssl, uint8_t *buf, size_t size) { - if (!ssl || !buf) +faith_status_code_t +faith_read_bytes_sync(SSL *ssl, uint8_t *buf, size_t size) { + if (!ssl || !buf) { return FAITH_ERR_INVALID; + } size_t total = 0; while (total < size) { size_t nread = 0; - int ok = SSL_read_ex(ssl, buf + total, size - total, &nread); + int ok = SSL_read_ex(ssl, buf + total, size - total, &nread); - if (ok <= 0) { - int err = SSL_get_error(ssl, ok); + if (ok == 1) { + total += nread; + continue; + } + + int err = SSL_get_error(ssl, 0); + + nob_log(ERROR, + "SSL_read_ex failed: ssl_err=%d, nread=%zu, total=%zu, want=%zu, " + "errno=%d (%s)\n", + err, nread, total, size - total, errno, strerror(errno)); + + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + continue; + + case SSL_ERROR_ZERO_RETURN: + return FAITH_ERR_CLOSED; + + case SSL_ERROR_SYSCALL: + return FAITH_ERR_CLOSED; - if (err == SSL_ERROR_ZERO_RETURN) { - fprintf(stderr, "error closed: %li\n", nread); + case SSL_ERROR_SSL: { + unsigned long e = ERR_peek_error(); + + if (ERR_GET_REASON(e) == SSL_R_UNEXPECTED_EOF_WHILE_READING) { + ERR_clear_error(); return FAITH_ERR_CLOSED; } ERR_print_errors_fp(stderr); - fprintf(stderr, "error io: %li\n", nread); - return FAITH_ERR_IO; + return FAITH_ERR_SSL; } - total += nread; + default: + return FAITH_ERR_IO; + } } return FAITH_OK; @@ -105,8 +157,13 @@ faith_status_code_t faith_read_frame_sync(SSL *ssl, faith_frame_t *out) { if (frame_size < sizeof(hdr_buf)) return FAITH_ERR_BAD_FRAME; - if (frame_size > FAITH_MAX_FRAME_LEN) + if (frame_size > FAITH_MAX_FRAME_LEN) { + nob_log(ERROR, + "Failed to read frame; Frame is too large, " + "frame_size=%i MAX_FRAME_LEN=%i", + (int32_t)frame_size, (int32_t)FAITH_MAX_FRAME_LEN); return FAITH_ERR_FRAME_TOO_LARGE; + } _FH_CHECK_RETURN(faith_read_bytes_sync(ssl, hdr_buf, sizeof(hdr_buf))); @@ -118,8 +175,14 @@ faith_status_code_t faith_read_frame_sync(SSL *ssl, faith_frame_t *out) { if (out->proto_ver != FAITH_PROTO_VERSION) return FAITH_ERR_UNSUPPORTED_VER; - if (out->payload_size > FAITH_MAX_PAYLOAD_SIZE) + if (out->payload_size > FAITH_MAX_PAYLOAD_SIZE) { + nob_log(ERROR, + "Failed to read frame; Frame is too large, " + "frame_size=%zu MAX_FRAME_LEN=%i", + out->payload_size, (int32_t)FAITH_MAX_FRAME_LEN); + return FAITH_ERR_FRAME_TOO_LARGE; + } if (out->payload_size > 0) { out->payload = malloc(out->payload_size); @@ -180,21 +243,44 @@ faith_status_code_t faith_encode_frame(faith_frame_msg_type_t type, if (payload_size > 0 && !payload) return FAITH_ERR_INVALID; - if (payload_size > FAITH_MAX_PAYLOAD_SIZE) + if (payload_size > FAITH_MAX_PAYLOAD_SIZE) { + nob_log(ERROR, + "Failed to encode frame to buffer; Frame is too large, " + "frame_size=%zu MAX_FRAME_LEN=%i", + payload_size, (int32_t)FAITH_MAX_FRAME_LEN); return FAITH_ERR_FRAME_TOO_LARGE; + } const size_t header_size_bytes = FAITH_HEADER_SIZE; - if (payload_size > SIZE_MAX - header_size_bytes) + if (payload_size > SIZE_MAX - header_size_bytes) { + nob_log(ERROR, + "Failed to encode frame to buffer; Frame is too large, " + "frame_size=%zu MAX_FRAME_LEN=%i", + payload_size, (int32_t)FAITH_MAX_FRAME_LEN); + return FAITH_ERR_FRAME_TOO_LARGE; + } size_t frame_size_size_t = sizeof(uint16_t) + sizeof(uint16_t) + payload_size; - if (frame_size_size_t > UINT32_MAX) + if (frame_size_size_t > UINT32_MAX) { + nob_log(ERROR, + "Failed to encode frame to buffer; Frame is too large, " + "frame_size=%zu MAX_FRAME_LEN=%i", + frame_size_size_t, (int32_t)FAITH_MAX_FRAME_LEN); + return FAITH_ERR_FRAME_TOO_LARGE; + } + + if (frame_size_size_t > FAITH_MAX_FRAME_LEN) { + nob_log(ERROR, + "Failed to encode frame to buffer; Frame is too large, " + "frame_size=%zu MAX_FRAME_LEN=%i", + frame_size_size_t, (int32_t)FAITH_MAX_FRAME_LEN); - if (frame_size_size_t > FAITH_MAX_FRAME_LEN) return FAITH_ERR_FRAME_TOO_LARGE; + } size_t data_size = header_size_bytes + payload_size; @@ -361,12 +447,11 @@ faith_status_code_t faith_encode_envelope(uint8_t *out_buf, size_t *out_size, _FH_CHECK_RETURN(faith_write_u32_be(out_buf + offset, env->type)); offset += sizeof(uint32_t); // Sender (faith_client_id_t -> 16 raw bytes) - memcpy(out_buf + offset, env->sender_id.bytes, sizeof(env->sender_id.bytes)); - offset += sizeof(env->sender_id.bytes); + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, env->sender_id.bytes, + sizeof(env->sender_id.bytes)); // Recipient (faith_client_id_t -> 16 raw bytes) - memcpy(out_buf + offset, env->recipient_id.bytes, - sizeof(env->recipient_id.bytes)); - offset += sizeof(env->recipient_id.bytes); + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, env->recipient_id.bytes, + sizeof(env->recipient_id.bytes)); // Body Size (uint32_t) _FH_CHECK_RETURN(faith_write_u32_be(out_buf + offset, env->body_size)); @@ -375,7 +460,7 @@ faith_status_code_t faith_encode_envelope(uint8_t *out_buf, size_t *out_size, if (offset + env->body_size > buf_cap_in_bytes) return FAITH_ERR_OVERFLOW; - memcpy(out_buf + offset, env->body, env->body_size); + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, env->body, env->body_size); } *out_size = env_size; @@ -403,13 +488,13 @@ faith_status_code_t faith_decode_envelope(const uint8_t *payload, uint32_t type = faith_read_u32_be(payload + offset); offset += sizeof(uint32_t); // Sender (faith_client_id_t -> 16 raw bytes) - faith_client_id_t sender_id; - memcpy(sender_id.bytes, payload + offset, sizeof(sender_id.bytes)); - offset += sizeof(sender_id.bytes); + faith_client_id_t sender_id = {0}; + FAITH_DECODE_RETURN(payload, payload_size, offset, sender_id.bytes, + sizeof(sender_id.bytes)); // Recipient (faith_client_id_t -> 16 raw bytes) - faith_client_id_t recipient_id; - memcpy(recipient_id.bytes, payload + offset, sizeof(recipient_id.bytes)); - offset += sizeof(recipient_id.bytes); + faith_client_id_t recipient_id = {0}; + FAITH_DECODE_RETURN(payload, payload_size, offset, recipient_id.bytes, + sizeof(recipient_id.bytes)); // Body Size (uint32_t) uint32_t body_size = faith_read_u32_be(payload + offset); @@ -427,7 +512,7 @@ faith_status_code_t faith_decode_envelope(const uint8_t *payload, if (!body) return FAITH_ERR_NOMEM; - memcpy(body, payload + offset, body_size); + FAITH_DECODE_RETURN(payload, payload_size, offset, body, body_size); } *o_envl = (faith_envelope_t){.body_size = body_size, @@ -439,6 +524,134 @@ faith_status_code_t faith_decode_envelope(const uint8_t *payload, return FAITH_OK; } +faith_status_code_t +faith_encode_device_link_req(uint8_t *out_buf, size_t *out_size, + size_t buf_cap_in_bytes, + const faith_client_device_link_req_t *req) { + if(!out_buf || !out_size || !req) return FAITH_ERR_INVALID; + + size_t device_link_req_size = sizeof(faith_client_device_link_req_t); + + if(device_link_req_size < buf_cap_in_bytes) + return FAITH_ERR_OVERFLOW; + + size_t offset = 0; + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, req->auth_id.bytes, + sizeof(req->auth_id.bytes)); + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, req->public_key_new_device, + sizeof(req->public_key_new_device)); + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, req->device_id_new.bytes, + sizeof(req->device_id_new.bytes)); + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, req->code, sizeof(req->code)); + + _FH_CHECK_RETURN(faith_write_u64_be(out_buf + offset, req->expires_at_ms)); + + *out_size = device_link_req_size; + + return FAITH_OK; +} + +faith_status_code_t +faith_decode_device_link_req(const uint8_t *payload, size_t payload_size, + faith_client_device_link_req_t *o_req) { + if (!o_req) + return FAITH_ERR_INVALID; + + memset(o_req, 0, sizeof(*o_req)); + + if (!payload) + return payload_size == 0 ? FAITH_ERR_BAD_FRAME : FAITH_ERR_INVALID; + + if (payload_size < sizeof(faith_client_device_link_req_t)) + return FAITH_ERR_BAD_FRAME; + + faith_client_id_t auth_id = {0}; + size_t offset = 0; + FAITH_DECODE_RETURN(payload, payload_size, offset, auth_id.bytes, + sizeof(auth_id.bytes)); + + uint8_t public_key_new_device[FAITH_ED25519_PUBLIC_KEY_SIZE]; + FAITH_DECODE_RETURN(payload, payload_size, offset, public_key_new_device, + sizeof(public_key_new_device)); + + faith_device_id_t device_id_new = {0}; + FAITH_DECODE_RETURN(payload, payload_size, offset, device_id_new.bytes, + sizeof(device_id_new.bytes)); + + uint8_t code[16]; + FAITH_DECODE_RETURN(payload, payload_size, offset, code, sizeof(code)); + + uint64_t expires_at_ms = faith_read_u64_be(payload + offset) ; + + faith_client_device_link_req_t req = {0}; + req.auth_id = auth_id, + memcpy(req.public_key_new_device, public_key_new_device, + sizeof(public_key_new_device)); + req.device_id_new = device_id_new, memcpy(req.code, code, sizeof(code)); + req.expires_at_ms = expires_at_ms; + + *o_req = req; + + return FAITH_OK; +} + +faith_status_code_t faith_encode_device_link_approval( + uint8_t *out_buf, size_t *out_size, size_t buf_cap_in_bytes, + const faith_client_device_link_approval_t *approval) { + if (!out_buf || !out_size || !approval) + return FAITH_ERR_INVALID; + + const size_t approval_size = sizeof(faith_client_device_link_approval_t); + + if (buf_cap_in_bytes < approval_size) + return FAITH_ERR_OVERFLOW; + + size_t offset = 0; + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, + approval->signature_approval, + sizeof(approval->signature_approval)); + + FAITH_APPEND_RETURN(out_buf, buf_cap_in_bytes, offset, + approval->device_id_new.bytes, + sizeof(approval->device_id_new.bytes)); + + *out_size = approval_size; + + return FAITH_OK; +} + +faith_status_code_t faith_decode_device_link_approval( + const uint8_t *payload, size_t payload_size, + faith_client_device_link_approval_t *o_approval) { + if (!o_approval) + return FAITH_ERR_INVALID; + + memset(o_approval, 0, sizeof(*o_approval)); + + if (!payload) + return payload_size == 0 ? FAITH_ERR_BAD_FRAME : FAITH_ERR_INVALID; + + if (payload_size < sizeof(faith_client_device_link_approval_t)) + return FAITH_ERR_BAD_FRAME; + + size_t offset = 0; + + FAITH_DECODE_RETURN(payload, payload_size, offset, + o_approval->signature_approval, + sizeof(o_approval->signature_approval)); + + FAITH_DECODE_RETURN(payload, payload_size, offset, + o_approval->device_id_new.bytes, + sizeof(o_approval->device_id_new.bytes)); + + return FAITH_OK; +} + void faith_log_handler(Nob_Log_Level level, const char *fmt, va_list args) { if (level < nob_minimal_log_level) return; @@ -595,39 +808,62 @@ faith_gen_ed25519_keypair(void *handle, } faith_status_code_t -faith_gen_signing_msg_buf(uint8_t *o_buf, size_t buf_cap, - const faith_client_id_t *client_id, - uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE], - uint64_t client_nonce, uint64_t server_nonce) { - if (!o_buf) +faith_gen_sign_buf_hello_handshake(uint8_t *o_buf, size_t buf_cap_in_bytes, + const faith_signature_hello_handshake_t* src) { + if (!src || !o_buf) return FAITH_ERR_INVALID; - if (buf_cap < FAITH_HELLO_HANDSHAKE_SIGNATURE_SIZE) - return FAITH_ERR_INVALID; - if (!public_key) + + if (buf_cap_in_bytes < sizeof(faith_signature_hello_handshake_t)) return FAITH_ERR_INVALID; size_t offset = 0; - memcpy(o_buf, client_id->bytes, sizeof(client_id->bytes)); - offset += sizeof(client_id->bytes); - if (offset > buf_cap) - return FAITH_ERR_INVALID; + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, src->auth_id.bytes, + sizeof(src->auth_id.bytes)); - memcpy(o_buf + offset, public_key, FAITH_ED25519_PUBLIC_KEY_SIZE); - offset += FAITH_ED25519_PUBLIC_KEY_SIZE; - if (offset > buf_cap) - return FAITH_ERR_INVALID; + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, src->public_key, + sizeof(src->public_key)); + + _FH_CHECK_RETURN(faith_write_u64_be(o_buf + offset, src->client_nonce)); + offset += sizeof(src->client_nonce); - memcpy(o_buf + offset, &client_nonce, sizeof(client_nonce)); - offset += sizeof(client_nonce); - if (offset > buf_cap) + _FH_CHECK_RETURN(faith_write_u64_be(o_buf + offset, src->server_nonce)); + offset += sizeof(src->server_nonce); + + return FAITH_OK; +} + +faith_status_code_t faith_gen_sign_buf_device_link_approval( + uint8_t *o_buf, size_t buf_cap_in_bytes, + const faith_signature_device_link_approval_t *src) { + if (!src || !o_buf) return FAITH_ERR_INVALID; - memcpy(o_buf + offset, &server_nonce, sizeof(server_nonce)); - offset += sizeof(server_nonce); - if (offset > buf_cap) + if (buf_cap_in_bytes < sizeof(faith_signature_device_link_approval_t)) return FAITH_ERR_INVALID; + size_t offset = 0; + + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, src->auth_id.bytes, + sizeof(src->auth_id.bytes)); + + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, src->device_id_new.bytes, + sizeof(src->device_id_new.bytes)); + + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, + src->public_key_new_device, + sizeof(src->public_key_new_device)); + + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, src->code, + sizeof(src->code)); + + _FH_CHECK_RETURN(faith_write_u64_be(o_buf + offset, src->expires_at_ms)); + offset += sizeof(src->expires_at_ms); + + FAITH_APPEND_RETURN(o_buf, buf_cap_in_bytes, offset, + src->device_id_approving.bytes, + sizeof(src->device_id_approving.bytes)); + return FAITH_OK; } diff --git a/faithd/src/shared.h b/faithd/src/shared.h index 79b14ed..2092c1a 100644 --- a/faithd/src/shared.h +++ b/faithd/src/shared.h @@ -7,7 +7,7 @@ #include "../nob.h" #define FAITH_PROTO_VERSION faith_version_pack(0, 0, 1) -#define FAITH_MAX_FRAME_LEN 128 +#define FAITH_MAX_FRAME_LEN 256 #define FAITH_MAX_MSG_SIZE 65536 #define FAITH_MAX_PAYLOAD_SIZE 128 @@ -32,13 +32,10 @@ sizeof(uint32_t) /* body size */) #define FAITH_ENVL_HELLO_BODY_SIZE \ - sizeof(faith_device_id_t) + FAITH_ED25519_PUBLIC_KEY_SIZE + sizeof(uint64_t) + ((uint32_t)(sizeof(faith_device_id_t) + FAITH_ED25519_PUBLIC_KEY_SIZE + \ + sizeof(uint64_t))) -#define FAITH_ENVL_HELLO_CHALLENGE_BODY_SIZE sizeof(uint64_t) - -#define FAITH_HELLO_HANDSHAKE_SIGNATURE_SIZE \ - sizeof(faith_client_id_t) + FAITH_ED25519_PUBLIC_KEY_SIZE + \ - sizeof(uint64_t) + sizeof(uint64_t) +#define FAITH_ENVL_HELLO_CHALLENGE_BODY_SIZE ((uint32_t)(sizeof(uint64_t))) #define _FH_CHECK_RETURN(expr) \ do { \ @@ -63,6 +60,12 @@ #define _FH_CHECK_DEFER(expr) _FH_CHECK_GOTO((expr), _fh_result, defer) +#define _FH_RETURN_DEFER(rc) \ + do { \ + _fh_result = (rc); \ + goto defer; \ + } while (0) + #define _FH_CHECK(expr) \ faith_status_code_t _fh_rc = (expr); \ do { \ @@ -72,6 +75,18 @@ } \ } while (0) +#define FAITH_APPEND_RETURN(buf, cap, off, ptr, len) \ + do { \ + if (!faith_buf_append((buf), (cap), &(off), (ptr), (len))) \ + return FAITH_ERR_INVALID; \ + } while (0) + +#define FAITH_DECODE_RETURN(buf, size, off, dst, len) \ + do { \ + if (!faith_buf_decode((buf), (size), &(off), (dst), (len))) \ + return FAITH_ERR_BAD_FRAME; \ + } while (0) + typedef struct { uint32_t frame_size; uint16_t proto_ver; @@ -99,7 +114,9 @@ typedef struct { X(FAITH_ERR_NOT_FOUND, 14) \ X(FAITH_ERR_SSL, 15) \ X(FAITH_ERR_CRYPTO, 16) \ - X(FAITH_ERR_NOT_EQUAL, 17) + X(FAITH_ERR_NOT_EQUAL, 17) \ + X(FAITH_ERR_NOT_CONNECTED, 18) \ + X(FAITH_ERR_EXPIRED, 19) typedef enum { #define X(name, value) name = value, @@ -114,7 +131,9 @@ typedef enum { X(FAITH_EVENT_DISCONNECTED, 3) \ X(FAITH_EVENT_PONG, 4) \ X(FAITH_EVENT_ERROR, 5) \ - X(FAITH_EVENT_MESSAGE_RECEIVED, 6) + X(FAITH_EVENT_MESSAGE_RECEIVED, 6) \ + X(FAITH_EVENT_DEVICE_AUTH_PENDING, 7) \ + X(FAITH_EVENT_DEVICE_LINK_REQUEST, 8) typedef enum { #define X(name, value) name = value, @@ -141,7 +160,13 @@ typedef enum { X(FAITH_ENVELOPE_MSG_ACK, 4) \ X(FAITH_ENVELOPE_MSG_ERR, 5) \ X(FAITH_ENVELOPE_CHALLENGE, 6) \ - X(FAITH_ENVELOPE_CHALLENGE_RESPONSE, 7) + X(FAITH_ENVELOPE_CHALLENGE_RESPONSE, 7) \ + X(FAITH_ENVELOPE_DEVICE_LINK_REQUEST, 8) \ + X(FAITH_ENVELOPE_DEVICE_AUTH_PENDING, 9) \ + X(FAITH_ENVELOPE_DEVICE_AUTH_APPROVE, 10) \ + X(FAITH_ENVELOPE_DEVICE_AUTH_DENY, 11) + +#define FAITH_DEVICE_LINK_REQ_EXPIRATION_TIME_MS 1000 * 30 // 30 seconds typedef enum { #define X(name, value) name = value, @@ -168,6 +193,42 @@ typedef struct { uint8_t *body; } faith_envelope_t; +typedef struct { + // Auth ID of the session that wants to link a new device + faith_client_id_t auth_id; + uint8_t public_key_new_device[FAITH_ED25519_PUBLIC_KEY_SIZE]; + faith_device_id_t device_id_new; + + // 128 bit randomly generated verification code + uint8_t code[16]; + + uint64_t expires_at_ms; +} faith_client_device_link_req_t; + +typedef struct { + uint8_t signature_approval[FAITH_ED25519_SIGNATURE_SIZE]; + faith_device_id_t device_id_new; + +} faith_client_device_link_approval_t; + +typedef struct { + faith_client_id_t auth_id; + uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE]; + uint64_t client_nonce; + uint64_t server_nonce; + +} faith_signature_hello_handshake_t; + +typedef struct { + faith_client_id_t auth_id; + faith_device_id_t device_id_new; + uint8_t public_key_new_device[FAITH_ED25519_PUBLIC_KEY_SIZE]; + uint8_t code[16]; + uint64_t expires_at_ms; + faith_device_id_t device_id_approving; + +} faith_signature_device_link_approval_t; + uint16_t faith_version_pack(uint8_t major, uint8_t minor, uint8_t patch); uint8_t faith_version_major(uint16_t v); uint8_t faith_version_minor(uint16_t v); @@ -175,6 +236,12 @@ uint8_t faith_version_patch(uint16_t v); const char *faith_strerror(int code); +int faith_buf_append(uint8_t *buf, size_t cap, size_t *offset, const void *src, + size_t len); + +int faith_buf_decode(const uint8_t *buf, size_t size, size_t *offset, void *dst, + size_t len); + faith_status_code_t faith_write_bytes_sync(SSL *ssl, const uint8_t *buf, size_t size); faith_status_code_t faith_read_bytes_sync(SSL *ssl, uint8_t *buf, size_t size); @@ -215,6 +282,23 @@ faith_status_code_t faith_decode_envelope(const uint8_t *payload, size_t payload_size, faith_envelope_t *o_envl); +faith_status_code_t +faith_encode_device_link_req(uint8_t *out_buf, size_t *out_size, + size_t buf_cap_in_bytes, + const faith_client_device_link_req_t *req); + +faith_status_code_t +faith_decode_device_link_req(const uint8_t *payload, size_t payload_size, + faith_client_device_link_req_t *o_req); + +faith_status_code_t faith_encode_device_link_approval( + uint8_t *out_buf, size_t *out_size, size_t buf_cap_in_bytes, + const faith_client_device_link_approval_t *approval); + +faith_status_code_t faith_decode_device_link_approval( + const uint8_t *payload, size_t payload_size, + faith_client_device_link_approval_t *o_approval); + void faith_log_handler(Nob_Log_Level level, const char *fmt, va_list args); int faith_client_id_equal(faith_client_id_t a, faith_client_id_t b); @@ -231,11 +315,13 @@ faith_gen_ed25519_keypair(void *handle, uint8_t private_key[FAITH_ED25519_PRIVATE_KEY_SIZE], uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE]); -faith_status_code_t -faith_gen_signing_msg_buf(uint8_t *o_buf, size_t buf_cap, - const faith_client_id_t *client_id, - uint8_t public_key[FAITH_ED25519_PUBLIC_KEY_SIZE], - uint64_t client_nonce, uint64_t server_nonce); +faith_status_code_t faith_gen_sign_buf_hello_handshake( + uint8_t *o_buf, size_t buf_cap_in_bytes, + const faith_signature_hello_handshake_t *src); + +faith_status_code_t faith_gen_sign_buf_device_link_approval( + uint8_t *o_buf, size_t buf_cap_in_bytes, + const faith_signature_device_link_approval_t *src); faith_status_code_t faith_gen_signature(EVP_PKEY *keypair, uint8_t *o_signature, size_t *o_signature_size,