From 1ed6cac1c483806a4e859580932715687e6c4e28 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 8 Oct 2024 13:56:46 -0500 Subject: [PATCH] Add a crypto module to convert ECDSA private CryptoKey into a public key --- src/utils/crypto.test.ts | 28 ++++++++++++++++++++++++++++ src/utils/crypto.ts | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/utils/crypto.test.ts create mode 100644 src/utils/crypto.ts diff --git a/src/utils/crypto.test.ts b/src/utils/crypto.test.ts new file mode 100644 index 00000000..d2b444a1 --- /dev/null +++ b/src/utils/crypto.test.ts @@ -0,0 +1,28 @@ +import { assertEquals } from '@std/assert'; + +import { getEcdsaPublicKey } from '@/utils/crypto.ts'; + +Deno.test('getEcdsaPublicKey', async () => { + const { publicKey, privateKey } = await crypto.subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['sign', 'verify'], + ); + + const result = await getEcdsaPublicKey(privateKey, true); + + assertKeysEqual(result, publicKey); +}); + +/** Assert that two CryptoKey objects are equal by value. Keys must be exportable. */ +async function assertKeysEqual(a: CryptoKey, b: CryptoKey): Promise { + const [jwk1, jwk2] = await Promise.all([ + crypto.subtle.exportKey('jwk', a), + crypto.subtle.exportKey('jwk', b), + ]); + + assertEquals(jwk1, jwk2); +} diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts new file mode 100644 index 00000000..80d031ed --- /dev/null +++ b/src/utils/crypto.ts @@ -0,0 +1,25 @@ +/** + * Convert an ECDSA private key into a public key. + * https://stackoverflow.com/a/72153942 + */ +export async function getEcdsaPublicKey( + privateKey: CryptoKey, + extractable: boolean, +): Promise { + if (privateKey.type !== 'private') { + throw new Error('Expected a private key.'); + } + if (privateKey.algorithm.name !== 'ECDSA') { + throw new Error('Expected a private key with the ECDSA algorithm.'); + } + + const jwk = await crypto.subtle.exportKey('jwk', privateKey); + const keyUsages: KeyUsage[] = ['verify']; + + // Remove the private property from the JWK. + delete jwk.d; + jwk.key_ops = keyUsages; + jwk.ext = extractable; + + return crypto.subtle.importKey('jwk', jwk, privateKey.algorithm, extractable, keyUsages); +}