toiljs 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +72 -14
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +293 -142
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.js +1 -1
- package/build/client/components/Image.d.ts +1 -1
- package/build/client/dev/devtools.js +4 -2
- package/build/client/index.d.ts +2 -2
- package/build/client/index.js +2 -2
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +2 -2
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +1 -1
- package/build/compiler/seo.js +1 -3
- package/build/compiler/template-build.d.ts +5 -2
- package/build/compiler/template-build.js +19 -7
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/cache.js +0 -0
- package/build/devserver/crypto.js +45 -17
- package/build/devserver/database.d.ts +1 -1
- package/build/devserver/database.js +84 -0
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/email/config.js +7 -2
- package/build/devserver/email/validate.js +1 -4
- package/build/devserver/host.js +18 -1
- package/build/devserver/index.d.ts +1 -1
- package/build/devserver/index.js +3 -2
- package/build/devserver/module.js +51 -12
- package/build/devserver/proxy.js +2 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +5 -5
- package/build/io/codec.js +193 -77
- package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
- package/examples/basic/client/public/images/logo.svg +37 -34
- package/examples/basic/client/public/index.html +14 -14
- package/examples/basic/client/routes/auth.tsx +18 -10
- package/examples/basic/client/routes/cookies.tsx +15 -24
- package/examples/basic/client/routes/crypto.tsx +4 -5
- package/examples/basic/client/routes/features/template/template.tsx +1 -1
- package/examples/basic/client/routes/hello.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +14 -14
- package/examples/basic/client/routes/rest.tsx +1 -3
- package/examples/basic/client/styles/main.css +25 -22
- package/examples/basic/client/toil.tsx +1 -1
- package/examples/basic/server/README.md +8 -8
- package/examples/basic/server/core/AppHandler.ts +4 -7
- package/examples/basic/server/routes/Auth.ts +13 -10
- package/examples/basic/server/routes/EnvDemo.ts +9 -3
- package/examples/basic/server/routes/Guestbook.ts +2 -4
- package/package.json +26 -26
- package/src/backend/index.ts +4 -2
- package/src/cli/create.ts +19 -4
- package/src/cli/diagnostics.ts +48 -0
- package/src/cli/doctor.ts +155 -9
- package/src/cli/notify.ts +1 -6
- package/src/cli/ui.ts +3 -3
- package/src/cli/version-check.ts +5 -1
- package/src/client/auth.ts +33 -10
- package/src/client/components/Form.tsx +2 -2
- package/src/client/components/Image.tsx +1 -1
- package/src/client/components/Script.tsx +1 -1
- package/src/client/components/Slot.tsx +1 -1
- package/src/client/dev/devtools.tsx +126 -55
- package/src/client/dev/error-overlay.tsx +7 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/client/index.ts +13 -2
- package/src/client/routing/Router.tsx +2 -2
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/hooks.ts +5 -3
- package/src/client/routing/loader.ts +2 -2
- package/src/client/routing/mount.tsx +5 -6
- package/src/compiler/docs.ts +1 -1
- package/src/compiler/email-preview.ts +1 -1
- package/src/compiler/generate.ts +1 -1
- package/src/compiler/seo.ts +1 -3
- package/src/compiler/ssg.ts +10 -4
- package/src/compiler/template-build.ts +43 -11
- package/src/compiler/template.ts +1 -4
- package/src/compiler/vite.ts +1 -1
- package/src/devserver/cache.ts +0 -0
- package/src/devserver/crypto.ts +140 -51
- package/src/devserver/database.ts +168 -9
- package/src/devserver/dotenv.ts +10 -2
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +8 -2
- package/src/devserver/email/index.ts +3 -3
- package/src/devserver/email/validate.ts +1 -4
- package/src/devserver/envelope.ts +3 -3
- package/src/devserver/host.ts +46 -6
- package/src/devserver/index.ts +15 -6
- package/src/devserver/module.ts +56 -14
- package/src/devserver/proxy.ts +5 -7
- package/src/io/codec.ts +226 -83
- package/test/devserver-database.test.ts +60 -0
- package/test/devserver-secrets.test.ts +59 -0
- package/test/doctor.test.ts +30 -0
|
@@ -17,14 +17,10 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import fs from 'node:fs';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
20
21
|
import path from 'node:path';
|
|
21
22
|
|
|
22
|
-
import {
|
|
23
|
-
createElement,
|
|
24
|
-
type ComponentType,
|
|
25
|
-
type Context,
|
|
26
|
-
type ReactNode,
|
|
27
|
-
} from 'react';
|
|
23
|
+
import { type ComponentType, type Context, createElement, type ReactNode } from 'react';
|
|
28
24
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
29
25
|
import { createServer } from 'vite';
|
|
30
26
|
|
|
@@ -36,8 +32,8 @@ import {
|
|
|
36
32
|
assignSlotIds,
|
|
37
33
|
coherenceHash,
|
|
38
34
|
encodeSlots,
|
|
39
|
-
extractFromHtml,
|
|
40
35
|
type Extracted,
|
|
36
|
+
extractFromHtml,
|
|
41
37
|
} from './template.js';
|
|
42
38
|
import { createViteConfig } from './vite.js';
|
|
43
39
|
|
|
@@ -60,6 +56,14 @@ export interface RouteRenderInput {
|
|
|
60
56
|
setSsrBuild: (on: boolean) => void;
|
|
61
57
|
/** The built HTML shell (with hashed script tags) to splice into. */
|
|
62
58
|
shell: string;
|
|
59
|
+
/** React's `createElement` from the SAME instance the page imports (the Vite
|
|
60
|
+
* SSR graph), so element creation and the components' hooks share one React.
|
|
61
|
+
* Defaults to the compiler's own React when omitted (unit tests). Mixing two
|
|
62
|
+
* React copies leaves the hook dispatcher null ("Cannot read properties of
|
|
63
|
+
* null (reading 'useRef')"). */
|
|
64
|
+
createElement?: typeof createElement;
|
|
65
|
+
/** `renderToStaticMarkup` paired with {@link createElement}'s React. */
|
|
66
|
+
renderToStaticMarkup?: typeof renderToStaticMarkup;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
export interface TemplateArtifacts {
|
|
@@ -81,13 +85,14 @@ export function assembleRouteElement(
|
|
|
81
85
|
layouts: ComponentType<{ children?: ReactNode }>[],
|
|
82
86
|
loaderData: unknown,
|
|
83
87
|
loaderContext: Context<unknown> | null,
|
|
88
|
+
h: typeof createElement = createElement,
|
|
84
89
|
): ReactNode {
|
|
85
|
-
let node: ReactNode =
|
|
90
|
+
let node: ReactNode = h(Page);
|
|
86
91
|
if (loaderContext) {
|
|
87
|
-
node =
|
|
92
|
+
node = h(loaderContext.Provider, { value: loaderData }, node);
|
|
88
93
|
}
|
|
89
94
|
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
90
|
-
node =
|
|
95
|
+
node = h(layouts[i], null, node);
|
|
91
96
|
}
|
|
92
97
|
return node;
|
|
93
98
|
}
|
|
@@ -103,16 +108,19 @@ export function injectIntoShell(shell: string, routeHtml: string): string {
|
|
|
103
108
|
|
|
104
109
|
/** Render one route to its template artifacts (pure given its inputs). */
|
|
105
110
|
export function extractRouteTemplate(input: RouteRenderInput): TemplateArtifacts {
|
|
111
|
+
const h = input.createElement ?? createElement;
|
|
112
|
+
const render = input.renderToStaticMarkup ?? renderToStaticMarkup;
|
|
106
113
|
const element = assembleRouteElement(
|
|
107
114
|
input.Page,
|
|
108
115
|
input.layouts,
|
|
109
116
|
input.loaderData,
|
|
110
117
|
input.loaderContext,
|
|
118
|
+
h,
|
|
111
119
|
);
|
|
112
120
|
input.setSsrBuild(true);
|
|
113
121
|
let routeHtml: string;
|
|
114
122
|
try {
|
|
115
|
-
routeHtml =
|
|
123
|
+
routeHtml = render(element);
|
|
116
124
|
} finally {
|
|
117
125
|
input.setSsrBuild(false);
|
|
118
126
|
}
|
|
@@ -193,6 +201,28 @@ export async function extractTemplates(
|
|
|
193
201
|
LoaderDataContext: Context<unknown>;
|
|
194
202
|
};
|
|
195
203
|
|
|
204
|
+
// Install the ambient `Toil` global (+ registered pages / transitions) exactly
|
|
205
|
+
// as the client entry does, by evaluating the generated globals module. Without
|
|
206
|
+
// it, any layout or component using `Toil` (e.g. `<Toil.Head>`, `<Toil.Link>`)
|
|
207
|
+
// throws "Toil is not defined" during extraction, so the route is silently
|
|
208
|
+
// skipped and falls back to client rendering.
|
|
209
|
+
const globalsModule = path.join(cfg.toilDir, 'globals.ts');
|
|
210
|
+
if (fs.existsSync(globalsModule)) {
|
|
211
|
+
await server.ssrLoadModule(globalsModule);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Render with the SAME React the components import. Vite externalizes react
|
|
215
|
+
// (CommonJS) for SSR and resolves it from the app root, so resolve it the same
|
|
216
|
+
// way (from cfg.root) rather than using the compiler's own copy. Two React
|
|
217
|
+
// copies leave the hook dispatcher null, so a layout/component hook
|
|
218
|
+
// (`useLocation` -> `useRef`) throws. (`ssrLoadModule('react')` can't be used:
|
|
219
|
+
// Vite's SSR runner cannot evaluate the CJS module -> "module is not defined".)
|
|
220
|
+
const appRequire = createRequire(path.join(cfg.root, 'package.json'));
|
|
221
|
+
const react = appRequire('react') as { createElement: typeof createElement };
|
|
222
|
+
const reactDomServer = appRequire('react-dom/server') as {
|
|
223
|
+
renderToStaticMarkup: typeof renderToStaticMarkup;
|
|
224
|
+
};
|
|
225
|
+
|
|
196
226
|
const ssrDir = path.join(outDir, '_ssr');
|
|
197
227
|
const hostsTmplDir = path.join(cfg.root, 'hosts', hostName, '_tmpl');
|
|
198
228
|
const generated: string[] = [];
|
|
@@ -237,6 +267,8 @@ export async function extractTemplates(
|
|
|
237
267
|
loaderContext: client.LoaderDataContext,
|
|
238
268
|
setSsrBuild: client.__setSsrBuild,
|
|
239
269
|
shell,
|
|
270
|
+
createElement: react.createElement,
|
|
271
|
+
renderToStaticMarkup: reactDomServer.renderToStaticMarkup,
|
|
240
272
|
});
|
|
241
273
|
writeTemplateArtifacts(ssrDir, art);
|
|
242
274
|
fs.mkdirSync(hostsTmplDir, { recursive: true });
|
package/src/compiler/template.ts
CHANGED
|
@@ -249,10 +249,7 @@ export function reactEscapeHtml(s: string): string {
|
|
|
249
249
|
* any tooling that needs to materialise a full page from a template + values).
|
|
250
250
|
* `values` maps a byte offset to the bytes inserted there (offsets may repeat
|
|
251
251
|
* is not allowed; pass them in `slots` order). */
|
|
252
|
-
export function spliceTemplate(
|
|
253
|
-
tmpl: Buffer,
|
|
254
|
-
inserts: { offset: number; value: Buffer }[],
|
|
255
|
-
): Buffer {
|
|
252
|
+
export function spliceTemplate(tmpl: Buffer, inserts: { offset: number; value: Buffer }[]): Buffer {
|
|
256
253
|
const parts: Buffer[] = [];
|
|
257
254
|
let prev = 0;
|
|
258
255
|
for (const ins of inserts) {
|
package/src/compiler/vite.ts
CHANGED
|
@@ -7,7 +7,7 @@ import pc from 'picocolors';
|
|
|
7
7
|
import react from '@vitejs/plugin-react';
|
|
8
8
|
import { imagetools } from 'vite-imagetools';
|
|
9
9
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
10
|
-
import { createLogger,
|
|
10
|
+
import { createLogger, type InlineConfig, type Logger, mergeConfig, type PluginOption } from 'vite';
|
|
11
11
|
|
|
12
12
|
import { type ResolvedToilConfig } from './config.js';
|
|
13
13
|
import { fontPreloadPlugin } from './fonts.js';
|
package/src/devserver/cache.ts
CHANGED
|
Binary file
|
package/src/devserver/crypto.ts
CHANGED
|
@@ -25,9 +25,24 @@ import type { MemoryRef } from './host.js';
|
|
|
25
25
|
|
|
26
26
|
// --- ABI id tables (must match the std + Rust backend) ----------------------
|
|
27
27
|
const ALG = {
|
|
28
|
-
SHA1: 1,
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
SHA1: 1,
|
|
29
|
+
SHA256: 2,
|
|
30
|
+
SHA384: 3,
|
|
31
|
+
SHA512: 4,
|
|
32
|
+
SHA3_256: 5,
|
|
33
|
+
SHA3_384: 6,
|
|
34
|
+
SHA3_512: 7,
|
|
35
|
+
AES_GCM: 10,
|
|
36
|
+
AES_CBC: 11,
|
|
37
|
+
AES_CTR: 12,
|
|
38
|
+
AES_KW: 13,
|
|
39
|
+
HMAC: 20,
|
|
40
|
+
ECDSA: 32,
|
|
41
|
+
ED25519: 33,
|
|
42
|
+
ECDH: 50,
|
|
43
|
+
X25519: 51,
|
|
44
|
+
HKDF: 52,
|
|
45
|
+
PBKDF2: 53,
|
|
31
46
|
} as const;
|
|
32
47
|
const FMT = { RAW: 0, PKCS8: 1, SPKI: 2, JWK: 3 } as const;
|
|
33
48
|
|
|
@@ -77,7 +92,9 @@ function readBytes(ref: MemoryRef, ptr: number, len: number): Buffer {
|
|
|
77
92
|
function writeBytes(ref: MemoryRef, ptr: number, bytes: Buffer | Uint8Array): void {
|
|
78
93
|
const m = memBuf(ref);
|
|
79
94
|
if (ptr < 0 || ptr + bytes.length > m.length)
|
|
80
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
`crypto write out of bounds: ptr=${String(ptr)} len=${String(bytes.length)}`,
|
|
97
|
+
);
|
|
81
98
|
m.set(bytes, ptr);
|
|
82
99
|
}
|
|
83
100
|
|
|
@@ -85,25 +102,21 @@ function writeBytes(ref: MemoryRef, ptr: number, bytes: Buffer | Uint8Array): vo
|
|
|
85
102
|
class ParamReader {
|
|
86
103
|
private pos = 0;
|
|
87
104
|
constructor(private readonly buf: Buffer) {}
|
|
88
|
-
|
|
89
|
-
* error (trap-equivalent, caught by the dispatcher) rather than a raw
|
|
90
|
-
* Node RangeError. */
|
|
91
|
-
private need(n: number): void {
|
|
92
|
-
if (n < 0 || this.pos + n > this.buf.length)
|
|
93
|
-
throw new Error('crypto: malformed params buffer (truncated)');
|
|
94
|
-
}
|
|
105
|
+
|
|
95
106
|
readI32(): number {
|
|
96
107
|
this.need(4);
|
|
97
108
|
const v = this.buf.readInt32LE(this.pos);
|
|
98
109
|
this.pos += 4;
|
|
99
110
|
return v;
|
|
100
111
|
}
|
|
112
|
+
|
|
101
113
|
readU32(): number {
|
|
102
114
|
this.need(4);
|
|
103
115
|
const v = this.buf.readUInt32LE(this.pos);
|
|
104
116
|
this.pos += 4;
|
|
105
117
|
return v;
|
|
106
118
|
}
|
|
119
|
+
|
|
107
120
|
readBlob(): Buffer {
|
|
108
121
|
const n = this.readU32();
|
|
109
122
|
this.need(n);
|
|
@@ -111,18 +124,34 @@ class ParamReader {
|
|
|
111
124
|
this.pos += n;
|
|
112
125
|
return s;
|
|
113
126
|
}
|
|
127
|
+
|
|
128
|
+
/** Bounds-check before a read so a malformed buffer throws a controlled
|
|
129
|
+
* error (trap-equivalent, caught by the dispatcher) rather than a raw
|
|
130
|
+
* Node RangeError. */
|
|
131
|
+
private need(n: number): void {
|
|
132
|
+
if (n < 0 || this.pos + n > this.buf.length)
|
|
133
|
+
throw new Error('crypto: malformed params buffer (truncated)');
|
|
134
|
+
}
|
|
114
135
|
}
|
|
115
136
|
|
|
116
137
|
function hashName(id: number): string {
|
|
117
138
|
switch (id) {
|
|
118
|
-
case ALG.SHA1:
|
|
119
|
-
|
|
120
|
-
case ALG.
|
|
121
|
-
|
|
122
|
-
case ALG.
|
|
123
|
-
|
|
124
|
-
case ALG.
|
|
125
|
-
|
|
139
|
+
case ALG.SHA1:
|
|
140
|
+
return 'sha1';
|
|
141
|
+
case ALG.SHA256:
|
|
142
|
+
return 'sha256';
|
|
143
|
+
case ALG.SHA384:
|
|
144
|
+
return 'sha384';
|
|
145
|
+
case ALG.SHA512:
|
|
146
|
+
return 'sha512';
|
|
147
|
+
case ALG.SHA3_256:
|
|
148
|
+
return 'sha3-256';
|
|
149
|
+
case ALG.SHA3_384:
|
|
150
|
+
return 'sha3-384';
|
|
151
|
+
case ALG.SHA3_512:
|
|
152
|
+
return 'sha3-512';
|
|
153
|
+
default:
|
|
154
|
+
throw new Error(`crypto: bad hash id ${String(id)}`);
|
|
126
155
|
}
|
|
127
156
|
}
|
|
128
157
|
|
|
@@ -151,8 +180,7 @@ export function buildCryptoImports(
|
|
|
151
180
|
|
|
152
181
|
'crypto.take_result': (outPtr: number, outLen: number): number => {
|
|
153
182
|
const r = cs.lastResult;
|
|
154
|
-
if (!r || r.length !== outLen)
|
|
155
|
-
throw new Error('crypto.take_result: length mismatch');
|
|
183
|
+
if (!r || r.length !== outLen) throw new Error('crypto.take_result: length mismatch');
|
|
156
184
|
writeBytes(ref, outPtr, r);
|
|
157
185
|
cs.lastResult = null;
|
|
158
186
|
return r.length;
|
|
@@ -191,9 +219,15 @@ export function buildCryptoImports(
|
|
|
191
219
|
if (e.raw && format === FMT.RAW) return stash(cs, e.raw);
|
|
192
220
|
if (e.keyObject) {
|
|
193
221
|
if (format === FMT.PKCS8 && e.isPrivate)
|
|
194
|
-
return stash(
|
|
222
|
+
return stash(
|
|
223
|
+
cs,
|
|
224
|
+
e.keyObject.export({ format: 'der', type: 'pkcs8' }) as Buffer,
|
|
225
|
+
);
|
|
195
226
|
if (format === FMT.SPKI && !e.isPrivate)
|
|
196
|
-
return stash(
|
|
227
|
+
return stash(
|
|
228
|
+
cs,
|
|
229
|
+
e.keyObject.export({ format: 'der', type: 'spki' }) as Buffer,
|
|
230
|
+
);
|
|
197
231
|
}
|
|
198
232
|
return ERR_UNSUPPORTED;
|
|
199
233
|
} catch {
|
|
@@ -210,7 +244,13 @@ export function buildCryptoImports(
|
|
|
210
244
|
signOp(cs, ref, h, pp, pl, dp, dl),
|
|
211
245
|
|
|
212
246
|
'crypto.verify': (
|
|
213
|
-
h: number,
|
|
247
|
+
h: number,
|
|
248
|
+
pp: number,
|
|
249
|
+
pl: number,
|
|
250
|
+
sp: number,
|
|
251
|
+
sl: number,
|
|
252
|
+
dp: number,
|
|
253
|
+
dl: number,
|
|
214
254
|
): number => verifyOp(cs, ref, h, pp, pl, sp, sl, dp, dl),
|
|
215
255
|
|
|
216
256
|
'crypto.derive_bits': (h: number, pp: number, pl: number, lengthBits: number): number =>
|
|
@@ -220,10 +260,14 @@ export function buildCryptoImports(
|
|
|
220
260
|
// host (`mldsa_verify_import.rs`): same size asserts, 1/0/neg result.
|
|
221
261
|
// Backed by the same noble lib the client signs with, so dev == prod.
|
|
222
262
|
'crypto.mldsa_verify': (
|
|
223
|
-
pkPtr: number,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
263
|
+
pkPtr: number,
|
|
264
|
+
pkLen: number,
|
|
265
|
+
msgPtr: number,
|
|
266
|
+
msgLen: number,
|
|
267
|
+
sigPtr: number,
|
|
268
|
+
sigLen: number,
|
|
269
|
+
ctxPtr: number,
|
|
270
|
+
ctxLen: number,
|
|
227
271
|
): number => {
|
|
228
272
|
if (pkLen !== 1312 || sigLen !== 2420 || ctxLen > 255) return -4;
|
|
229
273
|
try {
|
|
@@ -243,8 +287,10 @@ export function buildCryptoImports(
|
|
|
243
287
|
// the server's static secret key, write it to `outPtr`, return 0 / neg.
|
|
244
288
|
// Backed by the same noble lib the client encapsulates with (dev == prod).
|
|
245
289
|
'crypto.mlkem_decapsulate': (
|
|
246
|
-
ctPtr: number,
|
|
247
|
-
|
|
290
|
+
ctPtr: number,
|
|
291
|
+
ctLen: number,
|
|
292
|
+
skPtr: number,
|
|
293
|
+
skLen: number,
|
|
248
294
|
outPtr: number,
|
|
249
295
|
): number => {
|
|
250
296
|
if (ctLen !== 1088 || skLen !== 2400) return -4;
|
|
@@ -267,9 +313,12 @@ export function buildCryptoImports(
|
|
|
267
313
|
// `@noble/curves` ristretto255_oprf, which matches the edge byte-for-byte
|
|
268
314
|
// (both RFC 9497), so dev == prod.
|
|
269
315
|
'crypto.voprf_evaluate': (
|
|
270
|
-
seedPtr: number,
|
|
271
|
-
|
|
272
|
-
|
|
316
|
+
seedPtr: number,
|
|
317
|
+
seedLen: number,
|
|
318
|
+
infoPtr: number,
|
|
319
|
+
infoLen: number,
|
|
320
|
+
blindedPtr: number,
|
|
321
|
+
blindedLen: number,
|
|
273
322
|
outPtr: number,
|
|
274
323
|
): number => {
|
|
275
324
|
// seedLen MUST be exactly 32 (RFC 9497 Ns; noble deriveKeyPair rejects
|
|
@@ -309,8 +358,13 @@ function importKey(
|
|
|
309
358
|
try {
|
|
310
359
|
// Symmetric / MAC / KDF: raw bytes.
|
|
311
360
|
if (
|
|
312
|
-
alg === ALG.AES_GCM ||
|
|
313
|
-
alg === ALG.
|
|
361
|
+
alg === ALG.AES_GCM ||
|
|
362
|
+
alg === ALG.AES_CBC ||
|
|
363
|
+
alg === ALG.AES_CTR ||
|
|
364
|
+
alg === ALG.AES_KW ||
|
|
365
|
+
alg === ALG.HMAC ||
|
|
366
|
+
alg === ALG.PBKDF2 ||
|
|
367
|
+
alg === ALG.HKDF
|
|
314
368
|
) {
|
|
315
369
|
if (format !== FMT.RAW) return ERR_UNSUPPORTED;
|
|
316
370
|
return newEntry({ raw: key, keyObject: null, alg, hash, isPrivate: false });
|
|
@@ -339,8 +393,14 @@ function aesAlgName(keyLen: number, mode: 'gcm' | 'cbc' | 'ctr'): string {
|
|
|
339
393
|
}
|
|
340
394
|
|
|
341
395
|
function aesOp(
|
|
342
|
-
cs: CryptoState,
|
|
343
|
-
|
|
396
|
+
cs: CryptoState,
|
|
397
|
+
ref: MemoryRef,
|
|
398
|
+
encrypt: boolean,
|
|
399
|
+
handle: number,
|
|
400
|
+
pp: number,
|
|
401
|
+
pl: number,
|
|
402
|
+
dp: number,
|
|
403
|
+
dl: number,
|
|
344
404
|
): number {
|
|
345
405
|
const e = cs.keys.get(handle);
|
|
346
406
|
if (!e || !e.raw) throw new Error('crypto: invalid AES key handle');
|
|
@@ -356,7 +416,9 @@ function aesOp(
|
|
|
356
416
|
if (tagBits !== 0 && tagBits !== 128) return ERR_INVALID_PARAMS;
|
|
357
417
|
if (encrypt) {
|
|
358
418
|
const c = nodeCrypto.createCipheriv(
|
|
359
|
-
aesAlgName(e.raw.length, 'gcm'),
|
|
419
|
+
aesAlgName(e.raw.length, 'gcm'),
|
|
420
|
+
e.raw,
|
|
421
|
+
iv,
|
|
360
422
|
) as nodeCrypto.CipherGCM;
|
|
361
423
|
if (aad.length) c.setAAD(aad);
|
|
362
424
|
const ct = Buffer.concat([c.update(data), c.final()]);
|
|
@@ -366,7 +428,9 @@ function aesOp(
|
|
|
366
428
|
// never authenticate.
|
|
367
429
|
if (data.length < 16) return ERR_OPERATION_FAILED;
|
|
368
430
|
const d = nodeCrypto.createDecipheriv(
|
|
369
|
-
aesAlgName(e.raw.length, 'gcm'),
|
|
431
|
+
aesAlgName(e.raw.length, 'gcm'),
|
|
432
|
+
e.raw,
|
|
433
|
+
iv,
|
|
370
434
|
) as nodeCrypto.DecipherGCM;
|
|
371
435
|
if (aad.length) d.setAAD(aad);
|
|
372
436
|
const tag = data.subarray(data.length - 16);
|
|
@@ -395,8 +459,13 @@ function aesOp(
|
|
|
395
459
|
}
|
|
396
460
|
|
|
397
461
|
function signOp(
|
|
398
|
-
cs: CryptoState,
|
|
399
|
-
|
|
462
|
+
cs: CryptoState,
|
|
463
|
+
ref: MemoryRef,
|
|
464
|
+
handle: number,
|
|
465
|
+
pp: number,
|
|
466
|
+
pl: number,
|
|
467
|
+
dp: number,
|
|
468
|
+
dl: number,
|
|
400
469
|
): number {
|
|
401
470
|
const e = cs.keys.get(handle);
|
|
402
471
|
if (!e) throw new Error('crypto.sign: invalid handle');
|
|
@@ -426,8 +495,15 @@ function signOp(
|
|
|
426
495
|
}
|
|
427
496
|
|
|
428
497
|
function verifyOp(
|
|
429
|
-
cs: CryptoState,
|
|
430
|
-
|
|
498
|
+
cs: CryptoState,
|
|
499
|
+
ref: MemoryRef,
|
|
500
|
+
handle: number,
|
|
501
|
+
pp: number,
|
|
502
|
+
pl: number,
|
|
503
|
+
sp: number,
|
|
504
|
+
sl: number,
|
|
505
|
+
dp: number,
|
|
506
|
+
dl: number,
|
|
431
507
|
): number {
|
|
432
508
|
const e = cs.keys.get(handle);
|
|
433
509
|
if (!e) throw new Error('crypto.verify: invalid handle');
|
|
@@ -442,10 +518,15 @@ function verifyOp(
|
|
|
442
518
|
return mac.length === sig.length && nodeCrypto.timingSafeEqual(mac, sig) ? 1 : 0;
|
|
443
519
|
}
|
|
444
520
|
if (e.alg === ALG.ECDSA && e.keyObject) {
|
|
445
|
-
const ok = nodeCrypto.verify(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
521
|
+
const ok = nodeCrypto.verify(
|
|
522
|
+
hashName(hash),
|
|
523
|
+
data,
|
|
524
|
+
{
|
|
525
|
+
key: e.keyObject,
|
|
526
|
+
dsaEncoding: 'ieee-p1363',
|
|
527
|
+
},
|
|
528
|
+
sig,
|
|
529
|
+
);
|
|
449
530
|
return ok ? 1 : 0;
|
|
450
531
|
}
|
|
451
532
|
if (e.alg === ALG.ED25519 && e.keyObject) {
|
|
@@ -458,8 +539,12 @@ function verifyOp(
|
|
|
458
539
|
}
|
|
459
540
|
|
|
460
541
|
function deriveBitsOp(
|
|
461
|
-
cs: CryptoState,
|
|
462
|
-
|
|
542
|
+
cs: CryptoState,
|
|
543
|
+
ref: MemoryRef,
|
|
544
|
+
handle: number,
|
|
545
|
+
pp: number,
|
|
546
|
+
pl: number,
|
|
547
|
+
lengthBits: number,
|
|
463
548
|
): number {
|
|
464
549
|
if (lengthBits < 0 || lengthBits % 8 !== 0) return ERR_INVALID_PARAMS;
|
|
465
550
|
const outLen = lengthBits / 8;
|
|
@@ -473,7 +558,10 @@ function deriveBitsOp(
|
|
|
473
558
|
if (alg === ALG.PBKDF2 && e.raw) {
|
|
474
559
|
const iterations = pr.readU32();
|
|
475
560
|
const salt = pr.readBlob();
|
|
476
|
-
return stash(
|
|
561
|
+
return stash(
|
|
562
|
+
cs,
|
|
563
|
+
nodeCrypto.pbkdf2Sync(e.raw, salt, iterations, outLen, hashName(hash)),
|
|
564
|
+
);
|
|
477
565
|
}
|
|
478
566
|
if (alg === ALG.HKDF && e.raw) {
|
|
479
567
|
const salt = pr.readBlob();
|
|
@@ -484,7 +572,8 @@ function deriveBitsOp(
|
|
|
484
572
|
if ((alg === ALG.ECDH || alg === ALG.X25519) && e.keyObject) {
|
|
485
573
|
const peerHandle = pr.readI32();
|
|
486
574
|
const peer = cs.keys.get(peerHandle);
|
|
487
|
-
if (!peer || !peer.keyObject)
|
|
575
|
+
if (!peer || !peer.keyObject)
|
|
576
|
+
throw new Error('crypto.derive_bits: invalid peer handle');
|
|
488
577
|
const shared = nodeCrypto.diffieHellman({
|
|
489
578
|
privateKey: e.keyObject,
|
|
490
579
|
publicKey: peer.keyObject,
|