feat(voice)!: add new encryption methods, remove old methods (#10451)

BREAKING CHANGE: This library no longer supports using `tweetnacl` as an encryption library due to Discord deprecating the algorithms that `tweetnacl` helped us support (read more [here](https://discord.com/developers/docs/change-log#voice-encryption-modes)). Please migrate to one of: `sodium-native`, `sodium`, `@stablelib/xchacha20poly1305`, `@noble/ciphers` or `libsodium-wrappers` unless your system supports `aes-256-gcm` (verify by running `require('node:crypto').getCiphers().includes('aes-256-gcm')`).

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
This commit is contained in:
pat
2024-11-18 09:08:51 +11:00
committed by GitHub
parent 51a017a14e
commit 9f8b9b1d66
11 changed files with 358 additions and 203 deletions

View File

@@ -1,75 +1,135 @@
import { Buffer } from 'node:buffer';
interface Methods {
close(opusPacket: Buffer, nonce: Buffer, secretKey: Uint8Array): Buffer;
open(buffer: Buffer, nonce: Buffer, secretKey: Uint8Array): Buffer | null;
random(bytes: number, nonce: Buffer): Buffer;
crypto_aead_xchacha20poly1305_ietf_decrypt(
cipherText: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
): Buffer;
crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
): Buffer;
}
const libs = {
'sodium-native': (sodium: any): Methods => ({
open: (buffer: Buffer, nonce: Buffer, secretKey: Uint8Array) => {
if (buffer) {
const output = Buffer.allocUnsafe(buffer.length - sodium.crypto_box_MACBYTES);
if (sodium.crypto_secretbox_open_easy(output, buffer, nonce, secretKey)) return output;
}
return null;
crypto_aead_xchacha20poly1305_ietf_decrypt: (
cipherText: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
const message = Buffer.alloc(cipherText.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(message, null, cipherText, additionalData, nonce, key);
return message;
},
close: (opusPacket: Buffer, nonce: Buffer, secretKey: Uint8Array) => {
const output = Buffer.allocUnsafe(opusPacket.length + sodium.crypto_box_MACBYTES);
sodium.crypto_secretbox_easy(output, opusPacket, nonce, secretKey);
return output;
},
random: (num: number, buffer: Buffer = Buffer.allocUnsafe(num)) => {
sodium.randombytes_buf(buffer);
return buffer;
crypto_aead_xchacha20poly1305_ietf_encrypt: (
plaintext: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
const cipherText = Buffer.alloc(plaintext.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, plaintext, additionalData, null, nonce, key);
return cipherText;
},
}),
sodium: (sodium: any): Methods => ({
open: sodium.api.crypto_secretbox_open_easy,
close: sodium.api.crypto_secretbox_easy,
random: (num: number, buffer: Buffer = Buffer.allocUnsafe(num)) => {
sodium.api.randombytes_buf(buffer);
return buffer;
crypto_aead_xchacha20poly1305_ietf_decrypt: (
cipherText: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
return sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(cipherText, additionalData, null, nonce, key);
},
crypto_aead_xchacha20poly1305_ietf_encrypt: (
plaintext: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
return sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key);
},
}),
'libsodium-wrappers': (sodium: any): Methods => ({
open: sodium.crypto_secretbox_open_easy,
close: sodium.crypto_secretbox_easy,
random: sodium.randombytes_buf,
crypto_aead_xchacha20poly1305_ietf_decrypt: (
cipherText: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, cipherText, additionalData, nonce, key);
},
crypto_aead_xchacha20poly1305_ietf_encrypt: (
plaintext: Buffer,
additionalData: Buffer,
nonce: Buffer,
key: ArrayBufferLike,
) => {
return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key);
},
}),
tweetnacl: (tweetnacl: any): Methods => ({
open: tweetnacl.secretbox.open,
close: tweetnacl.secretbox,
random: tweetnacl.randomBytes,
'@stablelib/xchacha20poly1305': (stablelib: any): Methods => ({
crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, nonce, key) {
const crypto = new stablelib.XChaCha20Poly1305(key);
return crypto.open(nonce, plaintext, additionalData);
},
crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, additionalData, nonce, key) {
const crypto = new stablelib.XChaCha20Poly1305(key);
return crypto.seal(nonce, cipherText, additionalData);
},
}),
'@noble/ciphers/chacha': (noble: any): Methods => ({
crypto_aead_xchacha20poly1305_ietf_decrypt(cipherText, additionalData, nonce, key) {
const chacha = noble.xchacha20poly1305(key, nonce, additionalData);
return chacha.decrypt(cipherText);
},
crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, nonce, key) {
const chacha = noble.xchacha20poly1305(key, nonce, additionalData);
return chacha.encrypt(plaintext);
},
}),
} as const;
const fallbackError = () => {
throw new Error(
`Cannot play audio as no valid encryption package is installed.
- Install sodium, libsodium-wrappers, or tweetnacl.
- Install one of:
- sodium
- libsodium-wrappers
- @stablelib/xchacha20poly1305
- @noble/ciphers.
- Use the generateDependencyReport() function for more information.\n`,
);
};
const methods: Methods = {
open: fallbackError,
close: fallbackError,
random: fallbackError,
crypto_aead_xchacha20poly1305_ietf_encrypt: fallbackError,
crypto_aead_xchacha20poly1305_ietf_decrypt: fallbackError,
};
void (async () => {
// eslint-disable-next-line no-async-promise-executor
export const secretboxLoadPromise = new Promise<void>(async (resolve) => {
for (const libName of Object.keys(libs) as (keyof typeof libs)[]) {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const lib = require(libName);
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready;
const lib = await import(libName);
if (libName === 'libsodium-wrappers' && lib.ready) {
await lib.ready;
}
Object.assign(methods, libs[libName](lib));
break;
} catch {}
}
})();
resolve();
});
export { methods };

View File

@@ -68,7 +68,8 @@ export function generateDependencyReport() {
addVersion('sodium-native');
addVersion('sodium');
addVersion('libsodium-wrappers');
addVersion('tweetnacl');
addVersion('@stablelib/xchacha20poly1305');
addVersion('@noble/ciphers');
report.push('');
// ffmpeg