mtcute/packages/core/src/network/auth-key.ts

130 lines
4.1 KiB
TypeScript
Raw Normal View History

2023-06-10 00:37:26 +03:00
import Long from 'long'
import { tl } from '@mtcute/tl'
import { TlBinaryReader, TlReaderMap } from '@mtcute/tl-runtime'
2023-06-10 00:37:26 +03:00
2023-09-22 15:32:28 +03:00
import { MtcuteError } from '../types'
import { buffersEqual, ICryptoProvider, Logger, randomBytes } from '../utils'
import { createAesIgeForMessage } from '../utils/crypto/mtproto'
export class AuthKey {
ready = false
key!: Buffer
id!: Buffer
clientSalt!: Buffer
serverSalt!: Buffer
2023-09-24 01:32:22 +03:00
constructor(readonly _crypto: ICryptoProvider, readonly log: Logger, readonly _readerMap: TlReaderMap) {}
match(keyId: Buffer): boolean {
return this.ready && buffersEqual(keyId, this.id)
}
async setup(authKey?: Buffer | null): Promise<void> {
if (!authKey) return this.reset()
this.ready = true
this.key = authKey
this.clientSalt = authKey.slice(88, 120)
this.serverSalt = authKey.slice(96, 128)
this.id = (await this._crypto.sha1(authKey)).slice(-8)
2022-11-07 00:08:59 +03:00
this.log.verbose('auth key set up, id = %h', this.id)
}
2023-09-24 01:32:22 +03:00
async encryptMessage(message: Buffer, serverSalt: Long, sessionId: Long): Promise<Buffer> {
2023-09-22 15:32:28 +03:00
if (!this.ready) throw new MtcuteError('Keys are not set up!')
2023-09-24 01:32:22 +03:00
let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16
padding = 12 + (padding ? 16 - padding : 0)
const buf = Buffer.alloc(16 + message.length + padding)
buf.writeInt32LE(serverSalt.low)
buf.writeInt32LE(serverSalt.high, 4)
buf.writeInt32LE(sessionId.low, 8)
buf.writeInt32LE(sessionId.high, 12)
message.copy(buf, 16)
randomBytes(padding).copy(buf, 16 + message.length)
2023-09-24 01:32:22 +03:00
const messageKey = (await this._crypto.sha256(Buffer.concat([this.clientSalt, buf]))).slice(8, 24)
const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, true)
const encryptedData = await ige.encrypt(buf)
return Buffer.concat([this.id, messageKey, encryptedData])
}
async decryptMessage(
data: Buffer,
sessionId: Long,
2023-06-10 00:37:26 +03:00
callback: (msgId: tl.Long, seqNo: number, data: TlBinaryReader) => void,
): Promise<void> {
const messageKey = data.slice(8, 24)
const encryptedData = data.slice(24)
2023-09-24 01:32:22 +03:00
const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, false)
const innerData = await ige.decrypt(encryptedData)
2023-09-24 01:32:22 +03:00
const expectedMessageKey = (await this._crypto.sha256(Buffer.concat([this.serverSalt, innerData]))).slice(8, 24)
if (!buffersEqual(messageKey, expectedMessageKey)) {
this.log.warn(
'[%h] received message with invalid messageKey = %h (expected %h)',
messageKey,
2023-06-10 00:37:26 +03:00
expectedMessageKey,
)
2023-06-10 00:37:26 +03:00
return
}
const innerReader = new TlBinaryReader(this._readerMap, innerData)
innerReader.seek(8) // skip salt
const sessionId_ = innerReader.long()
const messageId = innerReader.long(true)
if (sessionId_.neq(sessionId)) {
2023-09-24 01:32:22 +03:00
this.log.warn('ignoring message with invalid sessionId = %h', sessionId_)
2023-06-10 00:37:26 +03:00
return
}
const seqNo = innerReader.uint()
const length = innerReader.uint()
if (length > innerData.length - 32 /* header size */) {
2023-09-24 01:32:22 +03:00
this.log.warn('ignoring message with invalid length: %d > %d', length, innerData.length - 32)
2023-06-10 00:37:26 +03:00
return
}
if (length % 4 !== 0) {
2023-09-24 01:32:22 +03:00
this.log.warn('ignoring message with invalid length: %d is not a multiple of 4', length)
2023-06-10 00:37:26 +03:00
return
}
const paddingSize = innerData.length - length - 32 // header size
if (paddingSize < 12 || paddingSize > 1024) {
2023-09-24 01:32:22 +03:00
this.log.warn('ignoring message with invalid padding size: %d', paddingSize)
2023-06-10 00:37:26 +03:00
return
}
callback(messageId, seqNo, innerReader)
}
copyFrom(authKey: AuthKey): void {
this.ready = authKey.ready
this.key = authKey.key
this.id = authKey.id
this.serverSalt = authKey.serverSalt
this.clientSalt = authKey.clientSalt
}
reset(): void {
this.ready = false
}
}