Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove v1 integrity block format support from wbn-sign #897

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ draft-yasskin-dispatch-web-packaging.xml
/lib
loading.html
subresource-loading.html
.vscode/
6 changes: 3 additions & 3 deletions js/sign/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ There are the following command-line flags available:
- (optional) `--output <filePath>` (`-o <filePath>`)
which takes the path to the wanted signed web bundle output. Default:
`signed.swbn`.
- (optional) `--version <version>`
which can be either `v1` or `v2`, defaulting to `v1`. Sets the integrity block format.
- (required if more than one key is provided) `--web-bundle-id <web-bundle-id>`
which takes the `web-bundle-id` to be associated with the web bundle.

Expand All @@ -130,7 +128,6 @@ wbn-sign \
-o ~/path/to/signed-webbundle.swbn \
-k ~/path/to/ed25519key.pem \
-k ~/path/to/ecdsa_p256key.pem
--version v2 \
--web-bundle-id \
amfcf7c4bmpbjbmq4h4yptcobves56hfdyr7tm3doxqvfmsk5ss6maacai
```
Expand Down Expand Up @@ -194,6 +191,9 @@ environment variable named `WEB_BUNDLE_SIGNING_PASSPHRASE`.

## Release Notes

### v0.2.2
- BREAKING CHANGE: Removed support for v1 integrity block format.

### v0.2.1

- Moved is_v2 to the last and optional (defaulting to true) argument of
Expand Down
4 changes: 2 additions & 2 deletions js/sign/package-lock.json

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

2 changes: 1 addition & 1 deletion js/sign/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wbn-sign",
"version": "0.2.1",
"version": "0.2.2",
"description": "Signing tool to sign a web bundle with integrity block",
"homepage": "https://github.com/WICG/webpackage/tree/main/js/sign",
"main": "./lib/wbn-sign.cjs",
Expand Down
34 changes: 5 additions & 29 deletions js/sign/src/cli-sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ const program = new Command()

function readOptions() {
return program
.addOption(
new Option('--version <version>').choices(['v1', 'v2']).default('v2')
)
.requiredOption(
'-i, --input <file>',
'input web bundle to be signed (required)'
Expand All @@ -37,30 +34,10 @@ function readOptions() {
)
.option('--web-bundle-id <web-bundle-id>', 'web bundle ID (only for v2)')
.action((options) => {
switch (options.version) {
case 'v1':
{
if (options.privateKey.length > 1) {
throw new Error(
`It's not allowed to specify more than one private key for v1 signing.`
);
}
if (options.webBundleId) {
throw new Error(
`It's not allowed to specify --web-bundle-id for v1 signing.`
);
}
}
break;
case 'v2':
{
if (options.privateKey.length > 1 && !options.webBundleId) {
throw new Error(
`--web-bundle-id must be specified if there's more than 1 signing key involved.`
);
}
}
break;
if (options.privateKey.length > 1 && !options.webBundleId) {
throw new Error(
`--web-bundle-id must be specified if there's more than 1 signing key involved.`
);
}
})
.parse(process.argv)
Expand All @@ -80,10 +57,9 @@ export async function main() {
? options.webBundleId
: new WebBundleId(privateKeys[0]).serialize();
const signer = new IntegrityBlockSigner(
webBundle,
Uint8Array.from(webBundle),
webBundleId,
privateKeys.map((privateKey) => new NodeCryptoSigningStrategy(privateKey)),
/*is_v2=*/ options.version === 'v2'
);
const { signedWebBundle } = await signer.sign();
greenConsoleLog(`${webBundleId}`);
Expand Down
48 changes: 13 additions & 35 deletions js/sign/src/signers/integrity-block-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import crypto, { KeyObject } from 'crypto';
import * as cborg from 'cborg';
import {
INTEGRITY_BLOCK_MAGIC,
VERSION_B1,
VERSION_B2,
} from '../utils/constants.js';
import { checkDeterministic } from '../cbor/deterministic.js';
Expand All @@ -22,22 +21,18 @@ type IntegritySignature = {
};

export class IntegrityBlockSigner {
// `webBundleId` is ignored if `is_v2` is false.
constructor(
private readonly webBundle: Uint8Array,
private readonly webBundleId: string,
private readonly signingStrategies: Array<ISigningStrategy>,
private readonly is_v2: boolean = true
) {}

async sign(): Promise<{
integrityBlock: Uint8Array;
signedWebBundle: Uint8Array;
}> {
const integrityBlock = this.obtainIntegrityBlock().integrityBlock;
if (this.is_v2) {
integrityBlock.setWebBundleId(this.webBundleId);
}
integrityBlock.setWebBundleId(this.webBundleId);

const signatures = new Array<IntegritySignature>();
for (const signingStrategy of this.signingStrategies) {
Expand Down Expand Up @@ -104,7 +99,7 @@ export class IntegrityBlockSigner {
'IntegrityBlockSigner: Re-signing signed bundles is not supported yet.'
);
}
return { integrityBlock: new IntegrityBlock(this.is_v2), offset: 0 };
return { integrityBlock: new IntegrityBlock(), offset: 0 };
}

calcWebBundleHash(): Uint8Array {
Expand Down Expand Up @@ -172,14 +167,9 @@ export class IntegrityBlock {
private attributes: Map<string, string> = new Map();
private signatureStack: IntegritySignature[] = [];

constructor(private readonly is_v2: boolean = false) {}
constructor() {}

setWebBundleId(webBundleId: string) {
if (!this.is_v2) {
throw new Error(
'setWebBundleId() is only available for v2 integrity blocks.'
);
}
this.attributes.set('webBundleId', webBundleId);
}

Expand All @@ -188,27 +178,15 @@ export class IntegrityBlock {
}

toCBOR(): Uint8Array {
if (this.is_v2) {
return cborg.encode([
INTEGRITY_BLOCK_MAGIC,
VERSION_B2,
this.attributes,
this.signatureStack.map((integritySig) => {
// The CBOR must have an array of length 2 containing the following:
// (0) attributes and (1) signature. The order is important.
return [integritySig.signatureAttributes, integritySig.signature];
}),
]);
} else {
return cborg.encode([
INTEGRITY_BLOCK_MAGIC,
VERSION_B1,
this.signatureStack.map((integritySig) => {
// The CBOR must have an array of length 2 containing the following:
// (0) attributes and (1) signature. The order is important.
return [integritySig.signatureAttributes, integritySig.signature];
}),
]);
}
return cborg.encode([
INTEGRITY_BLOCK_MAGIC,
VERSION_B2,
this.attributes,
this.signatureStack.map((integritySig) => {
// The CBOR must have an array of length 2 containing the following:
// (0) attributes and (1) signature. The order is important.
return [integritySig.signatureAttributes, integritySig.signature];
}),
]);
}
}
1 change: 0 additions & 1 deletion js/sign/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ export const PUBLIC_KEY_ATTRIBUTE_NAME_MAPPING = new Map<SignatureType, string>(
export const INTEGRITY_BLOCK_MAGIC = new Uint8Array([
0xf0, 0x9f, 0x96, 0x8b, 0xf0, 0x9f, 0x93, 0xa6,
]); // 🖋📦
export const VERSION_B1 = new Uint8Array([0x31, 0x62, 0x00, 0x00]); // 1b\0\0
export const VERSION_B2 = new Uint8Array([0x32, 0x62, 0x00, 0x00]); // 2b\0\0
38 changes: 25 additions & 13 deletions js/sign/tests/integrity-block-signer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import url from 'url';
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const TEST_WEB_BUNDLE_HASH =
'95f8713d382ffefb8f1e4f464e39a2bf18280c8b26434d2fcfc08d7d710c8919ace5a652e25e66f9292cda424f20e4b53bf613bf9488140272f56a455393f7e6';
const EMPTY_INTEGRITY_BLOCK_HEX = '8348f09f968bf09f93a6443162000080';
const EMPTY_INTEGRITY_BLOCK_HEX = '8448f09f968bf09f93a64432620000a080';
const TEST_ED25519_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIB8nP5PpWU7HiILHSfh5PYzb5GAcIfHZ+bw6tcd/LZXh
-----END PRIVATE KEY-----`;
Expand Down Expand Up @@ -66,11 +66,11 @@ describe('Integrity Block Signer', () => {
const contents = fs.readFileSync(file);
const signer = new wbnSign.IntegrityBlockSigner(
contents,
/*webBundleId=*/ webBundleId,
/*webBundleId=*/ webBundleId ||
new wbnSign.WebBundleId(privateKeys[0]).serialize(),
privateKeys.map(
(privateKey) => new wbnSign.NodeCryptoSigningStrategy(privateKey)
),
/*is_v2=*/ !!webBundleId
)
);
return signer;
}
Expand Down Expand Up @@ -120,10 +120,11 @@ describe('Integrity Block Signer', () => {
);

const decoded = cborg.decode(cbor);
expect(decoded.length).toEqual(3);
expect(decoded.length).toEqual(4);
expect(decoded[0]).toEqual(constants.INTEGRITY_BLOCK_MAGIC);
expect(decoded[1]).toEqual(constants.VERSION_B1);
expect(decoded[2]).toEqual([]);
expect(decoded[1]).toEqual(constants.VERSION_B2);
expect(decoded[2]).toEqual({});
expect(decoded[3]).toEqual([]);
});

it('calculates the hash of the web bundle correctly.', () => {
Expand Down Expand Up @@ -173,7 +174,7 @@ describe('Integrity Block Signer', () => {
const hexHashString =
/*64*/ '0000000000000040' +
TEST_WEB_BUNDLE_HASH +
/*16*/ '0000000000000010' +
/*17*/ '0000000000000011' +
EMPTY_INTEGRITY_BLOCK_HEX +
attributesCborHex;

Expand All @@ -195,18 +196,29 @@ describe('Integrity Block Signer', () => {
const sigAttr = {
[utils.getPublicKeyAttributeName(keypair.publicKey)]: rawPubKey,
};
const webBundleId = new wbnSign.WebBundleId(
keypair.privateKey
).serialize();

const ibWithoutSignatures = new wbnSign.IntegrityBlock();
ibWithoutSignatures.setWebBundleId(webBundleId);

const dataToBeSigned = signer.generateDataToBeSigned(
signer.calcWebBundleHash(),
new wbnSign.IntegrityBlock().toCBOR(),
ibWithoutSignatures.toCBOR(),
cborg.encode(sigAttr)
);

const ib = cborg.decode((await signer.sign()).integrityBlock);
expect(ib.length).toEqual(3);
expect(ib.length).toEqual(4);

const [magic, version, attributes, signatureStack] = ib;

const [magic, version, signatureStack] = ib;
expect(magic).toEqual(constants.INTEGRITY_BLOCK_MAGIC);
expect(version).toEqual(constants.VERSION_B1);
expect(version).toEqual(constants.VERSION_B2);
expect(attributes).toEqual({
webBundleId: webBundleId,
});
expect(signatureStack.length).toEqual(1);
expect(signatureStack[0].length).toEqual(2);

Expand Down Expand Up @@ -253,7 +265,7 @@ describe('Integrity Block Signer', () => {
expect(signatureStack.length).toEqual(keyPairs.length);
signatureStack.map((entry) => expect(entry.length).toEqual(2));

const ibWithoutSignatures = new wbnSign.IntegrityBlock(/*is_v2=*/ true);
const ibWithoutSignatures = new wbnSign.IntegrityBlock();
ibWithoutSignatures.setWebBundleId(webBundleId);

for (let i = 0; i < keyPairs.length; i++) {
Expand Down
Loading