minor adjustments to HMAC signing

This commit is contained in:
nai-degen 2024-08-22 19:53:53 -05:00
parent 5000e59a61
commit ce490efd7d
6 changed files with 35 additions and 27 deletions

View File

@ -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": {

View File

@ -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) {

View File

@ -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");
}

View File

@ -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",

View File

@ -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 }),

View File

@ -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.",