Move the bulk of ssb.privateMessageEncrypt work (CPU + DB) off the main thread.
This commit is contained in:
parent
9b52415b35
commit
d5a7e19f1a
239
src/ssb.js.c
239
src/ssb.js.c
@ -1854,6 +1854,142 @@ static bool _tf_ssb_get_private_key_curve25519(sqlite3* db, const char* user, co
|
||||
return success;
|
||||
}
|
||||
|
||||
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];
|
||||
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;
|
||||
|
||||
static void _tf_ssb_private_message_encrypt_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
private_message_encrypt_t* work = user_data;
|
||||
|
||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
bool found = _tf_ssb_get_private_key_curve25519(db, work->signer_user, work->signer_identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error_id_not_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_private_message_encrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
private_message_encrypt_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue result = JS_UNDEFINED;
|
||||
if (work->error_secretbox_failed)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "crypto_secretbox_easy failed");
|
||||
}
|
||||
else if (work->error_scalarmult_failed)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "crypto_scalarmult failed");
|
||||
}
|
||||
else if (work->error_id_not_found)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "Unable to get key for ID %s of user %s.", work->signer_identity, work->signer_user);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_NewStringLen(context, work->encrypted, work->encrypted_length);
|
||||
tf_free((void*)work->encrypted);
|
||||
}
|
||||
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
JS_FreeCString(context, work->signer_user);
|
||||
JS_FreeCString(context, work->signer_identity);
|
||||
JS_FreeCString(context, work->message);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
@ -1863,11 +1999,7 @@ 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);
|
||||
}
|
||||
|
||||
const char* signer_user = JS_ToCString(context, argv[0]);
|
||||
const char* signer_identity = JS_ToCString(context, argv[1]);
|
||||
uint8_t recipients[k_max_private_message_recipients][crypto_scalarmult_curve25519_SCALARBYTES];
|
||||
size_t message_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &message_size, argv[3]);
|
||||
uint8_t recipients[k_max_private_message_recipients][crypto_scalarmult_curve25519_SCALARBYTES] = { 0 };
|
||||
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
|
||||
{
|
||||
JSValue recipient = JS_GetPropertyUint32(context, argv[2], i);
|
||||
@ -1892,89 +2024,26 @@ static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst
|
||||
|
||||
if (JS_IsUndefined(result))
|
||||
{
|
||||
const char* signer_user = JS_ToCString(context, argv[0]);
|
||||
const char* signer_identity = JS_ToCString(context, argv[1]);
|
||||
size_t message_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &message_size, argv[3]);
|
||||
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
bool found = _tf_ssb_get_private_key_curve25519(db, signer_user, signer_identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
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)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)) * recipient_count + crypto_secretbox_MACBYTES + 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 < recipient_count && JS_IsUndefined(result); i++)
|
||||
{
|
||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||
if (crypto_scalarmult(shared_secret, secret_key, recipients[i]) == 0)
|
||||
{
|
||||
if (crypto_secretbox_easy(p, length_and_key, sizeof(length_and_key), nonce, shared_secret) != 0)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "crypto_secretbox_easy failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
p += crypto_secretbox_MACBYTES + sizeof(length_and_key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "crypto_scalarmult failed");
|
||||
}
|
||||
private_message_encrypt_t* work = tf_malloc(sizeof(private_message_encrypt_t));
|
||||
*work = (private_message_encrypt_t) {
|
||||
.signer_user = signer_user,
|
||||
.signer_identity = signer_identity,
|
||||
.recipient_count = recipient_count,
|
||||
.message = message,
|
||||
.message_size = message_size,
|
||||
};
|
||||
static_assert(sizeof(work->recipients) == sizeof(recipients), "size mismatch");
|
||||
memcpy(work->recipients, recipients, sizeof(recipients));
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_private_message_encrypt_work, _tf_ssb_private_message_encrypt_after_work, work);
|
||||
}
|
||||
|
||||
if (JS_IsUndefined(result))
|
||||
{
|
||||
if (crypto_secretbox_easy(p, (const uint8_t*)message, message_size, nonce, body_key) != 0)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "crypto_secretbox_easy failed for the message.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
p += crypto_secretbox_MACBYTES + 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;
|
||||
result = JS_NewStringLen(context, encoded, encoded_length);
|
||||
tf_free(encoded);
|
||||
}
|
||||
}
|
||||
tf_free(payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "Unable to get key for ID %s of user %s.", signer_identity, signer_user);
|
||||
}
|
||||
}
|
||||
|
||||
JS_FreeCString(context, signer_user);
|
||||
JS_FreeCString(context, signer_identity);
|
||||
JS_FreeCString(context, message);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -819,3 +819,42 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
|
||||
|
||||
uv_loop_close(&loop);
|
||||
}
|
||||
|
||||
static void _write_file(const char* path, const char* contents)
|
||||
{
|
||||
FILE* file = fopen(path, "w");
|
||||
if (!file)
|
||||
{
|
||||
printf("Unable to write %s: %s.\n", path, strerror(errno));
|
||||
fflush(stdout);
|
||||
abort();
|
||||
}
|
||||
fputs(contents, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
#define TEST_ARGS " --ssb-port=0 --http-port=0 --https-port=0"
|
||||
|
||||
void tf_ssb_test_encrypt(const tf_test_options_t* options)
|
||||
{
|
||||
_write_file("out/test.js",
|
||||
"async function main() {\n"
|
||||
" let a = await ssb.createIdentity('test');\n"
|
||||
" let b = await ssb.createIdentity('test');\n"
|
||||
" let c = await ssb.privateMessageEncrypt('test', a, [a, b], {'foo': 1});\n"
|
||||
" if (!c.endsWith('.box')) {\n"
|
||||
" exit(1);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"main().catch(() => exit(2));\n");
|
||||
|
||||
unlink("out/testdb.sqlite");
|
||||
char command[256];
|
||||
snprintf(command, sizeof(command), "%s run --db-path=out/testdb.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
|
||||
tf_printf("%s\n", command);
|
||||
int result = system(command);
|
||||
(void)result;
|
||||
assert(WIFEXITED(result));
|
||||
printf("returned %d\n", WEXITSTATUS(result));
|
||||
assert(WEXITSTATUS(result) == 0);
|
||||
}
|
||||
|
@ -47,4 +47,10 @@ void tf_ssb_test_bench(const tf_test_options_t* options);
|
||||
*/
|
||||
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
|
||||
|
||||
/**
|
||||
** Test encrypting a private message.
|
||||
** @param options The test options.
|
||||
*/
|
||||
void tf_ssb_test_encrypt(const tf_test_options_t* options);
|
||||
|
||||
/** @} */
|
||||
|
@ -914,6 +914,7 @@ void tf_tests(const tf_test_options_t* options)
|
||||
_tf_test_run(options, "bench", tf_ssb_test_bench, false);
|
||||
_tf_test_run(options, "auto", _test_auto, false);
|
||||
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
|
||||
_tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false);
|
||||
tf_printf("Tests completed.\n");
|
||||
#endif
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user