Skip to content

Commit

Permalink
refactor(experimental): a function for generating secret keys
Browse files Browse the repository at this point in the history
## 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: WICG/webcrypto-secure-curves#20

## Test Plan

```
cd packages/keys/
pnpm test:unit:browser
pnpm test:unit:node
```
  • Loading branch information
steveluscher committed Jul 13, 2023
1 parent 515e32f commit 6e4aa80
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/keys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ function handleSubmit() {
}
}
```

### `generateKeyPair()`

Generates an Ed25519 public/private keypair for use with other methods in this package that accept `CryptoKey` objects.
51 changes: 51 additions & 0 deletions packages/keys/src/__tests__/keypair-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { generateKeyPair } from '../keypair';

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']);
});
});
1 change: 1 addition & 0 deletions packages/keys/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './base58';
export * from './keypair';
11 changes: 11 additions & 0 deletions packages/keys/src/keypair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { assertKeyGenerationIsAvailable } from './guard';

export async function generateKeyPair(): Promise<CryptoKeyPair> {
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;
}

0 comments on commit 6e4aa80

Please sign in to comment.