Added token checksum to non-access/refresh tokens

This commit is contained in:
Jonathan Barrow 2023-05-01 15:02:04 -04:00
parent a4bc671957
commit d269258b61
No known key found for this signature in database
GPG Key ID: E86E9FE9049C741F
4 changed files with 91 additions and 14 deletions

52
package-lock.json generated
View File

@ -11,8 +11,10 @@
"dependencies": {
"aws-sdk": "^2.978.0",
"bcrypt": "^5.0.0",
"buffer-crc32": "^0.2.13",
"colors": "^1.4.0",
"cors": "^2.8.5",
"crc": "^4.3.2",
"dicer": "^0.2.5",
"dotenv": "^16.0.3",
"email-validator": "^2.0.4",
@ -44,6 +46,7 @@
"devDependencies": {
"@hcaptcha/types": "^1.0.3",
"@types/bcrypt": "^5.0.0",
"@types/buffer-crc32": "^0.2.2",
"@types/cors": "^2.8.13",
"@types/dicer": "^0.2.2",
"@types/express": "^4.17.17",
@ -547,6 +550,15 @@
"@types/node": "*"
}
},
"node_modules/@types/buffer-crc32": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@types/buffer-crc32/-/buffer-crc32-0.2.2.tgz",
"integrity": "sha512-UpJyUKgG33LVehYtv9k2x4HUEY5ThV62YNGFBbQNBgtoky/0tQCceh8BPI9r3XL5hQ1tGmq34jGWNRBKf2P1UQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@ -1209,6 +1221,16 @@
"node": ">= 10.0.0"
}
},
"node_modules/aws-sdk/node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -1372,14 +1394,12 @@
"node": ">=14.20.1"
}
},
"node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"engines": {
"node": "*"
}
},
"node_modules/buffer-equal": {
@ -1670,6 +1690,22 @@
"node": ">= 0.10"
}
},
"node_modules/crc": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz",
"integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==",
"engines": {
"node": ">=12"
},
"peerDependencies": {
"buffer": ">=6.0.3"
},
"peerDependenciesMeta": {
"buffer": {
"optional": true
}
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",

View File

@ -27,8 +27,10 @@
"dependencies": {
"aws-sdk": "^2.978.0",
"bcrypt": "^5.0.0",
"buffer-crc32": "^0.2.13",
"colors": "^1.4.0",
"cors": "^2.8.5",
"crc": "^4.3.2",
"dicer": "^0.2.5",
"dotenv": "^16.0.3",
"email-validator": "^2.0.4",
@ -60,6 +62,7 @@
"devDependencies": {
"@hcaptcha/types": "^1.0.3",
"@types/bcrypt": "^5.0.0",
"@types/buffer-crc32": "^0.2.2",
"@types/cors": "^2.8.13",
"@types/dicer": "^0.2.2",
"@types/express": "^4.17.17",

View File

@ -10,10 +10,14 @@ async function APIMiddleware(request: express.Request, _response: express.Respon
return next();
}
const token: string = authHeader.split(' ')[1];
const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token);
try {
const token: string = authHeader.split(' ')[1];
const pnid: HydratedPNIDDocument | null = await getPNIDByBearerAuth(token);
request.pnid = pnid;
request.pnid = pnid;
} catch (error) {
// TODO - Log error
}
return next();
}

View File

@ -5,6 +5,8 @@ import fs from 'fs-extra';
import express from 'express';
import mongoose from 'mongoose';
import { ParsedQs } from 'qs';
import crc32 from 'buffer-crc32';
import crc from 'crc';
import { sendMail } from '@/mailer';
import { config, disabledFeatures } from '@/config-manager';
import { TokenOptions } from '@/types/common/token-options';
@ -56,6 +58,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null
dataBuffer.writeBigUInt64LE(options.expire_time, 0x6);
if (options.token_type !== 0x1 && options.token_type !== 0x2) {
// * Access and refresh tokens have smaller bodies due to size constraints
if (options.access_level === undefined || options.title_id === undefined) {
return null;
}
@ -72,20 +75,51 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null
const iv: Buffer = Buffer.alloc(16);
const cipher: crypto.Cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv);
return Buffer.concat([
const encrypted = Buffer.concat([
cipher.update(dataBuffer),
cipher.final()
]);
let final: Buffer = encrypted;
if (options.token_type !== 0x1 && options.token_type !== 0x2) {
// * Access and refresh tokens don't get a checksum due to size constraints
const checksum: Buffer = crc32(dataBuffer);
final = Buffer.concat([
checksum,
final
]);
}
return final;
}
export function decryptToken(token: Buffer): Buffer {
let encryptedBody: Buffer;
let expectedChecksum: number = 0;
if (token.length === 16) {
// * Token is an access/refresh token, no checksum
encryptedBody = token;
} else {
expectedChecksum = token.readUint32BE();
encryptedBody = token.subarray(4);
}
const iv: Buffer = Buffer.alloc(16);
const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv);
return Buffer.concat([
decipher.update(token),
const decrypted: Buffer = Buffer.concat([
decipher.update(encryptedBody),
decipher.final()
]);
if (expectedChecksum && (expectedChecksum !== crc.crc32(decrypted))) {
throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?');
}
return decrypted;
}
export function unpackToken(token: Buffer): Token {