2023-06-10 00:37:26 +03:00
|
|
|
import Long from 'long'
|
|
|
|
|
|
|
|
import { tl } from '@mtcute/tl'
|
2022-11-06 02:27:46 +03:00
|
|
|
import { TlBinaryReader, TlReaderMap } from '@mtcute/tl-runtime'
|
2023-06-10 00:37:26 +03:00
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
import { MtcuteError } from '../types/errors.js'
|
|
|
|
import { createAesIgeForMessage } from '../utils/crypto/mtproto.js'
|
2023-11-12 00:36:00 +03:00
|
|
|
import { buffersEqual, concatBuffers, dataViewFromBuffer, ICryptoProvider, Logger } from '../utils/index.js'
|
2022-11-06 02:27:46 +03:00
|
|
|
|
|
|
|
export class AuthKey {
|
|
|
|
ready = false
|
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
key!: Uint8Array
|
|
|
|
id!: Uint8Array
|
|
|
|
clientSalt!: Uint8Array
|
|
|
|
serverSalt!: Uint8Array
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-10-06 21:15:52 +03:00
|
|
|
constructor(
|
|
|
|
readonly _crypto: ICryptoProvider,
|
|
|
|
readonly log: Logger,
|
|
|
|
readonly _readerMap: TlReaderMap,
|
|
|
|
) {}
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
match(keyId: Uint8Array): boolean {
|
2022-11-06 02:27:46 +03:00
|
|
|
return this.ready && buffersEqual(keyId, this.id)
|
|
|
|
}
|
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
setup(authKey?: Uint8Array | null): void {
|
2022-11-06 02:27:46 +03:00
|
|
|
if (!authKey) return this.reset()
|
|
|
|
|
|
|
|
this.ready = true
|
|
|
|
this.key = authKey
|
2023-10-16 19:23:53 +03:00
|
|
|
this.clientSalt = authKey.subarray(88, 120)
|
|
|
|
this.serverSalt = authKey.subarray(96, 128)
|
2023-11-07 22:49:35 +03:00
|
|
|
this.id = this._crypto.sha1(authKey).subarray(-8)
|
2022-11-07 00:08:59 +03:00
|
|
|
|
|
|
|
this.log.verbose('auth key set up, id = %h', this.id)
|
2022-11-06 02:27:46 +03:00
|
|
|
}
|
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
encryptMessage(message: Uint8Array, serverSalt: Long, sessionId: Long): Uint8Array {
|
2023-09-22 15:32:28 +03:00
|
|
|
if (!this.ready) throw new MtcuteError('Keys are not set up!')
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-09-24 01:32:22 +03:00
|
|
|
let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16
|
2022-11-06 02:27:46 +03:00
|
|
|
padding = 12 + (padding ? 16 - padding : 0)
|
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
const buf = new Uint8Array(16 + message.length + padding)
|
|
|
|
const dv = dataViewFromBuffer(buf)
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
dv.setInt32(0, serverSalt.low, true)
|
|
|
|
dv.setInt32(4, serverSalt.high, true)
|
|
|
|
dv.setInt32(8, sessionId.low, true)
|
|
|
|
dv.setInt32(12, sessionId.high, true)
|
|
|
|
buf.set(message, 16)
|
2023-11-12 00:36:00 +03:00
|
|
|
this._crypto.randomFill(buf.subarray(16 + message.length, 16 + message.length + padding))
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
const messageKey = this._crypto.sha256(concatBuffers([this.clientSalt, buf])).subarray(8, 24)
|
|
|
|
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true)
|
2023-11-04 06:44:18 +03:00
|
|
|
const encryptedData = ige.encrypt(buf)
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-10-16 19:23:53 +03:00
|
|
|
return concatBuffers([this.id, messageKey, encryptedData])
|
2022-11-06 02:27:46 +03:00
|
|
|
}
|
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
decryptMessage(
|
2023-10-16 19:23:53 +03:00
|
|
|
data: Uint8Array,
|
2022-11-06 02:27:46 +03:00
|
|
|
sessionId: Long,
|
2023-06-10 00:37:26 +03:00
|
|
|
callback: (msgId: tl.Long, seqNo: number, data: TlBinaryReader) => void,
|
2023-11-07 22:49:35 +03:00
|
|
|
): void {
|
2023-10-16 19:23:53 +03:00
|
|
|
const messageKey = data.subarray(8, 24)
|
|
|
|
let encryptedData = data.subarray(24)
|
|
|
|
|
|
|
|
const mod16 = encryptedData.byteLength % 16
|
|
|
|
|
|
|
|
if (mod16 !== 0) {
|
|
|
|
// strip padding in case of padded transport.
|
|
|
|
// i wish this could be done at transport level, but we can't properly align anything there
|
|
|
|
// because padding size is not known, and transport level should not be aware of MTProto structure
|
|
|
|
encryptedData = encryptedData.subarray(0, encryptedData.byteLength - mod16)
|
|
|
|
}
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false)
|
2023-11-04 06:44:18 +03:00
|
|
|
const innerData = ige.decrypt(encryptedData)
|
2022-11-06 02:27:46 +03:00
|
|
|
|
2023-11-07 22:49:35 +03:00
|
|
|
const msgKeySource = this._crypto.sha256(concatBuffers([this.serverSalt, innerData]))
|
2023-10-16 19:23:53 +03:00
|
|
|
const expectedMessageKey = msgKeySource.subarray(8, 24)
|
2022-11-06 02:27:46 +03:00
|
|
|
|
|
|
|
if (!buffersEqual(messageKey, expectedMessageKey)) {
|
2023-11-12 00:36:00 +03:00
|
|
|
this.log.warn('received message with invalid messageKey = %h (expected %h)', messageKey, expectedMessageKey)
|
2023-06-10 00:37:26 +03:00
|
|
|
|
2022-11-06 02:27:46 +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
|
|
|
|
2022-11-06 02:27:46 +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
|
|
|
|
2022-11-06 02:27:46 +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
|
|
|
|
2022-11-06 02:27:46 +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
|
|
|
|
2022-11-06 02:27:46 +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
|
|
|
|
}
|
|
|
|
}
|