In rollup-plugin-webbundle there is this
This plugin requires Node v14.0.0+ and Rollup v1.21.0+.
which implies Node.js and Rollup are required to build a Signed Web Bundle and Isolated Web App.
What about Deno and Bun?
The repository uses node:crypto to generate secure curve crypto keys. The problem is Node.js node:crypto module cannot be polyfilled or exported
[AskJS] Why is the internal crypto module so difficult to bundle as a standalone portable script?
The crypto module is not entirely Javascript. Some of its implementation uses native code which only runs in the nodejs environment and some of its implementation uses nodejs internals (like the thread pool).
Web Cryptography API supports secured curves, see Secure Curves in the Web Cryptography API and Secure Curves in the Web Cryptography API and
the node:crypto module that Deno and Bun have implemented exposes the Web Cryptography API in the webcrypto object
import * as crypto from "node:crypto";
const { webcrypto } = crypto;
I wrote a Web Cryptography API version of the Rollup plugin previously here
- https://github.com/guest271314/wbn-sign-webcrypto
- https://github.com/guest271314/webbundle
- https://github.com/guest271314/telnet-client
- https://github.com/guest271314/direct-sockets-http-ws-server
Recently the Integrity Block version was updated to version 2 here https://github.com/WICG/webpackage/blob/main/explainers/integrity-signature.md and here https://github.com/WICG/webpackage/pull/892/files#diff-b9f1e3e307dfa243a4fa6880490390bacb73b68d8d6a8cbcff4d9d6939794187, which effectively means that
Chromium browser would not install the .swbn in chrome://web-app-internals that it did the day before when I fetched the lastet Chromium build a couple days ago Version 129.0.6661.0 (Developer Build) (64-bit)
Installing IWA: failed to install: Failed to get IsolationInfo: Failed to read the integrity block of the signed web bundle: Integrity Block V1 has been deprecated since M129. Please re-sign your bundle.
and there is/was no backwards compatibility to use version 1, even though I don't see in the source code where version 1 is not allowed, see What specifcally needs to be changed to support Integrity Block V2?#80.
So, back to the text editor to support Deno and Bun. The idea that Node.js is required to build a Signed Web Bundle and Isolated Web App just doesn't sit right with me. The same code should be able to be run in multiple JavaScript runtimes.
Now, I understand that creating JavaScript runtime agnostic source code is not necessarily a priority for JavaScript authors and developers at large, even with Winter Community Group doing what it is doing. That's fine. I constantly test multiple JavaScript runtimes, using the same source code for each runtime when possible in my laboratory, so that is a priority for me.
I've notified the maintainers of the technology that Web Cryptography API can be used instead of the Node.js-specific node:crypto module, and that the source code as-is in the relevant repositories do not support Deno or Bun. I have not observed any movement to support Deno and Bun, even though that is possible, without adversely impacting the apparent focus on Node.js implementation of creating SWBN's and IWA's, see Ed25519 is supported by Web Cryptography API. Create Web API version that does not depend on Node.js crypto module.
Here's what I did to support Deno and Bun building SWBN's and IWA's.
git clone https://github.com/GoogleChromeLabs/webbundle-pluginscd webbundle-plugins/packages/rollup-plugin-webbundlebun install -p- In
src/index.tscomment line 18,: EnforcedPlugin, line 32const opts = await getValidatedOptionsWithDefaults(rawOpts);and lines 65-121, because I will not be using Rollup - Bundle with Bun
bun build --target=node --format=esm --sourcemap=none --outfile=webpackage-bundle.js ./webbundle-plugins/packages/rollup-plugin-webbundle/src/index.ts - Create reference to Web Cryptography API that will be used in the code in the bundled script instead of
node:cryptodirectlyimport { webcrypto } from "node:crypto"; - In
/node_modules/wbn-sign/lib/utils/utils.jsuseswitch (key.algorithm.name) { getRawPublicKeybecomes anasyncfunction for substitutingconst exportedKey = await webcrypto.subtle.exportKey("spki", publicKey);forpublicKey.export({ type: "spki", format: "der" });- In
/node_modules/wbn-sign/lib/signers/integrity-block-signer.jsuseconst publicKey = await signingStrategy.getPublicKey();and[getPublicKeyAttributeName(publicKey)]: await getRawPublicKey(publicKey);verifySignature()also becomes anasyncfunction whereconst algorithm = { name: "Ed25519" }; const isVerified = await webcrypto.subtle.verify(algorithm, publicKey, signature, data);is substituted forconst isVerified = crypto2.verify(undefined, data, publicKey, signature); - In
/node_modules/wbn-sign/lib/web-bundle-id.jsserialize()function becomesasyncforreturn base32Encode(new Uint8Array([...await getRawPublicKey(this.key), ...this.typeSuffix]), "RFC4648", { padding: false }).toLowerCase();; andserializeWithIsolatedWebAppOrigin()becomes anasyncfunction forreturn ${this.scheme}${await this.serialize()}/;;toString()becomes anasyncfunction forreturn Web Bundle ID: ${await this.serialize()} Isolated Web App Origin: ${await this.serializeWithIsolatedWebAppOrigin()}; - In
src/index.tsexport {WebBundleId, bundleIsolatedWebApp}; - In
index.js, the entry point for how I am creating the SWBN and IWA I get the public and private keys created with Web Cryptography API, and use Web Cryptography API to sign and verify
globalThis.Buffer ??= (await import("node:buffer")).Buffer; // For Deno
import { bundleIsolatedWebApp, WebBundleId } from "./webpackage-bundle.js";
// import { WebBundleId } from "wbn-sign";
import * as fs from "node:fs";
import * as path from "node:path";
import * as crypto from "node:crypto";
const { webcrypto } = crypto;
const algorithm = { name: "Ed25519" };
const decoder = new TextDecoder();
const controller = fs.readFileSync("./direct-sockets/direct-socket-controller.js");
const script = fs.readFileSync("./assets/script.js");
const privateKey = fs.readFileSync("./privateKey.json");
const publicKey = fs.readFileSync("./publicKey.json");
// https://github.com/tQsW/webcrypto-curve25519/blob/master/explainer.md
const cryptoKey = {
privateKey: await webcrypto.subtle.importKey(
"jwk",
JSON.parse(decoder.decode(privateKey)),
algorithm.name,
true,
["sign"],
),
publicKey: await webcrypto.subtle.importKey(
"jwk",
JSON.parse(decoder.decode(publicKey)),
algorithm.name,
true,
["verify"],
),
};
const webBundleId = await new WebBundleId(
cryptoKey.publicKey,
).serialize();
const isolatedWebAppURL = await new WebBundleId(
cryptoKey.publicKey,
).serializeWithIsolatedWebAppOrigin();
fs.writeFileSync(
"./direct-sockets/direct-socket-controller.js",
decoder.decode(controller).replace(
"IWA_URL",
`isolated-app://${new URL(isolatedWebAppURL).hostname}`
)
);
fs.writeFileSync(
"./assets/script.js",
decoder.decode(script).replace(
/USER_AGENT\s=\s"?.+"/g,
`USER_AGENT = "Built with ${navigator.userAgent}"`,
),
);
const { fileName, source } = await bundleIsolatedWebApp({
baseURL: isolatedWebAppURL,
static: { dir: "assets" },
formatVersion: "b2",
output: "signed.swbn",
integrityBlockSign: {
webBundleId,
isIwa: true,
// https://github.com/GoogleChromeLabs/webbundle-plugins/blob/d251f6efbdb41cf8d37b9b7c696fd5c795cdc231/packages/rollup-plugin-webbundle/test/test.js#L408
// wbn-sign/lib/signers/node-crypto-signing-strategy.js
strategies: [new (class CustomSigningStrategy {
async sign(data) {
return new Uint8Array(
await webcrypto.subtle.sign(algorithm, cryptoKey.privateKey, data),
);
}
async getPublicKey() {
return cryptoKey.publicKey;
}
})()],
},
headerOverride: {
"cross-origin-embedder-policy": "require-corp",
"cross-origin-opener-policy": "same-origin",
"cross-origin-resource-policy": "same-origin",
"content-security-policy":
"base-uri 'none'; default-src 'self'; object-src 'none'; frame-src 'self' https: blob: data:; connect-src 'self' https: wss:; script-src 'self' 'wasm-unsafe-eval'; img-src 'self' https: blob: data:; media-src 'self' https: blob: data:; font-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; require-trusted-types-for 'script';",
},
});
fs.writeFileSync(fileName, source);
console.log(`\x1b[38;5;220m${fileName} ${source.byteLength} bytes.`);
The above changes don't change Node.js behaviour.
Now the same code can be used by node, deno, and bun. The bytes of the generated .swbn are different between the runtimes only because I dynamically write the user agent of the runtime in the script that is bundled and run in the IWA.
$ deno run -A --unstable-byonm index.js
isolated-app://yoihnto6u24xgcuwc374ffu44koa6tsmdsi7giqqoinwcyjhxxnqaaic/
signed.swbn, 18093 bytes.
$ bun run index.js
isolated-app://yoihnto6u24xgcuwc374ffu44koa6tsmdsi7giqqoinwcyjhxxnqaaic/
signed.swbn, 18092 bytes.
$ node --experimental-default-type=module index.js
(node:71318) ExperimentalWarning: The Ed25519 Web Crypto API algorithm is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
isolated-app://yoihnto6u24xgcuwc374ffu44koa6tsmdsi7giqqoinwcyjhxxnqaaic/
signed.swbn, 18092 bytes.
Happy hacking SWBN's and IWA's using Node.js and Deno and Bun.