chore: [app dir bootstrapping 1] generate nonce with native crypto API (#11969)
This commit is contained in:
parent
39cfe18ffe
commit
64d634e406
|
@ -0,0 +1,96 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
|
||||||
|
import { buildNonce } from "./buildNonce";
|
||||||
|
|
||||||
|
describe("buildNonce", () => {
|
||||||
|
it("should return an empty string for an empty array", () => {
|
||||||
|
const nonce = buildNonce(new Uint8Array());
|
||||||
|
|
||||||
|
expect(nonce).toEqual("");
|
||||||
|
expect(atob(nonce).length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for values from 0 to 63", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("ABCDEFGHIJKLMNOPQRSTQQ==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for values from 64 to 127", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i + 64);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("ABCDEFGHIJKLMNOPQRSTQQ==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for values from 128 to 191", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i + 128);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("ABCDEFGHIJKLMNOPQRSTQQ==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for values from 192 to 255", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i + 192);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("ABCDEFGHIJKLMNOPQRSTQQ==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for values from 0 to 42", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => 2 * i);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("ACEGIKMOQSUWYacegikmgg==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for 0 values", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => 0);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("AAAAAAAAAAAAAAAAAAAAAA==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a base64 string for 0xFF values", () => {
|
||||||
|
const array = Array(22)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => 0xff);
|
||||||
|
const nonce = buildNonce(new Uint8Array(array));
|
||||||
|
|
||||||
|
expect(nonce.length).toEqual(24);
|
||||||
|
expect(nonce).toEqual("////////////////////ww==");
|
||||||
|
|
||||||
|
expect(atob(nonce).length).toEqual(16);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,46 @@
|
||||||
|
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
/*
|
||||||
|
The buildNonce array allows a randomly generated 22-unsigned-byte array
|
||||||
|
and returns a 24-ASCII character string that mimics a base64-string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const buildNonce = (uint8array: Uint8Array): string => {
|
||||||
|
// the random uint8array should contain 22 bytes
|
||||||
|
// 22 bytes mimic the base64-encoded 16 bytes
|
||||||
|
// base64 encodes 6 bits (log2(64)) with 8 bits (64 allowed characters)
|
||||||
|
// thus ceil(16*8/6) gives us 22 bytes
|
||||||
|
if (uint8array.length != 22) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each random byte, we take:
|
||||||
|
// a) only the last 6 bits (so we map them to the base64 alphabet)
|
||||||
|
// b) for the last byte, we are interested in two bits
|
||||||
|
// explaination:
|
||||||
|
// 16*8 bits = 128 bits of information (order: left->right)
|
||||||
|
// 22*6 bits = 132 bits (order: left->right)
|
||||||
|
// thus the last byte has 4 redundant (least-significant, right-most) bits
|
||||||
|
// it leaves the last byte with 2 bits of information before the redundant bits
|
||||||
|
// so the bitmask is 0x110000 (2 bits of information, 4 redundant bits)
|
||||||
|
const bytes = uint8array.map((value, i) => {
|
||||||
|
if (i < 20) {
|
||||||
|
return value & 0b111111;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value & 0b110000;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nonceCharacters: string[] = [];
|
||||||
|
|
||||||
|
bytes.forEach((value) => {
|
||||||
|
nonceCharacters.push(BASE64_ALPHABET.charAt(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
// base64-encoded strings can be padded with 1 or 2 `=`
|
||||||
|
// since 22 % 4 = 2, we pad with two `=`
|
||||||
|
nonceCharacters.push("==");
|
||||||
|
|
||||||
|
// the end result has 22 information and 2 padding ASCII characters = 24 ASCII characters
|
||||||
|
return nonceCharacters.join("");
|
||||||
|
};
|
|
@ -1,10 +1,11 @@
|
||||||
import crypto from "crypto";
|
|
||||||
import type { IncomingMessage, OutgoingMessage } from "http";
|
import type { IncomingMessage, OutgoingMessage } from "http";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { buildNonce } from "@lib/buildNonce";
|
||||||
|
|
||||||
function getCspPolicy(nonce: string) {
|
function getCspPolicy(nonce: string) {
|
||||||
//TODO: Do we need to explicitly define it in turbo.json
|
//TODO: Do we need to explicitly define it in turbo.json
|
||||||
const CSP_POLICY = process.env.CSP_POLICY;
|
const CSP_POLICY = process.env.CSP_POLICY;
|
||||||
|
@ -59,7 +60,7 @@ export function csp(req: IncomingMessage | null, res: OutgoingMessage | null) {
|
||||||
}
|
}
|
||||||
const CSP_POLICY = process.env.CSP_POLICY;
|
const CSP_POLICY = process.env.CSP_POLICY;
|
||||||
const cspEnabledForInstance = CSP_POLICY;
|
const cspEnabledForInstance = CSP_POLICY;
|
||||||
const nonce = crypto.randomBytes(16).toString("base64");
|
const nonce = buildNonce(crypto.getRandomValues(new Uint8Array(22)));
|
||||||
|
|
||||||
const parsedUrl = new URL(req.url, "http://base_url");
|
const parsedUrl = new URL(req.url, "http://base_url");
|
||||||
const cspEnabledForPage = cspEnabledForInstance && isPagePathRequest(parsedUrl);
|
const cspEnabledForPage = cspEnabledForInstance && isPagePathRequest(parsedUrl);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user