ssb: Finish moving private message encrypt/decrypt to C.
This commit is contained in:
27
core/core.js
27
core/core.js
@@ -486,33 +486,6 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
||||
});
|
||||
}
|
||||
};
|
||||
imports.ssb.privateMessageEncrypt = function (id, recipients, message) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.privateMessageEncrypt(
|
||||
process.credentials.session.name,
|
||||
id,
|
||||
recipients,
|
||||
message
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.privateMessageDecrypt = function (id, message) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.privateMessageDecrypt(
|
||||
process.credentials.session.name,
|
||||
id,
|
||||
message
|
||||
);
|
||||
}
|
||||
};
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
|
||||
316
src/api.js.c
316
src/api.js.c
@@ -7,8 +7,17 @@
|
||||
#include "task.h"
|
||||
#include "util.js.h"
|
||||
|
||||
#include <quickjs.h>
|
||||
#include "quickjs.h"
|
||||
#include "sodium/crypto_box.h"
|
||||
#include "sodium/crypto_scalarmult.h"
|
||||
#include "sodium/crypto_scalarmult_curve25519.h"
|
||||
#include "sodium/crypto_scalarmult_ed25519.h"
|
||||
#include "sodium/crypto_secretbox.h"
|
||||
#include "sodium/crypto_sign.h"
|
||||
#include "sodium/randombytes.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||
@@ -1219,6 +1228,309 @@ static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueCons
|
||||
return result;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
tf_printf("user=%p identity=%p out_private_key=%p\n", user, identity, out_private_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
sqlite3_stmt* statement = NULL;
|
||||
if (sqlite3_prepare_v2(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, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
while (sqlite3_step(statement) == SQLITE_ROW)
|
||||
{
|
||||
uint8_t key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
int length = tf_base64_decode((const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), key, sizeof(key));
|
||||
if (length == crypto_sign_SECRETKEYBYTES)
|
||||
{
|
||||
success = crypto_sign_ed25519_sk_to_curve25519(out_private_key, key) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool _tf_ssb_get_private_key_curve25519(tf_ssb_t* ssb, sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
||||
{
|
||||
if (_tf_ssb_get_private_key_curve25519_internal(db, user, identity, out_private_key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tf_ssb_db_user_has_permission(ssb, db, user, "administration"))
|
||||
{
|
||||
return _tf_ssb_get_private_key_curve25519_internal(db, ":admin", identity, out_private_key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct _private_message_encrypt_t
|
||||
{
|
||||
const char* signer_user;
|
||||
const char* signer_identity;
|
||||
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;
|
||||
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(ssb, db, work->signer_user, work->signer_identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (found)
|
||||
{
|
||||
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
|
||||
{
|
||||
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;
|
||||
bool success = false;
|
||||
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 if (!work->encrypted)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "Encrypt failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_NewStringLen(context, work->encrypted, work->encrypted_length);
|
||||
tf_free((void*)work->encrypted);
|
||||
success = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < work->recipient_count; i++)
|
||||
{
|
||||
tf_free((void*)work->recipients[i]);
|
||||
}
|
||||
JSValue error = JS_Call(context, work->promise[success ? 0 : 1], 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, int magic, JSValue* data)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
JSValue process = data[0];
|
||||
int recipient_count = tf_util_get_length(context, argv[1]);
|
||||
if (recipient_count < 1 || recipient_count > 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* session_name_string = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||
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[1], i);
|
||||
const char* id = JS_ToCString(context, recipient);
|
||||
if (id)
|
||||
{
|
||||
recipients[i] = tf_strdup(id);
|
||||
JS_FreeCString(context, id);
|
||||
}
|
||||
JS_FreeValue(context, recipient);
|
||||
}
|
||||
|
||||
if (JS_IsUndefined(result))
|
||||
{
|
||||
const char* signer_identity = JS_ToCString(context, argv[0]);
|
||||
size_t message_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &message_size, argv[2]);
|
||||
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
private_message_encrypt_t* work = tf_malloc(sizeof(private_message_encrypt_t));
|
||||
*work = (private_message_encrypt_t) {
|
||||
.signer_user = session_name_string,
|
||||
.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);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _private_message_decrypt_t
|
||||
{
|
||||
const char* user;
|
||||
const char* identity;
|
||||
size_t message_size;
|
||||
const char* message;
|
||||
const char* decrypted;
|
||||
size_t decrypted_size;
|
||||
const char* error;
|
||||
JSValue promise[2];
|
||||
} private_message_decrypt_t;
|
||||
|
||||
static void _tf_ssb_private_message_decrypt_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
private_message_decrypt_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(ssb, db, work->user, work->identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (found)
|
||||
{
|
||||
if (work->message_size >= strlen(".box") && memcmp(work->message + work->message_size - strlen(".box"), ".box", strlen(".box")) == 0)
|
||||
{
|
||||
uint8_t* decoded = tf_malloc(work->message_size);
|
||||
int decoded_length = tf_base64_decode(work->message, work->message_size - strlen(".box"), decoded, work->message_size);
|
||||
uint8_t* nonce = decoded;
|
||||
uint8_t* public_key = decoded + crypto_box_NONCEBYTES;
|
||||
if (public_key + crypto_secretbox_KEYBYTES < decoded + decoded_length)
|
||||
{
|
||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||
if (crypto_scalarmult(shared_secret, private_key, public_key) == 0)
|
||||
{
|
||||
enum
|
||||
{
|
||||
k_recipient_header_bytes = crypto_secretbox_MACBYTES + sizeof(uint8_t) + crypto_secretbox_KEYBYTES
|
||||
};
|
||||
for (uint8_t* p = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES; p <= decoded + decoded_length - k_recipient_header_bytes;
|
||||
p += k_recipient_header_bytes)
|
||||
{
|
||||
uint8_t out[k_recipient_header_bytes] = { 0 };
|
||||
int opened = crypto_secretbox_open_easy(out, p, k_recipient_header_bytes, nonce, shared_secret);
|
||||
if (opened != -1)
|
||||
{
|
||||
int recipients = (int)out[0];
|
||||
uint8_t* body = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES + k_recipient_header_bytes * recipients;
|
||||
size_t body_size = decoded + decoded_length - body;
|
||||
uint8_t* decrypted = tf_malloc(body_size);
|
||||
uint8_t* key = out + 1;
|
||||
if (crypto_secretbox_open_easy(decrypted, body, body_size, nonce, key) != -1)
|
||||
{
|
||||
work->decrypted = (const char*)decrypted;
|
||||
work->decrypted_size = body_size - crypto_secretbox_MACBYTES;
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Received key to open secret box containing message body, but it did not work.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "crypto_scalarmult failed.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Encrypted message was not long enough to contain its one-time public key.";
|
||||
}
|
||||
tf_free(decoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Message does not end in \".box\".";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Private key not found for user.";
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_private_message_decrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
private_message_decrypt_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue error = JS_UNDEFINED;
|
||||
if (work->error)
|
||||
{
|
||||
JSValue result = JS_ThrowInternalError(context, "%s", work->error);
|
||||
error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
else if (work->decrypted)
|
||||
{
|
||||
JSValue result = JS_NewStringLen(context, work->decrypted, work->decrypted_size);
|
||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &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->user);
|
||||
JS_FreeCString(context, work->identity);
|
||||
JS_FreeCString(context, work->message);
|
||||
tf_free((void*)work->decrypted);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
JSValue process = data[0];
|
||||
const char* identity = JS_ToCString(context, argv[0]);
|
||||
size_t message_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &message_size, argv[1]);
|
||||
const char* session_name_string = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||
|
||||
private_message_decrypt_t* work = tf_malloc(sizeof(private_message_decrypt_t));
|
||||
*work = (private_message_decrypt_t) {
|
||||
.user = session_name_string,
|
||||
.identity = identity,
|
||||
.message_size = message_size,
|
||||
.message = message,
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_private_message_decrypt_work, _tf_ssb_private_message_decrypt_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue imports = argv[0];
|
||||
@@ -1245,6 +1557,8 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
|
||||
JS_SetPropertyStr(context, ssb, "getActiveIdentity", JS_NewCFunctionData(context, _tf_ssb_getActiveIdentity, 0, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "getIdentities", JS_NewCFunctionData(context, _tf_ssb_getIdentities, 0, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "privateMessageEncrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_encrypt, 3, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "privateMessageDecrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_decrypt, 2, 0, 1, &process));
|
||||
JS_FreeValue(context, ssb);
|
||||
|
||||
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||
|
||||
12
src/ssb.c
12
src/ssb.c
@@ -4766,6 +4766,7 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
||||
|
||||
uint8_t* payload = tf_malloc(payload_size);
|
||||
|
||||
char* encoded = NULL;
|
||||
uint8_t* p = payload;
|
||||
memcpy(p, nonce, sizeof(nonce));
|
||||
p += sizeof(nonce);
|
||||
@@ -4780,17 +4781,17 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
||||
tf_ssb_id_str_to_bin(key, recipients[i]);
|
||||
if (crypto_sign_ed25519_pk_to_curve25519(recipient, key) != 0)
|
||||
{
|
||||
return NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||
if (crypto_scalarmult(shared_secret, secret_key, recipient) != 0)
|
||||
{
|
||||
return NULL;
|
||||
goto fail;
|
||||
}
|
||||
if (crypto_secretbox_easy(p, length_and_key, sizeof(length_and_key), nonce, shared_secret) != 0)
|
||||
{
|
||||
return NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
p += crypto_secretbox_MACBYTES + sizeof(length_and_key);
|
||||
@@ -4798,16 +4799,17 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
||||
|
||||
if (crypto_secretbox_easy(p, (const uint8_t*)message, message_size, nonce, body_key) != 0)
|
||||
{
|
||||
return NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
p += crypto_secretbox_MACBYTES + message_size;
|
||||
assert((size_t)(p - payload) == payload_size);
|
||||
|
||||
char* encoded = tf_malloc(payload_size * 2 + 5);
|
||||
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);
|
||||
|
||||
fail:
|
||||
tf_free(payload);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
306
src/ssb.js.c
306
src/ssb.js.c
@@ -7,18 +7,11 @@
|
||||
#include "ssb.h"
|
||||
#include "util.js.h"
|
||||
|
||||
#include "sodium/crypto_box.h"
|
||||
#include "sodium/crypto_scalarmult.h"
|
||||
#include "sodium/crypto_scalarmult_curve25519.h"
|
||||
#include "sodium/crypto_scalarmult_ed25519.h"
|
||||
#include "sodium/crypto_secretbox.h"
|
||||
#include "sodium/crypto_sign.h"
|
||||
#include "sodium/randombytes.h"
|
||||
#include "sqlite3.h"
|
||||
#include "string.h"
|
||||
#include "uv.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
static const int k_sql_async_timeout_ms = 60 * 1000;
|
||||
@@ -1545,303 +1538,6 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
|
||||
return result ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
tf_printf("user=%p identity=%p out_private_key=%p\n", user, identity, out_private_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
sqlite3_stmt* statement = NULL;
|
||||
if (sqlite3_prepare_v2(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, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
while (sqlite3_step(statement) == SQLITE_ROW)
|
||||
{
|
||||
uint8_t key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
int length = tf_base64_decode((const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), key, sizeof(key));
|
||||
if (length == crypto_sign_SECRETKEYBYTES)
|
||||
{
|
||||
success = crypto_sign_ed25519_sk_to_curve25519(out_private_key, key) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool _tf_ssb_get_private_key_curve25519(tf_ssb_t* ssb, sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
||||
{
|
||||
if (_tf_ssb_get_private_key_curve25519_internal(db, user, identity, out_private_key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tf_ssb_db_user_has_permission(ssb, db, user, "administration"))
|
||||
{
|
||||
return _tf_ssb_get_private_key_curve25519_internal(db, ":admin", identity, out_private_key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct _private_message_encrypt_t
|
||||
{
|
||||
const char* signer_user;
|
||||
const char* signer_identity;
|
||||
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;
|
||||
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(ssb, db, work->signer_user, work->signer_identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (found)
|
||||
{
|
||||
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
|
||||
{
|
||||
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->encrypted)
|
||||
{
|
||||
result = JS_ThrowInternalError(context, "Encrypt 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);
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
int recipient_count = tf_util_get_length(context, argv[2]);
|
||||
if (recipient_count < 1 || recipient_count > k_max_private_message_recipients)
|
||||
{
|
||||
return JS_ThrowRangeError(context, "Number of recipients must be between 1 and %d.", k_max_private_message_recipients);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
recipients[i] = tf_strdup(id);
|
||||
JS_FreeCString(context, id);
|
||||
}
|
||||
JS_FreeValue(context, recipient);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _private_message_decrypt_t
|
||||
{
|
||||
const char* user;
|
||||
const char* identity;
|
||||
size_t message_size;
|
||||
const char* message;
|
||||
const char* decrypted;
|
||||
size_t decrypted_size;
|
||||
const char* error;
|
||||
JSValue promise[2];
|
||||
} private_message_decrypt_t;
|
||||
|
||||
static void _tf_ssb_private_message_decrypt_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
private_message_decrypt_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(ssb, db, work->user, work->identity, private_key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (found)
|
||||
{
|
||||
if (work->message_size >= strlen(".box") && memcmp(work->message + work->message_size - strlen(".box"), ".box", strlen(".box")) == 0)
|
||||
{
|
||||
uint8_t* decoded = tf_malloc(work->message_size);
|
||||
int decoded_length = tf_base64_decode(work->message, work->message_size - strlen(".box"), decoded, work->message_size);
|
||||
uint8_t* nonce = decoded;
|
||||
uint8_t* public_key = decoded + crypto_box_NONCEBYTES;
|
||||
if (public_key + crypto_secretbox_KEYBYTES < decoded + decoded_length)
|
||||
{
|
||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||
if (crypto_scalarmult(shared_secret, private_key, public_key) == 0)
|
||||
{
|
||||
enum
|
||||
{
|
||||
k_recipient_header_bytes = crypto_secretbox_MACBYTES + sizeof(uint8_t) + crypto_secretbox_KEYBYTES
|
||||
};
|
||||
for (uint8_t* p = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES; p <= decoded + decoded_length - k_recipient_header_bytes;
|
||||
p += k_recipient_header_bytes)
|
||||
{
|
||||
uint8_t out[k_recipient_header_bytes] = { 0 };
|
||||
int opened = crypto_secretbox_open_easy(out, p, k_recipient_header_bytes, nonce, shared_secret);
|
||||
if (opened != -1)
|
||||
{
|
||||
int recipients = (int)out[0];
|
||||
uint8_t* body = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES + k_recipient_header_bytes * recipients;
|
||||
size_t body_size = decoded + decoded_length - body;
|
||||
uint8_t* decrypted = tf_malloc(body_size);
|
||||
uint8_t* key = out + 1;
|
||||
if (crypto_secretbox_open_easy(decrypted, body, body_size, nonce, key) != -1)
|
||||
{
|
||||
work->decrypted = (const char*)decrypted;
|
||||
work->decrypted_size = body_size - crypto_secretbox_MACBYTES;
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Received key to open secret box containing message body, but it did not work.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "crypto_scalarmult failed.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Encrypted message was not long enough to contain its one-time public key.";
|
||||
}
|
||||
tf_free(decoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Message does not end in \".box\".";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work->error = "Private key not found for user.";
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_private_message_decrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
private_message_decrypt_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue error = JS_UNDEFINED;
|
||||
if (work->error)
|
||||
{
|
||||
JSValue result = JS_ThrowInternalError(context, "%s", work->error);
|
||||
error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
else if (work->decrypted)
|
||||
{
|
||||
JSValue result = JS_NewStringLen(context, work->decrypted, work->decrypted_size);
|
||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &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->user);
|
||||
JS_FreeCString(context, work->identity);
|
||||
JS_FreeCString(context, work->message);
|
||||
tf_free((void*)work->decrypted);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
const char* user = JS_ToCString(context, argv[0]);
|
||||
const char* identity = JS_ToCString(context, argv[1]);
|
||||
size_t message_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &message_size, argv[2]);
|
||||
|
||||
private_message_decrypt_t* work = tf_malloc(sizeof(private_message_decrypt_t));
|
||||
*work = (private_message_decrypt_t) {
|
||||
.user = user,
|
||||
.identity = identity,
|
||||
.message_size = message_size,
|
||||
.message = message,
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_private_message_decrypt_work, _tf_ssb_private_message_decrypt_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _following_t
|
||||
{
|
||||
JSContext* context;
|
||||
@@ -2086,8 +1782,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
||||
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
|
||||
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
|
||||
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
|
||||
JS_SetPropertyStr(context, object, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4));
|
||||
JS_SetPropertyStr(context, object, "privateMessageDecrypt", JS_NewCFunction(context, _tf_ssb_private_message_decrypt, "privateMessageDecrypt", 3));
|
||||
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
|
||||
/* Write. */
|
||||
JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3));
|
||||
|
||||
Reference in New Issue
Block a user