forked from cory/tildefriends
		
	Exposed functions to encrypt and decrypt private messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4197 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		
							
								
								
									
										14
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								core/core.js
									
									
									
									
									
								
							| @@ -343,6 +343,20 @@ 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 && | 			if (process.credentials && | ||||||
| 				process.credentials.session && | 				process.credentials.session && | ||||||
|   | |||||||
| @@ -564,7 +564,7 @@ static int _tf_command_check(const char* file, int argc, char* argv[]) | |||||||
| 	const char** extras = NULL; | 	const char** extras = NULL; | ||||||
| 	int extra_count = 0; | 	int extra_count = 0; | ||||||
| 	const char *err = NULL; | 	const char *err = NULL; | ||||||
| 	XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "post [options]", "options:", NULL, 15); | 	XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_STRICT, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "check [options]", "options:", NULL, 15); | ||||||
| 	if (err) | 	if (err) | ||||||
| 	{ | 	{ | ||||||
| 		if (extras) | 		if (extras) | ||||||
|   | |||||||
							
								
								
									
										237
									
								
								src/ssb.js.c
									
									
									
									
									
								
							
							
						
						
									
										237
									
								
								src/ssb.js.c
									
									
									
									
									
								
							| @@ -7,11 +7,17 @@ | |||||||
| #include "util.js.h" | #include "util.js.h" | ||||||
|  |  | ||||||
| #include <sodium/crypto_hash_sha256.h> | #include <sodium/crypto_hash_sha256.h> | ||||||
|  | #include <sodium/crypto_box.h> | ||||||
|  | #include <sodium/crypto_scalarmult.h> | ||||||
|  | #include <sodium/crypto_scalarmult_curve25519.h> | ||||||
|  | #include <sodium/crypto_secretbox.h> | ||||||
| #include <sodium/crypto_sign.h> | #include <sodium/crypto_sign.h> | ||||||
|  | #include <sodium/randombytes.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <sqlite3.h> | #include <sqlite3.h> | ||||||
| #include <uv.h> | #include <uv.h> | ||||||
|  |  | ||||||
|  | #include <assert.h> | ||||||
| #include <inttypes.h> | #include <inttypes.h> | ||||||
|  |  | ||||||
| #include "quickjs-libc.h" | #include "quickjs-libc.h" | ||||||
| @@ -1114,6 +1120,235 @@ static JSValue _tf_ssb_followingDeep(JSContext* context, JSValueConst this_val, | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | enum { k_max_private_message_recipients = 8 }; | ||||||
|  |  | ||||||
|  | static bool _tf_ssb_get_private_key_curve25519(sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES]) | ||||||
|  | { | ||||||
|  | 	if (!user || !identity || !out_private_key) | ||||||
|  | 	{ | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool success = false; | ||||||
|  | 	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, *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 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); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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]); | ||||||
|  | 	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"); | ||||||
|  | 			} | ||||||
|  | 			JS_FreeCString(context, id); | ||||||
|  | 		} | ||||||
|  | 		JS_FreeValue(context, recipient); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (JS_IsUndefined(result)) | ||||||
|  | 	{ | ||||||
|  | 		tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); | ||||||
|  | 		sqlite3* db = tf_ssb_get_db(ssb); | ||||||
|  |  | ||||||
|  | 		uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 }; | ||||||
|  | 		if (_tf_ssb_get_private_key_curve25519(db, signer_user, signer_identity, private_key)) | ||||||
|  | 		{ | ||||||
|  | 			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"); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			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; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
|  | { | ||||||
|  | 	JSValue result = JS_UNDEFINED; | ||||||
|  | 	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]); | ||||||
|  |  | ||||||
|  | 	uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 }; | ||||||
|  |  | ||||||
|  | 	tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); | ||||||
|  | 	sqlite3* db = tf_ssb_get_db(ssb); | ||||||
|  | 	if (_tf_ssb_get_private_key_curve25519(db, user, identity, private_key)) | ||||||
|  | 	{ | ||||||
|  | 		uint8_t* decoded = tf_malloc(message_size); | ||||||
|  | 		int decoded_length = tf_base64_decode(message, message_size - strlen(".box"), decoded, 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) | ||||||
|  | 						{ | ||||||
|  | 							result = JS_NewStringLen(context, (const char*)decrypted, body_size - crypto_secretbox_MACBYTES); | ||||||
|  | 						} | ||||||
|  | 						else | ||||||
|  | 						{ | ||||||
|  | 							result = JS_ThrowInternalError(context, "Received key to open secret box containing message body, but it did not work."); | ||||||
|  | 						} | ||||||
|  | 						tf_free(decrypted); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				result = JS_ThrowInternalError(context, "crypto_scalarmult failed."); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			result = JS_ThrowInternalError(context, "Encrypted message was not long enough to contains its one-time public key."); | ||||||
|  | 		} | ||||||
|  | 		tf_free(decoded); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		result = JS_ThrowInternalError(context, "Private key not found for user %s with id %s.", user, identity); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	JS_FreeCString(context, user); | ||||||
|  | 	JS_FreeCString(context, identity); | ||||||
|  | 	JS_FreeCString(context, message); | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
| void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) | void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) | ||||||
| { | { | ||||||
| 	JS_NewClassID(&_tf_ssb_classId); | 	JS_NewClassID(&_tf_ssb_classId); | ||||||
| @@ -1137,6 +1372,8 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) | |||||||
| 	JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3)); | 	JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3)); | ||||||
| 	JS_SetPropertyStr(context, object, "hmacsha256sign", JS_NewCFunction(context, _tf_ssb_hmacsha256_sign, "hmacsha256sign", 3)); | 	JS_SetPropertyStr(context, object, "hmacsha256sign", JS_NewCFunction(context, _tf_ssb_hmacsha256_sign, "hmacsha256sign", 3)); | ||||||
| 	JS_SetPropertyStr(context, object, "hmacsha256verify", JS_NewCFunction(context, _tf_ssb_hmacsha256_verify, "hmacsha256verify", 3)); | 	JS_SetPropertyStr(context, object, "hmacsha256verify", JS_NewCFunction(context, _tf_ssb_hmacsha256_verify, "hmacsha256verify", 3)); | ||||||
|  | 	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)); | ||||||
|  |  | ||||||
| 	/* Does not require an identity. */ | 	/* Does not require an identity. */ | ||||||
| 	JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0)); | 	JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0)); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user