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;
|
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)
|
static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
JSValue result = JS_UNDEFINED;
|
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);
|
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]);
|
uint8_t recipients[k_max_private_message_recipients][crypto_scalarmult_curve25519_SCALARBYTES] = { 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]);
|
|
||||||
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
|
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
|
||||||
{
|
{
|
||||||
JSValue recipient = JS_GetPropertyUint32(context, argv[2], 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))
|
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);
|
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
private_message_encrypt_t* work = tf_malloc(sizeof(private_message_encrypt_t));
|
||||||
|
*work = (private_message_encrypt_t) {
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
.signer_user = signer_user,
|
||||||
bool found = _tf_ssb_get_private_key_curve25519(db, signer_user, signer_identity, private_key);
|
.signer_identity = signer_identity,
|
||||||
tf_ssb_release_db_reader(ssb, db);
|
.recipient_count = recipient_count,
|
||||||
|
.message = message,
|
||||||
if (found)
|
.message_size = message_size,
|
||||||
{
|
};
|
||||||
uint8_t public_key[crypto_box_PUBLICKEYBYTES] = { 0 };
|
static_assert(sizeof(work->recipients) == sizeof(recipients), "size mismatch");
|
||||||
uint8_t secret_key[crypto_box_SECRETKEYBYTES] = { 0 };
|
memcpy(work->recipients, recipients, sizeof(recipients));
|
||||||
uint8_t nonce[crypto_box_NONCEBYTES] = { 0 };
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
uint8_t body_key[crypto_box_SECRETKEYBYTES] = { 0 };
|
tf_ssb_run_work(ssb, _tf_ssb_private_message_encrypt_work, _tf_ssb_private_message_encrypt_after_work, work);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,3 +819,42 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
|
|||||||
|
|
||||||
uv_loop_close(&loop);
|
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);
|
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, "bench", tf_ssb_test_bench, false);
|
||||||
_tf_test_run(options, "auto", _test_auto, 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, "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");
|
tf_printf("Tests completed.\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user