|
'use strict'; |
|
|
|
const crypto = require('crypto'); |
|
const baseTicks = 621355968000000000; // ticks from 0001 to 1970 year |
|
|
|
class NetCookieService { |
|
/** |
|
* @param {String} decryptionKey - key for encrypt/decrypt .NET auth cookie |
|
* @param {String} validationKey - validation key for encrypt/decrypt .NET auth cookie |
|
*/ |
|
constructor(decryptionKey, validationKey) { |
|
this.decryptionKey = decryptionKey; |
|
this.validationKey = validationKey; |
|
} |
|
|
|
/** |
|
* Middleware for expressJs |
|
* |
|
* @returns {Function} |
|
*/ |
|
middleware() { |
|
const self = this; |
|
return (req, res, next) => { |
|
const cookieValue = req.cookies['.ncauth']; |
|
if (cookieValue) { |
|
try { |
|
const ticket = self.decode(cookieValue); |
|
if (ticket.is_persistent || Date.now() <= ticket.utc_expire_date) { |
|
req.user = ticket; |
|
} |
|
} catch (e) {} |
|
} |
|
return next(); |
|
}; |
|
} |
|
|
|
/** |
|
* Decode .NET cookie value |
|
* |
|
* @param cookie |
|
* @returns {{ |
|
* utc_expire_date: Number, |
|
* is_persistent: Boolean, |
|
* username: String, |
|
* user_id: Number, |
|
* session_id: String, |
|
* tfa_status: String, |
|
* admin_user_name: String, |
|
* user_hash: String, |
|
* user_agent_hash: String |
|
* }} |
|
*/ |
|
decode(cookie) { |
|
cookie = new Buffer(cookie, 'hex'); |
|
cookie = cookie.slice(0, cookie.length - 20); |
|
|
|
const decipher = crypto.createDecipheriv('aes-192-cbc', new Buffer(this.decryptionKey, 'hex'), new Buffer(16)); |
|
let decoded = new Buffer(decipher.update(cookie, 'binary', 'binary') + decipher.final(), 'binary').slice(24); |
|
|
|
let utcExpireTicks = parseInt(this.__reverseHexString(decoded.slice(11, 19).toString('hex')), 16); |
|
// @see: https://stackoverflow.com/questions/7966559/how-to-convert-javascript-date-object-to-ticks |
|
utcExpireTicks = Math.floor((utcExpireTicks - baseTicks) / 10000); |
|
const isPersistent = parseInt(decoded.slice(19, 20).toString('hex'), 16); |
|
|
|
decoded = decoded.toString('utf8').replace(/\0/g, ''); |
|
decoded = decoded.substr(0, decoded.lastIndexOf('|#]')); |
|
decoded = decoded.substr(decoded.indexOf('U:'), decoded.length).split('|#]'); |
|
|
|
return { |
|
utc_expire_date: utcExpireTicks, |
|
is_persistent: !!isPersistent, |
|
username: this.__parseCookieField(decoded[0]), |
|
user_id: parseInt(this.__parseCookieField(decoded[1]), 10), |
|
session_id: this.__parseCookieField(decoded[2]), |
|
tfa_status: this.__parseCookieField(decoded[3]), |
|
admin_user_name: this.__parseCookieField(decoded[4]), |
|
user_hash: this.__parseCookieField(decoded[5]), |
|
user_agent_hash: this.__parseCookieField(decoded[6]) |
|
}; |
|
} |
|
|
|
/** |
|
* Encode .NET cookie |
|
* |
|
* @param {{ |
|
* username: String, |
|
* user_id: Number, |
|
* session_id: String, |
|
* tfa_status: String, |
|
* admin_user_name: String, |
|
* user_hash: String, |
|
* user_agent_hash: String |
|
* }} userInfo |
|
* @param {Boolean} [isPersistent=false] |
|
* |
|
* @returns {String} |
|
*/ |
|
encode(userInfo, isPersistent) { |
|
const data = [ |
|
'U:' + userInfo.username, |
|
'I:' + userInfo.user_id, |
|
'S:' + userInfo.session_id, |
|
'2:' + userInfo.tfa_status, |
|
'A:' + userInfo.admin_user_name, |
|
'H:' + userInfo.user_hash, |
|
'B:' + userInfo.user_agent_hash |
|
].join('|#]') + '|#]'; |
|
|
|
const cipher = crypto.createCipheriv('aes-192-cbc', new Buffer(this.decryptionKey, 'hex'), new Buffer(16)); |
|
const ticket = new Buffer( |
|
['0x01', '0x02'] |
|
.concat(this.__generateNanoSecondsHexList(Date.now())) |
|
.concat(['0xfe']) |
|
.concat(this.__generateNanoSecondsHexList(Date.now() + 7200000)) // add 2 hours to expiration |
|
.concat(['0x0' + (isPersistent ? 1 : 0)]) |
|
); |
|
|
|
const encoded = cipher.update('0'.repeat(24) + ticket.toString('binary') + data + '__some_salt_string__', 'binary', 'hex') + cipher.final('hex'); |
|
return encoded + crypto.createHmac('sha1', this.validationKey).update(encoded).digest('hex'); |
|
} |
|
|
|
/** |
|
* Return reversed hex string |
|
* |
|
* @param {String} sourceString |
|
* @returns {String} |
|
* @private |
|
*/ |
|
__reverseHexString(sourceString) { |
|
return sourceString.match(/.{2}/g).reverse().join(''); |
|
} |
|
|
|
/** |
|
* Generate 100 nanoseconds from milliseconds |
|
* |
|
* @param {Number} milliseconds |
|
* @returns {Array} |
|
* @private |
|
*/ |
|
__generateNanoSecondsHexList(milliseconds) { |
|
// prepend to hex string '0' if positive or '1' if negative |
|
let hex = '0' + (milliseconds * 10000 + baseTicks).toString(16); |
|
return this.__reverseHexString(hex).match(/.{2}/g).map(chunk => '0x' + chunk); |
|
} |
|
|
|
/** |
|
* @param {String} field |
|
* @returns {String} |
|
* @private |
|
*/ |
|
__parseCookieField(field) { |
|
return field.replace(/^.+:/, ''); |
|
} |
|
} |
|
|
|
module.exports = NetCookieService; |
Hi, I ran into your code, and I am trying to use it to decrypt asp.net 4.5 auth cookie. I am using aes-256-cbc and with node 8.11.1. But I can't get it work, any suggestion? thank you