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.

In the case of a bash script, the Web Bundle ID can then be saved into
e.g. an environment variable or a file as instructed in the readme.
  • Loading branch information
sonkkeli committed Aug 3, 2023
1 parent 581d518 commit a26d4c0
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 97 deletions.
60 changes: 58 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,42 @@ 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) `--withIwaScheme <boolean>` (`-s`)
which dumps the Web Bundle ID with isolated-app:// scheme. By default it only
dumps the ID. Default: `false`.

Example command:

```bash
wbn-sign -s -k ~/path/to/ed25519key.pem
```

This would print the Web Bundle ID calculated from `ed25519key.pem` into the
console with the `isolated-app://` scheme.

If one wants to save the ID into a file or into an environment variable, one can
do the following (respectively):

```bash
wbn-dump-id -k file_enc.pem -s > webbundleid.txt
```

```bash
export DUMP_WEB_BUNDLE_ID="$(wbn-dump-id -k file_enc.pem -s)"
```

The environment variable set like this, can then be used in other scripts, for
example 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 +179,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
38 changes: 38 additions & 0 deletions js/sign/src/cli-dump-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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>',
'Reads an ed25519 private key from the given path. (required)'
)
.option(
'-s, --withIwaScheme',
'Dumps the Web Bundle ID with isolated-app:// scheme. By default it only dumps the ID. (optional)',
/*defaultValue=*/ false
)
.parse(process.argv);
}

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

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

greenConsoleLog(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);
}
87 changes: 0 additions & 87 deletions js/sign/src/cli.ts

This file was deleted.

51 changes: 51 additions & 0 deletions js/sign/src/utils/cli-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import tty from 'tty';
import { KeyObject } from 'crypto';
import { parsePemKey, readPassphrase } from '../wbn-sign.js';

// 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
// the user for the passphrase.
export async function parseMaybeEncryptedKey(
privateKeyFile: Buffer
): Promise<KeyObject> {
// Read unencrypted private key.
try {
return parsePemKey(privateKeyFile);
} catch (e) {
console.warn('This key is probably an encrypted private key.');
}

const hasEnvVarSet =
process.env.WEB_BUNDLE_SIGNING_PASSPHRASE &&
process.env.WEB_BUNDLE_SIGNING_PASSPHRASE !== '';

// Read encrypted private key.
try {
return parsePemKey(
privateKeyFile,
hasEnvVarSet
? process.env.WEB_BUNDLE_SIGNING_PASSPHRASE
: await readPassphrase()
);
} catch (e) {
throw Error(
`Failed decrypting encrypted private key with passphrase read from ${
hasEnvVarSet
? '`WEB_BUNDLE_SIGNING_PASSPHRASE` environment variable'
: 'prompt'
}`
);
}
}

export function greenConsoleLog(text: string): void {
const logColor = { green: '\x1b[32m', reset: '\x1b[0m' };

// `fd=1` is a numeric file descriptor referring to terminal. If the log is
// used for non-terminal, e.g., setting an environment variable, it shouldn't
// have any formatting.
console.log(
tty.isatty(/*fd=*/ 1) ? `${logColor.green}${text}${logColor.reset}` : text
);
}
3 changes: 3 additions & 0 deletions js/sign/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export async function readPassphrase(): Promise<string> {
prompt: 'Passphrase for the key: ',
silent: true,
replace: '*',
// Output must be != `stdout`. Otherwise saving the `wbn-dump-id`
// result into a file or an environment variable also includes the prompt.
output: process.stderr,
});
return passphrase;
} catch (er) {
Expand Down
Loading

0 comments on commit a26d4c0

Please sign in to comment.