160 lines
6.6 KiB
TypeScript
160 lines
6.6 KiB
TypeScript
import Long from 'long'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { defaultPlatform, defaultTestCryptoProvider } from '@mtcute/test'
|
|
import type { TlBinaryReader, TlReaderMap } from '@mtcute/tl-runtime'
|
|
import { hex, utf8 } from '@fuman/utils'
|
|
|
|
import { LogManager } from '../utils/index.js'
|
|
|
|
import { AuthKey } from './auth-key.js'
|
|
|
|
const authKey = new Uint8Array(256)
|
|
|
|
for (let i = 0; i < 256; i += 32) {
|
|
authKey.subarray(i, i + 32).set(hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0'))
|
|
}
|
|
|
|
describe('AuthKey', () => {
|
|
async function create() {
|
|
const logger = new LogManager(undefined, defaultPlatform)
|
|
const readerMap: TlReaderMap = {}
|
|
const crypto = await defaultTestCryptoProvider()
|
|
|
|
const key = new AuthKey(crypto, logger, readerMap)
|
|
key.setup(authKey)
|
|
|
|
return key
|
|
}
|
|
|
|
const msgId = Long.fromBits(0xBEEF1234, 0x1234BEEF, true)
|
|
const seqNo = 777
|
|
const serverSalt = Long.fromBits(0xDEADBEEF, 0xBEEFDEAD)
|
|
const sessionId = Long.fromBits(0xFEEDBEEF, 0xBEEFFEED)
|
|
|
|
function writeMessage(body: Uint8Array) {
|
|
const buf = new Uint8Array(16 + body.length)
|
|
const dv = new DataView(buf.buffer)
|
|
|
|
dv.setInt32(0, msgId.low, true)
|
|
dv.setInt32(4, msgId.high, true)
|
|
dv.setInt32(8, seqNo, true)
|
|
dv.setInt32(12, body.length, true)
|
|
buf.set(body, 16)
|
|
|
|
return buf
|
|
}
|
|
|
|
it('should calculate derivatives', async () => {
|
|
const key = await create()
|
|
|
|
expect(hex.encode(key.key)).toEqual(hex.encode(authKey))
|
|
expect(hex.encode(key.clientSalt)).toEqual('f73c3622dec230e098cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4b')
|
|
expect(hex.encode(key.serverSalt)).toEqual('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
|
expect(hex.encode(key.id)).toEqual('40fa5bb7cb56a895')
|
|
})
|
|
|
|
it('should encrypt a message', async () => {
|
|
const message = writeMessage(utf8.encoder.encode('hello, world!!!!'))
|
|
|
|
const key = await create()
|
|
const msg = key.encryptMessage(message, serverSalt, sessionId)
|
|
|
|
expect(hex.encode(msg)).toEqual(
|
|
'40fa5bb7cb56a895f6f5a88914892aadf87c68031cc953ba29d68e118021f329'
|
|
+ 'be386a620d49f3ad3a50c60dcef3733f214e8cefa3e403c11d193637d4971dc1'
|
|
+ '5db7f74b26fd16cb0e8fee30bf7e3f68858fe82927e2cd06',
|
|
)
|
|
})
|
|
|
|
describe('decrypt', () => {
|
|
async function decrypt(message: Uint8Array) {
|
|
const key = await create()
|
|
|
|
return new Promise<[Long, number, TlBinaryReader]>((resolve, reject) => {
|
|
// in this method, errors are not thrown but rather logged
|
|
vi.spyOn(key.log, 'warn').mockImplementation((msg, ...fmt) =>
|
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
reject(`${msg} : ${fmt.map(it => JSON.stringify(it)).join(' ')}`),
|
|
)
|
|
|
|
key.decryptMessage(message, sessionId, (...args) => resolve(args))
|
|
})
|
|
}
|
|
|
|
it('should decrypt a message', async () => {
|
|
const message = hex.decode(
|
|
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb'
|
|
+ '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b'
|
|
+ '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
|
)
|
|
|
|
const [decMsgId, decSeqNo, data] = await decrypt(message)
|
|
|
|
expect(decMsgId).toEqual(msgId)
|
|
expect(decSeqNo).toEqual(seqNo)
|
|
expect(utf8.decoder.decode(data.raw(16))).toEqual('hello, world!!!!')
|
|
})
|
|
|
|
it('should decrypt a message with padding', async () => {
|
|
const message = hex.decode(
|
|
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb'
|
|
+ '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b'
|
|
+ '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35'
|
|
+ 'deadbeef', // some padding (e.g. from padded transport),
|
|
)
|
|
|
|
const [decMsgId, decSeqNo, data] = await decrypt(message)
|
|
|
|
expect(decMsgId).toEqual(msgId)
|
|
expect(decSeqNo).toEqual(seqNo)
|
|
expect(utf8.decoder.decode(data.raw(16))).toEqual('hello, world!!!!')
|
|
})
|
|
|
|
it('should ignore messages with invalid message key', async () => {
|
|
const message = hex.decode(
|
|
'40fa5bb7cb56a8950000000000000000000000000000000050a714ce6d2d1bdb'
|
|
+ '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b'
|
|
+ '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
|
)
|
|
|
|
await expect(() => decrypt(message)).rejects.toThrow('message with invalid messageKey')
|
|
})
|
|
|
|
it('should ignore messages with invalid session_id', async () => {
|
|
const message = hex.decode(
|
|
'40fa5bb7cb56a895a986a7e97f4e90aa2769b5e702c6e86f5e1e82c6ff0c6829'
|
|
+ '2521a2ba9704fa37fb341d895cf32662c6cf47ba31cbf27c30d5c03f6c2930f4'
|
|
+ '30fd8858b836b73fe32d4a95b8ebcdbc9ca8908f7964c40a',
|
|
)
|
|
|
|
await expect(() => decrypt(message)).rejects.toThrow('message with invalid sessionId')
|
|
})
|
|
|
|
it('should ignore messages with invalid length', async () => {
|
|
const messageTooLong = hex.decode(
|
|
'40fa5bb7cb56a8950d19412233dd5d24be697c73274e08fbe515cf65e0c5f70c'
|
|
+ 'ad75fd2badc18c9f999f287351144eeb1cfcaa9bea33ef5058999ad96a498306'
|
|
+ '08d2859425685a55b21fab413bfabc42ec5da283853b28c0',
|
|
)
|
|
const messageUnaligned = hex.decode(
|
|
'40fa5bb7cb56a8957b4e4bec561eee4a5a1025bc8a35d3d0c79a3685d2b90ff0'
|
|
+ '5f638e9c42c9fd9448b0ce8e7d49e7ea1ce458e47b825b5c7fd8ddf5b4fded46'
|
|
+ '2a4bcc02f3ff2e89de6764d6d219f575e457fdcf8c163cdf',
|
|
)
|
|
|
|
await expect(() => decrypt(messageTooLong)).rejects.toThrow('message with invalid length: %d > %d')
|
|
await expect(() => decrypt(messageUnaligned)).rejects.toThrow(
|
|
'message with invalid length: %d is not a multiple of 4',
|
|
)
|
|
})
|
|
|
|
it('should ignore messages with invalid padding', async () => {
|
|
const message = hex.decode(
|
|
'40fa5bb7cb56a895133671d1c637a9836e2c64b4d1a0521d8a25a6416fd4dc9e'
|
|
+ '79f9478fb837703cc9efa0a19d12143c2a26e57cb4bc64d7bc972dd8f19c53c590cc258162f44afc',
|
|
)
|
|
|
|
await expect(() => decrypt(message)).rejects.toThrow('message with invalid padding size')
|
|
})
|
|
})
|
|
})
|