-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSodium.ts
223 lines (199 loc) · 9.19 KB
/
Sodium.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import {ConnectionData} from "@lib/voice";
import {Buffer} from "node:buffer";
import crypto from "node:crypto";
/**
* @author SNIPPIK
* @description Поддерживаемые библиотеки
* @private
*/
const support_libs: Methods.supported = {
"sodium-native": (lib) => ({
close: (opusPacket: Buffer, nonce: Buffer, secretKey: Uint8Array) => {
const output = Buffer.allocUnsafe(opusPacket.length + lib.crypto_box_MACBYTES);
lib.crypto_secretbox_easy(output, opusPacket, nonce, secretKey);
return output;
},
random(num: number, buffer: Buffer = Buffer.allocUnsafe(num)) {
lib.randombytes_buf(buffer);
return buffer;
},
crypto_aead_xchacha20poly1305_ietf_decrypt: (cipherText, additionalData, nonce, key) => {
const message = Buffer.alloc(cipherText.length - lib.crypto_aead_xchacha20poly1305_ietf_ABYTES);
lib.crypto_aead_xchacha20poly1305_ietf_decrypt(message, null, cipherText, additionalData, nonce, key);
return message;
},
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) => {
const cipherText = Buffer.alloc(plaintext.length + lib.crypto_aead_xchacha20poly1305_ietf_ABYTES);
lib.crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, plaintext, additionalData, null, nonce, key);
return cipherText;
}
}),
"libsodium-wrappers": (lib) => ({
close: lib.crypto_secretbox_easy,
random: lib.randombytes_buf,
crypto_aead_xchacha20poly1305_ietf_decrypt: (cipherText: Buffer, additionalData: Buffer, nonce: Buffer, key: ArrayBufferLike) => {
return lib.crypto_aead_xchacha20poly1305_ietf_decrypt(null, cipherText, additionalData, nonce, key);
},
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext: Buffer, additionalData: Buffer, nonce: Buffer, key: ArrayBufferLike) => {
return lib.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key);
}
})
};
/**
* @author SNIPPIK
* @description Здесь будет находиться найденная библиотека, если она конечно будет найдена
* @private
*/
const loaded_lib: Methods.current = {};
/**
* @author SNIPPIK
* @description Максимальный размер пакета
* @private
*/
const MAX_NONCE_SIZE = 2 ** 32 - 1;
/**
* @author SNIPPIK
* @description Доступные заголовки для отправки opus пакетов
* @private
*/
const SUPPORTED_ENCRYPTION_MODES = [
"aead_xchacha20_poly1305_rtpsize",
// Старые модификаторы
"xsalsa20_poly1305",
"xsalsa20_poly1305_lite",
"xsalsa20_poly1305_suffix"
];
/**
* @author SNIPPIK
* @description Выдаваемы методы для использования sodium
* @class Encryption
* @public
*/
export class Encryption {
/**
* @description Шифрует пакет Opus, используя формат, согласованный экземпляром и Discord.
* @param packet - Пакет Opus для шифрования
* @param connectionData - Текущие данные подключения экземпляра
* @public
* @static
*/
public static packet = (packet: Buffer, connectionData: ConnectionData) => {
const rtp_packet = Buffer.alloc(12);
rtp_packet[0] = 0x80;
rtp_packet[1] = 0x78;
const { sequence, timestamp, ssrc } = connectionData;
rtp_packet.writeUIntBE(sequence, 2, 2);
rtp_packet.writeUIntBE(timestamp, 4, 4);
rtp_packet.writeUIntBE(ssrc, 8, 4);
rtp_packet.copy(Buffer.alloc(24), 0, 0, 12);
return Buffer.concat([rtp_packet, ...this.crypto(packet, connectionData, rtp_packet)]);
};
/**
* @description Шифрует пакет Opus, используя формат, согласованный экземпляром и Discord.
* @param packet - Пакет Opus для шифрования
* @param connectionData - Текущие данные подключения экземпляра
* @param additionalData - Доп данные для отправки
* @private
* @static
*/
private static crypto = (packet: Buffer, connectionData: ConnectionData, additionalData: Buffer) => {
const { secretKey, encryptionMode } = connectionData;
// Оба поддерживаемых метода шифрования требуют, чтобы одноразовое число было инкрементным целым числом.
connectionData.nonce++;
if (connectionData.nonce > MAX_NONCE_SIZE) connectionData.nonce = 0;
connectionData.nonceBuffer.writeUInt32BE(connectionData.nonce, 0);
// 4 дополнительных байта заполнения в конце зашифрованного пакета
const noncePadding = connectionData.nonceBuffer.subarray(0, 4);
switch (encryptionMode) {
/**
* @description Новые методы шифрования
*/
case "aead_aes256_gcm_rtpsize": {
const cipher = crypto.createCipheriv('aes-256-gcm', secretKey, connectionData.nonceBuffer);
cipher.setAAD(additionalData);
return [Buffer.concat([cipher.update(packet), cipher.final(), cipher.getAuthTag()]), noncePadding];
}
case "aead_xchacha20_poly1305_rtpsize": {
return [loaded_lib.crypto_aead_xchacha20poly1305_ietf_encrypt(packet, additionalData, connectionData.nonceBuffer, secretKey), noncePadding];
}
/**
* @description Старые методы шифрования
*/
case "xsalsa20_poly1305_suffix": {
const random = loaded_lib.random(24, connectionData.nonceBuffer);
return [loaded_lib.close(packet, random, secretKey), random];
}
case "xsalsa20_poly1305_lite": {
connectionData.nonce++;
if (connectionData.nonce > MAX_NONCE_SIZE) connectionData.nonce = 0;
connectionData.nonceBuffer.writeUInt32BE(connectionData.nonce, 0);
return [loaded_lib.close(packet, connectionData.nonceBuffer, secretKey), connectionData.nonceBuffer.subarray(0, 4)];
}
/**
* @description Старый метод при отсутствии поддержки новых стандартов
*/
default: return [loaded_lib.close(packet, Buffer.alloc(24), secretKey)];
}
}
/**
* @description Выбирает режим шифрования из списка заданных параметров. Выбирает наиболее предпочтительный вариант.
* @param options - Доступные варианты шифрования
* @public
* @static
*/
public static mode = (options: string[]): string => {
const option = options.find((option) => SUPPORTED_ENCRYPTION_MODES.includes(option));
if (option) return option;
throw new Error(`No compatible encryption modes. Available include: ${options.join(', ')}`);
};
/**
* @description Возвращает случайное число, находящееся в диапазоне n бит.
* @param numberOfBits - Количество бит
* @public
* @static
*/
public static randomNBit = (numberOfBits: number) => Math.floor(Math.random() * 2 ** numberOfBits);
}
/**
* @author SNIPPIK
* @description Поддерживаемые методы шифровки пакетов
* @namespace Methods
* @private
*/
namespace Methods {
/**
* @description Поддерживаемый запрос к библиотеке
* @type supported
*/
export type supported = {
[name: string]: (lib: any) => current
}
/**
* @description Новый тип шифровки пакетов
* @interface current
*/
export interface current {
crypto_aead_xchacha20poly1305_ietf_decrypt?(cipherText: Buffer, additionalData: Buffer, nonce: Buffer, key: ArrayBufferLike | Uint8Array): Buffer;
crypto_aead_xchacha20poly1305_ietf_encrypt?(plaintext: Buffer, additionalData: Buffer, nonce: Buffer, key: ArrayBufferLike | Uint8Array): Buffer;
close?(opusPacket: Buffer, nonce: Buffer, secretKey: Uint8Array): Buffer;
random?(bytes: number, nonce: Buffer): Buffer;
}
}
/**
* @author SNIPPIK
* @description Делаем проверку на наличие библиотек Sodium
* @async
*/
(async () => {
const names = Object.keys(support_libs), libs = `\n - ${names.join("\n - ")}`;
for (const name of names) {
try {
const library = require(name);
if (library?.ready) await library.ready;
Object.assign(loaded_lib, support_libs[name](library));
delete require.cache[require.resolve(name)];
return;
} catch {}
}
throw Error(`[Critical]: No encryption package is installed. Set one to choose from. ${libs}`);
})();