Skip to content

Commit 9ffcc9d

Browse files
committed
feat: add working encryption for groups & communities
1 parent ab784e8 commit 9ffcc9d

File tree

10 files changed

+157
-69
lines changed

10 files changed

+157
-69
lines changed

CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ file(GLOB SOURCE_FILES src/*.cpp src/groups/*.cpp src/multi_encrypt/*.cpp)
4141

4242
add_subdirectory(libsession-util)
4343

44-
4544
if(MSVC)
4645
# Windows is horrible
4746
add_compile_definitions(NOMINMAX)

include/groups/meta_group_wrapper.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap<MetaGroupWrapper> {
8080
Napi::Value keysNeedsRekey(const Napi::CallbackInfo& info);
8181
Napi::Value keyRekey(const Napi::CallbackInfo& info);
8282
Napi::Value keyGetAll(const Napi::CallbackInfo& info);
83+
Napi::Value keyGetEncryptionKeyHex(const Napi::CallbackInfo& info);
8384
Napi::Value loadKeyMessage(const Napi::CallbackInfo& info);
8485
Napi::Value keyGetCurrentGen(const Napi::CallbackInfo& info);
8586
Napi::Value activeHashes(const Napi::CallbackInfo& info);

include/multi_encrypt/multi_encrypt.hpp

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <vector>
99

1010
#include "../utilities.hpp"
11+
#include "oxen/log.hpp"
1112
#include "session/attachments.hpp"
1213
#include "session/config/user_profile.hpp"
1314
#include "session/multi_encrypt.hpp"
@@ -17,62 +18,92 @@ namespace session::nodeapi {
1718

1819
inline std::vector<unsigned char> extractPlaintext(
1920
const Napi::Object& obj, const std::string identifier) {
21+
2022
assertIsUInt8Array(obj.Get("plaintext"), identifier);
2123
auto plaintext = toCppBuffer(obj.Get("plaintext"), identifier);
24+
2225
return plaintext;
2326
}
2427

2528
inline std::chrono::milliseconds extractSentTimestampMs(
2629
const Napi::Object& obj, const std::string identifier) {
2730
assertIsNumber(obj.Get("sentTimestampMs"), identifier);
2831
auto sentTimestampMs = toCppMs(obj.Get("sentTimestampMs"), identifier);
32+
2933
return sentTimestampMs;
3034
}
3135

32-
inline std::span<const unsigned char> extractSenderEd25519PrivkeyAsSpan(
36+
inline std::span<const unsigned char> extractSenderEd25519SeedAsSpan(
3337
const Napi::Object& obj, const std::string identifier) {
34-
assertIsString(obj.Get("senderEd25519Privkey"));
35-
auto ed25519PrivkeyHex = toCppString(obj.Get("senderEd25519Privkey"), identifier);
36-
return from_hex_to_span(ed25519PrivkeyHex);
38+
39+
assertIsUInt8Array(
40+
obj.Get("senderEd25519Seed"), "extractSenderEd25519SeedAsSpan.senderEd25519Seed");
41+
42+
auto senderEd25519Seed = toCppBuffer(obj.Get("senderEd25519Seed"), identifier);
43+
assert_length(senderEd25519Seed, 32, identifier);
44+
45+
return senderEd25519Seed;
3746
}
3847

3948
inline session::array_uc33 extractRecipientPubkeyAsArray(
4049
const Napi::Object& obj, const std::string identifier) {
4150
assertIsString(obj.Get("recipientPubkey"));
4251
auto recipientPubkeyHex = toCppString(obj.Get("recipientPubkey"), identifier);
52+
assert_length(recipientPubkeyHex, 66, identifier);
53+
4354
return from_hex_to_array<33>(recipientPubkeyHex);
4455
}
4556

4657
inline session::array_uc32 extractCommunityPubkeyAsArray(
4758
const Napi::Object& obj, const std::string identifier) {
4859
assertIsString(obj.Get("communityPubkey"));
4960
auto communityPubkeyHex = toCppString(obj.Get("communityPubkey"), identifier);
61+
assert_length(communityPubkeyHex, 64, identifier);
62+
5063
return from_hex_to_array<32>(communityPubkeyHex);
5164
}
5265

5366
inline session::array_uc33 extractGroupEd25519PubkeyAsArray(
5467
const Napi::Object& obj, const std::string identifier) {
5568
assertIsString(obj.Get("groupEd25519Pubkey"));
56-
auto communityPubkeyHex = toCppString(obj.Get("groupEd25519Pubkey"), identifier);
57-
return from_hex_to_array<33>(communityPubkeyHex);
69+
std::string groupEd25519PubkeyHex = toCppString(obj.Get("groupEd25519Pubkey"), identifier);
70+
71+
assert_length(groupEd25519PubkeyHex, 66, identifier);
72+
auto arr = from_hex_to_array<33>(groupEd25519PubkeyHex);
73+
74+
return arr;
5875
}
5976

60-
inline cleared_uc32 extractGroupEncPrivKeyAsArray(
77+
inline cleared_uc32 extractGroupEncKeyAsArray(
6178
const Napi::Object& obj, const std::string identifier) {
62-
assertIsString(obj.Get("groupEncPrivKey"));
63-
auto groupEncPrivKeyHex = toCppString(obj.Get("groupEncPrivKey"), identifier);
64-
auto arr = from_hex_to_array<32>(groupEncPrivKeyHex);
79+
assertIsString(obj.Get("groupEncKey"));
80+
81+
auto groupEncKeyHex = toCppString(obj.Get("groupEncKey"), identifier);
82+
assert_length(groupEncKeyHex, 64, identifier);
83+
84+
auto arr = from_hex_to_array<32>(groupEncKeyHex);
6585
cleared_uc32 result;
86+
6687
std::copy(arr.begin(), arr.end(), result.begin());
88+
6789
return result;
6890
}
6991

70-
inline std::span<const unsigned char> extractProRotatingEd25519PrivkeyAsSpan(
92+
inline std::optional<std::span<const unsigned char>> extractProRotatingEd25519PrivKeyAsSpan(
7193
const Napi::Object& obj, const std::string identifier) {
72-
assertIsStringOrNull(obj.Get("proRotatingEd25519Privkey"));
73-
auto proRotatingEd25519PrivkeyHex =
74-
maybeNonemptyString(obj.Get("proRotatingEd25519Privkey"), identifier);
75-
return from_hex_to_span(proRotatingEd25519PrivkeyHex.value_or(""));
94+
assertIsStringOrNull(obj.Get("proRotatingEd25519PrivKey"));
95+
auto proRotatingEd25519PrivKeyHex =
96+
maybeNonemptyString(obj.Get("proRotatingEd25519PrivKey"), identifier);
97+
98+
if (proRotatingEd25519PrivKeyHex.has_value() && proRotatingEd25519PrivKeyHex.value().size()) {
99+
assert_length(*proRotatingEd25519PrivKeyHex, 64, identifier);
100+
101+
auto ret = from_hex_to_span(*proRotatingEd25519PrivKeyHex);
102+
103+
return ret;
104+
}
105+
106+
return std::nullopt;
76107
}
77108

78109
class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
@@ -313,9 +344,9 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
313344
// {
314345
// "plaintext": Uint8Array,
315346
// "sentTimestampMs": Number,
316-
// "senderEd25519Privkey": Hexstring,
347+
// "senderEd25519Seed": Hexstring,
317348
// "recipientPubkey": Hexstring,
318-
// "proRotatingEd25519Privkey": Hexstring | null,
349+
// "proRotatingEd25519PrivKey": Hexstring | null,
319350
// }
320351
//
321352

@@ -337,12 +368,11 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
337368

338369
ready_to_send[i] = session::encode_for_1o1(
339370
extractPlaintext(obj, "encryptFor1o1.obj.plaintext"),
340-
extractSenderEd25519PrivkeyAsSpan(
341-
obj, "encryptFor1o1.obj.senderEd25519Privkey"),
371+
extractSenderEd25519SeedAsSpan(obj, "encryptFor1o1.obj.senderEd25519Seed"),
342372
extractSentTimestampMs(obj, "encryptFor1o1.obj.sentTimestampMs"),
343373
extractRecipientPubkeyAsArray(obj, "encryptFor1o1.obj.recipientPubkey"),
344-
extractProRotatingEd25519PrivkeyAsSpan(
345-
obj, "encryptFor1o1.obj.proRotatingEd25519Privkey"));
374+
extractProRotatingEd25519PrivKeyAsSpan(
375+
obj, "encryptFor1o1.obj.proRotatingEd25519PrivKey"));
346376
}
347377

348378
auto ret = Napi::Object::New(info.Env());
@@ -358,11 +388,11 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
358388
// properties:
359389
// {
360390
// "plaintext": Uint8Array,
361-
// "senderEd25519Privkey": Hexstring,
391+
// "senderEd25519Seed": Hexstring,
362392
// "sentTimestampMs": Number,
363393
// "recipientPubkey": Hexstring,
364394
// "communityPubkey": Hexstring,
365-
// "proRotatingEd25519Privkey": Hexstring | null,
395+
// "proRotatingEd25519PrivKey": Hexstring | null,
366396
// }
367397
//
368398

@@ -385,15 +415,15 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
385415

386416
ready_to_send[i] = session::encode_for_community_inbox(
387417
extractPlaintext(obj, "encryptForCommunityInbox.obj.plaintext"),
388-
extractSenderEd25519PrivkeyAsSpan(
389-
obj, "encryptForCommunityInbox.obj.senderEd25519Privkey"),
418+
extractSenderEd25519SeedAsSpan(
419+
obj, "encryptForCommunityInbox.obj.senderEd25519Seed"),
390420
extractSentTimestampMs(obj, "encryptForCommunityInbox.obj.sentTimestampMs"),
391421
extractRecipientPubkeyAsArray(
392422
obj, "encryptForCommunityInbox.obj.recipientPubkey"),
393423
extractCommunityPubkeyAsArray(
394424
obj, "encryptForCommunityInbox.obj.communityPubkey"),
395-
extractProRotatingEd25519PrivkeyAsSpan(
396-
obj, "encryptForCommunityInbox.obj.proRotatingEd25519Privkey"));
425+
extractProRotatingEd25519PrivKeyAsSpan(
426+
obj, "encryptForCommunityInbox.obj.proRotatingEd25519PrivKey"));
397427
}
398428

399429
auto ret = Napi::Object::New(info.Env());
@@ -409,7 +439,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
409439
// properties:
410440
// {
411441
// "plaintext": Uint8Array,
412-
// "proRotatingEd25519Privkey": Hexstring | null,
442+
// "proRotatingEd25519PrivKey": Hexstring | null,
413443
// }
414444
//
415445

@@ -431,8 +461,8 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
431461

432462
ready_to_send[i] = session::encode_for_community(
433463
extractPlaintext(obj, "encryptForCommunity.obj.plaintext"),
434-
extractProRotatingEd25519PrivkeyAsSpan(
435-
obj, "encryptForCommunity.obj.proRotatingEd25519Privkey"));
464+
extractProRotatingEd25519PrivKeyAsSpan(
465+
obj, "encryptForCommunity.obj.proRotatingEd25519PrivKey"));
436466
}
437467

438468
auto ret = Napi::Object::New(info.Env());
@@ -448,11 +478,11 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
448478
// properties:
449479
// {
450480
// "plaintext": Uint8Array,
451-
// "senderEd25519Privkey": Hexstring,
481+
// "senderEd25519Seed": Uint8Array, 32 bytes
452482
// "sentTimestampMs": Number,
453483
// "groupEd25519Pubkey": Hexstring,
454-
// "groupEncPrivKey": Hexstring,
455-
// "proRotatingEd25519Privkey": Hexstring | null,
484+
// "groupEncKey": Hexstring,
485+
// "proRotatingEd25519PrivKey": Hexstring | null,
456486
// }
457487
//
458488

@@ -472,16 +502,28 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
472502
}
473503
auto obj = itemValue.As<Napi::Object>();
474504

505+
auto plaintext = extractPlaintext(obj, "encryptForGroup.obj.plaintext");
506+
auto senderEd25519Seed = extractSenderEd25519SeedAsSpan(
507+
obj, "encryptForGroup.obj.senderEd25519Seed");
508+
509+
auto sentTimestampMs =
510+
extractSentTimestampMs(obj, "encryptForGroup.obj.sentTimestampMs");
511+
512+
auto groupEd25519Pubkey = extractGroupEd25519PubkeyAsArray(
513+
obj, "encryptForGroup.obj.recipientPubkey");
514+
515+
auto groupEncKey =
516+
extractGroupEncKeyAsArray(obj, "encryptForGroup.obj.groupEncKey");
517+
auto proRotatingEd25519PrivKey = extractProRotatingEd25519PrivKeyAsSpan(
518+
obj, "encryptForGroup.obj.proRotatingEd25519PrivKey");
519+
475520
ready_to_send[i] = session::encode_for_group(
476-
extractPlaintext(obj, "encryptForGroup.obj.plaintext"),
477-
extractSenderEd25519PrivkeyAsSpan(
478-
obj, "encryptForGroup.obj.senderEd25519Privkey"),
479-
extractSentTimestampMs(obj, "encryptForGroup.obj.sentTimestampMs"),
480-
extractGroupEd25519PubkeyAsArray(
481-
obj, "encryptForGroup.obj.recipientPubkey"),
482-
extractGroupEncPrivKeyAsArray(obj, "encryptForGroup.obj.groupEncPrivKey"),
483-
extractProRotatingEd25519PrivkeyAsSpan(
484-
obj, "encryptForGroup.obj.proRotatingEd25519Privkey"));
521+
plaintext,
522+
senderEd25519Seed,
523+
sentTimestampMs,
524+
groupEd25519Pubkey,
525+
groupEncKey,
526+
proRotatingEd25519PrivKey);
485527
}
486528

487529
auto ret = Napi::Object::New(info.Env());

include/utilities.hpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <unordered_set>
1111
#include <vector>
1212

13+
#include "oxenc/hex.h"
1314
#include "session/config/namespaces.hpp"
1415
#include "session/config/profile_pic.hpp"
1516
#include "session/types.hpp"
@@ -357,6 +358,34 @@ template <std::size_t N>
357358
std::array<uint8_t, N> spanToArray(std::span<const unsigned char> span);
358359

359360
template <std::size_t N>
360-
std::array<uint8_t, N> from_hex_to_array(std::string_view x);
361+
std::array<uint8_t, N> from_hex_to_array(std::string x) {
362+
std::string as_hex = oxenc::from_hex(x);
363+
if (as_hex.size() != N) {
364+
throw std::invalid_argument(
365+
std::format(
366+
"from_hex_to_array: Decoded hex size mismatch: expected {}, got {}",
367+
N,
368+
as_hex.size()));
369+
}
370+
371+
std::array<uint8_t, N> result;
372+
std::memcpy(result.data(), as_hex.data(), N);
373+
return result;
374+
}
375+
376+
// Concept to match containers with a size() method
377+
template <typename T>
378+
concept HasSize = requires(T t) {
379+
{t.size()}->std::convertible_to<size_t>;
380+
};
381+
382+
template <HasSize T>
383+
void assert_length(const T& x, size_t n, std::string_view base_identifier) {
384+
if (x.size() != n) {
385+
throw std::invalid_argument(
386+
std::format(
387+
"assert_length: expected {}, got {} for {}", n, x.size(), base_identifier));
388+
}
389+
}
361390

362391
} // namespace session::nodeapi

src/addon.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
3232
.As<Napi::Object>()
3333
.Get("log")
3434
.As<Napi::Function>();
35-
Napi::String jsStr = Napi::String::New(env, "libsession-util: " + *msg);
35+
Napi::String jsStr = Napi::String::New(env, "libsession: " + *msg);
3636
consoleLog.Call({jsStr});
3737
delete msg;
3838
});

src/groups/meta_group_wrapper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) {
139139
InstanceMethod("keysNeedsRekey", &MetaGroupWrapper::keysNeedsRekey),
140140
InstanceMethod("keyRekey", &MetaGroupWrapper::keyRekey),
141141
InstanceMethod("keyGetAll", &MetaGroupWrapper::keyGetAll),
142+
InstanceMethod("keyGetEncryptionKeyHex", &MetaGroupWrapper::keyGetEncryptionKeyHex),
142143
InstanceMethod("activeHashes", &MetaGroupWrapper::activeHashes),
143144
InstanceMethod("loadKeyMessage", &MetaGroupWrapper::loadKeyMessage),
144145
InstanceMethod("keyGetCurrentGen", &MetaGroupWrapper::keyGetCurrentGen),
@@ -743,6 +744,10 @@ Napi::Value MetaGroupWrapper::keyGetAll(const Napi::CallbackInfo& info) {
743744
return wrapResult(info, [&] { return meta_group->keys->group_keys(); });
744745
}
745746

747+
Napi::Value MetaGroupWrapper::keyGetEncryptionKeyHex(const Napi::CallbackInfo& info) {
748+
return wrapResult(info, [&] { return to_hex(meta_group->keys->group_enc_key()); });
749+
}
750+
746751
Napi::Value MetaGroupWrapper::loadKeyMessage(const Napi::CallbackInfo& info) {
747752
return wrapResult(info, [&] {
748753
assertInfoLength(info, 3);

src/utilities.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ std::optional<session::config::profile_pic> maybeNonemptyProfilePic(
200200
// when the `x` obj is provided (i.e. not null), it should have those 2 fields set.
201201
// They can be empty (meaning to remove the profile pic), but not undefined/null, as the
202202
// object itself should have been undefined/null
203-
throw new std::invalid_argument{"maybeNonemptyProfilePic with invalid input"};
203+
throw std::invalid_argument{"maybeNonemptyProfilePic with invalid input"};
204204
}
205205
return session::config::profile_pic{*url, *key};
206206

@@ -343,8 +343,4 @@ std::array<uint8_t, N> spanToArray(std::span<const unsigned char> span) {
343343
return result;
344344
}
345345

346-
template <std::size_t N>
347-
std::array<uint8_t, N> from_hex_to_array(std::string_view x) {
348-
return spanToArray<N>(session::to_span(oxenc::from_hex(x)));
349-
}
350346
} // namespace session::nodeapi

types/groups/groupkeys.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare module 'libsession_util_nodejs' {
66
keysNeedsRekey: () => boolean;
77
keyRekey: () => Uint8Array;
88
keyGetAll: () => Array<Uint8Array>;
9+
keyGetEncryptionKeyHex: () => string;
910
loadKeyMessage: (hash: string, data: Uint8Array, timestampMs: number) => boolean;
1011
keysAdmin: () => boolean;
1112
keyGetCurrentGen: () => number;

types/groups/metagroup.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ declare module 'libsession_util_nodejs' {
164164
| MakeActionCall<MetaGroupWrapper, 'keysNeedsRekey'>
165165
| MakeActionCall<MetaGroupWrapper, 'keyRekey'>
166166
| MakeActionCall<MetaGroupWrapper, 'keyGetAll'>
167+
| MakeActionCall<MetaGroupWrapper, 'keyGetEncryptionKeyHex'>
167168
| MakeActionCall<MetaGroupWrapper, 'loadKeyMessage'>
168169
| MakeActionCall<MetaGroupWrapper, 'keysAdmin'>
169170
| MakeActionCall<MetaGroupWrapper, 'keyGetCurrentGen'>

0 commit comments

Comments
 (0)