ssb: Support publishing private messages from the command-line. #89
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 26m28s

This commit is contained in:
2025-01-08 20:16:17 -05:00
parent 3352098284
commit 872201c886
4 changed files with 223 additions and 93 deletions

View File

@ -1857,11 +1857,6 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
return result ? JS_TRUE : JS_FALSE;
}
enum
{
k_max_private_message_recipients = 8
};
static bool _tf_ssb_get_private_key_curve25519_internal(sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
{
if (!user || !identity)
@ -1910,14 +1905,12 @@ typedef struct _private_message_encrypt_t
{
const char* signer_user;
const char* signer_identity;
uint8_t recipients[k_max_private_message_recipients][crypto_scalarmult_curve25519_SCALARBYTES];
const char* recipients[k_max_private_message_recipients];
int recipient_count;
const char* message;
size_t message_size;
JSValue promise[2];
bool error_id_not_found;
bool error_secretbox_failed;
bool error_scalarmult_failed;
char* encrypted;
size_t encrypted_length;
} private_message_encrypt_t;
@ -1933,73 +1926,8 @@ static void _tf_ssb_private_message_encrypt_work(tf_ssb_t* ssb, void* user_data)
if (found)
{
uint8_t public_key[crypto_box_PUBLICKEYBYTES] = { 0 };
uint8_t secret_key[crypto_box_SECRETKEYBYTES] = { 0 };
uint8_t nonce[crypto_box_NONCEBYTES] = { 0 };
uint8_t body_key[crypto_box_SECRETKEYBYTES] = { 0 };
crypto_box_keypair(public_key, secret_key);
randombytes_buf(nonce, sizeof(nonce));
randombytes_buf(body_key, sizeof(body_key));
uint8_t length_and_key[1 + sizeof(body_key)];
length_and_key[0] = (uint8_t)work->recipient_count;
memcpy(length_and_key + 1, body_key, sizeof(body_key));
size_t payload_size =
sizeof(nonce) + sizeof(public_key) + (crypto_secretbox_MACBYTES + sizeof(length_and_key)) * work->recipient_count + crypto_secretbox_MACBYTES + work->message_size;
uint8_t* payload = tf_malloc(payload_size);
uint8_t* p = payload;
memcpy(p, nonce, sizeof(nonce));
p += sizeof(nonce);
memcpy(p, public_key, sizeof(public_key));
p += sizeof(public_key);
for (int i = 0; i < work->recipient_count; i++)
{
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
if (crypto_scalarmult(shared_secret, secret_key, work->recipients[i]) == 0)
{
if (crypto_secretbox_easy(p, length_and_key, sizeof(length_and_key), nonce, shared_secret) != 0)
{
work->error_secretbox_failed = true;
break;
}
else
{
p += crypto_secretbox_MACBYTES + sizeof(length_and_key);
}
}
else
{
work->error_scalarmult_failed = true;
break;
}
}
if (!work->error_secretbox_failed && !work->error_scalarmult_failed)
{
if (crypto_secretbox_easy(p, (const uint8_t*)work->message, work->message_size, nonce, body_key) != 0)
{
work->error_scalarmult_failed = true;
}
else
{
p += crypto_secretbox_MACBYTES + work->message_size;
assert((size_t)(p - payload) == payload_size);
char* encoded = tf_malloc(payload_size * 2 + 5);
size_t encoded_length = tf_base64_encode(payload, payload_size, encoded, payload_size * 2 + 5);
memcpy(encoded + encoded_length, ".box", 5);
encoded_length += 4;
work->encrypted = encoded;
work->encrypted_length = encoded_length;
}
}
tf_free(payload);
work->encrypted = tf_ssb_private_message_encrypt(private_key, work->recipients, work->recipient_count, work->message, work->message_size);
work->encrypted_length = work->encrypted ? strlen(work->encrypted) : 0;
}
else
{
@ -2012,13 +1940,9 @@ static void _tf_ssb_private_message_encrypt_after_work(tf_ssb_t* ssb, int status
private_message_encrypt_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
if (work->error_secretbox_failed)
if (!work->encrypted)
{
result = JS_ThrowInternalError(context, "crypto_secretbox_easy failed");
}
else if (work->error_scalarmult_failed)
{
result = JS_ThrowInternalError(context, "crypto_scalarmult failed");
result = JS_ThrowInternalError(context, "Encrypt failed.");
}
else if (work->error_id_not_found)
{
@ -2030,6 +1954,10 @@ static void _tf_ssb_private_message_encrypt_after_work(tf_ssb_t* ssb, int status
tf_free((void*)work->encrypted);
}
for (int i = 0; i < work->recipient_count; i++)
{
tf_free((void*)work->recipients[i]);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
@ -2051,24 +1979,14 @@ static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst
return JS_ThrowRangeError(context, "Number of recipients must be between 1 and %d.", k_max_private_message_recipients);
}
uint8_t recipients[k_max_private_message_recipients][crypto_scalarmult_curve25519_SCALARBYTES] = { 0 };
char* recipients[k_max_private_message_recipients] = { 0 };
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
{
JSValue recipient = JS_GetPropertyUint32(context, argv[2], i);
const char* id = JS_ToCString(context, recipient);
if (id)
{
const char* type = strstr(id, ".ed25519");
const char* id_start = *id == '@' ? id + 1 : id;
uint8_t key[crypto_box_PUBLICKEYBYTES] = { 0 };
if (tf_base64_decode(id_start, type ? (size_t)(type - id_start) : strlen(id_start), key, sizeof(key)) != sizeof(key))
{
result = JS_ThrowInternalError(context, "Invalid recipient: %s.\n", id);
}
else if (crypto_sign_ed25519_pk_to_curve25519(recipients[i], key) != 0)
{
result = JS_ThrowInternalError(context, "Failed to convert recipient ID.\n");
}
recipients[i] = tf_strdup(id);
JS_FreeCString(context, id);
}
JS_FreeValue(context, recipient);