@tgsnake/core
Version:
Pure Telegram MTProto library for nodejs
124 lines (123 loc) • 5.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.kdf = kdf;
exports.pack = pack;
exports.unpack = unpack;
const platform_node_js_1 = require("../platform.node.js");
const index_js_1 = require("../errors/index.js");
const index_js_2 = require("../raw/index.js");
const MsgId_js_1 = require("../session/internals/MsgId.js");
const helpers_js_1 = require("../helpers.js");
const Aes_js_1 = require("./Aes.js");
const Logger_js_1 = require("../Logger.js");
const STORED_MSG_IDS_MAX_SIZE = 1000 * 2;
function sha256(data) {
const hash = platform_node_js_1.crypto.createHash('sha256');
hash.update(data);
return hash.digest();
}
function toBytes(value) {
const bytesArray = [];
for (let i = 0; i < 8; i++) {
let shift = value >> BigInt(8 * i);
shift &= BigInt(255);
bytesArray[i] = Number(String(shift));
}
return platform_node_js_1.Buffer.from(bytesArray);
}
function kdf(authKey, msgKey, outgoing) {
const x = outgoing ? 0 : 8;
const sha256A = sha256(platform_node_js_1.Buffer.concat([
msgKey,
authKey.subarray(x, x + 36),
]));
const sha256B = sha256(platform_node_js_1.Buffer.concat([
authKey.subarray(x + 40, x + 76),
msgKey,
]));
const aesKey = platform_node_js_1.Buffer.concat([
sha256A.subarray(0, 8),
sha256B.subarray(8, 24),
sha256A.subarray(24, 32),
]);
const aesIv = platform_node_js_1.Buffer.concat([
sha256B.subarray(0, 8),
sha256A.subarray(8, 24),
sha256B.subarray(24, 32),
]);
return [aesKey, aesIv];
}
function pack(message, salt, sessionId, authKey, authKeyId) {
const data = platform_node_js_1.Buffer.concat([
platform_node_js_1.Buffer.concat([
toBytes(salt),
sessionId,
]),
message.write(),
]);
const padding = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes((0, helpers_js_1.mod)(-(platform_node_js_1.Buffer.byteLength(data) + 12), 16) + 12));
const msgKeyLarge = sha256(platform_node_js_1.Buffer.concat([
authKey.subarray(88, 88 + 32),
data,
padding,
]));
const msgKey = msgKeyLarge.subarray(8, 24);
const [aesKey, aesIv] = kdf(authKey, msgKey, true);
return platform_node_js_1.Buffer.concat([
authKeyId,
msgKey,
(0, Aes_js_1.ige256Encrypt)(platform_node_js_1.Buffer.concat([data, padding]), aesKey, aesIv),
]);
}
async function unpack(b, sessionId, authKey, authKeyId, storedMsgId) {
index_js_1.SecurityCheckMismatch.check(b.read(8).equals(authKeyId), 'Provided auth key id is not equal with expected one.');
const msgKey = b.read(16);
const [aesKey, aesIv] = kdf(authKey, msgKey, false);
const encrypted = b.read();
const decrypted = (0, Aes_js_1.ige256Decrypt)(encrypted, aesKey, aesIv);
const hash = sha256(platform_node_js_1.Buffer.concat([
authKey.subarray(96, 96 + 32),
decrypted,
]));
index_js_1.SecurityCheckMismatch.check(msgKey.equals(hash.subarray(8, 24)), 'Provided msg key is not equal with expected one');
const data = new index_js_2.BytesIO(decrypted);
data.read(8);
index_js_1.SecurityCheckMismatch.check(platform_node_js_1.Buffer.from(data.read(8)).equals(sessionId), 'Provided session id is not equal with expected one.');
const message = await index_js_2.Message.read(new index_js_2.BytesIO(data.buffer.slice(16))).catch((error) => {
Logger_js_1.Logger.error(error);
});
data.seek(32);
const payload = data.read();
const padding = payload.subarray(message.length);
index_js_1.SecurityCheckMismatch.check(platform_node_js_1.Buffer.byteLength(padding) >= 12 && platform_node_js_1.Buffer.byteLength(padding) <= 1024, 'Payload padding is lower than 12 or bigger than 1024');
index_js_1.SecurityCheckMismatch.check((0, helpers_js_1.mod)(platform_node_js_1.Buffer.byteLength(padding), 4) === 0, 'Mod of padding length with 4 is equal with zero');
index_js_1.SecurityCheckMismatch.check((0, helpers_js_1.bigIntMod)(message.msgId, BigInt(2)) !== BigInt(0), 'Mod of msgId with 2 is not equal with zero');
if (storedMsgId.length > STORED_MSG_IDS_MAX_SIZE) {
storedMsgId.splice(0, Math.floor(STORED_MSG_IDS_MAX_SIZE / 2));
}
if (storedMsgId.length) {
if (message.msgId < storedMsgId[0]) {
throw new index_js_1.SecurityCheckMismatch('Msg id is lower than all of the stored values');
}
if (storedMsgId.includes(message.msgId)) {
throw new index_js_1.SecurityCheckMismatch('Msg id is equal to any of the stored values');
}
const msgId = new MsgId_js_1.MsgId();
const timeDiff = BigInt(message.msgId - msgId.getMsgId()) / BigInt(2 ** 32);
if (timeDiff > BigInt(30)) {
throw new index_js_1.SecurityCheckMismatch('Msg id belongs over 30 seconds in the future');
}
if (timeDiff < BigInt(-300)) {
throw new index_js_1.SecurityCheckMismatch('Msg id belongs over 300 seconds in the past');
}
}
storedMsgId.push(message.msgId);
storedMsgId.sort((a, b) => {
if (a > b)
return 1;
if (a < b)
return -1;
return 0;
});
return message;
}