steam-appticket
Version:
Decrypts and parses Steam app tickets
102 lines • 10.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAppTicket = void 0;
const bytebuffer_1 = __importDefault(require("bytebuffer"));
const ipv4_1 = require("@doctormckay/stdlib/ipv4");
const steam_crypto_1 = __importDefault(require("@doctormckay/steam-crypto"));
const steamid_1 = __importDefault(require("steamid"));
/**
* Parse a Steam app or session ticket and return an object containing its details.
* @param {Buffer} ticket - The binary appticket
* @param {boolean} [allowInvalidSignature=false] - If true, won't return null if the ticket has no valid signature
* @returns {AppOwnershipTicket|AppTicket|null} - object if well-formed ticket (may not be valid), or null if not well-formed
*/
function parseAppTicket(ticket, allowInvalidSignature = false) {
// https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl
if (!bytebuffer_1.default.isByteBuffer(ticket)) {
ticket = bytebuffer_1.default.wrap(ticket, bytebuffer_1.default.LITTLE_ENDIAN);
}
let bytebufferTicket = ticket;
// @ts-ignore
let details = {};
try {
let initialLength = bytebufferTicket.readUint32();
if (initialLength == 20) {
// This is a full appticket, with a GC token and session header (in addition to ownership ticket)
details.authTicket = bytebufferTicket.slice(bytebufferTicket.offset - 4, bytebufferTicket.offset - 4 + 52).toBuffer(); // this is the part that's passed back to Steam for validation
details.gcToken = bytebufferTicket.readUint64().toString();
//details.steamID = new SteamID(ticket.readUint64().toString());
bytebufferTicket.skip(8); // the SteamID gets read later on
details.tokenGenerated = new Date(bytebufferTicket.readUint32() * 1000);
if (bytebufferTicket.readUint32() != 24) {
// SESSIONHEADER should be 24 bytes.
return null;
}
bytebufferTicket.skip(8); // unknown 1 and unknown 2
details.sessionExternalIP = (0, ipv4_1.intToString)(bytebufferTicket.readUint32());
bytebufferTicket.skip(4); // filler
details.clientConnectionTime = bytebufferTicket.readUint32(); // time the client has been connected to Steam in ms
details.clientConnectionCount = bytebufferTicket.readUint32(); // how many servers the client has connected to
if (bytebufferTicket.readUint32() + bytebufferTicket.offset != bytebufferTicket.limit) {
// OWNERSHIPSECTIONWITHSIGNATURE sectlength
return null;
}
}
else {
bytebufferTicket.skip(-4);
}
// Start reading the ownership ticket
let ownershipTicketOffset = bytebufferTicket.offset;
let ownershipTicketLength = bytebufferTicket.readUint32(); // including itself, for some reason
if (ownershipTicketOffset + ownershipTicketLength != bytebufferTicket.limit && ownershipTicketOffset + ownershipTicketLength + 128 != bytebufferTicket.limit) {
return null;
}
let i, j, dlc;
details.version = bytebufferTicket.readUint32();
details.steamID = new steamid_1.default(bytebufferTicket.readUint64().toString());
details.appID = bytebufferTicket.readUint32();
details.ownershipTicketExternalIP = (0, ipv4_1.intToString)(bytebufferTicket.readUint32());
details.ownershipTicketInternalIP = (0, ipv4_1.intToString)(bytebufferTicket.readUint32());
details.ownershipFlags = bytebufferTicket.readUint32();
details.ownershipTicketGenerated = new Date(bytebufferTicket.readUint32() * 1000);
details.ownershipTicketExpires = new Date(bytebufferTicket.readUint32() * 1000);
details.licenses = [];
let licenseCount = bytebufferTicket.readUint16();
for (i = 0; i < licenseCount; i++) {
details.licenses.push(bytebufferTicket.readUint32());
}
details.dlc = [];
let dlcCount = bytebufferTicket.readUint16();
for (i = 0; i < dlcCount; i++) {
dlc = {};
dlc.appID = bytebufferTicket.readUint32();
dlc.licenses = [];
licenseCount = bytebufferTicket.readUint16();
for (j = 0; j < licenseCount; j++) {
dlc.licenses.push(bytebufferTicket.readUint32());
}
details.dlc.push(dlc);
}
bytebufferTicket.readUint16(); // reserved
if (bytebufferTicket.offset + 128 == bytebufferTicket.limit) {
// Has signature
details.signature = bytebufferTicket.slice(bytebufferTicket.offset, bytebufferTicket.offset + 128).toBuffer();
}
let date = new Date();
details.isExpired = details.ownershipTicketExpires < date;
details.hasValidSignature = !!details.signature && steam_crypto_1.default.verifySignature(bytebufferTicket.slice(ownershipTicketOffset, ownershipTicketOffset + ownershipTicketLength).toBuffer(), details.signature);
details.isValid = !details.isExpired && (!details.signature || details.hasValidSignature);
if (!details.hasValidSignature && !allowInvalidSignature) {
throw new Error('Missing or invalid signature');
}
}
catch (ex) {
return null; // not a valid ticket
}
return details;
}
exports.parseAppTicket = parseAppTicket;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFyc2VBcHBUaWNrZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tcG9uZW50cy9wYXJzZUFwcFRpY2tldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSw0REFBb0M7QUFDcEMsbURBQXFEO0FBQ3JELDZFQUFvRDtBQUNwRCxzREFBOEI7QUFJOUI7Ozs7O0dBS0c7QUFDSCxTQUFnQixjQUFjLENBQUMsTUFBeUIsRUFBRSx3QkFBaUMsS0FBSztJQUMvRix5RkFBeUY7SUFFekYsSUFBSSxDQUFDLG9CQUFVLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1FBQ3JDLE1BQU0sR0FBRyxvQkFBVSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsb0JBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQztLQUMzRDtJQUVELElBQUksZ0JBQWdCLEdBQUcsTUFBb0IsQ0FBQztJQUU1QyxhQUFhO0lBQ2IsSUFBSSxPQUFPLEdBQWEsRUFBRSxDQUFDO0lBRTNCLElBQUk7UUFDSCxJQUFJLGFBQWEsR0FBRyxnQkFBZ0IsQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNsRCxJQUFJLGFBQWEsSUFBSSxFQUFFLEVBQUU7WUFDeEIsaUdBQWlHO1lBQ2pHLE9BQU8sQ0FBQyxVQUFVLEdBQUcsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLGdCQUFnQixDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsZ0JBQWdCLENBQUMsTUFBTSxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLDhEQUE4RDtZQUVyTCxPQUFPLENBQUMsT0FBTyxHQUFHLGdCQUFnQixDQUFDLFVBQVUsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzNELGdFQUFnRTtZQUNoRSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxpQ0FBaUM7WUFDM0QsT0FBTyxDQUFDLGNBQWMsR0FBRyxJQUFJLElBQUksQ0FBQyxn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