minor adjustments to HMAC signing
This commit is contained in:
parent
5000e59a61
commit
ce490efd7d
|
@ -17,7 +17,7 @@ import {
|
||||||
} from "../../shared/users/schema";
|
} from "../../shared/users/schema";
|
||||||
import { getLastNImages } from "../../shared/file-storage/image-history";
|
import { getLastNImages } from "../../shared/file-storage/image-history";
|
||||||
import { blacklists, parseCidrs, whitelists } from "../../shared/cidr";
|
import { blacklists, parseCidrs, whitelists } from "../../shared/cidr";
|
||||||
import { invalidatePowHmacKey } from "../../user/web/pow-captcha";
|
import { invalidatePowChallenges } from "../../user/web/pow-captcha";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ router.post("/maintenance", (req, res) => {
|
||||||
user.disabledReason = "Admin forced expiration.";
|
user.disabledReason = "Admin forced expiration.";
|
||||||
userStore.upsertUser(user);
|
userStore.upsertUser(user);
|
||||||
});
|
});
|
||||||
invalidatePowHmacKey();
|
invalidatePowChallenges();
|
||||||
flash.type = "success";
|
flash.type = "success";
|
||||||
flash.message = `${temps.length} temporary users marked for expiration.`;
|
flash.message = `${temps.length} temporary users marked for expiration.`;
|
||||||
break;
|
break;
|
||||||
|
@ -348,6 +348,7 @@ router.post("/maintenance", (req, res) => {
|
||||||
throw new HttpError(400, "Invalid difficulty" + selected);
|
throw new HttpError(400, "Invalid difficulty" + selected);
|
||||||
}
|
}
|
||||||
config.powDifficultyLevel = selected;
|
config.powDifficultyLevel = selected;
|
||||||
|
invalidatePowChallenges();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "generateTempIpReport": {
|
case "generateTempIpReport": {
|
||||||
|
|
|
@ -519,7 +519,7 @@ function generateSigningKey() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const signingKey = generateSigningKey();
|
const signingKey = generateSigningKey();
|
||||||
export const COOKIE_SECRET = signingKey;
|
export const SECRET_SIGNING_KEY = signingKey;
|
||||||
|
|
||||||
export async function assertConfigIsValid() {
|
export async function assertConfigIsValid() {
|
||||||
if (process.env.MODEL_RATE_LIMIT !== undefined) {
|
if (process.env.MODEL_RATE_LIMIT !== undefined) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/** Module for generating and verifying HMAC signatures. */
|
||||||
|
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { SECRET_SIGNING_KEY } from "../config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a HMAC signature for the given message. Optionally salts the
|
||||||
|
* key with a provided string.
|
||||||
|
*/
|
||||||
|
export function signMessage(msg: any, salt: string = ""): string {
|
||||||
|
const hmac = crypto.createHmac("sha256", SECRET_SIGNING_KEY + salt);
|
||||||
|
if (typeof msg === "object") {
|
||||||
|
hmac.update(JSON.stringify(msg));
|
||||||
|
} else {
|
||||||
|
hmac.update(msg);
|
||||||
|
}
|
||||||
|
return hmac.digest("hex");
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { doubleCsrf } from "csrf-csrf";
|
import { doubleCsrf } from "csrf-csrf";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { config, COOKIE_SECRET } from "../config";
|
import { config, SECRET_SIGNING_KEY } from "../config";
|
||||||
|
|
||||||
const { generateToken, doubleCsrfProtection } = doubleCsrf({
|
const { generateToken, doubleCsrfProtection } = doubleCsrf({
|
||||||
getSecret: () => COOKIE_SECRET,
|
getSecret: () => SECRET_SIGNING_KEY,
|
||||||
cookieName: "csrf",
|
cookieName: "csrf",
|
||||||
cookieOptions: {
|
cookieOptions: {
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import expressSession from "express-session";
|
import expressSession from "express-session";
|
||||||
import MemoryStore from "memorystore";
|
import MemoryStore from "memorystore";
|
||||||
import { config, COOKIE_SECRET } from "../config";
|
import { config, SECRET_SIGNING_KEY } from "../config";
|
||||||
|
|
||||||
const ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
|
const ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
|
||||||
|
|
||||||
const cookieParserMiddleware = cookieParser(COOKIE_SECRET);
|
const cookieParserMiddleware = cookieParser(SECRET_SIGNING_KEY);
|
||||||
|
|
||||||
const sessionMiddleware = expressSession({
|
const sessionMiddleware = expressSession({
|
||||||
secret: COOKIE_SECRET,
|
secret: SECRET_SIGNING_KEY,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
store: new (MemoryStore(expressSession))({ checkPeriod: ONE_WEEK }),
|
store: new (MemoryStore(expressSession))({ checkPeriod: ONE_WEEK }),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import crypto from "crypto";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import argon2 from "@node-rs/argon2";
|
import argon2 from "@node-rs/argon2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { signMessage } from "../../shared/hmac-signing";
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
createUser,
|
createUser,
|
||||||
|
@ -13,15 +14,13 @@ import { config } from "../../config";
|
||||||
/** Lockout time after verification in milliseconds */
|
/** Lockout time after verification in milliseconds */
|
||||||
const LOCKOUT_TIME = 1000 * 60; // 60 seconds
|
const LOCKOUT_TIME = 1000 * 60; // 60 seconds
|
||||||
|
|
||||||
/** HMAC key for signing challenges; regenerated on startup */
|
let powKeySalt = crypto.randomBytes(32).toString("hex");
|
||||||
let hmacSecret = crypto.randomBytes(32).toString("hex");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regenerate the HMAC key used for signing challenges. Calling this function
|
* Invalidates any outstanding unsolved challenges.
|
||||||
* will invalidate all existing challenges.
|
|
||||||
*/
|
*/
|
||||||
export function invalidatePowHmacKey() {
|
export function invalidatePowChallenges() {
|
||||||
hmacSecret = crypto.randomBytes(32).toString("hex");
|
powKeySalt = crypto.randomBytes(32).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
const argon2Params = {
|
const argon2Params = {
|
||||||
|
@ -141,16 +140,6 @@ function generateChallenge(clientIp?: string, token?: string): Challenge {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function signMessage(msg: any): string {
|
|
||||||
const hmac = crypto.createHmac("sha256", hmacSecret);
|
|
||||||
if (typeof msg === "object") {
|
|
||||||
hmac.update(JSON.stringify(msg));
|
|
||||||
} else {
|
|
||||||
hmac.update(msg);
|
|
||||||
}
|
|
||||||
return hmac.digest("hex");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verifySolution(
|
async function verifySolution(
|
||||||
challenge: Challenge,
|
challenge: Challenge,
|
||||||
solution: string,
|
solution: string,
|
||||||
|
@ -225,11 +214,11 @@ router.post("/challenge", (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const challenge = generateChallenge(req.ip, refreshToken);
|
const challenge = generateChallenge(req.ip, refreshToken);
|
||||||
const signature = signMessage(challenge);
|
const signature = signMessage(challenge, powKeySalt);
|
||||||
res.json({ challenge, signature });
|
res.json({ challenge, signature });
|
||||||
} else {
|
} else {
|
||||||
const challenge = generateChallenge(req.ip);
|
const challenge = generateChallenge(req.ip);
|
||||||
const signature = signMessage(challenge);
|
const signature = signMessage(challenge, powKeySalt);
|
||||||
res.json({ challenge, signature });
|
res.json({ challenge, signature });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -253,7 +242,7 @@ router.post("/verify", async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { challenge, signature, solution } = result.data;
|
const { challenge, signature, solution } = result.data;
|
||||||
if (signMessage(challenge) !== signature) {
|
if (signMessage(challenge, powKeySalt) !== signature) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error:
|
error:
|
||||||
"Invalid signature; server may have restarted since challenge was issued. Please request a new challenge.",
|
"Invalid signature; server may have restarted since challenge was issued. Please request a new challenge.",
|
||||||
|
|
Loading…
Reference in New Issue