Repository: nodejs/node
Pull request: crypto: runtime-deprecate DEP0203 and DEP0204

Refs: https://github.com/nodejs/node/issues/55293
Refs: https://github.com/nodejs/node/issues/62321

Changed files:
filename: doc/api/crypto.md
patch: @@ -2255,12 +2255,20 @@ be listed in the `transferList` argument.
 
 <!-- YAML
 added: v15.0.0
+changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a non-extractable CryptoKey as `key` is deprecated.
 -->
 
 * `key` {CryptoKey}
 * Returns: {KeyObject}
 
-Example: Converting a `CryptoKey` instance to a `KeyObject`:
+Returns the underlying {KeyObject} of a {CryptoKey}. The returned {KeyObject}
+does not retain any of the restrictions imposed by the Web Crypto API on the
+original {CryptoKey}, such as the allowed key usages, the algorithm or hash
+algorithm bindings, and the extractability flag. In particular, the underlying
+key material of the returned {KeyObject} can always be exported.
 
 ```mjs
 const { KeyObject } = await import('node:crypto');
@@ -3522,6 +3530,9 @@ operations. The specific constants currently defined are described in
 <!-- YAML
 added: v0.1.94
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a CryptoKey as `key` is deprecated.
   - version:
     - v17.9.0
     - v16.17.0
@@ -3596,6 +3607,9 @@ given IV will be.
 <!-- YAML
 added: v0.1.94
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a CryptoKey as `key` is deprecated.
   - version:
     - v17.9.0
     - v16.17.0
@@ -3819,6 +3833,9 @@ input.on('readable', () => {
 <!-- YAML
 added: v0.1.94
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a CryptoKey as `key` is deprecated.
   - version: v15.0.0
     pr-url: https://github.com/nodejs/node/pull/35093
     description: The key can also be an ArrayBuffer or CryptoKey. The
@@ -3909,6 +3926,9 @@ input.on('readable', () => {
 <!-- YAML
 added: v11.6.0
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a CryptoKey as `key` is deprecated.
   - version: REPLACEME
     pr-url: https://github.com/nodejs/node/pull/62240
     description: Added support for `'raw-private'` and `'raw-seed'`
@@ -3957,6 +3977,9 @@ of the passphrase is limited to 1024 bytes.
 <!-- YAML
 added: v11.6.0
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Passing a CryptoKey as `key` is deprecated.
   - version: REPLACEME
     pr-url: https://github.com/nodejs/node/pull/62240
     description: Added support for `'raw-public'` format.

filename: doc/api/deprecations.md
patch: @@ -4485,12 +4485,15 @@ const server = http2.createSecureServer({
 
 <!-- YAML
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Runtime deprecation.
   - version: REPLACEME
     pr-url: https://github.com/nodejs/node/pull/62321
     description: Documentation-only deprecation.
 -->
 
-Type: Documentation-only
+Type: Runtime
 
 Passing a [`CryptoKey`][] to `node:crypto` functions is deprecated and
 will throw an error in a future version. This includes
@@ -4507,12 +4510,15 @@ will throw an error in a future version. This includes
 
 <!-- YAML
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/62453
+    description: Runtime deprecation.
   - version: REPLACEME
     pr-url: https://github.com/nodejs/node/pull/62321
     description: Documentation-only deprecation.
 -->
 
-Type: Documentation-only
+Type: Runtime
 
 Passing a non-extractable [`CryptoKey`][] to [`KeyObject.from()`][] is
 deprecated and will throw an error in a future version.

filename: lib/internal/crypto/keys.js
patch: @@ -74,6 +74,7 @@ const {
 
 const {
   customInspectSymbol: kInspect,
+  getDeprecationWarningEmitter,
   kEnumerableProperty,
   lazyDOMException,
 } = require('internal/util');
@@ -89,6 +90,18 @@ const kKeyUsages = Symbol('kKeyUsages');
 const kCachedAlgorithm = Symbol('kCachedAlgorithm');
 const kCachedKeyUsages = Symbol('kCachedKeyUsages');
 
+const emitDEP0203 = getDeprecationWarningEmitter(
+  'DEP0203',
+  'Passing a CryptoKey to node:crypto functions is deprecated.',
+);
+
+const maybeEmitDEP0204 = getDeprecationWarningEmitter(
+  'DEP0204',
+  'Passing a non-extractable CryptoKey to KeyObject.from() is deprecated.',
+  undefined,
+  false,
+  (key) => !key[kExtractable],
+);
 
 // Key input contexts.
 const kConsumePublic = 0;
@@ -140,6 +153,7 @@ const {
     static from(key) {
       if (!isCryptoKey(key))
         throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
+      maybeEmitDEP0204(key);
       return key[kKeyObject];
     }
 
@@ -796,21 +810,28 @@ function prepareAsymmetricKey(key, ctx) {
   if (isKeyObject(key)) {
     // Best case: A key object, as simple as that.
     return { data: getKeyObjectHandle(key, ctx) };
-  } else if (isCryptoKey(key)) {
+  }
+  if (isCryptoKey(key)) {
+    emitDEP0203();
     return { data: getKeyObjectHandle(key[kKeyObject], ctx) };
-  } else if (isStringOrBuffer(key)) {
+  }
+  if (isStringOrBuffer(key)) {
     // Expect PEM by default, mostly for backward compatibility.
     return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
-  } else if (typeof key === 'object') {
+  }
+  if (typeof key === 'object') {
     const { key: data, encoding, format } = key;
 
     // The 'key' property can be a KeyObject as well to allow specifying
     // additional options such as padding along with the key.
-    if (isKeyObject(data))
+    if (isKeyObject(data)) {
       return { data: getKeyObjectHandle(data, ctx) };
-    else if (isCryptoKey(data))
+    }
+    if (isCryptoKey(data)) {
+      emitDEP0203();
       return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
-    else if (format === 'jwk') {
+    }
+    if (format === 'jwk') {
       validateObject(data, 'key.key');
       return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' };
     } else if (format === 'raw-public' || format === 'raw-private' ||
@@ -836,6 +857,7 @@ function prepareAsymmetricKey(key, ctx) {
       ...parseKeyEncoding(key, undefined, isPublic),
     };
   }
+
   throw new ERR_INVALID_ARG_TYPE(
     'key',
     getKeyTypes(ctx !== kCreatePrivate),
@@ -856,7 +878,9 @@ function prepareSecretKey(key, encoding, bufferOnly = false) {
       if (key.type !== 'secret')
         throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
       return key[kHandle];
-    } else if (isCryptoKey(key)) {
+    }
+    if (isCryptoKey(key)) {
+      emitDEP0203();
       if (key[kKeyType] !== 'secret')
         throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key[kKeyType], 'secret');
       return key[kKeyObject][kHandle];

filename: test/parallel/test-crypto-dep0203.js
patch: @@ -0,0 +1,23 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const crypto = require('crypto');
+
+common.expectWarning({
+  DeprecationWarning: {
+    DEP0203: 'Passing a CryptoKey to node:crypto functions is deprecated.',
+  },
+});
+
+(async () => {
+  const key = await globalThis.crypto.subtle.generateKey(
+    { name: 'AES-CBC', length: 128 },
+    true,
+    ['encrypt'],
+  );
+
+  crypto.createCipheriv('aes-128-cbc', key, Buffer.alloc(16));
+})().then(common.mustCall());

filename: test/parallel/test-crypto-dep0204.js
patch: @@ -0,0 +1,23 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const { KeyObject } = require('crypto');
+
+common.expectWarning({
+  DeprecationWarning: {
+    DEP0204: 'Passing a non-extractable CryptoKey to KeyObject.from() is deprecated.',
+  },
+});
+
+(async () => {
+  const key = await globalThis.crypto.subtle.generateKey(
+    { name: 'AES-CBC', length: 128 },
+    false,  // non-extractable
+    ['encrypt'],
+  );
+
+  KeyObject.from(key);
+})().then(common.mustCall());