|
1 | 1 | #pragma once |
2 | 2 |
|
3 | 3 | #include <napi.h> |
| 4 | +#include <oxenc/base64.h> |
4 | 5 | #include <oxenc/hex.h> |
5 | 6 |
|
6 | 7 | #include <algorithm> |
|
16 | 17 |
|
17 | 18 | namespace session::nodeapi { |
18 | 19 |
|
| 20 | +namespace log = oxen::log; |
| 21 | + |
| 22 | +auto cat = log::Cat("multi_encrypt"); |
| 23 | + |
| 24 | +template <> |
| 25 | +struct toJs_impl<session::ProProof> { |
| 26 | + Napi::Object operator()(const Napi::Env& env, const session::ProProof pro_proof) { |
| 27 | + auto obj = Napi::Object::New(env); |
| 28 | + |
| 29 | + obj["version"] = toJs(env, pro_proof.version); |
| 30 | + obj["genIndexHashB64"] = toJs(env, oxenc::to_base64(pro_proof.gen_index_hash)); |
| 31 | + obj["rotatingPubkeyHex"] = toJs(env, oxenc::to_hex(pro_proof.rotating_pubkey)); |
| 32 | + obj["expiryMs"] = toJs(env, pro_proof.expiry_unix_ts.time_since_epoch().count()); |
| 33 | + |
| 34 | + return obj; |
| 35 | + } |
| 36 | +}; |
| 37 | + |
| 38 | +template <> |
| 39 | +struct toJs_impl<session::Envelope> { |
| 40 | + Napi::Object operator()(const Napi::Env& env, const session::Envelope envelope) { |
| 41 | + auto obj = Napi::Object::New(env); |
| 42 | + |
| 43 | + obj["timestampMs"] = toJs(env, envelope.timestamp.count()); |
| 44 | + obj["source"] = envelope.source.size() ? toJs(env, envelope.source) : env.Null(); |
| 45 | + obj["proSigHex"] = |
| 46 | + envelope.pro_sig.size() ? toJs(env, oxenc::to_hex(envelope.pro_sig)) : env.Null(); |
| 47 | + |
| 48 | + return obj; |
| 49 | + } |
| 50 | +}; |
| 51 | + |
19 | 52 | inline std::vector<unsigned char> extractPlaintext( |
20 | 53 | const Napi::Object& obj, const std::string identifier) { |
21 | 54 |
|
@@ -106,6 +139,34 @@ inline std::optional<std::span<const unsigned char>> extractProRotatingEd25519Pr |
106 | 139 | return std::nullopt; |
107 | 140 | } |
108 | 141 |
|
| 142 | +inline std::vector<unsigned char> extractContentOrEnvelope( |
| 143 | + const Napi::Object& obj, const std::string identifier) { |
| 144 | + assertIsUInt8Array(obj.Get("contentOrEnvelope"), identifier); |
| 145 | + auto contentOrEnvelope = toCppBuffer(obj.Get("contentOrEnvelope"), identifier); |
| 146 | + |
| 147 | + return contentOrEnvelope; |
| 148 | +} |
| 149 | + |
| 150 | +inline std::chrono::sys_time<std::chrono::milliseconds> extractNowSysMs( |
| 151 | + const Napi::Object& obj, const std::string identifier) { |
| 152 | + assertIsNumber(obj.Get("nowMs"), identifier); |
| 153 | + auto nowMs = toCppSysMs(obj.Get("nowMs"), identifier); |
| 154 | + |
| 155 | + return nowMs; |
| 156 | +} |
| 157 | + |
| 158 | +inline session::array_uc32 extractProBackendPubkeyHex( |
| 159 | + const Napi::Object& obj, const std::string identifier) { |
| 160 | + assertIsString(obj.Get("proBackendPubkeyHex")); |
| 161 | + |
| 162 | + auto proBackendPubkeyHex = toCppString(obj.Get("proBackendPubkeyHex"), identifier); |
| 163 | + assert_length(proBackendPubkeyHex, 64, identifier); |
| 164 | + |
| 165 | + auto arr = from_hex_to_array<32>(proBackendPubkeyHex); |
| 166 | + |
| 167 | + return arr; |
| 168 | +} |
| 169 | + |
109 | 170 | class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
110 | 171 | public: |
111 | 172 | MultiEncryptWrapper(const Napi::CallbackInfo& info) : |
@@ -156,6 +217,17 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
156 | 217 | "encryptForGroup", |
157 | 218 | static_cast<napi_property_attributes>( |
158 | 219 | napi_writable | napi_configurable)), |
| 220 | + |
| 221 | + // Destination decrypt |
| 222 | + // StaticMethod<&MultiEncryptWrapper::encryptFor1o1>( |
| 223 | + // "encryptFor1o1", |
| 224 | + // static_cast<napi_property_attributes>( |
| 225 | + // napi_writable | napi_configurable)), |
| 226 | + |
| 227 | + StaticMethod<&MultiEncryptWrapper::decryptForCommunity>( |
| 228 | + "decryptForCommunity", |
| 229 | + static_cast<napi_property_attributes>( |
| 230 | + napi_writable | napi_configurable)), |
159 | 231 | }); |
160 | 232 | } |
161 | 233 |
|
@@ -244,6 +316,12 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
244 | 316 | }); |
245 | 317 | }; |
246 | 318 |
|
| 319 | + /** |
| 320 | + * =========================================== |
| 321 | + * =========== ATTACHMENTS CALLS ============= |
| 322 | + * =========================================== |
| 323 | + */ |
| 324 | + |
247 | 325 | static Napi::Value attachmentEncrypt(const Napi::CallbackInfo& info) { |
248 | 326 | return wrapResult(info, [&] { |
249 | 327 | assertInfoLength(info, 1); |
@@ -337,9 +415,15 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
337 | 415 | }); |
338 | 416 | }; |
339 | 417 |
|
| 418 | + /** |
| 419 | + * =========================================== |
| 420 | + * ============= ENCRYPT CALLS =============== |
| 421 | + * =========================================== |
| 422 | + */ |
| 423 | + |
340 | 424 | static Napi::Value encryptFor1o1(const Napi::CallbackInfo& info) { |
341 | 425 | return wrapResult(info, [&] { |
342 | | - // we expect an single argument which is an array of objects with the following |
| 426 | + // we expect a single argument which is an array of objects with the following |
343 | 427 | // properties: |
344 | 428 | // { |
345 | 429 | // "plaintext": Uint8Array, |
@@ -384,7 +468,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
384 | 468 |
|
385 | 469 | static Napi::Value encryptForCommunityInbox(const Napi::CallbackInfo& info) { |
386 | 470 | return wrapResult(info, [&] { |
387 | | - // we expect an single argument which is an array of objects with the following |
| 471 | + // we expect a single argument which is an array of objects with the following |
388 | 472 | // properties: |
389 | 473 | // { |
390 | 474 | // "plaintext": Uint8Array, |
@@ -435,7 +519,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
435 | 519 |
|
436 | 520 | static Napi::Value encryptForCommunity(const Napi::CallbackInfo& info) { |
437 | 521 | return wrapResult(info, [&] { |
438 | | - // we expect an single argument which is an array of objects with the following |
| 522 | + // we expect a single argument which is an array of objects with the following |
439 | 523 | // properties: |
440 | 524 | // { |
441 | 525 | // "plaintext": Uint8Array, |
@@ -474,7 +558,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
474 | 558 |
|
475 | 559 | static Napi::Value encryptForGroup(const Napi::CallbackInfo& info) { |
476 | 560 | return wrapResult(info, [&] { |
477 | | - // we expect an single argument which is an array of objects with the following |
| 561 | + // we expect a single argument which is an array of objects with the following |
478 | 562 | // properties: |
479 | 563 | // { |
480 | 564 | // "plaintext": Uint8Array, |
@@ -532,5 +616,99 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> { |
532 | 616 | return ret; |
533 | 617 | }); |
534 | 618 | }; |
| 619 | + |
| 620 | + /** |
| 621 | + * =========================================== |
| 622 | + * ============= DECRYPT CALLS =============== |
| 623 | + * =========================================== |
| 624 | + */ |
| 625 | + |
| 626 | + static Napi::Value decryptForCommunity(const Napi::CallbackInfo& info) { |
| 627 | + return wrapResult(info, [&] { |
| 628 | + // we expect two arguments that match: |
| 629 | + // first: [{ |
| 630 | + // "contentOrEnvelope": Uint8Array, |
| 631 | + // }], |
| 632 | + // second: { |
| 633 | + // "nowMs": number, |
| 634 | + // "proBackendPubkeyHex": Hexstring, |
| 635 | + // } |
| 636 | + // |
| 637 | + |
| 638 | + assertInfoLength(info, 2); |
| 639 | + assertIsArray(info[0], "decryptForCommunity info[0]"); |
| 640 | + assertIsObject(info[1]); |
| 641 | + |
| 642 | + auto first = info[0].As<Napi::Array>(); |
| 643 | + |
| 644 | + if (first.IsEmpty()) |
| 645 | + throw std::invalid_argument("decryptForCommunity first received empty"); |
| 646 | + |
| 647 | + auto second = info[1].As<Napi::Array>(); |
| 648 | + |
| 649 | + if (second.IsEmpty()) |
| 650 | + throw std::invalid_argument("decryptForCommunity second received empty"); |
| 651 | + |
| 652 | + auto nowMs = extractNowSysMs(second, "decryptForCommunity.second.nowMs"); |
| 653 | + auto proBackendPubkeyHex = extractProBackendPubkeyHex( |
| 654 | + second, "decryptForCommunity.second.proBackendPubkeyHex"); |
| 655 | + |
| 656 | + std::vector<DecodedCommunityMessage> decrypted(first.Length()); |
| 657 | + |
| 658 | + for (uint32_t i = 0; i < first.Length(); i++) { |
| 659 | + auto itemValue = first.Get(i); |
| 660 | + if (!itemValue.IsObject()) { |
| 661 | + throw std::invalid_argument( |
| 662 | + "decryptForCommunity itemValue is not an " |
| 663 | + "object"); |
| 664 | + } |
| 665 | + auto obj = itemValue.As<Napi::Object>(); |
| 666 | + |
| 667 | + try { |
| 668 | + decrypted[i] = session::decode_for_community( |
| 669 | + extractContentOrEnvelope( |
| 670 | + obj, "decryptForCommunity.obj.contentOrEnvelope"), |
| 671 | + nowMs, |
| 672 | + proBackendPubkeyHex); |
| 673 | + |
| 674 | + } catch (const std::exception& e) { |
| 675 | + log::warning( |
| 676 | + cat, |
| 677 | + "decryptForCommunity: Failed to decrypt " |
| 678 | + "message at index {}", |
| 679 | + i); |
| 680 | + } |
| 681 | + } |
| 682 | + |
| 683 | + auto ret = Napi::Array::New(info.Env(), decrypted.size()); |
| 684 | + uint32_t i = 0; |
| 685 | + for (auto& d : decrypted) { |
| 686 | + auto to_insert = Napi::Object::New(info.Env()); |
| 687 | + std::span<unsigned char> content_plaintext_unpadded = |
| 688 | + std::span(d.content_plaintext) |
| 689 | + .subspan(0, d.content_plaintext_unpadded_size); |
| 690 | + |
| 691 | + to_insert.Set( |
| 692 | + "contentPlaintextUnpadded", toJs(info.Env(), content_plaintext_unpadded)); |
| 693 | + to_insert.Set( |
| 694 | + "envelope", d.envelope ? toJs(info.Env(), *d.envelope) : info.Env().Null()); |
| 695 | + |
| 696 | + if (d.pro_sig) |
| 697 | + to_insert.Set("proSigHex", toJs(info.Env(), oxenc::to_hex(*d.pro_sig))); |
| 698 | + else |
| 699 | + to_insert.Set("proSigHex", info.Env().Null()); |
| 700 | + |
| 701 | + if (!d.pro.has_value()) |
| 702 | + to_insert.Set("proProof", info.Env().Null()); |
| 703 | + else |
| 704 | + to_insert.Set("proProof", toJs(info.Env(), d.pro->proof)); |
| 705 | + |
| 706 | + ret.Set(i, to_insert); |
| 707 | + i++; |
| 708 | + } |
| 709 | + |
| 710 | + return ret; |
| 711 | + }); |
| 712 | + }; |
535 | 713 | }; |
536 | 714 | }; // namespace session::nodeapi |
0 commit comments