Closed
Description
The following export routines should account for the key material to be using compressed point format and uncompress it before exporting as per the WebCryptoAPI spec.
Lines 92 to 96 in eea742e
Lines 109 to 113 in eea742e
The following script demonstrates the issue
import * as assert from 'node:assert';
import { Buffer } from 'node:buffer';
const { subtle } = globalThis.crypto;
const keyData = {
'P-256': {
uncompressed: Buffer.from([
48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97, 232,
]),
compressed: Buffer.from([
48, 57, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 34, 0, 2, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209,
]),
},
'P-384': {
uncompressed: Buffer.from([
48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 34, 3, 98, 0, 4, 33, 156, 20, 214, 102, 23, 179, 110, 198, 216, 133, 107, 56, 91, 115, 167, 77, 52, 79, 216, 174, 117, 239, 4, 100, 53, 221, 165, 78, 59, 68, 189, 95, 189, 235, 209, 208, 141, 214, 158, 45, 125, 193, 220, 33, 140, 180, 53, 189, 40, 19, 140, 199, 120, 51, 122, 132, 47, 107, 214, 27, 36, 14, 116, 36, 159, 36, 102, 124, 42, 88, 16, 167, 107, 252, 40, 224, 51, 95, 136, 166, 80, 29, 236, 1, 151, 109, 168, 90, 251, 0, 134, 156, 182, 172, 232,
]),
compressed: Buffer.from([
48, 70, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 34, 3, 50, 0, 2, 33, 156, 20, 214, 102, 23, 179, 110, 198, 216, 133, 107, 56, 91, 115, 167, 77, 52, 79, 216, 174, 117, 239, 4, 100, 53, 221, 165, 78, 59, 68, 189, 95, 189, 235, 209, 208, 141, 214, 158, 45, 125, 193, 220, 33, 140, 180, 53,
]),
},
};
const curves = Object.keys(keyData);
const testVectors = [
{
name: 'ECDSA',
privateUsages: ['sign'],
publicUsages: ['verify'],
},
{
name: 'ECDH',
privateUsages: ['deriveKey', 'deriveBits'],
publicUsages: [],
},
];
async function testRoundTripSpki({ name, publicUsages }, namedCurve) {
const { compressed, uncompressed } = keyData[namedCurve];
const key = await subtle.importKey(
'spki',
compressed,
{ name, namedCurve },
true,
publicUsages
);
const spki = await subtle.exportKey('spki', key);
assert.deepStrictEqual(
Buffer.from(spki).toString('hex'),
uncompressed.toString('hex')
);
}
async function testRoundTripRaw({ name, publicUsages }, namedCurve) {
let { compressed, uncompressed } = keyData[namedCurve];
let i = uncompressed.indexOf(Buffer.from([0x00, 0x04])) + 1;
uncompressed = uncompressed.subarray(i);
i =
compressed.indexOf(Buffer.from([0x00, 0x02])) + 1 ||
compressed.indexOf(Buffer.from([0x00, 0x03])) + 1;
compressed = compressed.subarray(i);
const key = await subtle.importKey(
'raw',
compressed,
{ name, namedCurve },
true,
publicUsages
);
const raw = await subtle.exportKey('raw', key);
assert.strictEqual(
Buffer.from(raw).toString('hex'),
uncompressed.toString('hex')
);
}
for (const namedCurve of curves) {
for (const vector of testVectors) {
await testRoundTripSpki(vector, namedCurve).then(
() =>
console.log(namedCurve, 'spki'.padEnd(4), vector.name.padEnd(5), '✅'),
(e) => {
console.log(namedCurve, 'spki'.padEnd(4), vector.name.padEnd(5), '❌', e.message)
}
);
await testRoundTripRaw(vector, namedCurve).then(
() =>
console.log(namedCurve, 'raw'.padEnd(4), vector.name.padEnd(5), '✅'),
(e) => {
console.log(namedCurve, 'raw'.padEnd(4), vector.name.padEnd(5), '❌', e.message)
}
);
console.log('\n');
}
}
Activity