| // 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: [] |
| } |
| ]; |
| |
| // 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) { |
| // Skip X25519 if not supported |
| if (algorithm.name === "X25519") { |
| try { |
| await crypto.subtle.generateKey(algorithm.generateKeyParams, false, algorithm.usages); |
| } catch (e) { |
| if (e.name === "NotSupportedError") { |
| return; |
| } |
| throw e; |
| } |
| } |
| |
| 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"); |