Open bitdivine opened 2 years ago
Working on something similar https://github.com/guest271314/webbundle. My goal is to bundle the scripts to be used in the browser, bring your own .pem
file. What I've found is this library is not simple to bundle. Really does depend on Node.js. Deno throws no supported algorithm when signing.
Yes. Node support is unfortunately required as long as Ed25519 keys are not supported by SubtleCrypto so that JS-only library would be possible. So node is needed in order to sign a web bundle, as Node's Crypto API supports Ed25519 keys.
@sonkkeli That's rather restrictive. Means the source code is importable using Deno though throws. Have you reached out to the Deno people about supporting your library?
FWIW I couldn't get wbn
and wbn-sign
to generate an IWA standalone.
import webpack from "npm:webpack";
import WebBundlePlugin from "npm:webbundle-webpack-plugin";
import {NodeCryptoSigningStrategy,
parsePemKey,
readPassphrase,
WebBundleId} from "npm:wbn-sign@^0.1.0"
import { merge } from "npm:webpack-merge";
import HtmlWebpackPlugin from "npm:html-webpack-plugin";
import CopyPlugin from "npm:copy-webpack-plugin";
import fs from "node:fs";
import path from "node:path";
// ...
const privateKeyFile = "ed25519key.pem";
const privateKey = fs.readFileSync(privateKeyFile);
const parsedPrivateKey = parsePemKey(privateKey);
const baseURL = new WebBundleId(
parsedPrivateKey,
).serializeWithIsolatedWebAppOrigin();
console.log(baseURL);
const webBundlePlugin = new WebBundlePlugin({
baseURL,
output: "signed.swbn",
integrityBlockSign: {
key: parsedPrivateKey,
},
});
deno run -A webpack.wbn.latest.js
error: Uncaught TypeError: Unsupported algorithm
at Object.createPrivateKey (ext:deno_node/internal/crypto/keys.ts:104:23)
at parsePemKey (file:///home/user/webbundle/node_modules/.deno/wbn-sign@0.1.2/node_modules/wbn-sign/lib/utils/utils.js:24:19)
at file:///home/user/webbundle/webpack.wbn.latest.js:61:26
No I haven't. To me it looks like ed25519 should be supported by deno https://deno.land/std@0.166.0/node/internal/crypto/types.ts?s=KeyType but I'm using the Node.js so haven't tried myself.
Is a goal of webbundle to be used exclusively in a Node.js environment?
Bun has issues with worker_threads
not being implemented https://github.com/oven-sh/bun/issues/7479.
I usually try to write source code that can be used in Node.js, Deno, Bun, and other JavaScript runtimes in the spirit of proposal-common-minimum-api.
It's not limited to node-only as there's also golang version of the package. Whenever the support for ed25519 keys is added to SubtleCrypto, it'll be rather simple to create a browser-only version of the wbn-sign package. Web bundles are still experimental and I'm sure the tooling coverage improves when use of IWAs increases.
It looks like file:///home/user/webbundle/node_modules/.deno/wbn-sign@0.1.2/node_modules/wbn-sign/lib/utils/utils.js:24:19 is importing from keys.ts
at Object.createPrivateKey (ext:deno_node/internal/crypto/keys.ts:104:23)
not types.ts
. Is that a simple fix to adjust the library source code for the case of using Deno to sign the bundle?
Looks like this is supported in Deno https://github.com/denoland/deno/blob/ca64771257d23ceee97e882965269702c359f6aa/cli/tests/node_compat/test/parallel/test-webcrypto-sign-verify.js#L115-L133
// Test Sign/Verify Ed25519
{
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
You can create a pull request here https://github.com/WICG/webpackage/blob/main/js/sign/src/utils/utils.ts
I'm not sure yet how to incorporate that code into utils.js.
const signature = await subtle.sign({
name: 'Ed25519',
}, privateKey, ec.encode(data));
or
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
?
I'll do some tests and if I can get it working will file a PR.
@sonkkeli Almost forgot. WICG banned me so I can't file a PR over there.
Yes. Node support is unfortunately required as long as Ed25519 keys are not supported by SubtleCrypto
Node support is not required then, if we are talking about Ed25519, self.crypto.subtle
.
This deno test code modified to run the in the browser logs true
on Chromium Version 122.0.6170.0 (Developer Build) (64-bit) in DevTools console
// Test Sign/Verify Ed25519
{
async function test(data) {
const ec = new TextEncoder();
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
const signature = await subtle.sign({
name: 'Ed25519',
}, privateKey, ec.encode(data));
assert(await subtle.verify({
name: 'Ed25519',
}, publicKey, signature, ec.encode(data)));
}
test('hello world').then(common.mustCall());
}
This returns true
in the browser.
// Test Sign/Verify Ed25519
{
async function test(data) {
const {subtle} = self.crypto;
const ec = new TextEncoder();
const { publicKey, privateKey } = await subtle.generateKey({
name: 'Ed25519',
}, true, ['sign', 'verify']);
const signature = await subtle.sign({
name: 'Ed25519',
}, privateKey, ec.encode(data));
console.log(await subtle.verify({
name: 'Ed25519',
}, publicKey, signature, ec.encode(data)));
}
test('hello world').catch(console.error);
}
We can read and write files to the local file system with WICG File System Access API.
So we should be able to create Signed Web Bundles and Isolated Web Apps in the browser.
Secure Curves in the Web Cryptography API
The Implementation of the Ed25519 algorithm landed Chromium in Nov 2022 and shipped in Chrome 110.0.5424.0. The X25519 key sharing algorithm took more time due to the review process, but it finally landed in March 2023 and has been shipped in Chrome since 113.0.5657.0.
@sonkkeli
It's not limited to node-only as there's also golang version of the package. Whenever the support for ed25519 keys is added to SubtleCrypto, it'll be rather simple to create a browser-only version of the wbn-sign package.
Alright, a slightly modified version of this https://stackblitz.com/edit/single-node-js-bundle?file=src%2Findex.js bundles wbn
and wbn-sign
into a single script.
Now I just need to know what I'm doing incorrectly here. WIth this signed.swbn
is not being loaded as an Isolated Web App from file
const wbn = require("wbn");
const wbnSign = require("wbn-sign");
const fs = require("fs");
const path = require("path");
// const { merge } = require("webpack-merge");
// const HtmlWebpackPlugin = require("html-webpack-plugin");
// const CopyPlugin = require("copy-webpack-plugin");
// const CopyPlugin = require("copy-webpack-plugin");
// use imported module anywhere to avoid tree shaking of unused modules
console.log(
">> app",
wbn,
wbnSign,
);
const headers = {
"content-security-policy":
"base-uri 'none'; default-src 'self'; object-src 'none'; frame-src 'self' https: blob: data:; connect-src 'self' https:; script-src 'self' 'wasm-unsafe-eval'; img-src 'self' https: blob: data:; media-src 'self' https: blob: data:; font-src 'self' blob: data:; require-trusted-types-for 'script'; frame-ancestors 'self';",
"cross-origin-embedder-policy": "require-corp",
"cross-origin-opener-policy": "same-origin",
"cross-origin-resource-policy": "same-origin",
};
const privateKey = wbnSign.parsePemKey(
fs.readFileSync(path.resolve(__dirname, "../src/ed25519key.pem"), "utf-8"),
);
// Web Bundle ID only:
const webBundleId = new wbnSign.WebBundleId(privateKey).serialize();
// With origin, meaning "isolated-app://" combined with Web Bundle ID:
const webBundleIdWithIWAOrigin = new wbnSign.WebBundleId(
privateKey,
).serializeWithIsolatedWebAppOrigin();
console.log(webBundleId, webBundleIdWithIWAOrigin);
const builder = new wbn.BundleBuilder();
builder.addExchange(
webBundleIdWithIWAOrigin, // URL
200, // response code
{ "Content-Type": "text/html", ...headers }, // response headers
fs.readFileSync(path.resolve(__dirname, "../src/index.html")), // response body (string or Uint8Array)
);
builder.addExchange(
webBundleIdWithIWAOrigin + "script.js",
200,
{ "Content-Type": "text/javascript", ...headers },
fs.readFileSync(path.resolve(__dirname, "../src/script.js")),
);
builder.addExchange(
webBundleIdWithIWAOrigin + "manifest.webmanifest",
200,
{ "Content-Type": "application/manifest+json", ...headers },
fs.readFileSync(path.resolve(__dirname, "../src/manifest.webmanifest")),
);
builder.setPrimaryURL(webBundleIdWithIWAOrigin);
fs.writeFileSync("out.wbn", builder.createBundle());
const buf = fs.readFileSync("out.wbn");
const bundle = new wbn.Bundle(buf);
const exchanges = [];
for (const url of bundle.urls) {
const resp = bundle.getResponse(url);
exchanges.push({
url,
status: resp.status,
headers: resp.headers,
body: new TextDecoder("utf-8").decode(resp.body),
});
}
console.log(
JSON.stringify(
{
version: bundle.version, // format version
exchanges,
},
null,
2,
),
);
(async () => {
// Option 1: With the default (`NodeCryptoSigningStrategy`) signing strategy.
const { signedWebBundle } = await new wbnSign.IntegrityBlockSigner(
buf,
new wbnSign.NodeCryptoSigningStrategy(privateKey),
).sign();
fs.writeFileSync("signed.swbn", signedWebBundle);
})();
@sonkkeli I stopped asking questions and for help and got to work yesterday. Subtituted webcrypto
for node:crypto
usages in the Rollup version; and Uint8Array
for Buffer
. The same code can now be run using node
, deno
, and bun
to produce the same Signed Web Bundle and Isolated Web App. I still have to clean up the code before I file a PR.
Thank you for this plugin. This looks good, but is there a plugin for eager web-bundle consumers rather than creators?
My Goal
A user can click on https://somedomain.org and the web page loads, using a signed https exchange, without any web server being entrusted with a certificate for https://somedomain.org. Entrusting a webserver with a certificate is deeply problematic in some use cases.
I would like the solution to be generic, so that the solution (plugin or otherwise) can be audited once, then used with many domains, rather than special casing every single domain that wants to "go serverless with SXG". If this is too optimistic, I could start with a root domain such as "we-love-sxg.org" and each subdomain could be served with its own SXG file.
Plan
Have a generic way of serving SXG files if there is no https server but there is a DNS record indicating the location of an SXG file.
This could be a browser plugin that, if an https request fails, does a DNS lookup looking for a txt record with the URL of an SXG file. If one exists, get that instead. If such a plugin already exists, super, I'd love to play with it. If not, I would like to explore the space. It looks as if manifest3 plugins support redirects but not necessarily in a way compatible with this idea.