denoland/deno · error · DOMException
Usages cannot be empty when importing a private key.
Error message
Usages cannot be empty when importing a private key.
What it means
Thrown by PrivateKeyObject.toCryptoKey() when an asymmetric private KeyObject is converted to a WebCrypto CryptoKey with an empty usages array. A private key always corresponds to at least one operation (sign, decrypt, deriveKey, deriveBits), so WebCrypto import rules reject empty usages with a SyntaxError DOMException. The check runs after the algorithm/curve validation, so it only fires once the algorithm name matches the key type.
Source
Thrown at ext/node/polyfills/internal/crypto/keys.ts:910
};
}
toCryptoKey(
algorithm: string | object,
extractable: boolean,
usages: string[],
): CryptoKey {
const algName = typeof algorithm === "string"
? algorithm
: (algorithm as { name: string }).name;
_validateAsymmetricKeyAlgorithm(this, algName);
if (typeof algorithm === "object") {
_validateEcNamedCurve(this, algorithm);
}
if (usages.length === 0) {
throw new DOMException(
"Usages cannot be empty when importing a private key.",
"SyntaxError",
);
}
const pkcs8Data = Buffer.from(
op_node_export_private_key_der(this[kHandle], "pkcs8", null, null),
);
return importCryptoKeySync(
"pkcs8",
pkcs8Data,
algorithm,
extractable,
usages,
);
}
export(options: any) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Pass the operations the key performs: ['sign'] for ECDSA/RSA-PSS, ['decrypt'] for RSA-OAEP, ['deriveKey','deriveBits'] for ECDH/X25519
- Keep one algorithm-to-default-usages mapping and use it everywhere
- Fail fast in your wrapper if usages is empty, with a message naming the algorithm
Example fix
// before
const ck = privateKey.toCryptoKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, []); // throws
// after
const ck = privateKey.toCryptoKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign']); Defensive patterns
Strategy: validation
Validate before calling
const DEFAULT_USAGES: Record<string, string[]> = {
ECDSA: ['sign'], 'RSA-PSS': ['sign'], 'RSA-OAEP': ['decrypt'],
ECDH: ['deriveBits'], X25519: ['deriveBits'],
};
const usages = requested.length > 0 ? requested : DEFAULT_USAGES[algName];
if (!usages?.length) throw new Error(`no usages for ${algName}`);
const ck = privateKey.toCryptoKey(algorithm, extractable, usages); Try / catch
try {
ck = privateKey.toCryptoKey(algorithm, extractable, usages);
} catch (e) {
if (e instanceof DOMException && e.name === 'SyntaxError' && e.message.includes('Usages cannot be empty')) {
ck = privateKey.toCryptoKey(algorithm, extractable, ['sign']);
} else throw e;
} Prevention
- Never give wrappers an optional usages parameter that can default to empty
- Map each private key algorithm to its intended operations in one place
- Test the private-key conversion path for every algorithm you support
When it happens
Trigger: generateKeyPairSync('ec', { namedCurve: 'prime256v1' }).privateKey.toCryptoKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, []).
Common situations: Wrappers with an optional usages parameter that defaults to empty; converting Node KeyObjects to CryptoKeys in a generic pipeline where usages were never threaded through; porting code that generated CryptoKeys directly and later switched to KeyObject-first APIs.
Related errors
- Unsupported key usage for a PBKDF2 key
- Unsupported key usage for an HKDF key
- Usages cannot be empty when importing a secret key.
- Zero-length key is not supported
- PBKDF2 keys are not extractable
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/57a0ffc6ecd75e65.
Report an issue: GitHub.