Skip to content

(ext/crypto) imported raw/spki compressed EC public keys should be uncompressed when exported #18050

Closed
@panva

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.

RawKeyData::Public(data) => {
// public_key is a serialized EncodedPoint
p256::EncodedPoint::from_bytes(data)
.map_err(|_| type_error("expected valid public EC key"))
}

deno/ext/crypto/shared.rs

Lines 109 to 113 in eea742e

RawKeyData::Public(data) => {
// public_key is a serialized EncodedPoint
p384::EncodedPoint::from_bytes(data)
.map_err(|_| type_error("expected valid public EC key"))
}

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working correctlycryptoRelated to node:crypto or WebCrypto

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions