inithilyhb
This commit is contained in:
@@ -8,6 +8,8 @@ import { logger } from '../lib/logger';
|
||||
*
|
||||
* Token format: <b64url_payload>.<b64url_timestamp>.<b64url_signature>
|
||||
*
|
||||
* Digests: sha1 (20-byte sig, legacy Flask-WTF), sha256 (32), sha512 (64, itsdangerous 2.x default).
|
||||
*
|
||||
* Default algorithm (itsdangerous ≥ 2.0):
|
||||
* - digest: SHA-512 (HMAC)
|
||||
* - salt: "itsdangerous.Signer" (or app-specific, e.g. "csrf-token")
|
||||
@@ -155,7 +157,20 @@ export function generateCsrfToken(): { token: string; maxAgeSec: number } {
|
||||
};
|
||||
}
|
||||
|
||||
function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyResult {
|
||||
type CsrfDigest = 'sha1' | 'sha256' | 'sha512';
|
||||
|
||||
/** HMAC output length → digest (auth-service legacy часто sha1 → sigLen=20). */
|
||||
const DIGEST_BY_SIG_LEN: Record<number, CsrfDigest> = {
|
||||
20: 'sha1',
|
||||
32: 'sha256',
|
||||
64: 'sha512',
|
||||
};
|
||||
|
||||
function verifyCsrfTokenWithDigest(
|
||||
cfg: CsrfConfig,
|
||||
digest: CsrfDigest,
|
||||
token: string,
|
||||
): CsrfVerifyResult {
|
||||
if (!token || typeof token !== 'string') return { valid: false, reason: 'Empty token' };
|
||||
|
||||
const lastDot = token.lastIndexOf('.');
|
||||
@@ -169,8 +184,8 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
|
||||
|
||||
const tsStr = payloadTs.slice(prevDot + 1);
|
||||
|
||||
const derived = deriveKey(cfg.secret, cfg.salt, cfg.digest);
|
||||
const expectedSig = crypto.createHmac(cfg.digest, derived).update(payloadTs).digest();
|
||||
const derived = deriveKey(cfg.secret, cfg.salt, digest);
|
||||
const expectedSig = crypto.createHmac(digest, derived).update(payloadTs).digest();
|
||||
|
||||
let actualSig: Buffer;
|
||||
try {
|
||||
@@ -203,29 +218,48 @@ function verifyCsrfTokenWithConfig(cfg: CsrfConfig, token: string): CsrfVerifyRe
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
function digestsToTry(primary: CsrfVerifyResult): CsrfDigest[] {
|
||||
const order: CsrfDigest[] = [];
|
||||
const add = (d: CsrfDigest) => {
|
||||
if (!order.includes(d)) order.push(d);
|
||||
};
|
||||
|
||||
add(current!.digest);
|
||||
if (primary.actualSigLen !== undefined) {
|
||||
const inferred = DIGEST_BY_SIG_LEN[primary.actualSigLen];
|
||||
if (inferred) add(inferred);
|
||||
}
|
||||
add('sha1');
|
||||
add('sha256');
|
||||
add('sha512');
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify CSRF token. If Vault digest differs from auth-service (sha256 vs sha512),
|
||||
* retry once with the alternate digest — типичный случай Flask-WTF / itsdangerous 2.x.
|
||||
* Verify CSRF token. Fallback по длине подписи: sha1 (20) / sha256 (32) / sha512 (64).
|
||||
*/
|
||||
export function verifyCsrfToken(token: string): CsrfVerifyResult {
|
||||
if (!current) return { valid: false, reason: 'CSRF secret not loaded' };
|
||||
|
||||
const primary = verifyCsrfTokenWithConfig(current, token);
|
||||
const primaryDigest = current.digest;
|
||||
const primary = verifyCsrfTokenWithDigest(current, primaryDigest, token);
|
||||
if (primary.valid) return primary;
|
||||
|
||||
if (primary.reason !== 'Signature length mismatch') {
|
||||
if (primary.reason !== 'Signature length mismatch' && primary.reason !== 'Signature mismatch') {
|
||||
return primary;
|
||||
}
|
||||
|
||||
const altDigest: 'sha256' | 'sha512' = current.digest === 'sha256' ? 'sha512' : 'sha256';
|
||||
const fallback = verifyCsrfTokenWithConfig({ ...current, digest: altDigest }, token);
|
||||
if (fallback.valid) {
|
||||
for (const digest of digestsToTry(primary)) {
|
||||
if (digest === primaryDigest) continue;
|
||||
const attempt = verifyCsrfTokenWithDigest(current, digest, token);
|
||||
if (attempt.valid) {
|
||||
logger.warn(
|
||||
`CSRF verified with fallback digest ${altDigest} (Vault digest=${current.digest}). ` +
|
||||
'Align auth-service URLSafeTimedSerializer digest_method with Vault `digest` field.',
|
||||
`CSRF verified with fallback digest ${digest} (Vault digest=${primaryDigest}, ` +
|
||||
`sigLen=${primary.actualSigLen ?? '?'}). Align auth digest_method with Vault.`,
|
||||
);
|
||||
return { valid: true };
|
||||
}
|
||||
}
|
||||
|
||||
return primary;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user