PR-URL: https://github.com/nodejs/node/pull/63647 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
290 lines
9.5 KiB
JavaScript
290 lines
9.5 KiB
JavaScript
// META: title=WebCryptoAPI: getPublicKey() method
|
|
// META: timeout=long
|
|
// META: script=util/helpers.js
|
|
|
|
"use strict";
|
|
|
|
const algorithms = [
|
|
{
|
|
name: "ECDH",
|
|
generateKeyParams: { name: "ECDH", namedCurve: "P-256" },
|
|
usages: ["deriveKey", "deriveBits"],
|
|
publicKeyUsages: []
|
|
},
|
|
{
|
|
name: "ECDSA",
|
|
generateKeyParams: { name: "ECDSA", namedCurve: "P-256" },
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "Ed25519",
|
|
generateKeyParams: { name: "Ed25519" },
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "RSA-OAEP",
|
|
generateKeyParams: {
|
|
name: "RSA-OAEP",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]),
|
|
hash: "SHA-256"
|
|
},
|
|
usages: ["encrypt", "decrypt"],
|
|
publicKeyUsages: ["encrypt"]
|
|
},
|
|
{
|
|
name: "RSA-PSS",
|
|
generateKeyParams: {
|
|
name: "RSA-PSS",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]),
|
|
hash: "SHA-256"
|
|
},
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
generateKeyParams: {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]),
|
|
hash: "SHA-256"
|
|
},
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "X25519",
|
|
generateKeyParams: { name: "X25519" },
|
|
usages: ["deriveKey", "deriveBits"],
|
|
publicKeyUsages: []
|
|
},
|
|
{
|
|
name: "ML-DSA-44",
|
|
generateKeyParams: { name: "ML-DSA-44" },
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "ML-DSA-65",
|
|
generateKeyParams: { name: "ML-DSA-65" },
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "ML-DSA-87",
|
|
generateKeyParams: { name: "ML-DSA-87" },
|
|
usages: ["sign", "verify"],
|
|
publicKeyUsages: ["verify"]
|
|
},
|
|
{
|
|
name: "ML-KEM-512",
|
|
generateKeyParams: { name: "ML-KEM-512" },
|
|
usages: ["encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"],
|
|
publicKeyUsages: ["encapsulateBits", "encapsulateKey"]
|
|
},
|
|
{
|
|
name: "ML-KEM-768",
|
|
generateKeyParams: { name: "ML-KEM-768" },
|
|
usages: ["encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"],
|
|
publicKeyUsages: ["encapsulateBits", "encapsulateKey"]
|
|
},
|
|
{
|
|
name: "ML-KEM-1024",
|
|
generateKeyParams: { name: "ML-KEM-1024" },
|
|
usages: ["encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"],
|
|
publicKeyUsages: ["encapsulateBits", "encapsulateKey"]
|
|
}
|
|
];
|
|
|
|
// Test basic functionality for supported algorithms
|
|
algorithms.forEach(function(algorithm) {
|
|
promise_test(async function(t) {
|
|
// Generate a key pair
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
algorithm.generateKeyParams,
|
|
false, // extractable
|
|
algorithm.usages
|
|
);
|
|
|
|
assert_true(keyPair.privateKey instanceof CryptoKey, "Generated private key");
|
|
assert_equals(keyPair.privateKey.type, "private", "Private key type");
|
|
|
|
// Test getPublicKey with valid usages
|
|
const publicKey = await crypto.subtle.getPublicKey(
|
|
keyPair.privateKey,
|
|
algorithm.publicKeyUsages
|
|
);
|
|
|
|
// Verify the returned public key
|
|
assert_true(publicKey instanceof CryptoKey, "getPublicKey returns a CryptoKey");
|
|
assert_equals(publicKey.type, "public", "Returned key is public");
|
|
assert_equals(publicKey.algorithm.name, algorithm.name, "Algorithm name matches");
|
|
assert_true(publicKey.extractable, "Public key is extractable");
|
|
|
|
// Verify usages
|
|
assert_equals(publicKey.usages.length, algorithm.publicKeyUsages.length, "Usage count matches");
|
|
algorithm.publicKeyUsages.forEach(function(usage) {
|
|
assert_true(publicKey.usages.includes(usage), `Has ${usage} usage`);
|
|
});
|
|
|
|
// Verify that the derived public key matches the original public key
|
|
// by comparing their exported forms
|
|
const originalExported = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
const derivedExported = await crypto.subtle.exportKey("spki", publicKey);
|
|
|
|
assert_array_equals(
|
|
new Uint8Array(originalExported),
|
|
new Uint8Array(derivedExported),
|
|
"Exported public keys match"
|
|
);
|
|
|
|
}, `getPublicKey works for ${algorithm.name}`);
|
|
});
|
|
|
|
// Test functional equivalence - ensure derived public key works for crypto operations
|
|
promise_test(async function(t) {
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
false,
|
|
["sign", "verify"]
|
|
);
|
|
|
|
const derivedPublicKey = await crypto.subtle.getPublicKey(
|
|
keyPair.privateKey,
|
|
["verify"]
|
|
);
|
|
|
|
// Create test data
|
|
const data = new TextEncoder().encode("test message");
|
|
|
|
// Sign with private key
|
|
const signature = await crypto.subtle.sign(
|
|
{ name: "ECDSA", hash: "SHA-256" },
|
|
keyPair.privateKey,
|
|
data
|
|
);
|
|
|
|
// Verify with both original and derived public keys
|
|
const verifyOriginal = await crypto.subtle.verify(
|
|
{ name: "ECDSA", hash: "SHA-256" },
|
|
keyPair.publicKey,
|
|
signature,
|
|
data
|
|
);
|
|
|
|
const verifyDerived = await crypto.subtle.verify(
|
|
{ name: "ECDSA", hash: "SHA-256" },
|
|
derivedPublicKey,
|
|
signature,
|
|
data
|
|
);
|
|
|
|
assert_true(verifyOriginal, "Original public key verifies signature");
|
|
assert_true(verifyDerived, "Derived public key verifies signature");
|
|
|
|
}, "Derived public key is functionally equivalent to original public key");
|
|
|
|
// Test with empty usages array
|
|
algorithms.forEach(function(algorithm) {
|
|
promise_test(async function(t) {
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
algorithm.generateKeyParams,
|
|
false,
|
|
algorithm.usages
|
|
);
|
|
|
|
// Test with empty usages array
|
|
const publicKey = await crypto.subtle.getPublicKey(
|
|
keyPair.privateKey,
|
|
[]
|
|
);
|
|
|
|
assert_true(publicKey instanceof CryptoKey, "getPublicKey returns a CryptoKey");
|
|
assert_equals(publicKey.type, "public", "Returned key is public");
|
|
assert_equals(publicKey.usages.length, 0, "Public key has no usages");
|
|
|
|
}, `getPublicKey works with empty usages for ${algorithm.name}`);
|
|
});
|
|
|
|
// Test error cases
|
|
|
|
// Test with non-private key (should throw InvalidAccessError)
|
|
promise_test(async function(t) {
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
false,
|
|
["sign", "verify"]
|
|
);
|
|
|
|
await promise_rejects_dom(t, "InvalidAccessError",
|
|
crypto.subtle.getPublicKey(keyPair.publicKey, ["verify"]),
|
|
"getPublicKey should reject when called with a public key"
|
|
);
|
|
}, "getPublicKey rejects with InvalidAccessError when given a public key");
|
|
|
|
// Test with symmetric keys (should throw NotSupportedError for non-private keys)
|
|
promise_test(async function(t) {
|
|
const aesKey = await crypto.subtle.generateKey(
|
|
{ name: "AES-GCM", length: 256 },
|
|
false,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
|
|
await promise_rejects_dom(t, "NotSupportedError",
|
|
crypto.subtle.getPublicKey(aesKey, []),
|
|
"getPublicKey should reject AES-GCM keys"
|
|
);
|
|
}, "getPublicKey rejects with NotSupportedError for AES-GCM symmetric keys");
|
|
|
|
promise_test(async function(t) {
|
|
const hmacKey = await crypto.subtle.generateKey(
|
|
{ name: "HMAC", hash: "SHA-256" },
|
|
false,
|
|
["sign", "verify"]
|
|
);
|
|
|
|
await promise_rejects_dom(t, "NotSupportedError",
|
|
crypto.subtle.getPublicKey(hmacKey, []),
|
|
"getPublicKey should reject HMAC keys"
|
|
);
|
|
}, "getPublicKey rejects with NotSupportedError for HMAC symmetric keys");
|
|
|
|
// Test with invalid usages for the algorithm
|
|
promise_test(async function(t) {
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
false,
|
|
["sign", "verify"]
|
|
);
|
|
|
|
// Try to use "encrypt" usage with ECDSA (not supported for ECDSA public keys)
|
|
await promise_rejects_dom(t, "SyntaxError",
|
|
crypto.subtle.getPublicKey(keyPair.privateKey, ["encrypt"]),
|
|
"getPublicKey should reject invalid usages for the algorithm"
|
|
);
|
|
}, "getPublicKey rejects with SyntaxError for invalid usages");
|
|
|
|
// Test with mixed valid and invalid usages
|
|
promise_test(async function(t) {
|
|
const keyPair = await crypto.subtle.generateKey(
|
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
false,
|
|
["sign", "verify"]
|
|
);
|
|
|
|
// Mix valid ("verify") and invalid ("encrypt") usages
|
|
await promise_rejects_dom(t, "SyntaxError",
|
|
crypto.subtle.getPublicKey(keyPair.privateKey, ["verify", "encrypt"]),
|
|
"getPublicKey should reject when any usage is invalid"
|
|
);
|
|
}, "getPublicKey rejects with SyntaxError when any usage is invalid for the algorithm");
|
|
|
|
// Test that the method exists
|
|
test(function() {
|
|
assert_true("getPublicKey" in crypto.subtle, "getPublicKey method exists on SubtleCrypto");
|
|
assert_equals(typeof crypto.subtle.getPublicKey, "function", "getPublicKey is a function");
|
|
}, "getPublicKey method is available");
|