diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37f065fe..3ae72e6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12, 14, 16] + node-version: [16, 18, 20] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [14, 16] + node-version: [16, 18, 20] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 diff --git a/js/sign/README.md b/js/sign/README.md index 89ff4054..0c0cb9b6 100644 --- a/js/sign/README.md +++ b/js/sign/README.md @@ -17,7 +17,7 @@ npm install wbn-sign ## Requirements -This plugin requires Node v14.0.0+. +This plugin requires Node v16.0.0+. ## Usage @@ -179,6 +179,10 @@ environment variable named `WEB_BUNDLE_SIGNING_PASSPHRASE`. ## Release Notes +### v0.1.3 + +- Add support for ECDSA P-256 SHA-256 signatures + ### v0.1.2 - Add support for calculating the Web Bundle ID with the CLI tool. diff --git a/js/sign/package-lock.json b/js/sign/package-lock.json index 2acafc78..95b56630 100644 --- a/js/sign/package-lock.json +++ b/js/sign/package-lock.json @@ -19,7 +19,7 @@ "wbn-sign": "bin/wbn-sign.js" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^16.0.0", "esbuild": "^0.14.47", "jasmine": "^4.2.1", "mock-stdin": "^1.0.0", @@ -27,7 +27,7 @@ "typescript": "^4.7.3" }, "engines": { - "node": ">= 14.0.0", + "node": ">= 16.0.0", "npm": ">= 8.0.0" } }, @@ -48,9 +48,9 @@ } }, "node_modules/@types/node": { - "version": "14.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz", - "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==", + "version": "16.18.96", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", + "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==", "dev": true }, "node_modules/balanced-match": { @@ -626,9 +626,9 @@ "optional": true }, "@types/node": { - "version": "14.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz", - "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==", + "version": "16.18.96", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", + "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==", "dev": true }, "balanced-match": { diff --git a/js/sign/package.json b/js/sign/package.json index af404fc0..446f2ed4 100644 --- a/js/sign/package.json +++ b/js/sign/package.json @@ -1,6 +1,6 @@ { "name": "wbn-sign", - "version": "0.1.2", + "version": "0.1.3", "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", @@ -33,7 +33,8 @@ ], "author": "Sonja Laurila <laurila@google.com> (https://github.com/sonkkeli)", "contributors": [ - "Christian Flach <cmfcmf@google.com> (https://github.com/cmfcmf)" + "Christian Flach <cmfcmf@google.com> (https://github.com/cmfcmf)", + "Andrew Rayskiy <greengrape@google.com> (https://github.com/GrapeGreen)" ], "license": "W3C-20150513", "dependencies": { @@ -43,7 +44,7 @@ "read": "^2.0.0" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^16.0.0", "esbuild": "^0.14.47", "jasmine": "^4.2.1", "mock-stdin": "^1.0.0", @@ -51,7 +52,7 @@ "typescript": "^4.7.3" }, "engines": { - "node": ">= 14.0.0", + "node": ">= 16.0.0", "npm": ">= 8.0.0" }, "prettier": { diff --git a/js/sign/src/signers/integrity-block-signer.ts b/js/sign/src/signers/integrity-block-signer.ts index 11013596..8a5d0d18 100644 --- a/js/sign/src/signers/integrity-block-signer.ts +++ b/js/sign/src/signers/integrity-block-signer.ts @@ -1,15 +1,15 @@ import crypto, { KeyObject } from 'crypto'; import * as cborg from 'cborg'; -import { - ED25519_PK_SIGNATURE_ATTRIBUTE_NAME, - INTEGRITY_BLOCK_MAGIC, - VERSION_B1, -} from '../utils/constants.js'; +import { INTEGRITY_BLOCK_MAGIC, VERSION_B1 } from '../utils/constants.js'; import { checkDeterministic } from '../cbor/deterministic.js'; -import { getRawPublicKey, checkIsValidEd25519Key } from '../utils/utils.js'; +import { + getRawPublicKey, + checkIsValidKey, + getPublicKeyAttributeName, +} from '../utils/utils.js'; import { ISigningStrategy } from './signing-strategy-interface.js'; -type SignatureAttributeKey = typeof ED25519_PK_SIGNATURE_ATTRIBUTE_NAME; +type SignatureAttributeKey = string; type SignatureAttributes = { [SignatureAttributeKey: string]: Uint8Array }; type IntegritySignature = { @@ -29,10 +29,10 @@ export class IntegrityBlockSigner { }> { const integrityBlock = this.obtainIntegrityBlock().integrityBlock; const publicKey = await this.signingStrategy.getPublicKey(); - checkIsValidEd25519Key('public', publicKey); + checkIsValidKey('public', publicKey); const newAttributes: SignatureAttributes = { - [ED25519_PK_SIGNATURE_ATTRIBUTE_NAME]: getRawPublicKey(publicKey), + [getPublicKeyAttributeName(publicKey)]: getRawPublicKey(publicKey), }; const ibCbor = integrityBlock.toCBOR(); @@ -56,6 +56,7 @@ export class IntegrityBlockSigner { const signedIbCbor = integrityBlock.toCBOR(); checkDeterministic(signedIbCbor); + return { integrityBlock: signedIbCbor, signedWebBundle: new Uint8Array( @@ -132,6 +133,7 @@ export class IntegrityBlockSigner { signature: Uint8Array, publicKey: KeyObject ): void { + // For ECDSA P-256 keys the algorithm is implicitly selected as SHA-256. const isVerified = crypto.verify( /*algorithm=*/ undefined, data, diff --git a/js/sign/src/signers/node-crypto-signing-strategy.ts b/js/sign/src/signers/node-crypto-signing-strategy.ts index b9011024..d3839579 100644 --- a/js/sign/src/signers/node-crypto-signing-strategy.ts +++ b/js/sign/src/signers/node-crypto-signing-strategy.ts @@ -1,15 +1,16 @@ import crypto, { KeyObject } from 'crypto'; -import { checkIsValidEd25519Key } from '../utils/utils.js'; +import { checkIsValidKey } from '../utils/utils.js'; import { ISigningStrategy } from './signing-strategy-interface.js'; // Class to be used when signing with parsed `crypto.KeyObject` private key // provided directly in the constructor. export class NodeCryptoSigningStrategy implements ISigningStrategy { constructor(private readonly privateKey: KeyObject) { - checkIsValidEd25519Key('private', privateKey); + checkIsValidKey('private', privateKey); } async sign(data: Uint8Array): Promise<Uint8Array> { + // For ECDSA P-256 keys the algorithm is implicitly selected as SHA-256. return crypto.sign(/*algorithm=*/ undefined, data, this.privateKey); } diff --git a/js/sign/src/utils/constants.ts b/js/sign/src/utils/constants.ts index fc721bb6..15225e27 100644 --- a/js/sign/src/utils/constants.ts +++ b/js/sign/src/utils/constants.ts @@ -1,4 +1,15 @@ -export const ED25519_PK_SIGNATURE_ATTRIBUTE_NAME = 'ed25519PublicKey'; +export enum SignatureType { + Ed25519, + EcdsaP256SHA256, +} + +export const PUBLIC_KEY_ATTRIBUTE_NAME_MAPPING = new Map<SignatureType, string>( + [ + [SignatureType.Ed25519, 'ed25519PublicKey'], + [SignatureType.EcdsaP256SHA256, 'ecdsaP256SHA256PublicKey'], + ] +); + export const INTEGRITY_BLOCK_MAGIC = new Uint8Array([ 0xf0, 0x9f, 0x96, 0x8b, 0xf0, 0x9f, 0x93, 0xa6, ]); // 🖋📦 diff --git a/js/sign/src/utils/utils.ts b/js/sign/src/utils/utils.ts index d71dcdaa..b62de759 100644 --- a/js/sign/src/utils/utils.ts +++ b/js/sign/src/utils/utils.ts @@ -1,5 +1,10 @@ import crypto, { KeyObject } from 'crypto'; import read from 'read'; +import assert from 'assert'; +import { + PUBLIC_KEY_ATTRIBUTE_NAME_MAPPING, + SignatureType, +} from './constants.js'; // A helper function that can be used to read the passphrase to decrypt a // password-decrypted private key. @@ -31,15 +36,62 @@ export function parsePemKey( }); } -export function getRawPublicKey(publicKey: crypto.KeyObject) { - // Currently this is the only way for us to get the raw 32 bytes of the public key. - return new Uint8Array( - publicKey.export({ type: 'spki', format: 'der' }).slice(-32) +function maybeGetSignatureType(key: crypto.KeyObject): SignatureType | null { + switch (key.asymmetricKeyType) { + case 'ed25519': + return SignatureType.Ed25519; + case 'ec': + if (key.asymmetricKeyDetails?.namedCurve === 'prime256v1') { + return SignatureType.EcdsaP256SHA256; + } + break; + default: + break; + } + return null; +} + +export function isAsymmetricKeyTypeSupported(key: crypto.KeyObject): boolean { + return maybeGetSignatureType(key) !== null; +} + +export function getSignatureType(key: crypto.KeyObject): SignatureType { + const signatureType = maybeGetSignatureType(key); + assert( + signatureType !== null, + 'Expected either "Ed25519" or "ECDSA P-256" key.' ); + return signatureType; +} + +export function getPublicKeyAttributeName(key: crypto.KeyObject) { + return PUBLIC_KEY_ATTRIBUTE_NAME_MAPPING.get(getSignatureType(key))!; } -// Throws an error if the key is not a valid Ed25519 key of the specified type. -export function checkIsValidEd25519Key( +export function getRawPublicKey(publicKey: crypto.KeyObject) { + const exportedKey = publicKey.export({ type: 'spki', format: 'der' }); + switch (getSignatureType(publicKey)) { + case SignatureType.Ed25519: + // Currently this is the only way for us to get the raw 32 bytes of the public key. + return new Uint8Array(exportedKey.subarray(-32)); + case SignatureType.EcdsaP256SHA256: { + // The last 65 bytes are the raw bytes of the ECDSA P-256 public key. + // For the purposes of signing, we'd like to convert it to its compressed form that takes only 33 bytes. + const uncompressedKey = exportedKey.subarray(-65); + const compressedKey = crypto.ECDH.convertKey( + uncompressedKey, + 'prime256v1', + /*inputEncoding=*/ undefined, + /*outputEncoding=*/ undefined, + 'compressed' + ) as Buffer; + return new Uint8Array(compressedKey); + } + } +} + +// Throws an error if the key is not a valid Ed25519 or ECDSA P-256 key of the specified type. +export function checkIsValidKey( expectedKeyType: crypto.KeyObjectType, key: KeyObject ) { @@ -49,9 +101,7 @@ export function checkIsValidEd25519Key( ); } - if (key.asymmetricKeyType !== 'ed25519') { - throw new Error( - `Expected asymmetric key type to be "ed25519", but it was "${key.asymmetricKeyType}".` - ); + if (!isAsymmetricKeyTypeSupported(key)) { + throw new Error(`Expected either "Ed25519" or "ECDSA P-256" key.`); } } diff --git a/js/sign/src/web-bundle-id.ts b/js/sign/src/web-bundle-id.ts index efa8b30f..16abeb45 100644 --- a/js/sign/src/web-bundle-id.ts +++ b/js/sign/src/web-bundle-id.ts @@ -1,33 +1,44 @@ import crypto, { KeyObject } from 'crypto'; import base32Encode from 'base32-encode'; -import { getRawPublicKey } from './utils/utils.js'; +import { + getRawPublicKey, + isAsymmetricKeyTypeSupported, + getSignatureType, +} from './utils/utils.js'; +import { SignatureType } from './utils/constants.js'; // Web Bundle ID is a base32-encoded (without padding) ed25519 public key // transformed to lowercase. More information: // https://github.com/WICG/isolated-web-apps/blob/main/Scheme.md#signed-web-bundle-ids export class WebBundleId { // https://github.com/WICG/isolated-web-apps/blob/main/Scheme.md#suffix - private readonly appIdSuffix = [0x00, 0x01, 0x02]; + private readonly TYPE_SUFFIX_MAPPING = new Map<SignatureType, number[]>([ + [SignatureType.Ed25519, [0x00, 0x01, 0x02]], + [SignatureType.EcdsaP256SHA256, [0x00, 0x02, 0x02]], + ]); private readonly scheme = 'isolated-app://'; private readonly key: KeyObject; + private readonly typeSuffix: number[]; - constructor(ed25519key: KeyObject) { - if (ed25519key.asymmetricKeyType !== 'ed25519') { + constructor(key: KeyObject) { + if (!isAsymmetricKeyTypeSupported(key)) { throw new Error( - `WebBundleId: Only ed25519 keys are currently supported. Your key's type is ${ed25519key.asymmetricKeyType}.` + `WebBundleId: Only Ed25519 and ECDSA P-256 keys are currently supported.` ); } - if (ed25519key.type === 'private') { - this.key = crypto.createPublicKey(ed25519key); + if (key.type === 'private') { + this.key = crypto.createPublicKey(key); } else { - this.key = ed25519key; + this.key = key; } + + this.typeSuffix = this.TYPE_SUFFIX_MAPPING.get(getSignatureType(this.key))!; } serialize() { return base32Encode( - new Uint8Array([...getRawPublicKey(this.key), ...this.appIdSuffix]), + new Uint8Array([...getRawPublicKey(this.key), ...this.typeSuffix]), 'RFC4648', { padding: false } ).toLowerCase(); diff --git a/js/sign/telnet.swbn b/js/sign/telnet.swbn new file mode 100644 index 00000000..fc66404a Binary files /dev/null and b/js/sign/telnet.swbn differ diff --git a/js/sign/tests/integrity-block-signer_test.js b/js/sign/tests/integrity-block-signer_test.js index 9366dca7..d55918b5 100644 --- a/js/sign/tests/integrity-block-signer_test.js +++ b/js/sign/tests/integrity-block-signer_test.js @@ -1,5 +1,6 @@ import * as wbnSign from '../lib/wbn-sign.js'; import * as constants from '../lib/utils/constants.js'; +import * as utils from '../lib/utils/utils.js'; import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; @@ -10,22 +11,46 @@ const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); const TEST_WEB_BUNDLE_HASH = '95f8713d382ffefb8f1e4f464e39a2bf18280c8b26434d2fcfc08d7d710c8919ace5a652e25e66f9292cda424f20e4b53bf613bf9488140272f56a455393f7e6'; const EMPTY_INTEGRITY_BLOCK_HEX = '8348f09f968bf09f93a6443162000080'; -const TEST_PRIVATE_KEY = - '-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIB8nP5PpWU7HiILHSfh5PYzb5GAcIfHZ+bw6tcd/LZXh\n-----END PRIVATE KEY-----'; -const TEST_WEB_BUNDLE_ID = +const TEST_ED25519_PRIVATE_KEY = `-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIB8nP5PpWU7HiILHSfh5PYzb5GAcIfHZ+bw6tcd/LZXh +-----END PRIVATE KEY-----`; +const TEST_ECDSA_P256_PRIVATE_KEY = ` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIG6HAXvoG+dOP20rbyPuGC21od4DAZCKBkPy/1902xPnoAoGCCqGSM49 +AwEHoUQDQgAEHIIHO9B+7XJoXTXf3aTWC7aoK1PW4Db5Z8gSGXIkHlLrucUI4lyx +DttYYhi36vrg5nR6zrfdhe7+8F1MoTvLuw== +-----END EC PRIVATE KEY-----`; +const TEST_ED25519_WEB_BUNDLE_ID = '4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic'; +const TEST_ECDSA_P256_WEB_BUNDLE_ID = + 'amoiebz32b7o24tilu257xne2yf3nkblkploanxzm7ebeglseqpfeaacai'; + const IWA_SCHEME = 'isolated-app://'; describe('Web Bundle ID', () => { - const privateKey = wbnSign.parsePemKey(TEST_PRIVATE_KEY); - const testKeys = [privateKey, crypto.createPublicKey(privateKey)]; + const ed25519PrivateKey = wbnSign.parsePemKey(TEST_ED25519_PRIVATE_KEY); + const ecdsaP256PrivateKey = wbnSign.parsePemKey(TEST_ECDSA_P256_PRIVATE_KEY); + const testKeys = [ + ed25519PrivateKey, + crypto.createPublicKey(ed25519PrivateKey), + ecdsaP256PrivateKey, + crypto.createPublicKey(ecdsaP256PrivateKey), + ]; testKeys.forEach((key, index) => { it(`calculates the ID and isolated web app origin correctly with key #${index}.`, () => { - expect(TEST_WEB_BUNDLE_ID).toEqual( + const expectedWebBundleId = (() => { + switch (utils.getSignatureType(key)) { + case constants.SignatureType.Ed25519: + return TEST_ED25519_WEB_BUNDLE_ID; + case constants.SignatureType.EcdsaP256SHA256: + return TEST_ECDSA_P256_WEB_BUNDLE_ID; + } + })(); + expect(expectedWebBundleId).toEqual( new wbnSign.WebBundleId(key).serialize() ); - expect(`${IWA_SCHEME}${TEST_WEB_BUNDLE_ID}/`).toEqual( + expect(`${IWA_SCHEME}${expectedWebBundleId}/`).toEqual( new wbnSign.WebBundleId(key).serializeWithIsolatedWebAppOrigin() ); }); @@ -43,11 +68,23 @@ describe('Integrity Block Signer', () => { return signer; } - it('accepts only ed25519 type of key.', () => { - const keypair = crypto.generateKeyPairSync('ed25519'); - expect(() => - initSignerWithTestWebBundleAndKeys(keypair.privateKey) - ).not.toThrowError(); + function createTestSuffix(publicKey) { + return constants.SignatureType[utils.getSignatureType(publicKey)]; + } + + it('accepts only selected key types.', () => { + for (const validKey of [ + { keyType: 'ed25519' }, + { keyType: 'ec', options: { namedCurve: 'prime256v1' } }, + ]) { + const keypairValid = crypto.generateKeyPairSync( + validKey.keyType, + validKey.options + ); + expect(() => + initSignerWithTestWebBundleAndKeys(keypairValid.privateKey) + ).not.toThrowError(); + } for (const invalidKey of [ { keyType: 'rsa', options: { modulusLength: 2048 } }, @@ -91,63 +128,93 @@ describe('Integrity Block Signer', () => { ); }); - it('generates the dataToBeSigned correctly.', () => { - const keypair = crypto.generateKeyPairSync('ed25519'); - const signer = initSignerWithTestWebBundleAndKeys(keypair.privateKey); - const rawPubKey = wbnSign.getRawPublicKey(keypair.publicKey); - const dataToBeSigned = signer.generateDataToBeSigned( - signer.calcWebBundleHash(), - new wbnSign.IntegrityBlock().toCBOR(), - cborg.encode({ - [constants.ED25519_PK_SIGNATURE_ATTRIBUTE_NAME]: rawPubKey, - }) - ); + [ + crypto.generateKeyPairSync('ed25519'), + crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' }), + ].forEach((keypair) => { + it(`generates the dataToBeSigned correctly with ${createTestSuffix( + keypair.publicKey + )}.`, () => { + const signer = initSignerWithTestWebBundleAndKeys(keypair.privateKey); + const rawPubKey = wbnSign.getRawPublicKey(keypair.publicKey); + + const dataToBeSigned = signer.generateDataToBeSigned( + signer.calcWebBundleHash(), + new wbnSign.IntegrityBlock().toCBOR(), + cborg.encode({ + [utils.getPublicKeyAttributeName(keypair.publicKey)]: rawPubKey, + }) + ); - const hexHashString = - /*64*/ '0000000000000040' + - TEST_WEB_BUNDLE_HASH + - /*16*/ '0000000000000010' + - EMPTY_INTEGRITY_BLOCK_HEX + - /*52*/ '0000000000000034' + - 'a170656432353531395075626c69634b65795820' + - Buffer.from(rawPubKey).toString('hex'); - - expect(dataToBeSigned).toEqual( - Uint8Array.from(Buffer.from(hexHashString, 'hex')) - ); + const attributesCborHex = (() => { + switch (utils.getSignatureType(keypair.publicKey)) { + case constants.SignatureType.Ed25519: + return ( + /*52*/ '0000000000000034' + + 'a170656432353531395075626c69634b65795820' + + Buffer.from(rawPubKey).toString('hex') + ); + case constants.SignatureType.EcdsaP256SHA256: + return ( + /*62*/ '000000000000003e' + + 'a178186563647361503235365348413235365075626c69634b65795821' + + Buffer.from(rawPubKey).toString('hex') + ); + } + })(); + + const hexHashString = + /*64*/ '0000000000000040' + + TEST_WEB_BUNDLE_HASH + + /*16*/ '0000000000000010' + + EMPTY_INTEGRITY_BLOCK_HEX + + attributesCborHex; + + expect(dataToBeSigned).toEqual( + Uint8Array.from(Buffer.from(hexHashString, 'hex')) + ); + }); }); - it('generates a valid signature.', async () => { - const keypair = crypto.generateKeyPairSync('ed25519'); - const signer = initSignerWithTestWebBundleAndKeys(keypair.privateKey); - const rawPubKey = wbnSign.getRawPublicKey(keypair.publicKey); - const sigAttr = { - [constants.ED25519_PK_SIGNATURE_ATTRIBUTE_NAME]: rawPubKey, - }; - const dataToBeSigned = signer.generateDataToBeSigned( - signer.calcWebBundleHash(), - new wbnSign.IntegrityBlock().toCBOR(), - cborg.encode(sigAttr) - ); + [ + crypto.generateKeyPairSync('ed25519'), + crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' }), + ].forEach((keypair) => { + it(`generates a valid signature with ${createTestSuffix( + keypair.publicKey + )}.`, async () => { + const signer = initSignerWithTestWebBundleAndKeys(keypair.privateKey); + const rawPubKey = wbnSign.getRawPublicKey(keypair.publicKey); + const sigAttr = { + [utils.getPublicKeyAttributeName(keypair.publicKey)]: rawPubKey, + }; + const dataToBeSigned = signer.generateDataToBeSigned( + signer.calcWebBundleHash(), + new wbnSign.IntegrityBlock().toCBOR(), + cborg.encode(sigAttr) + ); - const ib = cborg.decode((await signer.sign()).integrityBlock); - expect(ib.length).toEqual(3); - - const [magic, version, signatureStack] = ib; - expect(magic).toEqual(constants.INTEGRITY_BLOCK_MAGIC); - expect(version).toEqual(constants.VERSION_B1); - expect(signatureStack.length).toEqual(1); - expect(signatureStack[0].length).toEqual(2); - - const [signatureAttributes, signature] = signatureStack[0]; - expect(signatureAttributes).toEqual(sigAttr); - expect( - crypto.verify( - /*algorithm=*/ undefined, - dataToBeSigned, - keypair.publicKey, - signature - ) - ).toBeTruthy(); + const ib = cborg.decode((await signer.sign()).integrityBlock); + expect(ib.length).toEqual(3); + + const [magic, version, signatureStack] = ib; + expect(magic).toEqual(constants.INTEGRITY_BLOCK_MAGIC); + expect(version).toEqual(constants.VERSION_B1); + expect(signatureStack.length).toEqual(1); + expect(signatureStack[0].length).toEqual(2); + + const [signatureAttributes, signature] = signatureStack[0]; + expect(signatureAttributes).toEqual(sigAttr); + + // For ECDSA P-256 keys the algorithm is implicitly selected as SHA-256. + expect( + crypto.verify( + /*algorithm=*/ undefined, + dataToBeSigned, + keypair.publicKey, + signature + ) + ).toBeTruthy(); + }); }); });