| // 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: "Ed448", | |
| generateKeyParams: { name: "Ed448" }, | |
| 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: "X448", | |
| generateKeyParams: { name: "X448" }, | |
| 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"] | |
| }, | |
| { | |
| name: "MLKEM768-P256", | |
| generateKeyParams: { name: "MLKEM768-P256" }, | |
| usages: ["encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"], | |
| publicKeyUsages: ["encapsulateBits", "encapsulateKey"] | |
| }, | |
| { | |
| name: "MLKEM768-X25519", | |
| generateKeyParams: { name: "MLKEM768-X25519" }, | |
| usages: ["encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"], | |
| publicKeyUsages: ["encapsulateBits", "encapsulateKey"] | |
| }, | |
| { | |
| name: "MLKEM1024-P384", | |
| generateKeyParams: { name: "MLKEM1024-P384" }, | |
| 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 exportFormat = algorithm.name.startsWith("MLKEM") ? "raw-public" : "spki"; | |
| const originalExported = await crypto.subtle.exportKey(exportFormat, keyPair.publicKey); | |
| const derivedExported = await crypto.subtle.exportKey(exportFormat, 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"); |