Skip to content

Commit 89f744b

Browse files
authored
Merge pull request #7 from commt/feature/e2e
Feature E2E
2 parents 0479ce2 + c83f5b4 commit 89f744b

File tree

5 files changed

+130
-32
lines changed

5 files changed

+130
-32
lines changed

src/components/RoomCard/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ const RoomCard = ({ room, onClickAction }: RoomCardProps) => {
147147
{!isLastMessageBySelfUser && room.groupName
148148
? `${room.lastMessage?.user.name}: `
149149
: ""}
150-
{room.lastMessage?.text}
150+
{room.lastMessage?.text &&
151+
room.lastMessage.text.slice(0, 25) +
152+
(room.lastMessage.text.length > 25 ? "..." : "")}
151153
</MessageText>
152154
)}
153155
</View>

src/hooks/useSendMessage.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { sendMessage } from "../utils/socket";
44
import { CommtContext } from "../context/Context";
55
import { addMessage } from "../context/actions/messagesAction";
66
import { updateLastMessage } from "../context/actions/roomsActions";
7-
import forge from "node-forge";
8-
import { aesEncrypt } from "../utils/encryption";
7+
import { aesEncrypt, rsaEncrypt } from "../utils/encryption";
98

109
interface onSendMessageProps {
1110
message: IMessage;
@@ -16,9 +15,10 @@ interface onSendMessageProps {
1615
const useSendMessage = () => {
1716
const {
1817
state: {
19-
users: { selfUser },
18+
users: { selfUser, users },
19+
rooms,
2020
app: {
21-
configs: { secretKey, apiKey, subscriptionKey, projectName },
21+
configs: { secretKey, apiKey, subscriptionKey, projectName, e2e },
2222
},
2323
},
2424
dispatch,
@@ -32,22 +32,47 @@ const useSendMessage = () => {
3232
senderId: message.user._id,
3333
};
3434

35-
// create iv and encrypt data for each message
36-
const iv = forge.random.getBytesSync(16);
37-
const encryptedMessage = aesEncrypt({
35+
let encryptedMessage = messageContent.text;
36+
37+
// Encrypt message with RSA; If the tenant enabled E2E encryption and it's not a system message
38+
if (e2e && !message.system) {
39+
// TODO: Investigation for group/community chat RSA encryption
40+
const room = rooms.find((room) => room.roomId === roomId);
41+
42+
if (room && !room.groupName) {
43+
// Find the opposite user ID among one-to-one room participants
44+
const oppositeUserId = room.participants.find(
45+
(id) => id !== selfUser?._id && !id.startsWith("system"),
46+
);
47+
48+
const oppositeUserPck = users.find(
49+
(user) => user._id === oppositeUserId,
50+
)?.publicKey;
51+
52+
// If the opposite user has a public key, the message text is encrypted using RSA.
53+
if (oppositeUserPck) {
54+
encryptedMessage = rsaEncrypt({
55+
message: encryptedMessage,
56+
publicKey: oppositeUserPck,
57+
});
58+
}
59+
}
60+
}
61+
62+
// AES encryption is the standard encryption method and encrypts every messages. It encrypts data by generating IV.
63+
const { encryptedMessage: encryptedMsg, iv } = aesEncrypt({
3864
key: secretKey,
39-
iv,
4065
messageData: JSON.stringify({
41-
message: messageContent,
66+
message: { ...messageContent, text: encryptedMessage },
4267
roomId,
4368
chatRoomAuthId,
4469
}),
4570
});
4671

4772
sendMessage(
4873
{
49-
message: encryptedMessage,
50-
iv: forge.util.bytesToHex(iv),
74+
message: encryptedMsg,
75+
iv,
5176
},
5277
(status) => {
5378
// If the message sending succesfully

src/service/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ import { RoomProps } from "../context/reducers/roomsReducer";
55
import PackageJson from "../../package.json";
66
import * as events from "../utils/events";
77

8-
interface AuthKeyProps {
8+
export interface InitiateProps {
99
apiKey: string;
1010
subscriptionKey: string;
1111
projectName: string;
1212
}
1313

14-
export interface InitiateProps extends AuthKeyProps {
15-
projectName: string;
16-
}
17-
18-
interface OnlineInfoProps extends AuthKeyProps {
14+
interface OnlineInfoProps extends InitiateProps {
1915
userIds: string;
2016
chatAuthId: string; // for selfUser's chatAuthId
2117
}
@@ -25,7 +21,7 @@ type OnlineInfoReturnProps = Pick<
2521
"chatAuthId" | "socketId" | "online"
2622
>;
2723

28-
interface ReadTokenProps extends AuthKeyProps {
24+
interface ReadTokenProps extends InitiateProps {
2925
roomIds: string;
3026
chatAuthId: string; //selfUser's chatAuthId
3127
}
@@ -162,7 +158,7 @@ export const handleLogger = async (props: HandleLoggerProps) => {
162158

163159
const logObject = {
164160
projectName,
165-
SDK: project.name,
161+
SDK: project.SDK,
166162
version: project.version,
167163
error,
168164
...(chatAuthId && { chatAuthId }),

src/utils/SocketController.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "../context/actions/roomsActions";
1212
import { updateTypingUsers } from "../context/actions/typingUsersAction";
1313
import { IndicatorProps } from "../context/reducers/appReducer";
14-
import { aesDecrypt } from "./encryption";
14+
import { aesDecrypt, rsaDecrypt } from "./encryption";
1515
import { handleLogger } from "../service";
1616
import * as events from "./events";
1717

@@ -28,6 +28,7 @@ const SocketController = () => {
2828
subscriptionKey,
2929
secretKey,
3030
projectName,
31+
e2e,
3132
},
3233
},
3334
},
@@ -213,7 +214,7 @@ const SocketController = () => {
213214
const handleMessage = (data: DataProps) => {
214215
try {
215216
const { message: messageData, iv } = data;
216-
// decrypt the encrypted message
217+
// Decrypt, encrypted message with AES
217218
const decryptedMessage: MessageInfoProps = JSON.parse(
218219
aesDecrypt({
219220
key: secretKey,
@@ -224,9 +225,30 @@ const SocketController = () => {
224225

225226
let { message } = decryptedMessage;
226227
const { roomId } = decryptedMessage;
228+
const room = rooms.find(({ roomId: Id }) => Id === roomId);
229+
230+
/**
231+
* If the tenant enabled E2E encryption and
232+
* It's not a system message and
233+
* The active user has a private key and
234+
* The room is not a community room (one-to-one room)
235+
* Decrypt RSA message
236+
*/
237+
if (
238+
e2e &&
239+
selfUser?.privateKey &&
240+
!room?.groupAvatar &&
241+
!message.system
242+
) {
243+
// decrypt message text using RSA
244+
const decryptedMsg = rsaDecrypt({
245+
encryptedMsg: message.text,
246+
privateKey: selfUser.privateKey,
247+
});
248+
message = { ...message, text: decryptedMsg };
249+
}
227250

228251
message = { ...message, user: { _id: message.senderId } };
229-
const room = rooms.find(({ roomId: Id }) => Id === roomId);
230252

231253
// if message belongs community room, add avatar and name to user object
232254
if (room && room.groupAvatar) {

src/utils/encryption.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import forge from "node-forge";
1+
import forge, { pki } from "node-forge";
22

33
interface KeyProps {
44
key: string;
5-
iv: string;
65
}
76

87
interface AesEncryptProps extends KeyProps {
@@ -11,20 +10,32 @@ interface AesEncryptProps extends KeyProps {
1110

1211
interface AesDecryptProps extends KeyProps {
1312
encryptedMessageData: string;
13+
iv: string;
1414
}
1515

16-
export const aesEncrypt = ({
17-
messageData,
18-
key,
19-
iv,
20-
}: AesEncryptProps): string => {
16+
interface RsaEncryptProps {
17+
publicKey: string;
18+
message: string;
19+
}
20+
21+
interface RsaDecryptProps {
22+
encryptedMsg: string;
23+
privateKey: string;
24+
}
25+
26+
export const aesEncrypt = ({ messageData, key }: AesEncryptProps) => {
27+
let iv = forge.random.getBytesSync(16);
2128
const cipher = forge.cipher.createCipher("AES-CBC", key);
2229

2330
cipher.start({ iv });
2431
cipher.update(forge.util.createBuffer(messageData, "utf8"));
2532
cipher.finish();
26-
// return encrypted data
27-
return cipher.output.toHex();
33+
34+
const encryptedMessage = cipher.output.toHex();
35+
iv = forge.util.bytesToHex(iv);
36+
37+
// return encrypted message and iv
38+
return { encryptedMessage, iv };
2839
};
2940

3041
export const aesDecrypt = ({
@@ -42,3 +53,45 @@ export const aesDecrypt = ({
4253
// return decrypted data
4354
return decipher.output.toString();
4455
};
56+
57+
export const rsaEncrypt = ({ publicKey, message }: RsaEncryptProps) => {
58+
// convert a PEM-formatted public key to a Forge public key
59+
const pck = pki.publicKeyFromPem(publicKey);
60+
// find the max chunk length according to public key
61+
const maxLength = pck.n.bitLength() / 8 - 11;
62+
const messageLength = message.length;
63+
const encryptedChunks = [];
64+
65+
// If the message is longer than the maximum length, divide it into chunks and encrypt
66+
if (messageLength > maxLength) {
67+
for (let i = 0; i < messageLength; i += maxLength) {
68+
const chunk = message.slice(i, i + maxLength);
69+
encryptedChunks.push(pck.encrypt(chunk));
70+
}
71+
} else {
72+
encryptedChunks.push(pck.encrypt(message));
73+
}
74+
75+
const encryptedMessage = forge.util.encode64(
76+
JSON.stringify({ encryptedChunks }),
77+
);
78+
79+
return encryptedMessage;
80+
};
81+
82+
export const rsaDecrypt = ({ privateKey, encryptedMsg }: RsaDecryptProps) => {
83+
// convert a PEM-formatted private key to a Forge private key
84+
const ptk = pki.privateKeyFromPem(privateKey);
85+
86+
// get the encryped chuncks
87+
const encryptedChunks = JSON.parse(
88+
forge.util.decode64(encryptedMsg),
89+
).encryptedChunks;
90+
91+
// decrypt and merge the chunks
92+
const decryptedMessage = encryptedChunks
93+
.map((encryptedChunk: string) => ptk.decrypt(encryptedChunk))
94+
.join("");
95+
96+
return decryptedMessage;
97+
};

0 commit comments

Comments
 (0)