Skip to content

Commit 70cc278

Browse files
authored
Merge pull request #32 from session-foundation/feat/add-attach-deterministic-encryption
feat: add encryption/decrypt of attachments deterministic
2 parents eb38ed8 + c62000a commit 70cc278

File tree

9 files changed

+172
-42
lines changed

9 files changed

+172
-42
lines changed

include/multi_encrypt/multi_encrypt.hpp

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#pragma once
22

33
#include <napi.h>
4-
#include <span>
5-
#include <vector>
64

75
#include <algorithm>
6+
#include <span>
7+
#include <vector>
88

99
#include "../utilities.hpp"
10+
#include "session/attachments.hpp"
1011
#include "session/config/user_profile.hpp"
1112
#include "session/multi_encrypt.hpp"
1213
#include "session/random.hpp"
@@ -35,7 +36,14 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
3536
"multiDecryptEd25519",
3637
static_cast<napi_property_attributes>(
3738
napi_writable | napi_configurable)),
38-
39+
StaticMethod<&MultiEncryptWrapper::attachmentDecrypt>(
40+
"attachmentDecrypt",
41+
static_cast<napi_property_attributes>(
42+
napi_writable | napi_configurable)),
43+
StaticMethod<&MultiEncryptWrapper::attachmentEncrypt>(
44+
"attachmentEncrypt",
45+
static_cast<napi_property_attributes>(
46+
napi_writable | napi_configurable)),
3947
});
4048
}
4149

@@ -83,8 +91,10 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
8391
}
8492
std::vector<unsigned char> random_nonce = session::random::random(24);
8593

86-
std::vector<std::span<const unsigned char>> messages_sv(messages.begin(), messages.end());
87-
std::vector<std::span<const unsigned char>> recipients_sv(recipients.begin(), recipients.end());
94+
std::vector<std::span<const unsigned char>> messages_sv(
95+
messages.begin(), messages.end());
96+
std::vector<std::span<const unsigned char>> recipients_sv(
97+
recipients.begin(), recipients.end());
8898

8999
// Note: this function needs the first 2 args to be vector of sv explicitly
90100
return session::encrypt_for_multiple_simple(
@@ -121,6 +131,99 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
121131
encoded, ed25519_secret_key, sender_ed25519_pubkey, domain);
122132
});
123133
};
134+
135+
static Napi::Value attachmentEncrypt(const Napi::CallbackInfo& info) {
136+
return wrapResult(info, [&] {
137+
assertInfoLength(info, 1);
138+
assertIsObject(info[0]);
139+
auto obj = info[0].As<Napi::Object>();
140+
141+
if (obj.IsEmpty())
142+
throw std::invalid_argument("attachmentEncrypt received empty");
143+
144+
assertIsUInt8Array(obj.Get("seed"), "attachmentEncrypt.seed");
145+
auto seed = toCppBuffer(obj.Get("seed"), "attachmentEncrypt.seed");
146+
147+
assertIsUInt8Array(obj.Get("data"), "attachmentEncrypt.data");
148+
auto data = toCppBuffer(obj.Get("data"), "attachmentEncrypt.data");
149+
150+
assertIsString(obj.Get("domain"));
151+
auto domain = toCppString(obj.Get("domain"), "attachmentEncrypt.domain");
152+
153+
assertIsBoolean(obj.Get("allowLarge"));
154+
155+
auto allow_large = toCppBoolean(obj.Get("allowLarge"), "attachmentEncrypt.allowLarge");
156+
157+
if (domain != "attachment" && domain != "profilePic") {
158+
throw std::invalid_argument(
159+
"attachmentEncrypt.domain must be either 'attachment' or 'profilePic'");
160+
}
161+
162+
session::attachment::Domain attachment_domain =
163+
domain == "attachment" ? session::attachment::Domain::ATTACHMENT
164+
: session::attachment::Domain::PROFILE_PIC;
165+
166+
std::vector<std::byte> seed_bytes(
167+
reinterpret_cast<const std::byte*>(seed.data()),
168+
reinterpret_cast<const std::byte*>(seed.data() + seed.size()));
169+
170+
std::vector<std::byte> data_bytes(
171+
reinterpret_cast<const std::byte*>(data.data()),
172+
reinterpret_cast<const std::byte*>(data.data() + data.size()));
173+
auto encrypted = session::attachment::encrypt(
174+
seed_bytes, data_bytes, attachment_domain, allow_large);
175+
176+
auto ret = Napi::Object::New(info.Env());
177+
ret.Set("encryptedData", toJs(info.Env(), encrypted.first));
178+
ret.Set("encryptionKey", toJs(info.Env(), encrypted.second));
179+
180+
return ret;
181+
});
182+
};
183+
184+
static Napi::Value attachmentDecrypt(const Napi::CallbackInfo& info) {
185+
return wrapResult(info, [&] {
186+
assertInfoLength(info, 1);
187+
assertIsObject(info[0]);
188+
auto obj = info[0].As<Napi::Object>();
189+
190+
if (obj.IsEmpty())
191+
throw std::invalid_argument("attachmentDecrypt received empty");
192+
193+
assertIsUInt8Array(obj.Get("encryptedData"), "attachmentDecrypt.encryptedData");
194+
auto encrypted_data =
195+
toCppBuffer(obj.Get("encryptedData"), "attachmentDecrypt.encryptedData");
196+
197+
assertIsUInt8Array(obj.Get("decryptionKey"), "attachmentDecrypt.decryptionKey");
198+
auto decryption_key =
199+
toCppBuffer(obj.Get("decryptionKey"), "attachmentDecrypt.decryptionKey");
200+
201+
std::vector<std::byte> encrypted_data_bytes(
202+
reinterpret_cast<const std::byte*>(encrypted_data.data()),
203+
reinterpret_cast<const std::byte*>(
204+
encrypted_data.data() + encrypted_data.size()));
205+
206+
std::vector<std::byte> decryption_key_bytes(
207+
reinterpret_cast<const std::byte*>(decryption_key.data()),
208+
reinterpret_cast<const std::byte*>(
209+
decryption_key.data() + decryption_key.size()));
210+
211+
if (decryption_key_bytes.size() != session::attachment::ENCRYPT_KEY_SIZE) {
212+
throw std::invalid_argument("Key size mismatch");
213+
}
214+
215+
std::span<const std::byte, session::attachment::ENCRYPT_KEY_SIZE> decryption_key_span(
216+
decryption_key_bytes.data(), session::attachment::ENCRYPT_KEY_SIZE);
217+
218+
auto decrypted =
219+
session::attachment::decrypt(encrypted_data_bytes, decryption_key_span);
220+
221+
auto ret = Napi::Object::New(info.Env());
222+
ret.Set("decryptedData", toJs(info.Env(), decrypted));
223+
224+
return ret;
225+
});
226+
};
124227
};
125228

126-
} // namespace session::nodeapi
229+
}; // namespace session::nodeapi

include/user_config.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon
2020
Napi::Value getPriority(const Napi::CallbackInfo& info);
2121
Napi::Value getName(const Napi::CallbackInfo& info);
2222
Napi::Value getProfilePic(const Napi::CallbackInfo& info);
23-
Napi::Value getProfilePicWithKeyHex(const Napi::CallbackInfo& info);
2423

2524
void setPriority(const Napi::CallbackInfo& info);
2625
void setName(const Napi::CallbackInfo& info);

include/utilities.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,38 @@ struct toJs_impl<std::vector<unsigned char>> {
145145
}
146146
};
147147

148+
// this wrap std::vector<std::byte> to Uint8array in the js world
149+
template <>
150+
struct toJs_impl<std::vector<std::byte>> {
151+
auto operator()(const Napi::Env& env, std::vector<std::byte> b) const {
152+
return Napi::Buffer<uint8_t>::Copy(
153+
env,
154+
reinterpret_cast<const unsigned char*>(b.data()),
155+
b.size()
156+
);
157+
}
158+
};
159+
160+
// this wrap std::array<std::byte> to Uint8array in the js world
161+
template <std::size_t N>
162+
struct toJs_impl<std::array<std::byte, N>> {
163+
auto operator()(const Napi::Env& env, const std::array<std::byte, N>& b) const {
164+
const auto* data_uchar = reinterpret_cast<const uint8_t*>(b.data());
165+
return Napi::Buffer<uint8_t>::Copy(env, data_uchar, b.size());
166+
}
167+
};
168+
169+
// this wrap std::span<std::byte> to Uint8array in the js world
170+
template <>
171+
struct toJs_impl<std::span<std::byte>> {
172+
auto operator()(const Napi::Env& env, std::span<std::byte> b) const {
173+
auto data = b.data();
174+
auto data_uchar = reinterpret_cast<const unsigned char*>(data, b.size());
175+
176+
return Napi::Buffer<uint8_t>::Copy(env, data_uchar, b.size());
177+
}
178+
};
179+
148180
template <typename T>
149181
struct toJs_impl<T, std::enable_if_t<std::is_base_of_v<Napi::Value, T>>> {
150182
auto operator()(const Napi::Env& env, const T& val) { return val; }

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"main": "index.js",
33
"name": "libsession_util_nodejs",
44
"description": "Wrappers for the Session Util Library",
5-
"version": "0.5.8",
5+
"version": "0.5.9",
66
"license": "GPL-3.0",
77
"author": {
88
"name": "Oxen Project",

src/addon.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
Napi::ThreadSafeFunction tsfn;
1515

1616
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
17-
using namespace session::nodeapi;
1817

1918
tsfn = Napi::ThreadSafeFunction::New(
2019
env,
@@ -40,20 +39,20 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
4039
});
4140
oxen::log::set_level_default(oxen::log::Level::info);
4241

43-
ConstantsWrapper::Init(env, exports);
42+
session::nodeapi::ConstantsWrapper::Init(env, exports);
4443

4544
// Group wrappers init
46-
MetaGroupWrapper::Init(env, exports);
45+
session::nodeapi::MetaGroupWrapper::Init(env, exports);
4746

4847
// User wrappers init
49-
UserConfigWrapper::Init(env, exports);
50-
ContactsConfigWrapper::Init(env, exports);
51-
UserGroupsWrapper::Init(env, exports);
52-
ConvoInfoVolatileWrapper::Init(env, exports);
48+
session::nodeapi::UserConfigWrapper::Init(env, exports);
49+
session::nodeapi::ContactsConfigWrapper::Init(env, exports);
50+
session::nodeapi::UserGroupsWrapper::Init(env, exports);
51+
session::nodeapi::ConvoInfoVolatileWrapper::Init(env, exports);
5352

5453
// Fully static wrappers init
55-
MultiEncryptWrapper::Init(env, exports);
56-
BlindingWrapper::Init(env, exports);
54+
session::nodeapi::MultiEncryptWrapper::Init(env, exports);
55+
session::nodeapi::BlindingWrapper::Init(env, exports);
5756

5857
return exports;
5958
}

src/user_config.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ void UserConfigWrapper::Init(Napi::Env env, Napi::Object exports) {
2020
InstanceMethod("getPriority", &UserConfigWrapper::getPriority),
2121
InstanceMethod("getName", &UserConfigWrapper::getName),
2222
InstanceMethod("getProfilePic", &UserConfigWrapper::getProfilePic),
23-
InstanceMethod(
24-
"getProfilePicWithKeyHex", &UserConfigWrapper::getProfilePicWithKeyHex),
2523
InstanceMethod(
2624
"getProfileUpdatedSeconds",
2725
&UserConfigWrapper::getProfileUpdatedSeconds),
@@ -143,18 +141,6 @@ Napi::Value UserConfigWrapper::getProfileUpdatedSeconds(const Napi::CallbackInfo
143141
});
144142
}
145143

146-
Napi::Value UserConfigWrapper::getProfilePicWithKeyHex(const Napi::CallbackInfo& info) {
147-
return wrapResult(info, [&]() -> std::optional<std::string> {
148-
auto env = info.Env();
149-
auto pic = config.get_profile_pic();
150-
// if pic.key and url are set, return a combined string with both merged by a hash, and the
151-
// key in hex
152-
if (!pic.url.empty() && !pic.key.empty()) {
153-
return std::string(pic.url + "#" + to_hex(pic.key));
154-
}
155-
return std::nullopt;
156-
});
157-
}
158144

159145
Napi::Value UserConfigWrapper::getEnableBlindedMsgRequest(const Napi::CallbackInfo& info) {
160146
return wrapResult(info, [&] {

types/multi_encrypt/multi_encrypt.d.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ declare module 'libsession_util_nodejs' {
2020
senderEd25519Pubkey: Uint8Array;
2121
domain: EncryptionDomain;
2222
}) => Uint8Array | null;
23+
/**
24+
* Throws if the encryption fails
25+
*/
26+
attachmentEncrypt: (opts: {
27+
seed: Uint8Array;
28+
data: Uint8Array;
29+
domain: 'attachment' | 'profilePic';
30+
allowLarge: boolean;
31+
}) => { encryptedData: Uint8Array; encryptionKey: Uint8Array };
32+
/**
33+
*
34+
* Throws if the decryption fails
35+
*/
36+
attachmentDecrypt: (opts: { encryptedData: Uint8Array; decryptionKey: Uint8Array }) => {
37+
decryptedData: Uint8Array;
38+
};
2339
};
2440

2541
export type MultiEncryptActionsCalls = MakeWrapperActionCalls<MultiEncryptWrapper>;
@@ -30,6 +46,8 @@ declare module 'libsession_util_nodejs' {
3046
export class MultiEncryptWrapperNode {
3147
public static multiEncrypt: MultiEncryptWrapper['multiEncrypt'];
3248
public static multiDecryptEd25519: MultiEncryptWrapper['multiDecryptEd25519'];
49+
public static attachmentDecrypt: MultiEncryptWrapper['attachmentDecrypt'];
50+
public static attachmentEncrypt: MultiEncryptWrapper['attachmentEncrypt'];
3351
}
3452

3553
/**
@@ -39,5 +57,7 @@ declare module 'libsession_util_nodejs' {
3957
*/
4058
export type MultiEncryptActionsType =
4159
| MakeActionCall<MultiEncryptWrapper, 'multiEncrypt'>
42-
| MakeActionCall<MultiEncryptWrapper, 'multiDecryptEd25519'>;
60+
| MakeActionCall<MultiEncryptWrapper, 'multiDecryptEd25519'>
61+
| MakeActionCall<MultiEncryptWrapper, 'attachmentDecrypt'>
62+
| MakeActionCall<MultiEncryptWrapper, 'attachmentEncrypt'>;
4363
}

types/user/userconfig.d.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,6 @@ declare module 'libsession_util_nodejs' {
2828
setReuploadProfilePic: (pic: ProfilePicture) => void;
2929

3030
getProfileUpdatedSeconds: () => number;
31-
/**
32-
* Returns the profile picture url and key merged as a url fragment, or null if not set.
33-
* So this could return something like
34-
* `http://filev2.getsession.org/file/1234#ba7e23ef3d4dc54b706d79c149ec571a4d64043e13771236afc030f6c8118575`
35-
*
36-
*/
37-
getProfilePicWithKeyHex: () => string | undefined;
3831

3932
setEnableBlindedMsgRequest: (msgRequest: boolean) => void;
4033
getEnableBlindedMsgRequest: () => boolean | undefined;
@@ -61,7 +54,6 @@ declare module 'libsession_util_nodejs' {
6154
public setNewProfilePic: UserConfigWrapper['setNewProfilePic'];
6255
public setReuploadProfilePic: UserConfigWrapper['setReuploadProfilePic'];
6356
public getProfileUpdatedSeconds: UserConfigWrapper['getProfileUpdatedSeconds'];
64-
public getProfilePicWithKeyHex: UserConfigWrapper['getProfilePicWithKeyHex'];
6557
public getEnableBlindedMsgRequest: UserConfigWrapper['getEnableBlindedMsgRequest'];
6658
public setEnableBlindedMsgRequest: UserConfigWrapper['setEnableBlindedMsgRequest'];
6759
public getNoteToSelfExpiry: UserConfigWrapper['getNoteToSelfExpiry'];
@@ -85,7 +77,6 @@ declare module 'libsession_util_nodejs' {
8577
| MakeActionCall<UserConfigWrapper, 'setNewProfilePic'>
8678
| MakeActionCall<UserConfigWrapper, 'setReuploadProfilePic'>
8779
| MakeActionCall<UserConfigWrapper, 'getProfileUpdatedSeconds'>
88-
| MakeActionCall<UserConfigWrapper, 'getProfilePicWithKeyHex'>
8980
| MakeActionCall<UserConfigWrapper, 'getEnableBlindedMsgRequest'>
9081
| MakeActionCall<UserConfigWrapper, 'setEnableBlindedMsgRequest'>
9182
| MakeActionCall<UserConfigWrapper, 'getNoteToSelfExpiry'>

0 commit comments

Comments
 (0)