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

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

Closed
panva opened this issue Mar 6, 2023 · 0 comments · Fixed by #25766
Closed
Labels
bug Something isn't working correctly crypto Related to node:crypto or WebCrypto

Comments

@panva
Copy link
Contributor

panva commented Mar 6, 2023

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');
  }
}
@panva panva changed the title (ext/crypto): imported raw/spki compressed EC public keys should be uncompressed when exported (ext/crypto) imported raw/spki compressed EC public keys should be uncompressed when exported Mar 6, 2023
@littledivy littledivy added bug Something isn't working correctly crypto Related to node:crypto or WebCrypto labels Sep 20, 2024
littledivy added a commit to littledivy/deno that referenced this issue Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working correctly crypto Related to node:crypto or WebCrypto
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants