From 1017e32111d5a6c01368f5c89bc217f37b8c0ddd Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Thu, 13 Jul 2023 20:31:55 +0000 Subject: [PATCH] refactor(experimental): a function for generating secret keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR introduces `generateSecretKey()`. You might need to use this when you need to sign for the creation of an account, for instance. Instead of vending the _bytes_ of a secret key, however, we use JS-native `CryptoKey` instances. These are opaque tokens that you can return at a later time to perform some action, like deriving the public key for the secret they represent, or signing a message. The idea is that you can freely pass these `CryptoKey` instances around your application without worrying about accidentally logging the key material itself – ie. to Sentry or to the browser console. The only environments that support Ed25519 key generation at the moment: * Node >=17.4 * Safari 17 For other environments, we'll supply a polyfill that implements key generation, signing, encryption, decryption, and verification in userspace. Spec: https://wicg.github.io/webcrypto-secure-curves/#ed25519 Proposal repo: https://github.com/WICG/webcrypto-secure-curves Implementation status: https://github.com/WICG/webcrypto-secure-curves/issues/20 ## Test Plan ``` cd packages/keys/ pnpm test:unit:browser pnpm test:unit:node ``` --- packages/keys/README.md | 4 ++ packages/keys/src/__tests__/key-pair-test.ts | 51 ++++++++++++++++++++ packages/keys/src/index.ts | 1 + packages/keys/src/key-pair.ts | 11 +++++ 4 files changed, 67 insertions(+) create mode 100644 packages/keys/src/__tests__/key-pair-test.ts create mode 100644 packages/keys/src/key-pair.ts diff --git a/packages/keys/README.md b/packages/keys/README.md index 10883e60cf0..2c1288bc440 100644 --- a/packages/keys/README.md +++ b/packages/keys/README.md @@ -50,3 +50,7 @@ function handleSubmit() { } } ``` + +### `generateKeyPair()` + +Generates an Ed25519 public/private key pair for use with other methods in this package that accept `CryptoKey` objects. diff --git a/packages/keys/src/__tests__/key-pair-test.ts b/packages/keys/src/__tests__/key-pair-test.ts new file mode 100644 index 00000000000..44ea0b725f4 --- /dev/null +++ b/packages/keys/src/__tests__/key-pair-test.ts @@ -0,0 +1,51 @@ +import { generateKeyPair } from '../key-pair'; + +describe('generateKeyPair', () => { + let oldIsSecureContext: boolean; + beforeEach(() => { + if (__BROWSER__) { + // FIXME: JSDOM does not set `isSecureContext` or otherwise allow you to configure it. + // Some discussion: https://github.com/jsdom/jsdom/issues/2751#issuecomment-846613392 + if (globalThis.isSecureContext !== undefined) { + oldIsSecureContext = globalThis.isSecureContext; + } + globalThis.isSecureContext = true; + } + }); + afterEach(() => { + if (oldIsSecureContext !== undefined) { + globalThis.isSecureContext = oldIsSecureContext; + } + }); + it.each(['private', 'public'])('generates an ed25519 %s `CryptoKey`', async type => { + expect.assertions(1); + const keyPair = await generateKeyPair(); + expect(keyPair).toMatchObject({ + [`${type}Key`]: expect.objectContaining({ + [Symbol.toStringTag]: 'CryptoKey', + algorithm: { name: 'Ed25519' }, + type, + }), + }); + }); + it('generates a non-extractable private key', async () => { + expect.assertions(1); + const { privateKey } = await generateKeyPair(); + expect(privateKey).toHaveProperty('extractable', false); + }); + it('generates a private key usable for signing operations', async () => { + expect.assertions(1); + const { privateKey } = await generateKeyPair(); + expect(privateKey).toHaveProperty('usages', ['sign']); + }); + it('generates an extractable public key', async () => { + expect.assertions(1); + const { publicKey } = await generateKeyPair(); + expect(publicKey).toHaveProperty('extractable', true); + }); + it('generates a public key usable for verifying signatures', async () => { + expect.assertions(1); + const { publicKey } = await generateKeyPair(); + expect(publicKey).toHaveProperty('usages', ['verify']); + }); +}); diff --git a/packages/keys/src/index.ts b/packages/keys/src/index.ts index a5c83cfaa57..d57857d13c7 100644 --- a/packages/keys/src/index.ts +++ b/packages/keys/src/index.ts @@ -1 +1,2 @@ export * from './base58'; +export * from './key-pair'; diff --git a/packages/keys/src/key-pair.ts b/packages/keys/src/key-pair.ts new file mode 100644 index 00000000000..dfd1076e190 --- /dev/null +++ b/packages/keys/src/key-pair.ts @@ -0,0 +1,11 @@ +import { assertKeyGenerationIsAvailable } from './guard'; + +export async function generateKeyPair(): Promise { + await assertKeyGenerationIsAvailable(); + const keyPair = await crypto.subtle.generateKey( + /* algorithm */ 'Ed25519', // Native implementation status: https://github.com/WICG/webcrypto-secure-curves/issues/20 + /* extractable */ false, // Prevents the bytes of the private key from being visible to JS. + /* allowed uses */ ['sign', 'verify'] + ); + return keyPair as CryptoKeyPair; +}