Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/security audit #16

Merged
merged 16 commits into from
Jul 23, 2024
92 changes: 19 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ Holo Key Manager is a browser extension for generating and managing Holochain ap

## Usage

### User Installation

To install the Holo Key Manager Extension, follow the instructions for your browser:

- Chrome: [Install from Chrome Web Store](https://chrome.google.com/webstore/detail/holo-key-manager/eggfhkdnfdhdpmkfpihjjbnncgmhihce)
- Edge: [Install from Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/jfecdgefjljjfcflgbhgfkbeofjenceh)
- Mozilla: [Install from Firefox Browser Add-ons](https://addons.mozilla.org/en-US/firefox/addon/holo-key-manager/)

### Interacting with the Extension

A javascript client library is available at `@holo-host/holo-key-manager-js-client`. See client documentation [here](holo-key-manager-js-client/README.md)

## Architecture
Expand Down Expand Up @@ -66,87 +76,21 @@ The monorepo consists of two main projects: `holo-key-manager-extension` and `ho
pnpm buildExtension
```

Build artifacts are in the `./holo-key-manager-extension/build` folder

2. **Build the client:**

```sh
pnpm buildClient
```

Build artifacts are in the `./holo-key-manager-js-client/lib` folder

3. **Build both projects for development:**
```sh
pnpm build
```

### Loading Extension Files into Chrome Browser

After building the extension, you need to load it into your Chrome browser to test and use it. Follow these steps:

1. **Open Chrome and navigate to the Extensions page:**

Open Chrome and go to `chrome://extensions/` or click on the three dots in the upper right corner, then go to `More tools` > `Extensions`.

2. **Enable Developer Mode:**

In the top right corner of the Extensions page, toggle the switch to enable Developer Mode.

3. **Load the unpacked extension:**

Click on the "Load unpacked" button that appears after enabling Developer Mode. This will open a file dialog.

4. **Select the build folder:**

Navigate to the `holo-key-manager-extension/build` directory in your file system and select it. This will load the extension into Chrome.

5. **Verify the extension is loaded:**

You should see the Holo Key Manager extension listed on the Extensions page. Ensure there are no errors and the extension is enabled.

By following these steps, you can load and test the Holo Key Manager extension in your Chrome browser. If you encounter any issues, check the console for error messages and ensure that the build process completed successfully.

### Working with holo-key-manager-js-client

If you want to work on the interaction between the extension and the client, you need to run the `buildPack` script inside the `holo-key-manager-js-client` directory. This will create a `.tgz` file that you can link to your new web app.

1. **Build the client package:**

```sh
cd holo-key-manager-js-client
pnpm buildPack
```

2. **Create a new web app:**

You can use any framework of your choice. Here are examples for Create React App and a new Svelte project.

**Create React App:**

```sh
npx create-react-app my-holo-app
cd my-holo-app
```

**New Svelte Project:**

```sh
npx degit sveltejs/template my-holo-app
cd my-holo-app
pnpm install
```

3. **Link the `.tgz` file:**

After building the client package, link the generated `.tgz` file to your new web app.

```sh
pnpm add ../holo-key-manager-js-client/holo-key-manager-js-client-1.0.0.tgz
```

4. **Call API functions:**

You can now call the API functions defined in the `README` of `holo-key-manager-js-client`. Here is an example of how to use the API in your new web app.

By following these steps, you can set up a new web app and interact with the Holo Key Manager extension using the `holo-key-manager-js-client` API.

### Linting and Formatting

To ensure code quality and consistency, use the following commands:
Expand Down Expand Up @@ -198,10 +142,12 @@ There are three types of tests in this repository:
```

3. **End-to-End (e2e) Tests** for the whole repository:
- To run e2e tests, you need to create a `.env` file with `CHROME_ID=eggfhkdnfdhdpmkfpihjjbnncgmhihce` that corresponds to the key property (which is actually the pub_key) in `holo-key-manager-extension/static/manifest.json`.
- The rest of the `.env` properties visible in `.env.example` are for CI/CD.
- Run the e2e tests using the command:
- To run e2e tests, you need to create a `.env` file with `CHROME_ID=eggfhkdnfdhdpmkfpihjjbnncgmhihce`
- The rest of the variables in `.env.example` are for deployment and not necessary for testing
- Run the e2e tests using the commands:
```sh
pnpm install
pnpm build
pnpm e2e-tests
```

Expand Down
48 changes: 46 additions & 2 deletions holo-key-manager-extension/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
# Holo Key Manager
# Holo Key Manager Extension

Holo Key Manager is a secure key manager for creating and managing Holochain keys. Get easy access to all your keys. You can set up and manager one or more keys for each application.
Holo Key Manager is a secure key manager for creating and managing Holochain keys. Get easy access to all your keys. You can set up and manage one or more keys for each application.

## Overview

The Holo Key Manager Extension is a browser extension designed to manage Holochain keys securely. It facilitates communication between web applications and the Holo Key Manager, ensuring that keys are handled safely and efficiently.

### Features

- Secure key management for Holochain applications.
- Cross-browser compatibility.
- Easy integration with web applications.
- Secure communication between content scripts and web pages.

## Building Extension

```
cd holo-key-manager-extension
pnpm build
```

## Loading Extension Files into Chrome Browser

After building the extension, you need to load it into your Chrome browser to test and use it. Follow these steps:

1. **Open Chrome and navigate to the Extensions page:**

Open Chrome and go to `chrome://extensions/` or click on the three dots in the upper right corner, then go to `More tools` > `Extensions`.

2. **Enable Developer Mode:**

In the top right corner of the Extensions page, toggle the switch to enable Developer Mode.

3. **Load the unpacked extension:**

Click on the "Load unpacked" button that appears after enabling Developer Mode. This will open a file dialog.

4. **Select the build folder:**

Navigate to the `holo-key-manager-extension/build` directory in your file system and select it. This will load the extension into Chrome.

5. **Verify the extension is loaded:**

You should see the Holo Key Manager extension listed on the Extensions page. Ensure there are no errors and the extension is enabled.

By following these steps, you can load and test the Holo Key Manager extension in your Chrome browser. If you encounter any issues, check the console for error messages and ensure that the build process completed successfully.
8 changes: 8 additions & 0 deletions holo-key-manager-extension/scripts/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const parseAndHandleMessage = async (event: MessageEvent) => {
try {
const response = await sendMessage({ ...parsedResult.data, origin: event.origin });
const parsedMessageSchema = parseMessageSchema(response);

// Using '*' as the target origin is necessary for cross-browser extension compatibility.
// This allows communication between the content script and the webpage across different browsers.
// Security note: We have a dedicated test in @preventSignatureFromOtherOrigin.ts that verifies
// the security of this communication. Additionally, we check the origin in the background script
// to ensure that only authenticated origins can sign messages.
// TODO: Consider alternatives to postMessage(..., '*') in the future, as discussed in:
// https://github.com/w3c/webextensions/issues/78
window.postMessage(responseToMessage(parsedMessageSchema.data, parsedResult.data.id), '*');
} catch (error) {
window.postMessage(
Expand Down
15 changes: 13 additions & 2 deletions holo-key-manager-extension/src/lib/queries/extensionQueries.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createMutation, createQuery, QueryClient } from '@tanstack/svelte-query';

import { handleSuccess } from '$helpers';
import { unlockKey } from '$services';
import { generateKeys, unlockKey } from '$services';
import { DEVICE_KEY, LOCAL, PASSWORD, SETUP_KEY } from '$shared/const';
import { isSetupComplete, storageService } from '$shared/services';
import { EncryptedDeviceKeySchema, HashSaltSchema } from '$shared/types';
import { deviceKeyContentStore, passphraseStore } from '$stores';
import { deviceKeyContentStore, keysStore, passphraseStore } from '$stores';

export function createSetupDeviceKeyQuery() {
return createQuery({
Expand Down Expand Up @@ -37,6 +37,17 @@ export function createStoreDeviceKey(queryClient: QueryClient) {
});
}

export function createGenerateKeys() {
return createMutation({
mutationFn: async (mutationData: { passphrase: string; password: string }) => {
// Promise that resolves automatically to prevent the window from freezing before the mutation enters the loading state
await new Promise((resolve) => setTimeout(resolve, 100));
const keys = await generateKeys(mutationData.passphrase, mutationData.password);
keysStore.set(keys);
}
});
}

export function createRecoverDeviceKeyMutation() {
return createMutation({
mutationFn: async (mutationData: { deviceKey: string; passphrase: string }) => {
Expand Down
5 changes: 4 additions & 1 deletion holo-key-manager-extension/src/lib/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createSignInWithKeyMutation
} from './applicationQueries';
import {
createGenerateKeys,
createRecoverDeviceKeyMutation,
createSetupDeviceKeyQuery,
createStoreDeviceKey
Expand All @@ -32,6 +33,7 @@ export function appQueries() {
const applicationsListQuery = createApplicationsListQuery();
const storeDeviceKey = createStoreDeviceKey(queryClient);
const recoverDeviceKeyMutation = createRecoverDeviceKeyMutation();
const generateKeysMutation = createGenerateKeys();
const applicationKeyMutation = createApplicationKeyMutation(queryClient);
const passwordAndStoreDeviceKeyMutation = createPasswordAndStoreDeviceKeyMutation(queryClient);

Expand All @@ -47,6 +49,7 @@ export function appQueries() {
applicationKeyMutation,
recoverDeviceKeyMutation,
passwordAndStoreDeviceKeyMutation,
signInWithKeyMutation
signInWithKeyMutation,
generateKeysMutation
};
}
23 changes: 8 additions & 15 deletions holo-key-manager-extension/src/lib/services/manage-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@ import {
import { base64ToUint8Array, uint8ArrayToBase64 } from '$shared/helpers';
import type { GeneratedKeys } from '$types';

const createSeedCipherPwHash = (password: string) =>
new SeedCipherPwHash(parseSecret(new TextEncoder().encode(password)), 'moderate');

const lock = (root: UnlockedSeedBundle, password: string) =>
root.lock([new SeedCipherPwHash(parseSecret(new TextEncoder().encode(password)), 'minimum')]);
root.lock([createSeedCipherPwHash(password)]);

const deriveAndLock = (
master: UnlockedSeedBundle,
derivationPath: number,
bundleType: string,
passphrase: string
) => {
const root = master.derive(derivationPath, {
bundle_type: bundleType
});

const root = master.derive(derivationPath, { bundle_type: bundleType });
const encodedBytes = lock(root, passphrase);
root.zero();

return encodedBytes;
};

Expand All @@ -34,11 +33,9 @@ export async function generateKeys(
): Promise<GeneratedKeys> {
await seedBundleReady;

const master = UnlockedSeedBundle.newRandom({
bundle_type: 'master'
});
const master = UnlockedSeedBundle.newRandom({ bundle_type: 'master' });

const encodedMasterBytes: Uint8Array = lock(master, passphrase);
const encodedMasterBytes = lock(master, passphrase);
const encodedRevocationBytes = deriveAndLock(master, 0, 'revocationRoot', passphrase);
const encodedDeviceBytes = deriveAndLock(master, 1, 'deviceRoot', passphrase);
const encodedDeviceBytesWithExtensionPassword = deriveAndLock(
Expand All @@ -60,12 +57,8 @@ export async function generateKeys(

export const lockKey = async (key: UnlockedSeedBundle, password: string) => {
await seedBundleReady;

const pw = new TextEncoder().encode(password);
const encodedBytes = key.lock([new SeedCipherPwHash(parseSecret(pw), 'moderate')]);

const encodedBytes = lock(key, password);
key.zero();

return uint8ArrayToBase64(encodedBytes);
};

Expand Down
26 changes: 7 additions & 19 deletions holo-key-manager-extension/src/lib/stores/keys-store.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { writable } from 'svelte/store';

import { generateKeys } from '$services';
import type { KeysState } from '$types';
import type { GeneratedKeys } from '$types';

const initKeysStore = () => {
const initialState: KeysState = {
keys: {
encodedMaster: null,
encodedDeviceWithExtensionPassword: null,
encodedDevice: null,
encodedRevocation: null
},
loading: false
const initialState: GeneratedKeys = {
encodedMaster: null,
encodedDeviceWithExtensionPassword: null,
encodedDevice: null,
encodedRevocation: null
};

const { subscribe, set, update } = writable(initialState);
Expand All @@ -20,15 +16,7 @@ const initKeysStore = () => {
isInitialState: () =>
subscribe((state) => JSON.stringify(state) === JSON.stringify(initialState)),
subscribe,
generate: async (passphrase: string, password: string) => {
update((state) => ({ ...state, loading: true }));
try {
const keys = await generateKeys(passphrase, password);
set({ keys, loading: false });
} catch (error) {
set(initialState);
}
},
set,
resetAll: () => update(() => initialState)
};
};
Expand Down
5 changes: 0 additions & 5 deletions holo-key-manager-extension/src/lib/types/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ export type GeneratedKeys = {
encodedRevocation: Uint8Array | null;
};

export type KeysState = {
keys: GeneratedKeys;
loading: boolean;
};

const requiredString = (fieldName: string) => z.string().min(1, `${fieldName} is required`);
const nonNegativeNumber = (fieldName: string) =>
z.number().nonnegative(`${fieldName} must be a non-negative number`);
Expand Down
Loading