From 12f2a09b8528bd524ea1aefffded4a96990af10b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Jan 2022 15:50:01 -0500
Subject: [PATCH 01/60] add CSP types
---
packages/kit/types/config.d.ts | 4 ++
packages/kit/types/csp.d.ts | 115 +++++++++++++++++++++++++++++++++
2 files changed, 119 insertions(+)
create mode 100644 packages/kit/types/csp.d.ts
diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts
index 788bef4cea3b..8095c492867f 100644
--- a/packages/kit/types/config.d.ts
+++ b/packages/kit/types/config.d.ts
@@ -1,5 +1,6 @@
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import { UserConfig as ViteConfig } from 'vite';
+import { CspDirectives } from './csp';
import { RecursiveRequired } from './helper';
import { HttpMethod, Logger, RouteSegment, TrailingSlash } from './internal';
@@ -117,6 +118,9 @@ export interface Config {
adapter?: Adapter;
amp?: boolean;
appDir?: string;
+ csp?: {
+ directives?: CspDirectives;
+ };
files?: {
assets?: string;
hooks?: string;
diff --git a/packages/kit/types/csp.d.ts b/packages/kit/types/csp.d.ts
new file mode 100644
index 000000000000..5b390dd12a9f
--- /dev/null
+++ b/packages/kit/types/csp.d.ts
@@ -0,0 +1,115 @@
+/**
+ * Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts
+ *
+ * MIT License
+ *
+ * Copyright (c) 2021-present, Joshua Hemphill
+ * Copyright (c) 2021, Tecnico Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+type SchemeSource = 'http:' | 'https:' | 'data:' | 'mediastream:' | 'blob:' | 'filesystem:';
+
+type HostProtocolSchemes = `${string}://` | '';
+type PortScheme = `:${number}` | '' | ':*';
+/** Can actually be any string, but typed more explicitly to
+ * restrict the combined optional types of Source from collapsing to just bing `string` */
+type HostNameScheme = `${string}.${string}` | `localhost`;
+type HostSource = `${HostProtocolSchemes}${HostNameScheme}${PortScheme}`;
+
+type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
+
+type BaseSource = 'self' | 'unsafe-eval' | 'unsafe-hashes' | 'unsafe-inline' | 'none';
+
+type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
+type Sources = Source[];
+
+type ActionSource = 'strict-dynamic' | 'report-sample';
+
+type FrameSource = HostSource | SchemeSource | 'self' | 'none';
+
+type HttpDelineator = '/' | '?' | '#' | '\\';
+type UriPath = `${HttpDelineator}${string}`;
+
+export type CspDirectives = {
+ 'child-src'?: Sources;
+ 'default-src'?: Sources;
+ 'frame-src'?: Sources;
+ 'worker-src'?: Sources;
+ 'connect-src'?: Sources;
+ 'font-src'?: Sources;
+ 'img-src'?: Sources;
+ 'manifest-src'?: Sources;
+ 'media-src'?: Sources;
+ 'object-src'?: Sources;
+ 'prefetch-src'?: Sources;
+ 'script-src'?: Sources;
+ 'script-src-elem'?: Sources;
+ 'script-src-attr'?: Sources;
+ 'style-src'?: Sources;
+ 'style-src-elem'?: Sources;
+ 'style-src-attr'?: Sources;
+ 'base-uri'?: Array;
+ sandbox?: Array<
+ | 'allow-downloads-without-user-activation'
+ | 'allow-forms'
+ | 'allow-modals'
+ | 'allow-orientation-lock'
+ | 'allow-pointer-lock'
+ | 'allow-popups'
+ | 'allow-popups-to-escape-sandbox'
+ | 'allow-presentation'
+ | 'allow-same-origin'
+ | 'allow-scripts'
+ | 'allow-storage-access-by-user-activation'
+ | 'allow-top-navigation'
+ | 'allow-top-navigation-by-user-activation'
+ >;
+ 'form-action'?: Sources;
+ 'frame-ancestors'?: Array;
+ 'navigate-to'?: Sources;
+ 'report-uri'?: UriPath[];
+ 'report-to'?: string[];
+
+ 'require-trusted-types-for'?: Array<'script'>;
+ 'trusted-types'?: Array<'none' | 'allow-duplicates' | '*' | string>;
+ 'upgrade-insecure-requests'?: boolean;
+
+ /** @deprecated */
+ 'require-sri-for'?: Array<'script' | 'style' | 'script style'>;
+
+ /** @deprecated */
+ 'block-all-mixed-content'?: boolean;
+
+ /** @deprecated */
+ 'plugin-types'?: Array<`${string}/${string}` | 'none'>;
+
+ /** @deprecated */
+ referrer?: Array<
+ | 'no-referrer'
+ | 'no-referrer-when-downgrade'
+ | 'origin'
+ | 'origin-when-cross-origin'
+ | 'same-origin'
+ | 'strict-origin'
+ | 'strict-origin-when-cross-origin'
+ | 'unsafe-url'
+ | 'none'
+ >;
+};
From 74a3432e85be1d2e8a1cc43e163db3faffbabf97 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Jan 2022 16:52:26 -0500
Subject: [PATCH 02/60] add csp stuff to config
---
.../fixtures/default/svelte.config.js | 0
.../fixtures/export-string/svelte.config.js | 0
packages/kit/src/core/config/index.spec.js | 225 ++++++++++--------
packages/kit/src/core/config/options.js | 50 ++++
packages/kit/src/core/config/test/index.js | 84 -------
.../kit/test/apps/options/svelte.config.js | 5 +
packages/kit/types/config.d.ts | 1 +
7 files changed, 179 insertions(+), 186 deletions(-)
rename packages/kit/src/core/config/{test => }/fixtures/default/svelte.config.js (100%)
rename packages/kit/src/core/config/{test => }/fixtures/export-string/svelte.config.js (100%)
delete mode 100644 packages/kit/src/core/config/test/index.js
diff --git a/packages/kit/src/core/config/test/fixtures/default/svelte.config.js b/packages/kit/src/core/config/fixtures/default/svelte.config.js
similarity index 100%
rename from packages/kit/src/core/config/test/fixtures/default/svelte.config.js
rename to packages/kit/src/core/config/fixtures/default/svelte.config.js
diff --git a/packages/kit/src/core/config/test/fixtures/export-string/svelte.config.js b/packages/kit/src/core/config/fixtures/export-string/svelte.config.js
similarity index 100%
rename from packages/kit/src/core/config/test/fixtures/export-string/svelte.config.js
rename to packages/kit/src/core/config/fixtures/export-string/svelte.config.js
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index df7f0a0e527a..79168d35c8e9 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -1,8 +1,99 @@
+import { join } from 'path';
+import { fileURLToPath } from 'url';
import { test } from 'uvu';
import * as assert from 'uvu/assert';
-
import { remove_keys } from '../../utils/object.js';
-import { validate_config } from './index.js';
+import { validate_config, load_config } from './index.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = join(__filename, '..');
+
+const get_defaults = (prefix = '') => ({
+ extensions: ['.svelte'],
+ kit: {
+ adapter: null,
+ amp: false,
+ appDir: '_app',
+ csp: {
+ mode: 'auto',
+ directives: {
+ 'child-src': [],
+ 'default-src': [],
+ 'frame-src': [],
+ 'worker-src': [],
+ 'connect-src': [],
+ 'font-src': [],
+ 'img-src': [],
+ 'manifest-src': [],
+ 'media-src': [],
+ 'object-src': [],
+ 'prefetch-src': [],
+ 'script-src': [],
+ 'script-src-elem': [],
+ 'script-src-attr': [],
+ 'style-src': [],
+ 'style-src-elem': [],
+ 'style-src-attr': [],
+ 'base-uri': [],
+ sandbox: [],
+ 'form-action': [],
+ 'frame-ancestors': [],
+ 'navigate-to': [],
+ 'report-uri': [],
+ 'report-to': [],
+ 'require-trusted-types-for': [],
+ 'trusted-types': [],
+ 'upgrade-insecure-requests': false,
+ 'require-sri-for': [],
+ 'block-all-mixed-content': false,
+ 'plugin-types': [],
+ referrer: []
+ }
+ },
+ files: {
+ assets: prefix + 'static',
+ hooks: prefix + 'src/hooks',
+ lib: prefix + 'src/lib',
+ routes: prefix + 'src/routes',
+ serviceWorker: prefix + 'src/service-worker',
+ template: prefix + 'src/app.html'
+ },
+ floc: false,
+ headers: undefined,
+ host: undefined,
+ hydrate: true,
+ inlineStyleThreshold: 0,
+ methodOverride: {
+ parameter: '_method',
+ allowed: []
+ },
+ package: {
+ dir: 'package',
+ emitTypes: true
+ },
+ serviceWorker: {
+ register: true
+ },
+ paths: {
+ base: '',
+ assets: ''
+ },
+ prerender: {
+ concurrency: 1,
+ crawl: true,
+ enabled: true,
+ entries: ['*'],
+ force: undefined,
+ onError: 'fail',
+ pages: undefined
+ },
+ protocol: undefined,
+ router: true,
+ ssr: null,
+ target: null,
+ trailingSlash: 'never'
+ }
+});
test('fills in defaults', () => {
const validated = validate_config({});
@@ -14,56 +105,7 @@ test('fills in defaults', () => {
remove_keys(validated, ([, v]) => typeof v === 'function');
- assert.equal(validated, {
- extensions: ['.svelte'],
- kit: {
- adapter: null,
- amp: false,
- appDir: '_app',
- files: {
- assets: 'static',
- hooks: 'src/hooks',
- lib: 'src/lib',
- routes: 'src/routes',
- serviceWorker: 'src/service-worker',
- template: 'src/app.html'
- },
- floc: false,
- headers: undefined,
- host: undefined,
- hydrate: true,
- inlineStyleThreshold: 0,
- methodOverride: {
- parameter: '_method',
- allowed: []
- },
- package: {
- dir: 'package',
- emitTypes: true
- },
- serviceWorker: {
- register: true
- },
- paths: {
- base: '',
- assets: ''
- },
- prerender: {
- concurrency: 1,
- crawl: true,
- enabled: true,
- entries: ['*'],
- force: undefined,
- onError: 'fail',
- pages: undefined
- },
- protocol: undefined,
- router: true,
- ssr: null,
- target: null,
- trailingSlash: 'never'
- }
- });
+ assert.equal(validated, get_defaults());
});
test('errors on invalid values', () => {
@@ -123,56 +165,10 @@ test('fills in partial blanks', () => {
remove_keys(validated, ([, v]) => typeof v === 'function');
- assert.equal(validated, {
- extensions: ['.svelte'],
- kit: {
- adapter: null,
- amp: false,
- appDir: '_app',
- files: {
- assets: 'public',
- hooks: 'src/hooks',
- lib: 'src/lib',
- routes: 'src/routes',
- serviceWorker: 'src/service-worker',
- template: 'src/app.html'
- },
- floc: false,
- headers: undefined,
- host: undefined,
- hydrate: true,
- inlineStyleThreshold: 0,
- methodOverride: {
- parameter: '_method',
- allowed: []
- },
- package: {
- dir: 'package',
- emitTypes: true
- },
- serviceWorker: {
- register: true
- },
- paths: {
- base: '',
- assets: ''
- },
- prerender: {
- concurrency: 1,
- crawl: true,
- enabled: true,
- entries: ['*'],
- force: undefined,
- onError: 'fail',
- pages: undefined
- },
- protocol: undefined,
- router: true,
- ssr: null,
- target: null,
- trailingSlash: 'never'
- }
- });
+ const config = get_defaults();
+ config.kit.files.assets = 'public';
+
+ assert.equal(validated, config);
});
test('fails if kit.appDir is blank', () => {
@@ -327,4 +323,29 @@ validate_paths(
}
);
+test('load default config (esm)', async () => {
+ const cwd = join(__dirname, 'fixtures/default');
+
+ const config = await load_config({ cwd });
+ remove_keys(config, ([, v]) => typeof v === 'function');
+
+ assert.equal(config, get_defaults(cwd + '/'));
+});
+
+test('errors on loading config with incorrect default export', async () => {
+ let message = null;
+
+ try {
+ const cwd = join(__dirname, 'fixtures', 'export-string');
+ await load_config({ cwd });
+ } catch (/** @type {any} */ e) {
+ message = e.message;
+ }
+
+ assert.equal(
+ message,
+ 'svelte.config.js must have a configuration object as its default export. See https://kit.svelte.dev/docs#configuration'
+ );
+});
+
test.run();
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index 96d1b8ad9ca1..c34bd4af11f2 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -55,6 +55,43 @@ const options = object(
return input;
}),
+ csp: object({
+ mode: list(['auto', 'hash', 'nonce']),
+ directives: object({
+ 'child-src': string_array(),
+ 'default-src': string_array(),
+ 'frame-src': string_array(),
+ 'worker-src': string_array(),
+ 'connect-src': string_array(),
+ 'font-src': string_array(),
+ 'img-src': string_array(),
+ 'manifest-src': string_array(),
+ 'media-src': string_array(),
+ 'object-src': string_array(),
+ 'prefetch-src': string_array(),
+ 'script-src': string_array(),
+ 'script-src-elem': string_array(),
+ 'script-src-attr': string_array(),
+ 'style-src': string_array(),
+ 'style-src-elem': string_array(),
+ 'style-src-attr': string_array(),
+ 'base-uri': string_array(),
+ sandbox: string_array(),
+ 'form-action': string_array(),
+ 'frame-ancestors': string_array(),
+ 'navigate-to': string_array(),
+ 'report-uri': string_array(),
+ 'report-to': string_array(),
+ 'require-trusted-types-for': string_array(),
+ 'trusted-types': string_array(),
+ 'upgrade-insecure-requests': boolean(false),
+ 'require-sri-for': string_array(),
+ 'block-all-mixed-content': boolean(false),
+ 'plugin-types': string_array(),
+ referrer: string_array()
+ })
+ }),
+
files: object({
assets: string('static'),
hooks: string('src/hooks'),
@@ -312,6 +349,19 @@ function string(fallback, allow_empty = true) {
});
}
+/**
+ * @returns {Validator}
+ */
+function string_array() {
+ return validate([], (input, keypath) => {
+ if (!Array.isArray(input) || input.some((value) => typeof value !== 'string')) {
+ throw new Error(`${keypath} must be an array of strings, if specified`);
+ }
+
+ return input;
+ });
+}
+
/**
* @param {number} fallback
* @returns {Validator}
diff --git a/packages/kit/src/core/config/test/index.js b/packages/kit/src/core/config/test/index.js
deleted file mode 100644
index 02cb1fd55703..000000000000
--- a/packages/kit/src/core/config/test/index.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import { join } from 'path';
-import { fileURLToPath } from 'url';
-
-import * as assert from 'uvu/assert';
-import { test } from 'uvu';
-
-import { remove_keys } from '../../../utils/object.js';
-import { load_config } from '../index.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = join(__filename, '..');
-
-test('load default config (esm)', async () => {
- const cwd = join(__dirname, 'fixtures/default');
-
- const config = await load_config({ cwd });
- remove_keys(config, ([, v]) => typeof v === 'function');
-
- assert.equal(config, {
- extensions: ['.svelte'],
- kit: {
- adapter: null,
- amp: false,
- appDir: '_app',
- files: {
- assets: join(cwd, 'static'),
- hooks: join(cwd, 'src/hooks'),
- lib: join(cwd, 'src/lib'),
- routes: join(cwd, 'src/routes'),
- serviceWorker: join(cwd, 'src/service-worker'),
- template: join(cwd, 'src/app.html')
- },
- floc: false,
- headers: undefined,
- host: undefined,
- hydrate: true,
- inlineStyleThreshold: 0,
- methodOverride: {
- parameter: '_method',
- allowed: []
- },
- package: {
- dir: 'package',
- emitTypes: true
- },
- serviceWorker: {
- register: true
- },
- paths: { base: '', assets: '' },
- prerender: {
- concurrency: 1,
- crawl: true,
- enabled: true,
- entries: ['*'],
- force: undefined,
- onError: 'fail',
- pages: undefined
- },
- protocol: undefined,
- router: true,
- ssr: null,
- target: null,
- trailingSlash: 'never'
- }
- });
-});
-
-test('errors on loading config with incorrect default export', async () => {
- let message = null;
-
- try {
- const cwd = join(__dirname, 'fixtures', 'export-string');
- await load_config({ cwd });
- } catch (/** @type {any} */ e) {
- message = e.message;
- }
-
- assert.equal(
- message,
- 'svelte.config.js must have a configuration object as its default export. See https://kit.svelte.dev/docs#configuration'
- );
-});
-
-test.run();
diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js
index f916490ddc01..e11dd0b93911 100644
--- a/packages/kit/test/apps/options/svelte.config.js
+++ b/packages/kit/test/apps/options/svelte.config.js
@@ -4,6 +4,11 @@ import path from 'path';
const config = {
extensions: ['.jesuslivesineveryone', '.whokilledthemuffinman', '.svelte.md', '.svelte'],
kit: {
+ csp: {
+ directives: {
+ 'script-src': ['self']
+ }
+ },
files: {
assets: 'public',
lib: 'source/components',
diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts
index 8095c492867f..4fadf2ee41c5 100644
--- a/packages/kit/types/config.d.ts
+++ b/packages/kit/types/config.d.ts
@@ -119,6 +119,7 @@ export interface Config {
amp?: boolean;
appDir?: string;
csp?: {
+ mode?: 'hash' | 'nonce' | 'auto';
directives?: CspDirectives;
};
files?: {
From 39c376d43f8ceb897cfe74897854a573df493ed6 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Jan 2022 19:06:05 -0500
Subject: [PATCH 03/60] add csp to SSRRenderOptions
---
packages/kit/types/internal.d.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index f3e6422e5604..7163d61424f0 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -1,4 +1,5 @@
import { OutputAsset, OutputChunk } from 'rollup';
+import { ValidatedConfig } from './config';
import { InternalApp, SSRManifest } from './app';
import { Fallthrough, RequestHandler } from './endpoint';
import { Either } from './helper';
@@ -115,6 +116,7 @@ export interface SSRNode {
export interface SSRRenderOptions {
amp: boolean;
+ csp: ValidatedConfig['kit']['csp'];
dev: boolean;
floc: boolean;
get_stack: (error: Error) => string | undefined;
From a767f4cc9a5178cddfe2f9f4e4da4d6859a53ab2 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Jan 2022 19:18:42 -0500
Subject: [PATCH 04/60] lay some groundwork
---
packages/kit/src/core/build/build_server.js | 1 +
packages/kit/src/core/config/index.spec.js | 58 +++++++-------
packages/kit/src/core/config/options.js | 7 +-
packages/kit/src/core/dev/plugin.js | 1 +
.../kit/src/runtime/server/page/render.js | 77 +++++++++++--------
5 files changed, 81 insertions(+), 63 deletions(-)
diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js
index 0e8f9b6cbd1d..de0c337ec0a6 100644
--- a/packages/kit/src/core/build/build_server.js
+++ b/packages/kit/src/core/build/build_server.js
@@ -60,6 +60,7 @@ export class App {
this.options = {
amp: ${config.kit.amp},
+ csp: ${s(config.kit.csp)},
dev: false,
floc: ${config.kit.floc},
get_stack: error => String(error), // for security
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index 79168d35c8e9..7f33e3452506 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -17,37 +17,37 @@ const get_defaults = (prefix = '') => ({
csp: {
mode: 'auto',
directives: {
- 'child-src': [],
- 'default-src': [],
- 'frame-src': [],
- 'worker-src': [],
- 'connect-src': [],
- 'font-src': [],
- 'img-src': [],
- 'manifest-src': [],
- 'media-src': [],
- 'object-src': [],
- 'prefetch-src': [],
- 'script-src': [],
- 'script-src-elem': [],
- 'script-src-attr': [],
- 'style-src': [],
- 'style-src-elem': [],
- 'style-src-attr': [],
- 'base-uri': [],
- sandbox: [],
- 'form-action': [],
- 'frame-ancestors': [],
- 'navigate-to': [],
- 'report-uri': [],
- 'report-to': [],
- 'require-trusted-types-for': [],
- 'trusted-types': [],
+ 'child-src': undefined,
+ 'default-src': undefined,
+ 'frame-src': undefined,
+ 'worker-src': undefined,
+ 'connect-src': undefined,
+ 'font-src': undefined,
+ 'img-src': undefined,
+ 'manifest-src': undefined,
+ 'media-src': undefined,
+ 'object-src': undefined,
+ 'prefetch-src': undefined,
+ 'script-src': undefined,
+ 'script-src-elem': undefined,
+ 'script-src-attr': undefined,
+ 'style-src': undefined,
+ 'style-src-elem': undefined,
+ 'style-src-attr': undefined,
+ 'base-uri': undefined,
+ sandbox: undefined,
+ 'form-action': undefined,
+ 'frame-ancestors': undefined,
+ 'navigate-to': undefined,
+ 'report-uri': undefined,
+ 'report-to': undefined,
+ 'require-trusted-types-for': undefined,
+ 'trusted-types': undefined,
'upgrade-insecure-requests': false,
- 'require-sri-for': [],
+ 'require-sri-for': undefined,
'block-all-mixed-content': false,
- 'plugin-types': [],
- referrer: []
+ 'plugin-types': undefined,
+ referrer: undefined
}
},
files: {
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index c34bd4af11f2..d9450b3a3c66 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -350,10 +350,13 @@ function string(fallback, allow_empty = true) {
}
/**
+ * @param {string[] | undefined} [fallback]
* @returns {Validator}
*/
-function string_array() {
- return validate([], (input, keypath) => {
+function string_array(fallback) {
+ return validate(fallback, (input, keypath) => {
+ if (input === undefined) return input;
+
if (!Array.isArray(input) || input.some((value) => typeof value !== 'string')) {
throw new Error(`${keypath} must be an array of strings, if specified`);
}
diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js
index 7fa63353e2e4..ffa994034776 100644
--- a/packages/kit/src/core/dev/plugin.js
+++ b/packages/kit/src/core/dev/plugin.js
@@ -224,6 +224,7 @@ export async function create_plugin(config, cwd) {
}),
{
amp: config.kit.amp,
+ csp: config.kit.csp,
dev: true,
floc: config.kit.floc,
get_stack: (error) => {
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 23b2de5c3758..b4a2f2a10a99 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -36,8 +36,8 @@ export async function render_response({
ssr,
stuff
}) {
- const css = new Set(options.manifest._.entry.css);
- const js = new Set(options.manifest._.entry.js);
+ const stylesheets = new Set(options.manifest._.entry.css);
+ const modulepreloads = new Set(options.manifest._.entry.js);
/** @type {Map} */
const styles = new Map();
@@ -55,8 +55,8 @@ export async function render_response({
if (ssr) {
branch.forEach(({ node, loaded, fetched, uses_credentials }) => {
- if (node.css) node.css.forEach((url) => css.add(url));
- if (node.js) node.js.forEach((url) => js.add(url));
+ if (node.css) node.css.forEach((url) => stylesheets.add(url));
+ if (node.js) node.js.forEach((url) => modulepreloads.add(url));
if (node.styles) Object.entries(node.styles).forEach(([k, v]) => styles.set(k, v));
// TODO probably better if `fetched` wasn't populated unless `hydrate`
@@ -128,6 +128,39 @@ export async function render_response({
const inlined_style = Array.from(styles.values()).join('\n');
+ const needs_scripts_csp =
+ options.csp.directives['script-src'] &&
+ options.csp.directives['script-src'].filter((value) => value !== 'unsafe-inline').length > 0;
+
+ const needs_styles_csp =
+ options.csp.directives['style-src'] &&
+ options.csp.directives['style-src'].filter((value) => value !== 'unsafe-inline').length > 0;
+
+ // prettier-ignore
+ const init = `
+ import { start } from ${s(options.prefix + options.manifest._.entry.file)};
+ start({
+ target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
+ paths: ${s(options.paths)},
+ session: ${try_serialize($session, (error) => {
+ throw new Error(`Failed to serialize session data: ${error.message}`);
+ })},
+ route: ${!!page_config.router},
+ spa: ${!ssr},
+ trailing_slash: ${s(options.trailing_slash)},
+ hydrate: ${ssr && page_config.hydrate ? `{
+ status: ${status},
+ error: ${serialize_error(error)},
+ nodes: [
+ ${(branch || [])
+ .map(({ node }) => `import(${s(options.prefix + node.entry)})`)
+ .join(',\n\t\t\t\t\t\t')}
+ ],
+ url: new URL(${s(url.href)}),
+ params: ${devalue(params)}
+ }` : 'null'}
+ });`;
+
if (state.prerender) {
if (maxage) {
head += ``;
@@ -150,43 +183,23 @@ export async function render_response({
}
} else {
if (inlined_style) {
- head += `\n\t`;
+ const attributes = [];
+ if (options.dev) attributes.push(' data-svelte');
+
+ head += `\n\t`;
}
// prettier-ignore
- head += Array.from(css)
+ head += Array.from(stylesheets)
.map((dep) => `\n\t`)
.join('');
if (page_config.router || page_config.hydrate) {
- head += Array.from(js)
+ head += Array.from(modulepreloads)
.map((dep) => `\n\t`)
.join('');
- // prettier-ignore
+
head += `
- `;
+ `;
body += serialized_data
.map(({ url, body, json }) => {
From 77c5665a48a09199deba20bd05a09db5d2661cc9 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 21 Jan 2022 19:44:34 -0500
Subject: [PATCH 05/60] fall back to default-src
---
packages/kit/src/runtime/server/page/render.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index b4a2f2a10a99..7896e910180f 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -128,13 +128,14 @@ export async function render_response({
const inlined_style = Array.from(styles.values()).join('\n');
+ const script_src = options.csp.directives['script-src'] || options.csp.directives['default-src'];
+ const style_src = options.csp.directives['style-src'] || options.csp.directives['default-src'];
+
const needs_scripts_csp =
- options.csp.directives['script-src'] &&
- options.csp.directives['script-src'].filter((value) => value !== 'unsafe-inline').length > 0;
+ script_src && script_src.filter((value) => value !== 'unsafe-inline').length > 0;
const needs_styles_csp =
- options.csp.directives['style-src'] &&
- options.csp.directives['style-src'].filter((value) => value !== 'unsafe-inline').length > 0;
+ style_src && style_src.filter((value) => value !== 'unsafe-inline').length > 0;
// prettier-ignore
const init = `
From 043d0642280990dfabb0dc02ee9e54ff66f2b92b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sat, 22 Jan 2022 07:46:07 -0500
Subject: [PATCH 06/60] more stuff
---
.../kit/src/runtime/server/page/render.js | 33 ++++++++++++++++---
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 7896e910180f..6e2ea4b0b3cd 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -36,6 +36,16 @@ export async function render_response({
ssr,
stuff
}) {
+ if (state.prerender) {
+ if (options.csp.mode === 'nonce') {
+ throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
+ }
+
+ if (options.template_contains_nonce) {
+ throw new Error('Cannot use prerendering if page template contains %svelte.nonce%');
+ }
+ }
+
const stylesheets = new Set(options.manifest._.entry.css);
const modulepreloads = new Set(options.manifest._.entry.js);
/** @type {Map} */
@@ -128,6 +138,7 @@ export async function render_response({
const inlined_style = Array.from(styles.values()).join('\n');
+ // CSP stuff
const script_src = options.csp.directives['script-src'] || options.csp.directives['default-src'];
const style_src = options.csp.directives['style-src'] || options.csp.directives['default-src'];
@@ -137,8 +148,15 @@ export async function render_response({
const needs_styles_csp =
style_src && style_src.filter((value) => value !== 'unsafe-inline').length > 0;
+ const use_hashes =
+ options.csp.mode === 'hash' || (options.csp.mode === 'auto' && state.prerender);
+
+ const use_nonce = options.csp.mode !== 'hash';
+
+ const nonce = (use_nonce || options.template_contains_nonce) && generate_nonce();
+
// prettier-ignore
- const init = `
+ const init_app = `
import { start } from ${s(options.prefix + options.manifest._.entry.file)};
start({
target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
@@ -160,7 +178,14 @@ export async function render_response({
url: new URL(${s(url.href)}),
params: ${devalue(params)}
}` : 'null'}
- });`;
+ });
+ `;
+
+ const init_service_worker = `
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register('${options.service_worker}');
+ }
+ `;
if (state.prerender) {
if (maxage) {
@@ -200,7 +225,7 @@ export async function render_response({
.join('');
head += `
- `;
+ `;
body += serialized_data
.map(({ url, body, json }) => {
@@ -229,7 +254,7 @@ export async function render_response({
const assets =
options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
- const html = options.template({ head, body, assets });
+ const html = options.template({ head, body, assets, nonce });
const headers = new Headers({
'content-type': 'text/html',
From cae0b70c22569f45f84d8b86da413da708c7eb45 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sat, 22 Jan 2022 07:49:12 -0500
Subject: [PATCH 07/60] generate meta tags last
---
.../kit/src/runtime/server/page/render.js | 27 +++++++++++--------
1 file changed, 16 insertions(+), 11 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 6e2ea4b0b3cd..89ae68efd003 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -187,12 +187,6 @@ export async function render_response({
}
`;
- if (state.prerender) {
- if (maxage) {
- head += ``;
- }
- }
-
if (options.amp) {
head += `
@@ -242,12 +236,23 @@ export async function render_response({
if (options.service_worker) {
// always include service worker unless it's turned off explicitly
head += `
- `;
+ `;
+ }
+ }
+
+ if (state.prerender) {
+ /** @type {Array<{ key: string, value: string }>} */
+ const http_equiv = [];
+
+ if (maxage) {
+ http_equiv.push({ key: 'cache-control', value: `max-age=${maxage}` });
}
+
+ const tags = http_equiv.map(
+ ({ key, value }) => ``
+ );
+
+ head = tags + head;
}
const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
From 32b485f83b50f676c4d058a2d7b17eaa4bde294b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Sun, 23 Jan 2022 23:02:31 -0500
Subject: [PATCH 08/60] move CSP logic out into separate (and more testable)
class
---
packages/kit/src/core/build/build_server.js | 13 +-
packages/kit/src/core/dev/plugin.js | 5 +-
packages/kit/src/runtime/server/page/csp.js | 210 ++++++++++++++++++
.../kit/src/runtime/server/page/render.js | 107 ++++++---
packages/kit/types/csp.d.ts | 2 +-
packages/kit/types/internal.d.ts | 13 +-
6 files changed, 310 insertions(+), 40 deletions(-)
create mode 100644 packages/kit/src/runtime/server/page/csp.js
diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js
index de0c337ec0a6..7a8efbeac424 100644
--- a/packages/kit/src/core/build/build_server.js
+++ b/packages/kit/src/core/build/build_server.js
@@ -11,21 +11,21 @@ import { s } from '../../utils/misc.js';
/**
* @param {{
- * cwd: string;
* hooks: string;
* config: import('types/config').ValidatedConfig;
* has_service_worker: boolean;
+ * template: string;
* }} opts
* @returns
*/
-const template = ({ cwd, config, hooks, has_service_worker }) => `
+const app_template = ({ config, hooks, has_service_worker, template }) => `
import root from '__GENERATED__/root.svelte';
import { respond } from '${runtime}/server/index.js';
import { set_paths, assets, base } from '${runtime}/paths.js';
import { set_prerendering } from '${runtime}/env.js';
import * as user_hooks from ${s(hooks)};
-const template = ({ head, body, assets }) => ${s(load_template(cwd, config))
+const template = ({ head, body, assets }) => ${s(template)
.replace('%svelte.head%', '" + head + "')
.replace('%svelte.body%', '" + body + "')
.replace(/%svelte\.assets%/g, '" + assets + "')};
@@ -90,6 +90,7 @@ export class App {
router: ${s(config.kit.router)},
target: ${s(config.kit.target)},
template,
+ template_contains_nonce: ${template.includes('%svelte.nonce%')},
trailing_slash: ${s(config.kit.trailingSlash)}
};
}
@@ -173,11 +174,11 @@ export async function build_server(
// prettier-ignore
fs.writeFileSync(
input.app,
- template({
- cwd,
+ app_template({
config,
hooks: app_relative(hooks_file),
- has_service_worker: service_worker_register && !!service_worker_entry_file
+ has_service_worker: service_worker_register && !!service_worker_entry_file,
+ template: load_template(cwd, config)
})
);
diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js
index ffa994034776..b27478a64f75 100644
--- a/packages/kit/src/core/dev/plugin.js
+++ b/packages/kit/src/core/dev/plugin.js
@@ -216,6 +216,8 @@ export async function create_plugin(config, cwd) {
return res.end(err.reason || 'Invalid request body');
}
+ const template = load_template(cwd, config);
+
const rendered = await respond(
new Request(url.href, {
headers: /** @type {Record} */ (req.headers),
@@ -261,7 +263,7 @@ export async function create_plugin(config, cwd) {
router: config.kit.router,
target: config.kit.target,
template: ({ head, body, assets }) => {
- let rendered = load_template(cwd, config)
+ let rendered = template
.replace('%svelte.head%', () => head)
.replace('%svelte.body%', () => body)
.replace(/%svelte\.assets%/g, assets);
@@ -312,6 +314,7 @@ export async function create_plugin(config, cwd) {
return rendered;
},
+ template_contains_nonce: template.includes('%svelte.nonce%'),
trailing_slash: config.kit.trailingSlash
}
);
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
new file mode 100644
index 000000000000..8a185408b6b0
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -0,0 +1,210 @@
+import { webcrypto } from 'crypto';
+import { escape_html_attr } from '../../../utils/escape.js';
+
+const array = new Uint8Array(16);
+
+export function generate_nonce() {
+ webcrypto.getRandomValues(array);
+ return base64(array);
+}
+
+/**
+ * @param {string} contents
+ * @param {string} algorithm
+ * @returns
+ */
+async function generate_hash(contents, algorithm = 'sha-256') {
+ const bytes = new TextEncoder().encode(contents);
+ const digest = new Uint8Array(await webcrypto.subtle.digest(algorithm, bytes));
+ return base64(digest);
+}
+
+const quoted = new Set([
+ 'self',
+ 'unsafe-eval',
+ 'unsafe-hashes',
+ 'unsafe-inline',
+ 'none',
+ 'strict-dynamic',
+ 'report-sample'
+]);
+
+const crypto_pattern = /^(nonce|sha\d\d\d)-/;
+
+export class Csp {
+ /** @type {boolean} */
+ #use_hashes;
+
+ /** @type {import('types/csp').CspDirectives} */
+ #directives;
+
+ /** @type {import('types/csp').Source[]} */
+ #script_src;
+
+ /** @type {import('types/csp').Source[]} */
+ #style_src;
+
+ /**
+ * @param {{
+ * mode: string,
+ * directives: import('types/csp').CspDirectives
+ * }} opts
+ * @param {boolean} prerender
+ */
+ constructor({ mode, directives }, prerender) {
+ this.#use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
+ this.#directives = { ...directives };
+
+ this.#script_src = [];
+ this.#style_src = [];
+
+ const effective_script_src = directives['script-src'] || directives['default-src'];
+ const effective_style_src = directives['style-src'] || directives['default-src'];
+
+ this.script_needs_csp =
+ effective_script_src &&
+ effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
+
+ this.style_needs_csp =
+ effective_style_src &&
+ effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
+
+ this.script_needs_nonce = this.script_needs_csp && !this.#use_hashes;
+ this.style_needs_nonce = this.style_needs_csp && !this.#use_hashes;
+
+ if (this.script_needs_nonce || this.style_needs_nonce) {
+ this.nonce = generate_nonce();
+ }
+ }
+
+ // TODO would be great if these methods weren't async
+ /** @param {string} content */
+ async add_script(content) {
+ if (this.script_needs_csp) {
+ if (this.#use_hashes) {
+ this.#script_src.push(`sha256-${await generate_hash(content)}`);
+ } else if (this.#script_src.length === 0) {
+ this.#script_src.push(`nonce-${this.nonce}`);
+ }
+ }
+ }
+
+ /** @param {string} content */
+ async add_style(content) {
+ if (this.style_needs_csp) {
+ if (this.#use_hashes) {
+ this.#style_src.push(`sha256-${await generate_hash(content)}`);
+ } else if (this.#style_src.length === 0) {
+ this.#style_src.push(`nonce-${this.nonce}`);
+ }
+ }
+ }
+
+ /** @param {boolean} [is_meta] */
+ get_header(is_meta = false) {
+ const header = [];
+
+ // due to browser inconsistencies, we can't append sources to default-src
+ // (specifically, Firefox appears to not ignore nonce-{nonce} directives
+ // on default-src), so we ensure that script-src and style-src exist
+
+ if (this.#script_src.length > 0) {
+ if (!this.#directives['script-src']) {
+ this.#directives['script-src'] = [...(this.#directives['default-src'] || [])];
+ }
+
+ this.#directives['script-src'].push(...this.#script_src);
+ }
+
+ if (this.#style_src.length > 0) {
+ if (!this.#directives['style-src']) {
+ this.#directives['style-src'] = [...(this.#directives['default-src'] || [])];
+ }
+
+ this.#directives['style-src'].push(...this.#style_src);
+ }
+
+ for (const key in this.#directives) {
+ if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
+ // these values cannot be used with a tag
+ // TODO warn?
+ continue;
+ }
+
+ // @ts-expect-error gimme a break typescript, `key` is obviously a member of this.#directives
+ const value = /** @type {string[] | true} */ (this.#directives[key]);
+
+ if (!value) continue;
+
+ const directive = [key];
+ if (Array.isArray(value)) {
+ value.forEach((value) => {
+ if (quoted.has(value) || crypto_pattern.test(value)) {
+ directive.push(`'${value}'`);
+ } else {
+ directive.push(value);
+ }
+ });
+ }
+
+ header.push(directive.join(' '));
+ }
+
+ return header.join('; ');
+ }
+
+ get_meta() {
+ const content = escape_html_attr(this.get_header(true));
+ return `> 2];
+ result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+ result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
+ result += chars[bytes[i] & 0x3f];
+ }
+ if (i === l + 1) {
+ // 1 octet yet to write
+ result += chars[bytes[i - 2] >> 2];
+ result += chars[(bytes[i - 2] & 0x03) << 4];
+ result += '==';
+ }
+ if (i === l) {
+ // 2 octets yet to write
+ result += chars[bytes[i - 2] >> 2];
+ result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+ result += chars[(bytes[i - 1] & 0x0f) << 2];
+ result += '=';
+ }
+ return result;
+}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 89ae68efd003..2bd082599081 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -5,6 +5,7 @@ import { hash } from '../../hash.js';
import { escape_html_attr } from '../../../utils/escape.js';
import { s } from '../../../utils/misc.js';
import { create_prerendering_url_proxy } from './utils.js';
+import { Csp } from './csp.js';
// TODO rename this function/module
@@ -138,22 +139,7 @@ export async function render_response({
const inlined_style = Array.from(styles.values()).join('\n');
- // CSP stuff
- const script_src = options.csp.directives['script-src'] || options.csp.directives['default-src'];
- const style_src = options.csp.directives['style-src'] || options.csp.directives['default-src'];
-
- const needs_scripts_csp =
- script_src && script_src.filter((value) => value !== 'unsafe-inline').length > 0;
-
- const needs_styles_csp =
- style_src && style_src.filter((value) => value !== 'unsafe-inline').length > 0;
-
- const use_hashes =
- options.csp.mode === 'hash' || (options.csp.mode === 'auto' && state.prerender);
-
- const use_nonce = options.csp.mode !== 'hash';
-
- const nonce = (use_nonce || options.template_contains_nonce) && generate_nonce();
+ const csp = new Csp(options.csp, !!state.prerender);
// prettier-ignore
const init_app = `
@@ -205,12 +191,31 @@ export async function render_response({
if (inlined_style) {
const attributes = [];
if (options.dev) attributes.push(' data-svelte');
+ if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
+
+ await csp.add_style(inlined_style);
head += `\n\t`;
}
+
// prettier-ignore
head += Array.from(stylesheets)
- .map((dep) => `\n\t`)
+ .map((dep) => {
+ const attributes = [
+ 'rel="stylesheet"',
+ `href="${options.prefix + dep}"`
+ ];
+
+ if (csp.style_needs_nonce) {
+ attributes.push(`nonce="${csp.nonce}"`);
+ }
+
+ if (styles.has(dep)) {
+ attributes.push('disabled', 'media="(max-width: 0)"');
+ }
+
+ return `\n\t`
+ })
.join('');
if (page_config.router || page_config.hydrate) {
@@ -218,14 +223,20 @@ export async function render_response({
.map((dep) => `\n\t`)
.join('');
- head += `
- `;
+ const attributes = ['type="module"'];
+ await csp.add_script(init_app);
+
+ if (csp.script_needs_nonce) {
+ attributes.push(`nonce="${csp.nonce}"`);
+ }
+
+ head += ``;
+
+ // prettier-ignore
body += serialized_data
.map(({ url, body, json }) => {
- let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(
- url
- )}`;
+ let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(url)}`;
if (body) attributes += ` data-body="${hash(body)}"`;
return ``;
@@ -235,31 +246,35 @@ export async function render_response({
if (options.service_worker) {
// always include service worker unless it's turned off explicitly
+ await csp.add_script(init_service_worker);
+
head += `
- `;
+ `;
}
}
if (state.prerender) {
- /** @type {Array<{ key: string, value: string }>} */
const http_equiv = [];
- if (maxage) {
- http_equiv.push({ key: 'cache-control', value: `max-age=${maxage}` });
+ const csp_headers = csp.get_meta();
+ if (csp_headers) {
+ http_equiv.push(csp_headers);
}
- const tags = http_equiv.map(
- ({ key, value }) => ``
- );
+ if (maxage) {
+ http_equiv.push(``);
+ }
- head = tags + head;
+ if (http_equiv.length > 0) {
+ head = http_equiv.join('\n') + head;
+ }
}
const segments = url.pathname.slice(options.paths.base.length).split('/').slice(2);
const assets =
options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
- const html = options.template({ head, body, assets, nonce });
+ const html = options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) });
const headers = new Headers({
'content-type': 'text/html',
@@ -274,12 +289,42 @@ export async function render_response({
headers.set('permissions-policy', 'interest-cohort=()');
}
+ if (!state.prerender) {
+ headers.set('content-security-policy', csp.get_header());
+ }
+
return new Response(html, {
status,
headers
});
}
+/**
+ *
+ * @param {string} type
+ * @param {Record} attributes
+ * @param {string} [content]
+ */
+function element(type, attributes, content) {
+ let result = `<${type}`;
+
+ for (const key in attributes) {
+ const value = attributes[key];
+ result += ' ' + key;
+ if (value !== true) {
+ result += escape_html_attr(value);
+ }
+ }
+
+ if (content) {
+ result += `>${content}${type}>`;
+ } else {
+ result += '/>';
+ }
+
+ return result;
+}
+
/**
* @param {any} data
* @param {(error: Error) => void} [fail]
diff --git a/packages/kit/types/csp.d.ts b/packages/kit/types/csp.d.ts
index 5b390dd12a9f..09c8b101ef9b 100644
--- a/packages/kit/types/csp.d.ts
+++ b/packages/kit/types/csp.d.ts
@@ -37,7 +37,7 @@ type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
type BaseSource = 'self' | 'unsafe-eval' | 'unsafe-hashes' | 'unsafe-inline' | 'none';
-type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
+export type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
type Sources = Source[];
type ActionSource = 'strict-dynamic' | 'report-sample';
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 7163d61424f0..8412a7a72dfe 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -136,7 +136,18 @@ export interface SSRRenderOptions {
router: boolean;
service_worker?: string;
target: string;
- template({ head, body, assets }: { head: string; body: string; assets: string }): string;
+ template({
+ head,
+ body,
+ assets,
+ nonce
+ }: {
+ head: string;
+ body: string;
+ assets: string;
+ nonce: string;
+ }): string;
+ template_contains_nonce: boolean;
trailing_slash: TrailingSlash;
}
From e86d2f0a8f1fa02b3e11440d0bb605431fba249b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 24 Jan 2022 21:33:35 -0500
Subject: [PATCH 09/60] fixes
---
packages/kit/src/core/build/build_server.js | 2 +-
packages/kit/src/runtime/server/page/render.js | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js
index c8d24d3a49e9..5f0954e49115 100644
--- a/packages/kit/src/core/build/build_server.js
+++ b/packages/kit/src/core/build/build_server.js
@@ -28,7 +28,7 @@ import * as user_hooks from ${s(hooks)};
const template = ({ head, body, assets, nonce }) => ${s(template)
.replace('%svelte.head%', '" + head + "')
.replace('%svelte.body%', '" + body + "')
- .replace(/%svelte\.assets%/g, '" + assets + "')}
+ .replace(/%svelte\.assets%/g, '" + assets + "')
.replace(/%svelte\.nonce%/g, '" + nonce + "')};
let read = null;
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 2bd082599081..7047857978dc 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -290,7 +290,10 @@ export async function render_response({
}
if (!state.prerender) {
- headers.set('content-security-policy', csp.get_header());
+ const csp_header = csp.get_header();
+ if (csp_header) {
+ headers.set('content-security-policy', csp_header);
+ }
}
return new Response(html, {
From c533bf80ea524ff09b015db48303ff2269968089 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 24 Jan 2022 21:39:00 -0500
Subject: [PATCH 10/60] fix
---
packages/kit/src/runtime/server/page/render.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 7047857978dc..6be03a0a56bf 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -214,7 +214,7 @@ export async function render_response({
attributes.push('disabled', 'media="(max-width: 0)"');
}
- return `\n\t`
+ return `\n\t`
})
.join('');
From a71d0d6c6aeb586a711b4350ec9e5de3361d32b2 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 24 Jan 2022 21:43:32 -0500
Subject: [PATCH 11/60] lint
---
packages/kit/src/runtime/server/page/csp.js | 11 ++++++--
.../kit/src/runtime/server/page/render.js | 28 +------------------
2 files changed, 9 insertions(+), 30 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 8a185408b6b0..95b50d723726 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -184,21 +184,25 @@ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
/** @param {Uint8Array} bytes */
function base64(bytes) {
- let result = '',
- i,
- l = bytes.length;
+ const l = bytes.length;
+
+ let result = '';
+ let i;
+
for (i = 2; i < l; i += 3) {
result += chars[bytes[i - 2] >> 2];
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
result += chars[bytes[i] & 0x3f];
}
+
if (i === l + 1) {
// 1 octet yet to write
result += chars[bytes[i - 2] >> 2];
result += chars[(bytes[i - 2] & 0x03) << 4];
result += '==';
}
+
if (i === l) {
// 2 octets yet to write
result += chars[bytes[i - 2] >> 2];
@@ -206,5 +210,6 @@ function base64(bytes) {
result += chars[(bytes[i - 1] & 0x0f) << 2];
result += '=';
}
+
return result;
}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 6be03a0a56bf..39a13eec5534 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -214,7 +214,7 @@ export async function render_response({
attributes.push('disabled', 'media="(max-width: 0)"');
}
- return `\n\t`
+ return `\n\t`;
})
.join('');
@@ -302,32 +302,6 @@ export async function render_response({
});
}
-/**
- *
- * @param {string} type
- * @param {Record} attributes
- * @param {string} [content]
- */
-function element(type, attributes, content) {
- let result = `<${type}`;
-
- for (const key in attributes) {
- const value = attributes[key];
- result += ' ' + key;
- if (value !== true) {
- result += escape_html_attr(value);
- }
- }
-
- if (content) {
- result += `>${content}${type}>`;
- } else {
- result += '/>';
- }
-
- return result;
-}
-
/**
* @param {any} data
* @param {(error: Error) => void} [fail]
From 4d519c371be698b7e105e3c2daf96e08277f1f29 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 24 Jan 2022 21:54:13 -0500
Subject: [PATCH 12/60] add test to show CSP headers are working
---
.../options/source/pages/csp/index.svelte | 7 ++++++
packages/kit/test/apps/options/test/test.js | 23 +++++++++++++++++++
2 files changed, 30 insertions(+)
create mode 100644 packages/kit/test/apps/options/source/pages/csp/index.svelte
diff --git a/packages/kit/test/apps/options/source/pages/csp/index.svelte b/packages/kit/test/apps/options/source/pages/csp/index.svelte
new file mode 100644
index 000000000000..8b0c1ae6ba35
--- /dev/null
+++ b/packages/kit/test/apps/options/source/pages/csp/index.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js
index d2d7b818fa3f..09221e5b3a6d 100644
--- a/packages/kit/test/apps/options/test/test.js
+++ b/packages/kit/test/apps/options/test/test.js
@@ -1,4 +1,5 @@
import { expect } from '@playwright/test';
+import { start_server } from '../../../utils.js';
import { test } from '../../../utils.js';
/** @typedef {import('@playwright/test').Response} Response */
@@ -78,6 +79,28 @@ test.describe.parallel('base path', () => {
});
});
+test.describe.parallel('CSP', () => {
+ test('blocks script from external site', async ({ page }) => {
+ const { server, port } = await start_server((req, res) => {
+ if (req.url === '/blocked.js') {
+ res.writeHead(200, {
+ 'content-type': 'text/javascript'
+ });
+
+ res.end('window.pwned = true');
+ } else {
+ res.writeHead(404).end('not found');
+ }
+ });
+
+ await page.goto(`/path-base/csp?port=${port}`);
+
+ expect(await page.evaluate('window.pwned')).toBe(undefined);
+
+ server.close();
+ });
+});
+
test.describe.parallel('Custom extensions', () => {
test('works with arbitrary extensions', async ({ page }) => {
await page.goto('/path-base/custom-extensions/');
From 750794e9c33cfc71b488697484f8b6cacaf59f38 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 24 Jan 2022 22:00:31 -0500
Subject: [PATCH 13/60] test for tags
---
.../prerendering/options/svelte.config.js | 8 ++++++++
.../test/prerendering/options/test/test.js | 9 +++++++++
pnpm-lock.yaml | 20 +++++++++++++++++++
pnpm-workspace.yaml | 1 +
4 files changed, 38 insertions(+)
diff --git a/packages/kit/test/prerendering/options/svelte.config.js b/packages/kit/test/prerendering/options/svelte.config.js
index 55f288d4a2a3..ac5499a0da49 100644
--- a/packages/kit/test/prerendering/options/svelte.config.js
+++ b/packages/kit/test/prerendering/options/svelte.config.js
@@ -5,10 +5,18 @@ import adapter from '../../../../adapter-static/index.js';
const config = {
kit: {
adapter: adapter(),
+
+ csp: {
+ directives: {
+ 'script-src': ['self']
+ }
+ },
+
paths: {
base: '/path-base',
assets: 'https://cdn.example.com/stuff'
},
+
vite: {
build: {
minify: false
diff --git a/packages/kit/test/prerendering/options/test/test.js b/packages/kit/test/prerendering/options/test/test.js
index 6aaaef278c5d..260d1f6f7532 100644
--- a/packages/kit/test/prerendering/options/test/test.js
+++ b/packages/kit/test/prerendering/options/test/test.js
@@ -20,4 +20,13 @@ test('prerenders nested /path-base', () => {
assert.ok(content.includes('http://sveltekit-prerender/path-base/nested'));
});
+test('adds CSP headers via meta tag', () => {
+ const content = read('index.html');
+ assert.ok(
+ content.includes(
+ `
Date: Tue, 25 Jan 2022 09:39:43 -0500
Subject: [PATCH 17/60] add relevant subset of sjcl
---
.../kit/src/runtime/server/page/crypto.js | 81 ++
.../src/runtime/server/page/crypto.spec.js | 17 +
packages/kit/src/runtime/server/page/sjcl.js | 802 ++++++++++++++++++
3 files changed, 900 insertions(+)
create mode 100644 packages/kit/src/runtime/server/page/crypto.js
create mode 100644 packages/kit/src/runtime/server/page/crypto.spec.js
create mode 100644 packages/kit/src/runtime/server/page/sjcl.js
diff --git a/packages/kit/src/runtime/server/page/crypto.js b/packages/kit/src/runtime/server/page/crypto.js
new file mode 100644
index 000000000000..450cc310a808
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/crypto.js
@@ -0,0 +1,81 @@
+import { sjcl } from './sjcl.js';
+
+// adapted from https://bitwiseshiftleft.github.io/sjcl/,
+// modified and redistributed under BSD license
+
+/** @param {string} text */
+export function sha256(text) {
+ const hashed = new Uint32Array(sjcl.hash.sha256.hash(text));
+ const buffer = hashed.buffer;
+ const uint8array = new Uint8Array(buffer);
+
+ // bitArray is big endian, uint32array is little endian, so we need to do this:
+ for (let i = 0; i < uint8array.length; i += 4) {
+ const a = uint8array[i + 0];
+ const b = uint8array[i + 1];
+ const c = uint8array[i + 2];
+ const d = uint8array[i + 3];
+
+ uint8array[i + 0] = d;
+ uint8array[i + 1] = c;
+ uint8array[i + 2] = b;
+ uint8array[i + 3] = a;
+ }
+
+ return base64(uint8array);
+}
+
+/*
+ Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
+
+ MIT License
+ Copyright (c) 2020 Egor Nepomnyaschih
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+/** @param {Uint8Array} bytes */
+function base64(bytes) {
+ const l = bytes.length;
+
+ let result = '';
+ let i;
+
+ for (i = 2; i < l; i += 3) {
+ result += chars[bytes[i - 2] >> 2];
+ result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+ result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
+ result += chars[bytes[i] & 0x3f];
+ }
+
+ if (i === l + 1) {
+ // 1 octet yet to write
+ result += chars[bytes[i - 2] >> 2];
+ result += chars[(bytes[i - 2] & 0x03) << 4];
+ result += '==';
+ }
+
+ if (i === l) {
+ // 2 octets yet to write
+ result += chars[bytes[i - 2] >> 2];
+ result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+ result += chars[(bytes[i - 1] & 0x0f) << 2];
+ result += '=';
+ }
+
+ return result;
+}
diff --git a/packages/kit/src/runtime/server/page/crypto.spec.js b/packages/kit/src/runtime/server/page/crypto.spec.js
new file mode 100644
index 000000000000..d620f39b514a
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/crypto.spec.js
@@ -0,0 +1,17 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import crypto from 'crypto';
+import { sha256 } from './crypto.js';
+
+const inputs = ['the quick brown fox jumps over the lazy dog', '工欲善其事,必先利其器'];
+
+inputs.forEach((input) => {
+ test(input, () => {
+ const expected_bytes = crypto.createHash('sha256').update(input, 'utf-8').digest();
+ const expected = expected_bytes.toString('base64');
+
+ assert.equal(sha256(input), expected);
+ });
+});
+
+test.run();
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
new file mode 100644
index 000000000000..ee9d9dc90d63
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -0,0 +1,802 @@
+export const sjcl = {
+ hash: {},
+ codec: {}
+};
+
+/** @fileOverview Bit array codec implementations.
+
+ *
+
+ * @author Emily Stark
+
+ * @author Mike Hamburg
+
+ * @author Dan Boneh
+
+ */
+
+/**
+
+ * UTF-8 strings
+
+ * @namespace
+
+ */
+
+sjcl.codec.utf8String = {
+ /** Convert from a bitArray to a UTF-8 string. */
+
+ fromBits: function (arr) {
+ var out = '',
+ bl = sjcl.bitArray.bitLength(arr),
+ i,
+ tmp;
+
+ for (i = 0; i < bl / 8; i++) {
+ if ((i & 3) === 0) {
+ tmp = arr[i / 4];
+ }
+
+ out += String.fromCharCode(tmp >>> 24);
+
+ tmp <<= 8;
+ }
+
+ return decodeURIComponent(escape(out));
+ },
+
+ /** Convert from a UTF-8 string to a bitArray. */
+
+ toBits: function (str) {
+ str = unescape(encodeURIComponent(str));
+
+ var out = [],
+ i,
+ tmp = 0;
+
+ for (i = 0; i < str.length; i++) {
+ tmp = (tmp << 8) | str.charCodeAt(i);
+
+ if ((i & 3) === 3) {
+ out.push(tmp);
+
+ tmp = 0;
+ }
+ }
+
+ if (i & 3) {
+ out.push(sjcl.bitArray.partial(8 * (i & 3), tmp));
+ }
+
+ return out;
+ }
+};
+
+/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
+
+ *
+
+ * @author Emily Stark
+
+ * @author Mike Hamburg
+
+ * @author Dan Boneh
+
+ */
+
+/**
+
+ * Arrays of bits, encoded as arrays of Numbers.
+
+ * @namespace
+
+ * @description
+
+ *
+
+ * These objects are the currency accepted by SJCL's crypto functions.
+
+ *
+
+ *
+
+ *
+
+ * Most of our crypto primitives operate on arrays of 4-byte words internally,
+
+ * but many of them can take arguments that are not a multiple of 4 bytes.
+
+ * This library encodes arrays of bits (whose size need not be a multiple of 8
+
+ * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
+
+ * array of words, 32 bits at a time. Since the words are double-precision
+
+ * floating point numbers, they fit some extra data. We use this (in a private,
+
+ * possibly-changing manner) to encode the number of bits actually present
+
+ * in the last word of the array.
+
+ *
+
+ *
+
+ *
+
+ * Because bitwise ops clear this out-of-band data, these arrays can be passed
+
+ * to ciphers like AES which want arrays of words.
+
+ *
+
+ */
+
+sjcl.bitArray = {
+ /**
+
+ * Array slices in units of bits.
+
+ * @param {bitArray} a The array to slice.
+
+ * @param {Number} bstart The offset to the start of the slice, in bits.
+
+ * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
+
+ * slice until the end of the array.
+
+ * @return {bitArray} The requested slice.
+
+ */
+
+ bitSlice: function (a, bstart, bend) {
+ a = sjcl.bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
+
+ return bend === undefined ? a : sjcl.bitArray.clamp(a, bend - bstart);
+ },
+
+ /**
+
+ * Extract a number packed into a bit array.
+
+ * @param {bitArray} a The array to slice.
+
+ * @param {Number} bstart The offset to the start of the slice, in bits.
+
+ * @param {Number} blength The length of the number to extract.
+
+ * @return {Number} The requested slice.
+
+ */
+
+ extract: function (a, bstart, blength) {
+ // FIXME: this Math.floor is not necessary at all, but for some reason
+
+ // seems to suppress a bug in the Chromium JIT.
+
+ var x,
+ sh = Math.floor((-bstart - blength) & 31);
+
+ if (((bstart + blength - 1) ^ bstart) & -32) {
+ // it crosses a boundary
+
+ x = (a[(bstart / 32) | 0] << (32 - sh)) ^ (a[(bstart / 32 + 1) | 0] >>> sh);
+ } else {
+ // within a single word
+
+ x = a[(bstart / 32) | 0] >>> sh;
+ }
+
+ return x & ((1 << blength) - 1);
+ },
+
+ /**
+
+ * Concatenate two bit arrays.
+
+ * @param {bitArray} a1 The first array.
+
+ * @param {bitArray} a2 The second array.
+
+ * @return {bitArray} The concatenation of a1 and a2.
+
+ */
+
+ concat: function (a1, a2) {
+ if (a1.length === 0 || a2.length === 0) {
+ return a1.concat(a2);
+ }
+
+ var last = a1[a1.length - 1],
+ shift = sjcl.bitArray.getPartial(last);
+
+ if (shift === 32) {
+ return a1.concat(a2);
+ } else {
+ return sjcl.bitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
+ }
+ },
+
+ /**
+
+ * Find the length of an array of bits.
+
+ * @param {bitArray} a The array.
+
+ * @return {Number} The length of a, in bits.
+
+ */
+
+ bitLength: function (a) {
+ var l = a.length,
+ x;
+
+ if (l === 0) {
+ return 0;
+ }
+
+ x = a[l - 1];
+
+ return (l - 1) * 32 + sjcl.bitArray.getPartial(x);
+ },
+
+ /**
+
+ * Truncate an array.
+
+ * @param {bitArray} a The array.
+
+ * @param {Number} len The length to truncate to, in bits.
+
+ * @return {bitArray} A new array, truncated to len bits.
+
+ */
+
+ clamp: function (a, len) {
+ if (a.length * 32 < len) {
+ return a;
+ }
+
+ a = a.slice(0, Math.ceil(len / 32));
+
+ var l = a.length;
+
+ len = len & 31;
+
+ if (l > 0 && len) {
+ a[l - 1] = sjcl.bitArray.partial(len, a[l - 1] & (0x80000000 >> (len - 1)), 1);
+ }
+
+ return a;
+ },
+
+ /**
+
+ * Make a partial word for a bit array.
+
+ * @param {Number} len The number of bits in the word.
+
+ * @param {Number} x The bits.
+
+ * @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
+
+ * @return {Number} The partial word.
+
+ */
+
+ partial: function (len, x, _end) {
+ if (len === 32) {
+ return x;
+ }
+
+ return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
+ },
+
+ /**
+
+ * Get the number of bits used by a partial word.
+
+ * @param {Number} x The partial word.
+
+ * @return {Number} The number of bits used by the partial word.
+
+ */
+
+ getPartial: function (x) {
+ return Math.round(x / 0x10000000000) || 32;
+ },
+
+ /**
+
+ * Compare two arrays for equality in a predictable amount of time.
+
+ * @param {bitArray} a The first array.
+
+ * @param {bitArray} b The second array.
+
+ * @return {boolean} true if a == b; false otherwise.
+
+ */
+
+ equal: function (a, b) {
+ if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
+ return false;
+ }
+
+ var x = 0,
+ i;
+
+ for (i = 0; i < a.length; i++) {
+ x |= a[i] ^ b[i];
+ }
+
+ return x === 0;
+ },
+
+ /** Shift an array right.
+
+ * @param {bitArray} a The array to shift.
+
+ * @param {Number} shift The number of bits to shift.
+
+ * @param {Number} [carry=0] A byte to carry in
+
+ * @param {bitArray} [out=[]] An array to prepend to the output.
+
+ * @private
+
+ */
+
+ _shiftRight: function (a, shift, carry, out) {
+ var i,
+ last2 = 0,
+ shift2;
+
+ if (out === undefined) {
+ out = [];
+ }
+
+ for (; shift >= 32; shift -= 32) {
+ out.push(carry);
+
+ carry = 0;
+ }
+
+ if (shift === 0) {
+ return out.concat(a);
+ }
+
+ for (i = 0; i < a.length; i++) {
+ out.push(carry | (a[i] >>> shift));
+
+ carry = a[i] << (32 - shift);
+ }
+
+ last2 = a.length ? a[a.length - 1] : 0;
+
+ shift2 = sjcl.bitArray.getPartial(last2);
+
+ out.push(
+ sjcl.bitArray.partial((shift + shift2) & 31, shift + shift2 > 32 ? carry : out.pop(), 1)
+ );
+
+ return out;
+ },
+
+ /** xor a block of 4 words together.
+
+ * @private
+
+ */
+
+ _xor4: function (x, y) {
+ return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
+ },
+
+ /** byteswap a word array inplace.
+
+ * (does not handle partial words)
+
+ * @param {sjcl.bitArray} a word array
+
+ * @return {sjcl.bitArray} byteswapped array
+
+ */
+
+ byteswapM: function (a) {
+ var i,
+ v,
+ m = 0xff00;
+
+ for (i = 0; i < a.length; ++i) {
+ v = a[i];
+
+ a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
+ }
+
+ return a;
+ }
+};
+
+/** @fileOverview Javascript SHA-256 implementation.
+
+ *
+
+ * An older version of this implementation is available in the public
+
+ * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
+
+ * Stanford University 2008-2010 and BSD-licensed for liability
+
+ * reasons.
+
+ *
+
+ * Special thanks to Aldo Cortesi for pointing out several bugs in
+
+ * this code.
+
+ *
+
+ * @author Emily Stark
+
+ * @author Mike Hamburg
+
+ * @author Dan Boneh
+
+ */
+
+/**
+
+ * Context for a SHA-256 operation in progress.
+
+ * @constructor
+
+ */
+
+sjcl.hash.sha256 = function (hash) {
+ if (!this._key[0]) {
+ this._precompute();
+ }
+
+ if (hash) {
+ this._h = hash._h.slice(0);
+
+ this._buffer = hash._buffer.slice(0);
+
+ this._length = hash._length;
+ } else {
+ this.reset();
+ }
+};
+
+/**
+
+ * Hash a string or an array of words.
+
+ * @static
+
+ * @param {bitArray|String} data the data to hash.
+
+ * @return {bitArray} The hash value, an array of 16 big-endian words.
+
+ */
+
+sjcl.hash.sha256.hash = function (data) {
+ return new sjcl.hash.sha256().update(data).finalize();
+};
+
+sjcl.hash.sha256.prototype = {
+ /**
+
+ * The hash's block size, in bits.
+
+ * @constant
+
+ */
+
+ blockSize: 512,
+
+ /**
+
+ * Reset the hash state.
+
+ * @return this
+
+ */
+
+ reset: function () {
+ this._h = this._init.slice(0);
+
+ this._buffer = [];
+
+ this._length = 0;
+
+ return this;
+ },
+
+ /**
+
+ * Input several words to the hash.
+
+ * @param {bitArray|String} data the data to hash.
+
+ * @return this
+
+ */
+
+ update: function (data) {
+ if (typeof data === 'string') {
+ data = sjcl.codec.utf8String.toBits(data);
+ }
+
+ var i,
+ b = (this._buffer = sjcl.bitArray.concat(this._buffer, data)),
+ ol = this._length,
+ nl = (this._length = ol + sjcl.bitArray.bitLength(data));
+
+ if (nl > 9007199254740991) {
+ throw new sjcl.exception.invalid('Cannot hash more than 2^53 - 1 bits');
+ }
+
+ if (typeof Uint32Array !== 'undefined') {
+ var c = new Uint32Array(b);
+
+ var j = 0;
+
+ for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
+ this._block(c.subarray(16 * j, 16 * (j + 1)));
+
+ j += 1;
+ }
+
+ b.splice(0, 16 * j);
+ } else {
+ for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
+ this._block(b.splice(0, 16));
+ }
+ }
+
+ return this;
+ },
+
+ /**
+
+ * Complete hashing and output the hash value.
+
+ * @return {bitArray} The hash value, an array of 8 big-endian words.
+
+ */
+
+ finalize: function () {
+ var i,
+ b = this._buffer,
+ h = this._h;
+
+ // Round out and push the buffer
+
+ b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1, 1)]);
+
+ // Round out the buffer to a multiple of 16 words, less the 2 length words.
+
+ for (i = b.length + 2; i & 15; i++) {
+ b.push(0);
+ }
+
+ // append the length
+
+ b.push(Math.floor(this._length / 0x100000000));
+
+ b.push(this._length | 0);
+
+ while (b.length) {
+ this._block(b.splice(0, 16));
+ }
+
+ this.reset();
+
+ return h;
+ },
+
+ /**
+
+ * The SHA-256 initialization vector, to be precomputed.
+
+ * @private
+
+ */
+
+ _init: [],
+
+ /*
+
+ _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
+
+ */
+
+ /**
+
+ * The SHA-256 hash key, to be precomputed.
+
+ * @private
+
+ */
+
+ _key: [],
+
+ /*
+
+ _key:
+
+ [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
+
+ */
+
+ /**
+
+ * Function to precompute _init and _key.
+
+ * @private
+
+ */
+
+ _precompute: function () {
+ var i = 0,
+ prime = 2,
+ factor,
+ isPrime;
+
+ function frac(x) {
+ return ((x - Math.floor(x)) * 0x100000000) | 0;
+ }
+
+ for (; i < 64; prime++) {
+ isPrime = true;
+
+ for (factor = 2; factor * factor <= prime; factor++) {
+ if (prime % factor === 0) {
+ isPrime = false;
+
+ break;
+ }
+ }
+
+ if (isPrime) {
+ if (i < 8) {
+ this._init[i] = frac(Math.pow(prime, 1 / 2));
+ }
+
+ this._key[i] = frac(Math.pow(prime, 1 / 3));
+
+ i++;
+ }
+ }
+ },
+
+ /**
+
+ * Perform one cycle of SHA-256.
+
+ * @param {Uint32Array|bitArray} w one block of words.
+
+ * @private
+
+ */
+
+ _block: function (w) {
+ var i,
+ tmp,
+ a,
+ b,
+ h = this._h,
+ k = this._key,
+ h0 = h[0],
+ h1 = h[1],
+ h2 = h[2],
+ h3 = h[3],
+ h4 = h[4],
+ h5 = h[5],
+ h6 = h[6],
+ h7 = h[7];
+
+ /* Rationale for placement of |0 :
+
+ * If a value can overflow is original 32 bits by a factor of more than a few
+
+ * million (2^23 ish), there is a possibility that it might overflow the
+
+ * 53-bit mantissa and lose precision.
+
+ *
+
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+
+ * propagates around the loop, and on the hash state h[]. I don't believe
+
+ * that the clamps on h4 and on h0 are strictly necessary, but it's close
+
+ * (for h4 anyway), and better safe than sorry.
+
+ *
+
+ * The clamps on h[] are necessary for the output to be correct even in the
+
+ * common case and for short inputs.
+
+ */
+
+ for (i = 0; i < 64; i++) {
+ // load up the input word for this round
+
+ if (i < 16) {
+ tmp = w[i];
+ } else {
+ a = w[(i + 1) & 15];
+
+ b = w[(i + 14) & 15];
+
+ tmp = w[i & 15] =
+ (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
+ ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
+ w[i & 15] +
+ w[(i + 9) & 15]) |
+ 0;
+ }
+
+ tmp =
+ tmp +
+ h7 +
+ ((h4 >>> 6) ^ (h4 >>> 11) ^ (h4 >>> 25) ^ (h4 << 26) ^ (h4 << 21) ^ (h4 << 7)) +
+ (h6 ^ (h4 & (h5 ^ h6))) +
+ k[i]; // | 0;
+
+ // shift register
+
+ h7 = h6;
+ h6 = h5;
+ h5 = h4;
+
+ h4 = (h3 + tmp) | 0;
+
+ h3 = h2;
+ h2 = h1;
+ h1 = h0;
+
+ h0 =
+ (tmp +
+ ((h1 & h2) ^ (h3 & (h1 ^ h2))) +
+ ((h1 >>> 2) ^ (h1 >>> 13) ^ (h1 >>> 22) ^ (h1 << 30) ^ (h1 << 19) ^ (h1 << 10))) |
+ 0;
+ }
+
+ h[0] = (h[0] + h0) | 0;
+
+ h[1] = (h[1] + h1) | 0;
+
+ h[2] = (h[2] + h2) | 0;
+
+ h[3] = (h[3] + h3) | 0;
+
+ h[4] = (h[4] + h4) | 0;
+
+ h[5] = (h[5] + h5) | 0;
+
+ h[6] = (h[6] + h6) | 0;
+
+ h[7] = (h[7] + h7) | 0;
+ }
+};
From 003b119b6fa8b80002e2e09d8558516646d50bf3 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:12:18 -0500
Subject: [PATCH 18/60] start tidying up
---
packages/kit/src/runtime/server/page/sjcl.js | 319 +++----------------
1 file changed, 49 insertions(+), 270 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index ee9d9dc90d63..c45d73e24f1e 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -1,189 +1,108 @@
export const sjcl = {
- hash: {},
- codec: {}
+ hash: {}
};
-/** @fileOverview Bit array codec implementations.
+/** @param {Uint32Array} uint32array' */
+function swap_endianness(uint32array) {
+ const uint8array = new Uint8Array(uint32array.buffer);
- *
-
- * @author Emily Stark
-
- * @author Mike Hamburg
-
- * @author Dan Boneh
+ for (let i = 0; i < uint8array.length; i += 4) {
+ const a = uint8array[i + 0];
+ const b = uint8array[i + 1];
+ const c = uint8array[i + 2];
+ const d = uint8array[i + 3];
- */
-
-/**
-
- * UTF-8 strings
-
- * @namespace
-
- */
-
-sjcl.codec.utf8String = {
- /** Convert from a bitArray to a UTF-8 string. */
-
- fromBits: function (arr) {
- var out = '',
- bl = sjcl.bitArray.bitLength(arr),
- i,
- tmp;
-
- for (i = 0; i < bl / 8; i++) {
- if ((i & 3) === 0) {
- tmp = arr[i / 4];
- }
+ uint8array[i + 0] = d;
+ uint8array[i + 1] = c;
+ uint8array[i + 2] = b;
+ uint8array[i + 3] = a;
+ }
- out += String.fromCharCode(tmp >>> 24);
+ return uint32array;
+}
- tmp <<= 8;
- }
+/** @param {string} str */
+function toBits(str) {
+ str = unescape(encodeURIComponent(str));
- return decodeURIComponent(escape(out));
- },
+ var out = [],
+ i,
+ tmp = 0;
- /** Convert from a UTF-8 string to a bitArray. */
+ for (i = 0; i < str.length; i++) {
+ tmp = (tmp << 8) | str.charCodeAt(i);
- toBits: function (str) {
- str = unescape(encodeURIComponent(str));
+ if ((i & 3) === 3) {
+ out.push(tmp);
- var out = [],
- i,
tmp = 0;
-
- for (i = 0; i < str.length; i++) {
- tmp = (tmp << 8) | str.charCodeAt(i);
-
- if ((i & 3) === 3) {
- out.push(tmp);
-
- tmp = 0;
- }
}
-
- if (i & 3) {
- out.push(sjcl.bitArray.partial(8 * (i & 3), tmp));
- }
-
- return out;
}
-};
-
-/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
-
- *
-
- * @author Emily Stark
- * @author Mike Hamburg
+ if (i & 3) {
+ out.push(sjcl.bitArray.partial(8 * (i & 3), tmp));
+ }
- * @author Dan Boneh
-
- */
+ return out;
+}
/**
-
* Arrays of bits, encoded as arrays of Numbers.
-
* @namespace
-
* @description
-
*
-
* These objects are the currency accepted by SJCL's crypto functions.
-
*
-
*
-
*
-
* Most of our crypto primitives operate on arrays of 4-byte words internally,
-
* but many of them can take arguments that are not a multiple of 4 bytes.
-
* This library encodes arrays of bits (whose size need not be a multiple of 8
-
* bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
-
* array of words, 32 bits at a time. Since the words are double-precision
-
* floating point numbers, they fit some extra data. We use this (in a private,
-
* possibly-changing manner) to encode the number of bits actually present
-
* in the last word of the array.
-
*
-
*
-
*
-
* Because bitwise ops clear this out-of-band data, these arrays can be passed
-
* to ciphers like AES which want arrays of words.
-
*
-
*/
sjcl.bitArray = {
/**
-
* Array slices in units of bits.
-
* @param {bitArray} a The array to slice.
-
* @param {Number} bstart The offset to the start of the slice, in bits.
-
* @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
-
* slice until the end of the array.
-
* @return {bitArray} The requested slice.
-
*/
-
bitSlice: function (a, bstart, bend) {
a = sjcl.bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
-
return bend === undefined ? a : sjcl.bitArray.clamp(a, bend - bstart);
},
/**
-
* Extract a number packed into a bit array.
-
* @param {bitArray} a The array to slice.
-
* @param {Number} bstart The offset to the start of the slice, in bits.
-
* @param {Number} blength The length of the number to extract.
-
* @return {Number} The requested slice.
-
*/
-
extract: function (a, bstart, blength) {
// FIXME: this Math.floor is not necessary at all, but for some reason
-
// seems to suppress a bug in the Chromium JIT.
-
var x,
sh = Math.floor((-bstart - blength) & 31);
if (((bstart + blength - 1) ^ bstart) & -32) {
// it crosses a boundary
-
x = (a[(bstart / 32) | 0] << (32 - sh)) ^ (a[(bstart / 32 + 1) | 0] >>> sh);
} else {
// within a single word
-
x = a[(bstart / 32) | 0] >>> sh;
}
@@ -191,17 +110,11 @@ sjcl.bitArray = {
},
/**
-
* Concatenate two bit arrays.
-
* @param {bitArray} a1 The first array.
-
* @param {bitArray} a2 The second array.
-
* @return {bitArray} The concatenation of a1 and a2.
-
*/
-
concat: function (a1, a2) {
if (a1.length === 0 || a2.length === 0) {
return a1.concat(a2);
@@ -218,15 +131,10 @@ sjcl.bitArray = {
},
/**
-
* Find the length of an array of bits.
-
* @param {bitArray} a The array.
-
* @return {Number} The length of a, in bits.
-
*/
-
bitLength: function (a) {
var l = a.length,
x;
@@ -241,17 +149,11 @@ sjcl.bitArray = {
},
/**
-
* Truncate an array.
-
* @param {bitArray} a The array.
-
* @param {Number} len The length to truncate to, in bits.
-
* @return {bitArray} A new array, truncated to len bits.
-
*/
-
clamp: function (a, len) {
if (a.length * 32 < len) {
return a;
@@ -271,19 +173,12 @@ sjcl.bitArray = {
},
/**
-
* Make a partial word for a bit array.
-
* @param {Number} len The number of bits in the word.
-
* @param {Number} x The bits.
-
* @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
-
* @return {Number} The partial word.
-
*/
-
partial: function (len, x, _end) {
if (len === 32) {
return x;
@@ -293,29 +188,19 @@ sjcl.bitArray = {
},
/**
-
* Get the number of bits used by a partial word.
-
* @param {Number} x The partial word.
-
* @return {Number} The number of bits used by the partial word.
-
*/
-
getPartial: function (x) {
return Math.round(x / 0x10000000000) || 32;
},
/**
-
* Compare two arrays for equality in a predictable amount of time.
-
* @param {bitArray} a The first array.
-
* @param {bitArray} b The second array.
-
* @return {boolean} true if a == b; false otherwise.
-
*/
equal: function (a, b) {
@@ -334,19 +219,12 @@ sjcl.bitArray = {
},
/** Shift an array right.
-
* @param {bitArray} a The array to shift.
-
* @param {Number} shift The number of bits to shift.
-
* @param {Number} [carry=0] A byte to carry in
-
* @param {bitArray} [out=[]] An array to prepend to the output.
-
* @private
-
*/
-
_shiftRight: function (a, shift, carry, out) {
var i,
last2 = 0,
@@ -384,25 +262,17 @@ sjcl.bitArray = {
},
/** xor a block of 4 words together.
-
* @private
-
*/
-
_xor4: function (x, y) {
return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
},
/** byteswap a word array inplace.
-
* (does not handle partial words)
-
* @param {sjcl.bitArray} a word array
-
* @return {sjcl.bitArray} byteswapped array
-
*/
-
byteswapM: function (a) {
var i,
v,
@@ -419,39 +289,22 @@ sjcl.bitArray = {
};
/** @fileOverview Javascript SHA-256 implementation.
-
*
-
* An older version of this implementation is available in the public
-
* domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
-
* Stanford University 2008-2010 and BSD-licensed for liability
-
* reasons.
-
*
-
* Special thanks to Aldo Cortesi for pointing out several bugs in
-
* this code.
-
*
-
* @author Emily Stark
-
* @author Mike Hamburg
-
* @author Dan Boneh
-
*/
-
/**
-
* Context for a SHA-256 operation in progress.
-
* @constructor
-
*/
sjcl.hash.sha256 = function (hash) {
@@ -471,63 +324,41 @@ sjcl.hash.sha256 = function (hash) {
};
/**
-
- * Hash a string or an array of words.
-
- * @static
-
- * @param {bitArray|String} data the data to hash.
-
- * @return {bitArray} The hash value, an array of 16 big-endian words.
-
- */
-
+ * Hash a string or an array of words.
+ * @static
+ * @param {bitArray|String} data the data to hash.
+ * @return {bitArray} The hash value, an array of 16 big-endian words.
+ */
sjcl.hash.sha256.hash = function (data) {
return new sjcl.hash.sha256().update(data).finalize();
};
sjcl.hash.sha256.prototype = {
/**
-
* The hash's block size, in bits.
-
* @constant
-
*/
-
blockSize: 512,
/**
-
* Reset the hash state.
-
* @return this
-
*/
-
reset: function () {
this._h = this._init.slice(0);
-
this._buffer = [];
-
this._length = 0;
-
return this;
},
/**
-
* Input several words to the hash.
-
* @param {bitArray|String} data the data to hash.
-
* @return this
-
*/
-
update: function (data) {
if (typeof data === 'string') {
- data = sjcl.codec.utf8String.toBits(data);
+ data = toBits(data);
}
var i,
@@ -561,13 +392,9 @@ sjcl.hash.sha256.prototype = {
},
/**
-
* Complete hashing and output the hash value.
-
* @return {bitArray} The hash value, an array of 8 big-endian words.
-
*/
-
finalize: function () {
var i,
b = this._buffer,
@@ -599,61 +426,37 @@ sjcl.hash.sha256.prototype = {
},
/**
-
* The SHA-256 initialization vector, to be precomputed.
-
* @private
-
*/
-
_init: [],
/*
-
_init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
-
*/
/**
-
* The SHA-256 hash key, to be precomputed.
-
* @private
-
*/
-
_key: [],
/*
-
_key:
-
[0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
-
*/
/**
-
* Function to precompute _init and _key.
-
* @private
-
*/
-
_precompute: function () {
var i = 0,
prime = 2,
@@ -688,15 +491,10 @@ sjcl.hash.sha256.prototype = {
},
/**
-
* Perform one cycle of SHA-256.
-
* @param {Uint32Array|bitArray} w one block of words.
-
* @private
-
*/
-
_block: function (w) {
var i,
tmp,
@@ -714,30 +512,18 @@ sjcl.hash.sha256.prototype = {
h7 = h[7];
/* Rationale for placement of |0 :
-
- * If a value can overflow is original 32 bits by a factor of more than a few
-
- * million (2^23 ish), there is a possibility that it might overflow the
-
- * 53-bit mantissa and lose precision.
-
- *
-
- * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
-
- * propagates around the loop, and on the hash state h[]. I don't believe
-
- * that the clamps on h4 and on h0 are strictly necessary, but it's close
-
- * (for h4 anyway), and better safe than sorry.
-
- *
-
- * The clamps on h[] are necessary for the output to be correct even in the
-
- * common case and for short inputs.
-
- */
+ * If a value can overflow is original 32 bits by a factor of more than a few
+ * million (2^23 ish), there is a possibility that it might overflow the
+ * 53-bit mantissa and lose precision.
+ *
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+ * propagates around the loop, and on the hash state h[]. I don't believe
+ * that the clamps on h4 and on h0 are strictly necessary, but it's close
+ * (for h4 anyway), and better safe than sorry.
+ *
+ * The clamps on h[] are necessary for the output to be correct even in the
+ * common case and for short inputs.
+ */
for (i = 0; i < 64; i++) {
// load up the input word for this round
@@ -784,19 +570,12 @@ sjcl.hash.sha256.prototype = {
}
h[0] = (h[0] + h0) | 0;
-
h[1] = (h[1] + h1) | 0;
-
h[2] = (h[2] + h2) | 0;
-
h[3] = (h[3] + h3) | 0;
-
h[4] = (h[4] + h4) | 0;
-
h[5] = (h[5] + h5) | 0;
-
h[6] = (h[6] + h6) | 0;
-
h[7] = (h[7] + h7) | 0;
}
};
From 0e676fe6248c09f886c9f2e522bf8d508be775ca Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:18:18 -0500
Subject: [PATCH 19/60] remove some unused code
---
packages/kit/src/runtime/server/page/sjcl.js | 143 ++-----------------
1 file changed, 15 insertions(+), 128 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index c45d73e24f1e..7fd1c5d2a23c 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -40,7 +40,7 @@ function toBits(str) {
}
if (i & 3) {
- out.push(sjcl.bitArray.partial(8 * (i & 3), tmp));
+ out.push(BitArray.partial(8 * (i & 3), tmp));
}
return out;
@@ -71,44 +71,9 @@ function toBits(str) {
*
*/
-sjcl.bitArray = {
- /**
- * Array slices in units of bits.
- * @param {bitArray} a The array to slice.
- * @param {Number} bstart The offset to the start of the slice, in bits.
- * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
- * slice until the end of the array.
- * @return {bitArray} The requested slice.
- */
- bitSlice: function (a, bstart, bend) {
- a = sjcl.bitArray._shiftRight(a.slice(bstart / 32), 32 - (bstart & 31)).slice(1);
- return bend === undefined ? a : sjcl.bitArray.clamp(a, bend - bstart);
- },
-
- /**
- * Extract a number packed into a bit array.
- * @param {bitArray} a The array to slice.
- * @param {Number} bstart The offset to the start of the slice, in bits.
- * @param {Number} blength The length of the number to extract.
- * @return {Number} The requested slice.
- */
- extract: function (a, bstart, blength) {
- // FIXME: this Math.floor is not necessary at all, but for some reason
- // seems to suppress a bug in the Chromium JIT.
- var x,
- sh = Math.floor((-bstart - blength) & 31);
-
- if (((bstart + blength - 1) ^ bstart) & -32) {
- // it crosses a boundary
- x = (a[(bstart / 32) | 0] << (32 - sh)) ^ (a[(bstart / 32 + 1) | 0] >>> sh);
- } else {
- // within a single word
- x = a[(bstart / 32) | 0] >>> sh;
- }
-
- return x & ((1 << blength) - 1);
- },
+/** @typedef {number[]} bitArray */
+const BitArray = {
/**
* Concatenate two bit arrays.
* @param {bitArray} a1 The first array.
@@ -121,12 +86,12 @@ sjcl.bitArray = {
}
var last = a1[a1.length - 1],
- shift = sjcl.bitArray.getPartial(last);
+ shift = BitArray.getPartial(last);
if (shift === 32) {
return a1.concat(a2);
} else {
- return sjcl.bitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
+ return BitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
}
},
@@ -145,31 +110,7 @@ sjcl.bitArray = {
x = a[l - 1];
- return (l - 1) * 32 + sjcl.bitArray.getPartial(x);
- },
-
- /**
- * Truncate an array.
- * @param {bitArray} a The array.
- * @param {Number} len The length to truncate to, in bits.
- * @return {bitArray} A new array, truncated to len bits.
- */
- clamp: function (a, len) {
- if (a.length * 32 < len) {
- return a;
- }
-
- a = a.slice(0, Math.ceil(len / 32));
-
- var l = a.length;
-
- len = len & 31;
-
- if (l > 0 && len) {
- a[l - 1] = sjcl.bitArray.partial(len, a[l - 1] & (0x80000000 >> (len - 1)), 1);
- }
-
- return a;
+ return (l - 1) * 32 + BitArray.getPartial(x);
},
/**
@@ -196,44 +137,18 @@ sjcl.bitArray = {
return Math.round(x / 0x10000000000) || 32;
},
- /**
- * Compare two arrays for equality in a predictable amount of time.
- * @param {bitArray} a The first array.
- * @param {bitArray} b The second array.
- * @return {boolean} true if a == b; false otherwise.
- */
-
- equal: function (a, b) {
- if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
- return false;
- }
-
- var x = 0,
- i;
-
- for (i = 0; i < a.length; i++) {
- x |= a[i] ^ b[i];
- }
-
- return x === 0;
- },
-
/** Shift an array right.
* @param {bitArray} a The array to shift.
- * @param {Number} shift The number of bits to shift.
- * @param {Number} [carry=0] A byte to carry in
- * @param {bitArray} [out=[]] An array to prepend to the output.
+ * @param {number} shift The number of bits to shift.
+ * @param {number} [carry] A byte to carry in
+ * @param {bitArray} [out] An array to prepend to the output.
* @private
*/
- _shiftRight: function (a, shift, carry, out) {
+ _shiftRight: function (a, shift, carry = 0, out = []) {
var i,
last2 = 0,
shift2;
- if (out === undefined) {
- out = [];
- }
-
for (; shift >= 32; shift -= 32) {
out.push(carry);
@@ -252,39 +167,11 @@ sjcl.bitArray = {
last2 = a.length ? a[a.length - 1] : 0;
- shift2 = sjcl.bitArray.getPartial(last2);
+ shift2 = BitArray.getPartial(last2);
- out.push(
- sjcl.bitArray.partial((shift + shift2) & 31, shift + shift2 > 32 ? carry : out.pop(), 1)
- );
+ out.push(BitArray.partial((shift + shift2) & 31, shift + shift2 > 32 ? carry : out.pop(), 1));
return out;
- },
-
- /** xor a block of 4 words together.
- * @private
- */
- _xor4: function (x, y) {
- return [x[0] ^ y[0], x[1] ^ y[1], x[2] ^ y[2], x[3] ^ y[3]];
- },
-
- /** byteswap a word array inplace.
- * (does not handle partial words)
- * @param {sjcl.bitArray} a word array
- * @return {sjcl.bitArray} byteswapped array
- */
- byteswapM: function (a) {
- var i,
- v,
- m = 0xff00;
-
- for (i = 0; i < a.length; ++i) {
- v = a[i];
-
- a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
- }
-
- return a;
}
};
@@ -362,9 +249,9 @@ sjcl.hash.sha256.prototype = {
}
var i,
- b = (this._buffer = sjcl.bitArray.concat(this._buffer, data)),
+ b = (this._buffer = BitArray.concat(this._buffer, data)),
ol = this._length,
- nl = (this._length = ol + sjcl.bitArray.bitLength(data));
+ nl = (this._length = ol + BitArray.bitLength(data));
if (nl > 9007199254740991) {
throw new sjcl.exception.invalid('Cannot hash more than 2^53 - 1 bits');
@@ -402,7 +289,7 @@ sjcl.hash.sha256.prototype = {
// Round out and push the buffer
- b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1, 1)]);
+ b = BitArray.concat(b, [BitArray.partial(1, 1)]);
// Round out the buffer to a multiple of 16 words, less the 2 length words.
From cc9aca87a5332c6e5aabaf33028c2aa0d9b415cf Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:27:05 -0500
Subject: [PATCH 20/60] move some stuff out of the prototype
---
packages/kit/src/runtime/server/page/sjcl.js | 72 +++++++++-----------
1 file changed, 33 insertions(+), 39 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 7fd1c5d2a23c..56e38036e340 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -175,6 +175,34 @@ const BitArray = {
}
};
+/**
+ * The SHA-256 initialization vector, to be precomputed.
+ * @private
+ */
+const init = [];
+
+/*
+ _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
+ */
+
+/**
+ * The SHA-256 hash key, to be precomputed.
+ * @private
+ */
+const key = [];
+
+/*
+ _key:
+ [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
+ */
+
/** @fileOverview Javascript SHA-256 implementation.
*
* An older version of this implementation is available in the public
@@ -195,7 +223,7 @@ const BitArray = {
*/
sjcl.hash.sha256 = function (hash) {
- if (!this._key[0]) {
+ if (!key[0]) {
this._precompute();
}
@@ -221,18 +249,12 @@ sjcl.hash.sha256.hash = function (data) {
};
sjcl.hash.sha256.prototype = {
- /**
- * The hash's block size, in bits.
- * @constant
- */
- blockSize: 512,
-
/**
* Reset the hash state.
* @return this
*/
reset: function () {
- this._h = this._init.slice(0);
+ this._h = init.slice(0);
this._buffer = [];
this._length = 0;
return this;
@@ -312,34 +334,6 @@ sjcl.hash.sha256.prototype = {
return h;
},
- /**
- * The SHA-256 initialization vector, to be precomputed.
- * @private
- */
- _init: [],
-
- /*
- _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
- */
-
- /**
- * The SHA-256 hash key, to be precomputed.
- * @private
- */
- _key: [],
-
- /*
- _key:
- [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
- */
-
/**
* Function to precompute _init and _key.
* @private
@@ -367,10 +361,10 @@ sjcl.hash.sha256.prototype = {
if (isPrime) {
if (i < 8) {
- this._init[i] = frac(Math.pow(prime, 1 / 2));
+ init[i] = frac(Math.pow(prime, 1 / 2));
}
- this._key[i] = frac(Math.pow(prime, 1 / 3));
+ key[i] = frac(Math.pow(prime, 1 / 3));
i++;
}
@@ -388,7 +382,7 @@ sjcl.hash.sha256.prototype = {
a,
b,
h = this._h,
- k = this._key,
+ k = key,
h0 = h[0],
h1 = h[1],
h2 = h[2],
From 07f86fc59eaa53369dbefc37088449e7345d7a26 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:29:27 -0500
Subject: [PATCH 21/60] use a class
---
.../kit/src/runtime/server/page/crypto.js | 4 +-
packages/kit/src/runtime/server/page/sjcl.js | 62 +++++++++----------
2 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.js b/packages/kit/src/runtime/server/page/crypto.js
index 450cc310a808..f2e5a57ac55f 100644
--- a/packages/kit/src/runtime/server/page/crypto.js
+++ b/packages/kit/src/runtime/server/page/crypto.js
@@ -1,11 +1,11 @@
-import { sjcl } from './sjcl.js';
+import { sjcl, Sha256 } from './sjcl.js';
// adapted from https://bitwiseshiftleft.github.io/sjcl/,
// modified and redistributed under BSD license
/** @param {string} text */
export function sha256(text) {
- const hashed = new Uint32Array(sjcl.hash.sha256.hash(text));
+ const hashed = new Uint32Array(Sha256.hash(text));
const buffer = hashed.buffer;
const uint8array = new Uint8Array(buffer);
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 56e38036e340..7b0a28dec086 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -222,50 +222,50 @@ const key = [];
* @constructor
*/
-sjcl.hash.sha256 = function (hash) {
- if (!key[0]) {
- this._precompute();
- }
+export class Sha256 {
+ constructor(hash) {
+ if (!key[0]) {
+ this._precompute();
+ }
- if (hash) {
- this._h = hash._h.slice(0);
+ if (hash) {
+ this._h = hash._h.slice(0);
- this._buffer = hash._buffer.slice(0);
+ this._buffer = hash._buffer.slice(0);
- this._length = hash._length;
- } else {
- this.reset();
+ this._length = hash._length;
+ } else {
+ this.reset();
+ }
}
-};
-/**
- * Hash a string or an array of words.
- * @static
- * @param {bitArray|String} data the data to hash.
- * @return {bitArray} The hash value, an array of 16 big-endian words.
- */
-sjcl.hash.sha256.hash = function (data) {
- return new sjcl.hash.sha256().update(data).finalize();
-};
+ /**
+ * Hash a string or an array of words.
+ * @static
+ * @param {bitArray|String} data the data to hash.
+ * @return {bitArray} The hash value, an array of 16 big-endian words.
+ */
+ static hash(data) {
+ return new Sha256().update(data).finalize();
+ }
-sjcl.hash.sha256.prototype = {
/**
* Reset the hash state.
* @return this
*/
- reset: function () {
+ reset() {
this._h = init.slice(0);
this._buffer = [];
this._length = 0;
return this;
- },
+ }
/**
* Input several words to the hash.
* @param {bitArray|String} data the data to hash.
* @return this
*/
- update: function (data) {
+ update(data) {
if (typeof data === 'string') {
data = toBits(data);
}
@@ -298,13 +298,13 @@ sjcl.hash.sha256.prototype = {
}
return this;
- },
+ }
/**
* Complete hashing and output the hash value.
* @return {bitArray} The hash value, an array of 8 big-endian words.
*/
- finalize: function () {
+ finalize() {
var i,
b = this._buffer,
h = this._h;
@@ -332,13 +332,13 @@ sjcl.hash.sha256.prototype = {
this.reset();
return h;
- },
+ }
/**
* Function to precompute _init and _key.
* @private
*/
- _precompute: function () {
+ _precompute() {
var i = 0,
prime = 2,
factor,
@@ -369,14 +369,14 @@ sjcl.hash.sha256.prototype = {
i++;
}
}
- },
+ }
/**
* Perform one cycle of SHA-256.
* @param {Uint32Array|bitArray} w one block of words.
* @private
*/
- _block: function (w) {
+ _block(w) {
var i,
tmp,
a,
@@ -459,4 +459,4 @@ sjcl.hash.sha256.prototype = {
h[6] = (h[6] + h6) | 0;
h[7] = (h[7] + h7) | 0;
}
-};
+}
From 3ec91c6558fb0b7f8b109b48a0e025045c08577e Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:31:24 -0500
Subject: [PATCH 22/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 70 +++++---------------
1 file changed, 16 insertions(+), 54 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 7b0a28dec086..62b9fa10512e 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -177,66 +177,41 @@ const BitArray = {
/**
* The SHA-256 initialization vector, to be precomputed.
- * @private
+ * @type {number[]}
*/
const init = [];
/*
- _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
+ * init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
*/
/**
* The SHA-256 hash key, to be precomputed.
- * @private
+ * @type {number[]}
*/
const key = [];
/*
- _key:
- [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
- */
-
-/** @fileOverview Javascript SHA-256 implementation.
- *
- * An older version of this implementation is available in the public
- * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
- * Stanford University 2008-2010 and BSD-licensed for liability
- * reasons.
- *
- * Special thanks to Aldo Cortesi for pointing out several bugs in
- * this code.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-/**
- * Context for a SHA-256 operation in progress.
- * @constructor
+ * key:
+ * [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ * 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ * 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ * 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ * 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ * 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ * 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ * 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
*/
export class Sha256 {
- constructor(hash) {
+ constructor() {
if (!key[0]) {
this._precompute();
}
- if (hash) {
- this._h = hash._h.slice(0);
-
- this._buffer = hash._buffer.slice(0);
-
- this._length = hash._length;
- } else {
- this.reset();
- }
+ this._h = init.slice(0);
+ this._buffer = [];
+ this._length = 0;
}
/**
@@ -249,17 +224,6 @@ export class Sha256 {
return new Sha256().update(data).finalize();
}
- /**
- * Reset the hash state.
- * @return this
- */
- reset() {
- this._h = init.slice(0);
- this._buffer = [];
- this._length = 0;
- return this;
- }
-
/**
* Input several words to the hash.
* @param {bitArray|String} data the data to hash.
@@ -329,8 +293,6 @@ export class Sha256 {
this._block(b.splice(0, 16));
}
- this.reset();
-
return h;
}
From 527bb33fc748b52df08e3fd2317ebffe70778c8d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:33:27 -0500
Subject: [PATCH 23/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 87 ++++++++++----------
1 file changed, 43 insertions(+), 44 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 62b9fa10512e..a95f46c767d5 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -203,10 +203,47 @@ const key = [];
* 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
*/
+/**
+ * Function to precompute _init and _key.
+ */
+function precompute() {
+ var i = 0,
+ prime = 2,
+ factor,
+ isPrime;
+
+ /** @param {number} x */
+ function frac(x) {
+ return ((x - Math.floor(x)) * 0x100000000) | 0;
+ }
+
+ for (; i < 64; prime++) {
+ isPrime = true;
+
+ for (factor = 2; factor * factor <= prime; factor++) {
+ if (prime % factor === 0) {
+ isPrime = false;
+
+ break;
+ }
+ }
+
+ if (isPrime) {
+ if (i < 8) {
+ init[i] = frac(Math.pow(prime, 1 / 2));
+ }
+
+ key[i] = frac(Math.pow(prime, 1 / 3));
+
+ i++;
+ }
+ }
+}
+
export class Sha256 {
constructor() {
if (!key[0]) {
- this._precompute();
+ precompute();
}
this._h = init.slice(0);
@@ -240,7 +277,7 @@ export class Sha256 {
nl = (this._length = ol + BitArray.bitLength(data));
if (nl > 9007199254740991) {
- throw new sjcl.exception.invalid('Cannot hash more than 2^53 - 1 bits');
+ throw new Error('Cannot hash more than 2^53 - 1 bits');
}
if (typeof Uint32Array !== 'undefined') {
@@ -249,7 +286,7 @@ export class Sha256 {
var j = 0;
for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
- this._block(c.subarray(16 * j, 16 * (j + 1)));
+ this.#block(c.subarray(16 * j, 16 * (j + 1)));
j += 1;
}
@@ -257,7 +294,7 @@ export class Sha256 {
b.splice(0, 16 * j);
} else {
for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
- this._block(b.splice(0, 16));
+ this.#block(b.splice(0, 16));
}
}
@@ -290,55 +327,17 @@ export class Sha256 {
b.push(this._length | 0);
while (b.length) {
- this._block(b.splice(0, 16));
+ this.#block(b.splice(0, 16));
}
return h;
}
- /**
- * Function to precompute _init and _key.
- * @private
- */
- _precompute() {
- var i = 0,
- prime = 2,
- factor,
- isPrime;
-
- function frac(x) {
- return ((x - Math.floor(x)) * 0x100000000) | 0;
- }
-
- for (; i < 64; prime++) {
- isPrime = true;
-
- for (factor = 2; factor * factor <= prime; factor++) {
- if (prime % factor === 0) {
- isPrime = false;
-
- break;
- }
- }
-
- if (isPrime) {
- if (i < 8) {
- init[i] = frac(Math.pow(prime, 1 / 2));
- }
-
- key[i] = frac(Math.pow(prime, 1 / 3));
-
- i++;
- }
- }
- }
-
/**
* Perform one cycle of SHA-256.
* @param {Uint32Array|bitArray} w one block of words.
- * @private
*/
- _block(w) {
+ #block(w) {
var i,
tmp,
a,
From e5cf81dc05d7b5305c504d522e9d0052f02e19fd Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:38:21 -0500
Subject: [PATCH 24/60] more tidying
---
.../kit/src/runtime/server/page/crypto.js | 2 +-
packages/kit/src/runtime/server/page/sjcl.js | 29 ++++++-------------
2 files changed, 10 insertions(+), 21 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.js b/packages/kit/src/runtime/server/page/crypto.js
index f2e5a57ac55f..57bf63ca2479 100644
--- a/packages/kit/src/runtime/server/page/crypto.js
+++ b/packages/kit/src/runtime/server/page/crypto.js
@@ -1,4 +1,4 @@
-import { sjcl, Sha256 } from './sjcl.js';
+import { Sha256 } from './sjcl.js';
// adapted from https://bitwiseshiftleft.github.io/sjcl/,
// modified and redistributed under BSD license
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index a95f46c767d5..0e3ba3744b4f 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -1,7 +1,3 @@
-export const sjcl = {
- hash: {}
-};
-
/** @param {Uint32Array} uint32array' */
function swap_endianness(uint32array) {
const uint8array = new Uint8Array(uint32array.buffer);
@@ -254,7 +250,7 @@ export class Sha256 {
/**
* Hash a string or an array of words.
* @static
- * @param {bitArray|String} data the data to hash.
+ * @param {bitArray | string} data the data to hash.
* @return {bitArray} The hash value, an array of 16 big-endian words.
*/
static hash(data) {
@@ -263,8 +259,7 @@ export class Sha256 {
/**
* Input several words to the hash.
- * @param {bitArray|String} data the data to hash.
- * @return this
+ * @param {bitArray | string} data the data to hash.
*/
update(data) {
if (typeof data === 'string') {
@@ -280,24 +275,18 @@ export class Sha256 {
throw new Error('Cannot hash more than 2^53 - 1 bits');
}
- if (typeof Uint32Array !== 'undefined') {
- var c = new Uint32Array(b);
+ var c = new Uint32Array(b);
- var j = 0;
+ var j = 0;
- for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
- this.#block(c.subarray(16 * j, 16 * (j + 1)));
-
- j += 1;
- }
+ for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
+ this.#block(c.subarray(16 * j, 16 * (j + 1)));
- b.splice(0, 16 * j);
- } else {
- for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
- this.#block(b.splice(0, 16));
- }
+ j += 1;
}
+ b.splice(0, 16 * j);
+
return this;
}
From 1f2015a9ce04f94873ccabef542a63c0090e8ba4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:40:49 -0500
Subject: [PATCH 25/60] fix all type errors
---
packages/kit/src/runtime/server/page/sjcl.js | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 0e3ba3744b4f..677282c94d56 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -94,7 +94,7 @@ const BitArray = {
/**
* Find the length of an array of bits.
* @param {bitArray} a The array.
- * @return {Number} The length of a, in bits.
+ * @return {number} The length of a, in bits.
*/
bitLength: function (a) {
var l = a.length,
@@ -165,7 +165,13 @@ const BitArray = {
shift2 = BitArray.getPartial(last2);
- out.push(BitArray.partial((shift + shift2) & 31, shift + shift2 > 32 ? carry : out.pop(), 1));
+ out.push(
+ BitArray.partial(
+ (shift + shift2) & 31,
+ shift + shift2 > 32 ? carry : /** @type {number} */ (out.pop()),
+ 1
+ )
+ );
return out;
}
@@ -243,6 +249,7 @@ export class Sha256 {
}
this._h = init.slice(0);
+ /** @type {bitArray} */
this._buffer = [];
this._length = 0;
}
From d226b5b2b296f4b8d4483c45d299cc526fe0ff4a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:44:47 -0500
Subject: [PATCH 26/60] store init vector and hash key as typed arrays
---
packages/kit/src/runtime/server/page/sjcl.js | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 677282c94d56..459f242d41df 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -179,9 +179,8 @@ const BitArray = {
/**
* The SHA-256 initialization vector, to be precomputed.
- * @type {number[]}
*/
-const init = [];
+const init = new Uint32Array(8);
/*
* init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
@@ -189,9 +188,8 @@ const init = [];
/**
* The SHA-256 hash key, to be precomputed.
- * @type {number[]}
*/
-const key = [];
+const key = new Uint32Array(64);
/*
* key:
@@ -216,7 +214,7 @@ function precompute() {
/** @param {number} x */
function frac(x) {
- return ((x - Math.floor(x)) * 0x100000000) | 0;
+ return (x - Math.floor(x)) * 0x100000000;
}
for (; i < 64; prime++) {
From 7dc289b2fc8c8c0e1ed0d99dab25c60557c1f3f5 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 10:53:50 -0500
Subject: [PATCH 27/60] convert to closure
---
.../kit/src/runtime/server/page/crypto.js | 5 +-
packages/kit/src/runtime/server/page/sjcl.js | 137 ++++++++----------
2 files changed, 60 insertions(+), 82 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.js b/packages/kit/src/runtime/server/page/crypto.js
index 57bf63ca2479..ebceeb5e2902 100644
--- a/packages/kit/src/runtime/server/page/crypto.js
+++ b/packages/kit/src/runtime/server/page/crypto.js
@@ -1,11 +1,12 @@
-import { Sha256 } from './sjcl.js';
+import { hash } from './sjcl.js';
// adapted from https://bitwiseshiftleft.github.io/sjcl/,
// modified and redistributed under BSD license
/** @param {string} text */
export function sha256(text) {
- const hashed = new Uint32Array(Sha256.hash(text));
+ // const hashed = new Uint32Array(Sha256.hash(text));
+ const hashed = new Uint32Array(hash(text));
const buffer = hashed.buffer;
const uint8array = new Uint8Array(buffer);
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 459f242d41df..d4841ea1eb74 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -240,103 +240,45 @@ function precompute() {
}
}
-export class Sha256 {
- constructor() {
- if (!key[0]) {
- precompute();
- }
-
- this._h = init.slice(0);
- /** @type {bitArray} */
- this._buffer = [];
- this._length = 0;
- }
-
- /**
- * Hash a string or an array of words.
- * @static
- * @param {bitArray | string} data the data to hash.
- * @return {bitArray} The hash value, an array of 16 big-endian words.
- */
- static hash(data) {
- return new Sha256().update(data).finalize();
+/** @param {bitArray | string} data */
+export function hash(data) {
+ if (!key[0]) {
+ precompute();
}
- /**
- * Input several words to the hash.
- * @param {bitArray | string} data the data to hash.
- */
- update(data) {
- if (typeof data === 'string') {
- data = toBits(data);
- }
-
- var i,
- b = (this._buffer = BitArray.concat(this._buffer, data)),
- ol = this._length,
- nl = (this._length = ol + BitArray.bitLength(data));
-
- if (nl > 9007199254740991) {
- throw new Error('Cannot hash more than 2^53 - 1 bits');
- }
-
- var c = new Uint32Array(b);
-
- var j = 0;
-
- for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
- this.#block(c.subarray(16 * j, 16 * (j + 1)));
-
- j += 1;
- }
+ const _h = init.slice(0);
+ /** @type {bitArray} */
+ let _buffer = [];
+ let _length = 0;
- b.splice(0, 16 * j);
-
- return this;
+ if (typeof data === 'string') {
+ data = toBits(data);
}
- /**
- * Complete hashing and output the hash value.
- * @return {bitArray} The hash value, an array of 8 big-endian words.
- */
- finalize() {
- var i,
- b = this._buffer,
- h = this._h;
-
- // Round out and push the buffer
-
- b = BitArray.concat(b, [BitArray.partial(1, 1)]);
+ // update
+ var i,
+ b = (_buffer = BitArray.concat(_buffer, data)),
+ ol = _length,
+ nl = (_length = ol + BitArray.bitLength(data));
- // Round out the buffer to a multiple of 16 words, less the 2 length words.
-
- for (i = b.length + 2; i & 15; i++) {
- b.push(0);
- }
-
- // append the length
-
- b.push(Math.floor(this._length / 0x100000000));
-
- b.push(this._length | 0);
+ if (nl > 9007199254740991) {
+ throw new Error('Cannot hash more than 2^53 - 1 bits');
+ }
- while (b.length) {
- this.#block(b.splice(0, 16));
- }
+ var c = new Uint32Array(b);
- return h;
- }
+ var j = 0;
/**
* Perform one cycle of SHA-256.
* @param {Uint32Array|bitArray} w one block of words.
*/
- #block(w) {
+ const block = (w) => {
var i,
tmp,
a,
b,
- h = this._h,
+ h = _h,
k = key,
h0 = h[0],
h1 = h[1],
@@ -413,5 +355,40 @@ export class Sha256 {
h[5] = (h[5] + h5) | 0;
h[6] = (h[6] + h6) | 0;
h[7] = (h[7] + h7) | 0;
+ };
+
+ for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
+ block(c.subarray(16 * j, 16 * (j + 1)));
+
+ j += 1;
}
+
+ b.splice(0, 16 * j);
+
+ // finalize
+ var i,
+ b = _buffer,
+ h = _h;
+
+ // Round out and push the buffer
+
+ b = BitArray.concat(b, [BitArray.partial(1, 1)]);
+
+ // Round out the buffer to a multiple of 16 words, less the 2 length words.
+
+ for (i = b.length + 2; i & 15; i++) {
+ b.push(0);
+ }
+
+ // append the length
+
+ b.push(Math.floor(_length / 0x100000000));
+
+ b.push(_length | 0);
+
+ while (b.length) {
+ block(b.splice(0, 16));
+ }
+
+ return h;
}
From b2f77bc28c230c77499760911b8726088c9b40aa Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 11:11:01 -0500
Subject: [PATCH 28/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 140 ++++++++-----------
1 file changed, 62 insertions(+), 78 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index d4841ea1eb74..328687fb3ccb 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -242,52 +242,38 @@ function precompute() {
/** @param {bitArray | string} data */
export function hash(data) {
- if (!key[0]) {
- precompute();
- }
-
- const _h = init.slice(0);
- /** @type {bitArray} */
- let _buffer = [];
- let _length = 0;
+ if (!key[0]) precompute();
if (typeof data === 'string') {
data = toBits(data);
}
- // update
- var i,
- b = (_buffer = BitArray.concat(_buffer, data)),
- ol = _length,
- nl = (_length = ol + BitArray.bitLength(data));
-
- if (nl > 9007199254740991) {
- throw new Error('Cannot hash more than 2^53 - 1 bits');
- }
+ const out = init.slice(0);
- var c = new Uint32Array(b);
+ /** @type {bitArray} */
+ let _buffer = data;
+ let _length = BitArray.bitLength(data);
- var j = 0;
+ // update
+ const c = new Uint32Array(_buffer);
/**
* Perform one cycle of SHA-256.
* @param {Uint32Array|bitArray} w one block of words.
*/
const block = (w) => {
- var i,
- tmp,
- a,
- b,
- h = _h,
- k = key,
- h0 = h[0],
- h1 = h[1],
- h2 = h[2],
- h3 = h[3],
- h4 = h[4],
- h5 = h[5],
- h6 = h[6],
- h7 = h[7];
+ var tmp;
+ var a;
+ var b;
+
+ let out0 = out[0];
+ let out1 = out[1];
+ let out2 = out[2];
+ let out3 = out[3];
+ let out4 = out[4];
+ let out5 = out[5];
+ let out6 = out[6];
+ let out7 = out[7];
/* Rationale for placement of |0 :
* If a value can overflow is original 32 bits by a factor of more than a few
@@ -295,15 +281,15 @@ export function hash(data) {
* 53-bit mantissa and lose precision.
*
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
- * propagates around the loop, and on the hash state h[]. I don't believe
- * that the clamps on h4 and on h0 are strictly necessary, but it's close
- * (for h4 anyway), and better safe than sorry.
+ * propagates around the loop, and on the hash state out[]. I don't believe
+ * that the clamps on out4 and on out0 are strictly necessary, but it's close
+ * (for out4 anyway), and better safe than sorry.
*
- * The clamps on h[] are necessary for the output to be correct even in the
+ * The clamps on out[] are necessary for the output to be correct even in the
* common case and for short inputs.
*/
- for (i = 0; i < 64; i++) {
+ for (let i = 0; i < 64; i++) {
// load up the input word for this round
if (i < 16) {
@@ -323,72 +309,70 @@ export function hash(data) {
tmp =
tmp +
- h7 +
- ((h4 >>> 6) ^ (h4 >>> 11) ^ (h4 >>> 25) ^ (h4 << 26) ^ (h4 << 21) ^ (h4 << 7)) +
- (h6 ^ (h4 & (h5 ^ h6))) +
- k[i]; // | 0;
+ out7 +
+ ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
+ (out6 ^ (out4 & (out5 ^ out6))) +
+ key[i]; // | 0;
// shift register
+ out7 = out6;
+ out6 = out5;
+ out5 = out4;
- h7 = h6;
- h6 = h5;
- h5 = h4;
-
- h4 = (h3 + tmp) | 0;
+ out4 = (out3 + tmp) | 0;
- h3 = h2;
- h2 = h1;
- h1 = h0;
+ out3 = out2;
+ out2 = out1;
+ out1 = out0;
- h0 =
+ out0 =
(tmp +
- ((h1 & h2) ^ (h3 & (h1 ^ h2))) +
- ((h1 >>> 2) ^ (h1 >>> 13) ^ (h1 >>> 22) ^ (h1 << 30) ^ (h1 << 19) ^ (h1 << 10))) |
+ ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
+ ((out1 >>> 2) ^
+ (out1 >>> 13) ^
+ (out1 >>> 22) ^
+ (out1 << 30) ^
+ (out1 << 19) ^
+ (out1 << 10))) |
0;
}
- h[0] = (h[0] + h0) | 0;
- h[1] = (h[1] + h1) | 0;
- h[2] = (h[2] + h2) | 0;
- h[3] = (h[3] + h3) | 0;
- h[4] = (h[4] + h4) | 0;
- h[5] = (h[5] + h5) | 0;
- h[6] = (h[6] + h6) | 0;
- h[7] = (h[7] + h7) | 0;
+ out[0] = (out[0] + out0) | 0;
+ out[1] = (out[1] + out1) | 0;
+ out[2] = (out[2] + out2) | 0;
+ out[3] = (out[3] + out3) | 0;
+ out[4] = (out[4] + out4) | 0;
+ out[5] = (out[5] + out5) | 0;
+ out[6] = (out[6] + out6) | 0;
+ out[7] = (out[7] + out7) | 0;
};
- for (i = 512 + ol - ((512 + ol) & 511); i <= nl; i += 512) {
+ let j = 0;
+ for (let i = 512; i <= _length; i += 512) {
block(c.subarray(16 * j, 16 * (j + 1)));
j += 1;
}
- b.splice(0, 16 * j);
+ _buffer.splice(0, 16 * j);
// finalize
- var i,
- b = _buffer,
- h = _h;
// Round out and push the buffer
-
- b = BitArray.concat(b, [BitArray.partial(1, 1)]);
+ _buffer = BitArray.concat(_buffer, [BitArray.partial(1, 1)]);
// Round out the buffer to a multiple of 16 words, less the 2 length words.
-
- for (i = b.length + 2; i & 15; i++) {
- b.push(0);
+ for (let i = _buffer.length + 2; i & 15; i++) {
+ _buffer.push(0);
}
// append the length
+ _buffer.push(Math.floor(_length / 0x100000000));
+ _buffer.push(_length | 0);
- b.push(Math.floor(_length / 0x100000000));
-
- b.push(_length | 0);
-
- while (b.length) {
- block(b.splice(0, 16));
+ while (_buffer.length) {
+ block(_buffer.splice(0, 16));
}
- return h;
+ return out;
}
From 58a22a1a1122eb2a89b5f87877dacdc84aec308b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 11:14:20 -0500
Subject: [PATCH 29/60] hoist block
---
packages/kit/src/runtime/server/page/sjcl.js | 186 +++++++++----------
1 file changed, 93 insertions(+), 93 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 328687fb3ccb..5eb690a0de63 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -240,6 +240,97 @@ function precompute() {
}
}
+/**
+ * Perform one cycle of SHA-256.
+ * @param {Uint32Array} out
+ * @param {Uint32Array | bitArray} w one block of words.
+ */
+const block = (out, w) => {
+ var tmp;
+ var a;
+ var b;
+
+ let out0 = out[0];
+ let out1 = out[1];
+ let out2 = out[2];
+ let out3 = out[3];
+ let out4 = out[4];
+ let out5 = out[5];
+ let out6 = out[6];
+ let out7 = out[7];
+
+ /* Rationale for placement of |0 :
+ * If a value can overflow is original 32 bits by a factor of more than a few
+ * million (2^23 ish), there is a possibility that it might overflow the
+ * 53-bit mantissa and lose precision.
+ *
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+ * propagates around the loop, and on the hash state out[]. I don't believe
+ * that the clamps on out4 and on out0 are strictly necessary, but it's close
+ * (for out4 anyway), and better safe than sorry.
+ *
+ * The clamps on out[] are necessary for the output to be correct even in the
+ * common case and for short inputs.
+ */
+
+ for (let i = 0; i < 64; i++) {
+ // load up the input word for this round
+
+ if (i < 16) {
+ tmp = w[i];
+ } else {
+ a = w[(i + 1) & 15];
+
+ b = w[(i + 14) & 15];
+
+ tmp = w[i & 15] =
+ (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
+ ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
+ w[i & 15] +
+ w[(i + 9) & 15]) |
+ 0;
+ }
+
+ tmp =
+ tmp +
+ out7 +
+ ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
+ (out6 ^ (out4 & (out5 ^ out6))) +
+ key[i]; // | 0;
+
+ // shift register
+ out7 = out6;
+ out6 = out5;
+ out5 = out4;
+
+ out4 = (out3 + tmp) | 0;
+
+ out3 = out2;
+ out2 = out1;
+ out1 = out0;
+
+ out0 =
+ (tmp +
+ ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
+ ((out1 >>> 2) ^
+ (out1 >>> 13) ^
+ (out1 >>> 22) ^
+ (out1 << 30) ^
+ (out1 << 19) ^
+ (out1 << 10))) |
+ 0;
+ }
+
+ out[0] = (out[0] + out0) | 0;
+ out[1] = (out[1] + out1) | 0;
+ out[2] = (out[2] + out2) | 0;
+ out[3] = (out[3] + out3) | 0;
+ out[4] = (out[4] + out4) | 0;
+ out[5] = (out[5] + out5) | 0;
+ out[6] = (out[6] + out6) | 0;
+ out[7] = (out[7] + out7) | 0;
+};
+
/** @param {bitArray | string} data */
export function hash(data) {
if (!key[0]) precompute();
@@ -257,100 +348,9 @@ export function hash(data) {
// update
const c = new Uint32Array(_buffer);
- /**
- * Perform one cycle of SHA-256.
- * @param {Uint32Array|bitArray} w one block of words.
- */
- const block = (w) => {
- var tmp;
- var a;
- var b;
-
- let out0 = out[0];
- let out1 = out[1];
- let out2 = out[2];
- let out3 = out[3];
- let out4 = out[4];
- let out5 = out[5];
- let out6 = out[6];
- let out7 = out[7];
-
- /* Rationale for placement of |0 :
- * If a value can overflow is original 32 bits by a factor of more than a few
- * million (2^23 ish), there is a possibility that it might overflow the
- * 53-bit mantissa and lose precision.
- *
- * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
- * propagates around the loop, and on the hash state out[]. I don't believe
- * that the clamps on out4 and on out0 are strictly necessary, but it's close
- * (for out4 anyway), and better safe than sorry.
- *
- * The clamps on out[] are necessary for the output to be correct even in the
- * common case and for short inputs.
- */
-
- for (let i = 0; i < 64; i++) {
- // load up the input word for this round
-
- if (i < 16) {
- tmp = w[i];
- } else {
- a = w[(i + 1) & 15];
-
- b = w[(i + 14) & 15];
-
- tmp = w[i & 15] =
- (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
- ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
- w[i & 15] +
- w[(i + 9) & 15]) |
- 0;
- }
-
- tmp =
- tmp +
- out7 +
- ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
- (out6 ^ (out4 & (out5 ^ out6))) +
- key[i]; // | 0;
-
- // shift register
- out7 = out6;
- out6 = out5;
- out5 = out4;
-
- out4 = (out3 + tmp) | 0;
-
- out3 = out2;
- out2 = out1;
- out1 = out0;
-
- out0 =
- (tmp +
- ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
- ((out1 >>> 2) ^
- (out1 >>> 13) ^
- (out1 >>> 22) ^
- (out1 << 30) ^
- (out1 << 19) ^
- (out1 << 10))) |
- 0;
- }
-
- out[0] = (out[0] + out0) | 0;
- out[1] = (out[1] + out1) | 0;
- out[2] = (out[2] + out2) | 0;
- out[3] = (out[3] + out3) | 0;
- out[4] = (out[4] + out4) | 0;
- out[5] = (out[5] + out5) | 0;
- out[6] = (out[6] + out6) | 0;
- out[7] = (out[7] + out7) | 0;
- };
-
let j = 0;
for (let i = 512; i <= _length; i += 512) {
- block(c.subarray(16 * j, 16 * (j + 1)));
-
+ block(out, c.subarray(16 * j, 16 * (j + 1)));
j += 1;
}
@@ -371,7 +371,7 @@ export function hash(data) {
_buffer.push(_length | 0);
while (_buffer.length) {
- block(_buffer.splice(0, 16));
+ block(out, _buffer.splice(0, 16));
}
return out;
From 277658e2252678e6b834af68fbd53818f97a06ad Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 11:33:53 -0500
Subject: [PATCH 30/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 37 +++++++++-----------
1 file changed, 17 insertions(+), 20 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 5eb690a0de63..8e3b41ef0de1 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -21,9 +21,10 @@ function swap_endianness(uint32array) {
function toBits(str) {
str = unescape(encodeURIComponent(str));
- var out = [],
- i,
- tmp = 0;
+ const out = [];
+
+ let i;
+ let tmp = 0;
for (i = 0; i < str.length; i++) {
tmp = (tmp << 8) | str.charCodeAt(i);
@@ -331,47 +332,43 @@ const block = (out, w) => {
out[7] = (out[7] + out7) | 0;
};
-/** @param {bitArray | string} data */
+/** @param {string} data */
export function hash(data) {
if (!key[0]) precompute();
- if (typeof data === 'string') {
- data = toBits(data);
- }
-
const out = init.slice(0);
/** @type {bitArray} */
- let _buffer = data;
- let _length = BitArray.bitLength(data);
+ let buffer = toBits(data);
+ let length = BitArray.bitLength(buffer);
// update
- const c = new Uint32Array(_buffer);
+ const c = new Uint32Array(buffer);
let j = 0;
- for (let i = 512; i <= _length; i += 512) {
+ for (let i = 512; i <= length; i += 512) {
block(out, c.subarray(16 * j, 16 * (j + 1)));
j += 1;
}
- _buffer.splice(0, 16 * j);
+ buffer.splice(0, 16 * j);
// finalize
// Round out and push the buffer
- _buffer = BitArray.concat(_buffer, [BitArray.partial(1, 1)]);
+ buffer = BitArray.concat(buffer, [BitArray.partial(1, 1)]);
// Round out the buffer to a multiple of 16 words, less the 2 length words.
- for (let i = _buffer.length + 2; i & 15; i++) {
- _buffer.push(0);
+ for (let i = buffer.length + 2; i & 15; i++) {
+ buffer.push(0);
}
// append the length
- _buffer.push(Math.floor(_length / 0x100000000));
- _buffer.push(_length | 0);
+ buffer.push(Math.floor(length / 0x100000000));
+ buffer.push(length | 0);
- while (_buffer.length) {
- block(out, _buffer.splice(0, 16));
+ while (buffer.length) {
+ block(out, buffer.splice(0, 16));
}
return out;
From 4517b3bedc9fd14a16e6fb1f198e91909b5f5599 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 12:18:51 -0500
Subject: [PATCH 31/60] use textdecoder
---
packages/kit/src/runtime/server/page/crypto.spec.js | 6 +++++-
packages/kit/src/runtime/server/page/sjcl.js | 10 +++++-----
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.spec.js b/packages/kit/src/runtime/server/page/crypto.spec.js
index d620f39b514a..dda6e0985302 100644
--- a/packages/kit/src/runtime/server/page/crypto.spec.js
+++ b/packages/kit/src/runtime/server/page/crypto.spec.js
@@ -3,7 +3,11 @@ import * as assert from 'uvu/assert';
import crypto from 'crypto';
import { sha256 } from './crypto.js';
-const inputs = ['the quick brown fox jumps over the lazy dog', '工欲善其事,必先利其器'];
+const inputs = [
+ 'abc',
+ 'the quick brown fox jumps over the lazy dog',
+ '工欲善其事,必先利其器'
+].slice(0);
inputs.forEach((input) => {
test(input, () => {
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 8e3b41ef0de1..4e2af2477139 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -19,25 +19,25 @@ function swap_endianness(uint32array) {
/** @param {string} str */
function toBits(str) {
- str = unescape(encodeURIComponent(str));
+ let uint8array = new TextEncoder().encode(str);
const out = [];
let i;
let tmp = 0;
- for (i = 0; i < str.length; i++) {
- tmp = (tmp << 8) | str.charCodeAt(i);
+ for (i = 0; i < uint8array.length; i++) {
+ tmp = (tmp << 8) | uint8array[i];
if ((i & 3) === 3) {
out.push(tmp);
-
tmp = 0;
}
}
if (i & 3) {
- out.push(BitArray.partial(8 * (i & 3), tmp));
+ let partial = BitArray.partial(8 * (i & 3), tmp);
+ out.push(partial);
}
return out;
From 90de3b5f731f1f1c151243261bd27ec1f45253d1 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 12:19:12 -0500
Subject: [PATCH 32/60] create textencoder once
---
packages/kit/src/runtime/server/page/sjcl.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 4e2af2477139..eff5a0e51a16 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -17,9 +17,11 @@ function swap_endianness(uint32array) {
return uint32array;
}
+const encoder = new TextEncoder();
+
/** @param {string} str */
function toBits(str) {
- let uint8array = new TextEncoder().encode(str);
+ let uint8array = encoder.encode(str);
const out = [];
From 532471c65a40247c0ba0b73d4c6704a80818c6d5 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 12:21:02 -0500
Subject: [PATCH 33/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 21 +++++++++-----------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index eff5a0e51a16..bd4a8204f344 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -210,33 +210,30 @@ const key = new Uint32Array(64);
* Function to precompute _init and _key.
*/
function precompute() {
- var i = 0,
- prime = 2,
- factor,
- isPrime;
-
/** @param {number} x */
function frac(x) {
return (x - Math.floor(x)) * 0x100000000;
}
- for (; i < 64; prime++) {
- isPrime = true;
+ let prime = 2;
+
+ for (let i = 0; i < 64; prime++) {
+ let is_prime = true;
- for (factor = 2; factor * factor <= prime; factor++) {
+ for (let factor = 2; factor * factor <= prime; factor++) {
if (prime % factor === 0) {
- isPrime = false;
+ is_prime = false;
break;
}
}
- if (isPrime) {
+ if (is_prime) {
if (i < 8) {
- init[i] = frac(Math.pow(prime, 1 / 2));
+ init[i] = frac(prime ** (1 / 2));
}
- key[i] = frac(Math.pow(prime, 1 / 3));
+ key[i] = frac(prime ** (1 / 3));
i++;
}
From 9b87c5444c30dff3ab017ed05d983b6851c204ea Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 13:52:39 -0500
Subject: [PATCH 34/60] simplify further
---
.../src/runtime/server/page/crypto.spec.js | 3 ++-
packages/kit/src/runtime/server/page/sjcl.js | 21 +++++--------------
2 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.spec.js b/packages/kit/src/runtime/server/page/crypto.spec.js
index dda6e0985302..a46a84018462 100644
--- a/packages/kit/src/runtime/server/page/crypto.spec.js
+++ b/packages/kit/src/runtime/server/page/crypto.spec.js
@@ -14,7 +14,8 @@ inputs.forEach((input) => {
const expected_bytes = crypto.createHash('sha256').update(input, 'utf-8').digest();
const expected = expected_bytes.toString('base64');
- assert.equal(sha256(input), expected);
+ const actual = sha256(input);
+ assert.equal(actual, expected);
});
});
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index bd4a8204f344..7aba1cae7b8f 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -243,7 +243,7 @@ function precompute() {
/**
* Perform one cycle of SHA-256.
* @param {Uint32Array} out
- * @param {Uint32Array | bitArray} w one block of words.
+ * @param {Uint32Array} w one block of words.
*/
const block = (out, w) => {
var tmp;
@@ -341,19 +341,6 @@ export function hash(data) {
let buffer = toBits(data);
let length = BitArray.bitLength(buffer);
- // update
- const c = new Uint32Array(buffer);
-
- let j = 0;
- for (let i = 512; i <= length; i += 512) {
- block(out, c.subarray(16 * j, 16 * (j + 1)));
- j += 1;
- }
-
- buffer.splice(0, 16 * j);
-
- // finalize
-
// Round out and push the buffer
buffer = BitArray.concat(buffer, [BitArray.partial(1, 1)]);
@@ -366,8 +353,10 @@ export function hash(data) {
buffer.push(Math.floor(length / 0x100000000));
buffer.push(length | 0);
- while (buffer.length) {
- block(out, buffer.splice(0, 16));
+ const uint32array = new Uint32Array(buffer);
+
+ for (let i = 0; i < uint32array.length; i += 16) {
+ block(out, uint32array.subarray(i, i + 16));
}
return out;
From dfb2c5d7979a79fbf845b3c11bcae29efb2f15d4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 14:45:33 -0500
Subject: [PATCH 35/60] more tidying
---
packages/kit/src/runtime/server/page/sjcl.js | 106 +++++++++----------
1 file changed, 48 insertions(+), 58 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 7aba1cae7b8f..ee9d3838b073 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -1,3 +1,5 @@
+const encoder = new TextEncoder();
+
/** @param {Uint32Array} uint32array' */
function swap_endianness(uint32array) {
const uint8array = new Uint8Array(uint32array.buffer);
@@ -17,7 +19,27 @@ function swap_endianness(uint32array) {
return uint32array;
}
-const encoder = new TextEncoder();
+/**
+ * Get the number of bits used by a partial word.
+ * @param {number} n The partial word.
+ * @return {number} The number of bits used by the partial word.
+ */
+function count_bits_in_word(n) {
+ return Math.round(n / 0x10000000000) || 32;
+}
+
+/**
+ * Find the length of an array of bits.
+ * @param {Uint32Array} a The array.
+ * @return {number} The length of a, in bits.
+ */
+function count_bits_in_buffer(a) {
+ const l = a.length;
+ if (l === 0) return 0;
+
+ const x = a[l - 1];
+ return (l - 1) * 32 + count_bits_in_word(x);
+}
/** @param {string} str */
function toBits(str) {
@@ -75,41 +97,19 @@ function toBits(str) {
const BitArray = {
/**
* Concatenate two bit arrays.
- * @param {bitArray} a1 The first array.
- * @param {bitArray} a2 The second array.
- * @return {bitArray} The concatenation of a1 and a2.
- */
- concat: function (a1, a2) {
- if (a1.length === 0 || a2.length === 0) {
- return a1.concat(a2);
- }
-
- var last = a1[a1.length - 1],
- shift = BitArray.getPartial(last);
-
- if (shift === 32) {
- return a1.concat(a2);
- } else {
- return BitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
- }
- },
-
- /**
- * Find the length of an array of bits.
- * @param {bitArray} a The array.
- * @return {number} The length of a, in bits.
+ * @param {bitArray} array The array.
+ * @param {number} n The number.
+ * @return {bitArray} The concatenation of a1 and n.
*/
- bitLength: function (a) {
- var l = a.length,
- x;
+ concat: function (array, n) {
+ if (array.length === 0) return [n];
- if (l === 0) {
- return 0;
- }
+ const last = array[array.length - 1];
+ const shift = count_bits_in_word(last);
- x = a[l - 1];
+ if (shift === 32) return array.concat(n);
- return (l - 1) * 32 + BitArray.getPartial(x);
+ return BitArray._shiftRight(n, shift, last, array.slice(0, array.length - 1));
},
/**
@@ -127,27 +127,14 @@ const BitArray = {
return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
},
- /**
- * Get the number of bits used by a partial word.
- * @param {Number} x The partial word.
- * @return {Number} The number of bits used by the partial word.
- */
- getPartial: function (x) {
- return Math.round(x / 0x10000000000) || 32;
- },
-
/** Shift an array right.
- * @param {bitArray} a The array to shift.
+ * @param {number} a The array to shift.
* @param {number} shift The number of bits to shift.
* @param {number} [carry] A byte to carry in
* @param {bitArray} [out] An array to prepend to the output.
* @private
*/
_shiftRight: function (a, shift, carry = 0, out = []) {
- var i,
- last2 = 0,
- shift2;
-
for (; shift >= 32; shift -= 32) {
out.push(carry);
@@ -158,15 +145,11 @@ const BitArray = {
return out.concat(a);
}
- for (i = 0; i < a.length; i++) {
- out.push(carry | (a[i] >>> shift));
+ out.push(carry | (a >>> shift));
+ carry = a << (32 - shift);
- carry = a[i] << (32 - shift);
- }
-
- last2 = a.length ? a[a.length - 1] : 0;
-
- shift2 = BitArray.getPartial(last2);
+ const last2 = a;
+ const shift2 = count_bits_in_word(last2);
out.push(
BitArray.partial(
@@ -337,12 +320,19 @@ export function hash(data) {
const out = init.slice(0);
+ let uint8array = encoder.encode(data);
+ let l = 4 * Math.ceil(uint8array.length / 4);
+ if (uint8array.length < l) {
+ const tmp = new Uint8Array(l);
+ tmp.set(uint8array);
+ uint8array = tmp;
+ }
/** @type {bitArray} */
let buffer = toBits(data);
- let length = BitArray.bitLength(buffer);
+ let bits = count_bits_in_buffer(buffer);
// Round out and push the buffer
- buffer = BitArray.concat(buffer, [BitArray.partial(1, 1)]);
+ buffer = BitArray.concat(buffer, 0xff80000000);
// Round out the buffer to a multiple of 16 words, less the 2 length words.
for (let i = buffer.length + 2; i & 15; i++) {
@@ -350,8 +340,8 @@ export function hash(data) {
}
// append the length
- buffer.push(Math.floor(length / 0x100000000));
- buffer.push(length | 0);
+ buffer.push(Math.floor(bits / 0x100000000)); // this will always be zero for us
+ buffer.push(bits | 0);
const uint32array = new Uint32Array(buffer);
From 720ad9191bc98b8fd98533744d9199d6de44c952 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 15:23:57 -0500
Subject: [PATCH 36/60] more tidying
---
.../src/runtime/server/page/crypto.spec.js | 3 +-
packages/kit/src/runtime/server/page/sjcl.js | 69 ++++++++++++-------
2 files changed, 48 insertions(+), 24 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.spec.js b/packages/kit/src/runtime/server/page/crypto.spec.js
index a46a84018462..115986537a86 100644
--- a/packages/kit/src/runtime/server/page/crypto.spec.js
+++ b/packages/kit/src/runtime/server/page/crypto.spec.js
@@ -4,7 +4,8 @@ import crypto from 'crypto';
import { sha256 } from './crypto.js';
const inputs = [
- 'abc',
+ '',
+ 'abcd',
'the quick brown fox jumps over the lazy dog',
'工欲善其事,必先利其器'
].slice(0);
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index ee9d3838b073..16be9fe6412c 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -42,7 +42,7 @@ function count_bits_in_buffer(a) {
}
/** @param {string} str */
-function toBits(str) {
+function to_bits(str) {
let uint8array = encoder.encode(str);
const out = [];
@@ -97,19 +97,41 @@ function toBits(str) {
const BitArray = {
/**
* Concatenate two bit arrays.
- * @param {bitArray} array The array.
- * @param {number} n The number.
- * @return {bitArray} The concatenation of a1 and n.
+ * @param {bitArray} a1 The first array.
+ * @param {bitArray} a2 The second array.
+ * @return {bitArray} The concatenation of a1 and a2.
*/
- concat: function (array, n) {
- if (array.length === 0) return [n];
+ concat: function (a1, a2) {
+ if (a1.length === 0) {
+ return a1.concat(a2);
+ }
- const last = array[array.length - 1];
+ const last = a1[a1.length - 1];
const shift = count_bits_in_word(last);
- if (shift === 32) return array.concat(n);
+ if (shift === 32) {
+ return a1.concat(a2);
+ } else {
+ return BitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
+ }
+ },
+
+ /**
+ * Find the length of an array of bits.
+ * @param {bitArray} a The array.
+ * @return {number} The length of a, in bits.
+ */
+ bitLength: function (a) {
+ var l = a.length,
+ x;
- return BitArray._shiftRight(n, shift, last, array.slice(0, array.length - 1));
+ if (l === 0) {
+ return 0;
+ }
+
+ x = a[l - 1];
+
+ return (l - 1) * 32 + count_bits_in_word(x);
},
/**
@@ -128,13 +150,17 @@ const BitArray = {
},
/** Shift an array right.
- * @param {number} a The array to shift.
+ * @param {bitArray} a The array to shift.
* @param {number} shift The number of bits to shift.
* @param {number} [carry] A byte to carry in
* @param {bitArray} [out] An array to prepend to the output.
* @private
*/
_shiftRight: function (a, shift, carry = 0, out = []) {
+ var i,
+ last2 = 0,
+ shift2;
+
for (; shift >= 32; shift -= 32) {
out.push(carry);
@@ -145,11 +171,15 @@ const BitArray = {
return out.concat(a);
}
- out.push(carry | (a >>> shift));
- carry = a << (32 - shift);
+ for (i = 0; i < a.length; i++) {
+ out.push(carry | (a[i] >>> shift));
- const last2 = a;
- const shift2 = count_bits_in_word(last2);
+ carry = a[i] << (32 - shift);
+ }
+
+ last2 = a.length ? a[a.length - 1] : 0;
+
+ shift2 = count_bits_in_word(last2);
out.push(
BitArray.partial(
@@ -320,19 +350,12 @@ export function hash(data) {
const out = init.slice(0);
- let uint8array = encoder.encode(data);
- let l = 4 * Math.ceil(uint8array.length / 4);
- if (uint8array.length < l) {
- const tmp = new Uint8Array(l);
- tmp.set(uint8array);
- uint8array = tmp;
- }
/** @type {bitArray} */
- let buffer = toBits(data);
+ let buffer = to_bits(data);
let bits = count_bits_in_buffer(buffer);
// Round out and push the buffer
- buffer = BitArray.concat(buffer, 0xff80000000);
+ buffer = BitArray.concat(buffer, [0xff80000000]);
// Round out the buffer to a multiple of 16 words, less the 2 length words.
for (let i = buffer.length + 2; i & 15; i++) {
From 1dc0a050ae1c12828117badf5d44bf549ec3d459 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 15:36:04 -0500
Subject: [PATCH 37/60] simplify
---
packages/kit/src/runtime/server/page/sjcl.js | 25 ++++----------------
1 file changed, 5 insertions(+), 20 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 16be9fe6412c..e2bc2b058b69 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -28,24 +28,11 @@ function count_bits_in_word(n) {
return Math.round(n / 0x10000000000) || 32;
}
-/**
- * Find the length of an array of bits.
- * @param {Uint32Array} a The array.
- * @return {number} The length of a, in bits.
- */
-function count_bits_in_buffer(a) {
- const l = a.length;
- if (l === 0) return 0;
-
- const x = a[l - 1];
- return (l - 1) * 32 + count_bits_in_word(x);
-}
-
/** @param {string} str */
function to_bits(str) {
let uint8array = encoder.encode(str);
- const out = [];
+ const buffer = [];
let i;
let tmp = 0;
@@ -54,17 +41,17 @@ function to_bits(str) {
tmp = (tmp << 8) | uint8array[i];
if ((i & 3) === 3) {
- out.push(tmp);
+ buffer.push(tmp);
tmp = 0;
}
}
if (i & 3) {
let partial = BitArray.partial(8 * (i & 3), tmp);
- out.push(partial);
+ buffer.push(partial);
}
- return out;
+ return { buffer, bits: uint8array.length * 8 };
}
/**
@@ -350,9 +337,7 @@ export function hash(data) {
const out = init.slice(0);
- /** @type {bitArray} */
- let buffer = to_bits(data);
- let bits = count_bits_in_buffer(buffer);
+ let { buffer, bits } = to_bits(data);
// Round out and push the buffer
buffer = BitArray.concat(buffer, [0xff80000000]);
From 2b3ca8fa857c742e5a1d126a323c52a0106cfc2f Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 16:39:43 -0500
Subject: [PATCH 38/60] radically simplify
---
.../src/runtime/server/page/crypto.spec.js | 1 +
packages/kit/src/runtime/server/page/sjcl.js | 187 ++----------------
2 files changed, 16 insertions(+), 172 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/crypto.spec.js b/packages/kit/src/runtime/server/page/crypto.spec.js
index 115986537a86..cf967e184c97 100644
--- a/packages/kit/src/runtime/server/page/crypto.spec.js
+++ b/packages/kit/src/runtime/server/page/crypto.spec.js
@@ -4,6 +4,7 @@ import crypto from 'crypto';
import { sha256 } from './crypto.js';
const inputs = [
+ 'hello world',
'',
'abcd',
'the quick brown fox jumps over the lazy dog',
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index e2bc2b058b69..2e27bbd4eaad 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -1,6 +1,6 @@
const encoder = new TextEncoder();
-/** @param {Uint32Array} uint32array' */
+/** @param {Uint32Array} uint32array */
function swap_endianness(uint32array) {
const uint8array = new Uint8Array(uint32array.buffer);
@@ -19,167 +19,25 @@ function swap_endianness(uint32array) {
return uint32array;
}
-/**
- * Get the number of bits used by a partial word.
- * @param {number} n The partial word.
- * @return {number} The number of bits used by the partial word.
- */
-function count_bits_in_word(n) {
- return Math.round(n / 0x10000000000) || 32;
-}
-
/** @param {string} str */
function to_bits(str) {
- let uint8array = encoder.encode(str);
+ const encoded = encoder.encode(str);
+ const length = encoded.length * 8;
- const buffer = [];
+ const size = 512 * Math.ceil((length + 129) / 512);
+ const bytes = new Uint8Array(size / 8);
+ bytes.set(encoded);
+ bytes[encoded.length] = 0b10000000;
- let i;
- let tmp = 0;
+ swap_endianness(new Uint32Array(bytes.buffer));
- for (i = 0; i < uint8array.length; i++) {
- tmp = (tmp << 8) | uint8array[i];
-
- if ((i & 3) === 3) {
- buffer.push(tmp);
- tmp = 0;
- }
- }
+ const words = new Uint32Array(bytes.buffer);
+ words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
+ words[words.length - 1] = length;
- if (i & 3) {
- let partial = BitArray.partial(8 * (i & 3), tmp);
- buffer.push(partial);
- }
-
- return { buffer, bits: uint8array.length * 8 };
+ return words;
}
-/**
- * Arrays of bits, encoded as arrays of Numbers.
- * @namespace
- * @description
- *
- * These objects are the currency accepted by SJCL's crypto functions.
- *
- *
- *
- * Most of our crypto primitives operate on arrays of 4-byte words internally,
- * but many of them can take arguments that are not a multiple of 4 bytes.
- * This library encodes arrays of bits (whose size need not be a multiple of 8
- * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
- * array of words, 32 bits at a time. Since the words are double-precision
- * floating point numbers, they fit some extra data. We use this (in a private,
- * possibly-changing manner) to encode the number of bits actually present
- * in the last word of the array.
- *
- *
- *
- * Because bitwise ops clear this out-of-band data, these arrays can be passed
- * to ciphers like AES which want arrays of words.
- *
- */
-
-/** @typedef {number[]} bitArray */
-
-const BitArray = {
- /**
- * Concatenate two bit arrays.
- * @param {bitArray} a1 The first array.
- * @param {bitArray} a2 The second array.
- * @return {bitArray} The concatenation of a1 and a2.
- */
- concat: function (a1, a2) {
- if (a1.length === 0) {
- return a1.concat(a2);
- }
-
- const last = a1[a1.length - 1];
- const shift = count_bits_in_word(last);
-
- if (shift === 32) {
- return a1.concat(a2);
- } else {
- return BitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1));
- }
- },
-
- /**
- * Find the length of an array of bits.
- * @param {bitArray} a The array.
- * @return {number} The length of a, in bits.
- */
- bitLength: function (a) {
- var l = a.length,
- x;
-
- if (l === 0) {
- return 0;
- }
-
- x = a[l - 1];
-
- return (l - 1) * 32 + count_bits_in_word(x);
- },
-
- /**
- * Make a partial word for a bit array.
- * @param {Number} len The number of bits in the word.
- * @param {Number} x The bits.
- * @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
- * @return {Number} The partial word.
- */
- partial: function (len, x, _end) {
- if (len === 32) {
- return x;
- }
-
- return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000;
- },
-
- /** Shift an array right.
- * @param {bitArray} a The array to shift.
- * @param {number} shift The number of bits to shift.
- * @param {number} [carry] A byte to carry in
- * @param {bitArray} [out] An array to prepend to the output.
- * @private
- */
- _shiftRight: function (a, shift, carry = 0, out = []) {
- var i,
- last2 = 0,
- shift2;
-
- for (; shift >= 32; shift -= 32) {
- out.push(carry);
-
- carry = 0;
- }
-
- if (shift === 0) {
- return out.concat(a);
- }
-
- for (i = 0; i < a.length; i++) {
- out.push(carry | (a[i] >>> shift));
-
- carry = a[i] << (32 - shift);
- }
-
- last2 = a.length ? a[a.length - 1] : 0;
-
- shift2 = count_bits_in_word(last2);
-
- out.push(
- BitArray.partial(
- (shift + shift2) & 31,
- shift + shift2 > 32 ? carry : /** @type {number} */ (out.pop()),
- 1
- )
- );
-
- return out;
- }
-};
-
/**
* The SHA-256 initialization vector, to be precomputed.
*/
@@ -336,25 +194,10 @@ export function hash(data) {
if (!key[0]) precompute();
const out = init.slice(0);
+ const array = to_bits(data);
- let { buffer, bits } = to_bits(data);
-
- // Round out and push the buffer
- buffer = BitArray.concat(buffer, [0xff80000000]);
-
- // Round out the buffer to a multiple of 16 words, less the 2 length words.
- for (let i = buffer.length + 2; i & 15; i++) {
- buffer.push(0);
- }
-
- // append the length
- buffer.push(Math.floor(bits / 0x100000000)); // this will always be zero for us
- buffer.push(bits | 0);
-
- const uint32array = new Uint32Array(buffer);
-
- for (let i = 0; i < uint32array.length; i += 16) {
- block(out, uint32array.subarray(i, i + 16));
+ for (let i = 0; i < array.length; i += 16) {
+ block(out, array.subarray(i, i + 16));
}
return out;
From 65be836569d4b4bdd67829e487de9e93734317d3 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 16:48:16 -0500
Subject: [PATCH 39/60] simplify further
---
packages/kit/src/runtime/server/page/sjcl.js | 273 ++++++++-----------
1 file changed, 121 insertions(+), 152 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
index 2e27bbd4eaad..b26f49271cc5 100644
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ b/packages/kit/src/runtime/server/page/sjcl.js
@@ -1,72 +1,12 @@
const encoder = new TextEncoder();
-/** @param {Uint32Array} uint32array */
-function swap_endianness(uint32array) {
- const uint8array = new Uint8Array(uint32array.buffer);
-
- for (let i = 0; i < uint8array.length; i += 4) {
- const a = uint8array[i + 0];
- const b = uint8array[i + 1];
- const c = uint8array[i + 2];
- const d = uint8array[i + 3];
-
- uint8array[i + 0] = d;
- uint8array[i + 1] = c;
- uint8array[i + 2] = b;
- uint8array[i + 3] = a;
- }
-
- return uint32array;
-}
-
-/** @param {string} str */
-function to_bits(str) {
- const encoded = encoder.encode(str);
- const length = encoded.length * 8;
-
- const size = 512 * Math.ceil((length + 129) / 512);
- const bytes = new Uint8Array(size / 8);
- bytes.set(encoded);
- bytes[encoded.length] = 0b10000000;
-
- swap_endianness(new Uint32Array(bytes.buffer));
-
- const words = new Uint32Array(bytes.buffer);
- words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
- words[words.length - 1] = length;
-
- return words;
-}
-
-/**
- * The SHA-256 initialization vector, to be precomputed.
- */
+/** The SHA-256 initialization vector */
const init = new Uint32Array(8);
-/*
- * init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
- */
-
-/**
- * The SHA-256 hash key, to be precomputed.
- */
+/** The SHA-256 hash key */
const key = new Uint32Array(64);
-/*
- * key:
- * [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- * 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- * 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- * 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- * 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- * 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- * 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- * 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
- */
-
-/**
- * Function to precompute _init and _key.
- */
+/** Function to precompute init and key. */
function precompute() {
/** @param {number} x */
function frac(x) {
@@ -98,106 +38,135 @@ function precompute() {
}
}
-/**
- * Perform one cycle of SHA-256.
- * @param {Uint32Array} out
- * @param {Uint32Array} w one block of words.
- */
-const block = (out, w) => {
- var tmp;
- var a;
- var b;
-
- let out0 = out[0];
- let out1 = out[1];
- let out2 = out[2];
- let out3 = out[3];
- let out4 = out[4];
- let out5 = out[5];
- let out6 = out[6];
- let out7 = out[7];
-
- /* Rationale for placement of |0 :
- * If a value can overflow is original 32 bits by a factor of more than a few
- * million (2^23 ish), there is a possibility that it might overflow the
- * 53-bit mantissa and lose precision.
- *
- * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
- * propagates around the loop, and on the hash state out[]. I don't believe
- * that the clamps on out4 and on out0 are strictly necessary, but it's close
- * (for out4 anyway), and better safe than sorry.
- *
- * The clamps on out[] are necessary for the output to be correct even in the
- * common case and for short inputs.
- */
-
- for (let i = 0; i < 64; i++) {
- // load up the input word for this round
-
- if (i < 16) {
- tmp = w[i];
- } else {
- a = w[(i + 1) & 15];
-
- b = w[(i + 14) & 15];
-
- tmp = w[i & 15] =
- (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
- ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
- w[i & 15] +
- w[(i + 9) & 15]) |
- 0;
- }
+/** @param {string} str */
+function encode(str) {
+ const encoded = encoder.encode(str);
+ const length = encoded.length * 8;
- tmp =
- tmp +
- out7 +
- ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
- (out6 ^ (out4 & (out5 ^ out6))) +
- key[i]; // | 0;
-
- // shift register
- out7 = out6;
- out6 = out5;
- out5 = out4;
-
- out4 = (out3 + tmp) | 0;
-
- out3 = out2;
- out2 = out1;
- out1 = out0;
-
- out0 =
- (tmp +
- ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
- ((out1 >>> 2) ^
- (out1 >>> 13) ^
- (out1 >>> 22) ^
- (out1 << 30) ^
- (out1 << 19) ^
- (out1 << 10))) |
- 0;
+ // result should be a multiple of 512 bits in length,
+ // with room for a 1 (after the data) and two 32-bit
+ // words containing the original input bit length
+ const size = 512 * Math.ceil((length + 65) / 512);
+ const bytes = new Uint8Array(size / 8);
+ bytes.set(encoded);
+
+ // append a 1
+ bytes[encoded.length] = 0b10000000;
+
+ // swap endianness
+ for (let i = 0; i < bytes.length; i += 4) {
+ const a = bytes[i + 0];
+ const b = bytes[i + 1];
+ const c = bytes[i + 2];
+ const d = bytes[i + 3];
+
+ bytes[i + 0] = d;
+ bytes[i + 1] = c;
+ bytes[i + 2] = b;
+ bytes[i + 3] = a;
}
- out[0] = (out[0] + out0) | 0;
- out[1] = (out[1] + out1) | 0;
- out[2] = (out[2] + out2) | 0;
- out[3] = (out[3] + out3) | 0;
- out[4] = (out[4] + out4) | 0;
- out[5] = (out[5] + out5) | 0;
- out[6] = (out[6] + out6) | 0;
- out[7] = (out[7] + out7) | 0;
-};
+ // add the input bit length
+ const words = new Uint32Array(bytes.buffer);
+ words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
+ words[words.length - 1] = length;
+
+ return words;
+}
/** @param {string} data */
export function hash(data) {
if (!key[0]) precompute();
const out = init.slice(0);
- const array = to_bits(data);
+ const array = encode(data);
for (let i = 0; i < array.length; i += 16) {
- block(out, array.subarray(i, i + 16));
+ const w = array.subarray(i, i + 16);
+
+ let tmp;
+ let a;
+ let b;
+
+ let out0 = out[0];
+ let out1 = out[1];
+ let out2 = out[2];
+ let out3 = out[3];
+ let out4 = out[4];
+ let out5 = out[5];
+ let out6 = out[6];
+ let out7 = out[7];
+
+ /* Rationale for placement of |0 :
+ * If a value can overflow is original 32 bits by a factor of more than a few
+ * million (2^23 ish), there is a possibility that it might overflow the
+ * 53-bit mantissa and lose precision.
+ *
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+ * propagates around the loop, and on the hash state out[]. I don't believe
+ * that the clamps on out4 and on out0 are strictly necessary, but it's close
+ * (for out4 anyway), and better safe than sorry.
+ *
+ * The clamps on out[] are necessary for the output to be correct even in the
+ * common case and for short inputs.
+ */
+
+ for (let i = 0; i < 64; i++) {
+ // load up the input word for this round
+
+ if (i < 16) {
+ tmp = w[i];
+ } else {
+ a = w[(i + 1) & 15];
+
+ b = w[(i + 14) & 15];
+
+ tmp = w[i & 15] =
+ (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
+ ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
+ w[i & 15] +
+ w[(i + 9) & 15]) |
+ 0;
+ }
+
+ tmp =
+ tmp +
+ out7 +
+ ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
+ (out6 ^ (out4 & (out5 ^ out6))) +
+ key[i]; // | 0;
+
+ // shift register
+ out7 = out6;
+ out6 = out5;
+ out5 = out4;
+
+ out4 = (out3 + tmp) | 0;
+
+ out3 = out2;
+ out2 = out1;
+ out1 = out0;
+
+ out0 =
+ (tmp +
+ ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
+ ((out1 >>> 2) ^
+ (out1 >>> 13) ^
+ (out1 >>> 22) ^
+ (out1 << 30) ^
+ (out1 << 19) ^
+ (out1 << 10))) |
+ 0;
+ }
+
+ out[0] = (out[0] + out0) | 0;
+ out[1] = (out[1] + out1) | 0;
+ out[2] = (out[2] + out2) | 0;
+ out[3] = (out[3] + out3) | 0;
+ out[4] = (out[4] + out4) | 0;
+ out[5] = (out[5] + out5) | 0;
+ out[6] = (out[6] + out6) | 0;
+ out[7] = (out[7] + out7) | 0;
}
return out;
From 3126da02c6e797037b42d4da8c16a7e94080aebc Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 17:38:28 -0500
Subject: [PATCH 40/60] more crypto stuff
---
packages/adapter-netlify/src/shims.js | 2 -
packages/adapter-node/src/shims.js | 2 -
packages/adapter-vercel/files/shims.js | 2 -
packages/kit/package.json | 3 -
packages/kit/rollup.config.js | 1 -
.../kit/src/core/adapt/prerender/prerender.js | 2 -
packages/kit/src/core/dev/plugin.js | 2 -
packages/kit/src/core/preview/index.js | 2 -
packages/kit/src/install-crypto.js | 12 -
.../kit/src/runtime/server/page/crypto.js | 205 ++++++++++++++++--
packages/kit/src/runtime/server/page/csp.js | 46 ++--
.../kit/src/runtime/server/page/render.js | 3 +-
packages/kit/src/runtime/server/page/sjcl.js | 173 ---------------
13 files changed, 208 insertions(+), 247 deletions(-)
delete mode 100644 packages/kit/src/install-crypto.js
delete mode 100644 packages/kit/src/runtime/server/page/sjcl.js
diff --git a/packages/adapter-netlify/src/shims.js b/packages/adapter-netlify/src/shims.js
index 2e7f455d2b07..8bd70fe8b758 100644
--- a/packages/adapter-netlify/src/shims.js
+++ b/packages/adapter-netlify/src/shims.js
@@ -1,5 +1,3 @@
-import { install_crypto } from '@sveltejs/kit/install-crypto';
import { install_fetch } from '@sveltejs/kit/install-fetch';
-install_crypto();
install_fetch();
diff --git a/packages/adapter-node/src/shims.js b/packages/adapter-node/src/shims.js
index 2e7f455d2b07..8bd70fe8b758 100644
--- a/packages/adapter-node/src/shims.js
+++ b/packages/adapter-node/src/shims.js
@@ -1,5 +1,3 @@
-import { install_crypto } from '@sveltejs/kit/install-crypto';
import { install_fetch } from '@sveltejs/kit/install-fetch';
-install_crypto();
install_fetch();
diff --git a/packages/adapter-vercel/files/shims.js b/packages/adapter-vercel/files/shims.js
index 2e7f455d2b07..8bd70fe8b758 100644
--- a/packages/adapter-vercel/files/shims.js
+++ b/packages/adapter-vercel/files/shims.js
@@ -1,5 +1,3 @@
-import { install_crypto } from '@sveltejs/kit/install-crypto';
import { install_fetch } from '@sveltejs/kit/install-fetch';
-install_crypto();
install_fetch();
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 9042aebc85b9..9bdf36eb11c8 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -86,9 +86,6 @@
"./hooks": {
"import": "./dist/hooks.js"
},
- "./install-crypto": {
- "import": "./dist/install-crypto.js"
- },
"./install-fetch": {
"import": "./dist/install-fetch.js"
}
diff --git a/packages/kit/rollup.config.js b/packages/kit/rollup.config.js
index 1da2dd0fbee9..9361bebb710b 100644
--- a/packages/kit/rollup.config.js
+++ b/packages/kit/rollup.config.js
@@ -57,7 +57,6 @@ export default [
cli: 'src/cli.js',
node: 'src/node.js',
hooks: 'src/hooks.js',
- 'install-crypto': 'src/install-crypto.js',
'install-fetch': 'src/install-fetch.js'
},
output: {
diff --git a/packages/kit/src/core/adapt/prerender/prerender.js b/packages/kit/src/core/adapt/prerender/prerender.js
index 5bc1ec76ad1f..368a8d01d6de 100644
--- a/packages/kit/src/core/adapt/prerender/prerender.js
+++ b/packages/kit/src/core/adapt/prerender/prerender.js
@@ -3,7 +3,6 @@ import { dirname, join, resolve as resolve_path } from 'path';
import { pathToFileURL, URL } from 'url';
import { mkdirp } from '../../../utils/filesystem.js';
import { install_fetch } from '../../../install-fetch.js';
-import { install_crypto } from '../../../install-crypto.js';
import { SVELTE_KIT } from '../../constants.js';
import { is_root_relative, resolve } from '../../../utils/url.js';
import { queue } from './queue.js';
@@ -58,7 +57,6 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
}
install_fetch();
- install_crypto();
mkdirp(out);
diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js
index a50ca589b79e..8aa8560c83c0 100644
--- a/packages/kit/src/core/dev/plugin.js
+++ b/packages/kit/src/core/dev/plugin.js
@@ -5,7 +5,6 @@ import colors from 'kleur';
import sirv from 'sirv';
import { respond } from '../../runtime/server/index.js';
import { install_fetch } from '../../install-fetch.js';
-import { install_crypto } from '../../install-crypto.js';
import { create_app } from '../create_app/index.js';
import create_manifest_data from '../create_manifest_data/index.js';
import { getRequest, setResponse } from '../../node.js';
@@ -33,7 +32,6 @@ export async function create_plugin(config, cwd) {
configureServer(vite) {
install_fetch();
- install_crypto();
/** @type {import('types/app').SSRManifest} */
let manifest;
diff --git a/packages/kit/src/core/preview/index.js b/packages/kit/src/core/preview/index.js
index 10f0565177ab..7136edd96a55 100644
--- a/packages/kit/src/core/preview/index.js
+++ b/packages/kit/src/core/preview/index.js
@@ -6,7 +6,6 @@ import sirv from 'sirv';
import { pathToFileURL } from 'url';
import { getRequest, setResponse } from '../../node.js';
import { install_fetch } from '../../install-fetch.js';
-import { install_crypto } from '../../install-crypto.js';
import { SVELTE_KIT, SVELTE_KIT_ASSETS } from '../constants.js';
/** @param {string} dir */
@@ -33,7 +32,6 @@ export async function preview({
cwd = process.cwd()
}) {
install_fetch();
- install_crypto();
const app_file = resolve(cwd, `${SVELTE_KIT}/output/server/app.js`);
const manifest_file = resolve(cwd, `${SVELTE_KIT}/output/server/manifest.js`);
diff --git a/packages/kit/src/install-crypto.js b/packages/kit/src/install-crypto.js
deleted file mode 100644
index 9eee91b082f5..000000000000
--- a/packages/kit/src/install-crypto.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { webcrypto } from 'crypto';
-
-// exported for dev/preview and node environments
-export function install_crypto() {
- Object.defineProperties(globalThis, {
- crypto: {
- enumerable: true,
- configurable: true,
- value: webcrypto
- }
- });
-}
diff --git a/packages/kit/src/runtime/server/page/crypto.js b/packages/kit/src/runtime/server/page/crypto.js
index ebceeb5e2902..4c6998a8f38a 100644
--- a/packages/kit/src/runtime/server/page/crypto.js
+++ b/packages/kit/src/runtime/server/page/crypto.js
@@ -1,29 +1,186 @@
-import { hash } from './sjcl.js';
-
-// adapted from https://bitwiseshiftleft.github.io/sjcl/,
-// modified and redistributed under BSD license
-
-/** @param {string} text */
-export function sha256(text) {
- // const hashed = new Uint32Array(Sha256.hash(text));
- const hashed = new Uint32Array(hash(text));
- const buffer = hashed.buffer;
- const uint8array = new Uint8Array(buffer);
-
- // bitArray is big endian, uint32array is little endian, so we need to do this:
- for (let i = 0; i < uint8array.length; i += 4) {
- const a = uint8array[i + 0];
- const b = uint8array[i + 1];
- const c = uint8array[i + 2];
- const d = uint8array[i + 3];
-
- uint8array[i + 0] = d;
- uint8array[i + 1] = c;
- uint8array[i + 2] = b;
- uint8array[i + 3] = a;
+const encoder = new TextEncoder();
+
+/**
+ * SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
+ * modified and redistributed under BSD license
+ * @param {string} data
+ */
+export function sha256(data) {
+ if (!key[0]) precompute();
+
+ const out = init.slice(0);
+ const array = encode(data);
+
+ for (let i = 0; i < array.length; i += 16) {
+ const w = array.subarray(i, i + 16);
+
+ let tmp;
+ let a;
+ let b;
+
+ let out0 = out[0];
+ let out1 = out[1];
+ let out2 = out[2];
+ let out3 = out[3];
+ let out4 = out[4];
+ let out5 = out[5];
+ let out6 = out[6];
+ let out7 = out[7];
+
+ /* Rationale for placement of |0 :
+ * If a value can overflow is original 32 bits by a factor of more than a few
+ * million (2^23 ish), there is a possibility that it might overflow the
+ * 53-bit mantissa and lose precision.
+ *
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+ * propagates around the loop, and on the hash state out[]. I don't believe
+ * that the clamps on out4 and on out0 are strictly necessary, but it's close
+ * (for out4 anyway), and better safe than sorry.
+ *
+ * The clamps on out[] are necessary for the output to be correct even in the
+ * common case and for short inputs.
+ */
+
+ for (let i = 0; i < 64; i++) {
+ // load up the input word for this round
+
+ if (i < 16) {
+ tmp = w[i];
+ } else {
+ a = w[(i + 1) & 15];
+
+ b = w[(i + 14) & 15];
+
+ tmp = w[i & 15] =
+ (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
+ ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
+ w[i & 15] +
+ w[(i + 9) & 15]) |
+ 0;
+ }
+
+ tmp =
+ tmp +
+ out7 +
+ ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
+ (out6 ^ (out4 & (out5 ^ out6))) +
+ key[i]; // | 0;
+
+ // shift register
+ out7 = out6;
+ out6 = out5;
+ out5 = out4;
+
+ out4 = (out3 + tmp) | 0;
+
+ out3 = out2;
+ out2 = out1;
+ out1 = out0;
+
+ out0 =
+ (tmp +
+ ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
+ ((out1 >>> 2) ^
+ (out1 >>> 13) ^
+ (out1 >>> 22) ^
+ (out1 << 30) ^
+ (out1 << 19) ^
+ (out1 << 10))) |
+ 0;
+ }
+
+ out[0] = (out[0] + out0) | 0;
+ out[1] = (out[1] + out1) | 0;
+ out[2] = (out[2] + out2) | 0;
+ out[3] = (out[3] + out3) | 0;
+ out[4] = (out[4] + out4) | 0;
+ out[5] = (out[5] + out5) | 0;
+ out[6] = (out[6] + out6) | 0;
+ out[7] = (out[7] + out7) | 0;
+ }
+
+ const bytes = new Uint8Array(out.buffer);
+ reverse_endianness(bytes);
+
+ return base64(bytes);
+}
+
+/** The SHA-256 initialization vector */
+const init = new Uint32Array(8);
+
+/** The SHA-256 hash key */
+const key = new Uint32Array(64);
+
+/** Function to precompute init and key. */
+function precompute() {
+ /** @param {number} x */
+ function frac(x) {
+ return (x - Math.floor(x)) * 0x100000000;
}
- return base64(uint8array);
+ let prime = 2;
+
+ for (let i = 0; i < 64; prime++) {
+ let is_prime = true;
+
+ for (let factor = 2; factor * factor <= prime; factor++) {
+ if (prime % factor === 0) {
+ is_prime = false;
+
+ break;
+ }
+ }
+
+ if (is_prime) {
+ if (i < 8) {
+ init[i] = frac(prime ** (1 / 2));
+ }
+
+ key[i] = frac(prime ** (1 / 3));
+
+ i++;
+ }
+ }
+}
+
+/** @param {Uint8Array} bytes */
+function reverse_endianness(bytes) {
+ for (let i = 0; i < bytes.length; i += 4) {
+ const a = bytes[i + 0];
+ const b = bytes[i + 1];
+ const c = bytes[i + 2];
+ const d = bytes[i + 3];
+
+ bytes[i + 0] = d;
+ bytes[i + 1] = c;
+ bytes[i + 2] = b;
+ bytes[i + 3] = a;
+ }
+}
+
+/** @param {string} str */
+function encode(str) {
+ const encoded = encoder.encode(str);
+ const length = encoded.length * 8;
+
+ // result should be a multiple of 512 bits in length,
+ // with room for a 1 (after the data) and two 32-bit
+ // words containing the original input bit length
+ const size = 512 * Math.ceil((length + 65) / 512);
+ const bytes = new Uint8Array(size / 8);
+ bytes.set(encoded);
+
+ // append a 1
+ bytes[encoded.length] = 0b10000000;
+
+ reverse_endianness(bytes);
+
+ // add the input bit length
+ const words = new Uint32Array(bytes.buffer);
+ words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
+ words[words.length - 1] = length;
+
+ return words;
}
/*
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 4b9b6da58175..57a31a7926ec 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -1,21 +1,25 @@
import { escape_html_attr } from '../../../utils/escape.js';
-
-const array = new Uint8Array(16);
-
-export function generate_nonce() {
- crypto.getRandomValues(array);
- return base64(array);
-}
-
-/**
- * @param {string} contents
- * @param {string} algorithm
- * @returns
- */
-async function generate_hash(contents, algorithm = 'sha-256') {
- const bytes = new TextEncoder().encode(contents);
- const digest = new Uint8Array(await crypto.subtle.digest(algorithm, bytes));
- return base64(digest);
+import { sha256 } from './crypto.js';
+
+/** @type {Promise} */
+export let csp_ready;
+
+/** @type {() => string} */
+let generate_nonce;
+
+if (typeof crypto !== 'undefined') {
+ const array = new Uint8Array(16);
+
+ generate_nonce = () => {
+ crypto.getRandomValues(array);
+ return base64(array);
+ };
+} else {
+ csp_ready = import('crypto').then((crypto) => {
+ generate_nonce = () => {
+ return crypto.randomBytes(16).toString('base64');
+ };
+ });
}
const quoted = new Set([
@@ -78,10 +82,10 @@ export class Csp {
// TODO would be great if these methods weren't async
/** @param {string} content */
- async add_script(content) {
+ add_script(content) {
if (this.script_needs_csp) {
if (this.#use_hashes) {
- this.#script_src.push(`sha256-${await generate_hash(content)}`);
+ this.#script_src.push(`sha256-${sha256(content)}`);
} else if (this.#script_src.length === 0) {
this.#script_src.push(`nonce-${this.nonce}`);
}
@@ -89,10 +93,10 @@ export class Csp {
}
/** @param {string} content */
- async add_style(content) {
+ add_style(content) {
if (this.style_needs_csp) {
if (this.#use_hashes) {
- this.#style_src.push(`sha256-${await generate_hash(content)}`);
+ this.#style_src.push(`sha256-${sha256(content)}`);
} else if (this.#style_src.length === 0) {
this.#style_src.push(`nonce-${this.nonce}`);
}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 39a13eec5534..456f1dffbd15 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -5,7 +5,7 @@ import { hash } from '../../hash.js';
import { escape_html_attr } from '../../../utils/escape.js';
import { s } from '../../../utils/misc.js';
import { create_prerendering_url_proxy } from './utils.js';
-import { Csp } from './csp.js';
+import { Csp, csp_ready } from './csp.js';
// TODO rename this function/module
@@ -139,6 +139,7 @@ export async function render_response({
const inlined_style = Array.from(styles.values()).join('\n');
+ await csp_ready;
const csp = new Csp(options.csp, !!state.prerender);
// prettier-ignore
diff --git a/packages/kit/src/runtime/server/page/sjcl.js b/packages/kit/src/runtime/server/page/sjcl.js
deleted file mode 100644
index b26f49271cc5..000000000000
--- a/packages/kit/src/runtime/server/page/sjcl.js
+++ /dev/null
@@ -1,173 +0,0 @@
-const encoder = new TextEncoder();
-
-/** The SHA-256 initialization vector */
-const init = new Uint32Array(8);
-
-/** The SHA-256 hash key */
-const key = new Uint32Array(64);
-
-/** Function to precompute init and key. */
-function precompute() {
- /** @param {number} x */
- function frac(x) {
- return (x - Math.floor(x)) * 0x100000000;
- }
-
- let prime = 2;
-
- for (let i = 0; i < 64; prime++) {
- let is_prime = true;
-
- for (let factor = 2; factor * factor <= prime; factor++) {
- if (prime % factor === 0) {
- is_prime = false;
-
- break;
- }
- }
-
- if (is_prime) {
- if (i < 8) {
- init[i] = frac(prime ** (1 / 2));
- }
-
- key[i] = frac(prime ** (1 / 3));
-
- i++;
- }
- }
-}
-
-/** @param {string} str */
-function encode(str) {
- const encoded = encoder.encode(str);
- const length = encoded.length * 8;
-
- // result should be a multiple of 512 bits in length,
- // with room for a 1 (after the data) and two 32-bit
- // words containing the original input bit length
- const size = 512 * Math.ceil((length + 65) / 512);
- const bytes = new Uint8Array(size / 8);
- bytes.set(encoded);
-
- // append a 1
- bytes[encoded.length] = 0b10000000;
-
- // swap endianness
- for (let i = 0; i < bytes.length; i += 4) {
- const a = bytes[i + 0];
- const b = bytes[i + 1];
- const c = bytes[i + 2];
- const d = bytes[i + 3];
-
- bytes[i + 0] = d;
- bytes[i + 1] = c;
- bytes[i + 2] = b;
- bytes[i + 3] = a;
- }
-
- // add the input bit length
- const words = new Uint32Array(bytes.buffer);
- words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
- words[words.length - 1] = length;
-
- return words;
-}
-
-/** @param {string} data */
-export function hash(data) {
- if (!key[0]) precompute();
-
- const out = init.slice(0);
- const array = encode(data);
-
- for (let i = 0; i < array.length; i += 16) {
- const w = array.subarray(i, i + 16);
-
- let tmp;
- let a;
- let b;
-
- let out0 = out[0];
- let out1 = out[1];
- let out2 = out[2];
- let out3 = out[3];
- let out4 = out[4];
- let out5 = out[5];
- let out6 = out[6];
- let out7 = out[7];
-
- /* Rationale for placement of |0 :
- * If a value can overflow is original 32 bits by a factor of more than a few
- * million (2^23 ish), there is a possibility that it might overflow the
- * 53-bit mantissa and lose precision.
- *
- * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
- * propagates around the loop, and on the hash state out[]. I don't believe
- * that the clamps on out4 and on out0 are strictly necessary, but it's close
- * (for out4 anyway), and better safe than sorry.
- *
- * The clamps on out[] are necessary for the output to be correct even in the
- * common case and for short inputs.
- */
-
- for (let i = 0; i < 64; i++) {
- // load up the input word for this round
-
- if (i < 16) {
- tmp = w[i];
- } else {
- a = w[(i + 1) & 15];
-
- b = w[(i + 14) & 15];
-
- tmp = w[i & 15] =
- (((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
- ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
- w[i & 15] +
- w[(i + 9) & 15]) |
- 0;
- }
-
- tmp =
- tmp +
- out7 +
- ((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
- (out6 ^ (out4 & (out5 ^ out6))) +
- key[i]; // | 0;
-
- // shift register
- out7 = out6;
- out6 = out5;
- out5 = out4;
-
- out4 = (out3 + tmp) | 0;
-
- out3 = out2;
- out2 = out1;
- out1 = out0;
-
- out0 =
- (tmp +
- ((out1 & out2) ^ (out3 & (out1 ^ out2))) +
- ((out1 >>> 2) ^
- (out1 >>> 13) ^
- (out1 >>> 22) ^
- (out1 << 30) ^
- (out1 << 19) ^
- (out1 << 10))) |
- 0;
- }
-
- out[0] = (out[0] + out0) | 0;
- out[1] = (out[1] + out1) | 0;
- out[2] = (out[2] + out2) | 0;
- out[3] = (out[3] + out3) | 0;
- out[4] = (out[4] + out4) | 0;
- out[5] = (out[5] + out5) | 0;
- out[6] = (out[6] + out6) | 0;
- out[7] = (out[7] + out7) | 0;
- }
-
- return out;
-}
From 969a77116ed64a3cadcb029fde206e8165a955ea Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 17:50:41 -0500
Subject: [PATCH 41/60] use node crypto module to generate hashes where
possible
---
packages/kit/src/runtime/server/page/csp.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 57a31a7926ec..0252020e8ee7 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -7,6 +7,9 @@ export let csp_ready;
/** @type {() => string} */
let generate_nonce;
+/** @type {(input: string) => string} */
+let generate_hash;
+
if (typeof crypto !== 'undefined') {
const array = new Uint8Array(16);
@@ -14,11 +17,17 @@ if (typeof crypto !== 'undefined') {
crypto.getRandomValues(array);
return base64(array);
};
+
+ generate_hash = sha256;
} else {
csp_ready = import('crypto').then((crypto) => {
generate_nonce = () => {
return crypto.randomBytes(16).toString('base64');
};
+
+ generate_hash = (input) => {
+ return crypto.createHash('sha256').update(input, 'utf-8').digest().toString('base64');
+ };
});
}
@@ -85,7 +94,7 @@ export class Csp {
add_script(content) {
if (this.script_needs_csp) {
if (this.#use_hashes) {
- this.#script_src.push(`sha256-${sha256(content)}`);
+ this.#script_src.push(`sha256-${generate_hash(content)}`);
} else if (this.#script_src.length === 0) {
this.#script_src.push(`nonce-${this.nonce}`);
}
@@ -96,7 +105,7 @@ export class Csp {
add_style(content) {
if (this.style_needs_csp) {
if (this.#use_hashes) {
- this.#style_src.push(`sha256-${sha256(content)}`);
+ this.#style_src.push(`sha256-${generate_hash(content)}`);
} else if (this.#style_src.length === 0) {
this.#style_src.push(`nonce-${this.nonce}`);
}
From 6e31aa0574da5d9092e397cbd1aec6aa29294911 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 17:55:50 -0500
Subject: [PATCH 42/60] remove unnecessary awaits
---
packages/kit/src/runtime/server/page/render.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 456f1dffbd15..763ef25dc058 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -194,7 +194,7 @@ export async function render_response({
if (options.dev) attributes.push(' data-svelte');
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
- await csp.add_style(inlined_style);
+ csp.add_style(inlined_style);
head += `\n\t`;
}
@@ -226,7 +226,7 @@ export async function render_response({
const attributes = ['type="module"'];
- await csp.add_script(init_app);
+ csp.add_script(init_app);
if (csp.script_needs_nonce) {
attributes.push(`nonce="${csp.nonce}"`);
@@ -247,7 +247,7 @@ export async function render_response({
if (options.service_worker) {
// always include service worker unless it's turned off explicitly
- await csp.add_script(init_service_worker);
+ csp.add_script(init_service_worker);
head += `
`;
From 591663678938506ec4907704650f2c5262a622d4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 18:13:04 -0500
Subject: [PATCH 43/60] fix mutation bug
---
packages/kit/src/runtime/server/page/csp.js | 28 ++++++++++-----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 0252020e8ee7..648d3bb12f39 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -65,7 +65,7 @@ export class Csp {
*/
constructor({ mode, directives }, prerender) {
this.#use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
- this.#directives = { ...directives };
+ this.#directives = directives;
this.#script_src = [];
this.#style_src = [];
@@ -120,31 +120,31 @@ export class Csp {
// (specifically, Firefox appears to not ignore nonce-{nonce} directives
// on default-src), so we ensure that script-src and style-src exist
- if (this.#script_src.length > 0) {
- if (!this.#directives['script-src']) {
- this.#directives['script-src'] = [...(this.#directives['default-src'] || [])];
- }
+ const directives = { ...this.#directives };
- this.#directives['script-src'].push(...this.#script_src);
+ if (this.#script_src.length > 0) {
+ directives['script-src'] = [
+ ...(directives['script-src'] || directives['default-src'] || []),
+ ...this.#script_src
+ ];
}
if (this.#style_src.length > 0) {
- if (!this.#directives['style-src']) {
- this.#directives['style-src'] = [...(this.#directives['default-src'] || [])];
- }
-
- this.#directives['style-src'].push(...this.#style_src);
+ directives['style-src'] = [
+ ...(directives['style-src'] || directives['default-src'] || []),
+ ...this.#style_src
+ ];
}
- for (const key in this.#directives) {
+ for (const key in directives) {
if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
// these values cannot be used with a tag
// TODO warn?
continue;
}
- // @ts-expect-error gimme a break typescript, `key` is obviously a member of this.#directives
- const value = /** @type {string[] | true} */ (this.#directives[key]);
+ // @ts-expect-error gimme a break typescript, `key` is obviously a member of directives
+ const value = /** @type {string[] | true} */ (directives[key]);
if (!value) continue;
From 17afa3ee0ecb6ec75808f552103ce15f0b111f93 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 18:17:09 -0500
Subject: [PATCH 44/60] trick esbuild
---
packages/kit/src/runtime/server/page/csp.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 648d3bb12f39..8dc39168a8ef 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -20,7 +20,8 @@ if (typeof crypto !== 'undefined') {
generate_hash = sha256;
} else {
- csp_ready = import('crypto').then((crypto) => {
+ const name = 'crypto'; // store in a variable to fool esbuild when adapters bundle kit
+ csp_ready = import(name).then((crypto) => {
generate_nonce = () => {
return crypto.randomBytes(16).toString('base64');
};
From cb0968e30dbaa0095fd45fd7c35650ff81019b7c Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 18:21:59 -0500
Subject: [PATCH 45/60] windows fix, hopefully
---
packages/kit/src/core/config/index.spec.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index 7f33e3452506..0ef2d5f9fe9d 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -51,12 +51,12 @@ const get_defaults = (prefix = '') => ({
}
},
files: {
- assets: prefix + 'static',
- hooks: prefix + 'src/hooks',
- lib: prefix + 'src/lib',
- routes: prefix + 'src/routes',
- serviceWorker: prefix + 'src/service-worker',
- template: prefix + 'src/app.html'
+ assets: join(prefix, 'static'),
+ hooks: join(prefix, 'src/hooks'),
+ lib: join(prefix, 'src/lib'),
+ routes: join(prefix, 'src/routes'),
+ serviceWorker: join(prefix, 'src/service-worker'),
+ template: join(prefix, 'src/app.html')
},
floc: false,
headers: undefined,
From 7587a35ce5518b13211fbf640f713b6c1557b357 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Tue, 25 Jan 2022 18:46:09 -0500
Subject: [PATCH 46/60] add unsafe-inline styles in dev
---
packages/kit/src/runtime/server/page/csp.js | 44 ++++++++++++++-----
.../kit/src/runtime/server/page/render.js | 2 +-
2 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 8dc39168a8ef..3ac8a918bc70 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -48,6 +48,15 @@ export class Csp {
/** @type {boolean} */
#use_hashes;
+ /** @type {boolean} */
+ #dev;
+
+ /** @type {boolean} */
+ #script_needs_csp;
+
+ /** @type {boolean} */
+ #style_needs_csp;
+
/** @type {import('types/csp').CspDirectives} */
#directives;
@@ -62,11 +71,13 @@ export class Csp {
* mode: string,
* directives: import('types/csp').CspDirectives
* }} opts
+ * @param {boolean} dev
* @param {boolean} prerender
*/
- constructor({ mode, directives }, prerender) {
+ constructor({ mode, directives }, dev, prerender) {
this.#use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
this.#directives = directives;
+ this.#dev = dev;
this.#script_src = [];
this.#style_src = [];
@@ -74,16 +85,17 @@ export class Csp {
const effective_script_src = directives['script-src'] || directives['default-src'];
const effective_style_src = directives['style-src'] || directives['default-src'];
- this.script_needs_csp =
- effective_script_src &&
+ this.#script_needs_csp =
+ !!effective_script_src &&
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
- this.style_needs_csp =
- effective_style_src &&
+ this.#style_needs_csp =
+ !dev &&
+ !!effective_style_src &&
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
- this.script_needs_nonce = this.script_needs_csp && !this.#use_hashes;
- this.style_needs_nonce = this.style_needs_csp && !this.#use_hashes;
+ this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
+ this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
if (this.script_needs_nonce || this.style_needs_nonce) {
this.nonce = generate_nonce();
@@ -93,7 +105,7 @@ export class Csp {
// TODO would be great if these methods weren't async
/** @param {string} content */
add_script(content) {
- if (this.script_needs_csp) {
+ if (this.#script_needs_csp) {
if (this.#use_hashes) {
this.#script_src.push(`sha256-${generate_hash(content)}`);
} else if (this.#script_src.length === 0) {
@@ -104,7 +116,7 @@ export class Csp {
/** @param {string} content */
add_style(content) {
- if (this.style_needs_csp) {
+ if (this.#style_needs_csp) {
if (this.#use_hashes) {
this.#style_src.push(`sha256-${generate_hash(content)}`);
} else if (this.#style_src.length === 0) {
@@ -130,7 +142,19 @@ export class Csp {
];
}
- if (this.#style_src.length > 0) {
+ if (this.#dev) {
+ const effective_style_src = directives['style-src'] || directives['default-src'];
+
+ // in development, we need to be able to inject