|
'use strict'; |
|
var crypto = require('crypto'); |
|
|
|
const block_size_bits = 128; |
|
const block_size_bytes = block_size_bits / 8; // 16 bytes |
|
|
|
// This is the reversal (decode) of the gorilla toolkit securecookie encode. |
|
// https://github.com/gorilla/securecookie/blob/master/securecookie.go |
|
class Decoder { |
|
constructor(cookieName, hashKey, blockKey) { |
|
this.cookieName = cookieName; |
|
this.hashKey = hashKey; |
|
this.blockKey = blockKey; |
|
} |
|
|
|
decodeToJson(cookieContent) { |
|
var decoded = this.decode(cookieContent); |
|
return JSON.parse(decoded); |
|
} |
|
|
|
decode(cookieContent) { |
|
// Step 1: Decode base64 |
|
var decodedContent = new Buffer(cookieContent, 'base64'); |
|
|
|
var parts = this.splitParts(decodedContent); |
|
var date = parts[0]; |
|
var encryptedValue = parts[1]; |
|
var mac = parts[2] |
|
|
|
// Step 2: Verify MAC. Decoded content is "date|value|MAC", but MAC content is "name|date|value" |
|
if(!this.verifyMac(date, encryptedValue, mac)) { |
|
throw "Cookie HMAC verification failed"; |
|
} |
|
|
|
// Step 3: Decrypt the cookie and return |
|
var decryptedCookie = this.decryptContent(encryptedValue); |
|
return decryptedCookie; |
|
} |
|
|
|
// We avoid using String.slice because we want to keep the original Buffer unmodified throughout |
|
// the entire verify/decrypt process. It would be nice to just slice along the "|", but it's so much |
|
// easier to just work with the untranslated Buffer objects in future crypto functions |
|
splitParts(decodedContent) { |
|
var start = 0; |
|
var end = decodedContent.indexOf("|"); |
|
|
|
if(end == -1) { |
|
throw "Invalid cookie format, cannot find \"date|value|MAC\" structure"; |
|
} |
|
|
|
var date = decodedContent.slice(start, end); |
|
|
|
start = end + 1; |
|
end = decodedContent.indexOf("|", start); |
|
|
|
if(end == -1) { |
|
throw "Invalid cookie format, cannot find \"date|value|MAC\" structure"; |
|
} |
|
|
|
var value = decodedContent.slice(start, end); |
|
var mac = decodedContent.slice(end + 1); |
|
|
|
return [date, value, mac]; |
|
} |
|
|
|
verifyMac(date, encryptedValue, receivedMac) { |
|
var macContent = new Buffer(this.cookieName + "|" + date + "|" + encryptedValue, 'utf8'); |
|
var computedMac = crypto.createHmac('sha256', this.hashKey).update(macContent).digest(); |
|
|
|
return receivedMac.equals(computedMac); |
|
} |
|
|
|
|
|
decryptContent(encryptedValue) { |
|
// Be very careful here. If you get any of this encoding conversion wrong, nothing will decrypt correctly. |
|
var b = new Buffer(encryptedValue.toString('utf8'), 'base64'); |
|
|
|
var iv = b.slice(0, block_size_bytes); |
|
var cipherText = b.slice(block_size_bytes); |
|
|
|
// !!! WARNING !!! - The AES-256-CTR cipher might not be part of your OpenSSL library. It's fairly new, so make sure |
|
// you have it installed! |
|
var decipher = crypto.createDecipheriv('aes-256-ctr', this.blockKey, iv); |
|
var clearText = decipher.update(cipherText); |
|
clearText += decipher.final(); |
|
|
|
return clearText.toString('utf8'); |
|
} |
|
} |
|
|
|
exports.Decoder = Decoder; |
|
|
|
// Called like so: |
|
var decoder = new Decoder('my-cookie-name', 'hashKey', 'blockKey'); |
|
var cookieContents = decoder.decode("the-base64-encoded-text-of-the-cookie"); |