ssb: Work in progress invite support. We can generate them. We can connect using an invite code. We can't yet invite.use().

This commit is contained in:
Cory McWilliams 2025-01-19 16:00:37 -05:00
parent 11564a5292
commit fd09a766d2
9 changed files with 388 additions and 37 deletions

172
src/ssb.c
View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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.

View File

@ -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. */

View File

@ -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

View File

@ -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);
/** @} */

View File

@ -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
}

View File

@ -10,6 +10,8 @@
#include <stdbool.h>
typedef struct uv_loop_s uv_loop_t;
/**
** Register utility script functions.
** @param context The JS context.