diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 342b5ee05fe0..f8a1ef8d00e7 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -530,19 +530,20 @@ function exportKeySpki(key) { } function exportKeyPkcs8(key) { + let exporter; switch (getCryptoKeyAlgorithm(key).name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return require('internal/crypto/rsa') - .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); + exporter = require('internal/crypto/rsa').rsaExportKey; + break; case 'ECDSA': // Fall through case 'ECDH': - return require('internal/crypto/ec') - .ecExportKey(key, kWebCryptoKeyFormatPKCS8); + exporter = require('internal/crypto/ec').ecExportKey; + break; case 'Ed25519': // Fall through case 'Ed448': @@ -550,25 +551,30 @@ function exportKeyPkcs8(key) { case 'X25519': // Fall through case 'X448': - return require('internal/crypto/cfrg') - .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); + exporter = require('internal/crypto/cfrg').cfrgExportKey; + break; case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - return require('internal/crypto/ml_dsa') - .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); + exporter = require('internal/crypto/ml_dsa').mlDsaExportKey; + break; case 'ML-KEM-512': // Fall through case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - return require('internal/crypto/ml_kem') - .mlKemExportKey(key, kWebCryptoKeyFormatPKCS8); + exporter = require('internal/crypto/ml_kem').mlKemExportKey; + break; default: return undefined; } + + if (getCryptoKeyType(key) !== 'private') + throw lazyDOMException('Key must be a private key', 'InvalidAccessError'); + + return exporter(key, kWebCryptoKeyFormatPKCS8); } function exportKeyRawPublic(key, format) { @@ -777,9 +783,7 @@ function exportKeySync(format, key) { break; } case 'pkcs8': { - if (type === 'private') { - result = exportKeyPkcs8(key); - } + result = exportKeyPkcs8(key); break; } case 'jwk': { diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index 978aad14ebd9..f4f36ceeff5a 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -120,6 +120,12 @@ async function testImportSpki({ name, publicUsages }, namedCurve, extractable) { assert.strictEqual( Buffer.from(spki).toString('hex'), keyData[namedCurve].spki.toString('hex')); + + await assert.rejects( + subtle.exportKey('pkcs8', key), { + message: 'Key must be a private key', + name: 'InvalidAccessError', + }); } else { await assert.rejects( subtle.exportKey('spki', key), { diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index 64ff4c5f5342..5ffffe230ac6 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -567,6 +567,17 @@ async function testNonByteLengthWrapUnwrap({ name: 'InvalidAccessError', }); + // Symmetric case: exporting a public key as 'pkcs8' must also fail with + // InvalidAccessError, not the generic NotSupportedError. + await assert.rejects( + subtle.wrapKey('pkcs8', ecKey.publicKey, wrapKey, { + name: 'AES-GCM', + iv: new Uint8Array(12), + }), { + message: 'Key must be a private key', + name: 'InvalidAccessError', + }); + // --- unwrapKey validation tests --- const ciphertext = new Uint8Array(32); // Dummy ciphertext