diff --git a/src/ssb.c b/src/ssb.c index db5137ae..00639341 100644 --- a/src/ssb.c +++ b/src/ssb.c @@ -103,6 +103,7 @@ typedef struct _tf_ssb_broadcast_t struct sockaddr_in addr; tf_ssb_connection_t* tunnel_connection; uint8_t pub[crypto_sign_PUBLICKEYBYTES]; + uint8_t invite[crypto_sign_ed25519_SEEDBYTES]; } tf_ssb_broadcast_t; typedef struct _tf_ssb_rpc_callback_node_t tf_ssb_rpc_callback_node_t; @@ -292,6 +293,9 @@ typedef struct _tf_ssb_connection_t char host[256]; int port; + uint8_t invite_pub[crypto_sign_PUBLICKEYBYTES]; + uint8_t invite_priv[crypto_sign_SECRETKEYBYTES]; + tf_ssb_state_t state; bool is_attendant; int32_t attendant_request_number; @@ -365,7 +369,7 @@ static void _tf_ssb_connection_finalizer(JSRuntime* runtime, JSValue value); static void _tf_ssb_connection_on_close(uv_handle_t* handle); static void _tf_ssb_nonce_inc(uint8_t* nonce); static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection); -static bool _tf_ssb_parse_broadcast(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast); +static bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast); static void _tf_ssb_start_update_settings(tf_ssb_t* ssb); static void _tf_ssb_update_settings(tf_ssb_t* ssb); static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size); @@ -475,6 +479,16 @@ static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t si } } +static const uint8_t* _tf_ssb_connection_get_public_key(tf_ssb_connection_t* connection) +{ + return (connection->flags & k_tf_ssb_connect_flag_use_invite) ? connection->invite_pub : connection->ssb->pub; +} + +static const uint8_t* _tf_ssb_connection_get_secret_key(tf_ssb_connection_t* connection) +{ + return (connection->flags & k_tf_ssb_connect_flag_use_invite) ? connection->invite_priv : connection->ssb->priv; +} + static void _tf_ssb_connection_send_identity(tf_ssb_connection_t* connection, uint8_t* hmac, uint8_t* pubkey) { memcpy(connection->serverepub, pubkey, sizeof(connection->serverepub)); @@ -514,15 +528,15 @@ static void _tf_ssb_connection_send_identity(tf_ssb_connection_t* connection, ui memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->serverpub), hash, sizeof(hash)); unsigned long long siglen; - if (crypto_sign_detached(connection->detached_signature_A, &siglen, msg, sizeof(msg), connection->ssb->priv) != 0) + if (crypto_sign_detached(connection->detached_signature_A, &siglen, msg, sizeof(msg), _tf_ssb_connection_get_secret_key(connection)) != 0) { tf_ssb_connection_close(connection, "unable to compute detached_signature_A as client"); return; } - uint8_t tosend[crypto_sign_BYTES + sizeof(connection->ssb->pub)]; + uint8_t tosend[crypto_sign_BYTES + crypto_sign_PUBLICKEYBYTES]; memcpy(tosend, connection->detached_signature_A, sizeof(connection->detached_signature_A)); - memcpy(tosend + sizeof(connection->detached_signature_A), connection->ssb->pub, sizeof(connection->ssb->pub)); + memcpy(tosend + sizeof(connection->detached_signature_A), _tf_ssb_connection_get_public_key(connection), crypto_sign_PUBLICKEYBYTES); uint8_t nonce[crypto_secretbox_NONCEBYTES] = { 0 }; uint8_t tohash[sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB)]; @@ -1259,7 +1273,7 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection, } uint8_t clientcurvepriv[crypto_scalarmult_curve25519_SCALARBYTES]; - if (crypto_sign_ed25519_sk_to_curve25519(clientcurvepriv, connection->ssb->priv) != 0) + if (crypto_sign_ed25519_sk_to_curve25519(clientcurvepriv, _tf_ssb_connection_get_secret_key(connection)) != 0) { tf_ssb_connection_close(connection, "unable to convert key to curve25519"); return; @@ -1282,7 +1296,7 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection, uint8_t hash3a[crypto_hash_sha256_BYTES + crypto_sign_PUBLICKEYBYTES]; crypto_hash_sha256(hash3a, hash2, sizeof(hash2)); - memcpy(hash3a + crypto_hash_sha256_BYTES, connection->ssb->pub, sizeof(connection->ssb->pub)); + memcpy(hash3a + crypto_hash_sha256_BYTES, _tf_ssb_connection_get_public_key(connection), crypto_sign_PUBLICKEYBYTES); crypto_hash_sha256(connection->s_to_c_box_key, hash3a, sizeof(hash3a)); uint8_t hash3b[crypto_hash_sha256_BYTES + crypto_sign_PUBLICKEYBYTES]; @@ -1300,11 +1314,11 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection, uint8_t hash3[crypto_hash_sha256_BYTES]; crypto_hash_sha256(hash3, shared_secret_ab, sizeof(shared_secret_ab)); - uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub) + sizeof(hash3)]; + uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + crypto_sign_PUBLICKEYBYTES + sizeof(hash3)]; memcpy(msg, connection->ssb->network_key, sizeof(connection->ssb->network_key)); memcpy(msg + sizeof(connection->ssb->network_key), connection->detached_signature_A, sizeof(connection->detached_signature_A)); - memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A), connection->ssb->pub, sizeof(connection->ssb->pub)); - memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub), hash3, sizeof(hash3)); + memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A), _tf_ssb_connection_get_public_key(connection), crypto_sign_PUBLICKEYBYTES); + memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + crypto_sign_PUBLICKEYBYTES, hash3, sizeof(hash3)); if (crypto_sign_verify_detached(m, msg, sizeof(msg), connection->serverpub) != 0) { tf_ssb_connection_close(connection, "unable to verify server identity"); @@ -1428,7 +1442,7 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne ** ) */ uint8_t curvepriv[crypto_scalarmult_curve25519_SCALARBYTES]; - if (crypto_sign_ed25519_sk_to_curve25519(curvepriv, connection->ssb->priv) != 0) + if (crypto_sign_ed25519_sk_to_curve25519(curvepriv, _tf_ssb_connection_get_secret_key(connection)) != 0) { tf_ssb_connection_close(connection, "unable to convert key to curve25519"); return; @@ -1488,10 +1502,10 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne uint8_t hash3[crypto_hash_sha256_BYTES]; crypto_hash_sha256(hash3, shared_secret_ab, sizeof(shared_secret_ab)); - uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->ssb->pub) + sizeof(hash3)]; + uint8_t msg[sizeof(connection->ssb->network_key) + crypto_sign_PUBLICKEYBYTES + sizeof(hash3)]; memcpy(msg, connection->ssb->network_key, sizeof(connection->ssb->network_key)); - memcpy(msg + sizeof(connection->ssb->network_key), connection->ssb->pub, sizeof(connection->ssb->pub)); - memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->ssb->pub), hash3, sizeof(hash3)); + memcpy(msg + sizeof(connection->ssb->network_key), _tf_ssb_connection_get_public_key(connection), crypto_sign_PUBLICKEYBYTES); + memcpy(msg + sizeof(connection->ssb->network_key) + crypto_sign_PUBLICKEYBYTES, hash3, sizeof(hash3)); if (crypto_sign_verify_detached(detached_signature_A, msg, sizeof(msg), connection->serverpub) != 0) { tf_ssb_connection_close(connection, "unable to verify client identity"); @@ -1523,7 +1537,7 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne uint8_t detached_signature_B[crypto_sign_BYTES]; unsigned long long siglen; - if (crypto_sign_detached(detached_signature_B, &siglen, sign_b, sizeof(sign_b), connection->ssb->priv) != 0) + if (crypto_sign_detached(detached_signature_B, &siglen, sign_b, sizeof(sign_b), _tf_ssb_connection_get_secret_key(connection)) != 0) { tf_ssb_connection_close(connection, "unable to compute detached_signature_B as server"); return; @@ -1554,7 +1568,7 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne uint8_t hash3a[crypto_hash_sha256_BYTES + crypto_sign_PUBLICKEYBYTES]; crypto_hash_sha256(hash3a, key_hash, sizeof(key_hash)); - memcpy(hash3a + crypto_hash_sha256_BYTES, connection->ssb->pub, sizeof(connection->ssb->pub)); + memcpy(hash3a + crypto_hash_sha256_BYTES, _tf_ssb_connection_get_public_key(connection), crypto_sign_PUBLICKEYBYTES); crypto_hash_sha256(connection->s_to_c_box_key, hash3a, sizeof(hash3a)); uint8_t hash3b[crypto_hash_sha256_BYTES + crypto_sign_PUBLICKEYBYTES]; @@ -1927,6 +1941,10 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch } if (!connection->destroy_reason) { + if (ssb->verbose) + { + tf_printf("Destroying connection: %s.\n", reason); + } connection->destroy_reason = tf_strdup(reason); } _tf_ssb_connection_dispatch_scheduled(connection); @@ -2580,6 +2598,12 @@ void tf_ssb_destroy(tf_ssb_t* ssb) uv_close((uv_handle_t*)&ssb->timers[i]->timer, _tf_ssb_on_timer_close); } + if (ssb->connections_tracker) + { + tf_ssb_connections_destroy(ssb->connections_tracker); + ssb->connections_tracker = NULL; + } + if (!ssb->quiet) { tf_printf("Waiting for closes.\n"); @@ -2588,6 +2612,18 @@ void tf_ssb_destroy(tf_ssb_t* ssb) while (ssb->broadcast_listener.data || ssb->broadcast_sender.data || ssb->broadcast_timer.data || ssb->broadcast_cleanup_timer.data || ssb->trace_timer.data || ssb->server.data || ssb->ref_count || ssb->request_activity_timer.data || ssb->timers_count) { + tf_printf("bl=%p bs=%p bt=%p bc=%p tt=%p s=%p rc=%d rat=%p tc=%d\n", + ssb->broadcast_listener.data, + ssb->broadcast_sender.data, + ssb->broadcast_timer.data, + ssb->broadcast_cleanup_timer.data, + ssb->trace_timer.data, + ssb->server.data, + ssb->ref_count, + ssb->request_activity_timer.data, + ssb->timers_count); + tf_printf("--\n"); + uv_print_all_handles(ssb->loop, stdout); uv_run(ssb->loop, UV_RUN_ONCE); } @@ -2670,12 +2706,6 @@ void tf_ssb_destroy(tf_ssb_t* ssb) tf_printf("Closed.\n"); } - if (ssb->connections_tracker) - { - tf_ssb_connections_destroy(ssb->connections_tracker); - ssb->connections_tracker = NULL; - } - uv_run(ssb->loop, UV_RUN_NOWAIT); if (ssb->loop == &ssb->own_loop) @@ -2824,7 +2854,7 @@ static tf_ssb_connection_t* _tf_ssb_connection_create_internal(tf_ssb_t* ssb, co } static tf_ssb_connection_t* _tf_ssb_connection_create( - tf_ssb_t* ssb, const char* host, const struct sockaddr_in* addr, const uint8_t* public_key, tf_ssb_connect_callback_t* callback, void* user_data) + tf_ssb_t* ssb, const char* host, const struct sockaddr_in* addr, const uint8_t* public_key, const uint8_t* invite, tf_ssb_connect_callback_t* callback, void* user_data) { for (tf_ssb_connection_t* connection = ssb->connections; connection; connection = connection->next) { @@ -2862,6 +2892,11 @@ static tf_ssb_connection_t* _tf_ssb_connection_create( connection->connect_callback = callback; connection->connect_callback_user_data = user_data; + if (invite) + { + crypto_sign_ed25519_seed_keypair(connection->invite_pub, connection->invite_priv, invite); + } + char public_key_str[k_id_base64_len] = { 0 }; if (tf_ssb_id_bin_to_str(public_key_str, sizeof(public_key_str), public_key)) { @@ -2965,6 +3000,7 @@ typedef struct _connect_t int port; int flags; uint8_t key[k_id_bin_len]; + uint8_t invite[crypto_sign_ed25519_SEEDBYTES]; tf_ssb_connect_callback_t* callback; void* user_data; } connect_t; @@ -2978,7 +3014,8 @@ static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, s { struct sockaddr_in addr = *(struct sockaddr_in*)info->ai_addr; addr.sin_port = htons(connect->port); - tf_ssb_connection_t* connection = _tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key, connect->callback, connect->user_data); + uint8_t* invite = (connect->flags & k_tf_ssb_connect_flag_use_invite) ? connect->invite : NULL; + tf_ssb_connection_t* connection = _tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key, invite, connect->callback, connect->user_data); if (connection) { connection->flags = connect->flags; @@ -3043,6 +3080,55 @@ void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* ke } } +static void _tf_ssb_connect_with_invite(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key, const uint8_t* invite, int connect_flags, tf_ssb_connect_callback_t* callback, void* user_data) +{ + if (ssb->shutting_down) + { + if (callback) + { + callback(NULL, "Shutting down.", user_data); + } + return; + } + + char connecting_to[k_id_base64_len]; + tf_ssb_id_bin_to_str(connecting_to, sizeof(connecting_to), key); + tf_printf("connecting to %s\n", connecting_to); + + connect_t* connect = tf_malloc(sizeof(connect_t)); + *connect = (connect_t) { + .ssb = ssb, + .port = port, + .flags = connect_flags | k_tf_ssb_connect_flag_use_invite, + .req.data = connect, + .callback = callback, + .user_data = user_data, + }; + char id[k_id_base64_len] = { 0 }; + tf_ssb_id_bin_to_str(id, sizeof(id), key); + if ((connect_flags & k_tf_ssb_connect_flag_do_not_store) == 0) + { + tf_ssb_connections_store(ssb->connections_tracker, host, port, id); + } + snprintf(connect->host, sizeof(connect->host), "%s", host); + memcpy(connect->key, key, k_id_bin_len); + memcpy(connect->invite, invite, sizeof(connect->invite)); + tf_ssb_ref(ssb); + int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET }); + if (r < 0) + { + if (callback) + { + char reason[1024]; + snprintf(reason, sizeof(reason), "uv_getaddr_info(%s): %s", host, uv_strerror(r)); + callback(NULL, reason, user_data); + } + tf_printf("uv_getaddrinfo(%s): %s\n", host, uv_strerror(r)); + tf_free(connect); + tf_ssb_unref(ssb); + } +} + static void _tf_ssb_on_connection(uv_stream_t* stream, int status) { tf_ssb_t* ssb = stream->data; @@ -3177,7 +3263,7 @@ static void _tf_ssb_update_seeds_after_work(tf_ssb_t* ssb, int status, void* use for (int i = 0; i < seeds->seeds_count; i++) { tf_ssb_broadcast_t broadcast = { .origin = k_tf_ssb_broadcast_origin_peer_exchange }; - if (_tf_ssb_parse_broadcast(seeds->seeds[i], &broadcast)) + if (_tf_ssb_parse_connect_string(seeds->seeds[i], &broadcast)) { _tf_ssb_add_broadcast(ssb, &broadcast, k_seed_expire_seconds); } @@ -3286,17 +3372,25 @@ bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size) return tf_ssb_id_bin_to_str(out_id, out_id_size, ssb->pub); } -static bool _tf_ssb_parse_broadcast(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast) +static bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast) { - char public_key_str[45] = { 0 }; + char public_key_str[54] = { 0 }; + char secret_key_str[45] = { 0 }; int port = 0; static_assert(sizeof(out_broadcast->host) == 256, "host field size"); if (sscanf(in_broadcast, "net:%255[0-9A-Za-z.-]:%d~shs:%44s", out_broadcast->host, &port, public_key_str) == 3) { out_broadcast->addr.sin_family = AF_INET; out_broadcast->addr.sin_port = htons((uint16_t)port); - int r = tf_base64_decode(public_key_str, strlen(public_key_str), out_broadcast->pub, crypto_sign_PUBLICKEYBYTES); - return r != -1; + return tf_base64_decode(public_key_str, strlen(public_key_str), out_broadcast->pub, crypto_sign_PUBLICKEYBYTES) != 0; + } + else if (sscanf(in_broadcast, "%255[0-9A-Za-z.-]:%d:%53s~%44s", out_broadcast->host, &port, public_key_str, secret_key_str) == 4) + { + out_broadcast->addr.sin_family = AF_INET; + out_broadcast->addr.sin_port = htons((uint16_t)port); + return + tf_ssb_id_str_to_bin(out_broadcast->pub, public_key_str) && + tf_base64_decode(secret_key_str, strlen(secret_key_str), out_broadcast->invite, sizeof(out_broadcast->invite)); } else if (strncmp(in_broadcast, "ws:", 3) == 0) { @@ -3308,9 +3402,16 @@ static bool _tf_ssb_parse_broadcast(const char* in_broadcast, tf_ssb_broadcast_t void tf_ssb_connect_str(tf_ssb_t* ssb, const char* address, int connect_flags, tf_ssb_connect_callback_t* callback, void* user_data) { tf_ssb_broadcast_t broadcast = { 0 }; - if (_tf_ssb_parse_broadcast(address, &broadcast)) + if (_tf_ssb_parse_connect_string(address, &broadcast)) { - tf_ssb_connect(ssb, broadcast.host, ntohs(broadcast.addr.sin_port), broadcast.pub, connect_flags, callback, user_data); + if (memcmp(broadcast.invite, (uint8_t[crypto_sign_ed25519_SEEDBYTES]) { 0 }, crypto_sign_ed25519_SEEDBYTES) == 0) + { + tf_ssb_connect(ssb, broadcast.host, ntohs(broadcast.addr.sin_port), broadcast.pub, connect_flags, callback, user_data); + } + else + { + _tf_ssb_connect_with_invite(ssb, broadcast.host, ntohs(broadcast.addr.sin_port), broadcast.pub, broadcast.invite, connect_flags, callback, user_data); + } } else if (callback) { @@ -3397,7 +3498,7 @@ static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broad void tf_ssb_add_broadcast(tf_ssb_t* ssb, const char* connection, tf_ssb_broadcast_origin_t origin, int64_t expires_seconds) { tf_ssb_broadcast_t broadcast = { .origin = origin }; - if (_tf_ssb_parse_broadcast(connection, &broadcast)) + if (_tf_ssb_parse_connect_string(connection, &broadcast)) { _tf_ssb_add_broadcast(ssb, &broadcast, expires_seconds); } @@ -3420,7 +3521,7 @@ static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread, while (entry) { tf_ssb_broadcast_t broadcast = { .origin = k_tf_ssb_broadcast_origin_discovery }; - if (_tf_ssb_parse_broadcast(entry, &broadcast)) + if (_tf_ssb_parse_connect_string(entry, &broadcast)) { _tf_ssb_add_broadcast(ssb, &broadcast, k_udp_discovery_expires_seconds); } @@ -4178,7 +4279,7 @@ void tf_ssb_run_work(tf_ssb_t* ssb, void (*work_callback)(tf_ssb_t* ssb, void* u int result = uv_queue_work(ssb->loop, &work->work, _tf_ssb_work_callback, _tf_ssb_after_work_callback); if (result) { - _tf_ssb_connection_after_work_callback(&work->work, result); + _tf_ssb_after_work_callback(&work->work, result); } } @@ -4276,7 +4377,10 @@ static void _tf_ssb_update_settings(tf_ssb_t* ssb) static void _tf_ssb_start_update_settings(tf_ssb_t* ssb) { - tf_ssb_schedule_work(ssb, 5000, _tf_ssb_start_update_settings_timer, NULL); + if (!ssb->shutting_down) + { + tf_ssb_schedule_work(ssb, 5000, _tf_ssb_start_update_settings_timer, NULL); + } } void tf_ssb_set_verbose(tf_ssb_t* ssb, bool verbose) diff --git a/src/ssb.connections.c b/src/ssb.connections.c index 90c4d5f5..d1ada42b 100644 --- a/src/ssb.connections.c +++ b/src/ssb.connections.c @@ -114,6 +114,7 @@ static void _tf_ssb_connections_timer(uv_timer_t* timer) tf_ssb_connections_t* connections = timer->data; if (tf_ssb_is_shutting_down(connections->ssb)) { + uv_timer_stop(timer); return; } tf_ssb_connection_t* active[4]; @@ -233,7 +234,10 @@ static void _tf_ssb_connections_update_after_work(tf_ssb_t* ssb, int status, voi static void _tf_ssb_connections_queue_update(tf_ssb_connections_t* connections, tf_ssb_connections_update_t* update) { - tf_ssb_run_work(connections->ssb, _tf_ssb_connections_update_work, _tf_ssb_connections_update_after_work, update); + if (!tf_ssb_is_shutting_down(connections->ssb)) + { + tf_ssb_run_work(connections->ssb, _tf_ssb_connections_update_work, _tf_ssb_connections_update_after_work, update); + } } void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key) diff --git a/src/ssb.db.c b/src/ssb.db.c index cd421b9b..4c82498b 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -9,6 +9,8 @@ #include "ow-crypt.h" #include "sodium/crypto_hash_sha256.h" #include "sodium/crypto_sign.h" +#include "sodium/crypto_sign_ed25519.h" +#include "sodium/randombytes.h" #include "sqlite3.h" #include "uv.h" @@ -168,6 +170,14 @@ void tf_ssb_db_init(tf_ssb_t* ssb) _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS identities_user ON identities (user, public_key)"); _tf_ssb_db_exec(db, "DELETE FROM identities WHERE user = ':auth'"); + _tf_ssb_db_exec(db, + "CREATE TABLE IF NOT EXISTS invites (" + " invite_public_key TEXT PRIMARY KEY," + " account TEXT," + " use_count INTEGER," + " expires INTEGER" + ")"); + bool populate_fts = false; if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_fts')")) { @@ -2137,3 +2147,44 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id) } return result; } + +bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, int port, int use_count, int expires_seconds, char* out_invite, size_t size) +{ + if (use_count < -1 || use_count == 0) + { + return false; + } + + uint8_t public_key[crypto_sign_ed25519_PUBLICKEYBYTES] = { 0 }; + uint8_t secret_key[crypto_sign_ed25519_SECRETKEYBYTES] = { 0 }; + uint8_t seed[crypto_sign_ed25519_SEEDBYTES] = { 0 }; + + randombytes_buf(seed, sizeof(seed)); + crypto_sign_ed25519_seed_keypair(public_key, secret_key, seed); + + char public[k_id_base64_len]; + tf_ssb_id_bin_to_str(public, sizeof(public), public_key); + tf_printf("invite is for public key %s\n", public); + + char seed_b64[64]; + tf_base64_encode(seed, sizeof(seed), seed_b64, sizeof(seed_b64)); + + bool inserted = false; + sqlite3_stmt* statement; + if (sqlite3_prepare(db, + "INSERT INTO invites (invite_public_key, account, use_count, expires) VALUES (?, ?, ?, ?)", + -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, public, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK && + sqlite3_bind_int(statement, 3, use_count) == SQLITE_OK && + sqlite3_bind_int64(statement, 4, (int64_t)time(NULL) + expires_seconds) == SQLITE_OK) + { + inserted = sqlite3_step(statement) == SQLITE_DONE; + } + sqlite3_finalize(statement); + } + + snprintf(out_invite, size, "%s:%d:%s~%s", host, port, id, seed_b64); + return inserted; +} diff --git a/src/ssb.db.h b/src/ssb.db.h index 36daf4ab..b7984f5f 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -502,6 +502,20 @@ const char* tf_ssb_db_get_profile(sqlite3* db, const char* id); */ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id); +/** +** Generate an invite code and store information for it to be usable. +** @param db The database. +** @param id The identity. +** @param host Hostname to which recipient should connect. +** @param port The port to which the recipient should connect. +** @param use_count Number of times the invite code is allowed to be used, or -1 for indefinitely. +** @param expires_seconds How long the invite lasts. +** @param out_invite Populated with the invite code on success. +** @param size The size of the out_invite buffer. +** @return true If an invite was generated. +*/ +bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, int port, int use_count, int expires_seconds, char* out_invite, size_t size); + /** ** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use. ** @param user_data User data registered with the authorizer. diff --git a/src/ssb.h b/src/ssb.h index d646dae7..9e0f0194 100644 --- a/src/ssb.h +++ b/src/ssb.h @@ -74,6 +74,7 @@ typedef enum _tf_ssb_connect_flags_t { k_tf_ssb_connect_flag_one_shot = 0x1, k_tf_ssb_connect_flag_do_not_store = 0x2, + k_tf_ssb_connect_flag_use_invite = 0x4, } tf_ssb_connect_flags_t; /** An SSB instance. */ diff --git a/src/ssb.tests.c b/src/ssb.tests.c index 4041b77c..f7037c9d 100644 --- a/src/ssb.tests.c +++ b/src/ssb.tests.c @@ -767,7 +767,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options) static void _ssb_test_room_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data) { - const char* changes[] = { "create", "connect", "remove" }; + const char* changes[] = { "create", "connect", "remove", "update" }; tf_printf("change=%s %p connection=%s:%d\n", changes[change], connection, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection)); } @@ -1221,4 +1221,165 @@ void tf_ssb_test_replicate(const tf_test_options_t* options) uv_loop_close(&loop); } +void tf_ssb_test_connect_str(const tf_test_options_t* options) +{ + tf_printf("Testing connect string.\n"); + + uv_loop_t loop = { 0 }; + uv_loop_init(&loop); + + unlink("out/test_db0.sqlite"); + tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb0), ssb0); + unlink("out/test_db1.sqlite"); + tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb1), ssb1); + + test_t test = { + .ssb0 = ssb0, + .ssb1 = ssb1, + }; + + tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, NULL, &test); + tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, NULL, &test); + + uint8_t priv0[crypto_sign_SECRETKEYBYTES] = { 0 }; + uint8_t priv1[crypto_sign_SECRETKEYBYTES] = { 0 }; + tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0)); + tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1)); + + char id0[k_id_base64_len] = { 0 }; + char id1[k_id_base64_len] = { 0 }; + bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0)); + (void)b; + assert(b); + b = tf_ssb_whoami(ssb1, id1, sizeof(id1)); + assert(b); + tf_printf("ID %s and %s\n", id0, id1); + + char priv0_str[512] = { 0 }; + char priv1_str[512] = { 0 }; + tf_base64_encode(priv0, sizeof(priv0), priv0_str, sizeof(priv0_str)); + tf_base64_encode(priv1, sizeof(priv0), priv1_str, sizeof(priv1_str)); + tf_ssb_db_identity_add(ssb0, "test", id0 + 1, priv0_str); + tf_ssb_db_identity_add(ssb1, "test", id1 + 1, priv1_str); + + tf_ssb_server_open(ssb0, 12347); + + char connect[1024] = { 0 }; + snprintf(connect, sizeof(connect), "net:127.0.0.1:12347~shs:%.44s", id0 + 1); + tf_printf("connect string: %s\n", connect); + tf_ssb_connect_str(ssb1, connect, 0, NULL, NULL); + + tf_printf("Waiting for connection.\n"); + while (test.connection_count0 != 1 || test.connection_count1 != 1) + { + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_ONCE); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + } + tf_ssb_server_close(ssb0); + + tf_ssb_send_close(ssb1); + + tf_printf("final run\n"); + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_DEFAULT); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + tf_printf("done\n"); + + tf_printf("destroy 0\n"); + tf_ssb_destroy(ssb0); + tf_printf("destroy 1\n"); + tf_ssb_destroy(ssb1); + + tf_printf("close\n"); + uv_loop_close(&loop); +} + +void tf_ssb_test_invite(const tf_test_options_t* options) +{ + tf_printf("Testing invites.\n"); + + uv_loop_t loop = { 0 }; + uv_loop_init(&loop); + + unlink("out/test_db0.sqlite"); + tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb0), ssb0); + unlink("out/test_db1.sqlite"); + tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb1), ssb1); + + test_t test = { + .ssb0 = ssb0, + .ssb1 = ssb1, + }; + + tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, NULL, &test); + tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, NULL, &test); + + uint8_t priv0[crypto_sign_SECRETKEYBYTES] = { 0 }; + uint8_t priv1[crypto_sign_SECRETKEYBYTES] = { 0 }; + tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0)); + tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1)); + + char id0[k_id_base64_len] = { 0 }; + char id1[k_id_base64_len] = { 0 }; + bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0)); + (void)b; + assert(b); + b = tf_ssb_whoami(ssb1, id1, sizeof(id1)); + assert(b); + tf_printf("ID %s and %s\n", id0, id1); + + char priv0_str[512] = { 0 }; + char priv1_str[512] = { 0 }; + tf_base64_encode(priv0, sizeof(priv0), priv0_str, sizeof(priv0_str)); + tf_base64_encode(priv1, sizeof(priv0), priv1_str, sizeof(priv1_str)); + + tf_ssb_server_open(ssb0, 12347); + + sqlite3* writer = tf_ssb_acquire_db_writer(ssb0); + char invite[1024]; + tf_ssb_db_generate_invite(writer, id0, "127.0.0.1", 12347, 1, 60 * 60, invite, sizeof(invite)); + tf_ssb_release_db_writer(ssb0, writer); + tf_printf("invite: %s\n", invite); + + tf_ssb_connect_str(ssb1, invite, 0, NULL, NULL); + + tf_printf("Waiting for connection.\n"); + while (test.connection_count0 != 1 || test.connection_count1 != 1) + { + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_ONCE); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + } + + tf_ssb_server_close(ssb0); + tf_ssb_send_close(ssb1); + + tf_printf("final run\n"); + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_DEFAULT); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + tf_printf("done\n"); + + tf_printf("destroy 0\n"); + tf_ssb_destroy(ssb0); + tf_printf("destroy 1\n"); + tf_ssb_destroy(ssb1); + + tf_printf("close\n"); + uv_loop_close(&loop); +} + #endif diff --git a/src/ssb.tests.h b/src/ssb.tests.h index 00480af4..2e414bcf 100644 --- a/src/ssb.tests.h +++ b/src/ssb.tests.h @@ -66,9 +66,21 @@ void tf_ssb_test_peer_exchange(const tf_test_options_t* options); void tf_ssb_test_publish(const tf_test_options_t* options); /** -** Test replication. +** Test connecting by string. ** @param options The test options. */ void tf_ssb_test_replicate(const tf_test_options_t* options); +/** +** Test invites. +** @param options The test options. +*/ +void tf_ssb_test_connect_str(const tf_test_options_t* options); + +/** +** Test invites. +** @param options The test options. +*/ +void tf_ssb_test_invite(const tf_test_options_t* options); + /** @} */ diff --git a/src/tests.c b/src/tests.c index d8664fbe..84d38e6e 100644 --- a/src/tests.c +++ b/src/tests.c @@ -1079,6 +1079,8 @@ void tf_tests(const tf_test_options_t* options) _tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false); _tf_test_run(options, "publish", tf_ssb_test_publish, false); _tf_test_run(options, "replicate", tf_ssb_test_replicate, false); + _tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false); + _tf_test_run(options, "invite", tf_ssb_test_invite, false); tf_printf("Tests completed.\n"); #endif } diff --git a/src/util.js.h b/src/util.js.h index 228e2957..2d965f44 100644 --- a/src/util.js.h +++ b/src/util.js.h @@ -10,6 +10,8 @@ #include +typedef struct uv_loop_s uv_loop_t; + /** ** Register utility script functions. ** @param context The JS context.