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