Skip to content

Commit

Permalink
wbn-sign: Add support for calculating the Web Bundle ID with CLI tool
Browse files Browse the repository at this point in the history
This adds the support to automatically calculate the Web Bundle ID also
when using the package's Node CLI tool without Webpack / Rollup plugins.

There's two options how to output the ID:
1. Printing to the console
2. Creating a file where it is saved.

In the case of a bash script, the file can be read and saved into an
environment variable for example like this:
`export DUMP_WEB_BUNDLE_ID="$(<webbundleid.txt )"`
  • Loading branch information
sonkkeli committed Aug 3, 2023
1 parent 581d518 commit 893d306
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 56 deletions.
59 changes: 57 additions & 2 deletions js/sign/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,24 @@ const webBundleIdWithIWAOrigin = new wbnSign.WebBundleId(

## CLI

This package also includes a CLI tool `wbn-sign` which lets you sign a web
bundle easily without having to write any additional JavaScript.
This package also includes 2 CLI tools

- `wbn-sign` which lets you sign a web bundle easily without having to write any
additional JavaScript.
- `wbn-dump-id` which can be used to calculate the Web Bundle ID corresponding
to your signing key.

### Running wbn-sign

There are the following command-line flags available:

- (required) `--privateKey <filePath>` (`-k <filePath>`)
which takes the path to ed25519 private key.
- (required) `--input <filePath>` (`-i <filePath>`)
which takes the path to the web bundle to be signed.
- (optional) `--output <filePath>` (`-o <filePath>`)
which takes the path to the wanted signed web bundle output. Default:
`signed.swbn`.

Example command:

Expand All @@ -104,6 +120,41 @@ wbn-sign \
-k ~/path/to/ed25519key.pem
```

### Running wbn-dump-id

There are the following command-line flags available:

- (required) `--privateKey <filePath>` (`-k <filePath>`)
which takes the path to ed25519 private key.
- (optional) `--scheme <boolean>` (`-s`)
which dumps the Web Bundle ID with isolated-app:// scheme. By default it only
dumps the ID. Default: `false`.
- (optional) `--output <"print"|<fileName>>` (`-o <"print"|<fileName>>`)
which either prints the Web Bundle ID to the console or saves it into a file
with the given name. Default: `print`.

Example command:

```bash
wbn-sign \
-s \
-o ~/path/to/web-bundle-id.txt \
-k ~/path/to/ed25519key.pem
```

This would save the Web Bundle ID calculated from `ed25519key.pem` into a file
named `web-bundle-id.txt` with the `isolated-app://` scheme.

If the ID was saved into a file, one can easily use it in a bash script and save
it into an environment variable like this:
`export DUMP_WEB_BUNDLE_ID="$(<webbundleid.txt)"`

And then the environment variable which was set like this, can be used in other
scripts, e.g. in `--baseURL` when creating a web bundle with
[wbn CLI tool](https://github.com/WICG/webpackage/tree/main/js/bundle#cli).

## Generating Ed25519 key

An unencrypted ed25519 private key can be generated with:

```
Expand All @@ -127,6 +178,10 @@ environment variable named `WEB_BUNDLE_SIGNING_PASSPHRASE`.

## Release Notes

### v0.1.2

- Add support for calculating the Web Bundle ID with the CLI tool.

### v0.1.1

- Add support for bypassing the passphrase prompt for encrypted private keys by
Expand Down
4 changes: 4 additions & 0 deletions js/sign/bin/wbn-dump-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
import { main } from '../lib/cli-dump-id.js';

main();
2 changes: 1 addition & 1 deletion js/sign/bin/wbn-sign.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node
import { main } from '../lib/cli.js';
import { main } from '../lib/cli-sign.js';

main();
1 change: 1 addition & 0 deletions js/sign/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion js/sign/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"lint": "npx prettier --write . --ignore-unknown --config ./package.json"
},
"bin": {
"wbn-sign": "./bin/wbn-sign.js"
"wbn-sign": "./bin/wbn-sign.js",
"wbn-dump-id": "./bin/wbn-dump-id.js"
},
"repository": {
"type": "git",
Expand Down
47 changes: 47 additions & 0 deletions js/sign/src/cli-dump-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import commander from 'commander';
import { WebBundleId } from './wbn-sign.js';
import * as fs from 'fs';
import { greenConsoleLog, parseMaybeEncryptedKey } from './utils/cli-utils.js';
import { KeyObject } from 'crypto';

const program = new commander.Command()
.name('wbn-dump-id')
.description(
'A simple CLI tool to dump the Web Bundle ID matching to the given private key.'
);

function readOptions() {
return program
.requiredOption(
'-k, --privateKey <file>',
'path to ed25519 private key (required)'
)
.option(
'-s, --scheme',
'Dumps the Web Bundle ID with isolated-app:// scheme. By default it only dumps the ID.',
/*defaultValue=*/ false
)
.option(
'-o, --output <"print"|<fileName>>',
'Either prints the Web Bundle ID to the console or saves it into a file with the given name.',
/*defaultValue=*/ 'print'
)
.parse(process.argv);
}

export async function main() {
const options = readOptions();
const parsedPrivateKey: KeyObject = await parseMaybeEncryptedKey(
fs.readFileSync(options.privateKey)
);

const webBundleId = options.scheme
? new WebBundleId(parsedPrivateKey).serializeWithIsolatedWebAppOrigin()
: new WebBundleId(parsedPrivateKey).serialize();

if (options.output === 'print') {
greenConsoleLog(webBundleId);
} else {
fs.writeFileSync(options.output, webBundleId);
}
}
48 changes: 48 additions & 0 deletions js/sign/src/cli-sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import commander from 'commander';
import {
NodeCryptoSigningStrategy,
IntegrityBlockSigner,
WebBundleId,
} from './wbn-sign.js';
import * as fs from 'fs';
import { greenConsoleLog, parseMaybeEncryptedKey } from './utils/cli-utils.js';
import { KeyObject } from 'crypto';

const program = new commander.Command()
.name('wbn-sign')
.description(
'A simple CLI tool to sign the given web bundle with the given private key.'
);

function readOptions() {
return program
.requiredOption(
'-i, --input <file>',
'input web bundle to be signed (required)'
)
.requiredOption(
'-k, --privateKey <file>',
'path to ed25519 private key (required)'
)
.option(
'-o, --output <file>',
'signed web bundle output file',
/*defaultValue=*/ 'signed.swbn'
)
.parse(process.argv);
}

export async function main() {
const options = readOptions();
const webBundle = fs.readFileSync(options.input);
const parsedPrivateKey: KeyObject = await parseMaybeEncryptedKey(
fs.readFileSync(options.privateKey)
);
const signer = new IntegrityBlockSigner(
webBundle,
new NodeCryptoSigningStrategy(parsedPrivateKey)
);
const { signedWebBundle } = await signer.sign();
greenConsoleLog(`${new WebBundleId(parsedPrivateKey)}`);
fs.writeFileSync(options.output, signedWebBundle);
}
49 changes: 3 additions & 46 deletions js/sign/src/cli.ts → js/sign/src/utils/cli-utils.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
import commander from 'commander';
import {
NodeCryptoSigningStrategy,
IntegrityBlockSigner,
WebBundleId,
parsePemKey,
readPassphrase,
} from './wbn-sign.js';
import * as fs from 'fs';
import { WebBundleId, parsePemKey, readPassphrase } from '../wbn-sign.js';
import { KeyObject } from 'crypto';

function readOptions() {
return commander
.requiredOption(
'-i, --input <file>',
'input web bundle to be signed (required)'
)
.requiredOption(
'-k, --privateKey <file>',
'path to ed25519 private key (required)'
)
.option(
'-o, --output <file>',
'signed web bundle output file',
'signed.wbn'
)
.parse(process.argv);
}

// Parses either an unencrypted or encrypted private key. For encrypted keys, it
// reads the passphrase to decrypt them from either the
// `WEB_BUNDLE_SIGNING_PASSPHRASE` environment variable, or, if not set, prompts
Expand Down Expand Up @@ -64,24 +38,7 @@ export async function parseMaybeEncryptedKey(
}
}

export async function main() {
const options = readOptions();
const webBundle = fs.readFileSync(options.input);
const parsedPrivateKey = await parseMaybeEncryptedKey(
fs.readFileSync(options.privateKey)
);
const signer = new IntegrityBlockSigner(
webBundle,
new NodeCryptoSigningStrategy(parsedPrivateKey)
);
const { signedWebBundle } = await signer.sign();

export function greenConsoleLog(textToPrint: string): void {
const consoleLogColor = { green: '\x1b[32m', reset: '\x1b[0m' };
console.log(
`${consoleLogColor.green}${new WebBundleId(parsedPrivateKey)}${
consoleLogColor.reset
}`
);

fs.writeFileSync(options.output, signedWebBundle);
console.log(`${consoleLogColor.green}${textToPrint}${consoleLogColor.reset}`);
}
20 changes: 14 additions & 6 deletions js/sign/tests/cli_test.js → js/sign/tests/cli-utils_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as cli from '../lib/cli.js';
import * as cliUtils from '../lib/utils/cli-utils.js';
import * as mockStdin from 'mock-stdin';

const TEST_UNENCRYPTED_PRIVATE_KEY = Buffer.from(
Expand Down Expand Up @@ -37,19 +37,25 @@ describe('CLI key parsing', () => {
});

it('works for unencrypted key.', async () => {
const key = await cli.parseMaybeEncryptedKey(TEST_UNENCRYPTED_PRIVATE_KEY);
const key = await cliUtils.parseMaybeEncryptedKey(
TEST_UNENCRYPTED_PRIVATE_KEY
);
expect(key.type).toEqual('private');
});

it('works for encrypted key read from `WEB_BUNDLE_SIGNING_PASSPHRASE`.', async () => {
process.env.WEB_BUNDLE_SIGNING_PASSPHRASE = PASSPHRASE;
const key = await cli.parseMaybeEncryptedKey(TEST_ENCRYPTED_PRIVATE_KEY);
const key = await cliUtils.parseMaybeEncryptedKey(
TEST_ENCRYPTED_PRIVATE_KEY
);
expect(key.type).toEqual('private');
});

it('works for encrypted key read from a prompt.', async () => {
const stdin = mockStdin.stdin();
const keyPromise = cli.parseMaybeEncryptedKey(TEST_ENCRYPTED_PRIVATE_KEY);
const keyPromise = cliUtils.parseMaybeEncryptedKey(
TEST_ENCRYPTED_PRIVATE_KEY
);
stdin.send(`${PASSPHRASE}\n`);
expect((await keyPromise).type).toEqual('private');
});
Expand All @@ -58,14 +64,16 @@ describe('CLI key parsing', () => {
process.env.WEB_BUNDLE_SIGNING_PASSPHRASE = 'helloworld1';

await expectToThrowErrorAsync(() =>
cli.parseMaybeEncryptedKey(TEST_ENCRYPTED_PRIVATE_KEY)
cliUtils.parseMaybeEncryptedKey(TEST_ENCRYPTED_PRIVATE_KEY)
);
});

it('fails for faulty passphrase read from a prompt.', async () => {
await expectToThrowErrorAsync(async () => {
const stdin = mockStdin.stdin();
const keyPromise = cli.parseMaybeEncryptedKey(TEST_ENCRYPTED_PRIVATE_KEY);
const keyPromise = cliUtils.parseMaybeEncryptedKey(
TEST_ENCRYPTED_PRIVATE_KEY
);
stdin.send(`${PASSPHRASE}1\n`);
await keyPromise;
});
Expand Down

0 comments on commit 893d306

Please sign in to comment.