Closed guest271314 closed 3 months ago
Are you using the js (1) or go (2) tool to create and sign the bundles, or did you write your own? If you wrote your own, it will need to be updated, details on the v2 format here, diff
Thanks for the links.
I wrote my own based on the Rollup version, that does not depend on Rollup or Webpack, mostly by hand to substitute Web Cryptography API and Node.js-specific code for runtime agnostic code.
It is JavaScript runtime agnostic, the same code can be run using node
, deno
, and bun
; and uses Web Cryptography API instead of Node.js-specific node:crypto
module, which cannot be polyfilled.
Did you consider that all existing SWBN's break at the slightest change in the integrity block signing algorithm, particularly when uploading the file that worked the previous day in Chromium 129?
Are you expecting all existing SWBN to not work, be broken immediately, with no backward compatibility, every time integrity block signers change?
@robbiemc What is the purpose of getMajorType()
?
function getMajorType(b) {
return (b & 255) >> 5;
}
Is there any technical reason const { webcrypto } = "node:crypto"
is not being used in the source code?
That would make it much simpler to port this library to Deno and Bun.
This is completely avoidable by library authors using Web Cryptography API to implement secure curve algorithms. Nothing is lost, functionality in two JavaScript runtimes other than Node.js is gained.
$ bun run webpack.config.js
HookWebpackError: [
{
"code": "unrecognized_keys",
"keys": [
"integrityBlockSign"
],
"path": [],
"message": "Unrecognized key(s) in object: 'integrityBlockSign'"
}
]
$ deno run -A webpack.config.js
error: Uncaught (in promise) TypeError: Unsupported algorithm
at Object.createPrivateKey (ext:deno_node/internal/crypto/keys.ts:118:19)
at parsePemKey
This is what I have so far re-writing wbn-sign
, utils.js
, etc. to use Web Cryptography API, again. For some reason the code doesn't get past deterministicRec()
webpackage-bundle.js.tar.gz
@robbiemc Alright, I made the changes by hand to update to Integrity Block V2, and supporting Web Cryptography API so this library can be run by deno
, bun
, and node
.
How I did this.
git clone https://github.com/GoogleChromeLabs/webbundle-plugins
cd webbundle-plugins/packages/rollup-plugin-webbundle
bun install -p
src/index.ts
comment line 18, : EnforcedPlugin
, line 32 const opts = await getValidatedOptionsWithDefaults(rawOpts);
and lines 65-121, because I will not be using Rollupbun build --target=node --format=esm --sourcemap=none --outfile=webpackage-bundle.js ./webbundle-plugins/packages/rollup-plugin-webbundle/src/index.ts
node:crypto
directly import { webcrypto } from "node:crypto";
/node_modules/wbn-sign/lib/utils/utils.js
use switch (key.algorithm.name) {
getRawPublicKey
becomes an async
function for substituting const exportedKey = await webcrypto.subtle.exportKey("spki", publicKey);
for publicKey.export({ type: "spki", format: "der" });
/node_modules/wbn-sign/lib/signers/integrity-block-signer.js
use const publicKey = await signingStrategy.getPublicKey();
and [getPublicKeyAttributeName(publicKey)]: await getRawPublicKey(publicKey)
; verifySignature()
also becomes an async
function where const algorithm = { name: "Ed25519" }; const isVerified = await webcrypto.subtle.verify(algorithm, publicKey, signature, data);
is substituted for const isVerified = crypto2.verify(undefined, data, publicKey, signature);
/node_modules/wbn-sign/lib/web-bundle-id.js
serialize()
function becomes async
for return base32Encode(new Uint8Array([...await getRawPublicKey(this.key), ...this.typeSuffix]), "RFC4648", { padding: false }).toLowerCase();
; and serializeWithIsolatedWebAppOrigin()
becomes an async
function for return
${this.scheme}${await this.serialize()}/;
; toString()
becomes an async
function for return \
Web Bundle ID: ${await this.serialize()} Isolated Web App Origin: ${await this.serializeWithIsolatedWebAppOrigin()}`;`src/index.ts
export {WebBundleId, bundleIsolatedWebApp};
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 verifyglobalThis.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.
My code using V1 has been working for some time now. What specifcally needs to be changed to support Integrity Block V2?