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
…#879)

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 authored Aug 3, 2023
1 parent 89b97c7 commit e68d465
Show file tree
Hide file tree
Showing 11 changed files with 224 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.

55 changes: 55 additions & 0 deletions js/sign/src/utils/cli-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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' };

// @ts-expect-error Unknown property `fd`.
const fileDescriptor: number = process.stdout.fd ?? 1;

// If the log is used for non-terminal (fd != 1), e.g., setting an environment
// variable, it shouldn't have any formatting.
console.log(
tty.isatty(fileDescriptor)
? `${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 e68d465

Please sign in to comment.