Skip to content

Commit 3259e11

Browse files
committed
src: ensure exported ec keys are uncompressed
The WebCrypto spec apparently mandates that EC keys must be exported in uncompressed point format. This commit makes it so. Fixes: #45859
1 parent 4830a6c commit 3259e11

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/crypto/crypto_ec.cc

+36-1
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,42 @@ WebCryptoKeyExportStatus ECKeyExportTraits::DoExport(
706706
case kWebCryptoKeyFormatSPKI:
707707
if (key_data->GetKeyType() != kKeyTypePublic)
708708
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
709-
return PKEY_SPKI_Export(key_data.get(), out);
709+
// Ensure exported key is in uncompressed point format.
710+
// The temporary EC key is so we can have i2d_PUBKEY_bio() write out
711+
// the header but it is a somewhat silly hoop to jump through because
712+
// the header is for all practical purposes a static 26 byte sequence
713+
// where only the second byte changes.
714+
{
715+
ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey();
716+
Mutex::ScopedLock lock(*m_pkey.mutex());
717+
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(m_pkey.get());
718+
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
719+
const EC_POINT* point = EC_KEY_get0_public_key(ec_key);
720+
const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
721+
const size_t need =
722+
EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
723+
if (need == 0) return WebCryptoKeyExportStatus::FAILED;
724+
ByteSource::Builder data(need);
725+
const size_t have =
726+
EC_POINT_point2oct(group, point, form, data.data<unsigned char>(),
727+
need, nullptr);
728+
if (have == 0) return WebCryptoKeyExportStatus::FAILED;
729+
ECKeyPointer ec(EC_KEY_new());
730+
CHECK_EQ(1, EC_KEY_set_group(ec.get(), group));
731+
ECPointPointer uncompressed(EC_POINT_new(group));
732+
CHECK_EQ(1, EC_POINT_oct2point(group, uncompressed.get(),
733+
data.data<unsigned char>(), data.size(),
734+
nullptr));
735+
CHECK_EQ(1, EC_KEY_set_public_key(ec.get(), uncompressed.get()));
736+
EVPKeyPointer pkey(EVP_PKEY_new());
737+
CHECK_EQ(1, EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()));
738+
BIOPointer bio(BIO_new(BIO_s_mem()));
739+
CHECK(bio);
740+
if (!i2d_PUBKEY_bio(bio.get(), pkey.get()))
741+
return WebCryptoKeyExportStatus::FAILED;
742+
*out = ByteSource::FromBIO(bio);
743+
return WebCryptoKeyExportStatus::OK;
744+
}
710745
default:
711746
UNREACHABLE();
712747
}

test/parallel/test-webcrypto-export-import-ec.js

+13
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,19 @@ async function testImportRaw({ name, publicUsages }, namedCurve) {
327327
await Promise.all(tests);
328328
})().then(common.mustCall());
329329

330+
331+
// https://github.com/nodejs/node/issues/45859
332+
(async function() {
333+
const 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]);
334+
const 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]);
335+
for (const name of ['ECDH', 'ECDSA']) {
336+
const options = { name, namedCurve: 'P-256' };
337+
const key = await subtle.importKey('spki', compressed, options, true, []);
338+
const spki = await subtle.exportKey('spki', key);
339+
assert.deepStrictEqual(uncompressed, Buffer.from(spki));
340+
}
341+
})().then(common.mustCall());
342+
330343
{
331344
const rsaPublic = crypto.createPublicKey(
332345
fixtures.readKey('rsa_public_2048.pem'));

0 commit comments

Comments
 (0)