diff --git a/packages/core/package.json b/packages/core/package.json index e95cd640..709f4198 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,5 +13,9 @@ }, "dependencies": { "@mtcute/tl": "^0.0.0" + }, + "devDependencies": { + "@types/ws": "^7.4.1", + "ws": "^7.4.4" } } diff --git a/packages/core/src/utils/buffer-utils.ts b/packages/core/src/utils/buffer-utils.ts index 0705be53..4e2008c5 100644 --- a/packages/core/src/utils/buffer-utils.ts +++ b/packages/core/src/utils/buffer-utils.ts @@ -16,17 +16,6 @@ export function typedArrayToBuffer(arr: NodeJS.TypedArray): Buffer { Buffer.from(arr) } -export function reverseBuffer(buffer: Buffer): Buffer { - for (let i = 0, j = buffer.length - 1; i < j; ++i, --j) { - const t = buffer[j] - - buffer[j] = buffer[i] - buffer[i] = t - } - - return buffer -} - export function buffersEqual(a: Buffer, b: Buffer): boolean { if (a.length !== b.length) return false diff --git a/packages/core/src/utils/crypto/abstract.ts b/packages/core/src/utils/crypto/abstract.ts index 0582ca51..2006d0bb 100644 --- a/packages/core/src/utils/crypto/abstract.ts +++ b/packages/core/src/utils/crypto/abstract.ts @@ -29,7 +29,7 @@ export interface ICryptoProvider { rsaEncrypt(data: Buffer, key: TlPublicKey): MaybeAsync // in telegram, iv is always either used only once, or is the same for all calls for the key - createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme + createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme createAesIge(key: Buffer, iv: Buffer): IEncryptionScheme createAesEcb(key: Buffer): IEncryptionScheme @@ -65,7 +65,7 @@ export abstract class BaseCryptoProvider implements ICryptoProvider { return bigIntToBuffer(encryptedBigInt) } - abstract createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme + abstract createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme abstract createAesEcb(key: Buffer): IEncryptionScheme diff --git a/packages/core/src/utils/crypto/forge-crypto.ts b/packages/core/src/utils/crypto/forge-crypto.ts index 23cb24f9..cc948e22 100644 --- a/packages/core/src/utils/crypto/forge-crypto.ts +++ b/packages/core/src/utils/crypto/forge-crypto.ts @@ -15,41 +15,41 @@ export class ForgeCryptoProvider extends BaseCryptoProvider { ) } - createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme { - return this._createAes(key, iv, 'CTR') + createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme { + const cipher = forge.cipher[encrypt ? 'createCipher' : 'createDecipher']('AES-CTR', key.toString('binary')) + cipher.start({ iv: iv.toString('binary') }) + + const update = (data: Buffer): Buffer => { + cipher.output.data = '' + cipher.update(forge.util.createBuffer(data.toString('binary'))) + return Buffer.from(cipher.output.data, 'binary') + } + + return { + encrypt: update, + decrypt: update + } } createAesEcb(key: Buffer): IEncryptionScheme { - return this._createAes(key, null, 'ECB') - } - - private _createAes( - key: Buffer, - iv: Buffer | null, - method: string - ): IEncryptionScheme { - const methodName = `AES-${method}` const keyBuffer = key.toString('binary') - const ivBuffer = iv ? iv.toString('binary') : undefined return { encrypt(data: Buffer) { - const cipher = forge.cipher.createCipher(methodName, keyBuffer) - if (method === 'ECB') - cipher.mode.pad = cipher.mode.unpad = false - cipher.start(method === 'ECB' ? {} : { iv: ivBuffer }) + const cipher = forge.cipher.createCipher('AES-ECB', keyBuffer) + cipher.start({}) + cipher.mode.pad = cipher.mode.unpad = false cipher.update(forge.util.createBuffer(data.toString('binary'))) cipher.finish() return Buffer.from(cipher.output.data, 'binary') }, decrypt(data: Buffer) { const cipher = forge.cipher.createDecipher( - methodName, + 'AES-ECB', keyBuffer ) - if (method === 'ECB') - cipher.mode.pad = cipher.mode.unpad = false - cipher.start(method === 'ECB' ? {} : { iv: ivBuffer }) + cipher.start({}) + cipher.mode.pad = cipher.mode.unpad = false cipher.update(forge.util.createBuffer(data.toString('binary'))) cipher.finish() return Buffer.from(cipher.output.data, 'binary') diff --git a/packages/core/src/utils/crypto/node-crypto.ts b/packages/core/src/utils/crypto/node-crypto.ts index ec848190..6e43f445 100644 --- a/packages/core/src/utils/crypto/node-crypto.ts +++ b/packages/core/src/utils/crypto/node-crypto.ts @@ -9,29 +9,29 @@ export class NodeCryptoProvider extends BaseCryptoProvider { throw new Error('Cannot use Node crypto functions outside NodeJS!') } - createAesCtr(key: Buffer, iv: Buffer): IEncryptionScheme { - return this._createAes(key, iv, 'ctr') + createAesCtr(key: Buffer, iv: Buffer, encrypt: boolean): IEncryptionScheme { + const cipher = nodeCrypto[encrypt ? 'createCipheriv' : 'createDecipheriv'](`aes-${key.length * 8}-ctr`, key, iv) + + const update = (data: Buffer) => cipher.update(data) + + return { + encrypt: update, + decrypt: update, + } } createAesEcb(key: Buffer): IEncryptionScheme { - return this._createAes(key, null, 'ecb') - } + const methodName = `aes-${key.length * 8}-ecb` - private _createAes( - key: Buffer, - iv: Buffer | null, - method: string - ): IEncryptionScheme { - const methodName = `aes-${key.length * 8}-${method}` return { encrypt(data: Buffer) { - const cipher = nodeCrypto.createCipheriv(methodName, key, iv) - if (method === 'ecb') cipher.setAutoPadding(false) + const cipher = nodeCrypto.createCipheriv(methodName, key, null) + cipher.setAutoPadding(false) return Buffer.concat([cipher.update(data), cipher.final()]) }, decrypt(data: Buffer) { - const cipher = nodeCrypto.createDecipheriv(methodName, key, iv) - if (method === 'ecb') cipher.setAutoPadding(false) + const cipher = nodeCrypto.createDecipheriv(methodName, key, null) + cipher.setAutoPadding(false) return Buffer.concat([cipher.update(data), cipher.final()]) }, } diff --git a/packages/core/tests/buffer-utils.spec.ts b/packages/core/tests/buffer-utils.spec.ts index 8556da5f..78cc0a4e 100644 --- a/packages/core/tests/buffer-utils.spec.ts +++ b/packages/core/tests/buffer-utils.spec.ts @@ -4,30 +4,14 @@ import { buffersEqual, cloneBuffer, encodeUrlSafeBase64, - isProbablyPlainText, parseUrlSafeBase64, randomBytes, - reverseBuffer, telegramRleDecode, telegramRleEncode, xorBuffer, xorBufferInPlace, } from '../src/utils/buffer-utils' -describe('reverseBuffer', () => { - it('should reverse even-sized buffers', () => { - const buf = Buffer.from([1, 2, 3, 4]) - reverseBuffer(buf) - expect([...buf]).to.eql([4, 3, 2, 1]) - }) - - it('should reverse odd-sized buffers', () => { - const buf = Buffer.from([1, 2, 3]) - reverseBuffer(buf) - expect([...buf]).to.eql([3, 2, 1]) - }) -}) - describe('buffersEqual', () => { it('should return true for equal buffers', () => { expect(buffersEqual(Buffer.from([]), Buffer.from([]))).is.true @@ -207,60 +191,60 @@ describe('telegramRleDecode', () => { }) }) -describe('isProbablyPlainText', () => { - it('should return true for buffers only containing printable ascii', () => { - expect( - isProbablyPlainText(Buffer.from('hello this is some ascii text')) - ).to.be.true - expect( - isProbablyPlainText( - Buffer.from( - 'hello this is some ascii text\nwith unix new lines' - ) - ) - ).to.be.true - expect( - isProbablyPlainText( - Buffer.from( - 'hello this is some ascii text\r\nwith windows new lines' - ) - ) - ).to.be.true - expect( - isProbablyPlainText( - Buffer.from( - 'hello this is some ascii text\n\twith unix new lines and tabs' - ) - ) - ).to.be.true - expect( - isProbablyPlainText( - Buffer.from( - 'hello this is some ascii text\r\n\twith windows new lines and tabs' - ) - ) - ).to.be.true - }) - - it('should return false for buffers containing some binary data', () => { - expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to - .be.false - expect( - isProbablyPlainText( - Buffer.from('hello this is some ascii text with emojis 🌸') - ) - ).to.be.false - - // random strings of 16 bytes - expect( - isProbablyPlainText( - Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex') - ) - ).to.be.false - expect( - isProbablyPlainText( - Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex') - ) - ).to.be.false - }) -}) +// describe('isProbablyPlainText', () => { +// it('should return true for buffers only containing printable ascii', () => { +// expect( +// isProbablyPlainText(Buffer.from('hello this is some ascii text')) +// ).to.be.true +// expect( +// isProbablyPlainText( +// Buffer.from( +// 'hello this is some ascii text\nwith unix new lines' +// ) +// ) +// ).to.be.true +// expect( +// isProbablyPlainText( +// Buffer.from( +// 'hello this is some ascii text\r\nwith windows new lines' +// ) +// ) +// ).to.be.true +// expect( +// isProbablyPlainText( +// Buffer.from( +// 'hello this is some ascii text\n\twith unix new lines and tabs' +// ) +// ) +// ).to.be.true +// expect( +// isProbablyPlainText( +// Buffer.from( +// 'hello this is some ascii text\r\n\twith windows new lines and tabs' +// ) +// ) +// ).to.be.true +// }) +// +// it('should return false for buffers containing some binary data', () => { +// expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to +// .be.false +// expect( +// isProbablyPlainText( +// Buffer.from('hello this is some ascii text with emojis 🌸') +// ) +// ).to.be.false +// +// // random strings of 16 bytes +// expect( +// isProbablyPlainText( +// Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex') +// ) +// ).to.be.false +// expect( +// isProbablyPlainText( +// Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex') +// ) +// ).to.be.false +// }) +// }) diff --git a/packages/core/tests/crypto-providers.spec.ts b/packages/core/tests/crypto-providers.spec.ts index 1f30e2f5..c6506c8f 100644 --- a/packages/core/tests/crypto-providers.spec.ts +++ b/packages/core/tests/crypto-providers.spec.ts @@ -46,16 +46,31 @@ export function testCryptoProvider(c: ICryptoProvider): void { }) it('should encrypt and decrypt aes-ctr', async () => { - const aes = c.createAesCtr( - Buffer.from('8ddcf593fd74ec251038e459c165461f', 'hex'), - Buffer.from('0fea3601c60e770ac57ffe6b33ca8be1', 'hex') + let aes = c.createAesCtr( + Buffer.from('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b', 'hex'), + Buffer.from('0182de2bd789c295c3c6c875c5e9e190', 'hex'), + true ) - expect((await aes.encrypt(Buffer.from('hello'))).toString('hex')).to.eq( - '4f6d702526' + + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('a5fea1') + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('ab51ca') + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('365e5c') + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('4b94a9') + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('776387') + expect((await aes.encrypt(Buffer.from([1, 2, 3]))).toString('hex')).eq('c940be') + + aes = c.createAesCtr( + Buffer.from('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b', 'hex'), + Buffer.from('0182de2bd789c295c3c6c875c5e9e190', 'hex'), + false ) - expect( - (await aes.decrypt(Buffer.from('4f6d702526', 'hex'))).toString() - ).to.eq('hello') + + expect((await aes.decrypt(Buffer.from('a5fea1', 'hex'))).toString('hex')).eq('010203') + expect((await aes.decrypt(Buffer.from('ab51ca', 'hex'))).toString('hex')).eq('010203') + expect((await aes.decrypt(Buffer.from('365e5c', 'hex'))).toString('hex')).eq('010203') + expect((await aes.decrypt(Buffer.from('4b94a9', 'hex'))).toString('hex')).eq('010203') + expect((await aes.decrypt(Buffer.from('776387', 'hex'))).toString('hex')).eq('010203') + expect((await aes.decrypt(Buffer.from('c940be', 'hex'))).toString('hex')).eq('010203') }) it('should encrypt and decrypt aes-ige', async () => { diff --git a/yarn.lock b/yarn.lock index e2c9c5e0..346e63d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2722,6 +2722,13 @@ dependencies: "@types/node" "*" +"@types/ws@^7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.1.tgz#49eacb15a0534663d53a36fbf5b4d98f5ae9a73a" + integrity sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" @@ -15016,7 +15023,7 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@7.4.4, ws@^7.3.0, ws@~7.4.2: +ws@7.4.4, ws@^7.3.0, ws@^7.4.4, ws@~7.4.2: version "7.4.4" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==