From f764007fc6a5d10c13a65c76326368d04b87fdd7 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 14 Jul 2022 01:01:14 +0000 Subject: [PATCH] Tiny steps toward getting away from one global identity. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3932 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/core.js | 14 ++++++++ src/ssb.c | 28 +++++++++++++--- src/ssb.db.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ssb.db.h | 5 +++ src/ssb.h | 2 ++ src/ssb.js.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 220 insertions(+), 5 deletions(-) diff --git a/core/core.js b/core/core.js index 2062e0cf..25c98d31 100644 --- a/core/core.js +++ b/core/core.js @@ -192,6 +192,20 @@ async function getProcessBlob(blobId, key, options) { } }; imports.ssb = Object.fromEntries(Object.keys(ssb).map(key => [key, ssb[key].bind(ssb)])); + imports.ssb.createIdentity = function() { + if (process.credentials && + process.credentials.session && + process.credentials.session.name) { + return ssb.createIdentity(process.credentials.session.name); + } + }; + imports.ssb.getIdentities = function() { + if (process.credentials && + process.credentials.session && + process.credentials.session.name) { + return ssb.getIdentities(process.credentials.session.name); + } + }; if (process.credentials && process.credentials.session && process.credentials.session.name) { diff --git a/src/ssb.c b/src/ssb.c index 534b39dd..93bcecbe 100644 --- a/src/ssb.c +++ b/src/ssb.c @@ -1267,11 +1267,8 @@ static bool _tf_ssb_connection_box_stream_recv(tf_ssb_connection_t* connection) return true; } -void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message) +void tf_ssb_append_message_with_keys(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message) { - char author[k_id_base64_len]; - tf_ssb_id_bin_to_str(author, sizeof(author), ssb->pub); - char previous_id[crypto_hash_sha256_BYTES * 2]; int64_t previous_sequence = 0; bool have_previous = tf_ssb_db_get_latest_message_by_author(ssb, author, &previous_sequence, previous_id, sizeof(previous_id)); @@ -1304,7 +1301,7 @@ void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message) uint8_t signature[crypto_sign_BYTES]; unsigned long long siglen; - bool valid = crypto_sign_detached(signature, &siglen, (const uint8_t*)json, len, ssb->priv) == 0; + bool valid = crypto_sign_detached(signature, &siglen, (const uint8_t*)json, len, private_key) == 0; JS_FreeCString(context, json); JS_FreeValue(context, jsonval); @@ -1342,6 +1339,13 @@ void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message) JS_FreeValue(context, root); } +void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message) +{ + char author[k_id_base64_len]; + tf_ssb_id_bin_to_str(author, sizeof(author), ssb->pub); + tf_ssb_append_message_with_keys(ssb, author, ssb->priv, message); +} + void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason) { tf_ssb_t* ssb = connection->ssb; @@ -1825,6 +1829,20 @@ void tf_ssb_generate_keys(tf_ssb_t* ssb) crypto_sign_ed25519_keypair(ssb->pub, ssb->priv); } +void tf_ssb_generate_keys_buffer(char* out_public, size_t public_size, char* out_private, size_t private_size) +{ + uint8_t public[crypto_sign_PUBLICKEYBYTES]; + uint8_t private[crypto_sign_SECRETKEYBYTES]; + crypto_sign_ed25519_keypair(public, private); + + uint8_t buffer[512]; + base64c_encode(public, sizeof(public), buffer, sizeof(buffer)); + snprintf(out_public, public_size, "%s.ed25519", buffer); + + base64c_encode(private, sizeof(private), buffer, sizeof(buffer)); + snprintf(out_private, private_size, "%s.ed25519", buffer); +} + void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace) { ssb->trace = trace; diff --git a/src/ssb.db.c b/src/ssb.db.c index 57fbc6b6..6827ffad 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,13 @@ void tf_ssb_db_init(tf_ssb_t* ssb) " last_success INTEGER," " UNIQUE(host, port, key)" ")"); + _tf_ssb_db_exec(db, + "CREATE TABLE IF NOT EXISTS identities (" + " user TEXT," + " public_key TEXT UNIQUE," + " private_key TEXT UNIQUE" + ")"); + _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS identities_user ON identities (user, public_key)"); bool need_add_sequence_before_author = true; bool need_convert_timestamp_to_real = false; @@ -690,3 +698,86 @@ bool tf_ssb_db_check(sqlite3* db, const char* check_author) JS_FreeRuntime(runtime); return false; } + +int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user) +{ + int count = 0; + sqlite3* db = tf_ssb_get_db(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "SELECT COUNT(*) FROM identities WHERE user = ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) == SQLITE_ROW) + { + count = sqlite3_column_int(statement, 0); + } + } + sqlite3_finalize(statement); + } + return count; +} + +bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key) +{ + bool added = false; + sqlite3* db = tf_ssb_get_db(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "INSERT INTO identities (user, public_key, private_key) VALUES (?, ?, ?) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 2, public_key, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 3, private_key, -1, NULL) == SQLITE_OK) + { + added = + sqlite3_step(statement) == SQLITE_DONE && + sqlite3_changes(db) != 0; + if (!added) + { + printf("Unable to add identity: %s.\n", sqlite3_errmsg(db)); + } + } + sqlite3_finalize(statement); + } + return added; +} + +void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data) +{ + sqlite3* db = tf_ssb_get_db(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "SELECT public_key FROM identities WHERE user = ? ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK) + { + while (sqlite3_step(statement) == SQLITE_ROW) + { + callback((const char*)sqlite3_column_text(statement, 0), user_data); + } + } + sqlite3_finalize(statement); + } +} + +bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size) +{ + bool success = false; + memset(out_private_key, 0, crypto_sign_SECRETKEYBYTES); + sqlite3* db = tf_ssb_get_db(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 2, (public_key && *public_key == '@') ? public_key + 1 : public_key, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) == SQLITE_ROW) + { + const uint8_t* key = sqlite3_column_text(statement, 0); + int r = base64c_decode(key, sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), out_private_key, private_key_size); + success = r > 0; + } + } + sqlite3_finalize(statement); + } + return success; +} diff --git a/src/ssb.db.h b/src/ssb.db.h index 8a4b1571..62e2e373 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -17,3 +17,8 @@ JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue bi typedef struct sqlite3 sqlite3; bool tf_ssb_db_check(sqlite3* db, const char* author); + +int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user); +bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key); +void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data); +bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size); diff --git a/src/ssb.h b/src/ssb.h index bca6129b..6e08f88c 100644 --- a/src/ssb.h +++ b/src/ssb.h @@ -64,6 +64,7 @@ sqlite3* tf_ssb_get_db(tf_ssb_t* ssb); uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb); void tf_ssb_generate_keys(tf_ssb_t* ssb); +void tf_ssb_generate_keys_buffer(char* out_public, size_t public_size, char* out_private, size_t private_size); void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace); tf_trace_t* tf_ssb_get_trace(tf_ssb_t* ssb); @@ -73,6 +74,7 @@ JSContext* tf_ssb_get_context(tf_ssb_t* ssb); void tf_ssb_broadcast_listener_start(tf_ssb_t* ssb, bool linger); void tf_ssb_run(tf_ssb_t* ssb); void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message); +void tf_ssb_append_message_with_keys(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message); void tf_ssb_append_post(tf_ssb_t* ssb, const char* text); bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size); diff --git a/src/ssb.js.c b/src/ssb.js.c index a6e9543c..725aa004 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -32,6 +32,87 @@ static JSValue _tf_ssb_whoami(JSContext* context, JSValueConst this_val, int arg return JS_NULL; } +static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + JSValue result = JS_UNDEFINED; + if (ssb) + { + const char* user = JS_ToCString(context, argv[0]); + int count = tf_ssb_db_identity_get_count_for_user(ssb, user); + if (count < 16) + { + char public[512]; + char private[512]; + tf_ssb_generate_keys_buffer(public, sizeof(public), private, sizeof(private)); + if (!tf_ssb_db_identity_add(ssb, user, public, private)) + { + result = JS_ThrowInternalError(context, "Unable to add identity."); + } + } + else + { + result = JS_ThrowInternalError(context, "Too many identities for user."); + } + JS_FreeCString(context, user); + } + return result; +} + +typedef struct _identities_visit_t +{ + JSContext* context; + JSValue array; + int count; +} identities_visit_t; + +static void _tf_ssb_getIdentities_visit(const char* identity, void* data) +{ + identities_visit_t* state = data; + JS_SetPropertyUint32(state->context, state->array, state->count++, JS_NewString(state->context, identity)); +} + +static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + JSValue result = JS_NewArray(context); + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + if (ssb) + { + const char* user = JS_ToCString(context, argv[0]); + identities_visit_t state = + { + .context = context, + .array = result, + }; + tf_ssb_db_identity_visit(ssb, user, _tf_ssb_getIdentities_visit, &state); + JS_FreeCString(context, user); + } + return result; +} + +static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + JSValue result = JS_UNDEFINED; + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + if (ssb) + { + const char* user = JS_ToCString(context, argv[0]); + const char* id = JS_ToCString(context, argv[1]); + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + if (tf_ssb_db_identity_get_private_key(ssb, user, id, private_key, sizeof(private_key))) + { + tf_ssb_append_message_with_keys(ssb, id, private_key, argv[2]); + } + else + { + result = JS_ThrowInternalError(context, "Unable to get private key for user %s with identity %s.", user, id); + } + JS_FreeCString(context, id); + JS_FreeCString(context, user); + } + return result; +} + static JSValue _tf_ssb_getMessage(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_NULL; @@ -765,6 +846,10 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) JSValue object = JS_NewObjectClass(context, _tf_ssb_classId); JS_SetPropertyStr(context, global, "ssb", object); JS_SetOpaque(object, ssb); + JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1)); + JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1)); + JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3)); + JS_SetPropertyStr(context, object, "whoami", JS_NewCFunction(context, _tf_ssb_whoami, "whoami", 0)); JS_SetPropertyStr(context, object, "getMessage", JS_NewCFunction(context, _tf_ssb_getMessage, "getMessage", 2)); JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));