From 78e29449236c63c016c86a52de6f5b83c3b35a95 Mon Sep 17 00:00:00 2001 From: Max Kurapov Date: Fri, 25 Nov 2022 01:50:13 +0100 Subject: [PATCH] feat(open-payments): generateJwk --- packages/open-payments/src/index.ts | 2 ++ packages/open-payments/src/jwk.test.ts | 44 ++++++++++++++++++++++++++ packages/open-payments/src/jwk.ts | 34 ++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 packages/open-payments/src/jwk.test.ts create mode 100644 packages/open-payments/src/jwk.ts diff --git a/packages/open-payments/src/index.ts b/packages/open-payments/src/index.ts index 78b8b06253..b9ff461d0f 100644 --- a/packages/open-payments/src/index.ts +++ b/packages/open-payments/src/index.ts @@ -16,3 +16,5 @@ export { AuthenticatedClient, UnauthenticatedClient } from './client' + +export { generateJwk } from './jwk' diff --git a/packages/open-payments/src/jwk.test.ts b/packages/open-payments/src/jwk.test.ts new file mode 100644 index 0000000000..5dc94b08de --- /dev/null +++ b/packages/open-payments/src/jwk.test.ts @@ -0,0 +1,44 @@ +import { generateKeyPairSync } from 'crypto' +import { generateJwk } from './jwk' + +describe('jwk', (): void => { + describe('generateJwk', (): void => { + test('properly generates jwk', async (): Promise => { + expect(generateJwk({ keyId: 'keyid' })).toEqual({ + alg: 'EdDSA', + kid: 'keyid', + kty: 'OKP', + crv: 'Ed25519', + x: expect.any(String) + }) + }) + + test('properly generates jwk with defined private key', async (): Promise => { + expect( + generateJwk({ + keyId: 'keyid', + privateKey: generateKeyPairSync('ed25519').privateKey + }) + ).toEqual({ + alg: 'EdDSA', + kid: 'keyid', + kty: 'OKP', + crv: 'Ed25519', + x: expect.any(String) + }) + }) + + test('throws if empty keyId', async (): Promise => { + expect(() => generateJwk({ keyId: '' })).toThrow('KeyId cannot be empty') + }) + + test('throws if provided key is not EdDSA-Ed25519', async (): Promise => { + expect(() => + generateJwk({ + keyId: 'keyid', + privateKey: generateKeyPairSync('ed448').privateKey + }) + ).toThrow('Key is not EdDSA-Ed25519') + }) + }) +}) diff --git a/packages/open-payments/src/jwk.ts b/packages/open-payments/src/jwk.ts new file mode 100644 index 0000000000..19fc51e954 --- /dev/null +++ b/packages/open-payments/src/jwk.ts @@ -0,0 +1,34 @@ +import { createPublicKey, generateKeyPairSync, KeyObject } from 'crypto' +import { JWK } from './types' + +export const generateJwk = ({ + privateKey: providedPrivateKey, + keyId +}: { + privateKey?: KeyObject + keyId: string +}): JWK => { + if (!keyId.trim()) { + throw new Error('KeyId cannot be empty') + } + + const privateKey = providedPrivateKey + ? providedPrivateKey + : generateKeyPairSync('ed25519').privateKey + + const jwk = createPublicKey(privateKey).export({ + format: 'jwk' + }) + + if (jwk.crv !== 'Ed25519' || jwk.kty !== 'OKP') { + throw new Error('Key is not EdDSA-Ed25519') + } + + return { + alg: 'EdDSA', + kid: keyId, + kty: jwk.kty, + crv: jwk.crv, + x: jwk.x + } +}