oidc-provider
Version:
OAuth 2.0 Authorization Server implementation for Node.js with OpenID Connect
100 lines (74 loc) • 2.01 kB
JavaScript
/* eslint-disable no-plusplus, no-bitwise, no-param-reassign */
import { createHmac } from 'node:crypto';
import * as base64url from './base64url.js';
function sixfourbeify(value) {
const buf = Buffer.alloc(8);
for (let i = buf.length - 1; i >= 0; i--) {
buf[i] = value & 0xff;
value >>= 8;
}
return buf;
}
function compute(secret, step) {
return base64url.encodeBuffer(createHmac('sha256', secret).update(sixfourbeify(step)).digest());
}
function compare(server, client) {
let result = 0;
if (server.length !== client.length) {
result = 1;
client = server;
}
for (let i = 0; i < server.length; i++) {
result |= server.charCodeAt(i) ^ client.charCodeAt(i);
}
return result;
}
const STEP = 60;
export default class DPoPNonces {
#counter;
#secret;
#prevprev;
#prev;
#now;
#next;
#nextnext;
constructor(secret) {
if (!Buffer.isBuffer(secret) || secret.byteLength !== 32) {
throw new TypeError('features.dPoP.nonceSecret must be a 32-byte Buffer instance');
}
this.#secret = Uint8Array.prototype.slice.call(secret);
this.#counter = Math.floor(Date.now() / 1000 / STEP);
[this.#prevprev, this.#prev, this.#now, this.#next, this.#nextnext] = [
this.#counter - 2,
this.#counter - 1,
this.#counter,
this.#counter + 1,
this.#counter++ + 2,
].map(compute.bind(undefined, this.#secret));
setInterval(() => {
[
this.#prevprev,
this.#prev,
this.#now,
this.#next,
this.#nextnext,
] = [
this.#prev,
this.#now,
this.#next,
this.#nextnext,
compute(this.#secret, this.#counter++ + 2),
];
}, STEP * 1000).unref();
}
nextNonce() {
return this.#next;
}
checkNonce(nonce) {
let result = 0;
for (const server of [this.#prevprev, this.#prev, this.#now, this.#next, this.#nextnext]) {
result ^= compare(server, nonce);
}
return result === 0;
}
}